blob: 45d303f59f69829e9beb6010bf0936c2d095bba3 [file] [log] [blame]
/*
* 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.editor.search;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.*;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.editor.EditorUI;
import org.netbeans.editor.MultiKeymap;
import org.netbeans.editor.ext.ExtKit;
import org.netbeans.lib.editor.util.swing.DocumentListenerPriority;
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
import org.openide.util.Exceptions;
public class SearchComboBoxEditor implements ComboBoxEditor {
private final JScrollPane scrollPane;
private final JEditorPane editorPane;
private Object oldValue;
private static final JTextField referenceTextField = (JTextField) new JComboBox<String>().getEditor().getEditorComponent();
private static final Logger LOG = Logger.getLogger(SearchComboBoxEditor.class.getName());
public SearchComboBoxEditor() {
editorPane = new JEditorPane();
changeToOneLineEditorPane(editorPane);
Set<AWTKeyStroke> tfkeys = referenceTextField.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS);
editorPane.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, tfkeys);
tfkeys = referenceTextField.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS);
editorPane.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, tfkeys);
LOG.log(Level.FINE, "Constructor - Reference Font: Name: {0}, Size: {1}\n", new Object[]{referenceTextField.getFont().getFontName(), referenceTextField.getFont().getSize()}); //NOI18N
editorPane.setFont(referenceTextField.getFont());
LOG.log(Level.FINE, "Constructor - Set Font: Name: {0}, Size: {1}\n", new Object[]{editorPane.getFont().getFontName(), editorPane.getFont().getSize()}); //NOI18N
final Insets margin = referenceTextField.getMargin();
scrollPane = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_NEVER,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER) {
@Override
public void setViewportView(Component view) {
if (view instanceof JComponent) {
((JComponent) view).setBorder(new EmptyBorder(margin)); // borderInsets
}
if (view instanceof JEditorPane) {
adjustScrollPaneSize(this, (JEditorPane) view);
}
super.setViewportView(view);
}
};
editorPane.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("editorKit".equals(evt.getPropertyName())) { // NOI18N
adjustScrollPaneSize(scrollPane, editorPane);
}
}
});
final Border border = referenceTextField.getBorder();
if (border != null) {
final Insets borderInsets = border.getBorderInsets(referenceTextField);
if (isCurrentLF("Aqua")) { //NOI18N
scrollPane.setBorder(new EmptyBorder (0, 0, 0, 0));
} else {
scrollPane.setBorder(new EmptyBorder(borderInsets));
}
} else {
scrollPane.setBorder(BorderFactory.createEmptyBorder());
}
scrollPane.setFont(referenceTextField.getFont());
scrollPane.setBackground(referenceTextField.getBackground());
int preferredHeight = referenceTextField.getPreferredSize().height;
Dimension spDim = scrollPane.getPreferredSize();
spDim.height = preferredHeight + getLFHeightAdjustment();
if (!isCurrentLF("Aqua")) { //NOI18N
spDim.height += margin.bottom + margin.top; //borderInsets.top + borderInsets.bottom;
}
scrollPane.setPreferredSize(spDim);
scrollPane.setMinimumSize(spDim);
scrollPane.setMaximumSize(spDim);
scrollPane.setViewportView(editorPane);
final DocumentListener manageViewListener = new ManageViewPositionListener(editorPane, scrollPane);
DocumentUtilities.addDocumentListener(editorPane.getDocument(), manageViewListener, DocumentListenerPriority.AFTER_CARET_UPDATE);
editorPane.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("document".equals(evt.getPropertyName())) { // NOI18N
Document oldDoc = (Document) evt.getOldValue();
if (oldDoc != null) {
DocumentUtilities.removeDocumentListener(oldDoc, manageViewListener, DocumentListenerPriority.AFTER_CARET_UPDATE);
}
Document newDoc = (Document) evt.getNewValue();
if (newDoc != null) {
DocumentUtilities.addDocumentListener(newDoc, manageViewListener, DocumentListenerPriority.AFTER_CARET_UPDATE);
}
}
// NETBEANS-4444
// selection is not removed when text is input via IME
// so, just remove it when the caret is changed
if ("caret".equals(evt.getPropertyName())) { // NOI18N
if (evt.getOldValue() instanceof Caret) {
Caret oldCaret = (Caret) evt.getOldValue();
if (oldCaret != null) {
int dotPosition = oldCaret.getDot();
int markPosition = oldCaret.getMark();
if (dotPosition != markPosition) {
try {
editorPane.getDocument().remove(Math.min(markPosition, dotPosition), Math.abs(markPosition - dotPosition));
} catch (BadLocationException ex) {
LOG.log(Level.WARNING, "Invalid removal offset: {0}", ex.offsetRequested()); // NOI18N
}
}
}
}
}
}
});
}
public static void changeToOneLineEditorPane(JEditorPane editorPane) {
editorPane.putClientProperty("AsTextField", Boolean.TRUE); //NOI18N
editorPane.putClientProperty(
"HighlightsLayerExcludes", //NOI18N
".*(?<!TextSelectionHighlighting)$" //NOI18N
);
EditorKit kit = MimeLookup.getLookup(SearchNbEditorKit.SEARCHBAR_MIMETYPE).lookup(EditorKit.class);
if (kit == null) {
throw new IllegalArgumentException("No EditorKit for '" + SearchNbEditorKit.SEARCHBAR_MIMETYPE + "' mimetype."); //NOI18N
}
editorPane.setEditorKit(kit);
ActionInvoker.putActionToComponent(new ActionInvoker(SearchNbEditorKit.SEARCH_ACTION, editorPane), editorPane);
ActionInvoker.putActionToComponent(new ActionInvoker(SearchNbEditorKit.REPLACE_ACTION, editorPane), editorPane);
ActionInvoker.putActionToComponent(new ActionInvoker(ExtKit.gotoAction, editorPane), editorPane);
InputMap im = editorPane.getInputMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), NO_ACTION);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), NO_ACTION);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), NO_ACTION);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), NO_ACTION);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), NO_ACTION);
((AbstractDocument) editorPane.getDocument()).setDocumentFilter(new DocumentFilter() {
@Override
public void insertString(DocumentFilter.FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException {
if (string != null) {
fb.insertString(offset, string.replaceAll("\\t", "").replaceAll("\\n", ""), attr); //NOI18N
}
}
@Override
public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String string, AttributeSet attr) throws BadLocationException {
if (string != null) {
fb.replace(offset, length, string.replaceAll("\\t", "").replaceAll("\\n", ""), attr); //NOI18N
}
}
});
editorPane.setBorder(new EmptyBorder (0, 0, 0, 0));
if (isCurrentLF("GTK")) {
editorPane.setBackground((Color) UIManager.get("text"));
} else {
editorPane.setBackground(referenceTextField.getBackground());
}
LOG.log(Level.FINE, "Changed editorkit - Set Font: Name: {0}, Size: {1}\n", new Object[]{editorPane.getFont().getFontName(), editorPane.getFont().getSize()}); //NOI18N
editorPane.setFont(referenceTextField.getFont());
if (!isCurrentLF("Nimbus")) {
editorPane.setCaretColor(referenceTextField.getCaretColor());
}
LOG.log(Level.FINE, "Changed editorkit - Set Font: Name: {0}, Size: {1}\n", new Object[]{editorPane.getFont().getFontName(), editorPane.getFont().getSize()}); //NOI18N
}
private static void adjustScrollPaneSize(JScrollPane sp, JEditorPane editorPane) {
int height;
Dimension prefSize = sp.getPreferredSize();
Insets borderInsets = sp.getBorder() != null ? sp.getBorder().getBorderInsets(sp) : sp.getInsets();
int vBorder = borderInsets.bottom + borderInsets.top;
EditorUI eui = org.netbeans.editor.Utilities.getEditorUI(editorPane);
if (eui != null) {
height = eui.getLineHeight();
if (height < eui.getLineAscent()) {
height = (eui.getLineAscent() * 4) / 3; // Hack for the case when line height = 1
}
} else {
java.awt.Font font = editorPane.getFont();
java.awt.FontMetrics fontMetrics = editorPane.getFontMetrics(font);
height = fontMetrics.getHeight();
}
height += vBorder + getLFHeightAdjustment();
//height += 2; // 2 for border
if (prefSize.height < height) {
prefSize.height = height;
sp.setPreferredSize(prefSize);
sp.setMinimumSize(prefSize);
sp.setMaximumSize(prefSize);
java.awt.Container c = sp.getParent();
if (c instanceof JComponent) {
((JComponent) c).revalidate();
}
}
}
private static boolean isCurrentLF(String lf) {
LookAndFeel laf = UIManager.getLookAndFeel();
String lfID = laf.getID();
return lf.equals(lfID);
}
private static int getLFHeightAdjustment() {
if (isCurrentLF("Metal")) { //NOI18N
return -7;
}
if (isCurrentLF("GTK")) { //NOI18N
return 2;
}
if (isCurrentLF("Motif")) { //NOI18N
return 3;
}
if (isCurrentLF("Nimbus")) { //NOI18N
return 0;
}
if (isCurrentLF("Aqua")) { //NOI18N
return -10;
}
return 0;
}
private static final class ManageViewPositionListener implements DocumentListener {
private final JEditorPane editorPane;
private final JScrollPane sp;
public ManageViewPositionListener(JEditorPane editorPane, JScrollPane sp) {
this.editorPane = editorPane;
this.sp = sp;
}
@Override
public void insertUpdate(DocumentEvent e) {
changed();
}
@Override
public void removeUpdate(DocumentEvent e) {
changed();
}
@Override
public void changedUpdate(DocumentEvent e) {
changed();
}
private void changed() {
JViewport viewport = sp.getViewport();
Point viewPosition = viewport.getViewPosition();
if (viewPosition.x > 0) {
try {
Rectangle textRect = editorPane.getUI().modelToView(editorPane, editorPane.getDocument().getLength());
int textLength = textRect.x + textRect.width;
int viewLength = viewport.getExtentSize().width;
if (textLength < (viewPosition.x + viewLength)) {
viewPosition.x = Math.max(textLength - viewLength, 0);
viewport.setViewPosition(viewPosition);
}
} catch (BadLocationException blex) {
Exceptions.printStackTrace(blex);
}
}
}
}
private static final String NO_ACTION = "no-action"; //NOI18N
@Override
public Component getEditorComponent() {
return scrollPane;
}
@Override
public void setItem(Object anObject) {
String text;
if (anObject != null) {
text = anObject.toString();
oldValue = anObject;
} else {
text = "";
}
// workaround for 4530952
if (!text.equals(editorPane.getText())) {
editorPane.setText(text);
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public Object getItem() {
Object newValue = editorPane.getText();
if (oldValue != null && !(oldValue instanceof String)) {
// The original value is not a string. Should return the value in it's
// original type.
if (newValue.equals(oldValue.toString())) {
return oldValue;
} else {
// Must take the value from the editor and get the value and cast it to the new type.
Class cls = oldValue.getClass();
try {
Method method = cls.getMethod("valueOf", new Class[]{String.class}); //NOI18N
newValue = method.invoke(oldValue, new Object[]{editorPane.getText()});
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
// Fail silently and return the newValue (a String object)
}
}
}
return newValue;
}
@Override
public void selectAll() {
editorPane.selectAll();
editorPane.requestFocus();
}
@Override
public void addActionListener(ActionListener l) {
}
@Override
public void removeActionListener(ActionListener l) {
}
public JScrollPane getScrollPane() {
return scrollPane;
}
public JEditorPane getEditorPane() {
return editorPane;
}
private static final class ActionInvoker extends AbstractAction {
private static final String PREFIX = "search-invoke-"; //NOI18N
private final String originalActionName;
private final Action delegateAction;
public ActionInvoker(String name, JTextComponent component) {
super(PREFIX + name);
originalActionName = name;
delegateAction = component.getActionMap().get(originalActionName);
}
@Override
public void actionPerformed(ActionEvent e) {
if (SearchBar.getInstance().getActualTextComponent() != null) {
ActionEvent newE = new ActionEvent(SearchBar.getInstance().getActualTextComponent(), e.getID(), e.getActionCommand());
delegateAction.actionPerformed(newE);
}
}
private static void putActionToComponent(ActionInvoker action, JTextComponent component) {
Keymap keymap = component.getKeymap();
if (keymap instanceof MultiKeymap) {
MultiKeymap multiKeymap = (MultiKeymap) keymap;
KeyStroke[] keyStrokesForAction = multiKeymap.getKeyStrokesForAction(component.getActionMap().get(action.getOriginalActionName()));
if (keyStrokesForAction == null) {
return;
}
for (KeyStroke ks : keyStrokesForAction) {
component.getInputMap().put(KeyStroke.getKeyStroke(ks.getKeyCode(), ks.getModifiers()), PREFIX + action.getOriginalActionName()); // NOI18N
}
component.getActionMap().put(PREFIX + action.getOriginalActionName(), action);
}
}
public String getOriginalActionName() {
return originalActionName;
}
};
}