wood burning stoves 2.0*
The moose likes Swing / AWT / SWT and the fly likes Wanted: How to use Strategy Pattern with Shapes? Big Moose Saloon
  Search | Java FAQ | Recent Topics | Flagged Topics | Hot Topics | Zero Replies
Register / Login


Win a copy of OCA/OCP Java SE 7 Programmer I & II Study Guide this week in the OCPJP forum!
JavaRanch » Java Forums » Java » Swing / AWT / SWT
Bookmark "Wanted: How to use Strategy Pattern with Shapes?" Watch "Wanted: How to use Strategy Pattern with Shapes?" New topic
Author

Wanted: How to use Strategy Pattern with Shapes?

Siegfried Heintze
Ranch Hand

Joined: Aug 11, 2000
Posts: 388
I have three questions
(1) How do I refactor Craig's code to use Strategy pattern so I can do
for(Myshape shape : Shapes) shape.draw(g2);
(2) How do I create a class called TextShape so I can use the above loop with shapes that contain text strings.

Craig used "g2.draw(at.createTransformedShape(shapes[0]));" and I cannot figure out how to refactor this.

When I pass an instance of TextShape to createTransformedShape I don't get an instace of TextShape back which I can subsequently draw using its member function.

We had previously tried to just apply the transform to the g2 instead of shape but that did not pan correctly.

(3) How do I implement hit testing? See my attempt below.

Thanks,
Siegfried

import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.font.*;
import java.awt.geom.*;

import javax.swing.*;
import javax.swing.event.*;

/*
* See http://www.iam.ubc.ca/guides/javatut99/uiswing/start/swingApplet.html
* http://java.sun.com/j2se/1.5.0/docs/guide/plugin/developer_guide/using_tags.html
*
* From Craig Wood http://www.coderanch.com/forums/
* $Log: GraphicsOnly.java,v $
* Revision 1.8 2006/10/28 03:34:09 siegfried
* use shape array.
*
* Revision 1.7 2006/10/27 20:37:54 siegfried
* Make coordinate system consistent for shapes and text.
*
* Revision 1.6 2006/10/27 18:40:45 siegfried
* Multiple instances of rotated text work!
* To do: Create descendant of class shape to hold text.
*
* Revision 1.5 2006/10/19 03:23:50 siegfried
* Add rotated text that should zoom and pan well.
*
* Revision 1.4 2006/10/19 03:10:39 siegfried
* update
*
* Revision 1.3 2006/10/08 23:33:24 siegfried
* Use Craig Wood's code to implement applet. http://www.coderanch.com/t/258528/Applets/java/null-Pointer-Exception-applet-viewer
*
* Revision 1.2 2006/10/07 21:14:45 siegfried
* Add applet compatibility.
*
* Revision 1.1 2006/10/07 19:54:31 siegfried
* initial from Craig Wood
*
*
* Begin commands to execute this file using Java with bash
* export CLASSPATH=.
* javac -g GraphicsOnly.java
* # javac -g PanAndZoom.java
* cat <<EOF >GraphicsOnlyTmp.html
* <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
* <!--
*
* cd c:/Program Files/Java/jdk1.5.0_04/lib/htmlconverter.jar
* ../bin/java -jar htmlconverter.jar -gui
*
* -->
* <HTML>
* <HEAD>
* <TITLE>Pan And Zoom</TITLE>
* <BODY>
* <!--"CONVERTED_APPLET"-->
* <!-- HTML CONVERTER -->
* <object
* classid = "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
* codebase = "http://java.sun.com/update/1.5.0/jinstall-1_5-windows-i586.cab#Version=5,0,0,5"
* WIDTH = 1000 HEIGHT = 800 >
* <PARAM NAME = CODE VALUE = "GraphicsOnly.class" >
* <param name = "type" value = "application/x-java-applet;version=1.5">
* <param name = "scriptable" value = "false">
*
* <comment>
* <embed
* type = "application/x-java-applet;version=1.5" \
* CODE = "GraphicsOnly.class" \
* WIDTH = 1000 \
* HEIGHT = 800
* scriptable = false
* pluginspage = "http://java.sun.com/products/plugin/index.html#download">
* <noembed>
* <B>Error! You must use a Java enabled browser.</B>
* </noembed>
* </embed>
* </comment>
* </object>
*
* <!--
* <APPLET CODE = "GraphicsOnly.class" WIDTH = 500 HEIGHT = 400>
* <B>Error! You must use a Java enabled browser.</B>
*
* </APPLET>
* -->
* <!--"END_CONVERTED_APPLET"-->
*
* </BODY>
* </HTML>
* EOF
* #appletviewer GraphicsOnlyTmp.html
* /c/Program\ Files/Mozilla\ Firefox/firefox.exe GraphicsOnlyTmp.html
* rm GraphicsOnlyTmp.html
* # IEXPLORE file://PanAndZoom.html
* #java GraphicsOnly
* # rm GraphicsOnly.class
* End commands to execute this file using Java with bash
*
*/

// public
class InnerGraphicsOnly extends JPanel {
Shape[] shapes;
RenderingHints hints;
Dimension m_dimOriginalWindow;
double scale = 1.0;
static final double pi = 4*Math.atan2(1,1);
private int m_nWidthWindow;
private int m_nHeightWindow;

public InnerGraphicsOnly() {
hints = new RenderingHints(null);
hints.put(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
hints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
hints.put(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
m_dimOriginalWindow = new Dimension(10,10);
setBackground(new Color(240,200,200));
addMouseListener(new MyMouseListener());
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHints(hints);
if(shapes == null) initShapes();
m_nWidthWindow = getWidth();
m_nHeightWindow = getHeight();
// Keep shapes centered on panel.
AffineTransform at = makeCenteredAffineTransform();
at.scale(scale, scale);
g2.setPaint(Color.blue);
g2.draw(at.createTransformedShape(shapes[0]));
g2.setPaint(Color.green.darker());
g2.draw(at.createTransformedShape(shapes[1]));
g2.setPaint(new Color(240,240,200));
g2.fill(at.createTransformedShape(shapes[2]));
g2.setPaint(Color.red);
g2.draw(at.createTransformedShape(shapes[2]));
Shape shape=at.createTransformedShape(shapes[3]);
//g2.draw(shape);
TextShape text=((TextShape)shapes[3]);
text.draw(g2);
// Draw some text.
drawString(g2, "Hello World", m_dimOriginalWindow.getWidth()/4, m_dimOriginalWindow.getHeight()/4);
drawString(g2, "Sieglinde", m_dimOriginalWindow.getWidth()/8, m_dimOriginalWindow.getHeight()/8);
}
/**
* @return
*/
private AffineTransform makeCenteredAffineTransform() {
double x = (getWidth() - scale*m_dimOriginalWindow.width)/2;
double y = (getHeight() - scale*m_dimOriginalWindow.height)/2;
AffineTransform at = AffineTransform.getTranslateInstance(x, y);
return at;
}

/**
* @param g2
* @param at
* @param s
*/
private void drawString(Graphics2D g2, String s, double xxx, double yyy) {
double x = scale*(xxx-m_dimOriginalWindow.width/2)+m_nWidthWindow/2;
double y = scale*(yyy-m_dimOriginalWindow.height/2)+m_nHeightWindow/2;
double xx = x, yy= y;
Font font = g2.getFont().deriveFont(18f);
g2.setFont(font);
Dimension str = getStringExtent(g2, s, font);
//x = x - scale*str.getWidth()/2;
//y = y + scale*str.getHeight()/2;
// We have finished using at above for the spatial transform
// work so we can refit/resue it for the text rendering.
AffineTransform at = AffineTransform.getTranslateInstance(x - scale*str.getWidth()/2, y + scale*str.getHeight()/2);//at.setToTranslation(x, y);
// Scale text in place (we have already adjusted the origin
// for the scaling portion).
at.scale(scale, scale);
// Rotate text to align with sw - ne diagonal.
// Determine the angle of rotation.
Rectangle2D bounds = shapes[0].getBounds2D();
double dy = -bounds.getHeight(); // looking up (-) toward this origin
double dx = bounds.getWidth();
double theta = /*Math.atan2(dy, dx);//*/(Math.atan2(dy, dx)+(scale-1)*2)%(2*pi);
// In java, positive angles are measured clockwise from 3 o'clock
// except in arc measure which is reversed.
//System.out.printf("theta = %.1f%n", Math.toDegrees(theta));
// Make up another transform that rotates theta radians about
// the center of the text bounding rectangle. Multiply this
// new transform by the translation/scaling transform.
// Since the text is centered on this component we can use
// its center coordinates for the rotation origin.
// Otherwise, we would locate the center of rotation as
//x += scale*widthString/2;
//y = y + scale*descentString - scale*str.getHeight()/2;
System.out.printf("x = %.1f\ty = %.1f\tw/2 = %d\th/2 = %d%n", x, y, (int)xx, (int)yy);
AffineTransform xfRotate = AffineTransform.getRotateInstance(theta, xx, yy);
xfRotate.concatenate(at); // The order is important (matrix math).
g2.setFont(g2.getFont().deriveFont(xfRotate));
// Since we did all the work in the transform, draw text at (0,0)
g2.drawString(s, 0, 0);
g2.setFont(font); // restore
}

/**
* @param g2
* @param s
* @param font
* @return
*/
protected Dimension getStringExtent(Graphics2D g2, String s, Font font) {
FontRenderContext frc = g2.getFontRenderContext();
float widthString = (float)font.getStringBounds(s, frc).getWidth();
LineMetrics lm = font.getLineMetrics(s, frc);
Dimension str = new Dimension();
str.setSize(widthString, lm.getHeight()+ lm.getDescent());
return str;
}

public Dimension getPreferredSize() {
int w = (int)(scale*m_dimOriginalWindow.width);
int h = (int)(scale*m_dimOriginalWindow.height);
return new Dimension(w, h);
}

private void initShapes() {
int w = getWidth();
int h = getHeight();
shapes = new Shape[] {
new Rectangle2D.Double(w / 16, h / 16, w * 7 / 8, h * 7 / 8),
new Line2D.Double(w / 16, h * 15 / 16, w * 15 / 16, h / 16),
new Ellipse2D.Double(w / 4, h / 4, w / 2, h / 2),
new TextShape(w / 16, h * 15 / 16,""+w/16+","+h*15/16,18f)
};
m_dimOriginalWindow.width = w;
m_dimOriginalWindow.height = h;
}

public JSlider getControl() {
JSlider slider = new JSlider(JSlider.HORIZONTAL, 5, 1000, 100);
slider.setMajorTickSpacing(50);
slider.setMinorTickSpacing(10);
slider.setPaintTicks(true);
slider.setPaintLabels(true);
slider.addChangeListener(new SliderChangeListener());
return slider;
}

public static void main(String[] args) {
InnerGraphicsOnly app = new InnerGraphicsOnly();
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new JScrollPane(app));
f.getContentPane().add(app.getControl(), "Last");
f.setSize(400, 400);
f.setLocation(200,200);
f.setVisible(true);
}
class SliderChangeListener implements ChangeListener{
public void stateChanged(ChangeEvent e) {
int value = ((JSlider)e.getSource()).getValue();
scale = value/100.0;
repaint();
revalidate();
}
}
class MyMouseListener implements MouseListener{
public void mouseClicked(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
AffineTransform at;
try {
at = makeCenteredAffineTransform();//.createInverse();

Graphics2D g2 = (Graphics2D) getGraphics();
Point2D out = new Point();
out = at.transform(e.getPoint(), out);
System.out.print("" + out.getX() + "," + out.getY());
for (Shape shape : shapes) {
if (g2.hit(new Rectangle((int) out.getX(),
(int) out.getY(), 10, 10), shape, true))
System.out.print("hit");
}
System.out.println("");
} catch (Throwable e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
public void mouseReleased(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}

}
interface IDrawable{
public void draw(Graphics2D g);
}
class TextShape extends Rectangle2D.Double implements IDrawable{
protected String m_sText;
public TextShape(double x, double y, String s, float fFontSize){
m_sText = s;
Graphics2D g2 = (Graphics2D)getGraphics();
Font font = g2.getFont().deriveFont(fFontSize);
Dimension d = getStringExtent(g2,s,font);
setRect(x-d.getWidth()/2,y-d.getHeight()/2,d.getWidth(),d.getHeight());
}
public void draw(Graphics2D g){
drawString(g,m_sText,super.getCenterX(),super.getCenterY());
}
}
}
public class GraphicsOnly extends JApplet {
public void init() {
InnerGraphicsOnly graphicsOnly = new InnerGraphicsOnly();
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
cp.add(new JScrollPane(graphicsOnly));
cp.add(graphicsOnly.getControl(), "Last");
}

public static void main(String[] args) {
JApplet applet = new GraphicsOnly();
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(applet);
f.setSize(400,400);
f.setLocation(200,200);
// Since there is no AppletContext we have to
// call the required methods in the applet.
applet.init();
f.setVisible(true);
}
}
Henry Wong
author
Sheriff

Joined: Sep 28, 2004
Posts: 18917
    
  40

Not exactly sure where this topic should go, but it looks like it is probably more related to Swing than Applets or Process. It definitely doesn't belong in the "Other API" forum.

Moving this to the Swing forum...

Henry


Books: Java Threads, 3rd Edition, Jini in a Nutshell, and Java Gems (contributor)
Ken Blair
Ranch Hand

Joined: Jul 15, 2003
Posts: 1078
This is an idea case for strategy and I've done this before with Swing too. All you need to implement strategy is a class or interface, preferably interface for reasons that should be apparent, and as many implementations as necessary. It's as simple as something like this:



The reason for providing the width and height is because it's expected that a Drawable might draw differently depending on the size of the area. There's no need for an x or y because the origin of the graphics context can be translated before we pass it. If this is being used in Swing then it's a reasonable assumption that we're going to have a Graphics2D and a lot of things you might want a Drawable to do can't be done on a Graphics.

It would then be easy to create a class DrawableShape that encapsulates everything need to draw a Shape. Alternatively, DrawableShape could be an interface though there is likely little point to doing so. A simple example:



In my work I actually use a "PaintStrategy" with a "paint" method that is identical. I use it to make arbitrary painting easy. Implementations I've created range from painting images in a myriad of ways to filling with arbitrary Paint instances to painting animations. Furthermore I've used the strategy with everything from custom JComponents to drawing on an image. I would also note that I recently discovered some of the Sun people working on swingx at java.net are doing almost the exact same thing but they call it a "Painter".

As for creating a shape that draws text: that's just silly. A Shape and text are different things. Instead I would create a Drawable implementation that can draw text. If you write your code to accept any Drawable then you're not limited to Shapes to begin with and there's no need to begin with.
Siegfried Heintze
Ranch Hand

Joined: Aug 11, 2000
Posts: 388
Ken,
Thanks but I'm still confused. I believe we are in agreement that I create descendant of class Shape called TextShape that will draw text.

When I do that, I have trouble making the following statement work:

g2.draw(at.createTransformedShape(shapes[0]));

For starters, draw is a member of Graphics, not shape. Secondly, how do I create my TextShape class so that at.CreateTransformedShape does the right thing?

How does at.CreateTransformedShape work? Is it going to modify shapes[0] or create a copy of that instance and return it. I think it is creating a copy because if it is modifying the one original copy, the scaling would be compounded every time I resize the window and that is not happening. Therefor it must be making a copy. But how does it know how to make a copy that includes the extra data members unique to my descendant class?

Also, I would still love some help with hit testing.

Thanks,
Siegfried
Ken Blair
Ranch Hand

Joined: Jul 15, 2003
Posts: 1078
Originally posted by Siegfried Heintze:
[QB]Ken,
Thanks but I'm still confused. I believe we are in agreement that I create descendant of class Shape called TextShape that will draw text.


No, there's no reason to. If you want to use strategy for painting then "Shape" is not the strategy. An implementation of strategy would be capable of painting arbitrary shapes. Consequently there's no reason to create a version of Shape that draws text which doesn't make any sense in the first place. You would not need to use createTransformedShape() to begin with because the implementation that draws text would be capable of calculating the proper width and height. Here's a simple example that would do the drawing you're doing:

 
I agree. Here's the link: http://aspose.com/file-tools
 
subject: Wanted: How to use Strategy Pattern with Shapes?