package org.jabref.gui.fieldeditors; import java.awt.event.ActionEvent; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.AbstractAction; import javax.swing.KeyStroke; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultHighlighter; import javax.swing.text.Document; import javax.swing.text.Highlighter; import javax.swing.text.Keymap; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoManager; import org.jabref.Globals; import org.jabref.gui.actions.Actions; import org.jabref.gui.util.component.JTextAreaWithPlaceholder; import org.jabref.logic.search.SearchQueryHighlightListener; import org.jabref.preferences.JabRefPreferences; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class JTextAreaWithHighlighting extends JTextAreaWithPlaceholder implements SearchQueryHighlightListener { private static final Log LOGGER = LogFactory.getLog(JTextAreaWithHighlighting.class); private Optional<Pattern> highlightPattern = Optional.empty(); private UndoManager undo; public JTextAreaWithHighlighting() { this(""); } public JTextAreaWithHighlighting(String text) { this(text, ""); } /** * Creates a text area with the ability to highlight parts of the content. * It also defines a placeholder which will be displayed the content is empty. * * @param text * @param placeholder */ public JTextAreaWithHighlighting(String text, String placeholder) { super(text, placeholder); setupUndoRedo(); setupPasteListener(); } private void setupPasteListener() { // Bind paste command to KeyBinds.PASTE getInputMap().put(Globals.getKeyPrefs().getKey(org.jabref.gui.keyboard.KeyBinding.PASTE), Actions.PASTE); } private void setupUndoRedo() { undo = new UndoManager(); Document doc = getDocument(); // Listen for undo and redo events doc.addUndoableEditListener(evt -> undo.addEdit(evt.getEdit())); // Create an undo action and add it to the text component getActionMap().put("Undo", new AbstractAction("Undo") { @Override public void actionPerformed(ActionEvent evt) { try { if (undo.canUndo()) { undo.undo(); } } catch (CannotUndoException ignored) { // Ignored } } }); // Bind the undo action to ctl-Z getInputMap().put(Globals.getKeyPrefs().getKey(org.jabref.gui.keyboard.KeyBinding.UNDO), "Undo"); // Create a redo action and add it to the text component getActionMap().put("Redo", new AbstractAction(Actions.REDO) { @Override public void actionPerformed(ActionEvent evt) { try { if (undo.canRedo()) { undo.redo(); } } catch (CannotRedoException ignored) { // Ignored } } }); // Bind the redo action to ctrl-Y boolean bind = true; KeyStroke redoKey = Globals.getKeyPrefs().getKey(org.jabref.gui.keyboard.KeyBinding.REDO); if (Globals.prefs.getBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS)) { // If emacs is enabled, check if we have a conflict at keys // If yes, do not bind // Typically, we have: CTRL+y is "yank" in emacs and REDO in 'normal' settings // The Emacs key bindings are stored in the keymap, not in the input map. Keymap keymap = getKeymap(); KeyStroke[] keys = keymap.getBoundKeyStrokes(); int i = 0; while ((i < keys.length) && !keys[i].equals(redoKey)) { i++; } if (i < keys.length) { // conflict found -> do not bind bind = false; } } if (bind) { getInputMap().put(redoKey, "Redo"); } } /** * Highlight words in the Textarea * * @param words to highlight */ private void highLight() { // highlight all characters that appear in charsToHighlight Highlighter highlighter = getHighlighter(); highlighter.removeAllHighlights(); if ((highlightPattern == null) || !highlightPattern.isPresent()) { return; } String content = getText(); if (content.isEmpty()) { return; } highlightPattern.ifPresent(pattern -> { Matcher matcher = pattern.matcher(content); while (matcher.find()) { try { highlighter.addHighlight(matcher.start(), matcher.end(), DefaultHighlighter.DefaultPainter); } catch (BadLocationException ble) { // should not occur if matcher works right LOGGER.warn("Highlighting not possible, bad location", ble); } } }); } @Override public void setText(String text) { super.setText(text); highLight(); if (undo != null) { undo.discardAllEdits(); } } @Override public void highlightPattern(Optional<Pattern> highlightPattern) { this.highlightPattern = highlightPattern; highLight(); } }