Get attributes of selected text in JTextPane - java

I'm trying to find out how to get the attributes of some selected text in JTextPane.
I found that the best solution is to do it with getInputAttributes() and CaretListener, but I have some issues with this implementation.
Here's my solution showing the attributes of the text on the last position of the caret, but not on the actual position of the caret. What am I doing wrong? Please.
Here is my SSCCE:
public class Testovani{
static JTextPane pane;
static JLabel label;
public static void main(String[] args) throws BadLocationException {
JFrame frame = new JFrame();
frame.setSize(350, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
pane = new JTextPane();
label = new JLabel();
pane.addCaretListener(new SelectionListener());
MutableAttributeSet attrs = new SimpleAttributeSet();
StyleConstants.setBold(attrs, true);
pane.getDocument().insertString(0, "\n", null);
pane.getDocument().insertString(0, "This is first row non bold", null);
pane.getDocument().insertString(0, "\n", null);
pane.getDocument().insertString(0, "This is second row bold", attrs);
pane.getDocument().insertString(0, "\n", null);
pane.getDocument().insertString(0, "This is third row bold", attrs);
pane.getDocument().insertString(0, "\n", null);
frame.add(pane);
frame.add(label, BorderLayout.SOUTH);
frame.setVisible(true);
}
private static class SelectionListener implements CaretListener{
#Override
public void caretUpdate(CaretEvent e) {
AttributeSet attrs =((StyledEditorKit)pane.getEditorKit()).getInputAttributes();
label.setText("Is bold: " + String.valueOf(StyleConstants.isBold(attrs)));
}
}}
And I have two bonus questions. Is this approach functional for selection or just for the position of caret? And what returns, if there is a selection of text where one part is bold and second part is not?

There is no expected behaviour for getting the attributes of a selection, simply because a selection can have character elements with each different attributes.
You have to understand that there is a difference between getInputAttributes which returns the attributes that the textPane calculated the best for a next input, and getCharacterAttributes that returns the attributes of the current caret position. In the case of a selection, the caret is the position where you ended the selection. It can be the getSelectionStart or getSelectionEnd given if you selected text from left to right or from right to left.
Anyway, what I advice you is to get the StyledDocument of your JTextPane, and then iterate through the character elements from getSelectionStart to getSelectionEnd :
for(int i=jtp.getSelectionStart(); i<jtp.getSelectionEnd(); i++) {
AttributeSet set = jtp.getStyledDocument().getCharacterElement(i).getAttributes();
//here, combine, analyse, do whatever you like with your AttributeSet
//You can use StyleConstants.xxx to analyse the attributes
}

Actually attributes of selection is complicated question and need your understanding of business requirements.
Suppose a fragment of text is selected and you need the selection font size. But
the fragment has 3 different pieces of text with 3 different sizes.
Selection start position is placed in the mid of 10pt text, then piece of text with 12pt size and selection ends in the mid of 14pt size fragment.
What size do you expect? 10, 12, 14 or (multiple)?
The simplest approach is to use inputAttributes.
By default the attributes are copied from caret position but of course you can add a caret listener and on each update check and fill the input attributes as you need according to your business logic (processing the multiple text fragments with different attributes).
UPDATE:
Try to wrap the AttributeSet attrs =((StyledEditorKit)pane.getEditorKit()).getInputAttributes(); in SwingUtilities.invokeLater() call

I used #Sharcoux 's solution with one change: I made sure that there there was always at least one character to iterate over. When there's no selection, getSelectionStart() == getSelectionEnd(). For this reason I would make the slight change of:
int iStart = jtp.getSelectionStart();
int iEnd = jtp.getSelectionEnd();
if(iStart > 0) iStart--;
else if(iEnd < jtp.getDocument().getEndPosition().getOffset()) iEnd++;
for(int i = iStart; i < iEnd; i++) {
AttributeSet set = jtp.getStyledDocument().getCharacterElement(i).getAttributes();
//here, combine, analyse, do whatever you like with your AttributeSet
//You can use StyleConstants.xxx to analyse the attributes
}
The only instance in which this changes nothing is when the document is completely empty, in which case getInputAttributes should work fine.

Related

identify line in a jtextpane when the row number is entered

public static void setJTextPaneFont(JTextPane jtp, Color c, int start_index,int end_index) {
MutableAttributeSet attrs = jtp.getInputAttributes();
StyleConstants.setForeground(attrs, c);
StyledDocument doc = jtp.getStyledDocument();
doc.setCharacterAttributes(start_index, end_index, attrs, false);
}
i created above code to change the forground of of specific word when i enter the start ndex and end index.But now i need to change the the forground when i pass the row number,start_index, and end index.Can you help me with this.How i identify a specific line when i enter the row number.
public void gotoStartOfLine(JTextComponent component, int line) {
Element root = component.getDocument().getDefaultRootElement();
line = Math.max(line, 1);
line = Math.min(line, root.getElementCount());
component.setCaretPosition(root.getElement(line - 1).getStartOffset());
}
i tried above code to go to specific row.but it didint work
How i identify a specific line when i enter the row number.
I think you mean you want the offset of the text for the given row. If so then take a look at the gotoStartOfLine() method from Text Utilities.
That is the code that sets the caret position will give you the starting offset of the line. Then you just add the start/end values to get the offsets of the text to highlight.
Look at using the javax.swing.text.Utilities class, especially the getRowStart(...) and getRowEnd(...) methods.

Get a component from a JTextPane through javax.swing.text.Element?

I am using a JTextPane to display characters and symbols, where the latter are represented by custom painted JComponents. For example, the text pane might show something like this:
The text pane is user editable and it is allowed for the user to add more symbols via a button at any position and as a replacement for selected text. I do this via the JTextPane.insertComponent() method. At some point in the application I need to know what is currently being displayed in the text pane, and by that I mean not only the entered text, but also the exact components contained within.
I went through extensive troubles with Positions and DocumentListeners to manage the content of my text pane, but I kept causing more problems than I was solving. That is why I finally decided, that my troubles are probably due to a design fault on my part, so I decided to see, if I can't get to my components through the text pane.
Searching through the documentation and the source code of AbstractDocument and other related classes, I found the interface javax.swing.text.Element. I then let my application output
for(int i = 0; i < textPane.getDocument().getLength(); i++) {
System.out.println(((StyledDocument) textPane.getDocument()).getCharacterElement(i));
}
which gave me:
LeafElement(content) 0,4
LeafElement(content) 0,4
LeafElement(content) 0,4
LeafElement(content) 0,4
LeafElement(component) 4,5
LeafElement(content) 5,9
LeafElement(content) 5,9
LeafElement(content) 5,9
LeafElement(content) 5,9
LeafElement(component) 9,10
Seeing that the LeafElements that I got do seem to have some kind of information about what is displayed at which position in the Document, I figured that it must be possible to get the actual content at that position. After searching for another half hour how to get the content each of the elements represent, I gave up and decided to post my question here, hoping that some of you might know how to accomplish this!?
I have seen this question where someone tries to access the components through textPane.getComponents(), which returns an array of components with the exact number of components actually contained in the JTextPane, but they are all of the type javax.swing.text.ComponentView$Invalidator, which is obviously of no use to me. Maybe I just don't see how to properly continue from here, because a cast to the original type of my symbol doesn't work.
tl;dr
How do I get a JComponent, which is inside the text of a JTextPane, and its position from the text pane?
You can traverse the text pane's StyledDocument to find elements that represent components or icons, as shown below.
BranchElement(section) 0,7
BranchElement(paragraph) 0,7
LeafElement(content) 0,4
LeafElement(icon) 4,5
class javax.swing.plaf.IconUIResource
LeafElement(component) 5,6
class javax.swing.JLabel
LeafElement(content) 6,7
SSCCE:
/**
* #see http://stackoverflow.com/a/15669307/230513
* #see http://stackoverflow.com/questions/2883413
*/
public class DocumentParse {
private static final String ELEM = AbstractDocument.ElementNameAttribute;
private static final String ICON = StyleConstants.IconElementName;
private static final String COMP = StyleConstants.ComponentElementName;
public static void main(String args[]) throws Exception {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTextPane jtp = new JTextPane();
StyledDocument doc = (StyledDocument) jtp.getDocument();
SimpleAttributeSet normal = new SimpleAttributeSet();
StyleConstants.setFontFamily(normal, "Serif");
StyleConstants.setFontSize(normal, 72);
StyleConstants.setForeground(normal, Color.blue);
doc.insertString(doc.getLength(), "Test", normal);
jtp.setSelectionStart(doc.getLength());
jtp.insertIcon(UIManager.getIcon("OptionPane.warningIcon"));
jtp.setSelectionStart(doc.getLength());
jtp.insertComponent(new JLabel("Label"));
jtp.setSelectionStart(doc.getLength());
ElementIterator iterator = new ElementIterator(doc);
Element element;
while ((element = iterator.next()) != null) {
System.out.println(element);
AttributeSet as = element.getAttributes();
if (as.containsAttribute(ELEM, ICON)) {
System.out.println(StyleConstants.getIcon(as).getClass());
}
if (as.containsAttribute(ELEM, COMP)) {
System.out.println(StyleConstants.getComponent(as).getClass());
}
}
f.add(jtp);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
The original component is the first (and only) child of the javax.swing.text.ComponentView$Invalidator as you can see from ComponentView.
You can get list of the invalidators and use their children to acccess inserted components.

Java Change Color of Element on JTextPane using StyledDocument

this is kinda overkill for me.. I am using a JTextPane for a chat, I have colors there.. What I want is, with reference to a element changing the color of it..
I am using StyledDocument, I have no clue how to do this..
Thanks in advance ;)
Use setCharacterAttributes(). Define desired color in a SimpleAttributeSet using StyleConstants.setBackground()/setForeground(). Use Element's start and end offsets for the offset and length.
If the last attribute is false only thouse attributes of Element which are defined in the SimpleAttributeSet are replaced.
Seems like what you asking for can be described in a single method, have a look :
private void appendToPane(JTextPane tp, String msg, Color c)
{
StyleContext sc = StyleContext.getDefaultStyleContext();
AttributeSet aset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, c);
aset = sc.addAttribute(aset, StyleConstants.FontFamily, "Lucida Console");
aset = sc.addAttribute(aset, StyleConstants.Alignment, StyleConstants.ALIGN_JUSTIFIED);
int len = tp.getDocument().getLength();
tp.setCaretPosition(len);
tp.setCharacterAttributes(aset, false);
tp.replaceSelection(msg);
}
Just try to pass the reference of your JTextPane along with your String and respective Colour that you want to provide, to this method and see the magic :-)

Increasing the font size of a JTextPane that displays HTML text

Lets say that I have a JTextPane that is showing a HTML document.
I want that, on the press of a button, the font size of the document is increased.
Unfortunately this is not as easy as it seems... I found a way to change the font size of the whole document, but that means that all the text is set to the font size that I specify. What I want is that the font size is increased in a proportional scale to what was already in the document.
Do I have to iterate over every element on the document, get the font size, calculate a new size and set it back? How can I do such an operation? What is the best way?
In the example that you linked to you will find some clues to what you are trying to do.
The line
StyleConstants.setFontSize(attrs, font.getSize());
changes the font size of the JTextPane and sets it to the size of the font that you pass as a parameter to this method. What you want to to set it to a new size based on the current size.
//first get the current size of the font
int size = StyleConstants.getFontSize(attrs);
//now increase by 2 (or whatever factor you like)
StyleConstants.setFontSize(attrs, size * 2);
This will cause the font of the JTextPane double in size. You could of course increase at a slower rate.
Now you want a button that will call your method.
JButton b1 = new JButton("Increase");
b1.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
increaseJTextPaneFont(text);
}
});
So you can write a method similar to the one in the example like this:
public static void increaseJTextPaneFont(JTextPane jtp) {
MutableAttributeSet attrs = jtp.getInputAttributes();
//first get the current size of the font
int size = StyleConstants.getFontSize(attrs);
//now increase by 2 (or whatever factor you like)
StyleConstants.setFontSize(attrs, size * 2);
StyledDocument doc = jtp.getStyledDocument();
doc.setCharacterAttributes(0, doc.getLength() + 1, attrs, false);
}
You could probably use css and modify only the styles font.
Since it renders th HTML as it is, changing the css class may be enough.
After exploring for a long time, I've found a way to zoom the fonts in a JTextPane that displays HTML in and out.
Here's the member function that enables a JTextPane to scale the fonts. It does not handle the images inside the JTextPane.
private void scaleFonts(double realScale) {
DefaultStyledDocument doc = (DefaultStyledDocument) getDocument();
Enumeration e1 = doc.getStyleNames();
while (e1.hasMoreElements()) {
String styleName = (String) e1.nextElement();
Style style = doc.getStyle(styleName);
StyleContext.NamedStyle s = (StyleContext.NamedStyle) style.getResolveParent();
if (s != null) {
Integer fs = styles.get(styleName);
if (fs != null) {
if (realScale >= 1) {
StyleConstants.setFontSize(s, (int) Math.ceil(fs * realScale));
} else {
StyleConstants.setFontSize(s, (int) Math.floor(fs * realScale));
}
style.setResolveParent(s);
}
}
}
}

How do you set the tab size in a JEditorPane?

A JTextArea's tab size can easily be set using setTabSize(int).
Is there a similar way to do it with a JEditorPane?
Right now, text with tabs in my pane looks like:
if (stuff){
more stuff;
}
And, I'd prefer a much smaller tab stop:
if (stuff){
more stuff;
}
As JEditorPane is designed to support different kinds of content types, it does not provide a way to specify a "tab size" directly, because the meaning of that should be defined by the content model.
However when you use a model that's a PlainDocument or one of its descendants, there is a "tabSizeAttribute" that provides what you are looking for.
Example:
JEditorPane pane = new JEditorPane(...);
...
Document doc = pane.getDocument();
if (doc instanceof PlainDocument) {
doc.putProperty(PlainDocument.tabSizeAttribute, 8);
}
...
From the Javadoc:
/**
* Name of the attribute that specifies the tab
* size for tabs contained in the content. The
* type for the value is Integer.
*/
public static final String tabSizeAttribute = "tabSize";
In case anyone's using a StyledDocument (The link on the other answer died)
You create a TabSet which is an array of TabStops. In my case I only cared about the 1st tab, and I wanted it 20px from the left, so this code worked for me:
StyleContext sc = StyleContext.getDefaultStyleContext();
TabSet tabs = new TabSet(new TabStop[] { new TabStop(20) });
AttributeSet paraSet = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.TabSet, tabs);
pane.setParagraphAttributes(paraSet, false);
Took me a while to figure this out.
And decided to use TabStop's in a TabSet that have calculated width based on the font size.
This has to be reset when ever the font size changes (in the paint() method of the JEditPane).
Complicated stuff! :(

Categories