Previous
Contents
Next

First Steps: Drawing into a Window (continuation)


Let us first discuss the exercise from the last session:

We want to add a menu item that brings up a second menu where you can select the number of vertex points in the range 3 .. 20.

First of all we need a second instance variable to store this number.

View subclass: #DrawingDisplayView
	instanceVariableNames: 'displayColor nroOfVertices '
	classVariableNames: ''
	poolDictionaries: ''
	category: 'MVC-FirstSteps'

Then we need an instance method that gives us the stored number or the default value, when the instance variable has no value:

nroOfVertices

  ^nroOfVertices ifNil: [5].

We have of course to slightly modify the display method, that has to use the stored value or, in the absence of a stored value, the default value.

displayView
 | points radius centralAngle n pen boxWidth center |

  boxWidth := self insetDisplayBox width min: self insetDisplayBox height.
  radius := boxWidth //2 - 16.
  center := self insetDisplayBox center.
  n := self nroOfVertices.
  centralAngle := 360.0/n.
  points := OrderedCollection new: n.
  0 to: n - 1 do:
      [:idx | | angle |
        angle := centralAngle * idx.
        points add: ((angle degreeSin @ angle degreeCos) * radius) rounded
                       + center].
  Display fill: self insetDisplayBox
          fillColor: Color white.  
  pen := Pen new
       defaultNib: 2;
       color: (displayColor ifNil: [Color black]);
       place: points last.
  points do: [:pt | pen goto: pt].
  pen roundNib: 9.
  1 to: n do:
      [:idx | | angle pt |
        angle := centralAngle * (idx - 1).
        pt := points at: idx.
        pen color: (Color h: angle s: 0.5 v: 0.9).
        pen place: pt; goto: pt].

Note that the instance method nroOfVertices is used to get the default when it is needed. When you do not want to do this, you have to write something like:

nroOfVertices isNil
  ifTrue: [n := 5]
  ifFalse: [n := nroOfVertices]

The simple assignment n := nroOfVertices can not be used because it will not always assign a number.

Finally, we have to add a new item to the menu:

getMenu: aBoolean
   | menu |
  menu := CustomMenu new.
  menu add: 'yellow' target: self selector: #setColor: argument: (Color yellow);
       add: 'green'  target: self selector: #setColor: argument: (Color green);
       add: 'blue'   target: self selector: #setColor: argument: (Color blue);
       add: 'red'    target: self selector: #setColor: argument: (Color red);
       add: 'gray'   target: self selector: #setColor: argument: (Color lightGray);
       addLine;
       add: 'nro of vertices'
          target: self selector: #setNroOfVertices: argument: self nroOfVertices.
 ^menu

We put a line between the color selection items and the new item. This is not necessary, but it tells the user that the new item is unrelated to the previous items.

The new menu item is handled by the instance method setNroOfVertices:. This method brings up an additional menu:

setNroOfVertices: anInteger
     | selectionIndex |
   selectionIndex := 
     (PopUpMenu
        labelArray: ((3 to: 20) collect: [:int | int printString])
      )
     startUpWithCaption: 
        'Nro of vertices? (currently: ', anInteger printString, ')'.
   selectionIndex ~= 0
     ifTrue:
       [nroOfVertices := selectionIndex + 2.
        self displayView].

This is a small method, but it contains a lot of interesting features:

Alternatively, we can use a prompter:

setNroOfVertices: anInteger
  | answerString |
   answerString := FillInTheBlank request: 'The number of vertices ?'
                                  initialAnswer: anInteger printString.
   nroOfVertices := Number readFromString: answerString.
   self displayView.

A prompter (FillInTheBlank) poses a question and waits for an answer. It returns the answer as a string. We cannot use that string immediately, we have to convert it into a number. We do this with the method Number>>readFromString:.

When we use a menu, we define the set of possible answers. When a prompter is used, we can not know what the user will answer. It is recommended to verify that the answer is really the writing form of a positive integer. The following version of the method setNroOfVertices: does this:

setNroOfVertices: anInteger
  | answerString strippedString n |
   answerString := FillInTheBlank request: 'The number of vertices ?'
                                  initialAnswer: anInteger printString.
   strippedString := answerString withBlanksTrimmed.
   (strippedString allSatisfy: [:character | character isDigit])
      ifFalse:
        [self inform: 'Your input is not an integer number'.
         ^self].
   n := Number readFrom: strippedString.
   n >= 3
     ifTrue:
       [nroOfVertices := n.
        self displayView]
     ifFalse:
       [self inform: 'at least 3 vertices are required'].

To give our example a final touch, we want to display the number of the vertices in the middle of the polygon. This requires that we create a DisplayText and place it centered into the display area of the view:

displayNroOfVertices

   | dt boundingBoxOfText displayRectangle |
  dt := DisplayText text: self nroOfVertices printString asText
                    textStyle: TextStyle default copy.
  dt foregroundColor: Color black
     backgroundColor: Color white.
  boundingBoxOfText := dt boundingBox.
  displayRectangle := Rectangle center: self insetDisplayBox center
                                extent: boundingBoxOfText extent.
  dt displayAt: displayRectangle origin.

Here is what the five statements of this method do:

We can of course use the class method open4Views to create a window with four polygons. After selection of different vertex numbers in the subviews this may give a result similar to this one:

four polygons with colored vertices

Perhaps you may wish to use a larger font. Here is a revised method that uses a larger font. The desired font size is given as argument of the method fontIndexOfSize:.

displayNroOfVertices
   | dt boundingBoxOfText displayRectangle text |
  text := self nroOfVertices printString asText.
  text addAttribute:
        (TextFontChange 
           fontNumber: (TextStyle default fontIndexOfSize: 22)).
  dt := DisplayText text: text
                    textStyle: TextStyle default copy.
  dt foregroundColor: Color black
     backgroundColor: Color white.
  boundingBoxOfText := dt boundingBox.
  displayRectangle := Rectangle center: self insetDisplayBox center
                                extent: boundingBoxOfText extent.
  dt displayAt: displayRectangle origin.

This example can be loaded from changes set GraphicalDemoFinal.cs.

Hint: A previously created window will not show a larger font immediately. You have to resize the window to see the change.

What we have learned:


Previous
Contents
Next