| /* |
| * 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; |
| } |
| }; |
| } |