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 Highlighter
note 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;
}
}
}
Related
My goal is to create a list with a length controlled by another component where each item's value can be edited.
My attempt uses an editable JComboBox that has a certain number of elements. In my code below, however, the selected index keeps changing to -1, which does not allow me to modify the item. Is there a way to select and edit an item using JComboBox?
//cb is a JComboBox with elements of type ComboItem. idx is defined elsewhere.
cb.addItemListener(new ItemListener() {
#SuppressWarnings("unchecked")
#Override
public void itemStateChanged(ItemEvent e) {
if(e.getStateChange() == ItemEvent.SELECTED)
idx = ((JComboBox<ComboItem>) e.getSource()).getSelectedIndex();
System.out.println("idx:"+idx);
}
});
//Pressing enter should commit changes.
cb.getEditor().getEditorComponent().addKeyListener(new KeyAdapter() {
#Override
public void keyReleased(KeyEvent e) {
if(e.getKeyChar() == KeyEvent.VK_ENTER) {
String parse = ((JTextComponent) cb.getEditor().getEditorComponent()).getText();
parse = parse.substring(parse.lastIndexOf(":")+1).replaceAll("[^0-9]+", ""); //Processes edits.
cb.getItemAt(idx).change("Layer "+idx, Integer.parseInt(parse)); //This method should change the
System.out.println("selected item:"+cb.getSelectedItem()); // data for each item.
}
}
});
//Editing the text in the JComboBox and pressing the enter key should update the selected item.
JComboBox is not required, so feel free to suggest a different component if it is a better choice for this task.
After a day's worth of trial and error, I finally made a working solution. Instead of using a JComboBox, which was probably not designed to perform the desired task, I made a JScrollPane that adds a child JPanel every time a button is pressed. Each panel has a text field object that can be customized and a button to delete it. In my case, I added a DocumentFilter that allows positive < 5 digit integers.
I cannot figure out how to remove the spacing between the added panels before the scroll bar appears, so please comment a solution if you have one. Also, if there are any other improvements that can be made, please comment those suggestions as well.
Scroll Panel
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
import javax.swing.JLabel;
#SuppressWarnings("serial")
public class TestScrollPane extends JPanel {
private int w,h;
private JPanel content;
private JScrollPane scroll;
private JButton add;
private JLabel getTextLabel;
private JButton getTextBtn;
/**
* Create the panel.
*/
public TestScrollPane(int width, int height) {
setLayout(null);
w = width; h = height;
scroll = new JScrollPane();
scroll.setBounds(0, 0, w, h);
scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
add(scroll);
content = new JPanel();
scroll.setViewportView(content);
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
add = new JButton("+");
add.setBounds(0, h, 89, 23);
add(add);
getTextLabel = new JLabel("");
getTextLabel.setBounds(10, 425, 215, 14);
add(getTextLabel);
getTextBtn = new JButton("Get Text");
getTextBtn.setBounds(225, 425, 215, 14);
getTextBtn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
String a[] = getText(),
b = "";
b += "[";
for(int i = 0; i < a.length - 1; i++)
b += a[i]+", ";
b += a[a.length-1]+"]";
System.out.println(b);
getTextLabel.setText(b);
}
});
add(getTextBtn);
add.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("adding "+content.getComponentCount());
content.add(new ScrollItem(content.getComponentCount()));
validate();
repaint();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(w, h);
}
public String[] getText() {
String out[] = new String[content.getComponentCount()],s;
for(int i = 0; i < out.length; i++)
out[i] = (s = ((ScrollItem) content.getComponent(i)).out) == null ? "0" : s;
return out;
}
private class ScrollItem extends JPanel {
private JTextField text;
private JButton del;
private int idx;
private String out;
public ScrollItem(int id) {
idx = id;
setBounds(0, idx*20, w-5, 20);
setLayout(null);
text = new JTextField();
text.setBounds(0, 0, (w-5)*3/4, 20);
text.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
out = text.getText();
}
});
AbstractDocument d = (AbstractDocument) text.getDocument();
d.setDocumentFilter(new TextFilter(4));
del = new JButton("X");
del.setBounds((w-5)*3/4, 0, (w-5)/4, 20);
del.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
content.remove(idx);
for(int i = idx; i < content.getComponentCount(); i++)
((ScrollItem) content.getComponent(i)).moveUp();
content.validate();
content.repaint();
System.out.println("Removed "+idx);
}
});
add(text);
add(del);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(w-5, 20);
}
public void moveUp() {
idx--;
content.validate();
content.repaint();
}
}
private class TextFilter extends DocumentFilter {
private int max;
public TextFilter(int maxChars) {
max = maxChars;
}
#Override
public void insertString(FilterBypass fb, int offs, String str, AttributeSet a) throws BadLocationException {
System.out.println("insert");
if ((fb.getDocument().getLength() + str.length()) <= max && str.matches("\\d+"))
super.insertString(fb, offs, str, a);
else
showError("Length: "+((fb.getDocument().getLength() + str.length()) <= max)+" | Text: "+str.matches("\\d+")+" | Str: "+str);
}
#Override
public void replace(FilterBypass fb, int offs, int length, String str, AttributeSet a) throws BadLocationException {
System.out.println("replace");
if ((fb.getDocument().getLength() + str.length() - length) <= max && str.matches("\\d+"))
super.replace(fb, offs, length, str, a);
else
showError("Length: "+((fb.getDocument().getLength() + str.length() - length) <= max)+" | Text: "+str.matches("\\d+")+" | Str: "+str);
}
private void showError(String cause) {
JOptionPane.showMessageDialog(null, cause);
}
}
}
Test Window
import java.awt.EventQueue;
import javax.swing.JFrame;
public class TestWindow {
private JFrame frame;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
TestWindow window = new TestWindow();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public TestWindow() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 435, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestScrollPane(430, 247));
}
}
I am making a Source code Editor using JtextArea.In that I want to do the following task." While user typing on Editor window(JtextArea),each and every time the updated text(last word) should compare with the set of words in the database, if it matches any one of the word then the definition will be open in new popup frame."
My coding is like following
String str = textarea.getText();
Class.forName(driver).newInstance();
conn = DriverManager.getConnection(url+dbName,userName,password);
String stm="select url from pingatabl where functn=?";
PreparedStatement st = conn.prepareStatement(stm);
st.setString(1, str);
//Excuting Query
ResultSet rs = st.executeQuery();
if (rs.next()) {
String s = rs.getString(1);
//Sets Records in frame
JFrame fm = new JFrame();
fm.setVisible(true);
fm.setSize(500,750);
JEditorPane jm = new JEditorPane();
fm.add(jm);
jm.setPage(ClassLoader.getSystemResource(s));
In the above coding String str = textarea.getText(); reads all the text in the textarea.. but i need to get last word only. How can i get latest word from JTextArea..
Use a DocumentListener to monitor for changes to the text component and use javax.swing.text.Utilities to calculate the start/end index of the word in the Document, from which you can the extract the result
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Utilities;
public class TheLastWord {
public static void main(String[] args) {
new TheLastWord();
}
public TheLastWord() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new BorderLayout());
JTextArea ta = new JTextArea(10, 20);
add(new JScrollPane(ta));
JLabel lastWord = new JLabel("...");
add(lastWord, BorderLayout.SOUTH);
ta.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void insertUpdate(DocumentEvent e) {
checkLastWord();
}
#Override
public void removeUpdate(DocumentEvent e) {
checkLastWord();
}
#Override
public void changedUpdate(DocumentEvent e) {
checkLastWord();
}
protected void checkLastWord() {
try {
int start = Utilities.getWordStart(ta, ta.getCaretPosition());
int end = Utilities.getWordEnd(ta, ta.getCaretPosition());
String text = ta.getDocument().getText(start, end - start);
lastWord.setText(text);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
}
You can use following code block if you want to get the last word in the testarea
String[] wordsArray = textarea.getText().split("\\s+");
String lastWord = wordsArray[wordsArray.length - 1];
But if you want to get the last updated word then you have to use a Listener for that.
Check on DocumentListener and DocumentEvent
http://docs.oracle.com/javase/7/docs/api/javax/swing/event/DocumentListener.html
http://docs.oracle.com/javase/7/docs/api/javax/swing/event/DocumentEvent.html
To get the last line in your JEditorPane, split the text in the editor on \n as shown below:
String text = editor.getText();
String[] lines = text.split("\n");
String lastLine = lines[lines.length-1];
System.out.println("Last line: " + lastLine);
Similarly, to get the last word, split the last line on space.
Here a code of method that returns the last word of a text
public static String getLastWord(String s) {
int endOfLine = s.length() - 1;
boolean start = false;
while (!start && endOfLine >= 0) {
if (!Character.isLetter(s.charAt(endOfLine))) {
endOfLine--;
} else {
start = true;
}
}
final StringBuilder lastWord = new StringBuilder("");
while (start && endOfLine >= 0) {
if (!Character.isLetter(s.charAt(endOfLine))) {
start = false;
} else {
lastWord.insert(0, s.charAt(endOfLine));
endOfLine--;
}
}
return lastWord.toString();
}
When I combine HTML tags into the JLabel text I am loosing the ellipsis behavior that is shown when the space is too small to display the complete text. In my specific case, it is a TableCellRenderer which extends JLabel (swing's default or other). Now, when the column width is too small for the text to be shown fully, it is not showing the ellipsis.
See the image below for example:
For the left column I wrapped the text at the renderer with HTML: setText("<html>" + "<strong>" + value.toString() + "</strong>" + "</html>");. As you can see when the column width is too small to contain the text, it is just cut. The right column however, showing the date and time and using DefaultTableCellRenderer is showing ellipsis when it fails to contain the complete text.
So my question is, can I have both? Meaning, wrapping the text with HTML and still get the ellipsis?
UPDATE:
I found the reason for not getting the ellipsis when using HTML. I followed the code from JComponent#paintComponent(Graphics g) all the way down to BasicLabelUI#layoutCL(...). See the following code snippet taken from the last. It is only clipping the string if it does not have the html property (which is true when the label text is wrapped with html). Yet I have no idea how to work around it:
v = (c != null) ? (View) c.getClientProperty("html") : null;
if (v != null) {
textR.width = Math.min(availTextWidth,
(int) v.getPreferredSpan(View.X_AXIS));
textR.height = (int) v.getPreferredSpan(View.Y_AXIS);
} else {
textR.width = SwingUtilities2.stringWidth(c, fm, text);
lsb = SwingUtilities2.getLeftSideBearing(c, fm, text);
if (lsb < 0) {
// If lsb is negative, add it to the width and later
// adjust the x location. This gives more space than is
// actually needed.
// This is done like this for two reasons:
// 1. If we set the width to the actual bounds all
// callers would have to account for negative lsb
// (pref size calculations ONLY look at width of
// textR)
// 2. You can do a drawString at the returned location
// and the text won't be clipped.
textR.width -= lsb;
}
if (textR.width > availTextWidth) {
text = SwingUtilities2.clipString(c, fm, text,
availTextWidth);
textR.width = SwingUtilities2.stringWidth(c, fm, text);
}
textR.height = fm.getHeight();
}
As long as the HTML content is simple, as in your question, the ellipsis showing can be accomplished with a custom made JLabel. Here is a working example.
Just resize the window and you see the ellipsis appear and disappear and the text cut appropriately as the label resizes with the window.
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JLabel;
import javax.swing.border.Border;
public class SimpleHTMLJLabel extends JLabel
{
private static final long serialVersionUID = -1799635451172963826L;
private String textproper;
private String ellipsis = "...";
private int textproperwidth;
private FontMetrics fontMetrics;
private int ellipsisWidth;
private int insetsHorizontal;
private int borderHorizontal;
public SimpleHTMLJLabel(String textstart, String textproper, String textend)
{
super(textstart + textproper + textend);
this.textproper = textproper;
insetsHorizontal = getInsets().left + getInsets().right;
fontMetrics = getFontMetrics(getFont());
calculateWidths();
addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e)
{
int availablewidth = getWidth();
if (textproperwidth > availablewidth - (insetsHorizontal + borderHorizontal))
{
String clippedtextproper = textproper;
while (clippedtextproper.length() > 0
&& fontMetrics.stringWidth(clippedtextproper) + ellipsisWidth > availablewidth - (insetsHorizontal + borderHorizontal))
{
clippedtextproper = clipText(clippedtextproper);
}
setText(textstart + clippedtextproper + ellipsis + textend);
} else
{
setText(textstart + textproper + textend);
}
}
});
}
private void calculateWidths()
{
if (textproper != null)
{
textproperwidth = fontMetrics.stringWidth(textproper);
}
if (ellipsis != null)
{
ellipsisWidth = fontMetrics.stringWidth(ellipsis);
}
}
#Override
public void setFont(Font font)
{
super.setFont(font);
fontMetrics = getFontMetrics(getFont());
calculateWidths();
}
private String clipText(String clippedtextproper)
{
return clippedtextproper.substring(0, clippedtextproper.length() - 1);
}
#Override
public void setBorder(Border border)
{
super.setBorder(border);
borderHorizontal = border.getBorderInsets(this).left + border.getBorderInsets(this).right;
}
}
MAIN
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Main
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run()
{
JFrame window = new JFrame();
window.setResizable(true);
window.setTitle("Label Test");
window.getContentPane().add(getContent());
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setSize(400, 200);
window.setLocationRelativeTo(null);
window.setVisible(true);
}
});
}
protected static Component getContent()
{
JPanel panel = new JPanel(new BorderLayout());
SimpleHTMLJLabel label = new SimpleHTMLJLabel("<html><strong>", "TEST1test2TEST3test4TEST5test6TEST7test8TEST", "</strong></html>");
label.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.BLUE, 5),
BorderFactory.createEmptyBorder(10, 10, 10, 10)));
label.setFont(label.getFont().deriveFont(20F));
panel.add(label, BorderLayout.CENTER);
return panel;
}
}
I'm going to say: No, you can't have both.
I think if you want custom styling and ellipsis you will have to do it yourself without HTML and with a custom TableCellRenderer.
If you want to try and have your cake and eat it too, you might be able to get there by creating your own View object and setting it with c.putClientProperty("html", value) but I suspect that the HTML rendering code has no notion of ellipsing (text-overflow is an HTML 5ish feature) so you would have to figure out how to teach it to do that. I suspect this would be very difficult and much harder than just writing your own TableCellRenderer.
Here is a modified version of SimpleHTMLJLabel which is using code above
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.io.IOException;
import java.io.StringReader;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import javax.swing.JLabel;
import javax.swing.border.Border;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.html.HTML.Tag;
import javax.swing.text.html.HTMLEditorKit.ParserCallback;
public class SimpleHTMLJLabel extends JLabel {
private static final String ellipsis = "...";
private static final String Set = null;
private int textproperwidth;
private FontMetrics fontMetrics;
private int ellipsisWidth;
private int insetsHorizontal;
private int borderHorizontal;
private List<Entry<String, String>> lines;
static String HTML = "<HTML>";
public SimpleHTMLJLabel() {
insetsHorizontal = getInsets().left + getInsets().right;
fontMetrics = getFontMetrics(getFont());
calculateWidths();
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
int availablewidth = getWidth();
renderHtml(availablewidth);
}
});
}
public SimpleHTMLJLabel(String text) {
this();
setText(text);
}
public SimpleHTMLJLabel(List<Entry<String, String>> lines) {
this();
this.lines = lines;
calculateWidths();
super.setText(HTML + toHtml(lines));
}
#Override
public void setText(String text) {
if (text.toUpperCase().startsWith(HTML)) {
this.lines = parseHtml(text);
calculateWidths();
super.setText(HTML + toHtml(lines));
return;
}
super.setText(text);
}
private List<Entry<String, String>> parseHtml(String text) {
List<Entry<String, String>> ret = new ArrayList<>();
java.util.Map<Tag, MutableAttributeSet> tags = new HashMap<>();
try {
(new javax.swing.text.html.parser.ParserDelegator()).parse(new StringReader(text), new ParserCallback() {
#Override
public void handleEndTag(Tag t, int pos) {
//TODO clean handle MutableAttributeSet a
tags.remove(t);
}
#Override
public void handleStartTag(javax.swing.text.html.HTML.Tag t, MutableAttributeSet a, int pos) {
if (t == Tag.HTML) return;
if (t == Tag.P) return;
if (t == Tag.BR) return;
if (t == Tag.BODY) return;
tags.put(t,a);
}
#Override
public void handleText(char[] data, int pos) {
String formats = tags.entrySet().stream().map(t -> "<" + t.getKey() + getAttrib(t.getValue) + ">").collect(Collectors.joining());
ret.add(new AbstractMap.SimpleEntry<>(formats, new String(data)));
}
private String getAttrib(MutableAttributeSet t) {
// TODO Auto-generated method stub
//return " style='color:red'";
return " " + t;
}
}, false);
} catch (IOException e) {
e.printStackTrace();
}
return ret;
}
private static String toEndTag(String s) {
return s.replace("<", "</");
}
private static String toHtml(List<Entry<String, String>> lines) {
return lines.stream().map(s -> s.getKey() + s.getValue() + toEndTag(s.getKey())).collect(Collectors.joining());
}
private static String toPlain(List<Entry<String, String>> lines) {
return lines.stream().map(s -> s.getValue()).collect(Collectors.joining(" "));
}
static private List<Entry<String, String>> clipText(List<Entry<String, String>> properList) {
Entry<String, String> last = properList.get(properList.size() - 1);
List<Entry<String, String>> ret = properList.subList(0, properList.size() - 1);
String newlastValue = truncate(last.getValue());
if (newlastValue.isEmpty()) {
return ret;
}
List<Entry<String, String>> retNew = new ArrayList<>();
retNew.addAll(ret);
retNew.add(new AbstractMap.SimpleEntry<>(last.getKey(), newlastValue));
return retNew;
}
static private String truncate(String newlastValue) {
newlastValue = newlastValue.substring(0, newlastValue.length() - 1);
while (newlastValue.endsWith(" ")) {
newlastValue = newlastValue.substring(0, newlastValue.length() - 1);
}
return newlastValue;
}
private void calculateWidths() {
if (lines != null) {
textproperwidth = fontMetrics.stringWidth(toPlain(lines));
}
ellipsisWidth = fontMetrics.stringWidth(ellipsis);
}
#Override
public void setFont(Font font) {
super.setFont(font);
fontMetrics = getFontMetrics(getFont());
calculateWidths();
}
#Override
public void setBorder(Border border) {
super.setBorder(border);
borderHorizontal = border.getBorderInsets(this).left + border.getBorderInsets(this).right;
}
}
When using my code in TableCellRenderer you need to resize immediately in constructor time, but when you do not have a size of the column:
public SimpleHTMLJLabel() {
...
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
int availablewidth = getWidth();
renderHtml(availablewidth);
}
});
}
protected void renderHtml(int availablewidth) {
if (lines == null || availablewidth == 0) return;
System.out.println("renderHtml " + textproperwidth + ">" + availablewidth);
if (textproperwidth > availablewidth - (insetsHorizontal + borderHorizontal)) {
List<Entry<String, String>> properList = clipText(lines);
while (properList.size() > 0 && fontMetrics.stringWidth(toPlain(properList)) + ellipsisWidth > availablewidth - (insetsHorizontal + borderHorizontal)) {
properList = clipText(properList);
}
SimpleHTMLJLabel.super.setText(HTML + toHtml(properList) + ellipsis);
} else {
SimpleHTMLJLabel.super.setText(HTML + toHtml(lines));
}
}
#Override
public void reshape(int x, int y, int w, int h) {
if (w > 0) renderHtml(w - 5);
super.reshape(x, y, w, h);
}
and in JTable
table = new JTable(model) {
#Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
TableColumn col = table.getColumnModel().getColumn(column);
javax.swing.table.DefaultTableCellRenderer.UIResource csss;
SimpleHTMLJLabel lab = new SimpleHTMLJLabel(((JLabel)
//lab.setXXX( c.getXXX)); for font bcolor, color, border, etc
lab.setText(c.getText());
lab.renderHtml(col.getWidth() - 5);
return lab;
}
};
One can reuse html-labell component to save GC
My goal is to create a filtered combobox.
I create an editable combobox. When the user enters text, the code searches matching items in the model. When the user leaves the combobox, the code selects the selected item from the list. Further, the text from the editor component gets the value of the selected item.
When the focus goes back to the combobox via shift + tab, the editor component gets the text of the selected element. After releasing the shift key, the keylistener runs. In this case, the editor component contains the text entered previously, e.g., the value set when the focus is lost.
How can I set the text of the editorComponent to make it persist?
Here's the code:
package de.ccw.reports.gui.incomingOrder.MyComboBox;
import java.awt.FlowLayout;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JTextField;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.text.JTextComponent;
import de.ccw.commons.ui.comp.XComboBox;
public class FilterableComboBox<E> extends XComboBox<E> {
ComboBoxModel<E> originalModel;
DefaultComboBoxModel<E> filteredModel;
JTextComponent editorComp;
JList<E> list;
public FilterableComboBox(ComboBoxModel<E> aModel) {
super();
receivePopupList();
originalModel = aModel;
filteredModel = new DefaultComboBoxModel<>();
setModel(filteredModel);
editorComp = (JTextComponent) getEditor().getEditorComponent();
editorComp.addFocusListener(new FocusAdapter() {
#Override
public void focusLost(FocusEvent e) {
System.out.println("focusLostStart=" + getSelectedItem() + "|" + editorComp.getText());
String text = editorComp.getText();
editorComp.setText("");
setSelectedItem(null);
if(list.getSelectedIndex() != -1 && text.isEmpty() == false){
setSelectedIndex(list.getSelectedIndex());
editorComp.setText(getSelectedItem().toString());
}
System.out.println("focusLostEnd=" + getSelectedItem() + "|" + editorComp.getText());
}
#Override
public void focusGained(FocusEvent e) {
System.out.println("focusGainedStart=" + getSelectedItem() + "|" + editorComp.getText());
E element = getSelectedItem();
String text = editorComp.getText();
performModelFilter(null);
showPopup();
setSelectedItem(element);
editorComp.setText(text);
editorComp.selectAll();
System.out.println("focusGainedEnd=" + getSelectedItem() + "|" + editorComp.getText());
}
});
editorComp.addKeyListener(new KeyAdapter() {
#Override
public void keyTyped(KeyEvent e) {
}
String filterBkp = "";
#Override
public void keyReleased(KeyEvent e) {
String filter = editorComp.getText();
System.out.println("keyReleased-" + filterBkp + filter);
if(filter.equals(filterBkp) == false)
refreshModel();
filterBkp = filter;
}
#Override
public void keyPressed(KeyEvent e) {
}
});
setEditable(true);
}
#SuppressWarnings("unchecked")
private void receivePopupList() {
BasicComboPopup popup = (BasicComboPopup) getAccessibleContext().getAccessibleChild(0);
list = popup.getList();
}
private void refreshModel() {
String filter = editorComp.getText();
performModelFilter(filter);
editorComp.setText(filter);
}
private void performModelFilter(String filter) {
System.out.println("performModelFilter-" + filter);
filteredModel.removeAllElements();
for(int i = 0; i < originalModel.getSize(); i++){
E element = originalModel.getElementAt(i);
String value = element.toString().toUpperCase();
if (filter == null || value.contains(filter.toUpperCase())) {
filteredModel.addElement(element);
}
}
}
public static void main(String args[]){
FilterableComboBox<String> combo = new FilterableComboBox<>(new DefaultComboBoxModel<>(new String[]{"abc", "def", "ghi", "jkl", "mnoabc", "pqrdef"}));
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new FlowLayout());
frame.add(new JTextField(20));
frame.add(combo);
frame.add(new JTextField(20));
frame.pack();
frame.setVisible(true);
}
}
I would like to develop a Swing application that may contain text field which will act like Terminal (the console from Unix/Linux systems).
Whenever a user entered a command, it will save into a container, then will be accessed thru up or down arrow (↑/ ↓).
I know it is possible but I still don't have the idea in implementing it right. My first implementation is to store the commands into a single text file, then accessed it in reverse order so that last input will be retrieve first.
My problem is that how will I know if I'm in the specified index (e.g. 2 ↑ arrows will go into length-2 index of file).
A very basic example but working fine.you can take user inputs when pressing enter and add them to a collection like arraylist ,vectors .i used vector in this example.when user hit enter store them in a collection and when user press (↑/ ↓) ,take from it back and show in textarea.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.KeyEvent;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.text.BadLocationException;
public class terminal extends JFrame {
Vector v = new Vector();
JTextArea area;
int pos = 0;
public terminal() {
setTitle("my terminal");
JPanel j = new JPanel();
setLayout(new GridLayout(1, 1));
setSize(400,250);
j.setLayout(new GridLayout(1, 1));
area = new JTextArea("terminal");
area.setBackground(Color.black);
area.setForeground(Color.white);
area.setCaretColor(Color.white);
area.addKeyListener(new java.awt.event.KeyAdapter() {
public void keyPressed(java.awt.event.KeyEvent evt) {
area(evt);
}
private void area(KeyEvent evt) {
int keyCode = evt.getKeyCode();
if (keyCode == 38) {
try {
String store = (String) v.get(v.size() - 1 - pos);
replacer(store);
} catch (Exception ex) {
//ex.printStackTrace();
}
pos++;
evt.consume();
} else if (keyCode == 40) {
try {
String store = (String) v.get(v.size() - 1 - pos);
replacer(store);
} catch (Exception ex) {
//ex.printStackTrace();
}
pos--;
evt.consume();
} else if (keyCode == 10) {
v.add(linetext());
}
}
});
j.add(area);
add(j);
setVisible(true);
}
void replacer(String rep) {
try {
int caretOffset = area.getCaretPosition();
int lineNumber = area.getLineOfOffset(caretOffset);
int startOffset = area.getLineStartOffset(lineNumber);
int endOffset = area.getLineEndOffset(lineNumber);
area.replaceRange(rep, startOffset, endOffset);
} catch (BadLocationException ex) {
//ex.printStackTrace();
}
}
String linetext() {
String text = null;
try {
JTextArea ta = area;
int offset = ta.getLineOfOffset(ta.getCaretPosition());
int start = ta.getLineStartOffset(offset);
int end = ta.getLineEndOffset(offset);
text = ta.getText(start, (end - start));
} catch (BadLocationException ex) {
//ex.printStackTrace();
}
return text;
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
terminal terminal = new terminal();
}
});
}
}
output>>