Hints and Discussion for:

Homework 2: Object Oriented Graphics

05-631: Software Architecture for User Interfaces , Fall, 2001

  1. LayoutGroup should call moveTo() on its children to reposition them.
  2. SimpleGroup and ScaledGroup should not call moveTo() on their children.
  3. What is the resizeChild() method for?
    It allows a faster implementation of LayoutGroup. Instead of recalculating the layout in every draw() call, LayoutGroup can recalculate only when children change size or drawing order.

> I hope this doesn't sound like an overly obvious question, but are we
> allowed to, for example, have a Rectangle2D object inside our Rect
> implementation, instead of individually holding the x,y,width and height,
> and let it calculate the bounding box, etc.?

That sounds fine to me.


> I had a couple of questions regarding the two hints you gave. I was 
> under the impression that a Group shouldn't be calling moveTo() on its 
> children because it shouldn't be changing the x, y positions of the 
> children that were passed at creation time. Consider the following code

> Rect rect = new Rect(20, 20, 50, 50, Color.blue, 1);
> LayoutGroup lg = new LayoutGroup();
> lg.addChild(rect);
> ...
> lg.removeChild(rect);
> ...
> simple.addChild(rect);

> When SimpleGroup simple, gets the rect, it will draw it at a location 
> which was decided by the LayoutGroup lg. Shouldn't rect be drawn at the 
> x, y coordinates that were passed in the constructor? If we didn't call 
> moveTo() in LayoutGroup and handled rect's drawing using g2.translate() 
> then we wouldn't have this problem.

If you want LayoutGroup to use translate() instead of moveTo() to position its children, that's fine. Your approach would make the Groups more consistent with each other, and it obviates the need for resizeChild(). Our tests won't try to discriminate the two approaches.


> I also have another problem. I am using a BufferedImage for all the 
> Group objects. So in damage, I have to somehow clear the damaged 
> rectangle on the bufferedimage before I redraw the damaged portion.

> When I call clearRect() on the Graphics2D object of the BufferedImage,
> Java2D seems to paint that portion of the rectangle black. How do I get
> it paint in the background color?

Groups are transparent! They don't have any notion of background color.  The background behind a group might be arbitrarily complicated -- other graphical objects, images, etc, all drawn before the group is drawn. I don't think your approach will work.


> For the Text object, I know we are supposed to use the FontMetrics class,
> but all the methods that return accurate bounding box information for
> strings also require a graphics context. I do not see how we instantiate a
> FontMetrics class or calculate the bounding box when the graphics
> context is not an input to the function, unless we remember the graphics context
> when the Text object's draw() function is called, but that would require
> always calling draw before getBoundingBox()...

Try java.awt.Toolkit.getDefaultToolkit ().getFontMetrics ().


> This call is deprecated- is that still ok for this assignment?
> > Try java.awt.Toolkit.getDefaultToolkit ().getFontMetrics ().

Yes, you can still use it if it works.


> Also, when running the TestRect program, my rectangles are repeatedly
> copied all over the drawing box (versus simply being moved around) is
> this the correct behaviour?

Nope. Your rectangle should be moving around cleanly, not leaving copies of itself in its old positions.


> Is LayoutGroup supposed to wrap? I was under the impression that it wasn't 
> supposed to wrap. I thought the "wrapping" version of LayoutGroup was the 
> extra credit Fill class.

LayoutGroup is definitely NOT supposed to fill, which is very clear from the homework assignment. It is very clear that FILL is an "extra credit" part of the assignment, as a separate kind of layout mechanism. I am sorry that Rishi gave the wrong answer to this question -- if you did a layout that does Fill, it will count for extra credit.


> For getBoundingBox(), when you say that you want the smallest box
> containing all the pixels, do you mean the smallest clipping region? Or
> do you mean the smallest box that contains all the pixels drawn? If you
> mean the latter, do you mean inclusive (line overlaps drawn pixels) or
> exclusive (line drawn adjacent to drawn pixels)?

It should be the smallest box such that if one were to draw a white filled rectangle using that box, all the pixels would be erased. Or if I set a clip rectangle to that box and drew the object, none of the object would get clipped.


> For the x and y on Text, should that indicate the default baseline
> position or the baseline position under the first drawn pixel?

I think the x and y supplied to the Text constructor can be the x,y of the baseline of the text object (and for getX and getY and setX and setY). But of course, the x and y in the Rectangle returned by getBoundingBox must be for the outside of the text object. I am sorry I didn't specify this more clearly. What did other people do for the x and y of text objects?


> For Rect and FilledRect, how are width and height supposed to work?
> Is a 50x50 Rect 50 pixels x 50 pixels or 49 pixels x 49 pixels?

Personally, I think the way the Java system does it is weird. I would prefer that Rect (with a 1 pixel wide line) and FillRect work the same way for which pixels are on, and that the width for a 1 pixel line outline rectangle is really the number of pixels that are turned on, but it is also OK if your Rect works the same way as the built-in Java rectangle drawing. In Amulet, we went to great lengths to have all rectangles (and elipses, etc.) grow only inwards, so the width was always the REAL width. But you don't have to do this in this assignment.


> How much do we have to worry about trickiness for HW2? For example, do
> we, have to worry about cases where it is attempted to add a shape to
> more than, one group (or the same group twice)? Or if something like,
> bringChildToFront() is called on a child that is not already part of
> the, group? Just wondering how much effort we should spend on
> robustifying our code for, these not-well-specified cases (i.e. how
> much it counts for our homework, score),

It would be good if your code handled these and other error cases in a graceful way (say by raising a Java exception). Sorry, I should have had that as part of the interface.


> The moveTo command spec says that the object to be moved should be moved
> so that the specified x and y are to be the upper left corner of the
> bounding box. Does this apply to text as well? Reason for my asking is
> because Text, unlike the other graphicaObjects, does not use the given x
> and y as the top left.

I think I already answered this with respect to lines, which don't use the x,y as the top left either. MoveTo should always move the top left corner. The motivation is: think about moving objects by dragging them in a drawing editor--it is very handy for the higher-level dragging code to be able to call MoveTo on all objects and get the same effect always, independent of what kind of object it is.


> I have a similar question about the 'background'. Consider the following
> course of events:

> Rect r = new Rect(0,0,20,20,Color.black,1)
> SimpleGroup sg = new SimpleGroup(50,50,200,200)
> sg.addChild(r);
> sg.draw();
> sg.removeChild(r);
> sg.draw();

> In this case, what should the resulting image be? It can either be a 20 by
> 20 black rectangle at 50,50, or nothing.

> If it is the rectangle, then all is well.....however, i believe that what it
> should be is nothing....specifically, that when
> you remove an object from a group, you remove it from the screen on the next
> redraw.....and if this is the case, then how do we get rid of it? As you
> said in response to this earlier guy, we dont have a background color to
> draw....should we just assume that its the users fault if nothing goes away
> even if we stopped redrawing it for not drawing over the nonexistant item?

When an object is removed, it should certainly no longer appear! 

SimpleGroup shouldn't have to deal with erasing damaged regions, because it is transparent! The stuff under the damaged regions might be arbitrarily complex (other graphical objects, for instance). The group's parent should take care of redrawing its background. Only an opaque Group -- like a window -- should erase damaged regions with a background color. The answer is, this sample code is actually wrong.

sg should not be calling draw at this point like this. Only the top-level window should really be managing the redraw. sg should be a member of a higher-level group, such as TestFrame, which receives sg's damage rectangle and erase it with the background color before telling sg to redraw itself.

Since the code above doesn't call sg properly, it's not sg's fault if the resulting display doesn't look right.


> So, is the sample code like TestFrame wrong? As far as i've seen, none of
> the sample code erases the damaged sections at the beginning of the
> redraw, which i don't have access to from SimpleGroup - the only way i can
> seen to make those demos behave properly is, when an object is moved or
> removed, add both the old and new bounds to the damaged area of its group,
> and then have SimpleGroup erase the damaged area off the bat when it is
> called upon to draw.

TestFrame erases the damaged sections much earlier, in damage(). If your SimpleGroup calls its parent's damage() method with the appropriate rectangles when one of its children moves or disappears, then TestFrame will erase the damage properly.


> When a GraphicalObject changes size, should it call both resizeChild as
> well as damage? 

Yep, call both methods.

> Along the same lines, should SimpleGroup and ScaledGroup
> ignore the resizeChild method or should it treat it like damage?

Feel free to ignore it. resizeChild is just a hint for groups that care about it.


> Consider the case when a Rect is added to a LayoutGroup. LayoutGroup is 
> supposed to call moveTo() on this Rect child. Now, what happens if setX(), 
> setY(), or moveTo() is called on this child? I would think nothing should 
> happen, or possibly an exception is raised. Can we assume that these types 
> of calls will never happen?

It is OK if the object is no longer in the right position of these methods are called after the LayoutGroup tells the object where to go. Groups should always enforce clipping to their boundaries however, so sub-objects can never draw outside their group's borders.


> When Group.draw() is called, should all of its children be redrawn, or
> only the ones which need to be redrawn? I know this is a basic question
> for this assignment, and I probably should have asked it before. Since
> this topic wasn't explicit on the assignment, I just assumed that only the
> children which need to be redrawn should be redrawn, because of efficiency. 
> This is the way I have been implementing it. However, keeping track of which
> children need to be redrawn and which don't has not only been inefficient
> in terms of the implementation, but it is becoming inefficient in the
> running time. It seems like it'd be easier and possibly more efficient
> just to redraw everything in the Group.draw() methods.

Either way is fine. Part of the point of the assignment is to investigate the trade-offs.

** BE SURE TO DOCUMENT WHAT YOUR CODE IS DOING **


> Just wanted to point out, if we start throwing Exceptions, then the
> methods will need to specify that they throw Exceptions, and the
> Interfaces will need to be altered. Is this okay?

Yes, that would be OK as well.


> Umm....so basically you only want damage to propagate upwards?

Correct.


Sam gets extra credit for pointing out an issue with the interface for HW 2:

If you have two groups which overlap, and each has an object in an area that overlaps, and the object in one group changes, the other group won't think that anything inside of it is damaged, so it won't redraw anything, which would be incorrect. Sam wanted to call the damage method downward on the clild groups to propogate the changed region to all overlapping groups, but this doesn't work because damage is only designed to go up.

The right way to handle this is for the top-level to collect all the damaged regions sent up, and use a different method to tell all groups what region needs to be repainted. Then, the groups do NOT save the damaged regions they computed after they pass the damage region to the parent, but instead just rely on the region passed in.

One way to solve this with the current interface for HW 2 would be to use the clip region set into the Graphics2D parameter to Draw. Then, each group would pull out the clip region, intersect its own bounding box with the passed-in clip region, and then determine what needs to be redrawn by clipping all children to this combined clip region. This works recursively.

Probably it would have been a better design (and more obvious) to have the clip region passed as an explicit parameter to the draw method.

Since it was not, if your code does NOT handle groups that overlap correctly, then no points will be taken off, but if you do handle it correctly, then you will get extra credit.


> Hmm...so since we dont have any opaque groups, like windows, we dont
> erase damaged regions with a background color?

Right. If we had asked you to write an opaque group, then you'd have to worry about erasing. We provided you with an opaque group, TestFrame.

> Also, I looked at TestFrame, and it is able to draw the background color
> because it owns a JComponent which we don't have, so do we really
> erase anything?

No, you don't have to erase anything.