The last days I have been trying to implement a highlighting feature in a small text editor. For some reason I get a strange result:
The given example should highlight each "dolor" - the first occurences are correctly found and highlighted but the next ones aren't.
Here is the code I wrote so far:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.DefaultHighlighter.DefaultHighlightPainter;
import javax.swing.text.DefaultStyledDocument;
/**
* Highlighting created on 04.11.2013<br>
* <br>
* Specification:<br>
*/
public class Highlighting extends JFrame implements MouseListener {
private JScrollPane scrollPane;
private JTextPane textPane;
private DefaultHighlighter highlighter;
private DefaultHighlightPainter painter;
public static void main(String[] args) {
new Highlighting().setVisible(true);
}
/**
*
*/
public Highlighting() {
this.initialize();
this.build();
this.configure();
}
/**
*
*/
public void initialize() {
this.scrollPane = new JScrollPane();
this.textPane = new JTextPane();
this.highlighter = new DefaultHighlighter();
this.painter = new DefaultHighlightPainter(Color.RED);
}
/**
*
*/
public void build() {
this.add(this.scrollPane);
}
/**
*
*/
public void configure() {
this.scrollPane.setViewportView(this.textPane);
this.textPane.setHighlighter(this.highlighter);
this.textPane.addMouseListener(this);
this.textPane.setDocument(new DefaultStyledDocument());
this.setPreferredSize(new Dimension(400, 500));
this.pack();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
/**
*
*/
private void highlight() {
this.highlighter.removeAllHighlights();
String selectedText = this.textPane.getSelectedText();
String text = this.textPane.getText();
int wordlength = selectedText.length();
int index = 0;
while ((index = text.indexOf(selectedText, index)) != -1) {
try {
this.highlighter.addHighlight(index, index + wordlength, this.painter);
} catch (BadLocationException e) {
e.printStackTrace();
}
index += wordlength;
}
}
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
this.highlight();
}
}
@Override
public void mousePressed(MouseEvent e) {}
@Override
public void mouseReleased(MouseEvent e) {}
@Override
public void mouseEntered(MouseEvent e) {}
@Override
public void mouseExited(MouseEvent e) {}
}
Does this has something to do with the line separators (\r\n
) ?
A JTextComponent
's getText()
and A JTextPane/JEditorPane
's getText()
has different implementation. JTextPane/JEditorPane
uses EditorKit
to write the document content(text) to a StringWriter
and then return the text with formatting and inserting a line/paragraph break into the document. But the JTextCompoent
returns document content directly by:
document.getText(0, document.getLength());
You will better understand if you try to compare the length : jTextPane1.getText().length()
and jTextPane1().getDocument().getLength()
.
Reproducing the difference in length by inserting string with:
DefaultStyleDocument.insertString(0, str, primaryStyle)
when str = "I\n not" ; document length = 6, getText().length = 7
when str = "I\r\n not" ; document length = 7, getText().length = 8
when str = "I\n\n not" ; document length = 7, getText().length = 9!
So, in your high-lighting text program try reading the content text using:
DefaultStyledDocument document = (DefaultStyledDocument) jTextPane1.getDocument();
try {
contText = document.getText(0, document.getLength());
} catch (BadLocationException ex) {
Logger.getLogger(JTextPaneTest.class.getName()).log(Level.SEVERE, null, ex);
}
Then search for your selected text position in the contText
as you were doing and you should be good to go. Because, highlighter.addHighlight(int p0, int p1, Highlighter.HighlightPainter p)
uses the document
for position offset.
Use CaretListener
:
To Highlight upon text selection, It is better to use CaretListener
, no need to add mouse and key board selection handling code at all:
jTextPane1.addCaretListener(new CaretListener() {
public void caretUpdate(CaretEvent evt) {
if(evt.getDot() == evt.getMark())return;
JTextPane txtPane = (JTextPane) evt.getSource();
DefaultHighlighter highlighter = (DefaultHighlighter) txtPane.getHighlighter();
highlighter.removeAllHighlights();
DefaultHighlightPainter hPainter = new DefaultHighlightPainter(new Color(0xFFAA00));
String selText = txtPane.getSelectedText();
String contText = "";// = jTextPane1.getText();
DefaultStyledDocument document = (DefaultStyledDocument) txtPane.getDocument();
try {
contText = document.getText(0, document.getLength());
} catch (BadLocationException ex) {
Logger.getLogger(JTextPaneTest.class.getName()).log(Level.SEVERE, null, ex);
}
int index = 0;
while((index = contText.indexOf(selText, index)) > -1){
try {
highlighter.addHighlight(index, selText.length()+index, hPainter);
index = index + selText.length();
} catch (BadLocationException ex) {
Logger.getLogger(JTextPaneTest.class.getName()).log(Level.SEVERE, null, ex);
//System.out.println(index);
}
}
}
});
for example, see
how to remove Highlighter
highlighter.removeHighlight(h);
View.modelToView
for a new Highlighternote I don't know how to determine a
new line \n
inside selection, not possible from this code
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.UIManager;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Document;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import javax.swing.text.LayeredHighlighter;
import javax.swing.text.Position;
import javax.swing.text.View;
public class HighlightExample {
private JFrame f = new JFrame("Highlight example");
private JPanel panel = new JPanel();
private JTextPane textPane = new JTextPane();
private JTextField tf = new JTextField("wrapping!");
private String word;
private Highlighter highlighter = new UnderlineHighlighter(null);
public HighlightExample() {
textPane.setHighlighter(highlighter);
textPane.setText("This text pane contains no html. It supports letter wrapping, "
+ "\nThis text pane contains no html. It supports letter wrapping!, "
+ "\nThis text pane contains no html. It supports letter wrapping?, "
+ "\nThis text pane contains no html. It supports letter wrapping-, "
+ "\nThis text pane contains no html. It supports letter wrapping!, "
+ "\nThis text pane contains no html. It supports letter wrapping_, "
+ "\nThis text pane contains no html. It supports letter wrapping!, "
+ "\nThis text pane contains no html. It supports letter wrapping?, "
+ "\nThis text pane contains no html. It supports letter wrapping!, "
+ "\nThis text pane contains no html. It supports letter wrapping, "
+ "\nThis text pane contains no html. It supports letter wrapping!, "
+ "\nThis text pane contains no html. It supports letter wrapping-, "
+ "\nThis text pane contains no html. It supports letter wrapping!, "
+ "\nThis text pane contains no html. It supports letter wrapping?");
panel.setLayout(new BorderLayout());
panel.add(new JLabel("Enter word, then press ENTER key: "), "West");
panel.add(tf, "Center");
/*try {
textPane.read(new FileReader("links1.html"), null);
} catch (Exception e) {
System.out.println("Failed to load file " + args[0]);
System.out.println(e);
}*/
final WordSearcher searcher = new WordSearcher(textPane);
tf.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
word = tf.getText().trim();
int offset = searcher.search(word);
if (offset != -1) {
try {
textPane.scrollRectToVisible(textPane
.modelToView(offset));
} catch (BadLocationException e) {
}
}
}
});
textPane.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent evt) {
searcher.search(word);
}
@Override
public void removeUpdate(DocumentEvent evt) {
searcher.search(word);
}
@Override
public void changedUpdate(DocumentEvent evt) {
}
});
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(panel, "South");
f.add(new JScrollPane(textPane), "Center");
f.setSize(400, 400);
f.setVisible(true);
}
public static void main(String[] args) {
UIManager.put("TextPane.caretForeground", Color.yellow);
UIManager.put("TextPane.selectionBackground", Color.green);
UIManager.put("TextPane.selectionForeground", Color.blue);
/*try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
} catch (Exception evt) {
}*/
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
new HighlightExample();
}
});
}
}
// A simple class that searches for a word in
// a document and highlights occurrences of that word
class WordSearcher {
protected JTextComponent comp;
protected Highlighter.HighlightPainter painter;
public WordSearcher(JTextComponent comp) {
this.comp = comp;
this.painter = new UnderlineHighlighter.UnderlineHighlightPainter(Color.red);
}
// Search for a word and return the offset of the first occurrence.
// Highlights are added for all occurrences found.
public int search(String word) {
int firstOffset = -1;
Highlighter highlighter = comp.getHighlighter();
// Remove any existing highlights for last word
Highlighter.Highlight[] highlights = highlighter.getHighlights();
for (int i = 0; i < highlights.length; i++) {
Highlighter.Highlight h = highlights[i];
if (h.getPainter() instanceof UnderlineHighlighter.UnderlineHighlightPainter) {
highlighter.removeHighlight(h);
}
}
if (word == null || word.equals("")) {
return -1;
}
String content = null; // Look for the word we are given - insensitive search
try {
Document d = comp.getDocument();
content = d.getText(0, d.getLength()).toLowerCase();
} catch (BadLocationException e) {
return -1; // Cannot happen
}
word = word.toLowerCase();
int lastIndex = 0;
int wordSize = word.length();
while ((lastIndex = content.indexOf(word, lastIndex)) != -1) {
int endIndex = lastIndex + wordSize;
try {
highlighter.addHighlight(lastIndex, endIndex, painter);
} catch (BadLocationException e) {
// Nothing to do
}
if (firstOffset == -1) {
firstOffset = lastIndex;
}
lastIndex = endIndex;
}
return firstOffset;
}
}
class UnderlineHighlighter extends DefaultHighlighter {
protected static final Highlighter.HighlightPainter sharedPainter = new UnderlineHighlightPainter(null);// Shared painter used for default highlighting
protected Highlighter.HighlightPainter painter; // Painter used for this highlighter
public UnderlineHighlighter(Color c) {
painter = (c == null ? sharedPainter : new UnderlineHighlightPainter(c));
}
// Convenience method to add a highlight with the default painter.
public Object addHighlight(int p0, int p1) throws BadLocationException {
return addHighlight(p0, p1, painter);
}
@Override
public void setDrawsLayeredHighlights(boolean newValue) {
if (newValue == false) {// Illegal if false - we only support layered highlights
throw new IllegalArgumentException(
"UnderlineHighlighter only draws layered highlights");
}
super.setDrawsLayeredHighlights(true);
}
// Painter for underlined highlights
public static class UnderlineHighlightPainter extends LayeredHighlighter.LayerPainter {
protected Color color; // The color for the underline
public UnderlineHighlightPainter(Color c) {
color = c;
}
@Override
public void paint(Graphics g, int offs0, int offs1, Shape bounds,
JTextComponent c) {// Do nothing: this method will never be called
}
@Override
public Shape paintLayer(Graphics g, int offs0, int offs1, Shape bounds,
JTextComponent c, View view) {
g.setColor(color == null ? c.getSelectionColor() : color);
Rectangle alloc = null;
if (offs0 == view.getStartOffset() && offs1 == view.getEndOffset()) {
if (bounds instanceof Rectangle) {
alloc = (Rectangle) bounds;
} else {
alloc = bounds.getBounds();
}
} else {
try {
Shape shape = view.modelToView(offs0, Position.Bias.Forward, offs1,
Position.Bias.Backward, bounds);
alloc = (shape instanceof Rectangle) ? (Rectangle) shape : shape.getBounds();
} catch (BadLocationException e) {
return null;
}
}
FontMetrics fm = c.getFontMetrics(c.getFont());
int baseline = alloc.y + alloc.height - fm.getDescent() + 1;
g.drawLine(alloc.x, baseline, alloc.x + alloc.width, baseline);
g.drawLine(alloc.x, baseline + 1, alloc.x + alloc.width, baseline + 1);
return alloc;
}
}
}
来源:https://stackoverflow.com/questions/19765489/jtextpane-highlighting-issue