/* * Copyright 2003-2011 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 jetbrains.mps.ide.search; import com.intellij.icons.AllIcons.Actions; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.editor.impl.EditorHeaderComponent; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.LightColors; import com.intellij.ui.components.panels.NonOpaquePanel; import jetbrains.mps.ide.actions.MPSActions; import jetbrains.mps.ide.ui.CompletionTextField; import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import java.awt.*; import java.awt.event.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; public abstract class AbstractSearchPanel extends EditorHeaderComponent { protected final Color myBadSequenceColor = LightColors.RED; protected final Color myDefaultBackground; protected HistoryCompletionTextField myText = new HistoryCompletionTextField(); private JCheckBox myIsCaseSensitive = new JCheckBox("Match Case"); private JCheckBox myIsWordsOnly = new JCheckBox("Words"); private JCheckBox myIsRegex = new JCheckBox("Regex"); protected JLabel myFindResult = new JLabel(); private JComponent myToolbarComponent; private String myErrorMessage = null; protected abstract SearchHistoryStorage getSearchHistory(); public abstract void goToPrevious(); public abstract void goToNext(); protected abstract void search(); protected abstract void deactivate(); protected AbstractSearchPanel() { super(); setPreferredSize(new Dimension((int) getPreferredSize().getWidth(), (int) myText.getPreferredSize().getHeight() + 5)); myDefaultBackground = myText.getBackground(); JPanel mainPanel = new NonOpaquePanel(new FlowLayout(FlowLayout.LEFT, 5, 0)); mainPanel.add(new JLabel("Text:")); mainPanel.add(myText); myText.setHideCompletionOnClick(true); setSmallerFont(myText); DefaultActionGroup group = new DefaultActionGroup("search bar", false); group.add(new ShowHistoryAction()); group.add(new PrevOccurenceAction()); group.add(new NextOccurenceAction()); if(showExportToFindTool()) { group.add(new FindAllAction()); } final ActionToolbar tb = ActionManager.getInstance().createActionToolbar("SearchBar", group, true); tb.setLayoutPolicy(ActionToolbar.NOWRAP_LAYOUT_POLICY); myToolbarComponent = tb.getComponent(); myToolbarComponent.setBorder(null); myToolbarComponent.setOpaque(false); mainPanel.add(myToolbarComponent); mainPanel.add(myIsCaseSensitive); myIsCaseSensitive.setMnemonic(KeyEvent.VK_M); myIsCaseSensitive.setFocusable(false); myIsCaseSensitive.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { search(); } }); mainPanel.add(myIsRegex); myIsRegex.setMnemonic(KeyEvent.VK_R); myIsRegex.setFocusable(false); myIsRegex.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { myIsWordsOnly.setEnabled(!myIsWordsOnly.isEnabled()); } }); mainPanel.add(myIsWordsOnly); myIsWordsOnly.setMnemonic(KeyEvent.VK_O); myIsWordsOnly.setFocusable(false); myIsWordsOnly.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { search(); } }); this.add(mainPanel, BorderLayout.WEST); JPanel tailPanel = new NonOpaquePanel(new BorderLayout(5, 0)); JPanel tailContainer = new NonOpaquePanel(new BorderLayout(5, 0)); JLabel escapeLabel = new JLabel(Actions.Cross); tailPanel.add(myFindResult, BorderLayout.CENTER); tailPanel.add(escapeLabel, BorderLayout.EAST); tailContainer.add(tailPanel, BorderLayout.EAST); this.add(tailContainer, BorderLayout.CENTER); escapeLabel.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { deactivate(); } }); setSmallerFontAndOpaque(myIsWordsOnly); setSmallerFontAndOpaque(myIsCaseSensitive); setSmallerFontAndOpaque(myIsRegex); setSmallerFontAndOpaque(myFindResult); myText.getDocument().addDocumentListener(new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { search(); } @Override public void removeUpdate(DocumentEvent e) { search(); } @Override public void changedUpdate(DocumentEvent e) { search(); } }); myText.setColumns(20); registerKeyboardAction(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { goToNext(); } }, KeyStroke.getKeyStroke("DOWN"), WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); registerKeyboardAction(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { goToPrevious(); } }, KeyStroke.getKeyStroke("UP"), WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); setVisible(false); registerKeyboardAction(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { deactivate(); } }, KeyStroke.getKeyStroke("ESCAPE"), WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); } protected void setInitialText(final String initialText) { final String text = initialText != null ? initialText : ""; if (text.contains("\n")) { myIsRegex.setSelected(true); myText.setText(StringUtil.escapeToRegexp(text)); } else { myText.setText(text); } } protected Pattern getPattern() { if (myIsRegex.isSelected()) { return SearchConditions.containsRegexp(myText.getText(), myIsCaseSensitive.isSelected()); } else if (myIsWordsOnly.isSelected()) { return SearchConditions.containsWholeWord(myText.getText(), myIsCaseSensitive.isSelected()); } else { return SearchConditions.containsString(myText.getText(), myIsCaseSensitive.isSelected()); } } protected boolean showExportToFindTool() { return false; } protected boolean canExportToFindTool() { return false; } protected void exportToFindTool() { } protected void setErrorMessage(String message) { myErrorMessage = message; if (message == null) { return; } Font font = myFindResult.getFont().deriveFont(Font.BOLD); myFindResult.setFont(font); myFindResult.setText(message); myText.setBackground(myBadSequenceColor); } private boolean hasErrors() { return myErrorMessage != null; } protected void updateSearchReport(int matches) { if (hasErrors()) { return; } Font font = myFindResult.getFont().deriveFont(Font.PLAIN); String text; if (matches > 100) { font = font.deriveFont(Font.BOLD); text = "More than 100 matches"; } else if (matches > 1) { text = String.valueOf(matches) + " matches"; } else if (matches == 1) { text = String.valueOf(matches) + " match"; } else { text = "No matches"; } myFindResult.setFont(font); myFindResult.setText(text); if (matches == 0 && !myText.getText().isEmpty()) { myText.setBackground(myBadSequenceColor); } else if (myText.getBackground() == myBadSequenceColor) { myText.setBackground(myDefaultBackground); } } public void activate() { if (getSearchHistory() != null && getSearchHistory().getSearches().size() != 0) { for (int i = getSearchHistory().getSearches().size() - 1; i >= 0; i--) { myText.addValue(getSearchHistory().getSearches().get(i)); } } revalidate(); setVisible(true); myText.requestFocus(); } protected void addToHistory() { myText.addValue(myText.getText()); getSearchHistory().setSearches(myText.getProposals(myText.getText())); } private static void setSmallerFontAndOpaque(final JComponent component) { setSmallerFont(component); component.setOpaque(false); } private static void setSmallerFont(final JComponent component) { if (SystemInfo.isMac) { Font f = component.getFont(); component.setFont(f.deriveFont(f.getStyle(), f.getSize() - 2)); } } protected class HistoryCompletionTextField extends CompletionTextField { private final int myPossibleValuesLimit = 30; private List<String> myPossibleValues = new ArrayList<String>(); public HistoryCompletionTextField() { super(); this.setHideCompletionOnClick(true); } public HistoryCompletionTextField(List<String> possibleValues) { super(); myPossibleValues.addAll(possibleValues); } public void addValue(String value) { boolean added = myPossibleValues.isEmpty() || !myPossibleValues.get(0).equals(value); if (added && !myPossibleValues.contains(value) && value.length() != 0) { myPossibleValues.add(0, value); if (myPossibleValues.size() > myPossibleValuesLimit) { for (int i = myPossibleValues.size() - 1; i >= myPossibleValuesLimit; i--) { myPossibleValues.remove(i); } } } } @Override protected boolean canShowPopupAutomatically() { return getText().length() == 0; } @Override protected boolean isCanShowCompletionOnRemove() { return false; } @Override public List<String> getProposals(String text) { return myPossibleValues; } } protected class ShowHistoryAction extends AnAction { private ShowHistoryAction() { getTemplatePresentation().setIcon(Actions.Search); getTemplatePresentation().setDescription("Search history"); getTemplatePresentation().setText("Search History"); ArrayList<Shortcut> shortcuts = new ArrayList<Shortcut>(); shortcuts.addAll(getActionShortcuts(MPSActions.EDITOR_FIND)); shortcuts.add(new KeyboardShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_H, KeyEvent.CTRL_DOWN_MASK), null)); registerCustomShortcutSet( new CustomShortcutSet(shortcuts.toArray(new Shortcut[shortcuts.size()])), myText); } @Override public void actionPerformed(AnActionEvent e) { myText.showCompletion(); } @Override public void update(final AnActionEvent e) { e.getPresentation().setEnabled(!myText.getProposals(null).isEmpty()); } } private class PrevOccurenceAction extends AnAction { public PrevOccurenceAction() { getTemplatePresentation().setIcon(Actions.PreviousOccurence); getTemplatePresentation().setDescription("Previous Occurrence"); getTemplatePresentation().setText("Previous Occurrence"); ArrayList<Shortcut> shortcuts = new ArrayList<Shortcut>(); shortcuts.addAll(getActionShortcuts(MPSActions.EDITOR_FIND_PREVIOUS)); shortcuts.addAll(getActionShortcuts(IdeActions.ACTION_EDITOR_MOVE_CARET_UP)); shortcuts.add(new KeyboardShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_DOWN_MASK), null)); registerCustomShortcutSet( new CustomShortcutSet(shortcuts.toArray(new Shortcut[shortcuts.size()])), myText); } @Override public void update(AnActionEvent e) { e.getPresentation().setEnabled(!myText.completionIsVisible() && myText.getText().length() > 0); } @Override public void actionPerformed(final AnActionEvent e) { goToPrevious(); } } private class NextOccurenceAction extends AnAction { public NextOccurenceAction() { getTemplatePresentation().setIcon(Actions.NextOccurence); getTemplatePresentation().setDescription("Next Occurrence"); getTemplatePresentation().setText("Next Occurrence"); ArrayList<Shortcut> shortcuts = new ArrayList<Shortcut>(); shortcuts.addAll(getActionShortcuts(MPSActions.EDITOR_FIND_NEXT)); shortcuts.addAll(getActionShortcuts(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN)); shortcuts.add(new KeyboardShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), null)); registerCustomShortcutSet( new CustomShortcutSet(shortcuts.toArray(new Shortcut[shortcuts.size()])), myText); } @Override public void update(AnActionEvent e) { e.getPresentation().setEnabled(!myText.completionIsVisible() && myText.getText().length() > 0); } @Override public void actionPerformed(final AnActionEvent e) { goToNext(); } } private class FindAllAction extends AnAction { private FindAllAction() { getTemplatePresentation().setIcon(Actions.Export); getTemplatePresentation().setDescription("Export matches to Find tool window"); getTemplatePresentation().setText("Find All"); AnAction findNext = ActionManager.getInstance().getAction(MPSActions.EDITOR_FIND_NEXT); if (findNext != null) { registerCustomShortcutSet(findNext.getShortcutSet(), myText); } } @Override public void update(AnActionEvent e) { super.update(e); e.getPresentation().setEnabled(canExportToFindTool()); } @Override public void actionPerformed(AnActionEvent e) { exportToFindTool(); } } private static List<Shortcut> getActionShortcuts(String actionId) { AnAction action = ActionManager.getInstance().getAction(actionId); if (action == null) { return Collections.emptyList(); } return Arrays.asList(action.getShortcutSet().getShortcuts()); } }