For the next example, we look at a drawing program that, while quite different from the one you wrote in lab this week, has some interesting similarities. Rather than allowing the user to draw scribbles, it let's a user draw shapes. It has choice buttons to determine the shape of the object to be drawn (square or circle) and its color. The program also allows the user to click on one of the objects on the canvas and drag it to a new position. We do that by providing a choice button that will allow the user to choose whether to draw a new geometric object, or select an old one to drag. We will need to hold all of the objects on the screen in some sort of data structure, so that we can remember those objects for selection and moving. We'll use an array.
Below is a demo of the program (if your browser handles applets correctly):
Here are some relevant excerpts from the program:
public class Drawing extends WindowController { // max number of objects to be displayed private static final int maxObjects = 20; private int numObjects = 0; // number of objects on screen private DrawableInterface[ ] geomObject = new DrawableInterface[maxObjects]; }Note that the last line above creates the array with maxObjects entries, but all of the entries are initially null.
The code to add a new element looks like:
public void onMousePress(Location point) { selected = null; // indicate nothing currently selected if(selectOrAdd.getSelectedItem().equals("Add new item")) addNew(point); else selectOld(point); } // Add new geometric shape where clicked. Type and color of object is determined by the // settings of the color and shape choice buttons. private void addNew(Location point) { if (numObjects < maxObjects) // only add if still room for more objects { Color objectColor; // local variable - color of object // string from colorChoice selection String colorString = colorChoice.getSelectedItem(); if (colorString.equals("Red")) // set objectColor to chosen color { objectColor = Color.red; } else if (colorString.equals("Green")) { objectColor = Color.green; } else { objectColor = Color.blue; } // string from shapeChoice selection String shapeString = shapeChoice.getSelectedItem(); if (shapeString.equals("Square")) // create new object to be shape chosen { geomObject[numObjects] = new FilledRect(point,100,100,canvas); } else { geomObject[numObjects] = new FilledOval(point,100,100,canvas); } geomObject[numObjects].setColor(objectColor); geomObject[numObjects].move(-50,-50); // make point clicked center of object numObjects++; } }Notice that we bump up the count of the number of objects after each addition of a new element. Thus numObjects provides an accurate count of the number of objects of the array which are really in use. The constant maxArray simply keeps track of the total number of available slots, not the number of slots in use.
Notice also that the first statement of the addNew method checks to make sure that numObjects is less than maxObjects. If we omitted that check and tried to insert something in geomObject[maxObjects] we would get an ArrayIndexOutOfBoundsException and your program would crash.
The most interesting code in the program is the one to determine whether the point clicked is inside any of the objects.
private void selectOld(Location point) { // see if point is in an object for (int objNum = 0; objNum < numObjects; objNum++) { if (geomObject[objNum].contains(point)) { selected = geomObject[objNum]; // remember top-most object w/point } } if (selected != null) // if point is in one of the objects, { // send it to the front lastPoint = point; selected.sendToFront(); } }The for loop cycles through the elements from the earliest added to the most recent. If point is in an element, then selected is made to refer to it. Notice that if point is in several elements, selected will only refer to the last of these. After checking all the elements, if the point was in one or more, the last of these is sent to the front and we set lastPoint in preparation for dragging it. The dragging code is exactly as with laundry and our other examples.
Unfortunately, this program does not quite do what we want it to do, as an element from the bottom of the pile of objects may be sent to the front, but still is in the same order in the array. Thus if we click on several items, we might find that rather than selecting the one on the top we have selected one which is buried below. We can fix that by changing the order of elements in the array when one is selected.
For example, suppose we initially create 4 items without doing any dragging. We then select an item and drag it so that it is now on top of the newest item. The left picture below shows the screen before the drag. The picture on the right below shows the screen after the drag.
![]() |
![]() |
How would we accomplish this shift? We want to walk the array starting at our selected object and move each shape above it down one. Then we want to put the selected shape in the last element. In this way the order of elements in our array exactly mirrors the order in which they are layered on the screen.
// select top-most object containing point and send to top of screen // and array private void selectOld(Location point) { int selectIndex=0; // see if point in an object for (int objNum = 0; objNum < numObjects; objNum++) if (geomObject[objNum].contains(point)) { // remember top-most object w/point selected = geomObject[objNum]; selectIndex = objNum; } // if point is in one of the objects, send it to the front visually // and in geomObject if (selected != null) { lastPoint = point; selected.sendToFront(); for (int objNum = selectIndex; objNum < numObjects-1; objNum++) geomObject[objNum] = geomObject[objNum+1]; geomObject[numObjects-1] = selected; } }
Now this code behaves as desired. When you click on the screen. The top-most object clicked on is selected and popped to the top.
A good thing to worry about is what would have happened if I had written the last for loop as:
for (int objNum = numObjects; objNum > selectIndex; objNum--) geomObject[objNum-1] = geomObject[objNum];Here we copy the last to the next to last slot, the next-to-last to the one before that, ..., until finally we copy the reference to the object at selectIndex+1 to the selectIndex slot. Can you see why this would not work properly? Simulate it by hand if you have difficulty seeing what goes wrong.
Perhaps you won't be surprised if I tell you that our DrawingCanvas class keeps track of a list of all the objects created involving the canvas. The reason we need to do this is that plain Java Canvases don't keep track of what is shown on them. Instead when a window is uncovered, it calls the repaint method, which first cleans up the window and then calls the paint method to redraw the window's contents. We do that by redrawing all of the items held in the list.
When an object is removeFromCanvas'ed, it is removed from the list, while creating an object adds adds it to the list.
It would be straightforward to add a choice to the drawing program so that the item clicked on would be removed from the list. The code would be the same as for selecting except that after shifting items down, the element removed would not be placed in the final slot. Instead it would simply be omitted. Of course, we would also have to decrease numObjects (the number of objects in the array) by 1.
Because there is a lot of common code between the methods, we should consider factoring out some of that common code to a new private method that is only there to be used in other methods:
// return the index of the last element of geomObject containing point // return -1 if no element of geomObject contains point private int getIndexOf(Location point) { int selectIndex=-1; // Give a default value if not found // see if point in an object for (int objNum = 0; objNum < numObjects; objNum++) if (geomObject[objNum].contains(point)) { selectIndex = objNum; } return selectIndex; } // remove geomObject[index] by moving later elements back. Decrease // numObjects private void removeEltWithIndex(int index) { for (int objNum = index; objNum < numObjects-1; objNum++) { geomObject[objNum] = geomObject[objNum+1]; } numObjects--; } // Set select to indicate top-most geometric item clicked in, and send it // to front of screen. If didn't click in any then select == null. // Remember where clicked in lastPoint. private void selectOld(Location point) { int selectIndex = getIndexOf(point); // if point is in one of the objects, send it to the front visually if (selectIndex != -1) { selected = geomObject[selectIndex]; lastPoint = point; selected.sendToFront(); removeEltWithIndex(selectIndex); geomObject[numObjects-1] = selected; numObjects++; } } // Remove top-most geometric item clicked in. If didn't click in any then // don't do anything. private void deleteOld(Location point) { int selectIndex = getIndexOf(point); if (selectIndex != -1) // if point is in one of the objects, delete it { geomObject[selectIndex].hide(); removeEltWithIndex(selectIndex); } }
We can make the getIndexOf method more efficient by using a while loop and searching backwards from the end of the array. This is more efficient because you stop searching as soon as you find it:
// return the index of the last element of geomObject containing point // return -1 if no element of geomObject contains point private int getIndexOf(Location point) { int selectIndex=numObjects-1; // prepare to search backward // see if point in an object while ((selectIndex >= 0) && !geomObject[selectIndex].contains(point)) selectIndex--; return selectIndex; }