Why does AffineTransform not work on JTextPane?

F

fiziwig

I can rotate every other type of component I've tried, but when I
rotate a JTextPane by 90 or 180 degrees (which I REALLY need for my
customer's application) The clipping and bordering get all messed up
and I'm at my wit's end trying to make it work.

Am I doing something wrong, or is there a Java bug in applying a
transform to a JTextPane? The same basic code works on a JLabel, why
won't it work on a JTextPane?

Any ideas or suggestions would be deeply appreciated.

OR any ideas for a different way to display STYLED text (font, size,
color, bold, italic on a character-by-character basis) at a 90 or 180
degree rotation. It doesn't have to be editable when rotated, but it
does need to be editable when upright, otherwise I'd just use HTML in a
JLabel.

Thanks,

--gary

Compilable example:


import javax.swing.*;
import javax.swing.text.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;

public class Rotate extends JPanel {

private TextPanel textPane;
private JLayeredPane parent;

public Rotate() {

setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
JToolBar toolBar = buildToolbar();
add(toolBar);

parent = new JLayeredPane();
add(parent);
parent.setBackground( Color.gray);
parent.setPreferredSize(new Dimension(640, 480));

// Create a text pane.

textPane = new TextPanel();
StyledDocument doc = textPane.getStyledDocument();
try {
doc.insertString(doc.getLength(), "This is some sample
text.\nIt can be Rotated.", null);
}
catch (BadLocationException ble) {
System.err.println("Couldn't insert initial text into text
pane.");
}
Border myBorder = BorderFactory.createLineBorder( Color.red );
textPane.setBorder(myBorder);
parent.setOpaque(true);
parent.add(textPane);
textPane.setDefaultBounds(120, 120, 240, 120);
}
private JToolBar buildToolbar() {

JToolBar toolBar = new JToolBar();
toolBar.setRollover( true );
toolBar.setFloatable( false );
JButton rotateButton = new JButton("Rotate");
rotateButton.setToolTipText( "Rotate text editing pane" );
rotateButton.addActionListener( new ActionListener() {
public void actionPerformed( ActionEvent e ) {
textPane.setRotation(textPane.getRotation()+1);
}
});
toolBar.add( rotateButton );
return toolBar;
}
private static void createAndShowGUI() {
//Make sure we have nice window decorations.
JFrame.setDefaultLookAndFeelDecorated(true);

//Create and set up the window.
JFrame frame = new JFrame("Rotate");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

//Create and set up the content pane.
JComponent newContentPane = new Rotate();
newContentPane.setOpaque(true); //content panes must be opaque
frame.setContentPane(newContentPane);

//Display the window.
frame.pack();
frame.setVisible(true);
}

public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}

}

class TextPanel extends JTextPane {

// implements rotation for a JTextPane

private int rotation;
private int tx, ty;
private int wide, high;

// valid rotation values are:
// 0 = no rotation
// 1 = rotation 90 degree clockwise
// 2 = rotation 180 degrees
// 3 = rotation 90 degrees counterclockwise

TextPanel() {
super();
rotation = 0;
tx = 0;
ty = 0;
}
public void setDefaultBounds( int x, int y, int width, int height)
{
high = height;
wide = width;
super.setBounds(x,y,width,height);
}
public void setRotation( int newRotation ) {
rotation = newRotation % 4;

if ((rotation%2)==0) {
setSize(wide,high);
} else {
setSize(high,wide);
}
switch (rotation) {
case 0 : tx = 0; ty = 0; break;
case 1 : tx = 1; ty = 0; break;
case 2 : tx = 1; ty = 1; break;
case 3 : tx = 0; ty = 1; break;
}
repaint();
}
public int getRotation() { return rotation; }

public void paintComponent(Graphics g) {

Graphics2D g2 = (Graphics2D) g;
double angle = rotation * Math.PI/2;
AffineTransform tr = g2.getTransform();
int h,w;
if ((rotation%2) == 0) {
w = wide;
h = high;
} else {
h = wide;
w = high;
}

tr.setToTranslation(getWidth()*tx,getHeight()*ty);
tr.rotate(angle);
g2.setTransform(tr); // <----- comment this line out to see
correct borders
super.paintComponent(g2);
}
}
 
O

Oliver Wong

fiziwig said:
I can rotate every other type of component I've tried, but when I
rotate a JTextPane by 90 or 180 degrees (which I REALLY need for my
customer's application) The clipping and bordering get all messed up
and I'm at my wit's end trying to make it work.

Am I doing something wrong, or is there a Java bug in applying a
transform to a JTextPane? The same basic code works on a JLabel, why
won't it work on a JTextPane?

Any ideas or suggestions would be deeply appreciated.

OR any ideas for a different way to display STYLED text (font, size,
color, bold, italic on a character-by-character basis) at a 90 or 180
degree rotation. It doesn't have to be editable when rotated, but it
does need to be editable when upright, otherwise I'd just use HTML in a
JLabel.

How about using a JTextPane for editing, and when editing is done, copy
the contents and put it into a JLabel, and rotate the JLabel?

- Oliver
 
F

fiziwig

Oliver said:
How about using a JTextPane for editing, and when editing is done, copy
the contents and put it into a JLabel, and rotate the JLabel?

- Oliver

That might work. The problem is then I have to go through the text
character by character pulling out the attributes (font, size, color,
bold, italic,...) and turning those into HTML tags so that they will
display properly in the JLabel. Then when the user selects the text for
editing again I have to turn around and parse out all the HTML tags and
build it back into a styled document.

This would be soooo easy in C++/Windows, but Java makes the hard things
easy by making the easy things hard, by keeping some very important
things hidden and inaccessable. That's great most of the time, but it's
a major pain in the neck at other times. It's like trying to build a
geodesic dome with Lincoln logs. The Java Lincoln logs are great as
long as you're building their kind of rectalinear log-cabin-type stuff,
but to build anything unconventional, like round or octagonal, you're
flat out of luck.

--gary
 
O

Oliver Wong

fiziwig said:
That might work. The problem is then I have to go through the text
character by character pulling out the attributes (font, size, color,
bold, italic,...) and turning those into HTML tags so that they will
display properly in the JLabel. Then when the user selects the text for
editing again I have to turn around and parse out all the HTML tags and
build it back into a styled document.

Or you could have a central "model" object which semantically preserves
all the information you need in your document, and generate HTML like views
for the Label, and the character-style like view and editor for the
JTextPane.

Another thing you could try is, once the user has finished editing
everything, taking a screen capture of the JTextPane, and then rotating that
image, and displaying it.

- Oliver
 
F

fiziwig

Another thing you could try is, once the user has finished editing
everything, taking a screen capture of the JTextPane, and then rotating that
image, and displaying it.

- Oliver

The screen cap idea is a good one. That would be perfectly suitable.
This is a page layout program for a customer who is a printer and he
needs to be able to turn blocks of text on their side or upside down in
doing multi-page layouts on a single very large sheet of paper. There
can be literally dozens of separate JTextPanes in a single document.

I'll look into the screen cap approach. If I can figure out how to do a
screen cap, that is. ;-)

Thanks,

--gary
 
F

fiziwig

Oliver said:

Thanks for all your help. After days and days of pulling my hair out,
the rotation finally works perfectly. Here's how I ended up doing it:

class TextPanel extends JTextPane {

// implements rotation for a JTextPane

private int rotation;
private int tx, ty;
private int wide, high;
private BufferedImage renderedText = null;

// valid rotation values are:
// 0 = no rotation
// 1 = rotation 90 degree clockwise
// 2 = rotation 180 degrees
// 3 = rotation 90 degrees counterclockwise

TextPanel() {
super();
rotation = 0;
tx = 0;
ty = 0;
}
public void setDefaultBounds( int x, int y, int width, int height)
{
high = height;
wide = width;
super.setBounds(x,y,width,height);
}
public void setRotation( int newRotation ) {

newRotation = newRotation % 4;
if ( rotation != newRotation ) {
switch (newRotation) {
case 0 : tx = 0; ty = 0; break;
case 1 : tx = 1; ty = 0; break;
case 2 : tx = 1; ty = 1; break;
case 3 : tx = 0; ty = 1; break;
}
if ( newRotation != 0 ) {
if ( renderedText==null) {
rotation = 0; // so that text is actually rendered
renderedText = new BufferedImage(wide, high,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2D = renderedText.createGraphics();
paint( g2D );
}
}
rotation = newRotation; // so the repaint will paint the
rendered image
if ((rotation%2)==0) {
setSize(wide,high);
} else {
setSize(high,wide);
}

repaint();
}
}
public int getRotation() { return rotation; }

public void paintComponent(Graphics g) {

if ( rotation == 0 ) {
super.paintComponent(g);
return;
}
Graphics2D g2 = (Graphics2D) g;
double angle = rotation * Math.PI/2;
AffineTransform tr = g2.getTransform();
if (rotation==2) {
tr.setToTranslation(wide*tx,high*ty);
} else {
tr.setToTranslation(high*tx,wide*ty);
}
tr.rotate(angle);
g2.drawImage(renderedText, tr, this);
}
}

--gary
 
T

Thomas Weidenfeller

fiziwig said:
public void paintComponent(Graphics g) {

Override paint(), not paintComponent() but before you do so, familiarize
yourself with the way paint() in Swing works. There is a TSC painting
article about this.

/Thomas
 
A

Alex Hunsley

Thomas said:
Override paint(), not paintComponent() but before you do so, familiarize
yourself with the way paint() in Swing works. There is a TSC painting
article about this.

Why do you suggest doing this?
 
T

Thomas Weidenfeller

Alex said:
Why do you suggest doing this?

Reading the documentation? Because it helps to know how things work.

Overriding paint()? Because it makes sense in this particular case.

paint() calls paintComponent(), paintBorder() and paintChildren(). If
you want to rotate a component it makes sense to also rotate its border
and potential children. Manipulating the Graphics context in paint()
would mean you can affect all three activities in a controlled way:
Change it and then call super.paint(g).

On the other hand, manipulating the Graphics context in
paintComponent(), and not reseting it at the end of the method, means
you are relying on a particular sequence of method invocations in
paint(). You are betting on an implementation detail.

/Thomas
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,066
Latest member
VytoKetoReviews

Latest Threads

Top