parent previous next question (Smalltalk Textbook 03)

EngiTopView

In this section we will create a useful new class which has features of both 'ScheduledWindow' and 'CompositePart'. An instance of 'ScheduledWindow' can only have one display component. And an instance of 'CompositePart' can group several display components together. But an instance of 'CompositePart' cannot handle size, display position, label, and the blue button menu of a window of type 'ScheduledWindow'.

So, we will create a new class named 'EngiTopView', which has both of these features. Program 3-1 shows the use of 'EngiTopView'. (Before you can execute Program 3-1, you must 'file in' Appendix 01 and also see the Patch for VisualWorks 2.0)


Program-3-1: (EngiTopView, LookPreferences, View; scrollbar)
---------------------------------------------------------------------
| topView edgeDecorator1 edgeDecorator2 |
topView := EngiTopView
                  model: nil
                  label: 'EngiTopView'
                  minimumSize: 250 @ 250.
edgeDecorator1 := LookPreferences edgeDecorator on: View new.
edgeDecorator1 noMenuBar.
edgeDecorator1 noVerticalScrollBar.
edgeDecorator1 noHorizontalScrollBar.
edgeDecorator2 := LookPreferences edgeDecorator on: View new.
edgeDecorator2 noMenuBar.
edgeDecorator2 noVerticalScrollBar.
edgeDecorator2 noHorizontalScrollBar.
topView add: edgeDecorator1 in: (0 @ 0 corner: 1 @ 0.5).
topView add: edgeDecorator2 in: (0 @ 0.5 corner: 1 @ 1).
topView open
---------------------------------------------------------------------

'EngiTopView' is used just like 'ScheduledWindow' but may have more than one display component as 'CompositePart'. You can send 'add:in:' message to 'topView' which is an instance of 'EngiTopView'. Program 3-2 shows that 'EngiTopView' can accept a 'popUp' message to open a window, in which case it returns a Boolean value indicating whether the user clicked on 'accept' or 'cancel'. ?? [default buttons don't work on the Mac. They highlight, but when the user presses Enter, nothing happens]


Program-3-2: (EngiTopView, LookPreferences, View; scrollbar; popUp)
---------------------------------------------------------------------
| topView edgeDecorator1 edgeDecorator2 aBoolan |
topView := EngiTopView new.
topView minimumSize: 250 @ 290.
edgeDecorator1 := LookPreferences edgeDecorator on: View new.
edgeDecorator1 noMenuBar.
edgeDecorator1 noVerticalScrollBar.
edgeDecorator1 noHorizontalScrollBar.
edgeDecorator2 := LookPreferences edgeDecorator on: View new.
edgeDecorator2 noMenuBar.
edgeDecorator2 noVerticalScrollBar.
edgeDecorator2 noHorizontalScrollBar.
topView add: edgeDecorator1 in: (0 @ 0 corner: 1 @ 0.5).
topView add: edgeDecorator2 in: (0 @ 0.5 corner: 1 @ 1).
aBoolan := topView popUp.
^aBoolan
---------------------------------------------------------------------

This program opens a dialog-type-window which has two buttons in the lower part. One is an 'accept' button which is highlighted to indicate it is the default button and the other is a 'cancel' button.

'EngiTopView' accepts various messages as follows.

Open a window with 'accept' as a default button and 'cancel' as a normal button.

---------------------------------------------------------------------
      EngiTopView new popUpAcceptCancel: true
---------------------------------------------------------------------

Open a window with 'accept' and 'cancel' buttons, but no default.

---------------------------------------------------------------------
      EngiTopView new popUpAcceptCancel: nil
---------------------------------------------------------------------

Open a window with 'cancel' as a default button and 'accept' as a normal button.

---------------------------------------------------------------------
      EngiTopView new popUpAcceptCancel: false
---------------------------------------------------------------------

Open a window with 'yes' as a default button and 'no' as a normal button.

---------------------------------------------------------------------
      EngiTopView new popUpYesNo: true
---------------------------------------------------------------------

Open a window with both 'yes' and 'no' as normal buttons.

---------------------------------------------------------------------
      EngiTopView new popUpYesNo: nil
---------------------------------------------------------------------

Open a window with 'no' as a default button and 'yes' as a normal button.

---------------------------------------------------------------------
      EngiTopView new popUpYesNo: false
---------------------------------------------------------------------

Open a window with 'agree' as a default button and 'retract' as a normal button.

---------------------------------------------------------------------
      EngiTopView new
            popUp: true
            trueLabel: 'agree'
            falseLabel: 'retract'
---------------------------------------------------------------------

Open a window with 'okay' as the only button, and also as the default.

---------------------------------------------------------------------
      EngiTopView new popUpOkay: true
---------------------------------------------------------------------

Open a window with 'okay' as a normal button.

---------------------------------------------------------------------
      EngiTopView new popUpOkay: nil
---------------------------------------------------------------------

Open a window with 'I see' as default button.

---------------------------------------------------------------------
      EngiTopView new popUp: true label: 'I see'
---------------------------------------------------------------------

Let us design and implement 'EngiTopView' given the requirements illustrated above. First we make 'EngiTopView' a sub class of 'CompositePart'. This provides us with a CompositePart to group display components together into one object. Next, consider how we can make 'EngiTopView' behave like 'ScheduledWindow'. To do this, 'EngiTopView' must have 3 instance variables (model,label,extent). Program 3-3 shows the resulting class definition of 'EngiTopView'.


Program-3-3: (EngiTopView, CompositePart; model, label, extent)
----------------------------------------------------------------------
CompositePart subclass: #EngiTopView
      instanceVariableNames: 'model label extent'
      classVariableNames: ''
      poolDictionaries: ''
      category: 'Engi-Interface'
----------------------------------------------------------------------

A brief description of Class Methods follows.

(To create instances)
model: aModel label: labelString minimumSize: minimumSize
new

(Each of these methods returns an instance of 'LayoutFrame')
frameFraction: fractionRectangle
frameFraction: fractionRectangle offset: offsetRectangle
frameOffset: offsetRectangle

A brief description of Instance Methods follows.

(To access window labels)
label
label: aString

(To access the minimum size of the window)
minimumSize
minimumSize: aPoint

(To access the model)
model
model: aModel

(Open/Close windows)
close
open
popUp
popUp: aBooleanOrNil label: labelString
popUp: aBooleanOrNil trueLabel: trueLabel falseLabel: falseLabel
popUpAcceptCancel: aBooleanOrNil
popUpOkay: aBooleanOrNil
popUpYesNo: aBooleanOrNil

(To return an instance of 'LayoutFrame')
frameFraction: fractionRectangle
frameFraction: fractionRectangle offset: offsetRectangle
frameOffset: offsetRectangle

(Minimum window size of a pop up dialog)
defaultPopUpMinimumSize

(To set the model, label and minimum size of a window)
model: aModel label: labelText minimumSize: minimumSize

The most difficult part of this program is 'popUp:trueLabel:falseLabel:'. Here is the method.


Program-3-4: (EngiTopView, CompositePart, ScheduledWindow, Button, 
ValueHolder, PluggableAdaptor, Rectangle, InputState; popUp)
----------------------------------------------------------------------
popUp: aBooleanOrNil trueLabel: trueString falseLabel: falseString
      | topWindow compositePart aModel
            trueButton falseButton aRectangle |
      topWindow := ScheduledWindow
                        model: model
                        label: label
                        minimumSize: extent.
      topWindow controller: EngiTopPreemptor new.
      compositePart := CompositePart new.
      aModel := ValueHolder new.
      trueButton := Button trigger.
      aBooleanOrNil = true ifTrue: [trueButton beDefault].
      trueButton label: trueString.
      trueButton model: ((PluggableAdaptor on: aModel)
                  getBlock: [:m | false]
                  putBlock:
                        [:m :v |
                        aModel value: true.
                        self close]
                  updateBlock: [:m :a :v | false]).
      falseButton := Button trigger.
      aBooleanOrNil = false ifTrue: [falseButton beDefault].
      falseButton label: falseString.
      falseButton model: ((PluggableAdaptor on: aModel)
                  getBlock: [:m | false]
                  putBlock:
                        [:m :v |
                        aModel value: false.
                        self close]
                  updateBlock: [:m :a :v | false]).
      compositePart
            add: self
            in: (self frameFraction: (0 @ 0 corner: 1 @ 1)
                  offset: (0 @ 0 corner: 0 @ -40)).
      compositePart
            add: trueButton
            in: (self frameFraction: (0.3 @ 1 corner: 0.3 @ 1)
                  offset: (-40 @ -35 corner: 40 @ -5)).
      compositePart
            add: falseButton
            in: (self frameFraction: (0.7 @ 1 corner: 0.7 @ 1)
                  offset: (-40 @ -35 corner: 40 @ -5)).
      topWindow component: compositePart.
      aRectangle := Point zero
                  extent: (topWindow minimumSize
                              max: self defaultPopUpMinimumSize).
      aRectangle := aRectangle
                  align: aRectangle center
                  with: InputState default mousePoint.
      topWindow openDialogIn: aRectangle.
      ^aModel value
----------------------------------------------------------------------

At first glance, this method is might be difficult to understand. However, if you master 'ScheduledWindow' and 'CompositePart', you'll be able to understand 'EngiTopView' also.

    This method:
  1. Creates two buttons and 'aScheduledWindow'
  2. Adds the buttons and self (the EngiTopView instance) to 'aCompositePart' as the display component of 'aScheduledWindow'
  3. Opens a dialog window centered around the current mouse position
  4. Waits for the user to click on a button. An instance of 'ValueHolder' named 'aModel' records which button (True or False) is pressed. When a button is pressed, the view is closed and the value of that button is returned as the value of the method.

The 4th step is expressed as a block closure of a button model's 'putBlock'. This block closure is evaluated when the button is pressed. Please notice line 8 which specifies an instance of 'EngiTopPreemptor' as the controller of the 'ScheduledWindow'. This controller handles all events (keyboard, mouse, etc). It has the highest processing priority and once it gets control, it does not relenquish it until it completes.

You might recall how a dialog or pop-up window disappears when you click the mouse outside of the window. In that case 'StandardSystemController' is used as the controller which releases control from the window when the mouse cursor is moved out of the window. Thus, when you click the mouse on another window, that window is raised. When 'EngiTopPreemptor' is used to open a 'EngiTopView' as a dialog or pop-up, once it gets control it does not relenquish it until a button is clicked. 'EngiTopPreemptor' is a specialized version of 'StandardSystemController'.


parent previous next question
Copyright (C) 1994-1996 by Atsushi Aoki
Translated by Kaoru Rin Hayashi & Brent N. Reeves