(in Shape)
protected void updateCanvas(Rectangle areaOfChange, boolean enlargeForKnobs){
Rectangle toRedraw = new Rectangle(areaOfChange);
if (enlargeForKnobs)
toRedraw.grow(KNOB_SIZE/2, KNOB_SIZE/2);
canvas.repaint(toRedraw);//<--
}
And when I try to move it, I get one at:
(in DrawingCanvas)
public void mouseDragged(MouseEvent event){
Point curPt = event.getPoint();
switch (dragStatus) {
case DRAG_MOVE:
selectedShape.translate(curPt.x - dragAnchor.x, //<--
curPt.y - dragAnchor.y);//<--
// update for next dragged event
dragAnchor = curPt;
break;
case DRAG_CREATE: case DRAG_RESIZE:
selectedShape.resize(dragAnchor, curPt);
break;
}
repaint();
}
How would I make it so I can call these methods without a problem?
------
Shape.java
import java.awt.*;
import java.io.Serializable;
import java.awt.datatransfer.*;
public abstract class Shape implements Serializable, Cloneable{
private Toolbar tb= new Toolbar();
protected Color shapecolor;
protected Rectangle bounds;
public transient DrawingCanvas canvas;
protected boolean isSelected;
protected static final int KNOB_SIZE = 6;
protected static final int NONE = -1;
protected static final int NW = 0;
protected static final int SW = 1;
protected static final int SE = 2;
protected static final int NE = 3;
public Shape(Point start, DrawingCanvas dcanvas){
canvas=dcanvas;
bounds= new Rectangle(start);
setColor(getColor());
}
public void setColor(Color setcol){
shapecolor=setcol;
}
public Color getColor(){
return canvas.toolbar.getCurrentColor();
}
/** The "primitive" for all resizing/moving/creating operations that
* affect the rect bounding box. The current implementation just resets
* the bounds variable and triggers a re-draw of the union of the old &
* new rectangles. This will redraw the shape in new size and place and
* also "erase" if bounds are now smaller than before. It is a good
* design to have all changes to a critical variable bottleneck through
* one method so that you can be sure that all the updating that goes
* with it only needs to be implemented in this one place. If any of your
* subclasses have additional work to do when the bounds change, this is
* the method to override. Make sure that any methods that change the
* bounds call this method instead of directly manipulating the variable.
*/
protected void setBounds(Rectangle newBounds){
Rectangle oldBounds = bounds;
bounds = newBounds;
updateCanvas(oldBounds.union(bounds));
}
/** The resize operation is called when first creating a rect, as well as
* when later resizing by dragging one of its knobs. The two parameters
* are the points that define the new bounding box. The anchor point
* is the location of the mouse-down event during a creation operation
* or the opposite corner of the knob being dragged during a resize
* operation. The end is the current location of the mouse. If you
* create the smallest rectangle which encloses these two points, you
* will have the new bounding box. Use the setBounds() primitive which
* is the bottleneck we are using for all geometry changes, it handles
* updating and redrawing.
*/
public void resize(Point anchor, Point end){
Rectangle newRect = new Rectangle(anchor);
// creates smallest rectangle which
// includes both anchor & end
newRect.add(end);
// reset bounds & redraw affected areas
setBounds(newRect);
}
/** The translate operation is called when moving a shape by dragging in
* the canvas. The two parameters are the delta-x and delta-y to move
* by. Note that either or both can be negative. Create a new rectangle
* from our bounds and translate and then go through the setBounds()
* primitive to change it.
*/
public void translate(int dx, int dy){
Rectangle newRect = new Rectangle(bounds);
newRect.translate(dx, dy);
setBounds(newRect);
}
/** Used to change the selected state of the shape which will require
* updating the affected area of the canvas to add/remove knobs.
*/
public void setSelected(boolean newState){
isSelected = newState;
// need to erase/add knobs
// including extent of extended bounds
updateCanvas(bounds, true);
}
/** The updateCanvas() methods are used when the state has changed
* in such a way that it needs to be refreshed in the canvas to properly
* reflect the new settings. The shape should take responsibility for
* messaging the canvas to properly update itself. The appropriate AWT/JFC
* way to re-draw a component is to send it the repaint() method with the
* rectangle that needs refreshing. This will cause an update() event to
* be sent to the component which in turn will call paint(), where the
* real drawing implementation goes. See the paint() method in
* DrawingCanvas to see how it is implemented.
*/
protected void updateCanvas(Rectangle areaOfChange, boolean enlargeForKnobs){
Rectangle toRedraw = new Rectangle(areaOfChange);
if (enlargeForKnobs)
toRedraw.grow(KNOB_SIZE/2, KNOB_SIZE/2);
canvas.repaint(toRedraw);
}
protected void updateCanvas(Rectangle areaOfChange){
updateCanvas(areaOfChange, isSelected);
}
/** When needed, we create the array of knob rectangles on demand. This
* does mean we create and discard the array and rectangles repeatedly.
* These are small objects, so perhaps it is not a big deal, but
* a valid alternative would be to store the array of knobs as an
* instance variable of the Shape and and update the knobs as the bounds
* change. This means a little more memory overhead for each Shape
* (since it is always storing the knobs, even when not being used) and
* having that redundant data opens up the possibility of bugs from
* getting out of synch (bounds move but knobs didn't, etc.) but you may
* find that a more appealing way to go. Either way is fine with us.
* Note this method provides a nice unified place for one override from
* a shape subclass to substitute fewer or different knobs.
*/
protected Rectangle[] getKnobRects(){
Rectangle[] knobs = new Rectangle[4];
knobs[NW] = new Rectangle(bounds.x - KNOB_SIZE/2,
bounds.y - KNOB_SIZE/2, KNOB_SIZE, KNOB_SIZE);
knobs[SW] = new Rectangle(bounds.x - KNOB_SIZE/2,
bounds.y + bounds.height - KNOB_SIZE/2,
KNOB_SIZE, KNOB_SIZE);
knobs[SE] = new Rectangle(bounds.x + bounds.width - KNOB_SIZE/2,
bounds.y + bounds.height - KNOB_SIZE/2,
KNOB_SIZE, KNOB_SIZE);
knobs[NE] = new Rectangle(bounds.x + bounds.width - KNOB_SIZE/2,
bounds.y - KNOB_SIZE/2,
KNOB_SIZE, KNOB_SIZE);
return knobs;
}
/** Helper method to determine if a point is within one of the resize
* corner knobs. If not selected, we have no resize knobs, so it can't
* have been a click on one. Otherwise, we calculate the knob rects and
* then check whether the point falls in one of them. The return value
* is one of NW, NE, SW, SE constants depending on which knob is found,
* or NONE if the click doesn't fall within any knob.
*/
protected int getKnobContainingPoint(Point pt){
// if we aren't selected, the knobs
// aren't showing and thus there are no knobs to check
if (!isSelected) return NONE;
Rectangle[] knobs = getKnobRects();
for (int i = 0; i < knobs.length; i++)
if (knobs[i].contains(pt))
return i;
return NONE;
}
/** Method used by DrawingCanvas to determine if a mouse click is starting
* a resize event. In order for it to be a resize, the click must have
* been within one of the knob rects (checked by the helper method
* getKnobContainingPoint) and if so, we return the "anchor" ie the knob
* opposite this corner that will remain fixed as the user drags the
* resizing knob of the other corner around. During the drag actions of a
* resize, that fixed anchor point and the current mouse point will be
* passed to the resize method, which will reset the bounds in response
* to the movement. If the mouseLocation wasn't a click in a knob and
* thus not the beginning of a resize event, null is returned.
*/
public Point getAnchorForResize(Point mouseLocation)
{
int whichKnob = getKnobContainingPoint(mouseLocation);
if (whichKnob == NONE) // no resize knob is at this location
return null;
Rectangle[] knobs = getKnobRects();
whichKnob = Math.abs(whichKnob - (int)(knobs.length / 2));
return (new Point(knobs[whichKnob].x + knobs[whichKnob].width /2,
knobs[whichKnob].y + knobs[whichKnob].height/2));
}
public abstract void draw(Graphics g, Rectangle regionToDraw);
public abstract boolean inside(Point pt);
public Object Clone(){
try {
return this.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
DrawingCanvas.java
/*
*-------------------------------------------------------------- 80 columns ---|
* The DrawingCanvas class a small extension of JComponent
* @version 1.1 15/04/01
* @author Julie Zelenski
* @author (touched up by Ian A. Mason)
* @see javax.swing.JComponent
*/
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.util.*;
public class DrawingCanvas extends JComponent implements ChangeListener{
static final int DRAG_NONE = 0;
static final int DRAG_CREATE = 1;
static final int DRAG_RESIZE = 2;
static final int DRAG_MOVE = 3;
// list of all shapes on canvas
protected Vector allShapes;
// currently selected shape (can be null at times)
protected Shape selectedShape;
// reference to toolbar to message for tool&color settings
protected Toolbar toolbar;
protected HMClip clip=new HMClip();
/* These are the unimplemented menu commands. The menus are already
* set up to send the correct messages to the canvas, but the
* method bodies themselves are currently completely empty. It will
* be your job to fill them in!
*/
public void cut() {
if(selectedShape!=null){
clip.setClip(selectedShape);
allShapes.remove(selectedShape);
}
else System.err.println("Nothing Selected");
repaint();
}
public void copy() {
if(selectedShape!=null){
clip.setClip(selectedShape);
}
else System.err.println("Nothing Selected");
repaint();
}
public void paste() {
if(clip.clip!=null){
selectedShape= (Shape) clip.getClip();
allShapes.add(selectedShape);
selectedShape.draw(getGraphics(), getBounds());
}
repaint();
}
public void delete() {
if(selectedShape!=null){
allShapes.remove(selectedShape);
}
repaint();
}
public void clearAll() {
allShapes.removeAllElements();
repaint();
}
public void loadFile() {
selectedShape=null;
String fname=filenameChosenByUser(true);
if(fname==null) fname="No File Name";
SimpleObjectReader read= SimpleObjectReader.openFileForReading(fname);
Shape incoming;
if (read == null) {
System.out.println("Couldn't open file!");
return;
}
clearAll();
while((incoming = (Shape) read.readObject())!=null){
allShapes.add(incoming);
}
read.close();
repaint();
}
public void saveToFile() {
selectedShape=null;
String fname=filenameChosenByUser(false);
if(fname==null) fname="No File Name";
SimpleObjectWriter write= SimpleObjectWriter.openFileForWriting(fname);
if (allShapes==null) return;
if (write!=null){
if(allShapes!=null){
for(int i=0; i<allShapes.size();i++)
write.writeObject(allShapes.get(i));
}
}
else System.err.println("Couldn't Open file");
write.close();
repaint();
}
public void bringToFront() {
if(selectedShape!=null){
allShapes.add(allShapes.lastIndexOf(allShapes.lastElement()), selectedShape);
allShapes.remove(selectedShape);
}
repaint();
}
public void sendToBack() {
if(selectedShape!=null){
allShapes.remove(selectedShape);
allShapes.add(0, selectedShape);
}
repaint();
}
/**
* Constructor for creating a new empty DrawingCanvas. We set up
* our size and background colors, instantiate an empty vector of shapes,
* and install a listener for mouse events using our inner class
* CanvasMouseHandler
*/
public DrawingCanvas(Toolbar tb, int width, int height){
setPreferredSize(new Dimension(width, height));
setBackground(Color.white);
toolbar = tb;
allShapes = new Vector();
selectedShape = null;
CanvasMouseHandler handler = new CanvasMouseHandler();
addMouseListener(handler);
addMouseMotionListener(handler);
toolbar.addChangeListener(this);
}
/**
* All components are responsible for drawing themselves in
* response to repaint() requests. The standard method a component
* overrides is paint(Graphics g), but for Swing components, the default
* paint() handler calls paintBorder(), paintComponent() and paintChildren()
* For a Swing component, you override paintComponent and do your
* drawing in that method. For the drawing canvas, we want to
* clear the background, then iterate through our shapes asking each
* to draw. The Graphics object is clipped to the region to update
* and we use to that avoid needlessly redrawing shapes outside the
* update region.
*/
public void paintComponent(Graphics g){
Rectangle regionToRedraw = g.getClipBounds();
g.setColor(getBackground());
g.fillRect(regionToRedraw.x, regionToRedraw.y,
regionToRedraw.width, regionToRedraw.height);
Iterator iter = allShapes.iterator();
while (iter.hasNext()){;
((Shape)iter.next()).draw(g, regionToRedraw);
}
}
/**
* Changes the currently selected shape. There is at most
* one shape selected at a time on the canvas. It is possible
* for the selected shape to be null. Messages the shape to
* change its selected state which will in turn refresh the
* shape with the knobs active.
*/
protected void setSelectedShape(Shape shapeToSelect) {
// if change in selection
if (selectedShape != shapeToSelect) {
// deselect previous selection
if (selectedShape != null)
selectedShape.setSelected(false);
// set selection to new shape
selectedShape = shapeToSelect;
if (selectedShape != null) {
shapeToSelect.setSelected(true);
toolbar.colorButton.setBackground(selectedShape.shapecolor);
}
}
}
/**
* A hit-test routine which finds the topmost shape underneath a
* given point.We search Vector of shapes in back-to-front order
* since shapes created later are added to end and drawn last, thus
* appearing to be "on top" of the earlier ones. When a click comes
* in, we want to select the top-most shape.
*/
protected Shape shapeContainingPoint(Point pt){
for (int i = allShapes.size()-1; i >= 0; i--) {
Shape r = (Shape)allShapes.elementAt(i);
if (r.inside(pt)) return r;
}
return null;
}
/**
* The inner class CanvasMouseHandler is the object that handles the
* mouse actions (press, drag, release) over the canvas. Since there is
* a bit of state to drag during the various operations (which shape,
* where we started from, etc.) it is convenient to encapsulate all that
* state with this little convenience object and register it as the
* handler for mouse events on the canvas.
*/
protected class CanvasMouseHandler
extends MouseAdapter implements MouseMotionListener {
Point dragAnchor;
// variables using to track state during drag operations
int dragStatus;
/** When the mouse is pressed we need to figure out what
* action to take. If the tool mode is arrow, the click might
* be a select, move or reisze. If the tool mode is one of the
* shapes, the click initiates creation of a new shape.
*/
public void mousePressed(MouseEvent event){
Shape clicked = null;
Point curPt = event.getPoint();
// first, determine if click was on resize knob of selected shape
if (toolbar.getCurrentTool() == Toolbar.SELECT) {
if (selectedShape != null &&
(dragAnchor = selectedShape.getAnchorForResize(curPt))
!= null) {
// drag will resize this shape
dragStatus = DRAG_RESIZE;
} else if ((clicked = shapeContainingPoint(curPt)) != null) {
// if not, check if any shape was clicked
setSelectedShape(clicked);
// drag will move this shape
dragStatus = DRAG_MOVE;
dragAnchor = curPt;
} else {
// else this was a click in empty area,
// deselect selected shape,
setSelectedShape(null);
// drag does nothing in this case
dragStatus = DRAG_NONE;
}
} else {
Shape newShape;
switch(toolbar.getCurrentTool()){
case Toolbar.RECT:
// create rect here
newShape = new Rect(curPt,DrawingCanvas.this);
break;
case Toolbar.OVAL:
// create oval here
newShape = new Oval(curPt,DrawingCanvas.this);
break;
case Toolbar.LINE:
// create line here
newShape = new Lines(curPt,DrawingCanvas.this);
break;
case Toolbar.SCRIBBLE:
newShape = new Scribbles(curPt,DrawingCanvas.this);
break;
default:
newShape = null;
break;
}
// create rect here
allShapes.add(newShape);
setSelectedShape(newShape);
dragStatus = DRAG_CREATE;
// drag will create (resize) this shape
dragAnchor = curPt;
}
repaint();
}
/** As the mouse is dragged, our listener will receive periodic
* updates as mouseDragged events. When we get an update position,
* we update the move/resize event that is in progress.
*/
public void mouseDragged(MouseEvent event){
Point curPt = event.getPoint();
switch (dragStatus) {
case DRAG_MOVE:
selectedShape.translate(curPt.x - dragAnchor.x,
curPt.y - dragAnchor.y);
// update for next dragged event
dragAnchor = curPt;
break;
case DRAG_CREATE: case DRAG_RESIZE:
selectedShape.resize(dragAnchor, curPt);
break;
}
repaint();
}
public void mouseMoved(MouseEvent e) {}
}
/** A little helper routine that will be useful for the load & save
* operations. It brings up the standard JFileChooser dialog and
* allows the user to specify a file to open or save. The return
* value is the full path to the chosen file or null if no file was
* selected.
*/
protected String filenameChosenByUser(boolean forOpen){
JFileChooser fc = new JFileChooser(System.getProperty("user.dir") +
java.io.File.separator + "Documents");
int result = (forOpen? (fc.showOpenDialog(this)) :
fc.showSaveDialog(this));
java.io.File chosenFile = fc.getSelectedFile();
if (result == JFileChooser.APPROVE_OPTION && chosenFile != null)
return chosenFile.getPath();
return null;
// return null if no file chosen or dialog cancelled
}
@Override
public void stateChanged(ChangeEvent colorchange) {
if(selectedShape!=null){
selectedShape.setColor(toolbar.getCurrentColor());
}
repaint();
}
}

New Topic/Question
Reply



MultiQuote





|