| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.openapi.fileChooser.ex; |
| |
| import com.intellij.codeInsight.hint.HintUtil; |
| import com.intellij.ide.IdeBundle; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.actionSystem.ActionManager; |
| import com.intellij.openapi.actionSystem.IdeActions; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.fileChooser.FileTextField; |
| import com.intellij.openapi.keymap.Keymap; |
| import com.intellij.openapi.keymap.KeymapManager; |
| import com.intellij.openapi.keymap.KeymapUtil; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.popup.*; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.Disposer; |
| import com.intellij.openapi.util.SystemInfo; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.codeStyle.MinusculeMatcher; |
| import com.intellij.psi.codeStyle.NameUtil; |
| import com.intellij.ui.ListScrollingUtil; |
| import com.intellij.ui.components.JBList; |
| import com.intellij.ui.popup.list.GroupedItemsListRenderer; |
| import com.intellij.util.ui.update.LazyUiDisposable; |
| import com.intellij.util.ui.update.MergingUpdateQueue; |
| import com.intellij.util.ui.update.UiNotifyConnector; |
| import com.intellij.util.ui.update.Update; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.event.DocumentEvent; |
| import javax.swing.event.DocumentListener; |
| import javax.swing.text.BadLocationException; |
| import javax.swing.text.Document; |
| import javax.swing.text.JTextComponent; |
| import java.awt.*; |
| import java.awt.event.*; |
| import java.io.File; |
| import java.util.*; |
| import java.util.List; |
| |
| public abstract class FileTextFieldImpl implements FileLookup, Disposable, FileTextField { |
| |
| private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileChooser.ex.FileTextFieldImpl"); |
| |
| |
| private JTextField myPathTextField; |
| |
| private CompletionResult myCurrentCompletion; |
| |
| private JBPopup myCurrentPopup; |
| private JBPopup myNoSuggestionsPopup; |
| |
| private JList myList; |
| |
| private MergingUpdateQueue myUiUpdater; |
| |
| private boolean myPathIsUpdating; |
| private Finder myFinder; |
| private LookupFilter myFilter; |
| private String myCompletionBase; |
| |
| private int myCurrentCompletionsPos = 1; |
| private String myFileSpitRegExp; |
| |
| private boolean myAutopopup = false; |
| private FileTextFieldImpl.CancelAction myCancelAction; |
| private Set<Action> myDisabledTextActions; |
| private Map<String, String> myMacroMap; |
| |
| public FileTextFieldImpl(Finder finder, LookupFilter filter, Map<String, String> macroMap) { |
| this(new JTextField(), finder, filter, macroMap, null); |
| } |
| |
| public FileTextFieldImpl(final JTextField field, Finder finder, LookupFilter filter, Map<String, String> macroMap, final Disposable parent) { |
| myPathTextField = field; |
| myMacroMap = new TreeMap<String, String>(); |
| myMacroMap.putAll(macroMap); |
| |
| |
| final InputMap listMap = (InputMap)UIManager.getDefaults().get("List.focusInputMap"); |
| final KeyStroke[] listKeys = listMap.keys(); |
| myDisabledTextActions = new HashSet<Action>(); |
| for (KeyStroke eachListStroke : listKeys) { |
| final String listActionID = (String)listMap.get(eachListStroke); |
| if ("selectNextRow".equals(listActionID) || "selectPreviousRow".equals(listActionID)) { |
| final Object textActionID = field.getInputMap().get(eachListStroke); |
| if (textActionID != null) { |
| final Action textAction = field.getActionMap().get(textActionID); |
| if (textAction != null) { |
| myDisabledTextActions.add(textAction); |
| } |
| } |
| } |
| } |
| |
| |
| final FileTextFieldImpl assigned = (FileTextFieldImpl)myPathTextField.getClientProperty(KEY); |
| if (assigned != null) { |
| assigned.myFinder = finder; |
| assigned.myFilter = filter; |
| return; |
| } |
| |
| myPathTextField.putClientProperty(KEY, this); |
| final boolean headless = ApplicationManager.getApplication().isUnitTestMode(); |
| |
| myUiUpdater = new MergingUpdateQueue("FileTextField.UiUpdater", 200, false, myPathTextField); |
| if (!headless) { |
| new UiNotifyConnector(myPathTextField, myUiUpdater); |
| } |
| |
| myFinder = finder; |
| myFilter = filter; |
| |
| myFileSpitRegExp = myFinder.getSeparator().replaceAll("\\\\", "\\\\\\\\"); |
| |
| myPathTextField.getDocument().addDocumentListener(new DocumentListener() { |
| public void insertUpdate(final DocumentEvent e) { |
| processTextChanged(); |
| } |
| |
| public void removeUpdate(final DocumentEvent e) { |
| processTextChanged(); |
| } |
| |
| public void changedUpdate(final DocumentEvent e) { |
| processTextChanged(); |
| } |
| }); |
| |
| myPathTextField.addKeyListener(new KeyAdapter() { |
| public void keyPressed(final KeyEvent e) { |
| processListSelection(e); |
| } |
| }); |
| |
| myPathTextField.addFocusListener(new FocusAdapter() { |
| public void focusLost(final FocusEvent e) { |
| closePopup(); |
| } |
| }); |
| |
| myCancelAction = new CancelAction(); |
| |
| |
| new LazyUiDisposable<FileTextFieldImpl>(parent, field, this) { |
| protected void initialize(@NotNull Disposable parent, @NotNull FileTextFieldImpl child, @Nullable Project project) { |
| Disposer.register(child, myUiUpdater); |
| } |
| }; |
| } |
| |
| public void dispose() { |
| } |
| |
| private void processTextChanged() { |
| if (myAutopopup && !isPathUpdating()) { |
| suggestCompletion(false, false); |
| } |
| |
| onTextChanged(getTextFieldText()); |
| } |
| |
| protected void onTextChanged(final String newValue) { |
| } |
| |
| private void suggestCompletion(final boolean selectReplacedText, final boolean isExplicitCall) { |
| if (isExplicitCall) { |
| myAutopopup = true; |
| } |
| |
| if (!getField().isFocusOwner()) return; |
| |
| final CompletionResult result = new CompletionResult(); |
| if (myList != null && myCurrentCompletion != null) { |
| int index = myList.getSelectedIndex(); |
| if (index >= 0 && index < myList.getModel().getSize()) { |
| result.myPreselected = (LookupFile)myList.getSelectedValue(); |
| } |
| } |
| |
| myUiUpdater.queue(new Update("textField.suggestCompletion") { |
| public void run() { |
| final String completionBase = getCompletionBase(); |
| if (completionBase != null) { |
| final LookupFile file = myFinder.find(completionBase); |
| if (file != null && file.exists() && !file.isDirectory()) { |
| // we've entered a complete path already, no need to autopopup completion again (IDEA-78996) |
| return; |
| } |
| } |
| result.myCompletionBase = completionBase; |
| if (result.myCompletionBase == null) return; |
| result.myFieldText = myPathTextField.getText(); |
| ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { |
| public void run() { |
| processCompletion(result); |
| SwingUtilities.invokeLater(new Runnable() { |
| public void run() { |
| if (!result.myCompletionBase.equals(getCompletionBase())) return; |
| |
| int pos = selectCompletionRemoveText(result, selectReplacedText); |
| |
| showCompletionPopup(result, pos, isExplicitCall); |
| } |
| }); |
| } |
| }); |
| } |
| }); |
| |
| } |
| |
| private int selectCompletionRemoveText(final CompletionResult result, boolean selectReplacedText) { |
| int pos = myPathTextField.getCaretPosition(); |
| |
| if (result.myToComplete.size() > 0 && selectReplacedText) { |
| myPathTextField.setCaretPosition(myPathTextField.getText().length()); |
| myPathTextField.moveCaretPosition(pos); |
| } |
| |
| return pos; |
| } |
| |
| @Nullable |
| public String getAdText(CompletionResult result) { |
| if (result.myCompletionBase == null) return null; |
| if (result.myCompletionBase.length() == result.myFieldText.length()) return null; |
| |
| String strokeText = KeymapUtil.getFirstKeyboardShortcutText(ActionManager.getInstance().getAction( |
| "EditorChooseLookupItemReplace")); |
| return IdeBundle.message("file.chooser.completion.ad.text", strokeText); |
| } |
| |
| public static class CompletionResult { |
| public List<LookupFile> myMacros; |
| public List<LookupFile> myToComplete; |
| public List<LookupFile> mySiblings; |
| public List<LookupFile> myKidsAfterSeparator; |
| public String myCompletionBase; |
| public LookupFile myClosestParent; |
| public LookupFile myPreselected; |
| public LookupFile current; |
| public boolean currentParentMatch; |
| public String effectivePrefix; |
| public LookupFile currentGrandparent; |
| public String grandparentPrefix; |
| public boolean closedPath; |
| public String myFieldText; |
| } |
| |
| private static class Separator { |
| private final String myText; |
| |
| private Separator(final String text) { |
| myText = text; |
| } |
| |
| public String getText() { |
| return myText; |
| } |
| } |
| |
| private void showCompletionPopup(final CompletionResult result, int position, boolean isExplicit) { |
| if (myList == null) { |
| myList = new JBList(); |
| myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); |
| |
| myList.setCellRenderer(new GroupedItemsListRenderer(new ListItemDescriptorAdapter() { |
| public String getTextFor(final Object value) { |
| final LookupFile file = (LookupFile)value; |
| |
| if (file.getMacro() != null) { |
| return file.getMacro(); |
| } else { |
| return (myCurrentCompletion != null && myCurrentCompletion.myKidsAfterSeparator.contains(file) ? myFinder.getSeparator() : "") + |
| file.getName(); |
| } |
| |
| } |
| |
| public Icon getIconFor(final Object value) { |
| final LookupFile file = (LookupFile)value; |
| return file.getIcon(); |
| } |
| |
| @Nullable |
| private Separator getSeparatorAboveOf(Object value) { |
| if (myCurrentCompletion == null) return null; |
| final LookupFile file = (LookupFile)value; |
| |
| final int fileIndex = myCurrentCompletion.myToComplete.indexOf(file); |
| if (fileIndex > 0 && !myCurrentCompletion.myMacros.contains(file)) { |
| final LookupFile prev = myCurrentCompletion.myToComplete.get(fileIndex - 1); |
| if (myCurrentCompletion.myMacros.contains(prev)) { |
| return new Separator(""); |
| } |
| } |
| |
| |
| if (myCurrentCompletion.myKidsAfterSeparator.indexOf(file) == 0 && myCurrentCompletion.mySiblings.size() > 0) { |
| final LookupFile parent = file.getParent(); |
| return parent == null ? new Separator("") : new Separator(parent.getName()); |
| } |
| |
| if (myCurrentCompletion.myMacros.size() > 0 && fileIndex == 0) { |
| return new Separator(IdeBundle.message("file.chooser.completion.path.variables.text")); |
| } |
| |
| return null; |
| } |
| |
| public boolean hasSeparatorAboveOf(final Object value) { |
| return getSeparatorAboveOf(value) != null; |
| } |
| |
| public String getCaptionAboveOf(final Object value) { |
| final FileTextFieldImpl.Separator separator = getSeparatorAboveOf(value); |
| return separator != null ? separator.getText() : null; |
| } |
| })); |
| } |
| |
| |
| if (myCurrentPopup != null) { |
| closePopup(); |
| } |
| |
| myCurrentCompletion = result; |
| myCurrentCompletionsPos = position; |
| |
| if (myCurrentCompletion.myToComplete.size() == 0) { |
| showNoSuggestions(isExplicit); |
| return; |
| } |
| |
| myList.setModel(new AbstractListModel() { |
| public int getSize() { |
| return myCurrentCompletion.myToComplete.size(); |
| } |
| |
| public Object getElementAt(final int index) { |
| return myCurrentCompletion.myToComplete.get(index); |
| } |
| }); |
| myList.getSelectionModel().clearSelection(); |
| final PopupChooserBuilder builder = JBPopupFactory.getInstance().createListPopupBuilder(myList); |
| builder.addListener(new JBPopupListener() { |
| public void beforeShown(LightweightWindowEvent event) { |
| myPathTextField |
| .registerKeyboardAction(myCancelAction, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); |
| for (Action each : myDisabledTextActions) { |
| each.setEnabled(false); |
| } |
| } |
| |
| public void onClosed(LightweightWindowEvent event) { |
| myPathTextField.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0)); |
| for (Action each : myDisabledTextActions) { |
| each.setEnabled(true); |
| } |
| } |
| }); |
| |
| myCurrentPopup = |
| builder.setRequestFocus(false).setAdText(getAdText(myCurrentCompletion)).setAutoSelectIfEmpty(false).setResizable(false).setCancelCallback(new Computable<Boolean>() { |
| public Boolean compute() { |
| final int caret = myPathTextField.getCaretPosition(); |
| myPathTextField.setSelectionStart(caret); |
| myPathTextField.setSelectionEnd(caret); |
| myPathTextField.setFocusTraversalKeysEnabled(true); |
| SwingUtilities.invokeLater(new Runnable() { |
| public void run() { |
| getField().requestFocus(); |
| } |
| }); |
| return Boolean.TRUE; |
| } |
| }).setItemChoosenCallback(new Runnable() { |
| public void run() { |
| processChosenFromCompletion(false); |
| } |
| }).setCancelKeyEnabled(false).setAlpha(0.1f).setFocusOwners(new Component[]{myPathTextField}). |
| createPopup(); |
| |
| |
| if (result.myPreselected != null) { |
| myList.setSelectedValue(result.myPreselected, false); |
| } |
| |
| myPathTextField.setFocusTraversalKeysEnabled(false); |
| |
| myCurrentPopup.showInScreenCoordinates(getField(), getLocationForCaret(myPathTextField)); |
| } |
| |
| private void showNoSuggestions(boolean isExplicit) { |
| hideCurrentPopup(); |
| |
| if (!isExplicit) return; |
| |
| final JComponent message = HintUtil.createErrorLabel(IdeBundle.message("file.chooser.completion.no.suggestions")); |
| final ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(message, message); |
| builder.setRequestFocus(false).setResizable(false).setAlpha(0.1f).setFocusOwners(new Component[] {myPathTextField}); |
| myNoSuggestionsPopup = builder.createPopup(); |
| myNoSuggestionsPopup.showInScreenCoordinates(getField(), getLocationForCaret(myPathTextField)); |
| } |
| |
| private void hideCurrentPopup() { |
| if (myCurrentPopup != null) { |
| myCurrentPopup.cancel(); |
| myCurrentPopup = null; |
| } |
| |
| if (myNoSuggestionsPopup != null) { |
| myNoSuggestionsPopup.cancel(); |
| myNoSuggestionsPopup = null; |
| } |
| } |
| |
| public static Point getLocationForCaret(JTextComponent pathTextField) { |
| Point point; |
| |
| int position = pathTextField.getCaretPosition(); |
| try { |
| final Rectangle rec = pathTextField.modelToView(position); |
| point = new Point((int)rec.getMaxX(), (int)rec.getMaxY()); |
| } |
| catch (BadLocationException e) { |
| point = pathTextField.getCaret().getMagicCaretPosition(); |
| } |
| |
| SwingUtilities.convertPointToScreen(point, pathTextField); |
| |
| point.y += 2; |
| |
| return point; |
| } |
| |
| public void processCompletion(final CompletionResult result) { |
| result.myToComplete = new ArrayList<LookupFile>(); |
| result.mySiblings = new ArrayList<LookupFile>(); |
| result.myKidsAfterSeparator = new ArrayList<LookupFile>(); |
| final String typed = result.myCompletionBase; |
| |
| if (typed == null) return; |
| |
| addMacroPaths(result, typed); |
| |
| final String typedText = myFinder.normalize(typed); |
| |
| |
| result.current = getClosestParent(typed); |
| result.myClosestParent = result.current; |
| |
| if (result.current != null) { |
| result.currentParentMatch = SystemInfo.isFileSystemCaseSensitive |
| ? typedText.equals(result.current.getAbsolutePath()) |
| : typedText.equalsIgnoreCase(result.current.getAbsolutePath()); |
| |
| result.closedPath = typed.endsWith(myFinder.getSeparator()) && typedText.length() > myFinder.getSeparator().length(); |
| final String currentParentText = result.current.getAbsolutePath(); |
| |
| if (!StringUtil.toUpperCase(typedText).startsWith(StringUtil.toUpperCase(currentParentText))) return; |
| |
| String prefix = typedText.substring(currentParentText.length()); |
| if (prefix.startsWith(myFinder.getSeparator())) { |
| prefix = prefix.substring(myFinder.getSeparator().length()); |
| } |
| else if (typed.endsWith(myFinder.getSeparator())) { |
| prefix = ""; |
| } |
| |
| result.effectivePrefix = prefix; |
| |
| result.currentGrandparent = result.current.getParent(); |
| if (result.currentGrandparent != null && result.currentParentMatch && !result.closedPath) { |
| final String currentGrandparentText = result.currentGrandparent.getAbsolutePath(); |
| if (StringUtil.startsWithConcatenation(typedText, currentGrandparentText, myFinder.getSeparator())) { |
| result.grandparentPrefix = currentParentText.substring(currentGrandparentText.length() + myFinder.getSeparator().length()); |
| } |
| } |
| } else { |
| result.effectivePrefix = typedText; |
| } |
| |
| |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| public void run() { |
| if (result.current != null) { |
| result.myToComplete.addAll(getMatchingChildren(result.effectivePrefix, result.current)); |
| |
| if (result.currentParentMatch && !result.closedPath && !typed.isEmpty()) { |
| result.myKidsAfterSeparator.addAll(result.myToComplete); |
| } |
| |
| if (result.grandparentPrefix != null) { |
| final List<LookupFile> siblings = getMatchingChildren(result.grandparentPrefix, result.currentGrandparent); |
| result.myToComplete.addAll(0, siblings); |
| result.mySiblings.addAll(siblings); |
| } |
| } |
| |
| int currentDiff = Integer.MIN_VALUE; |
| LookupFile toPreselect = result.myPreselected; |
| |
| if (toPreselect == null || !result.myToComplete.contains(toPreselect)) { |
| boolean toPreselectFixed = false; |
| if (result.effectivePrefix.length() > 0) { |
| for (LookupFile each : result.myToComplete) { |
| String eachName = StringUtil.toUpperCase(each.getName()); |
| if (!eachName.startsWith(result.effectivePrefix)) continue; |
| int diff = result.effectivePrefix.compareTo(eachName); |
| currentDiff = Math.max(diff, currentDiff); |
| if (currentDiff == diff) { |
| toPreselect = each; |
| toPreselectFixed = true; |
| break; |
| } |
| } |
| |
| if (!toPreselectFixed) { |
| toPreselect = null; |
| } |
| } |
| else { |
| toPreselect = null; |
| } |
| |
| if (toPreselect == null) { |
| if (result.myToComplete.size() == 1) { |
| toPreselect = result.myToComplete.get(0); |
| } |
| else if (result.effectivePrefix.length() == 0) { |
| if (result.mySiblings.size() > 0) { |
| toPreselect = result.mySiblings.get(0); |
| } |
| } |
| |
| if (toPreselect == null && !result.myToComplete.contains(toPreselect) && result.myToComplete.size() > 0) { |
| toPreselect = result.myToComplete.get(0); |
| } |
| } |
| } |
| |
| if (result.currentParentMatch && result.mySiblings.size() > 0) { |
| toPreselect = null; |
| } |
| |
| result.myPreselected = toPreselect; |
| } |
| |
| private List<LookupFile> getMatchingChildren(String prefix, LookupFile parent) { |
| final MinusculeMatcher matcher = createMatcher(prefix); |
| return parent.getChildren(new LookupFilter() { |
| public boolean isAccepted(final LookupFile file) { |
| return !file.equals(result.current) && myFilter.isAccepted(file) && matcher.matches(file.getName()); |
| } |
| }); |
| } |
| }); |
| } |
| |
| private static MinusculeMatcher createMatcher(String prefix) { |
| return NameUtil.buildMatcher("*" + prefix, NameUtil.MatchingCaseSensitivity.NONE); |
| } |
| |
| private void addMacroPaths(final CompletionResult result, final String typedText) { |
| result.myMacros = new ArrayList<LookupFile>(); |
| |
| MinusculeMatcher matcher = createMatcher(typedText); |
| |
| for (String eachMacro : myMacroMap.keySet()) { |
| if (matcher.matches(eachMacro)) { |
| final String eachPath = myMacroMap.get(eachMacro); |
| if (eachPath != null) { |
| final LookupFile macroFile = myFinder.find(eachPath); |
| if (macroFile != null && macroFile.exists()) { |
| result.myMacros.add(macroFile); |
| result.myToComplete.add(macroFile); |
| macroFile.setMacro(eachMacro); |
| } |
| } |
| } |
| } |
| } |
| |
| @Nullable |
| private LookupFile getClosestParent(final String typed) { |
| if (typed == null) return null; |
| LookupFile lastFound = myFinder.find(typed); |
| if (lastFound == null) return null; |
| if (typed.isEmpty()) return lastFound; |
| if (lastFound.exists()) { |
| if (typed.charAt(typed.length() - 1) != File.separatorChar) return lastFound.getParent(); |
| return lastFound; |
| } |
| |
| final String[] splits = myFinder.normalize(typed).split(myFileSpitRegExp); |
| StringBuilder fullPath = new StringBuilder(); |
| for (int i = 0; i < splits.length; i++) { |
| String each = splits[i]; |
| fullPath.append(each); |
| if (i < splits.length - 1) { |
| fullPath.append(myFinder.getSeparator()); |
| } |
| final LookupFile file = myFinder.find(fullPath.toString()); |
| if (file == null || !file.exists()) return lastFound; |
| lastFound = file; |
| } |
| |
| return lastFound; |
| } |
| |
| @Nullable |
| public LookupFile getFile() { |
| String text = getTextFieldText(); |
| if (text == null) return null; |
| return myFinder.find(text); |
| } |
| |
| private void processChosenFromCompletion(boolean nameOnly) { |
| final LookupFile file = getSelectedFileFromCompletionPopup(); |
| if (file == null) return; |
| |
| if (nameOnly) { |
| try { |
| final Document doc = myPathTextField.getDocument(); |
| int caretPos = myPathTextField.getCaretPosition(); |
| if (myFinder.getSeparator().equals(doc.getText(caretPos, 1))) { |
| for (;caretPos < doc.getLength(); caretPos++) { |
| final String eachChar = doc.getText(caretPos, 1); |
| if (!myFinder.getSeparator().equals(eachChar)) break; |
| } |
| } |
| |
| int start = caretPos > 0 ? caretPos - 1 : caretPos; |
| while(start >= 0) { |
| final String each = doc.getText(start, 1); |
| if (myFinder.getSeparator().equals(each)) { |
| start++; |
| break; |
| } |
| start--; |
| } |
| |
| int end = start < caretPos ? caretPos : start; |
| while(end <= doc.getLength()) { |
| final String each = doc.getText(end, 1); |
| if (myFinder.getSeparator().equals(each)) { |
| break; |
| } |
| end++; |
| } |
| |
| if (end > doc.getLength()) { |
| end = doc.getLength(); |
| } |
| |
| if (start > end || start < 0 || end > doc.getLength()) { |
| setTextToFile(file); |
| } else { |
| replacePathComponent(file, caretPos, start, end); |
| } |
| } |
| catch (BadLocationException e) { |
| LOG.error(e); |
| } |
| } else { |
| setTextToFile(file); |
| } |
| } |
| |
| /** |
| * Replace the path component under the caret with the file selected from the completion list. |
| * |
| * @param file the selected file. |
| * @param caretPos |
| * @param start the start offset of the path component under the caret. |
| * @param end the end offset of the path component under the caret. |
| * @throws BadLocationException |
| */ |
| private void replacePathComponent(LookupFile file, int caretPos, int start, int end) throws BadLocationException { |
| final Document doc = myPathTextField.getDocument(); |
| |
| myPathTextField.setSelectionStart(0); |
| myPathTextField.setSelectionEnd(0); |
| |
| final String name = file.getName(); |
| boolean toRemoveExistingName; |
| String prefix = ""; |
| |
| if (caretPos >= start) { |
| prefix = doc.getText(start, caretPos - start); |
| if (prefix.length() == 0) { |
| prefix = doc.getText(start, end - start); |
| } |
| if (SystemInfo.isFileSystemCaseSensitive) { |
| toRemoveExistingName = name.startsWith(prefix) && prefix.length() > 0; |
| } else { |
| toRemoveExistingName = StringUtil.toUpperCase(name).startsWith(StringUtil.toUpperCase(prefix)) && prefix.length() > 0; |
| } |
| } else { |
| toRemoveExistingName = true; |
| } |
| |
| int newPos; |
| if (toRemoveExistingName) { |
| doc.remove(start, end - start); |
| doc.insertString(start, name, doc.getDefaultRootElement().getAttributes()); |
| newPos = start + name.length(); |
| } else { |
| doc.insertString(caretPos, name, doc.getDefaultRootElement().getAttributes()); |
| newPos = caretPos + name.length(); |
| } |
| |
| if (file.isDirectory()) { |
| if (!myFinder.getSeparator().equals(doc.getText(newPos, 1))) { |
| doc.insertString(newPos, myFinder.getSeparator(), doc.getDefaultRootElement().getAttributes()); |
| newPos++; |
| } |
| } |
| |
| if (newPos < doc.getLength()) { |
| if (myFinder.getSeparator().equals(doc.getText(newPos, 1))) { |
| newPos++; |
| } |
| } |
| myPathTextField.setCaretPosition(newPos); |
| } |
| |
| private void setTextToFile(final LookupFile file) { |
| String text = file.getAbsolutePath(); |
| if (file.isDirectory() && !text.endsWith(myFinder.getSeparator())) { |
| text += myFinder.getSeparator(); |
| } |
| myPathTextField.setText(text); |
| } |
| |
| @SuppressWarnings("HardCodedStringLiteral") |
| private void processListSelection(final KeyEvent e) { |
| if (togglePopup(e)) return; |
| |
| if (!isPopupShowing()) return; |
| |
| final InputMap map = myPathTextField.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); |
| if (map != null) { |
| final Object object = map.get(KeyStroke.getKeyStrokeForEvent(e)); |
| if (object instanceof Action) { |
| final Action action = (Action)object; |
| if (action.isEnabled()) { |
| action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "action")); |
| e.consume(); |
| return; |
| } |
| } |
| } |
| |
| final Object action = getAction(e, myList); |
| |
| if ("selectNextRow".equals(action)) { |
| if (ensureSelectionExists()) { |
| ListScrollingUtil.moveDown(myList, e.getModifiersEx()); |
| e.consume(); |
| } |
| } |
| else if ("selectPreviousRow".equals(action)) { |
| ListScrollingUtil.moveUp(myList, e.getModifiersEx()); |
| e.consume(); |
| } |
| else if ("scrollDown".equals(action)) { |
| ListScrollingUtil.movePageDown(myList); |
| e.consume(); |
| } |
| else if ("scrollUp".equals(action)) { |
| ListScrollingUtil.movePageUp(myList); |
| e.consume(); |
| } |
| else if (getSelectedFileFromCompletionPopup() != null && (e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode() == KeyEvent.VK_TAB) && e.getModifiers() == 0) { |
| hideCurrentPopup(); |
| e.consume(); |
| processChosenFromCompletion(e.getKeyCode() == KeyEvent.VK_TAB); |
| } |
| } |
| |
| private |
| @Nullable |
| LookupFile getSelectedFileFromCompletionPopup() { |
| if (myList == null) return null; |
| return (LookupFile)myList.getSelectedValue(); |
| } |
| |
| private boolean ensureSelectionExists() { |
| if (myList.getSelectedIndex() < 0 || myList.getSelectedIndex() >= myList.getModel().getSize()) { |
| if (myList.getModel().getSize() >= 0) { |
| myList.setSelectedIndex(0); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| @SuppressWarnings("HardCodedStringLiteral") |
| private boolean togglePopup(KeyEvent e) { |
| final KeyStroke stroke = KeyStroke.getKeyStroke(e.getKeyCode(), e.getModifiers()); |
| final Object action = ((InputMap)UIManager.get("ComboBox.ancestorInputMap")).get(stroke); |
| if ("selectNext".equals(action)) { |
| if (!isPopupShowing()) { |
| return true; |
| } |
| else { |
| return false; |
| } |
| } |
| else if ("togglePopup".equals(action)) { |
| if (isPopupShowing()) { |
| closePopup(); |
| } |
| else { |
| suggestCompletion(true, true); |
| } |
| return true; |
| } |
| else { |
| final Keymap active = KeymapManager.getInstance().getActiveKeymap(); |
| final String[] ids = active.getActionIds(stroke); |
| if (ids.length > 0 && IdeActions.ACTION_CODE_COMPLETION.equals(ids[0])) { |
| suggestCompletion(true, true); |
| } |
| } |
| |
| return false; |
| } |
| |
| private static Object getAction(final KeyEvent e, final JComponent comp) { |
| final KeyStroke stroke = KeyStroke.getKeyStroke(e.getKeyCode(), e.getModifiers()); |
| return comp.getInputMap().get(stroke); |
| } |
| |
| |
| public JTextField getField() { |
| return myPathTextField; |
| } |
| |
| @Override |
| public boolean isPopupDisplayed() { |
| return myCurrentPopup != null && myCurrentPopup.isVisible(); |
| } |
| |
| public Finder getFinder() { |
| return myFinder; |
| } |
| |
| private boolean isPopupShowing() { |
| return myCurrentPopup != null && myList != null && myList.isShowing(); |
| } |
| |
| private void closePopup() { |
| hideCurrentPopup(); |
| myCurrentCompletion = null; |
| } |
| |
| @Nullable |
| public String getTextFieldText() { |
| final String text = myPathTextField.getText(); |
| if (text == null) return null; |
| return text; |
| } |
| |
| public final void setText(final String text, boolean now, @Nullable final Runnable onDone) { |
| final Update update = new Update("pathFromTree") { |
| public void run() { |
| myPathIsUpdating = true; |
| getField().setText(text); |
| myPathIsUpdating = false; |
| if (onDone != null) { |
| onDone.run(); |
| } |
| } |
| }; |
| if (now) { |
| update.run(); |
| } |
| else { |
| myUiUpdater.queue(update); |
| } |
| } |
| |
| public boolean isPathUpdating() { |
| return myPathIsUpdating; |
| } |
| |
| public |
| @Nullable |
| String getCompletionBase() { |
| String text = getTextFieldText(); |
| if (text == null) return null; |
| int pos = myPathTextField.getCaretPosition(); |
| return pos < text.length() ? text.substring(0, pos) : text; |
| } |
| |
| public static class Vfs extends FileTextFieldImpl { |
| |
| public Vfs(JTextField field, |
| Map<String, String> macroMap, |
| Disposable parent, final LookupFilter chooserFilter) { |
| super(field, new LocalFsFinder(), chooserFilter, macroMap, parent); |
| } |
| |
| public Vfs(Map<String, String> macroMap, |
| Disposable parent, final LookupFilter chooserFilter) { |
| this(new JTextField(), macroMap, parent, chooserFilter); |
| } |
| |
| public VirtualFile getSelectedFile() { |
| LookupFile lookupFile = getFile(); |
| return lookupFile != null ? ((LocalFsFinder.VfsFile)lookupFile).getFile() : null; |
| } |
| } |
| |
| private class CancelAction implements ActionListener { |
| public void actionPerformed(final ActionEvent e) { |
| if (myCurrentPopup != null) { |
| myAutopopup = false; |
| hideCurrentPopup(); |
| } |
| } |
| } |
| } |