Multi-dimensional arraysTopArrays continued

Arrays continued

Note that the chapter on arrays from the Java version of our text is available on-line at
http://eventfuljava.cs.williams.edu/chapters/Bruce_chapter14.pdf
It goes into much more detail than we can here.

Adding a new element to the end of seq is pretty straightforward:

public void addLast(SomeThing newElt) {
   if (size < seq. length) {
      seq[size] = newElt;
      size++;
   }

However, adding an element to the beginning of a list is much trickier. To add the new element we need to make a space by shifting all of the other elements over by 1.

public void addFirst(SomeThing newElt) {
   if (size < seq. length) {
      // shift over existing elements
      for (int index = size-1; index >= 0; index--) {
         seq[index+1] = seq[index]
      }
      seq[0] = newElt;
      size++;
   }

Exercise Why do we shift the last element to the right first, rather than shifting the element at 0 first?

Exercise Explain in detail what would happen if you made the horrible mistake of replacing the +1 in the assignment statement in the forloop by.

         seq[index++] = seq[index]

Explain why these horrible things happened and write on the board 100 times "I will never use ++ in the middle of an expression!".

To polish these skills let's look back at the DrawingList program in Grace and the corresponding program DrawingArray in Java. Here is a link to the Grace program. Take a quick look over it to refresh your memory and now we'll take a look at the equivalent Java program.

import java.awt.*;

import javax.swing.*;

import objectdraw.*;

public class Drawing extends WindowController {
   
   // max number of objects to be displayed
   private static final int SIZE = 100;
   private static final int MAX_OBJECTS = 20;
   
   // menus for shape, color, and command
   private JComboBox<String> shapeChoice;
   private JComboBox<String> colorChoice;
   private JComboBox<String> commandChoice;
   
   // Array of objects on screen.
   private DrawableInterface[] shapes = new DrawableInterface[MAX_OBJECTS];
   
   // number of objects on screen
   private int numShapes = 0;
   
   // item currently selected for dragging
   private DrawableInterface selected;
   
   // mouse loc'n when last handled mouse
   private Location lastPoint;
   
   /**
    *  Set up GUI components for program
    */
   public void begin() {
      // create panel to hold choice buttons
      JPanel menuPanel = new JPanel();
   
      // menu for selecting or adding
      commandChoice = new JComboBox<String>();
      commandChoice.addItem("Add new item");
      commandChoice.addItem ("Recolor item");
      commandChoice.addItem("Move item");
      commandChoice.addItem("Delete item");
      menuPanel.add(commandChoice);
   
      // Set up menu for shapes
      shapeChoice = new JComboBox<String>();
      shapeChoice.addItem("Circle");
      shapeChoice.addItem("Square");
      menuPanel.add(shapeChoice);
   
      // Set up menu for colors
      colorChoice = new JComboBox<String>();
      colorChoice.addItem("Red");
      colorChoice.addItem("Green");
      colorChoice.addItem("Blue");
      menuPanel.add(colorChoice);
   
      // Add the panel to screen
      getContentPane().add(menuPanel, BorderLayout.SOUTH);
      validate();
   }
   
   /**
    *  When the user clicks in the canvas, check the settings of the command 
    *  menu to determine what action to take.
    */
   public void onMousePress(Location point) {
      selected = null; // indicate nothing currently selected
      Object buttonLabel = commandChoice.getSelectedItem();
      if (buttonLabel.equals("Add new item")) {
         addNew(point);
      } else if (buttonLabel.equals("Recolor item")) {
         recolorShapeAt (point);
      } else if (buttonLabel.equals("Move item")) {
         selectShapeAt(point);
      } else {
         deleteShapeAt(point);
      }
   }
   
   /**
    * This method implements the "Add new item" command. Add new geometric
    * shape where clicked. Type and color of object is determined by the
    * settings of the color and shape menus.
    */
   private void addNew(Location point) {
      // only add if still room for more objects
      if (numShapes < MAX_OBJECTS) {
         Location centeredLocation = new Location (point.getX() - SIZE/2, 
                                 point.getY() - SIZE/2);
         Object shapeString = shapeChoice.getSelectedItem();
         DrawableInterface newShape;
            
         // create new object to be shape chosen
         if (shapeString.equals("Square")) {
            newShape =
               new FilledRect(centeredLocation, SIZE, SIZE, canvas);
         } else {
            newShape =
               new FilledOval(centeredLocation, SIZE, SIZE, canvas);
         }
            
         newShape.setColor(getSelectedColor());
         shapes[numShapes] = newShape;
         numShapes++;
      }
   }
   
   /**
    * @return the color corresponding to the string selected in the color menu.
    */
   private Color getSelectedColor() {
      Color objectColor; // local variable - color of object
   
      // get color showing in the color menu
      Object colorString = colorChoice.getSelectedItem();
   
      // set objectColor to chosen color
      if (colorString.equals("Red")) {
         objectColor = Color.RED;
      } else if (colorString.equals("Green")) {
         objectColor = Color.GREEN;
      } else {
         objectColor = Color.BLUE;
      }
      return objectColor;
   }

   /**
    * Change the color of the item the user clicks on.
    * This implements the "Recolor Item" command.  
    */
   private void recolorShapeAt(Location point) {
      int selectIndex = getIndexOf(point);

      if (selectIndex != -1) {
         shapes[selectIndex].setColor(getSelectedColor());
      }
   }
   
   /**
    * @param point location of interest
    * @return the index of the last element of shapes containing point
    *         return -1 if no element of shapes contains point
    */
   private int getIndexOf(Location point) {
      // Walk the array until we find the selected shape
      for (int selectIndex = numShapes - 1; selectIndex >= 0; selectIndex--) {
         if (shapes[selectIndex].contains(point)) {
            return selectIndex;
         }
      }
      return -1;
   }
   
   /**
    *  Remove top-most geometric item clicked in.  If didn't click in any 
    *  then don't do anything.
    * @param point location where user clicked.
    */
   private void deleteShapeAt(Location point) {
      int selectIndex = getIndexOf(point);
   
      // if point is in one of the objects, delete it
      if (selectIndex != -1) {
         shapes[selectIndex].removeFromCanvas();
         removeEltWithIndex(selectIndex);
         shapes[numShapes] = null;
      }
   }
   
   /**
    *  remove shapes[index] by moving later elements back.
    * @param index: element to be removed
    */
   private void removeEltWithIndex(int index) {
      for (int objNum = index; objNum < numShapes - 1; objNum++) {
         shapes[objNum] = shapes[objNum + 1];
      }
      numShapes--;
   }
   
   /**
    *  Set select to indicate top-most geometric item clicked in, and 
    *  send it to front of screen.  If didn't click in any then 
    *  set select to null.
    *  Update lastPoint to last place clicked so can drag it
    * @param point: where user clicked
    */
   private void selectShapeAt(Location point) {
      int selectIndex = getIndexOf(point);
   
      // Remember which shape is selected so onMouseDrag can move it.
      if (selectIndex != -1) {
         selected = shapes[selectIndex];
         lastPoint = point;
         
         // Move the selected object to the front of the display
         // and to the end of the array if it is not already there
         if (selectIndex != numShapes-1) {
            selected.sendToFront();
            removeEltWithIndex(selectIndex);
            shapes[numShapes] = selected;
            numShapes++;
         }
      }
   }
   
   /**
    *  If something was selected then drag it and remember where left off
    */
   public void onMouseDrag(Location point) {
      if (selected != null) {
         selected.move(
            point.getX() - lastPoint.getX(),
            point.getY() - lastPoint.getY());
         lastPoint = point;
      }
   }
   
   public static void main(String[] args) {
      new Drawing().startController(400,400);
   }

}

Let's talk through this program. For now, I'm going to skip the GUI component set up, which is handled in the begin method. Instead just trust that it also creates and installs three pop-up menus (called JComboBoxes in Java) at the bottom of the window, below the canvas.

The identifier shapes is declared as an instance variable that holds values of type DrawableInterface, an interface that is satisfied by all of the graphical objects in the objectdraw library. It is initialized to hold 20 objects. When the array is created the 20 slots all hold the value null, which represents an uninitialized value. You can test a value to see if it is null (e.g., shapes[4] == null), but if you send a method request to null, you will get a null pointer error, which generally happens when you forget to initialize a value.

Other instance variables keep track of the number of shapes on the screen (numShapes), recall the item currently selected for dragging (if any), and the location where the mouse was when the last event was triggered (lastPoint.

The onMousePress method determines what action is to be taken by checking the item selected in the commandChoice test field. The actual actions taken are the responsibility of the methods addNew. recolorShapeAt, selectShapeAt, and deleteShapeAt.

The method addNew creates a new geometric object on the screen, guided by the values selected in the pop-up menus. Based on what is showing on the shapeChoice menu, it will create a square or circle. It will then set its color according to the value returned by method getSelectedColor(), which returns the color corresponding to the string showing on the colorChoice menu. Once the object has been created and its color set, it is added to the shapes array at the end, and numShapes is incremented.

The method getIndexOf is a helper method (and is hence declared to be private). It determines the index of the top-most element in shapes that contains a point. It is very similar to the corresponding Grace method indexOf. They differ only in the syntax of the for loop.

We now have several private methods to help us write the methods to change color, delete, and move objects. Method getSelectedColor just grabs the text showing on the color menu and using an if-else-if-else returns the corresponding color. We'll talk about getting strings from menus later.

The method getIndexOf is a helper method (and is hence declared to be private). It determines the index of the top-most element in shapes that contains a point. It is very similar to the corresponding Grace method indexOf. They differ only in the syntax of the for loop.

Method recolorShapeAt uses getIndexOf to determine what object was clicked on. If there was on object clicked on then its color is changed. Method deleteShapeAt is similar, but with one twist. The selected element is removed from the canvas, but now we must remove it from the array. Unfortunately, there is no remove method for arrays (in fact, only length is defined). Thus we have to write our won. This is done in method removeEltWithIndex. The idea here is that we start with the element one to the right of the one we wish to remove and shove the elements to the left by one slot. Hence the assignment shapes[objNum] = shapes[objNum + 1]; that goes through all the elements from index to the end. After all the elements have been shifted, we cut back the number of elements by 1.

Method selectShapeAt finds the object selected, moves it to the front of the screen and then moves it in the array to the last element (which makes it the top element in terms of searches). The element is moved to the last element of the array by first removing it from the array (leaving the array one element shorter) and then adding it as the new last element (which is easy) and bumping up numShapes back to the original value.

Method onMouseDrag is as before, except that the name of the method to shift an element is move rather than moveBy.


Multi-dimensional arraysTopArrays continued