For this lab, we return to the GUI program that you worked on in Lab 9 and Lab 10. You will add two new tools to this program, and you will do something with resources such as cursors or sound. There is also a variety of extra credit exercises that you can try, if you like.
To build on your previous work, you should make a copy of your lab10 project and name the copy lab13. If you are not completely happy with your own solution to Labs 9 and 10, you can use my solution. You will find it in the directory /classes/s10/cs225/draw/. To use this directory, start a new project for lab13 and copy-and-paste the draw directory into that project.
This lab is due at the beginning of next week's lab. To submit it, copy the lab13 folder from your Eclipse workspace into your homework folder in /classes/s10/cs225/homework.
For the first part of the lab, you should add two new tools to the program: A text tool and a dragging (or selection) tool. Add commands to the "Tool" menu to let the user select these tools, and add new cases to the menu handler to handle the two new commands.
The text tool should allow the user to add a one-line string of text as an item in the picture. When the user presses the mouse while using the text tool, you should pop up a dialog box where the user can enter a line of text. For the dialog box, you can use the method JOptionPane.showInputDialog(component,message) to do this. This method asks the user a question and returns the user's response as a String. If the user cancels the dialog, then the return value is null. If the user inputs a non-empty string, you should add it to the picture at the point where the user clicked the mouse. To implement this, you will have to create a new subclass of Drawable to represent text items. A text item should have a color, which should be set to the current drawing color when the item is created.
The second required tool is a "Drag" tool. When the user has selected this tool, the user can use the mouse to drag items that already exist in the picture to new positions. (You can decide to do a "Select" tool instead -- one of the optional exercises below. The "Select" tool's functionality includes dragging.)
When the user presses the mouse while using the "Drag" tool, you must first figure out which item, if any, the user has clicked. To help you do this, you should add a new method
abstract public boolean hit(int a, int b);
to the Drawable class. This method is meant to test whether the point (a,b) "hits" the shape. You will have to implement this method for every kind of Drawable object. The simplest implementation will just test whether (a,b) is inside the rectangle that outlines the shape. For rectangles and ovals, that would just mean that
(a >= x && a <= x+width && b > y && b <= y+height)
where (x,y) is the upper left corner of the rectangle and width and height are its size. Or, you could try to implement more exact methods for ovals and for unfilled rectangles. For text, you will need to know the rectangle that encloses the text. The best thing to do is to compute and save this rectangle in the draw method of the text item, using a FontMetrics object.
When deciding which shape was hit, you should test the items in the arraylist in reverse order, so that when one shape is on top of another, you test the one that is on top first.
Once you know which shape is hit, you have to let the user drag that shape by dragging the mouse. You need to store three pieces of information in instance variables in the mouse handler: the shape that is being dragged and the x- and y-coordinates of the previous mouse position. All these variables must be set in the mousePressed() method. The previous x- and y-coordinates must also be reset at the end of the mouseDragged method.
In the mouseDragged method, you will then know which shape is being dragged, if any. You will know the previous mouse position (prevX,prevY) and the current mouse position (evt.getX(),evt.getY()). To implement dragging, you just have to move the shape exactly as far as the mouse has moved. That is, you must add (evt.getX() − prevX) to the x-position of the shape and (evt.getY() − prevY) to the y-position of the shape.
The second required part of the lab is to add some feature to the program that uses image or sound resources. Resource files for you to use can be found in the directory /classes/s10/cs225/resources. You will need only one of the subdirectories of that directory (unless you want to do some extra work for extra credit). Here are your options; you only need to do one of these:
This section suggests some things that you might do for extra credit. These are things that I might have required you to add to the program if we had more time.
Font Menu. It's nice to be able to control the font that is used for text items. You can have a set or current settings for the font, which are applied when a new text item is created. The current settings would be stored as properties in the Drawable item that represents the text. A "Font" menu could offer options such as: font size, italic, bold, and font name. Italic and bold are best represented as by JCheckboxMenuItems. For font size, you could either have a single command that pops up an input dialog where the user can enter the size, or you could have several commands offering a variety of specific font sizes. For the font name, you could have an individual command for each possibility (maybe just "Serif", "SansSerif", and "Monospaced"). Note: You might want to put some of the commands in submenus. Submenus are easy! You make a submenu just by adding one menu to another.
Freehand Curves. Add a freehand curve tool. The user can use this tool to sketch curves freehand. The Drawable item that represents the curve has to keep a list of all the points that are visited as the user drags the mouse.
Editing Objects. It would be nice to be able to change the properties of items after they have been created. For this, you need to be able to select the item that is to be edited. Add a selectedItem instance variable to your panel. It will probably work best to make it of type int. When no item is selected, the value should be -1. When an item is selected, the value should be the index of the selected item in the list of items. The paintComponent method can draw a box around the selected item, if there is one. When the user presses the mouse, find the item that was hit. If no item was hit, set selectedItem to -1. If an item was hit, set selectedItem to its index. The user can then drag the selected item or not -- in any case, is stays selected even after the user releases the mouse. Now, you need a way to edit the item. Make an "Object" menu with commands for modifying the object. For example, you can change its color. You can move it to the front or to the back of the list. For a text item, it might be nice to be able to change the text. For a shape, it would be nice to be able to change the size. You could have a command for deleting the selected item.
Improved Save Commands. Most programs have both a "Save" and a "Save As" command. The "Save As" commands acts like the "Save" command that you have already implemented; that is, it makes a new file to which it writes the data. The "Save" command, on the other hand, implements the idea of editing a file: When you open a file, change the picture, and Save, the new data should go back into the same file without asking you where you want to save it. Similarly, if you save the picture into a file, make some changes, and Save again, the changed data should go into the same file. To implement this, you just need to keep track of the file in an instance variable. When you open or save a file, store the File in an instance variable. When the "Save" command is used, check that instance variable. If it is non-null, save the data to that file; if the file is null, ask the user to select the output file. It's a nice touch to use the file name in the title bar of the window.
Window Events. When the user tries to close a window that has unsaved changes, it's common to ask the user if they really want to close it. You can keep a boolean instance variable named dirty to check whether the content of the window has changed. Set dirty equal to false when the user saves a file or clears the data. Set dirty equal to true whenever any change is made, such as when a new item is added or an item is dragged. When the user tries to close the window, test the value of dirty; if it's true, ask the user whether they really want to quit. The question is, how to tell that the user is trying to close the window? For that, you have to use a WindowListener. First of all, you can't set the default close operation on the JFrame to EXIT_ON_CLOSE because that setting means that the program ends as soon as the user clicks the close box, with no chance to intervene. So, set the default close operation to JFrame.DO_NOTHING_ON_CLOSE instead. This setting gives you full responsibility to say how to respond to a click on the JFrame's close box. To respond, you have to add a WindowListener to the JFrame. The windowClosing method in a WindowListener is called when the user clicks the close box of the window. In this method, you can optionally exit from the program, or take whatever other action you want. Here, for example, is the main program from a version of the drawing program that implements these ideas:
public static void main(String[] args) { final JFrame window = new JFrame("SimpleDraw"); final DrawPanel panel = new DrawPanel(); window.setContentPane(panel); window.setJMenuBar(panel.createMenuBar()); window.pack(); window.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); window.setResizable(false); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); window.setLocation( (screenSize.width - window.getWidth()) / 2, (screenSize.height - window.getHeight()) / 2 ); window.setVisible(true); window.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { if (panel.dirty) { // Ask whether user wants to close. int resp = JOptionPane.showConfirmDialog(window, "You have unsaved changes.\nDo you really want to close?", "Confirm close", JOptionPane.YES_NO_OPTION); if (resp == JOptionPane.YES_OPTION) { System.exit(0); // End the program if user wants to close. } } else { // Close without asking, if dirty is false. System.exit(0); // End the program if user wants to close. } } }); }