org.netbeans.modules.quicksearch.AbstractQuickSearchComboBar Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.netbeans.modules.quicksearch;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.FontMetrics;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.MissingResourceException;
import java.util.Set;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.MenuElement;
import javax.swing.MenuSelectionManager;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
import javax.swing.text.DocumentFilter.FilterBypass;
import javax.swing.text.JTextComponent;
import org.netbeans.modules.quicksearch.ProviderModel.Category;
import org.netbeans.modules.quicksearch.ResultsModel.ItemResult;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.windows.TopComponent;
/**
* Quick search toolbar component
* @author Jan Becicka
*/
public abstract class AbstractQuickSearchComboBar extends javax.swing.JPanel {
QuickSearchPopup displayer = new QuickSearchPopup(this);
WeakReference caller;
Color origForeground;
protected final KeyStroke keyStroke;
protected JTextComponent command;
public AbstractQuickSearchComboBar(KeyStroke ks) {
keyStroke = ks;
initComponents();
setShowHint(true);
command.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent arg0) {
textChanged();
}
public void removeUpdate(DocumentEvent arg0) {
textChanged();
}
public void changedUpdate(DocumentEvent arg0) {
textChanged();
}
private void textChanged () {
if (command.isFocusOwner()) {
displayer.maybeEvaluate(command.getText());
}
}
});
if (command.getDocument() instanceof AbstractDocument) {
AbstractDocument ad = (AbstractDocument) command.getDocument();
ad.setDocumentFilter(new InvalidSearchTextDocumentFilter());
}
}
public KeyStroke getKeyStroke() {
return keyStroke;
}
protected abstract JTextComponent createCommandField();
protected abstract JComponent getInnerComponent();
private void initComponents() {
setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1));
setMaximumSize(new java.awt.Dimension(200, 2147483647));
setName("Form"); // NOI18N
setOpaque(false);
addFocusListener(new java.awt.event.FocusAdapter() {
@Override
public void focusLost(java.awt.event.FocusEvent evt) {
formFocusLost(evt);
}
});
command = createCommandField();
String shortcutText = ""; //NOI18N
if (!SearchResultRender.getKeyStrokeAsText(keyStroke).isEmpty()) {
shortcutText = "(" + SearchResultRender.getKeyStrokeAsText( //NOI18N
keyStroke) + ")"; //NOI18N
}
command.setToolTipText(org.openide.util.NbBundle.getMessage(AbstractQuickSearchComboBar.class, "AbstractQuickSearchComboBar.command.toolTipText", new Object[] {shortcutText})); // NOI18N
command.setName("command"); // NOI18N
command.addFocusListener(new java.awt.event.FocusAdapter() {
@Override
public void focusGained(java.awt.event.FocusEvent evt) {
commandFocusGained(evt);
}
@Override
public void focusLost(java.awt.event.FocusEvent evt) {
commandFocusLost(evt);
}
});
command.addKeyListener(new java.awt.event.KeyAdapter() {
@Override
public void keyPressed(java.awt.event.KeyEvent evt) {
commandKeyPressed(evt);
}
});
command.addMouseListener(new MouseAdapter() {
public @Override void mouseClicked(MouseEvent e) {
displayer.explicitlyInvoked();
}
});
}
private void formFocusLost(java.awt.event.FocusEvent evt) {
displayer.setVisible(false);
}
private void commandKeyPressed(java.awt.event.KeyEvent evt) {
if (evt.getKeyCode()==KeyEvent.VK_DOWN) {
displayer.selectNext();
evt.consume();
} else if (evt.getKeyCode() == KeyEvent.VK_UP) {
displayer.selectPrev();
evt.consume();
} else if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
evt.consume();
invokeSelectedItem();
} else if ((evt.getKeyCode()) == KeyEvent.VK_ESCAPE) {
returnFocus(true);
displayer.clearModel();
} else if (evt.getKeyCode() == KeyEvent.VK_F10 &&
evt.isShiftDown()) {
evt.consume();
maybeShowPopup(null);
}
}
/** Actually invokes action selected in the results list */
public void invokeSelectedItem () {
JList list = displayer.getList();
ResultsModel.ItemResult ir = (ItemResult) list.getSelectedValue();
// special handling of invocation of "more results item" (three dots)
if (ir != null) {
Runnable action = ir.getAction();
if (action instanceof CategoryResult) {
CategoryResult cr = (CategoryResult)action;
evaluate(cr.getCategory());
return;
}
}
// #137259: invoke only some results were found
if (list.getModel().getSize() > 0) {
returnFocus(false);
// #137342: run action later to let focus indeed be transferred
// by previous returnFocus() call
SwingUtilities.invokeLater(new Runnable() {
public void run() {
displayer.invoke();
}
});
}
}
private void returnFocus (boolean force) {
displayer.setVisible(false);
if (caller != null) {
TopComponent tc = caller.get();
if (tc != null) {
tc.requestActive();
tc.requestFocus();
return;
}
}
if (force) {
KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();
}
}
private void commandFocusLost(java.awt.event.FocusEvent evt) {
displayer.setVisible(false);
setShowHint(true);
}
private void commandFocusGained(java.awt.event.FocusEvent evt) {
caller = new WeakReference(TopComponent.getRegistry().getActivated());
setShowHint(false);
CommandEvaluator.dropTemporaryCat();
}
protected void maybeShowPopup (MouseEvent evt) {
if (evt != null && !SwingUtilities.isLeftMouseButton(evt)) {
return;
}
JPopupMenu pm = new JPopupMenu();
final Set evalCats =
new LinkedHashSet();
evalCats.addAll(CommandEvaluator.getEvalCats());
JMenuItem allCats = new AllMenuItem(evalCats);
pm.add(allCats);
for (ProviderModel.Category cat : ProviderModel.getInstance().getCategories()) {
if (!CommandEvaluator.RECENT.equals(cat.getName())) {
JCheckBoxMenuItem item = new CategoryCheckBoxMenuItem(cat,
evalCats);
pm.add(item);
}
}
pm.show(getInnerComponent(), 0, getInnerComponent().getHeight() - 1);
}
private void updateCats(Set evalCats) {
CommandEvaluator.setEvalCats(evalCats);
CommandEvaluator.dropTemporaryCat();
// refresh hint
setShowHint(!command.isFocusOwner());
}
private void updateCheckBoxes(Container container, Set evalCats) {
Container parent = container.getParent();
for (Component c : parent.getComponents()) {
if (c instanceof CategoryCheckBoxMenuItem) {
CategoryCheckBoxMenuItem ci = (CategoryCheckBoxMenuItem) c;
ci.setSelected(evalCats.contains(ci.category));
ci.setTooltipText();
}
}
}
/**
* Runs evaluation. Possibly temporarily narrow to a specified category.
*
* @param tempCategory Temporary category. If set, only the specified
* category will be evaluated. If null, all enabled categories will be
* evaluated.
*/
public void evaluate(Category tempCategory) {
if (tempCategory != null) {
CommandEvaluator.setTemporaryCat(tempCategory);
} else {
CommandEvaluator.dropTemporaryCat();
}
displayer.maybeEvaluate(command.getText());
}
public void setNoResults (boolean areNoResults) {
// no op when called too soon
if (command == null || origForeground == null) {
return;
}
// don't alter color if showing hint already
if (command.getForeground().equals(command.getDisabledTextColor())) {
return;
}
command.setForeground(areNoResults ? Color.RED : origForeground);
}
private void setShowHint (boolean showHint) {
// remember orig color on first invocation
if (origForeground == null) {
origForeground = command.getForeground();
}
if (showHint) {
command.setForeground(command.getDisabledTextColor());
Set evalCats = CommandEvaluator.getEvalCats();
if (evalCats.size() < 3
&& !CommandEvaluator.isTemporaryCatSpecified()) {
Category bestFound = null;
for (Category c : evalCats) {
if (bestFound == null || CommandEvaluator.RECENT.equals(
bestFound.getName())) {
bestFound = c;
}
}
command.setText(getHintText(bestFound));
} else {
command.setText(getHintText(null));
}
} else {
command.setForeground(origForeground);
command.setText("");
}
}
private String getHintText (Category cat) {
StringBuilder sb = new StringBuilder();
if (cat != null) {
sb.append(NbBundle.getMessage(AbstractQuickSearchComboBar.class,
"MSG_DiscoverabilityHint2", cat.getDisplayName())); //NOI18N
} else {
sb.append(NbBundle.getMessage(AbstractQuickSearchComboBar.class, "MSG_DiscoverabilityHint")); //NOI18N
}
String keyStrokeAsText = SearchResultRender.getKeyStrokeAsText(keyStroke);
if (!keyStrokeAsText.isEmpty()) {
sb.append(" ("); //NOI18N
sb.append(keyStrokeAsText);
sb.append(")"); //NOI18N
}
return sb.toString();
}
@Override
public void requestFocus() {
super.requestFocus();
command.requestFocus();
}
public JTextComponent getCommand() {
return command;
}
public int getBottomLineY () {
return getInnerComponent().getY() + getInnerComponent().getHeight();
}
static Color getComboBorderColor () {
Color shadow = UIManager.getColor(
Utilities.isWindows() ? "Nb.ScrollPane.Border.color" : "TextField.shadow");
return shadow != null ? shadow : getPopupBorderColor();
}
static Color getPopupBorderColor () {
Color shadow = UIManager.getColor("controlShadow");
return shadow != null ? shadow : Color.GRAY;
}
static Color getTextBackground () {
Color textB = UIManager.getColor("TextPane.background");
if( "Aqua".equals(UIManager.getLookAndFeel().getID()) ) //NOI18N
textB = UIManager.getColor("NbExplorerView.background"); //NOI18N
return textB != null ? textB : Color.WHITE;
}
static Color getResultBackground () {
return getTextBackground();
}
static Color getCategoryTextColor () {
Color shadow = UIManager.getColor("textInactiveText");
if( "Aqua".equals(UIManager.getLookAndFeel().getID()) )
shadow = UIManager.getColor("Table.foreground");
return shadow != null ? shadow : Color.DARK_GRAY;
}
protected int computePrefWidth () {
FontMetrics fm = command.getFontMetrics(command.getFont());
ProviderModel pModel = ProviderModel.getInstance();
int maxWidth = 0;
for (Category cat : pModel.getCategories()) {
// skip recent category
if (CommandEvaluator.RECENT.equals(cat.getName())) {
continue;
}
maxWidth = Math.max(maxWidth, fm.stringWidth(getHintText(cat)));
}
// don't allow width grow too much
return Math.min(350, maxWidth);
}
private static String dispalyNameFor(Category category) {
if (null != category.getCommandPrefix()) {
return NbBundle.getMessage(AbstractQuickSearchComboBar.class,
"LBL_CategoryAndCommandPrefix", //NOI18N
category.getDisplayName(),
category.getCommandPrefix());
} else {
return category.getDisplayName();
}
}
/**
* Document filter that checks invalid input. See bug 217364.
*/
static class InvalidSearchTextDocumentFilter extends DocumentFilter {
private static final int SEARCH_TEXT_LENGTH_LIMIT = 256;
private static final int SEARCH_NUM_WORDS_LIMIT = 20;
@Override
public void insertString(FilterBypass fb, int offset,
String string, AttributeSet attr)
throws BadLocationException {
String normalized = normalizeWhiteSpaces(string);
if (isLengthInLimit(normalized, fb, 0)) {
super.insertString(fb, offset, normalized, attr);
} else {
warnAboutInvalidText();
}
}
@Override
public void replace(FilterBypass fb, int offset, int length,
String text, AttributeSet attrs)
throws BadLocationException {
String normalized = text == null
? null : normalizeWhiteSpaces(text); //NOI18N
if (normalized == null || isLengthInLimit(normalized, fb, length)) {
super.replace(fb, offset, length, normalized, attrs);
} else {
warnAboutInvalidText();
}
}
/**
* Check whether length limit or nubmer of words is exceeded when the
* new string is inserted or replaced.
*
* @param newContent String to be inserted
* @param fb Current FilterBypass
* @param charsToBeRemoved Number of characters going to be replaced by
* new string.
*/
private boolean isLengthInLimit(String newContent, FilterBypass fb,
int charsToBeRemoved) {
return isLengthInLimit(newContent, SEARCH_TEXT_LENGTH_LIMIT
- fb.getDocument().getLength() + charsToBeRemoved);
}
/**
* Check whether length limit or nubmer of words is exceeded when the
* new string is inserted or replaced.
*
* @param newContent String to be inserted.
* @param remainingChars Limit for characters to be inserted.
*/
boolean isLengthInLimit(String newContent, int remainingChars) {
return (newContent.length() <= remainingChars)
&& (newContent.split(" ").length //NOI18N
<= SEARCH_NUM_WORDS_LIMIT);
}
/**
* Replace all line breaks and multiple spaces with single space.
*/
String normalizeWhiteSpaces(String s) {
String replaced = s.replaceAll("\\s+", " "); //NOI18N
return (replaced.length() > 1) ? replaced.trim() : replaced;
}
/**
* Warn that search text would be invalid.
*/
@NbBundle.Messages({
"MSG_INVALID_SEARCH_TEST=Search text is too long."
})
private void warnAboutInvalidText() {
NotifyDescriptor nd = new NotifyDescriptor.Message(
Bundle.MSG_INVALID_SEARCH_TEST(),
NotifyDescriptor.ERROR_MESSAGE);
DialogDisplayer.getDefault().notifyLater(nd);
}
}
/**
* Show and select menu at a given path. Used to restore a menu after click.
*/
private void showMenuPath(MenuElement[] selectedPath) {
if (selectedPath != null && selectedPath.length > 1) {
if (selectedPath[0] instanceof JPopupMenu) {
((JPopupMenu) selectedPath[0]).setVisible(true);
MenuSelectionManager.defaultManager().setSelectedPath(
selectedPath);
}
}
}
/**
* Menu item representing a single category.
*/
private class CategoryCheckBoxMenuItem extends JCheckBoxMenuItem
implements ActionListener {
private MenuElement[] selectedPath = null;
private Category category;
private final Set evalCats;
public CategoryCheckBoxMenuItem(final Category category,
final Set evalCats) {
super(dispalyNameFor(category), evalCats.contains(category));
this.category = category;
this.evalCats = evalCats;
setTooltipText();
getModel().addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
if (isShowing() && model.isArmed()) {
selectedPath = MenuSelectionManager.defaultManager()
.getSelectedPath();
}
}
});
addActionListener(this);
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
mouseClickedOnItem(e);
}
});
}
@Override
public void doClick(int pressTime) {
super.doClick(pressTime);
setTooltipText();
showMenuPath(selectedPath);
}
private void mouseClickedOnItem(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) {
e.consume();
if (isSelected()) {
Iterator iterator = evalCats.iterator();
while (iterator.hasNext()) {
Category c = iterator.next();
if (!CommandEvaluator.RECENT.equals(c.getName())) {
iterator.remove();
}
}
evalCats.add(category);
} else {
evalCats.addAll(
ProviderModel.getInstance().getCategories());
evalCats.remove(category);
}
updateCheckBoxes(CategoryCheckBoxMenuItem.this, evalCats);
updateCats(evalCats);
}
}
@Override
public void actionPerformed(ActionEvent e) {
if (this.isSelected()) {
evalCats.add(category);
} else {
evalCats.remove(category);
}
updateCats(evalCats);
}
private void setTooltipText() throws MissingResourceException {
boolean selected = evalCats.contains(category);
String bundleKey = selected
? "MSG_RightClickEnablesAllOthers" //NOI18N
: "MSG_RightClickDisablesOthers"; //NOI18N
StringBuilder tooltip = new StringBuilder(""); //NOI18N
tooltip.append(NbBundle.getMessage(
AbstractQuickSearchComboBar.class, bundleKey));
if (null != category.getCommandPrefix()) {
tooltip.append("
");
tooltip.append(NbBundle.getMessage(
AbstractQuickSearchComboBar.class,
"LBL_TooltipCommandPrefix", //NOI18N
category.getCommandPrefix()));
}
tooltip.append(""); //NOI18N
setToolTipText(tooltip.toString());
}
@Override
public Point getToolTipLocation(MouseEvent event) {
Point p = new Point(((event.getX() - 25) / 5) * 5, // repaint every
((event.getY() + 15) / 5) * 5); // 5 pixels
return p;
}
}
/**
* Menu item for enabling or disabling all categories.
*/
private class AllMenuItem extends JMenuItem implements ActionListener {
private Set evalCats;
private int totalCount;
private MenuElement[] selectedPath = null;
public AllMenuItem(Set evalCats) {
this.evalCats = evalCats;
this.totalCount = ProviderModel.getInstance()
.getCategories().size();
getModel().addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
if (isShowing() && model.isArmed()) {
selectedPath = MenuSelectionManager.defaultManager()
.getSelectedPath();
}
}
});
addActionListener(this);
}
@Override
public void actionPerformed(ActionEvent e) {
if (evalCats.size() == totalCount) {
Iterator iterator = evalCats.iterator();
while (iterator.hasNext()) {
Category c = iterator.next();
if (!CommandEvaluator.RECENT.equals(c.getName())) {
iterator.remove();
}
}
} else {
evalCats.addAll(ProviderModel.getInstance().getCategories());
}
updateCats(evalCats);
updateCheckBoxes(this, evalCats);
}
@Override
public void doClick(int pressTime) {
super.doClick(pressTime);
showMenuPath(selectedPath);
}
@Override
public String getText() {
if (evalCats == null || evalCats.size() != totalCount) {
return NbBundle.getMessage(getClass(),
"LBL_AllCategories"); //NOI18N
} else {
return NbBundle.getMessage(getClass(),
"LBL_NoCategory"); //NOI18N
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy