[ Exercises | Chapter Index | Main Index ]

Solution for Programming Exercise 13.4


This page contains a sample solution to one of the exercises from Introduction to Programming Using Java.


Exercise 13.4:

The custom component example MirrorText.java, from Subsection 13.4.5, uses an off-screen canvas to show mirror-reversed text in a JPanel. An alternative approach would be to draw the text after applying a transform to the graphics context that is used for drawing. (See Subsection 13.2.5.) With this approach, the custom component can be defined as a subclass of JLabel in which the paintComponent() method is overridden. Write a version of the MirrorText component that takes this approach. The solution is very short, but tricky. Note that the scale transform g2.scale(-1,1) does a left-right reflection through the left edge of the component.


Discussion

The original version of MirrorText was defined as a subclass of JPanel, so it inherited only the methods defined in that class. Since JPanel has no setText() or getText() method, I had to define them myself. To make the custom component behave like a normal component, I had to define a getPreferredSize() method, and I had to call revalidate() in the setText() method. And my component still does not behave exactly like a standard component. For example, a JLabel generates an event when its text is changed.

By defining the new version of MirrorText as a subclass of JLabel, we avoid these problems, since the custom component will automatically have all the methods and behaviors of a JLabel. The main thing that has to be changed is the paintComponent() method. I also defined a constructor that sets the default alignment of the text in the label to centered rather than left-justified. In the paintComponent() method, I used

Graphics2D g2 = (Graphics2D)g.create();

to get a Graphics2D graphics context for drawing on the label. This is required since transforms are only defined in Graphics2D class. I used (Graphics2D)g.create() rather than simply (Graphics2D)g because I wanted to avoid changing the original graphics context, which might be used for further drawing after the paintComponent() method finishes. (See Section 13.2). To apply a mirror reversal to the label, I used g2.scale(-1,1), as suggested in the exercise. However, this reflects the contents of the label through the left edge of the label. If that were the only thing that I did, the text would end up on the other side of the left edge, and that means it wouldn't appear on the label at all. To fix this, before scaling, I translate the text to the left by an amount equal to the width of the label, with the command g2.translate(getWidth(),0). This puts the text to the left of the label, and then the scale transform reflects it back onto the label. Finally, I call super.paintComponent(g2) to call the paint method from the JLabel class. This will draw the text of the label, but the drawing will be subject to the transforms that have been applied to g2.

The way I did it is not the only way to apply the transforms. One alternative is to use g.scale(-1,1) followed by g.translate(-getWidth(),0). After the scale, the text lies to the left of the label. To translate it back onto the label, it must be moved to the right by getWidth() pixels. However, that direction is now the direction of the negative x-axis, so the translation is actually -getWidth(), with a minus sign to indicate that the motion is in the negative direction. (As I said before, it takes a fair amount of study to fully understand transforms.)


The Solution

import java.awt.*;
import javax.swing.*;

/**
 * A component for displaying a mirror-reversed line of text.
 * The text will be centered in the available space.  This component
 * is defined as a subclass of JLabel.
 * 
 * This version uses a transform in a Graphics2D graphics context
 * to achieve the mirror-reversal, and is defined as a simple
 * subclass of JLabel.
 */
public class MirrorText2 extends JLabel {
   
   /**
    * Define the constructor so that the default alignment of
    * the text will be centered, instead of left-justified.
    * @param text The string that is to be shown on the label.
    */
   public MirrorText2(String text) {
      super(text,JLabel.CENTER);
   }

   /**
    * The paintComponent method calls super.paintComponent() after
    * applying a left/right mirror transform.
    */
   public void paintComponent(Graphics g) {
      Graphics2D g2 = (Graphics2D)g.create();
      g2.translate(getWidth(),0);
      g2.scale(-1,1);
      super.paintComponent(g2);
      g2.dispose();
   }


}  // end MirrorText2

Here is a little program for testing the component:


import java.awt.*;
import javax.swing.*;

/**
 * A trivial program that tests the StopWatchLabel2 component.
 * The program just creates a window to show a StopWatchLabel2.
 */

public class TestMirrorText2 {

   public static void main(String[] args) {

      MirrorText2 mtext = new MirrorText2("Let me out of here!");
      mtext.setFont( new Font("SansSerif", Font.BOLD, 48) );
      mtext.setBackground(Color.BLACK);
      mtext.setForeground(Color.RED);
      mtext.setOpaque(true);
      mtext.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
  
      JFrame window = new JFrame("Stop Watch");
      window.setContentPane(mtext);
      window.pack();
      window.setResizable(false);
      window.setLocation(100,80);
      window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      window.setVisible(true);

   }

}

[ Exercises | Chapter Index | Main Index ]