2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com
.intellij
.ide
.util
.gotoByName
;
19 import com
.intellij
.Patches
;
20 import com
.intellij
.ide
.IdeBundle
;
21 import com
.intellij
.ide
.actions
.CopyReferenceAction
;
22 import com
.intellij
.ide
.ui
.UISettings
;
23 import com
.intellij
.openapi
.actionSystem
.*;
24 import com
.intellij
.openapi
.application
.ApplicationManager
;
25 import com
.intellij
.openapi
.application
.ModalityState
;
26 import com
.intellij
.openapi
.diagnostic
.Logger
;
27 import com
.intellij
.openapi
.editor
.colors
.EditorColorsManager
;
28 import com
.intellij
.openapi
.editor
.colors
.EditorColorsScheme
;
29 import com
.intellij
.openapi
.keymap
.KeymapManager
;
30 import com
.intellij
.openapi
.progress
.ProcessCanceledException
;
31 import com
.intellij
.openapi
.project
.Project
;
32 import com
.intellij
.openapi
.ui
.popup
.JBPopup
;
33 import com
.intellij
.openapi
.ui
.popup
.JBPopupFactory
;
34 import com
.intellij
.openapi
.util
.ActionCallback
;
35 import com
.intellij
.openapi
.util
.Comparing
;
36 import com
.intellij
.openapi
.util
.Pair
;
37 import com
.intellij
.openapi
.util
.registry
.Registry
;
38 import com
.intellij
.openapi
.util
.text
.StringUtil
;
39 import com
.intellij
.openapi
.wm
.IdeFocusManager
;
40 import com
.intellij
.openapi
.wm
.WindowManager
;
41 import com
.intellij
.openapi
.wm
.ex
.WindowManagerEx
;
42 import com
.intellij
.psi
.PsiElement
;
43 import com
.intellij
.psi
.codeStyle
.NameUtil
;
44 import com
.intellij
.psi
.statistics
.StatisticsInfo
;
45 import com
.intellij
.psi
.statistics
.StatisticsManager
;
46 import com
.intellij
.psi
.util
.proximity
.PsiProximityComparator
;
47 import com
.intellij
.ui
.DocumentAdapter
;
48 import com
.intellij
.ui
.ListScrollingUtil
;
49 import com
.intellij
.ui
.popup
.PopupOwner
;
50 import com
.intellij
.ui
.popup
.PopupUpdateProcessor
;
51 import com
.intellij
.util
.Alarm
;
52 import com
.intellij
.util
.Function
;
53 import com
.intellij
.util
.SmartList
;
54 import com
.intellij
.util
.containers
.ContainerUtil
;
55 import com
.intellij
.util
.diff
.Diff
;
56 import com
.intellij
.util
.ui
.UIUtil
;
57 import org
.jetbrains
.annotations
.NonNls
;
58 import org
.jetbrains
.annotations
.Nullable
;
61 import javax
.swing
.event
.DocumentEvent
;
62 import javax
.swing
.event
.ListSelectionEvent
;
63 import javax
.swing
.event
.ListSelectionListener
;
64 import javax
.swing
.text
.DefaultEditorKit
;
66 import java
.awt
.event
.*;
67 import java
.lang
.ref
.Reference
;
68 import java
.lang
.ref
.WeakReference
;
70 import java
.util
.List
;
72 public abstract class ChooseByNameBase
{
73 private static final Logger LOG
= Logger
.getInstance("#com.intellij.ide.util.gotoByName.ChooseByNameBase");
75 protected final Project myProject
;
76 protected final ChooseByNameModel myModel
;
77 protected final String myInitialText
;
78 private final Reference
<PsiElement
> myContext
;
80 protected Component myPreviouslyFocusedComponent
;
82 protected JPanelProvider myTextFieldPanel
;// Located in the layered pane
83 protected JTextField myTextField
;
84 private JPanel myCardContainer
;
85 private CardLayout myCard
;
86 protected JCheckBox myCheckBox
;
87 /** the tool area of the popup, it is just after card box */
88 protected JComponent myToolArea
;
90 protected JScrollPane myListScrollPane
; // Located in the layered pane
91 protected JList myList
;
92 private DefaultListModel myListModel
;
93 private List
<Pair
<String
, Integer
>> myHistory
;
94 private List
<Pair
<String
, Integer
>> myFuture
;
96 protected ChooseByNamePopupComponent
.Callback myActionListener
;
98 protected final Alarm myAlarm
= new Alarm();
100 private final ListUpdater myListUpdater
= new ListUpdater();
102 private volatile boolean myListIsUpToDate
= false;
103 protected boolean myDisposedFlag
= false;
104 private ActionCallback myPosponedOkAction
;
106 private final String
[][] myNames
= new String
[2][];
107 private CalcElementsThread myCalcElementsThread
;
108 private static int VISIBLE_LIST_SIZE_LIMIT
= 10;
109 private static final int MAXIMUM_LIST_SIZE_LIMIT
= 30;
110 private int myMaximumListSizeLimit
= MAXIMUM_LIST_SIZE_LIMIT
;
111 @NonNls private static final String NOT_FOUND_IN_PROJECT_CARD
= "syslib";
112 @NonNls private static final String NOT_FOUND_CARD
= "nfound";
113 @NonNls private static final String CHECK_BOX_CARD
= "chkbox";
114 @NonNls private static final String SEARCHING_CARD
= "searching";
115 private static final int REBUILD_DELAY
= 300;
117 private final Alarm myHideAlarm
= new Alarm();
119 private static class MatchesComparator
implements Comparator
<String
> {
120 private final String myOriginalPattern
;
122 private MatchesComparator(final String originalPattern
) {
123 myOriginalPattern
= originalPattern
.trim();
126 public int compare(final String a
, final String b
) {
127 boolean aStarts
= a
.startsWith(myOriginalPattern
);
128 boolean bStarts
= b
.startsWith(myOriginalPattern
);
129 if (aStarts
&& bStarts
) return a
.compareToIgnoreCase(b
);
130 if (aStarts
&& !bStarts
) return -1;
131 if (bStarts
&& !aStarts
) return 1;
132 return a
.compareToIgnoreCase(b
);
137 * @param initialText initial text which will be in the lookup text field
140 protected ChooseByNameBase(Project project
, ChooseByNameModel model
, String initialText
, final PsiElement context
) {
143 myInitialText
= initialText
;
144 myContext
= new WeakReference
<PsiElement
>(context
);
148 * Set tool area. The method may be called only before invoke.
149 * @param toolArea a tool area component
151 public void setToolArea(JComponent toolArea
) {
153 throw new IllegalStateException("Tool area is modifiable only before invoke()");
155 myToolArea
= toolArea
;
158 public void invoke(final ChooseByNamePopupComponent
.Callback callback
, final ModalityState modalityState
, boolean allowMultipleSelection
) {
159 initUI(callback
, modalityState
, allowMultipleSelection
);
162 public class JPanelProvider
extends JPanel
implements DataProvider
{
163 JBPopup myHint
= null;
164 boolean myFocusRequested
= false;
169 public Object
getData(String dataId
) {
170 if (PlatformDataKeys
.HELP_ID
.is(dataId
)) {
171 return myModel
.getHelpId();
173 if (!myListIsUpToDate
) {
176 if (LangDataKeys
.PSI_ELEMENT
.is(dataId
)) {
177 Object element
= getChosenElement();
179 if (element
instanceof PsiElement
) {
183 if (element
instanceof DataProvider
) {
184 return ((DataProvider
)element
).getData(dataId
);
187 else if (LangDataKeys
.PSI_ELEMENT_ARRAY
.is(dataId
)) {
188 final List
<Object
> chosenElements
= getChosenElements();
189 if (chosenElements
!= null) {
190 List
<PsiElement
> result
= new ArrayList
<PsiElement
>();
191 for (Object element
: chosenElements
) {
192 if (element
instanceof PsiElement
) {
193 result
.add((PsiElement
)element
);
196 return result
.toArray(new PsiElement
[result
.size()]);
199 else if (PlatformDataKeys
.DOMINANT_HINT_AREA_RECTANGLE
.is(dataId
)) {
205 public void registerHint(JBPopup h
) {
206 if (myHint
!= null && myHint
.isVisible() && myHint
!= h
){
212 public boolean focusRequested() {
213 boolean focusRequested
= myFocusRequested
;
215 myFocusRequested
= false;
217 return focusRequested
;
220 public void requestFocus() {
221 myFocusRequested
= true;
224 public void unregisterHint() {
228 public void hideHint() {
229 if (myHint
!= null) {
234 public JBPopup
getHint() {
238 public void updateHint(PsiElement element
) {
239 if (myHint
== null || !myHint
.isVisible()) return;
240 final PopupUpdateProcessor updateProcessor
= myHint
.getUserData(PopupUpdateProcessor
.class);
241 if (updateProcessor
!= null){
243 updateProcessor
.updatePopup(element
);
250 * @param modalityState - if not null rebuilds list in given {@link ModalityState}
251 * @param allowMultipleSelection
253 protected void initUI(final ChooseByNamePopupComponent
.Callback callback
, final ModalityState modalityState
, boolean allowMultipleSelection
) {
254 myPreviouslyFocusedComponent
= WindowManagerEx
.getInstanceEx().getFocusedComponent(myProject
);
256 myActionListener
= callback
;
257 myTextFieldPanel
= new JPanelProvider();
258 myTextFieldPanel
.setLayout(new BoxLayout(myTextFieldPanel
, BoxLayout
.Y_AXIS
));
259 final JPanel hBox
= new JPanel();
260 hBox
.setLayout(new BoxLayout(hBox
, BoxLayout
.X_AXIS
));
262 if (myModel
.getPromptText() != null) {
263 JLabel label
= new JLabel(" " + myModel
.getPromptText());
264 label
.setFont(UIUtil
.getLabelFont().deriveFont(Font
.BOLD
));
268 myCard
= new CardLayout();
269 myCardContainer
= new JPanel(myCard
);
271 final JPanel checkBoxPanel
= new JPanel();
272 checkBoxPanel
.setBorder(BorderFactory
.createEmptyBorder(0, 0, 0, 5));
273 myCheckBox
= new JCheckBox(myModel
.getCheckBoxName());
274 myCheckBox
.setSelected(myModel
.loadInitialCheckBoxState());
276 if (myModel
.getPromptText() != null){
277 checkBoxPanel
.setLayout(new BoxLayout(checkBoxPanel
, BoxLayout
.X_AXIS
));
278 checkBoxPanel
.add (new JLabel (" ("));
279 checkBoxPanel
.add (myCheckBox
);
280 checkBoxPanel
.add (new JLabel (")"));
282 checkBoxPanel
.setLayout(new BoxLayout(checkBoxPanel
, BoxLayout
.LINE_AXIS
));
283 checkBoxPanel
.setComponentOrientation(ComponentOrientation
.RIGHT_TO_LEFT
);
284 checkBoxPanel
.add (new JLabel (")"));
285 checkBoxPanel
.add (myCheckBox
);
286 checkBoxPanel
.add (new JLabel (" ("));
288 checkBoxPanel
.setVisible(myModel
.getCheckBoxName() != null);
289 JPanel panel
= new JPanel(new BorderLayout());
290 panel
.add(checkBoxPanel
, BorderLayout
.CENTER
);
291 myCardContainer
.add(panel
, CHECK_BOX_CARD
);
292 myCardContainer
.add(new JLabel(" (" + myModel
.getNotInMessage() + ")"), NOT_FOUND_IN_PROJECT_CARD
);
293 myCardContainer
.add(new JLabel(" " + IdeBundle
.message("label.choosebyname.no.matches.found")), NOT_FOUND_CARD
);
294 myCardContainer
.add(new JLabel(" " + IdeBundle
.message("label.choosebyname.searching")), SEARCHING_CARD
);
295 myCard
.show(myCardContainer
, CHECK_BOX_CARD
);
297 //myCaseCheckBox = new JCheckBox("Ignore case");
298 //myCaseCheckBox.setMnemonic('g');
299 //myCaseCheckBox.setSelected(true);
301 //myCamelCheckBox = new JCheckBox("Camel words");
302 //myCamelCheckBox.setMnemonic('w');
303 //myCamelCheckBox.setSelected(true);
305 if (isCheckboxVisible()) {
306 hBox
.add(myCardContainer
);
307 //hBox.add(myCheckBox);
308 //hBox.add(myCaseCheckBox);
309 //hBox.add(myCamelCheckBox);
311 if(myToolArea
!= null) {
312 // if too area was set, add it to hbox
313 hBox
.add(myToolArea
);
315 myTextFieldPanel
.add(hBox
);
317 myHistory
= new ArrayList
<Pair
<String
, Integer
>>();
318 myFuture
= new ArrayList
<Pair
<String
, Integer
>>();
319 myTextField
= new MyTextField();
320 myTextField
.setText(myInitialText
);
322 final ActionMap actionMap
= new ActionMap();
323 actionMap
.setParent(myTextField
.getActionMap());
324 actionMap
.put(DefaultEditorKit
.copyAction
, new AbstractAction() {
325 public void actionPerformed(ActionEvent e
) {
326 if (myTextField
.getSelectedText() != null) {
327 actionMap
.getParent().get(DefaultEditorKit
.copyAction
).actionPerformed(e
);
330 final Object chosenElement
= getChosenElement();
331 if (chosenElement
instanceof PsiElement
) {
332 CopyReferenceAction
.doCopy((PsiElement
)chosenElement
, myProject
);
336 myTextField
.setActionMap(actionMap
);
338 myTextFieldPanel
.add(myTextField
);
339 EditorColorsScheme scheme
= EditorColorsManager
.getInstance().getGlobalScheme();
340 Font editorFont
= new Font(scheme
.getEditorFontName(), Font
.PLAIN
, scheme
.getEditorFontSize());
341 myTextField
.setFont(editorFont
);
343 if (isCloseByFocusLost()) {
344 myTextField
.addFocusListener(new FocusAdapter() {
345 public void focusLost(final FocusEvent e
) {
346 myHideAlarm
.addRequest(new Runnable() {
348 if (!JBPopupFactory
.getInstance().isChildPopupFocused(e
.getComponent())) {
357 myCheckBox
.addItemListener(new ItemListener() {
358 public void itemStateChanged(ItemEvent e
) {
362 myCheckBox
.setFocusable(false);
364 myTextField
.getDocument().addDocumentListener(new DocumentAdapter() {
365 protected void textChanged(DocumentEvent e
) {
366 clearPosponedOkAction(false);
371 myTextField
.addKeyListener(new KeyAdapter() {
372 public void keyPressed(KeyEvent e
) {
373 if (!myListScrollPane
.isVisible()) {
376 final int keyCode
= e
.getKeyCode();
378 case KeyEvent
.VK_DOWN
:
379 ListScrollingUtil
.moveDown(myList
, e
.getModifiersEx());
382 ListScrollingUtil
.moveUp(myList
, e
.getModifiersEx());
384 case KeyEvent
.VK_PAGE_UP
:
385 ListScrollingUtil
.movePageUp(myList
);
387 case KeyEvent
.VK_PAGE_DOWN
:
388 ListScrollingUtil
.movePageDown(myList
);
390 case KeyEvent
.VK_ENTER
:
391 if (myList
.getSelectedValue() == EXTRA_ELEM
) {
392 myMaximumListSizeLimit
+= MAXIMUM_LIST_SIZE_LIMIT
;
393 rebuildList(myList
.getSelectedIndex(), REBUILD_DELAY
, null, ModalityState
.current());
401 myTextField
.addActionListener(new ActionListener() {
402 public void actionPerformed(ActionEvent actionEvent
) {
407 myListModel
= new DefaultListModel();
408 myList
= new JList(myListModel
);
409 myList
.setFocusable(false);
410 myList
.setSelectionMode(allowMultipleSelection ? ListSelectionModel
.MULTIPLE_INTERVAL_SELECTION
:
411 ListSelectionModel
.SINGLE_SELECTION
);
412 myList
.addMouseListener(new MouseAdapter() {
413 public void mouseClicked(MouseEvent e
) {
414 if (!myTextField
.hasFocus()) {
415 myTextField
.requestFocus();
418 if (e
.getClickCount() == 2) {
419 if (myList
.getSelectedValue() == EXTRA_ELEM
) {
420 myMaximumListSizeLimit
+= MAXIMUM_LIST_SIZE_LIMIT
;
421 rebuildList(myList
.getSelectedIndex(), REBUILD_DELAY
, null, ModalityState
.current());
430 myList
.setCellRenderer(myModel
.getListCellRenderer());
431 myList
.setFont(editorFont
);
433 myList
.addListSelectionListener(new ListSelectionListener() {
434 public void valueChanged(ListSelectionEvent e
) {
435 choosenElementMightChange();
436 updateDocumentation();
440 myListScrollPane
= new JScrollPane(myList
);
442 if (!UIUtil
.isMotifLookAndFeel()) {
443 UIUtil
.installPopupMenuBorder(myTextFieldPanel
);
445 UIUtil
.installPopupMenuColorAndFonts(myTextFieldPanel
);
447 showTextFieldPanel();
449 if (modalityState
!= null) {
450 rebuildList(0, 0, null, modalityState
);
454 private void hideHint() {
455 if (!myTextFieldPanel
.focusRequested()) {
457 myTextFieldPanel
.hideHint();
462 * Default rebuild list. It uses {@link #REBUILD_DELAY} and current modality state.
464 public void rebuildList() {
465 // TODO this method is public, because the chooser does not listed for the model.
466 rebuildList(0, REBUILD_DELAY
, null, ModalityState
.current());
469 private void updateDocumentation() {
470 final JBPopup hint
= myTextFieldPanel
.getHint();
471 final Object element
= getChosenElement();
473 if (element
instanceof PsiElement
) {
474 myTextFieldPanel
.updateHint((PsiElement
)element
);
475 } else if (element
instanceof DataProvider
) {
476 final Object o
= ((DataProvider
)element
).getData(LangDataKeys
.PSI_ELEMENT
.getName());
477 if (o
instanceof PsiElement
) {
478 myTextFieldPanel
.updateHint((PsiElement
)o
);
484 private void doClose(final boolean ok
) {
485 if (myDisposedFlag
) return;
487 if (posponeCloseWhenListReady(ok
)) return;
492 clearPosponedOkAction(ok
);
495 protected void cancelListUpdater() {
496 myListUpdater
.cancelAll();
499 private boolean posponeCloseWhenListReady(boolean ok
) {
500 if (!Registry
.is("actionSystem.fixLostTyping")) return false;
502 final String text
= myTextField
.getText();
503 if (ok
&& !myListIsUpToDate
&& text
!= null && text
.trim().length() > 0) {
504 myPosponedOkAction
= new ActionCallback();
505 IdeFocusManager
.getInstance(myProject
).suspendKeyProcessingUntil(myPosponedOkAction
);
512 private synchronized void ensureNamesLoaded(boolean checkboxState
) {
513 int index
= checkboxState ?
1 : 0;
514 if (myNames
[index
] != null) return;
516 Window window
= (Window
)SwingUtilities
.getAncestorOfClass(Window
.class, myTextField
);
517 //LOG.assertTrue (myTextField != null);
518 //LOG.assertTrue (window != null);
519 Window ownerWindow
= null;
520 if (window
!= null) {
521 window
.setCursor(Cursor
.getPredefinedCursor(Cursor
.WAIT_CURSOR
));
522 ownerWindow
= window
.getOwner();
523 if (ownerWindow
!= null) {
524 ownerWindow
.setCursor(Cursor
.getPredefinedCursor(Cursor
.WAIT_CURSOR
));
527 myNames
[index
] = myModel
.getNames(checkboxState
);
529 if (window
!= null) {
530 window
.setCursor(Cursor
.getDefaultCursor());
531 if (ownerWindow
!= null) {
532 ownerWindow
.setCursor(Cursor
.getDefaultCursor());
537 protected abstract boolean isCheckboxVisible();
539 protected abstract boolean isShowListForEmptyPattern();
541 protected abstract boolean isCloseByFocusLost();
543 protected void showTextFieldPanel() {
544 final JLayeredPane layeredPane
= getLayeredPane();
545 final Dimension preferredTextFieldPanelSize
= myTextFieldPanel
.getPreferredSize();
546 final int x
= (layeredPane
.getWidth() - preferredTextFieldPanelSize
.width
) / 2;
547 final int paneHeight
= layeredPane
.getHeight();
548 final int y
= paneHeight
/ 3 - preferredTextFieldPanelSize
.height
/ 2;
551 myTextFieldPanel
.setBounds(x
, y
, preferredTextFieldPanelSize
.width
, preferredTextFieldPanelSize
.height
);
552 layeredPane
.add(myTextFieldPanel
, Integer
.valueOf(500));
553 layeredPane
.moveToFront(myTextFieldPanel
);
554 VISIBLE_LIST_SIZE_LIMIT
= Math
.max
555 (10, (paneHeight
- (y
+ preferredTextFieldPanelSize
.height
)) / (preferredTextFieldPanelSize
.height
/ 2) - 1);
557 // I'm registering KeyListener to close popup only by KeyTyped event.
558 // If react on KeyPressed then sometime KeyTyped goes into underlying editor.
559 // It causes typing of Enter into it.
560 myTextFieldPanel
.registerKeyboardAction(new AbstractAction() {
561 public void actionPerformed(ActionEvent e
) {
564 }, KeyStroke
.getKeyStroke(KeyEvent
.VK_ESCAPE
, 0),
565 JComponent
.WHEN_IN_FOCUSED_WINDOW
568 myList
.registerKeyboardAction(new AbstractAction() {
569 public void actionPerformed(ActionEvent e
) {
572 }, KeyStroke
.getKeyStroke(KeyEvent
.VK_ESCAPE
, 0),
573 JComponent
.WHEN_IN_FOCUSED_WINDOW
576 IdeFocusManager
.getInstance(myProject
).requestFocus(myTextField
, true);
578 myTextFieldPanel
.validate();
579 myTextFieldPanel
.paintImmediately(0, 0, myTextFieldPanel
.getWidth(), myTextFieldPanel
.getHeight());
582 private JLayeredPane
getLayeredPane() {
583 JLayeredPane layeredPane
;
584 final Window window
= WindowManager
.getInstance().suggestParentWindow(myProject
);
585 if (window
instanceof JFrame
) {
586 layeredPane
= ((JFrame
)window
).getLayeredPane();
588 else if (window
instanceof JDialog
) {
589 layeredPane
= ((JDialog
)window
).getLayeredPane();
592 throw new IllegalStateException("cannot find parent window: project=" + myProject
+
593 (myProject
!= null ?
"; open=" + myProject
.isOpen() : "") +
594 "; window=" + window
);
599 private final Object myRebuildMutex
= new Object ();
601 protected void rebuildList(final int pos
, final int delay
, final Runnable postRunnable
, final ModalityState modalityState
) {
602 ApplicationManager
.getApplication().assertIsDispatchThread();
603 myListIsUpToDate
= false;
604 myAlarm
.cancelAllRequests();
605 myListUpdater
.cancelAll();
607 cancelCalcElementsThread();
608 ApplicationManager
.getApplication().invokeLater(new Runnable() {
610 final String text
= myTextField
.getText();
611 if (!isShowListForEmptyPattern() && (text
== null || text
.trim().length() == 0)) {
614 myCard
.show(myCardContainer
, CHECK_BOX_CARD
);
617 final Runnable request
= new Runnable() {
619 final CalcElementsCallback callback
= new CalcElementsCallback() {
620 public void run(final Set
<?
> elements
) {
621 synchronized (myRebuildMutex
) {
622 ApplicationManager
.getApplication().assertIsDispatchThread();
623 if (myDisposedFlag
) {
627 setElementsToList(pos
, elements
);
629 myListIsUpToDate
= true;
630 choosenElementMightChange();
632 if (postRunnable
!= null) {
639 cancelCalcElementsThread();
641 myCalcElementsThread
= new CalcElementsThread(text
, myCheckBox
.isSelected(), callback
, modalityState
, postRunnable
== null);
642 ApplicationManager
.getApplication().executeOnPooledThread(myCalcElementsThread
);
647 myAlarm
.addRequest(request
, delay
, ModalityState
.stateForComponent(myTextField
));
656 private void cancelCalcElementsThread() {
657 if (myCalcElementsThread
!= null) {
658 myCalcElementsThread
.cancel();
659 myCalcElementsThread
= null;
663 private void setElementsToList(int pos
, Set
<?
> elements
) {
664 myListUpdater
.cancelAll();
665 if (myDisposedFlag
) return;
666 if (elements
.isEmpty()) {
668 myTextField
.setForeground(Color
.red
);
669 myListUpdater
.cancelAll();
671 clearPosponedOkAction(false);
675 Object
[] oldElements
= myListModel
.toArray();
676 Object
[] newElements
= elements
.toArray();
677 Diff
.Change change
= Diff
.buildChanges(oldElements
, newElements
);
679 if (change
== null) return; // Nothing changed
681 List
<Cmd
> commands
= new ArrayList
<Cmd
>();
684 while (change
!= null) {
685 if (change
.deleted
> 0) {
686 final int start
= change
.line0
+ inserted
- deleted
;
687 commands
.add(new RemoveCmd(start
, start
+ change
.deleted
- 1));
690 if (change
.inserted
> 0) {
691 for (int i
= 0; i
< change
.inserted
; i
++) {
692 commands
.add(new InsertCmd(change
.line0
+ i
+ inserted
- deleted
, newElements
[change
.line1
+ i
]));
696 deleted
+= change
.deleted
;
697 inserted
+= change
.inserted
;
698 change
= change
.link
;
701 myTextField
.setForeground(UIUtil
.getTextFieldForeground());
702 if (!commands
.isEmpty()) {
704 myListUpdater
.appendToModel(commands
, pos
);
708 pos
= detectBestStatisticalPosition();
711 ListScrollingUtil
.selectItem(myList
, Math
.min(pos
, myListModel
.size() - 1));
712 myList
.setVisibleRowCount(Math
.min(VISIBLE_LIST_SIZE_LIMIT
, myList
.getModel().getSize()));
717 private int detectBestStatisticalPosition() {
719 int bestPosition
= 0;
720 final int count
= myListModel
.getSize();
722 final String statContext
= statisticsContext();
723 for (int i
= 0; i
< count
; i
++) {
724 final Object modelElement
= myListModel
.getElementAt(i
);
725 String text
= EXTRA_ELEM
.equals(modelElement
) ?
null : myModel
.getFullName(modelElement
);
727 int stats
= StatisticsManager
.getInstance().getUseCount(new StatisticsInfo(statContext
, text
));
739 protected String
statisticsContext() {
740 return "choose_by_name#"+myModel
.getPromptText()+"#"+ myCheckBox
.isSelected() + "#" + myTextField
.getText();
743 private String
getQualifierPattern(String pattern
) {
744 final String
[] separators
= myModel
.getSeparators();
745 int lastSeparatorOccurence
= 0;
746 for (String separator
: separators
) {
747 lastSeparatorOccurence
= Math
.max(lastSeparatorOccurence
, pattern
.lastIndexOf(separator
));
749 return pattern
.substring(0, lastSeparatorOccurence
);
752 public String
getNamePattern(String pattern
) {
753 final String
[] separators
= myModel
.getSeparators();
754 int lastSeparatorOccurence
= 0;
755 for (String separator
: separators
) {
756 final int idx
= pattern
.lastIndexOf(separator
);
757 lastSeparatorOccurence
= Math
.max(lastSeparatorOccurence
, idx
== -1 ? idx
: idx
+ separator
.length());
760 return pattern
.substring(lastSeparatorOccurence
);
763 private interface Cmd
{
767 private class RemoveCmd
implements Cmd
{
768 private final int start
;
769 private final int end
;
771 private RemoveCmd(final int start
, final int end
) {
776 public void apply() {
777 myListModel
.removeRange(start
, end
);
781 private class InsertCmd
implements Cmd
{
782 private final int idx
;
783 private final Object element
;
785 private InsertCmd(final int idx
, final Object element
) {
787 this.element
= element
;
790 public void apply() {
791 if (idx
< myListModel
.size()) {
792 myListModel
.add(idx
, element
);
795 myListModel
.addElement(element
);
800 private class ListUpdater
{
801 private final Alarm myAlarm
= new Alarm(Alarm
.ThreadToUse
.SWING_THREAD
);
802 private static final int DELAY
= 10;
803 private static final int MAX_BLOCKING_TIME
= 30;
804 private final List
<Cmd
> myCommands
= Collections
.synchronizedList(new ArrayList
<Cmd
>());
806 public void cancelAll() {
808 myAlarm
.cancelAllRequests();
811 public void appendToModel(final List
<Cmd
> commands
, final int selectionPos
) {
812 myAlarm
.cancelAllRequests();
813 myCommands
.addAll(commands
);
815 if (myCommands
.isEmpty() || myDisposedFlag
) return;
816 myAlarm
.addRequest(new Runnable() {
818 if (myDisposedFlag
) return;
819 final long startTime
= System
.currentTimeMillis();
820 while (!myCommands
.isEmpty() && System
.currentTimeMillis() - startTime
< MAX_BLOCKING_TIME
) {
821 final Cmd cmd
= myCommands
.remove(0);
825 myList
.setVisibleRowCount(Math
.min(VISIBLE_LIST_SIZE_LIMIT
, myList
.getModel().getSize()));
826 if (!myListModel
.isEmpty()) {
827 int pos
= selectionPos
== 0 ?
detectBestStatisticalPosition() : selectionPos
;
828 ListScrollingUtil
.selectItem(myList
, Math
.min(pos
, myListModel
.size() - 1));
831 if (!myCommands
.isEmpty()) {
832 myAlarm
.addRequest(this, DELAY
);
834 doPostponedOkIfNeeded();
836 if (!myDisposedFlag
) {
843 private void doPostponedOkIfNeeded() {
844 if (myPosponedOkAction
!= null) {
845 if (getChosenElement() != null) {
847 clearPosponedOkAction(myDisposedFlag
);
853 private void clearPosponedOkAction(boolean success
) {
854 if (myPosponedOkAction
!= null) {
856 myPosponedOkAction
.setDone();
858 myPosponedOkAction
.setRejected();
862 myPosponedOkAction
= null;
865 protected abstract void showList();
867 protected abstract void hideList();
869 protected abstract void close(boolean isOk
);
872 public Object
getChosenElement() {
873 final List
<Object
> elements
= getChosenElements();
874 return elements
!= null && elements
.size() == 1 ? elements
.get(0) : null;
877 protected List
<Object
> getChosenElements() {
878 if (myListIsUpToDate
) {
879 List
<Object
> values
= new ArrayList
<Object
>(Arrays
.asList(myList
.getSelectedValues()));
880 values
.remove(EXTRA_ELEM
);
884 final String text
= myTextField
.getText();
885 final boolean checkBoxState
= myCheckBox
.isSelected();
886 //ensureNamesLoaded(checkBoxState);
887 final String
[] names
= checkBoxState ? myNames
[1] : myNames
[0];
888 if (names
== null) return Collections
.emptyList();
890 Object uniqueElement
= null;
892 for (final String name
: names
) {
893 if (text
.equalsIgnoreCase(name
)) {
894 final Object
[] elements
= myModel
.getElementsByName(name
, checkBoxState
, text
);
895 if (elements
.length
> 1) return Collections
.emptyList();
896 if (elements
.length
== 0) continue;
897 if (uniqueElement
!= null) return Collections
.emptyList();
898 uniqueElement
= elements
[0];
901 return uniqueElement
== null ? Collections
.emptyList() : Collections
.singletonList(uniqueElement
);
904 protected void choosenElementMightChange() {
907 private final class MyTextField
extends JTextField
implements PopupOwner
{
908 private final KeyStroke myCompletionKeyStroke
;
909 private final KeyStroke forwardStroke
;
910 private final KeyStroke backStroke
;
912 private MyTextField() {
914 enableEvents(AWTEvent
.KEY_EVENT_MASK
);
915 myCompletionKeyStroke
= getShortcut(IdeActions
.ACTION_CODE_COMPLETION
);
916 forwardStroke
= getShortcut(IdeActions
.ACTION_GOTO_FORWARD
);
917 backStroke
= getShortcut(IdeActions
.ACTION_GOTO_BACK
);
921 private KeyStroke
getShortcut(String actionCodeCompletion
) {
922 final Shortcut
[] shortcuts
= KeymapManager
.getInstance().getActiveKeymap().getShortcuts(actionCodeCompletion
);
923 for (final Shortcut shortcut
: shortcuts
) {
924 if (shortcut
instanceof KeyboardShortcut
) {
925 return ((KeyboardShortcut
)shortcut
).getFirstKeyStroke();
931 protected void processKeyEvent(KeyEvent e
) {
932 final KeyStroke keyStroke
= KeyStroke
.getKeyStrokeForEvent(e
);
933 if (myCompletionKeyStroke
!= null && keyStroke
.equals(myCompletionKeyStroke
)) {
935 final String pattern
= myTextField
.getText();
936 final String oldText
= myTextField
.getText();
937 final int oldPos
= myList
.getSelectedIndex();
938 myHistory
.add(Pair
.create(oldText
, oldPos
));
939 final Runnable postRunnable
= new Runnable() {
941 fillInCommonPrefix(pattern
);
944 rebuildList(0, 0, postRunnable
, ModalityState
.current());
947 if (backStroke
!= null && keyStroke
.equals(backStroke
)) {
949 if (!myHistory
.isEmpty()) {
950 final String oldText
= myTextField
.getText();
951 final int oldPos
= myList
.getSelectedIndex();
952 final Pair
<String
, Integer
> last
= myHistory
.remove(myHistory
.size() - 1);
953 myTextField
.setText(last
.first
);
954 myFuture
.add(Pair
.create(oldText
, oldPos
));
955 rebuildList(0, 0, null, ModalityState
.current());
959 if (forwardStroke
!= null && keyStroke
.equals(forwardStroke
)) {
961 if (!myFuture
.isEmpty()) {
962 final String oldText
= myTextField
.getText();
963 final int oldPos
= myList
.getSelectedIndex();
964 final Pair
<String
, Integer
> next
= myFuture
.remove(myFuture
.size() - 1);
965 myTextField
.setText(next
.first
);
966 myHistory
.add(Pair
.create(oldText
, oldPos
));
967 rebuildList(0, 0, null, ModalityState
.current());
972 super.processKeyEvent(e
);
974 catch (NullPointerException e1
) {
975 if (!Patches
.SUN_BUG_6322854
) {
981 private void fillInCommonPrefix(final String pattern
) {
982 final ArrayList
<String
> list
= new ArrayList
<String
>();
983 getNamesByPattern(myCheckBox
.isSelected(), null, list
, pattern
);
985 if (isComplexPattern(pattern
)) return; //TODO: support '*'
986 final String oldText
= myTextField
.getText();
987 final int oldPos
= myList
.getSelectedIndex();
989 String commonPrefix
= null;
990 if (!list
.isEmpty()) {
991 for (String name
: list
) {
992 final String string
= name
.toLowerCase();
993 if (commonPrefix
== null) {
994 commonPrefix
= string
;
997 while (commonPrefix
.length() > 0) {
998 if (string
.startsWith(commonPrefix
)) {
1001 commonPrefix
= commonPrefix
.substring(0, commonPrefix
.length() - 1);
1003 if (commonPrefix
.length() == 0) break;
1006 commonPrefix
= list
.get(0).substring(0, commonPrefix
.length());
1007 for (int i
= 1; i
< list
.size(); i
++) {
1008 final String string
= list
.get(i
).substring(0, commonPrefix
.length());
1009 if (!string
.equals(commonPrefix
)) {
1010 commonPrefix
= commonPrefix
.toLowerCase();
1015 if (commonPrefix
== null) commonPrefix
= "";
1016 final String newPattern
= commonPrefix
;
1018 myHistory
.add(Pair
.create(oldText
, oldPos
));
1019 myTextField
.setText(newPattern
);
1020 myTextField
.setCaretPosition(newPattern
.length());
1025 private boolean isComplexPattern(final String pattern
) {
1026 if (pattern
.indexOf('*') >= 0) return true;
1027 for (String s
: myModel
.getSeparators()) {
1028 if (pattern
.contains(s
)) return true;
1035 public Point
getBestPopupPosition() {
1036 return new Point(myTextFieldPanel
.getWidth(), getHeight());
1039 protected void paintComponent(final Graphics g
) {
1040 UISettings
.setupAntialiasing(g
);
1041 super.paintComponent(g
);
1045 private static final String EXTRA_ELEM
= "...";
1047 private class CalcElementsThread
implements Runnable
{
1048 private final String myPattern
;
1049 private boolean myCheckboxState
;
1050 private final CalcElementsCallback myCallback
;
1051 private final ModalityState myModalityState
;
1053 private Set
<Object
> myElements
= null;
1055 private volatile boolean myCancelled
= false;
1056 private final boolean myCanCancel
;
1058 private CalcElementsThread(String pattern
, boolean checkboxState
, CalcElementsCallback callback
, ModalityState modalityState
, boolean canCancel
) {
1059 myPattern
= pattern
;
1060 myCheckboxState
= checkboxState
;
1061 myCallback
= callback
;
1062 myModalityState
= modalityState
;
1063 myCanCancel
= canCancel
;
1066 private final Alarm myShowCardAlarm
= new Alarm();
1068 showCard(SEARCHING_CARD
, 200);
1070 final Set
<Object
> elements
= new LinkedHashSet
<Object
>();
1071 Runnable action
= new Runnable() {
1074 ensureNamesLoaded(myCheckboxState
);
1075 addElementsByPattern(elements
, myPattern
);
1076 for (Object elem
: elements
) {
1080 if (elem
instanceof PsiElement
) {
1081 final PsiElement psiElement
= (PsiElement
)elem
;
1082 psiElement
.isWritable(); // That will cache writable flag in VirtualFile. Taking the action here makes it canceleable.
1086 catch (ProcessCanceledException e
) {
1091 ApplicationManager
.getApplication().runReadAction(action
);
1094 myShowCardAlarm
.cancelAllRequests();
1098 final String cardToShow
;
1099 if (elements
.isEmpty() && !myCheckboxState
) {
1100 myCheckboxState
= true;
1101 ApplicationManager
.getApplication().runReadAction(action
);
1102 cardToShow
= elements
.isEmpty() ? NOT_FOUND_CARD
: NOT_FOUND_IN_PROJECT_CARD
;
1105 cardToShow
= elements
.isEmpty() ? NOT_FOUND_CARD
: CHECK_BOX_CARD
;
1107 showCard(cardToShow
, 0);
1109 myElements
= elements
;
1111 ApplicationManager
.getApplication().invokeLater(new Runnable() {
1113 myCallback
.run(myElements
);
1115 }, myModalityState
);
1118 private void showCard(final String card
, final int delay
) {
1119 myShowCardAlarm
.cancelAllRequests();
1120 myShowCardAlarm
.addRequest(new Runnable() {
1122 myCard
.show(myCardContainer
, card
);
1124 }, delay
, myModalityState
);
1127 private void addElementsByPattern(Set
<Object
> elementsArray
, String pattern
) {
1128 String namePattern
= getNamePattern(pattern
);
1129 String qualifierPattern
= getQualifierPattern(pattern
);
1131 boolean empty
= namePattern
.length() == 0 || namePattern
.equals("@"); // TODO[yole]: remove implicit dependency
1132 if (empty
&& !isShowListForEmptyPattern()) return;
1134 List
<String
> namesList
= new ArrayList
<String
>();
1135 getNamesByPattern(myCheckboxState
, this, namesList
, namePattern
);
1137 throw new ProcessCanceledException();
1139 Collections
.sort(namesList
, new MatchesComparator(pattern
));
1141 boolean overflow
= false;
1142 List
<Object
> sameNameElements
= new SmartList
<Object
>();
1144 for (String name
: namesList
) {
1146 throw new ProcessCanceledException();
1148 final Object
[] elements
= myModel
.getElementsByName(name
, myCheckboxState
, namePattern
);
1149 if (elements
.length
> 1) {
1150 sameNameElements
.clear();
1151 for (final Object element
: elements
) {
1152 if (matchesQualifier(element
, qualifierPattern
)) {
1153 sameNameElements
.add(element
);
1156 sortByProximity(sameNameElements
);
1157 for (Object element
: sameNameElements
) {
1158 elementsArray
.add(element
);
1159 if (elementsArray
.size() >= myMaximumListSizeLimit
) {
1165 else if (elements
.length
== 1 && matchesQualifier(elements
[0], qualifierPattern
)) {
1166 elementsArray
.add(elements
[0]);
1167 if (elementsArray
.size() >= myMaximumListSizeLimit
) {
1175 elementsArray
.add(EXTRA_ELEM
);
1179 private void cancel() {
1186 private void sortByProximity(final List
<Object
> sameNameElements
) {
1187 Collections
.sort(sameNameElements
, new PathProximityComparator(myModel
, myContext
.get()));
1190 private List
<String
> split(String s
) {
1191 List
<String
> answer
= new ArrayList
<String
>();
1192 for (String token
: StringUtil
.tokenize(s
, StringUtil
.join(myModel
.getSeparators(), ""))) {
1193 if (token
.length() > 0) {
1198 return answer
.isEmpty() ? Collections
.singletonList(s
) : answer
;
1201 private boolean matchesQualifier(final Object element
, final String qualifierPattern
) {
1202 final String name
= myModel
.getFullName(element
);
1203 if (name
== null) return false;
1205 final List
<String
> suspects
= split(name
);
1206 final List
<Pair
<String
, NameUtil
.Matcher
>> patternsAndMatchers
= ContainerUtil
.map2List(split(qualifierPattern
), new Function
<String
, Pair
<String
, NameUtil
.Matcher
>>() {
1207 public Pair
<String
, NameUtil
.Matcher
> fun(String s
) {
1208 final String pattern
= getNamePattern(s
);
1209 final NameUtil
.Matcher matcher
= buildPatternMatcher(pattern
);
1211 return new Pair
<String
, NameUtil
.Matcher
>(pattern
, matcher
);
1215 int matchPosition
= 0;
1219 for (Pair
<String
, NameUtil
.Matcher
> patternAndMatcher
: patternsAndMatchers
) {
1220 final String pattern
= patternAndMatcher
.first
;
1221 final NameUtil
.Matcher matcher
= patternAndMatcher
.second
;
1222 if (pattern
.length() > 0) {
1223 for (int j
= matchPosition
; j
< suspects
.size() - 1; j
++) {
1224 String suspect
= suspects
.get(j
);
1225 if (matches(pattern
, matcher
, suspect
)) {
1226 matchPosition
= j
+ 1;
1234 } catch (Exception e
) {
1235 // Do nothing. No matches appears valid result for "bad" pattern
1242 private void getNamesByPattern(final boolean checkboxState
,
1243 CalcElementsThread calcElementsThread
,
1244 final List
<String
> list
,
1245 String pattern
) throws ProcessCanceledException
{
1246 if (!isShowListForEmptyPattern()) {
1247 LOG
.assertTrue(pattern
.length() > 0);
1250 if (pattern
.startsWith("@")) {
1251 pattern
= pattern
.substring(1);
1254 final String
[] names
= checkboxState ? myNames
[1] : myNames
[0];
1255 final NameUtil
.Matcher matcher
= buildPatternMatcher(pattern
);
1258 for (String name
: names
) {
1259 if (calcElementsThread
!= null && calcElementsThread
.myCancelled
) {
1262 if (matches(pattern
, matcher
, name
)) {
1267 catch (Exception e
) {
1268 // Do nothing. No matches appears valid result for "bad" pattern
1272 private boolean matches(String pattern
, NameUtil
.Matcher matcher
, String name
) {
1273 boolean matches
= false;
1275 if (myModel
instanceof CustomMatcherModel
) {
1276 if (((CustomMatcherModel
)myModel
).matches(name
, pattern
)) {
1280 else if (pattern
.length() == 0 || matcher
.matches(name
)) {
1287 private NameUtil
.Matcher
buildPatternMatcher(String pattern
) {
1288 return NameUtil
.buildMatcher(pattern
, 0, true, true, pattern
.toLowerCase().equals(pattern
));
1291 private interface CalcElementsCallback
{
1292 void run(Set
<?
> elements
);
1295 private static class PathProximityComparator
implements Comparator
<Object
> {
1296 private final ChooseByNameModel myModel
;
1297 private final PsiProximityComparator myProximityComparator
;
1299 private PathProximityComparator(final ChooseByNameModel model
, final PsiElement context
) {
1301 myProximityComparator
= new PsiProximityComparator(context
);
1304 public int compare(final Object o1
, final Object o2
) {
1305 int rc
= myProximityComparator
.compare(o1
, o2
);
1306 if (rc
!= 0) return rc
;
1308 return Comparing
.compare(myModel
.getFullName(o1
), myModel
.getFullName(o2
));