Skip to main content

A Buffered TextReader using a JEditorPane

5 replies [Last post]
i30817
Offline
Joined: 2006-05-02
Points: 0

I've recently (now) finished the rudiments of text reader using a jEditor pane subclass. Yeah, boring i know. It doesn't support scrolling or editing, and only moves in "pages" or by document index, but i think its pretty nice for emulating a page:
1) The only lines are those that are completly visible and it copes with resize well (JEditorPane chokes on it) because it is only rendering a small subset of the views.
2) The window can be resized and no matter if you move forward or backward, the visible text will either start after the last word you read (forward), or end before the first word of the old page.

I'm going to post the code here, and i'm open to suggestions (i'd like a clean way to implement the setDisplayableText() function):
There are tree classes:

/**
*@author Paulo Levi
*/
package ui;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;

import javax.swing.JTextPane;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.BoxView;
import javax.swing.text.ComponentView;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Element;
import javax.swing.text.IconView;
import javax.swing.text.LabelView;
import javax.swing.text.ParagraphView;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;

public class MovingPane extends JTextPane {

/**
* The index in the buffer
*/
private int index;

/**
* The maximum displayble text in this display envioroment
*/
private int displayableText;

/**
* The buffer
*/
private StyledDocument buffer;

/**
* An observer of the visible text
*/
private TextPaintObserver observer;

/**
* A Editor kit that this scrollpane requires to be installed.
*/
protected MovingEditorKit kit = new MovingEditorKit();

/**
* Indicates there was a up movement before, needed because the
* up movement can turn the index inconsistent if the component is
* resized afterwards.
* Used in getIndex() and reset in cleanIndex(), set in backwardDisplayText
*/
private boolean isDirty;

private static final long serialVersionUID = -7828351555750309111L;

public MovingPane() {
super();
setEditorKit(kit);
setEditable(false);
setHighlighter(null);
setFocusable(false);
setEnabled(false);
setDisabledTextColor(Color.BLACK);
this.observer = new TextPaintObserver(this);
setDisplayableText();
}

/**
* Moving text methods
*/

/**
* Used to return a consistent index, at this exact moment.
*
* @return the calculated index in the buffer
*/
public int getIndex(){
if(isDirty)
return index - (observer.getResult().getInterval()+1);
else return index;
}

/**
* Cleans the index when called. Only call this when you know
* you are going to move the index afterwards
*/
public void cleanIndex(){
index = getIndex();
isDirty = false;
}

/**
* This method sets a document as a buffer to display contents with a paging
* stategy.
*
* @param StyledDocument d :
* The document that serves as a memory based buffer to the
* textcomponent
* @param int position:
* The position that the text should start to display
* @throws BadLocationException if the position is out of bounds
*/
public void setDocumentBuffer(StyledDocument d, int position) throws BadLocationException {
buffer = d;
index = position;
//System.out.println("forward index : "+index);
setDisplayableText();
DefaultStyledDocument doc = new DefaultStyledDocument();
forwardConstructDocument(doc, index, getDisplayableText());
setDocumentForward(doc, true);
}

/**
* Sets the document with a order of layout
*/
private void setDocumentForward(StyledDocument doc, boolean layoutForward){
kit.setLayoutForward(layoutForward);
setDocument(doc);
}

/**
* @return : A estimative of an length of text beyond the upper-bound
* of the visible text in this scrollpane
*/
protected int getDisplayableText(){
return Math.min(buffer.getLength(),displayableText);
}

/**
* This method set the minimum number of text copied from the buffer
* to the component document. It uses a fill algorithm to guess a lower
* bound number of chars that the container can support in the current
* graphics context
*/
protected void setDisplayableText(){
displayableText = 8000;
}

/**
* This method moves the text forward using a paging stategy
*
*/
public void forwardDisplayText() {
/**
* If the component is dirty, we need to update the index before we move
*/
cleanIndex();

ObservedResult result = observer.getResult();

/**
* What happens here is that all abstract documents have an addicional
* character applied at the end (if we are at the end of the document in the
* pane, the get interval method returns + 1)
*/
if(buffer == null || (index + result.getInterval()) == (buffer.getLength()+1) )
return;

//add the observed interval + 1 to start at the next block
index += result.getInterval() + 1;
//System.out.println("forward index : "+index);
DefaultStyledDocument doc = new DefaultStyledDocument();
try {
forwardConstructDocument(doc, index, getDisplayableText());
} catch (BadLocationException e) {
//Never happens here...
e.printStackTrace();
}

setDocumentForward(doc, true);
}

/**
* This method moves the text backward using a paging stategy
*
*/
public void backwardDisplayText() {
/**
* If the component is dirty, we need to update the index before we move
*/
cleanIndex();

if(buffer == null || index == 0)
return;

DefaultStyledDocument doc = new DefaultStyledDocument();
try {
backwardConstructDocument(doc, index, getDisplayableText());
} catch (BadLocationException e) {
//Never happens here...
e.printStackTrace();
}

setDocumentForward(doc, false);
isDirty = true;

}

/**
* These auxiliar methods take the text and styles from the
* document buffer and apply them to the container
*
*
* @param doc : The document to apply the text
* @param start : The index where to start to get the text in the
* document buffer
* @param last : The minimum lenght of styled text to copy from the document
* buffer to the doc
*/
private void forwardConstructDocument(StyledDocument doc, int start, int last) throws BadLocationException{

Element e;
int var = start;
int len;
int aux = Math.min(start + last,buffer.getLength());

while(var <= aux) {
e = buffer.getCharacterElement(var);
len = e.getEndOffset() - var;
doc.insertString(doc.getLength(),buffer.getText(var,len),e.getAttributes());
var = e.getEndOffset();
}
}

/**
* These auxiliar methods take the text and styles from the
* document buffer and apply them to the container
*
*
* @param doc : The document to apply the text
* @param start : The index where to end the text taken from
* the document buffer
* @param length : The lenght of styled text to copy from the document
* buffer to the doc
*/
private void backwardConstructDocument(StyledDocument doc, int start, int length) throws BadLocationException{
int var = Math.max(start - length,0);
int trueLength = Math.max(start - var - 1, 0);
forwardConstructDocument(doc,var,trueLength);
doc.remove(trueLength,doc.getLength()-trueLength);
}

class MovingEditorKit extends StyledEditorKit implements ViewFactory{

/**
* Sets if the text is going to be layed-out forward
*/
private boolean layoutForward;

private static final long serialVersionUID = 8876216256327929774L;

/**
* InvertedBoxView for layout out the text backward
*/
class InvertedBoxView extends BoxView {

public InvertedBoxView(Element elem, int axis){
super(elem, axis);
}

public void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans){
super.layoutMajorAxis(targetSpan,axis,offsets,spans);
int aux = targetSpan;
for (int i = getViewCount()-1; i >= 0; i--){
aux = aux - spans[i];
offsets[i] = aux;
}
}

}

/**
* Counting paragraph view
*/
class CountingParagraphView extends ParagraphView {
private Rectangle line;
private Rectangle win;

public CountingParagraphView(Element elem){
super(elem);
//strategy = new MyFlowStrategy(); TODO
line = new Rectangle();
win = new Rectangle();
}

/**
* Overrides the normal paint method for eliminating
* cut lines at the top and bottom of the viewport
* and for counting the number of visible chars from
* the model (without added \n from line-wrap,or tabs)
* the visible chars are stored in an observer of
* the enclosing class who this method updates.
* Note that it can't be passed in the constructor without an
* exception since the super constructor calls paint, before
* the observer is assigned.
*
* @param g
* @param a
*/
public void paint(Graphics g, Shape a) {
Rectangle box = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();

getContainer().getParent().getBounds(win);

int n = getViewCount();
int x = box.x;
int y = box.y;

for (int i = 0; i < n; i++) {
line.x = x + getOffset(X_AXIS, i);
line.y = y + getOffset(Y_AXIS, i);
line.width = getSpan(X_AXIS, i);
line.height = getSpan(Y_AXIS, i);
View view = getView(i);

//java.awt.Color c = g.getColor();
//g.setColor(java.awt.Color.BLUE);
//g.draw3DRect(line.x,line.y,line.width,line.height,true);
//g.setColor(c);

if ( containsOrEqual( win , line) ) {
observer.observe(view,line);

paintChild(g, line, i);
}
}
}

/**
* The stupid rectangle class considers that a rectangle with
* a zero dimension can't be contained in any other.
* This is the workaround.
*
* @return: true if rectangle a contains or is equal to rectangle b
*/
private boolean containsOrEqual(Rectangle a, Rectangle b){

double bMinX = b.getMinX();
double bMaxX = b.getMaxX();
double bMinY = b.getMinY();
double bMaxY = b.getMaxY();
double aMinX = a.getMinX();
double aMaxX = a.getMaxX();
double aMinY = a.getMinY();
double aMaxY = a.getMaxY();

return (
bMinX >= aMinX &&
bMinY >= aMinY &&
bMinX <= aMaxX &&
bMinY <= aMaxY &&
bMaxX >= aMinX &&
bMaxY >= aMinY &&
bMaxX <= aMaxX &&
bMaxY <= aMaxY);
}

}

/**
* VIEWFACTORY METHODS
*/
public ViewFactory getViewFactory()
{
return this;
}

public View create(Element elem) {
String name = elem.getName();

if (name != null) {
if (name.equals(AbstractDocument.ContentElementName)) {
return new LabelView(elem);
} else if (name.equals(AbstractDocument.ParagraphElementName)) {
return new CountingParagraphView(elem);
} else if (name.equals(AbstractDocument.SectionElementName)) {
if(isLayoutForward())
return new BoxView(elem, View.Y_AXIS);
else
return new InvertedBoxView(elem, View.Y_AXIS);
} else if (name.equals(StyleConstants.ComponentElementName)) {
return new ComponentView(elem);
}else if (name.equals(StyleConstants.IconElementName)) {
return new IconView(elem);
}
}
return new LabelView(elem);
}

/**
* Indicates if we are going to layout the text forward or backward
*
* @return : if we are going layout the text forward
*/
public boolean isLayoutForward() {
return this.layoutForward;
}

/**
* Sets how the text is going to be layed out
* @param layoutForward if true the text is layed out from
* beggining to end, otherwise the opposite
*/
public void setLayoutForward(boolean layoutForward) {
this.layoutForward = layoutForward;
}

}

}

Class 2:

/**
* @author Paulo Levi
*/
package ui;

import java.awt.Rectangle;

import java.lang.reflect.InvocationTargetException;

import javax.swing.SwingUtilities;

import javax.swing.text.JTextComponent;

import javax.swing.text.View;

public class TextPaintObserver {

private int first;

private int last;

private Rectangle visible;

private Rectangle dirty;

private JTextComponent text;

public TextPaintObserver(JTextComponent text) {

reset();

this.text = text;

this.dirty = new Rectangle();

}

private void setFirst(int in) {

this.first = in;

}

private void setLast(int in) {

this.last = in;

}

private int getFirst() {

return first;

}

private int getLast() {

return last;

}

/**

* Resets the state of the observed values

*/

private void reset(){

first = Integer.MAX_VALUE;

last = Integer.MIN_VALUE;

visible = null;

}

/**

* Gets the visible Rectangle, can be null if there is no views

*/

private Rectangle getVisibleRectangle(){

return visible;

}

/**

* Gets the result of the computation

*/

public ObservedResult getResult(){

paint();

return new ObservedResult(getFirst(), getLast(), getVisibleRectangle());

}

/**

* Causes the state of the visible length to

* be updated

*

*/

private void paint(){

reset();

text.getParent().getBounds(dirty);

//System.out.println("Dirty area "+dirty);

if(SwingUtilities.isEventDispatchThread()){

text.paintImmediately(dirty);

}

else{

try {

SwingUtilities.invokeAndWait(

new Runnable(){

public void run(){

text.paintImmediately(dirty);

}

}

);

} catch (InterruptedException e) {

e.printStackTrace();

} catch (InvocationTargetException e) {

e.printStackTrace();

}

}

}

public void observe(View view, Rectangle lineOfText) {

int startOffSet = view.getStartOffset();

int endOffSet = view.getEndOffset();

if( startOffSet < getFirst()){

setFirst(startOffSet);

}

if( endOffSet > getLast()){

setLast(endOffSet);

}

if(visible == null)

visible = lineOfText;

else

SwingUtilities.computeUnion(lineOfText.x, lineOfText.y, lineOfText.width, lineOfText.height, visible);

}

}

class 3:

/**
*@author Paulo Levi
*/
package ui;

import java.awt.Rectangle;

public class ObservedResult {

private int first, last;

private Rectangle visibleRectangle;

public ObservedResult(int first, int last, Rectangle visibleRectangle) {

this.first = first;

this.last = last;

this.visibleRectangle = ( visibleRectangle == null ) ? new Rectangle() : (Rectangle) visibleRectangle.clone();

}

/**

* What happens here is that the endoffset and startoffset of any contigous

* views are the same. The effect is as if the endoffset of any view

* (or set of views)has a + 1 added to it

* We correct it so we don't astonish the clients of the class

*

* @return the observed interval

*/

public int getInterval(){

return last - first - 1;

}

/**

* The rectangle that encapsulates all the views observed

* May be useful for moving by coordinates

*

* @return the joining of all the views rectangles

*/

public Rectangle getVisibleRectangle() {

return visibleRectangle;

}

}

If you think this code is useful feel free to use it.
I'd like to post a jar demo, but i don't know how!

Message was edited by: i30817

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Patrick Wright

Hi

Can you give me a tip on how to use this? I have a MovingPane inside a
panel (just inside the contentPane for a frame), added a back/forward
button calling mp.backwardDisplayText() and mp.forwardDisplayText(), I
load a long text document and call mp.setText(). I get only the last
page of text, and pagination doesn't work--inside a scroll pane, only
the first page is showing as well (as you noted, scrolling not
supported). setCaretPosition() doesn't seem to do anything.

Roughly this:
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);

final MovingPane mp = new MovingPane();

JPanel control = new JPanel();
control.setLayout(new FlowLayout());
JButton back = new JButton("Back");
back.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Backward!");
mp.backwardDisplayText();
}
});
control.add(back);

JButton fwd = new JButton("Forward");
fwd.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Forward!");
mp.forwardDisplayText();
}
});
control.add(fwd);

frame.getContentPane().add(control, BorderLayout.NORTH);

frame.getContentPane().add(mp, BorderLayout.CENTER);
try {
String s = GeneralUtil.inputStreamToString(new
FileInputStream(new File(file)));
// make sure I got the whole doc
System.out.println("======\n" + s + "\n=======");
mp.setText(s);
mp.setCaretPosition(0);
} catch (IOException e) {
e.printStackTrace(); //To change body of catch statement
use File | Settings | File Templates.
}

frame.setVisible(true);

Thanks!
Patrick

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Patrick Wright

Hmm, got this to work

String s = GeneralUtil.inputStreamToString(new
FileInputStream(new File(file)));
StyledDocument pd = new DefaultStyledDocument();
Reader r = new StringReader(s);
EditorKit kit = mp.getEditorKit();
kit.read(r, pd, 0);
mp.setDocumentBuffer(pd, 0);

This seems too much work...is there an easier way?

Some comments:

- nice idea!

- would be nice to have a convenience method for canForward() and canBack()

- is there a way to get a count of "pages" (to show current page/total count)

Thanks
Patrick

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

i30817
Offline
Joined: 2006-05-02
Points: 0

Not yet, but i can add a convenience method to do that.
I didn't notice, since i'm using a custom class to extract only the text from a html file. But is not presentable :).
Wonder how it will work with a real html file, can you test it (a image and text or something)?

What is bothering me right now is that i extend JEditorPane, and a lot of methods are useless now. I really should compose it in a new JComponent, but this need a custom UI object (i think i can't use the jeditorpane composed one).

Sure I can place i boolean canForward()
its just this piece of code:

public boolean canForward(){
return getIndex() < (buffer.lenght()+1);
}

public boolean canBack(){
return getIndex() > 0;
}

I think this will work.

A more useful way of setting the dirty state is setting it
on the method

/**
* Sets the document with a order of layout
*/
private void setDocumentForward(StyledDocument doc, boolean layoutForward){
kit.setLayoutForward(layoutForward);
isDirty = !layoutforward;
setDocument(doc);
}

and erase the line isDirty = true in backwardDisplayText()

There is no way to count the "pages" since the whole point of the component is not to do processing about that but just move as needed. A resize would have to calculate everything again. But you can see the percentage value of the text, that is imo equivalent.

public double getPercentage(){
return getIndex()/buffer.lenght();
}

It would be nice too to have 2 colums at will, and a then clicking in a colums would move forward or backward consoant it was the second or first colum. But i don't have a clue how to do multiple colums that respect resize.

Message was edited by: i30817

Message was edited by: i30817

Message was edited by: i30817

i30817
Offline
Joined: 2006-05-02
Points: 0

Ok i've extracted my document reader code to the movingpane and you can use read(File) to read a html or text file, and i've added the sugested methods and removed the badlocationexceptions (if an incorrect index is given the function now crops it)

The only class modified is MovingPane, but there are 2 new classes the DocumentFactory, and the HTMLCallback, that parse and return the text from html (perserving italic and bold).

package ui;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;
import java.io.File;
import java.io.IOException;

import javax.swing.JTextPane;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.BoxView;
import javax.swing.text.ComponentView;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Element;
import javax.swing.text.IconView;
import javax.swing.text.LabelView;
import javax.swing.text.ParagraphView;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;

import ui.parsers.DocumentFactory;

public class MovingPane extends JTextPane {

/**
* The index in the buffer
*/
private int index;

/**
* The maximum displayble text in this display envioroment
*/
private int displayableText;

/**
* The buffer
*/
private StyledDocument buffer;

/**
* An observer of the visible text
*/
private TextPaintObserver observer;

/**
* A Editor kit that this scrollpane requires to be installed.
*/
protected MovingEditorKit kit = new MovingEditorKit();

/**
* Indicates there was a up movement before, needed because the
* up movement can turn the index inconsistent if the component is
* resized afterwards.
* Used in getIndex() and reset in cleanIndex(),
* set in setDocument(StyledDocument d, boolean forwardLayout)
*/
private boolean isDirty;

private static final long serialVersionUID = -7828351555750309111L;

public MovingPane() {
super();
setEditorKit(kit);
setEditable(false);
setHighlighter(null);
setFocusable(false);
setEnabled(false);
setDisabledTextColor(Color.BLACK);
this.observer = new TextPaintObserver(this);
setDisplayableText();
}

/**
* Moving text methods
*/

/**
* Gets where we are on the text in a percentage
*/
public double getPercentage(){
if(buffer != null)
return getIndex()/(buffer.getLength()+1);

return 0;
}

/**
* Used to return a consistent index, at this exact moment.
*
* @return the calculated index in the buffer
*/
public int getIndex(){
if(isDirty)
return index - (observer.getResult().getInterval()+1);
else return index;
}

/**
* Cleans the index when called. Only call this when you know
* you are going to move the index afterwards
*/
private void cleanIndex(){
index = getIndex();
isDirty = false;
}

/**
* Sets the document with a order of layout
*/
private void setDocumentForward(StyledDocument doc, boolean layoutForward){
kit.setLayoutForward(layoutForward);
isDirty = !layoutForward;
setDocument(doc);
}

/**
* See if the index and the buffer permit the text to go backwards
*
* @return : boolean the text can go backward
*/
public boolean canGoBackward(){
return getIndex() > 0;
}

/**
* See if the index and the buffer permit the text to go forward
*
* @return : boolean the text can go forward
*/
public boolean canGoForward(){
return getIndex() < (buffer.getLength() + 1);
}

/**
* This method sets a document as a buffer to display contents with a paging
* stategy.
*
* @param StyledDocument d :
* The document that serves as a memory based buffer to the
* textcomponent
* @param int position:
* The position that the text should start to display,
* truncated to a legal value if < 0 or > d.getLength() + 1
*/
public void setDocumentBuffer(StyledDocument d, int position) {
buffer = d;
index = Math.max(0,Math.min(position, buffer.getLength()+1));
//System.out.println("forward index : "+index);
setDisplayableText();
DefaultStyledDocument doc = new DefaultStyledDocument();
forwardConstructDocument(doc, index, getDisplayableText());
setDocumentForward(doc, true);
}

/**
* Parses a file and sets it as the document buffer
* TODO : Use swingworker
*
* @param file
* @throws IOException : if there is an exception reading the document
*/

public void read(File file, int position) throws IOException {
setDocumentBuffer(new DocumentFactory().create(file), position);
}

/**
* @return : A estimative of an length of text beyond the upper-bound
* of the visible text in this scrollpane
*/
protected int getDisplayableText(){
return Math.min(buffer.getLength(),displayableText);
}

/**
* This method set the minimum number of text copied from the buffer
* to the component document. It uses a fill algorithm to guess a lower
* bound number of chars that the container can support in the current
* graphics context
*/
protected void setDisplayableText(){
displayableText = 8000;
}

/**
* This method moves the text forward using a paging stategy
*
*/
public void forwardDisplayText() {
/**
* If the component is dirty, we need to update the index before we move
*/
cleanIndex();

ObservedResult result = observer.getResult();

/**
* What happens here is that all abstract documents have an addicional
* character applied at the end (if we are at the end of the document in the
* pane, the get interval method returns + 1)
*/
if(buffer == null || (index + result.getInterval()) == (buffer.getLength()+1) )
return;

//add the observed interval + 1 to start at the next block
index += result.getInterval() + 1;
//System.out.println("forward index : "+index);
DefaultStyledDocument doc = new DefaultStyledDocument();

forwardConstructDocument(doc, index, getDisplayableText());

setDocumentForward(doc, true);
}

/**
* This method moves the text backward using a paging stategy
*
*/
public void backwardDisplayText() {
/**
* If the component is dirty, we need to update the index before we move
*/
cleanIndex();

if(buffer == null || index == 0)
return;

DefaultStyledDocument doc = new DefaultStyledDocument();
backwardConstructDocument(doc, index, getDisplayableText());
setDocumentForward(doc, false);
}

/**
* These auxiliar methods take the text and styles from the
* document buffer and apply them to the container
*
*
* @param doc : The document to apply the text
* @param start : The index where to start to get the text in the
* document buffer
* @param last : The minimum lenght of styled text to copy from the document
* buffer to the doc
*/
private void forwardConstructDocument(StyledDocument doc, int start, int last) {

Element e;
int var = Math.max(start,0);
int len;
int aux = Math.min(start + last,buffer.getLength());

try{
while(var <= aux) {
e = buffer.getCharacterElement(var);
len = e.getEndOffset() - var;
doc.insertString(doc.getLength(),buffer.getText(var,len),e.getAttributes());
var = e.getEndOffset();
}
}catch(BadLocationException ble){
//Never happens
ble.printStackTrace();
}
}

/**
* These auxiliar methods take the text and styles from the
* document buffer and apply them to the container
*
*
* @param doc : The document to apply the text
* @param start : The index where to end the text taken from
* the document buffer
* @param length : The lenght of styled text to copy from the document
* buffer to the doc
*/
private void backwardConstructDocument(StyledDocument doc, int start, int length) {
int var = Math.max(start - length,0);
int trueLength = Math.max(start - var - 1, 0);
forwardConstructDocument(doc,var,trueLength);
try{
doc.remove(trueLength,doc.getLength()-trueLength);
}catch(BadLocationException ble){
//Never happens
ble.printStackTrace();
}
}

class MovingEditorKit extends StyledEditorKit implements ViewFactory{

/**
* Sets if the text is going to be layed-out forward
*/
private boolean layoutForward;

private static final long serialVersionUID = 8876216256327929774L;

/**
* InvertedBoxView for layout out the text backward
*/
class InvertedBoxView extends BoxView {

public InvertedBoxView(Element elem, int axis){
super(elem, axis);
}

public void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans){
super.layoutMajorAxis(targetSpan,axis,offsets,spans);
int aux = targetSpan;
for (int i = getViewCount()-1; i >= 0; i--){
aux = aux - spans[i];
offsets[i] = aux;
}
}

}

/**
* Counting paragraph view
*/
class CountingParagraphView extends ParagraphView {
private Rectangle line;
private Rectangle win;

public CountingParagraphView(Element elem){
super(elem);
//strategy = new MyFlowStrategy(); TODO
line = new Rectangle();
win = new Rectangle();
}

/**
* Overrides the normal paint method for eliminating
* cut lines at the top and bottom of the viewport
* and for counting the number of visible chars from
* the model (without added \n from line-wrap,or tabs)
* the visible chars are stored in an observer of
* the enclosing class who this method updates.
* Note that it can't be passed in the constructor without an
* exception since the super constructor calls paint, before
* the observer is assigned.
*
* @param g
* @param a
*/
public void paint(Graphics g, Shape a) {
Rectangle box = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();

getContainer().getParent().getBounds(win);

int n = getViewCount();
int x = box.x;
int y = box.y;

for (int i = 0; i < n; i++) {
line.x = x + getOffset(X_AXIS, i);
line.y = y + getOffset(Y_AXIS, i);
line.width = getSpan(X_AXIS, i);
line.height = getSpan(Y_AXIS, i);
View view = getView(i);

//java.awt.Color c = g.getColor();
//g.setColor(java.awt.Color.BLUE);
//g.draw3DRect(line.x,line.y,line.width,line.height,true);
//g.setColor(c);

if ( containsOrEqual( win , line) ) {
observer.observe(view,line);

paintChild(g, line, i);
}
}
}

/**
* The stupid rectangle class considers that a rectangle with
* a zero dimension can't be contained in any other.
* This is the workaround.
*
* @return: true if rectangle a contains or is equal to rectangle b
*/
private boolean containsOrEqual(Rectangle a, Rectangle b){

double bMinX = b.getMinX();
double bMaxX = b.getMaxX();
double bMinY = b.getMinY();
double bMaxY = b.getMaxY();
double aMinX = a.getMinX();
double aMaxX = a.getMaxX();
double aMinY = a.getMinY();
double aMaxY = a.getMaxY();

return (
bMinX >= aMinX &&
bMinY >= aMinY &&
bMinX <= aMaxX &&
bMinY <= aMaxY &&
bMaxX >= aMinX &&
bMaxY >= aMinY &&
bMaxX <= aMaxX &&
bMaxY <= aMaxY);
}

}

/**
* VIEWFACTORY METHODS
*/
public ViewFactory getViewFactory()
{
return this;
}

public View create(Element elem) {
String name = elem.getName();

if (name != null) {
if (name.equals(AbstractDocument.ContentElementName)) {
return new LabelView(elem);
} else if (name.equals(AbstractDocument.ParagraphElementName)) {
return new CountingParagraphView(elem);
} else if (name.equals(AbstractDocument.SectionElementName)) {
if(isLayoutForward())
return new BoxView(elem, View.Y_AXIS);
else
return new InvertedBoxView(elem, View.Y_AXIS);
} else if (name.equals(StyleConstants.ComponentElementName)) {
return new ComponentView(elem);
}else if (name.equals(StyleConstants.IconElementName)) {
return new IconView(elem);
}
}
return new LabelView(elem);
}

/**
* Indicates if we are going to layout the text forward or backward
*
* @return : if we are going layout the text forward
*/
public boolean isLayoutForward() {
return this.layoutForward;
}

/**
* Sets how the text is going to be layed out
* @param layoutForward if true the text is layed out from
* beggining to end, otherwise the opposite
*/
public void setLayoutForward(boolean layoutForward) {
this.layoutForward = layoutForward;
}

}

}

/*
* ParserVisitor.java
*
* Created on 7 de Janeiro de 2006, 1:55
*/

package ui.parsers;

import javax.swing.text.StyledDocument;
import javax.swing.text.DefaultStyledDocument;
import java.io.File;
import java.io.InputStreamReader;
import java.io.FileInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import javax.swing.text.ChangedCharSetException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.text.html.parser.ParserDelegator;
import javax.swing.text.html.HTMLEditorKit.ParserCallback;

/**
*
* @author Paulo
*/
public class DocumentFactory {
private ParserDelegator parser = null;
private ParserCallback callback = null;
private BufferedReader in = null;
private static Pattern charsetPattern = Pattern.compile( "charset\\s*=\\s*([^\\s;]+)\\s*;?", Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE);

public StyledDocument create(File file) throws IOException {
String name = file.toString().toLowerCase();

if(name.endsWith(".html") || name.endsWith(".htm"))
{
return parseHTML(file);
}
else if (name.endsWith(".rtf"))
{
return parseRTF(file);
}
else {
throw new IllegalArgumentException("Can't read file type");
}
}

private StyledDocument parseHTML(File file) throws IOException {

in = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
StyledDocument doc = new DefaultStyledDocument();
parser = new ParserDelegator();
callback = new HTMLCallBack(doc);

try{
parser.parse(in, callback, false);
} catch (ChangedCharSetException ccse){

Matcher mt = charsetPattern.matcher(ccse.getCharSetSpec());
String charset = (mt.find()) ? mt.group(1) : "ISO-8859-1";
in = new BufferedReader(new InputStreamReader(new FileInputStream(file), charset));
doc = new DefaultStyledDocument();
callback = new HTMLCallBack(doc);
parser.parse(in, callback, true);
}
finally{
in.close();
}

return doc;
}

private StyledDocument parseRTF(File file) throws IOException {

//TODO
return null;
}

}

package ui.parsers;

import javax.swing.text.html.HTML;

import javax.swing.text.html.HTMLEditorKit;

import javax.swing.text.MutableAttributeSet;

import javax.swing.text.StyledDocument;

import javax.swing.text.BadLocationException;

import javax.swing.text.SimpleAttributeSet;

import javax.swing.text.StyleConstants;

class HTMLCallBack extends HTMLEditorKit.ParserCallback {

StyledDocument doc;

MutableAttributeSet memory;

HTML.Tag lastTag;

HTML.Tag firstIgnorable;

boolean ignoreIt;

public HTMLCallBack (StyledDocument d){

doc = d;

memory = new SimpleAttributeSet();

lastTag = null;

firstIgnorable = null;

ignoreIt = false;

}

public void handleText(char[] text, int position) {

int end = doc.getLength();

try{

if(!ignoreIt) {

doc.insertString(end, new String(text), memory);

doc.setCharacterAttributes(end,doc.getLength(),memory,false);

}

} catch (BadLocationException ble){ble.printStackTrace();}

}

public void handleEndTag(HTML.Tag t, int position) {

try{

if (t.isBlock()) {

doc.insertString(doc.getLength(), "\n\n", null);

}

else if (t.breaksFlow()) {

doc.insertString(doc.getLength(), "\n", null);

}

} catch (BadLocationException ble){ble.printStackTrace();}

if(t == HTML.Tag.I || t == HTML.Tag.EM)

StyleConstants.setItalic(memory, false);

if(t == HTML.Tag.B)

StyleConstants.setBold(memory, false);

if(t == firstIgnorable){

firstIgnorable = null;

ignoreIt = false;

}

}

public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int p) {

try{

if (t.isBlock())

doc.insertString(doc.getLength(), "\n\n", null);

else if (t.breaksFlow())

doc.insertString(doc.getLength(), "\n", null);

else

doc.insertString(doc.getLength(), " ", null);

} catch (BadLocationException ble){ble.printStackTrace();}

}

public void handleStartTag(HTML.Tag t, MutableAttributeSet a,int pos){

if(t == HTML.Tag.I || t == HTML.Tag.EM)

StyleConstants.setItalic(memory, true);

if(t == HTML.Tag.B)

StyleConstants.setBold(memory, true);

if(t == HTML.Tag.HEAD || t == HTML.Tag.TITLE) {//Should be a enum of ingnorables

if (firstIgnorable == null)

firstIgnorable = t;

ignoreIt = true;

}

lastTag = t;

}

}

i30817
Offline
Joined: 2006-05-02
Points: 0

This is what i need for my program (to parse only txt), but maybe a better solution is moving the viewfactory code to a dedicated class and not to touch the editorkit. Then any kind of Document can be used... humm.

Edit:
Nope. There is no setViewFactory in any EditorKit. So i can't use another editorkit.

Message was edited by: i30817