Previous
Contents
Next

First Steps: Displaying Text in a Window


This section discusses the use of text, text attributes and fonts. The use of a PluggableTextView is also discussed in some detail. In this example, the model will substantially support the window.

At the end of the lesson we will have a window with several views that allows us to display a piece of text in different styles.

To do this example, we have to add one method to the instance protocol of PluggableTextView that adds additional flexibility to instances of that class. Please add this to the instance protocol of PluggableTextView if it is not already present:

editString: aParagraphOrString

  (aParagraphOrString isKindOf: DisplayText)
     ifFalse: [super editString: aParagraphOrString]
     ifTrue:
       [displayContents :=
          Paragraph
            withText: aParagraphOrString text
            style: aParagraphOrString textStyle
            compositionRectangle: (self insetDisplayBox insetBy: 6 @ 0)
            clippingRectangle: self insetDisplayBox
            foreColor: self foregroundColor
            backColor: self backgroundColor.
	(self controller isKindOf: ParagraphEditor)
           ifTrue: [controller changeParagraph: displayContents]
       ]

This is a method of general value, there is no need to remove it after this exercise.

We want to create a class that displays a string in a window. To store that string, we provide the class with an instance variable:

Model subclass: #TextStyleDemo
      instanceVariableNames: 'displayString'
      classVariableNames: ''
      poolDictionaries: ''
      category: 'MVCTutorial-TextStyles'

We want to ensure that the model always has a string to display. To do that, we provide an instance initialization method:

initialize
   displayString := 'The quick brown fox jumped over the lazy sleeping dog.'

The sentence 'The quick brown fox jumped over the lazy sleeping dog' contains all letters of the latin alphabet. In the time of electromechanical equipment it was used to test the proper work of teletyping machines. To verify the use of all letters of the alphabet, you can execute this statement with 'print it':

String withAll:
    'the quick brown fox jumped over the lazy sleeping dog'
         asSet asSortedCollection

Back to our work:
Class TextStyleDemo needs a window; we add the method to create it to the class protocol of the class:

open
    "TextStyleDemo open"
    | model topView textView |

    model := self new.
    topView := StandardSystemView new.
    topView label: 'TextStyle Demo';
             model: model;
             borderWidth: 1.
    textView := PluggableTextView
                     on: model
                     text: #getText
                     accept: nil
                     readSelection: nil
                     menu: nil. 
    textView borderWidth: 1.
    textView window: (0 @ 0 extent: 300 @ 600).

    topView addSubView: textView.

    topView controller open

The easy part of this method is that we create a top window. This time we use a StandardSystemView which is sufficient because we do not want to display a multicolored window content this time.

An instance of PluggableTextView is added as a subview to the topview. A PluggableTextView needs a model and the names of four methods that it will use to communicate with its model. The instance creation method
on: text: accept: readSelection: menu:
provides the instance to be created with a model and with all needed method names. In our example, we provide only the model and a name for the method that is sent to get the text to be displayed. Other access methods are not needed at this moment.

The PluggableTextView will send the message getText to its model to get the text to be displayed. We add a method with that name to the instance protocol of our class:

getText
     " return a paragraph that a
       PluggableTextView can display. "
  | text textStyle |

  text := displayString asText.
  textStyle := TextStyle default copy.
  ^Paragraph withText: text
             style: textStyle.

This method converts the string into a text and puts it into a paragraph that is also given a text style. For now we use a copy of the default text style, which is always a good first choice. This method will be substantially changed later.

Now that we have added this method, we can test our window for the first time. It comes up quite large, but you can resize it and than you have this:

A PluggabelTextView

To open the window at a smaller size, we add the method initialExtent to the instance protocol of the window model:

initialExtent
   ^220 @ 170

The text view has a menu: It is the same menu that you see in a workspace. Where does this menu comes from? It comes from ParagraphEditor where it is provided as the default menu for all PluggableTextViews. ParagraphEditor is the default controller class of a PluggableTextView and the controller of our view is an instance of ParagraphEditor.

To get rid of the default menu, we have to tell the PluggableTextView that it should request a menu from its model:

open
    "TextStyleDemo open"
    | model topView textView |

   model := self new.
   topView := StandardSystemView new.
   topView label: 'TextStyle Demo';
            model: model;
            borderWidth: 1.
   textView := PluggableTextView
                    on: model
                    text: #getText
                    accept: nil
                    readSelection: nil
                    menu: #getTextMenu:.
   textView borderWidth: 1.
   textView window: (0 @ 0 extent: 300 @ 600).

   topView addSubView: textView.

   topView controller open

In the instance protocol of the model, we have to add a method with name: getTextMenu:. We restrict ourselves to the items 'copy', 'cut', 'paste' and to an item that we will use to put our own sentence into the window. This gives a beautiful small menu with only four items:

getTextMenu: aCustomMenu

    ^SelectionMenu
        labels: 'copy\cut\paste\enter text...' withCRs
        lines: #(3)
        selections: #(#copySelection #cut #paste #enterText)

In this method, we do not use the empty CustomMenu that the method sender offers. We prefer to use a SelectionMenu which is simpler, but sufficient for our problem.

We can now open a new window to test this change. The text view has now a menu with only four items. When we select the option 'enter text...' we will get a walkback window, because we forgot to define a method for the selector #enterText that is associated with that menu option. It is not difficult to add such a method:

enterText

  displayString := FillInTheBlank
                         request: 'your sentence, please ...'
                         initialAnswer: displayString.
  self changed: #getText

Here the most important statement is of course the self changed: #getText which informs the text window about a change that requires an action from the side of the view. The view reacts in this way:

We do not need to implement #copySelection, #cut, #paste. They are implemented in ParagraphEditor, the default controller of a text view. Messages that are not implemented in the model are automatically forwarded to the controller. So we get copy, cut and paste for free.

The change set for this example is TextStyleDemo.1.cs.

What we have learned:


Previous
Contents
Next