Articles Projects Tips Downloads Contacts About

Marquee text feature in the JEditorPane/JTextPane Component
By Stanislav Lapitsky

 

HTML browsers support such a useful feature as Marquee (From HTML description - "The <MARQUEE> tag defines a moving section of text, in a manner similar to a movie marquee"). All the content included in the tag permanently moves on page. HTMLEditorKit doesnít provide the feature but itís easy to add the marquee in JEditorPane using the following example.

The idea is to replace RowView with custom one. RowView is an inner class of ParagraphView has custom paint() method which shift content on each call. A special Timer class calls permanent repaint which provide the visual effect of marquee.

The source code shows how to achieve this.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.text.DefaultHighlighter.*;

public class Marquee extends JEditorPane {
    public Marquee() {
        setEditorKit(new MarqueeEditorKit());
        setText("Marquee text in JEditorPane example.\nNew paragraph");
        setEditable(false);
    }

    public static void main(String[] args) {
        JFrame fr=new JFrame("Marquee text in JEditorPane");
        fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        fr.setSize(400,300);
        fr.getContentPane().add(new JScrollPane(new Marquee()));
        fr.setLocationRelativeTo(null);
        fr.setVisible(true);
    }

}

class MarqueeEditorKit extends StyledEditorKit {
    public ViewFactory getViewFactory() {
        return new MarqueeViewFactory();
    }

    class MarqueeViewFactory implements ViewFactory {

        public View create(Element elem) {
            String kind = elem.getName();
            if (kind != null) {
                if (kind.equals(AbstractDocument.ContentElementName)) {
                    return new LabelView(elem);
                }
                else if (kind.equals(AbstractDocument.ParagraphElementName)) {
                    return new MarqueeParagraphView(elem);
                }
                else if (kind.equals(AbstractDocument.SectionElementName)) {
                    return new MarqueeSectionView(elem);
                }
                else if (kind.equals(StyleConstants.ComponentElementName)) {
                    return new ComponentView(elem);
                }
                else if (kind.equals(StyleConstants.IconElementName)) {
                    return new IconView(elem);
                }
            }

            // default to text display
            return new LabelView(elem);
        }

    }
}

class TimerAction extends AbstractAction {
    Container c;
    public TimerAction(Container c) {
        this.c=c;
    }
    public void actionPerformed(ActionEvent e) {
        if (c.getParent()==null) {
            ((Timer)e.getSource()).stop();
        }
        c.repaint();
    }
}

class MarqueeSectionView extends BoxView {
    Timer marqueeTimer;
    long last=0;
    public MarqueeSectionView(Element elem) {
        super(elem, View.Y_AXIS);
    }
    public void paint(Graphics g, Shape allocation) {
        Shape oldClip=g.getClip();
        if (getContainer()!=null && getContainer() instanceof JEditorPane) {
            Container c=getContainer();
            if (marqueeTimer==null) {
                marqueeTimer = new Timer(25, new TimerAction(c));
                marqueeTimer.start();
            }
        }
        else {
            marqueeTimer.stop();
        }
        super.paint(g, allocation);
        g.setClip(oldClip);
    }
}
class MarqueeParagraphView extends ParagraphView {
    public MarqueeParagraphView(Element elem) {
        super(elem);
    }

    protected View createRow() {
        if (isMarquee()) {
            return new RowView(getElement());
        }
        return super.createRow();
    }

    protected void paintChild(Graphics g, Rectangle alloc, int index) {
        super.paintChild(g, alloc, index);
    }

    protected int getSpan(int axis, int childIndex) {
        if (isMarquee() && axis==View.X_AXIS) {

            return (int)getWidth()-getLeftInset()-getRightInset();
        }
        return super.getSpan(axis, childIndex);
    }

    protected int getOffset(int axis, int childIndex) {
        if (isMarquee() && axis==View.X_AXIS) {
            return getLeftInset();
        }
        return super.getOffset(axis, childIndex);
    }

    protected boolean isMarquee() {
        return true;
    }

    class RowView extends BoxView {
        int shift=0;
        RowView(Element elem) {
            super(elem, View.X_AXIS);
        }

        public void paint(Graphics g, Shape allocation) {
            Rectangle r=allocation.getBounds();
            int lastX=r.x+r.width;
            r.width=(int)getPreferredSpan(View.X_AXIS);
            r.x+=shift;
            shift++;
            if (r.x+r.width>lastX) {
                shift=0;
            }
            super.paint(g, r);
        }

        protected void loadChildren(ViewFactory f) {
        }

        public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
            Rectangle r = a.getBounds();
            View v = getViewAtPosition(pos, r);
            if ((v != null) && (!v.getElement().isLeaf())) {
                // Don't adjust the height if the view represents a branch.
                return super.modelToView(pos, a, b);
            }
            r = a.getBounds();
            int height = r.height;
            int y = r.y;
            Shape loc = super.modelToView(pos, a, b);
            r = loc.getBounds();
            r.height = height;
            r.y = y;
            return r;
        }

        public int getStartOffset() {
            int offs = Integer.MAX_VALUE;
            int n = getViewCount();
            for (int i = 0; i < n; i++) {
                View v = getView(i);
                offs = Math.min(offs, v.getStartOffset());
            }
            return offs;
        }

        public int getEndOffset() {
            int offs = 0;
            int n = getViewCount();
            for (int i = 0; i < n; i++) {
                View v = getView(i);
                offs = Math.max(offs, v.getEndOffset());
            }
            return offs;
        }

        protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
            baselineLayout(targetSpan, axis, offsets, spans);
        }

        protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
            return baselineRequirements(axis, r);
        }

        protected int getViewIndexAtPosition(int pos) {
            // This is expensive, but are views are not necessarily layed
            // out in model order.
            if(pos < getStartOffset() || pos >= getEndOffset())
                return -1;
            for(int counter = getViewCount() - 1; counter >= 0; counter--) {
                View v = getView(counter);
                if(pos >= v.getStartOffset() &&
                   pos < v.getEndOffset()) {
                    return counter;
                }
            }
            return -1;
        }

        protected short getLeftInset() {
            View parentView;
            int adjustment = 0;
            if ((parentView = getParent()) != null) { //use firstLineIdent for the first row
                if (this == parentView.getView(0)) {
                    adjustment = firstLineIndent;
                }
            }
            return (short)(super.getLeftInset() + adjustment);
        }

    }
}

Back to Table of Content