budd@mist.cs.orst.edu (Tim Budd) (03/04/88)
Some poor soul wanted to see an example Smalltalk program, and I can sympathize. It is easy enough to find out how to mess your display up by drawing spirals and arcs, but progressing from that to a real application is a significant step, and one for which (as of yet) there doesn't appear to be any real help. To do almost anything reasonable you must at least mention the words ``model-view-controller'', and these are VERY intimidating to the uninitiated. Here at OSU I try to expose junior level undergraduate students to Smalltalk as part of a programming languages course. It is the third new language they see in one term (the other two being Setl and Prolog), and I only have time for three simple assignments, spread over a period of three weeks. Clearly then there is a lot of hand holding, and we can't go too deeply into anything. In the first assignment they simply encounter the browser, find out about pens, and learn how to *do it*. In the second assignment they add some new classes and some new code, to create the commander pen example. For the third assignment they create a new application, a simple McPaint style program. At this point I could just give the McPaint application (it is only 6 methods), but, so as to not take all the fun of discovery away, I will simply give the assignment that I hand out to my class, and let the reader discover the code for themselves. (I appologize for those who don't speak troff, but this seemed easier than posting the troff output). enjoy --tim budd budd@cs.orst.edu department of computer science oregon state university corvallis, oregon 97330 p.s. and before anybody asks, yes, I am the author of Little Smalltalk and yes, I make them use Smalltalk-80, not Little Smalltalk. ------------------------------------------- .SH CS 318 Programming Assignment 10 .br Due, Friday March 11th .PP In your first Smalltalk assignment you investigated the nature of the objects of class Pen. In your second assignment you learned how to add new functionality to the system, by adding new classes and methods. In this assignment we will create an application using these techniques. The application will be a simple painting type program, similar to those made popular on the Macintosh. .PP Before we can begin to describe this program, we need to consider first the way that users communicate with a running Smalltalk program. Users convey information to a program by means of input devices, typically a keyboard and/or a mouse. Part of any program must therefore be devoted to listening to these devices, and responding in an appropriate fashion. This portion of the program is called the \fIController\fP. Another part of the program is concerned with presenting information in a form meaningful to the user, by displaying a representation of the information on an output device, the bitmapped display screen. This part of the program is called the \fIView\fP. Finally a third part of the program is the actual information being manipulated by the user. This is called the \fIModel\fP. .PP This way of dividing interactive programs up into three separate parts, the Model-View-Controller triad, allows a great deal of flexibility and modularity in the design of interfaces. For example, if there are different ways to view data (a graph and a table, for example) an application can have several different views and one controller and one model. In our assignment we will create our own model and controller, and use a standard view which is provided as part of the system. .PP Start in the browser in the first pane, and select ``add category''. Add a new category called \fIpainting\fP. Then modify the class template in the bottom pane to add a new class called \fBPaintController\fP, making it a subclass of \fBMouseMenuController\fP. Instances of PaintController should each have two instance variables, one called pen and one called canvas. There are no class variables. .PP Once you have created and \fIaccepted\fP the class description, click the \fIinstance\fP box in the second pane, then move to the third pane and select ``add protocol''. Add the name ``initialization''. Then modify the method description in the bottom pane so that it looks as follows: .DS I \fBinitialize\fP " painting program initialization " super initialize. pen \(<- Pen new. canvas \(<- Quadrangle fromUser. canvas borderWidth: 2. canvas display. view \(<- StandardSystemView new. view controller: self. " PaintController new startUp " .DE .PP This method tells how to initialize your painting program. The message \fIsuper initialize\fP causes any initialization associated with the superclass (MouseMenuController) to be performed. To initialize our own specific variables we make \fIpen\fP an instance of \fBPen\fP, and canvas a quadrangle (the dimensions of which are obtained interactively from the user). Our application will use the pen to write on the canvas. We put a border on the canvas quadrangle and display it. We then create an instance of a standard system view to serve as our view, and indicate that the controller for the view will be ourselves. .PP Now move to the third pane and add a protocol group for ``control activity''. In order to make our program respond to control events (that is, mouse and keyboard events) we need to have some way of telling the system when we want our program to take over control, and when we want to give up control. By default our program will be given control when it is started, and the easiest thing is to say we will give up control when the rightmost button is pressed. This button is known, for historical reasons, as the blue button. By defining the following method we indicate that our program should run as long as the blue button has not been pressed. Enter this method. .DS I \fBisControlActive\fP " run as long as the blue button is not pressed " \(ua sensor blueButtonPressed not .DE .PP But what is it we want our program to do? We want to create the ability to draw on the canvas. Let us set it up so that when the user presses the leftmost button (called the red button) we start drawing, and when the user presses the button again we stop drawing. We can associate an action that is to take place when a button is pressed by defining a method called \fBredButtonActivity\fP. We could start out, therefore, with something like the following: .DS I \fBredButtonActivity\fP " draw on the canvas until the red button is pressed again " [ sensor redButtonPressed not ] whileTrue: [ pen goto: sensor cursorPoint ] .DE .PP Enter this method, then go back to the initialize method and select the example text given in the final comment and \fIdo it\fP. A little square will appear under the cursor. Move down to the bottom of the display (so that you aren't on top of the browser) and click the leftmost button - the cursor will then move down to the lower left part of the rectangle, which you can adjust to any size you want. When you have a size you like, click the leftmost button again. Move into region you have outlined (the canvas region) and press the left button (the red button) again. The cursor should disappear, and the pen should track the cursor, drawing on the canvas. Click the button to make it stop. (I have found the mice in our lab to be somewhat unresponsive - probably they need a good cleaning. You may have to click several times or hold down the mouse to get the action you want. When then cursor disappears you are drawing, and when the cursor reappears you are not). You stop the program by clicking the blue button (the rightmost button). Note you can do this only when you are not drawing (when you have a cursor). .SH Question 1 .PP There are several problems with this. Try drawing outside of the canvas. Does it work? When you stop drawing, move the cursor, and start drawing again, what happens? .sp 2i .PP Lets address the second problem first. The problem is that when we start we want to without drawing first move the pen to the location where the user pressed the mouse down. You may have discovered already that if you give a pen the command \fBup\fP it will quit drawing, and if you give it the command \fBdown\fP it will start again. Thus, to move the pen to the location where the mouse event took place we need the three statement sequence: .DS I pen up. pen goto: sensor cursorPoint. pen down. .DE .PP Next, to tell if the current location is inside of the canvas, we can use the fact that rectangles (such as our canvas) respond to the message \fBcontainsPoint:\fP. Then we want to wrap our goto message in a conditional test, as follows: .DS I (canvas containsPoint: sensor cursorPoint) ifTrue: [ pen goto: sensor cursorPoint ] .DE .SH Question 2 .PP Making both of these changes, write the revised method for \fBredButtonActivity\fP. Accept it in the system, and write it below. .sp 2i .PP Well, pens are nice, but to be really interesting it would be useful if we had other writing instruments, such as a brush or an eraser. Both of these can be described as modifications of attributes of our pen. A brush can be described as a pen with a nib size of 4 and a black mask, an eraser as a pen with a nib size of 4 and a white mask, and of course a pen has a nib size of 1 and a black mask. If you don't remember how to modify nib sizes and masks, go back to class \fBPen\fP and browse around. .PP Well, how do we give the user the ability to make changes? The obvious solution is a menu. Let us use the middle button (the so called ``yellow'' button) for our menu selector. Our superclass, \fBMouseMenuController\fP, allows us to create a menu very easily with a single message. Go back to our initialization message, and add the following line to the very end. .DS I self yellowButtonMenu: (PopUpMenu labels: 'pen\ebrush\eerase' withCRs) yellowButtonMessages: #(doPen doBrush doErase). .DE .PP This message creates a new menu (an instance of PopUpMenu) with labels pen, brush, and erase. The message withCRs takes a string and repaces backslashes with carrage returns, which separate the different menu items from each other. When the user selects one of these, one of the messages doPen, doBrush or doErase will be activated. Each of these should change the characteristics of our pen into the desired format. After adding this line you may want to try your application. Start it up. When you are not drawing (that is, when you have a cursor), try pressing the middle (yellow) button. A menu should appear. Move out of the menu before releasing the cursor, otherwise you will receive an error box, since the controller will try to call a method that does not exist yet. .PP Now move to the third pane, select ``add protocol'', and create the category ``pen modification''. Then in this group, create the three methods doPen, doBrush, and doErase. Each of these should consist of just two statements, which set the pen nib and mask appropriately. .SH Question 3 .PP Write below the three methods doPen, doBrush and doErase. .sp 2i .PP Go back to the initialization method and start your application up again. Note how the pen should stay in the borders of the canvas. Try using the middle button to change the pen characteristics. Note that you can only change the pen characteristics when you are not drawing (that is, when you have a cursor). .PP There are of course a lot of additions we might want to make to our program. For example, you might want to allow gray tones, rather than simply black and white. You might also want to draw other forms, such as circles and rectangles. You may wish to investigate these other graphics classes and think about how this might be done. .PP When you are all finished, file your solution out (see handout on filing out). Attach to your answers to the questions posed here a copy of the filed out form of your solution.