Fenestra User's Guide (Chapter 1) -- contents | previous | next


1 Tutorial: Tic-tac-toe

As an introduction to the library, this chapter presents a complete small application with a main window, a dialog box, and a mouse interface. It will show how to use windows, draw in them, and how to build dialog boxes. It is a practical and realistic example: it has code for the non-GUI part of the application, and thus illustrates how the user interface interacts with the body of the program.

1.1 The application: a tic-tac-toe game

The sample application is an implementation of the Tic-tac-toe game (where the players try to make a line of three pieces on a 3 x 3 board). The following image shows the main window:

screenshot of tictactoe app

The mouse will be used to position a new piece on board, or to start a new game if the previous game was finished.

The `Game' menu will have three items:

The program will be made of three main classes: one for the game model, another for the main window, and one for the options dialog. The whole program is made entirely of Eiffel code, but for the program's icon which is the only external resource, which has to be compiled with the Win32 SDK's resource compiler and linked into the program executable file.

Please note that this chapter does not explain how to build a system description file -- the Eiffel's system `makefile' -- as this depends on the compiler used; section (see section 2.5) gives details on how to build programs with the supported compilers. The samples/tutorial directory in your installation includes the appropriate files for all supported compilers.

1.2 The model

The tic-tac-toe game itself is implemented in the class TICTACTOE. Here is its flat-short form:

class interface TICTACTOE

creation

    make

feature specification

    -- Constant(s)

    max_level : INTEGER is ???
            -- Maximum computer intelligence.

feature specification

    -- Reset

    reset
            -- Create/initialise.

feature specification

    -- Option(s)

    set_level (lvl : INTEGER)
            -- Level (1: Easy, Max_level: Difficult).
        require
            valid : (lvl > 0) and (lvl <= max_level);
        ensure
            done : level = lvl;

feature specification

    -- Status

    is_square (col, row : INTEGER) : BOOLEAN
            -- Is this (col,row) correct?
        ensure
            done : Result = ((((col >= 1) and (col <= 3)) and (row >= 1)) and (row <= 3));

    is_empty (col, row : INTEGER) : BOOLEAN
            -- Is this square empty?
        require
            in : is_square (col, row);

    is_user (col, row : INTEGER) : BOOLEAN
            -- Is this a user's square?
        require
            in : is_square (col, row);

    is_computer (col, row : INTEGER) : BOOLEAN
            -- Is this a computer's square?
        require
            in : is_square (col, row);

    is_finished : BOOLEAN
            -- Finished?

    is_user_winner : BOOLEAN
            -- Who wins?
        require
            ok : is_finished;

    is_computer_winner : BOOLEAN
        require
            ok : is_finished;

feature specification

    -- Action

    move (col, row : INTEGER)
            -- User plays on (col,row).
        require
            notend : not is_finished;
            ok : is_square (col, row);
            empty : is_empty (col, row);

    computer_move
            -- Computer plays.
        require
            notend : not is_finished;

end interface -- class TICTACTOE

As the functionality of this class is completely independent of the user interface, the implementation details are not shown here (the full source code of the example is of course provided with the library distribution). This same class can be used independently of the user interface -- it could be used with another GUI system or a TTY version of the game. As far as possible, it is good practice to separate the user interface code from what the program actually does.

1.3 The view

After the model, it's time to see the principal user interface class, the application's main window. The class, VIEW, inherits from APPLICATION, an heir of WINDOW through OVERLAPPED_WINDOW.

The principal class of this system is VIEW. Every application needs a main window which inherits from APPLICATION. The make procedure of APPLICATION launches the event loop (in the class NOTIFIER) which will distribute the incoming messages from the operating system to all windows and other objects receiving messages. Therefore, an heir of APPLICATION is usually the root class of a system using FenestraIt is not required to be the root class though, another class can be used to start the program and then will create an heir of APPLICATION when starting the user interface.. While it is usually the root class, it is not necessarily where the main part of the interface will be implemented, unlike this simple example.

Besides being an APPLICATION, VIEW is also a kind of WINDOW. A window can receive messages from the operating system, such as, among other cases, when the window is to be redrawn or when it is resized or when the mouse is clicked while the pointer is in its client area -- this is the area which is managed by the user program, as opposed to the border, menu bar, etc which are handled by the system. Object-oriented programming's emphasis on data works well with the event oriented model, as an object can have a state which will be altered by the flow of events (actually procedure calls).

With this library, procedures which correspond to events can be redefined for specialised behaviour using inheritance and are generally exported to the classification class PUBLIC_NONE (see Appendix (see section A.2) for details on this class). This simplifies and clarifies the documentation of classes which are designed to be used through inheritance. Most of these procedures names begin with the prefix `when_'. They are not usually deferred as the library provides default behaviour, so only the procedures who need specialisation will need to be actually redefined.

Incidentally, inheritance is not the only method available to reply to events, as the introduction of the menu system will show. The inheritance method is used when it makes sense to group several events which are all associated with a particular kind of objects. The processing of, say, keyboard entry, or mouse events, would not make much sense when isolated from the particular window they are associated with.

Let's now start to look at the actual class text. The class starts with its inheritance and creation clauses:

class VIEW

inherit
        APPLICATION
                redefine
                        when_created, 
                        when_mouse_clicked,
                        when_drawn,
                        when_resized
                end

creation
        make

Event processing

The class redefines four events from APPLICATION -- actually from WINDOW: the procedure when_started is called when the class is initialised and it is recommended to put all initialisation code here instead of having a new make procedure which would have to call the parent's make -- the make procedure in the creation clause above is the one provided by the library.

feature { NONE } -- Private attribute(s)

    model : TICTACTOE
    board_size : INTEGER
    painting : BOOLEAN

First, there are a few attributes: board_size and painting are state variables common to several of the drawing procedures, model is a reference to an instance of the class TICTACTOE which is associated with this VIEW.

 
feature { PUBLIC_NONE } -- Events

    when_created is
            -- Window initialised.
        local
            ico : ICON
        do
            -- Initialise model.
            !!model.make
            set_level(1)

            -- Set title.
            set_text("Tic-tac-toe")

            -- Set icon
            !!ico.make_resource("AppIcon")
            set_icon(ico)

            -- Menu bar.
            create_menu_bar
        end

    when_mouse_clicked(pt : POINT) is
            -- Mouse has been clicked.
        local
            col,row,cell : INTEGER
            sys : F_SYSTEM
        do
            if model.is_finished then
                -- Last game is finished, start a new one.
                new_game
            else
                -- Retrieve square.
                cell := board_size//3
                col := (pt.x - cell) // cell + 1
                row := (pt.y - cell//2) // cell + 1

                if model.is_square(col,row) and then model.is_empty(col,row) then
                    -- User clicked on a free square.
                    model.move(col,row)
                    if not model.is_finished then
                        model.computer_move
                    end
                    paint
                else
                    -- Bad place.
                    !!sys
                    sys.beep
                end
            end
        end

    when_drawn is
            -- Window drawn.
        local
            win_size : INTEGER
            r : RECTANGLE
        do
            -- Initialise window size.
            win_size := (client_size.width).min(client_size.height)
            board_size := win_size * 3 // 5

            !!r.make
            r.set_width(win_size)
            r.set_height(win_size)

            -- Resize. (`painting' avoids recursion)
            painting := true
            set_client_size(r)
            painting := false

            -- draw grid
            draw_board

            -- draw square
            draw_squares

            -- draw winner
            if model.is_finished then
                draw_winner
            end
        end

    when_resized is
            -- Window has been received.
        do
            if not painting then
                paint
            end
        end

The first redefined event, when_started, is called when the window is initialised, before being shown. It is used to setup the window, create the menu bar, and initialise the class attributes. The title is set as well as the application's icon, which is loaded from the resource section of the program executable file. The window will automatically be shown (made visible on the screen) after the creation event has been processed.

Mouse clicks are processed by when_mouse_clicked. The position of the mouse, a parameter of the event procedure, is retrieved and converted to a square on our board, and the appropriate command is sent to the model. After the model has been modified, the paint command starts a complete redraw of the client area. This could be optimised to redraw only the modified square.

The use of the F_SYSTEM class to beep if the user clicked outside the board is an example of a mixin class. A mixin class is a set of utility functions in a class with no parameters that can be used as a local variable, an expanded attribute, or through implementation inheritance, though the latter may not be recommended if careful usage of namespace is a concern.

All drawing is handled inside the when_drawn event. This event occurs when the window first appears, or when a part of the window becomes visible after it has been hidden and thus has to be redrawn, or after the window has been minimised or maximised. All the drawing in a normal window should be done during this event. It is possible if required to draw outside this event, but is not usually recommended: it is then necessary to synchronise the drawing done outside the event with the redrawing taking place during the event. When only a portion of the client area need be redrawn, the system prepares the device which will be used for graphical output by setting a clipping rectangle, so that output appears only in the required rectangle. This simple application depends on this feature -- the entire window is always logically redrawn -- it is possible to optimise this and draw only the visible rectangles.

The drawing procedure first sets the window size so that the client area is a square. Note the use of the painting variable to prevent infinite recursion. This is necessary because changing the window size triggers a when_resized event. This procedure is redefined to allow an update of the board size when the user changes the window size.

The remainder of the when_drawn procedure calls the function drawing the board itself and pieces, and, when the game is finished, the name of the winner.

Configuration

This section is used to keep the options associated with the view, and the corresponding access methods used by the options dialog box to change them.

feature -- Public attribute(s)

    firstmove_computer : BOOLEAN
            -- Is the computer going to move first?

    level : INTEGER
            -- Current level.

feature -- Options

    set_firstmove_computer is
            -- Computer moves first.
        do
            firstmove_computer := true
        end

    set_firstmove_user is
            -- User moves first.
        do
            firstmove_computer := false
        end
    
    set_level(lvl : INTEGER) is
            -- Set computer intelligence. 
        do
            level := lvl
            model.set_level(lvl)
        end

The menu bar

The following code is the instantiation of the menu bar and associated actions. The procedure create_menu_bar installs the menu bar, and binds the menu items with the actions, implemented by the procedures new_game, options, and quit.

feature { NONE } -- Menu

    create_menu_bar is
            -- Initialise the menu bar.
        local
            bar : MENU_BAR
            popup : POPUP_MENU
            item : MENU_ITEM
            command : GUI_COMMAND
        do
            !!bar.make(Current)

            !!popup.make(Current)
            popup.set_name("&Game")
            
            !CMD_NEW!command.make(Current)
            !!item.make("&New")
            item.set_command(command)
            popup.add_item(item)

            !CMD_OPTIONS!command.make(Current)
            !!item.make("&Options...")
            item.set_command(command)
            popup.add_item(item)
            
            popup.add_separator

            !CMD_QUIT!command.make(Current)
            !!item.make("&Quit")
            item.set_command(command)
            popup.add_item(item)
            
            bar.add_popup(popup)
            bar.attach
        end

feature { NONE } -- Dialog
    
    dialog : GAME_DIALOG

feature { GUI_COMMAND } -- Menu item(s)

    new_game is
            -- Start new game.
        do
            model.reset
            if firstmove_computer then
                model.computer_move
            end
            paint
        end

    quit is
            -- Exit application.
        do
            close
        end

    options is
            -- Set options.
        do
            -- Modeless dialog, menu may be used when dialog open.
            if dialog /= Void and then dialog.is_valid then
                dialog.activate
            else
                !!dialog.make(Current)
            end
        end

The menu bar is first created. Then, the popup menu is prepared, and menu items are associated with it. Once ready, the popup menu is added to the bar, which is in turn attached to its parent window, that is, displayed -- immediately if the window is visible or later otherwise. A menu bar or a menu popup is associated with an overlapped window, it is not possible for other type of windows to have a menu bar -- it would allow poor and confusing user interface designs. An application usually has one single menu bar. The popup menus may be used inside a bar, inside another popup, or independently (associated with the right mouse button for example).

Menu items and command classes

Each menu item can be associated with one event, which occurs when the menu item is chosen by the user. Although it is possible to inherit from MENU_ITEM and redefine the event procedure, it is usually preferred to use command classes. Command classes -- the Eiffel equivalent of other languages' function pointers, or functions as first class variables, or functors -- are small classes which redirect an event from a class to a method in another one. The basic class GUI_COMMAND has a single deferred feature, execute, which will be redefined by the descendant. If only one event is associated with a class implementing the reply, it can inherit directly from GUI_COMMAND, and use Current as the instance of the command. Usually, though, several events are associated with a class, and Eiffel does not permit direct repeated inheritance and polymorphism at the same time. It is therefore more convenient to create a small class which redirects the event.

The typical event handling class will look like CMD_NEW in this sample, which is associated with the procedure new_game of class VIEW.

-- Fenestra functor (automatically generated)

class CMD_NEW
inherit BOOMERANG_COMMAND[VIEW]
creation make
feature execute is do parent.new_game end
end

The class BOOMERANG_COMMAND is a convenience class providing a parent and a constructor to set it. The class above was generated with a small utility available in the distribution. It may be convenient to setup a macro or template in your favourite environment to produce those simple classes.

While the approach of having an independent class do such a small work may appear at first excessive, it is the normal Eiffel way to do this and it has some benefits: it scales well when implementing each command in its own class makes sense -- for an undo facility or a customisable user interface for instance. When these classes are used for redirection only, the amount of code is minimal, and these classes can be kept with their associated target class if the environment allows to put several Eiffel classes in a file. This approach has several benefits over redefining MENU_ITEM for a similar purpose -- though the latter is possible, as it has already been mentioned.

After it has been created, the command class is associated with the menu item thanks to the procedure set_command. The implementation of new_game and quit is rather straightforward. The procedure options is more interesting. It is used to create the modeless options dialog box. As the dialog is modeless -- that is the user can switch back and forth between the main window and the dialog -- this command can be called when the dialog is already open, this is the reason why a state variable with the current dialog. When a dialog is closed it becomes an invalid window, hence the test dialog.is_valid.

Drawing

The remaining features of the class VIEW are the actual drawing procedures, called during the when_drawn event. Drawing is done on a graphic device (base class GRAPHIC_DEVICE). The physical device associated with a device object can be anything from the screen to a printer page or a metafile. It can be seen as a rectangle of a definite size, where graphics commands can be issued.

A window has an associated device -- the attribute device -- which enables drawing in the client area of the window. A device must be ready to be used. The library prepares the device for the when_drawn event. When there is a need to draw or change the status of the device outside this event, it is then necessary to prepare, and then release the device.

Every device has a current status made of current settings, such as a font, a pen or a brush. A pen is used by the graphics subsystem to decide how to draw lines (colour, thickness, etc). The brush is used to decide how to draw filling patterns, such as the inside of a filled rectangle. A device also has an associated font, palette, drawing mode (how the new pixels may be combined with the existing), and mapping mode (units to use and where to place the origin of the coordinates system). The status persists for a given device even when the device is released Unlike what happens with GDI devices programmed at the API level., for instance, between two occurences of the when_drawn event.

It should be noted that the window background is painted by the system using the system's default background window colour -- for overlapping windows, not for child windows (see section (see section 3.3.2) on background painting).

feature { NONE } -- Drawing

    draw_board is
            -- Draw row.
        local
            clr : RGB_COLOR
            spen : SIMPLE_PEN
            cell,x,y,i : INTEGER
            dln : DRAWABLE_LINE
            pt : POINT
        do
            !!pt.make
            !!dln.make

            cell := board_size//3
            
            -- set color/pen
            !!clr.black
            
            !!spen.make
            spen.set_color(clr)
            spen.set_width(2)
            
            device.set_pen(spen)
            
            -- draw board
            from i := 0 variant 4 - i until i > 3 loop
                -- draw row
                y := (cell//2) + (i*cell)

                pt.set_xy(cell,y)
                dln.set_start(pt)
                
                pt.set_xy(4*cell,y)
                dln.set_final(pt)

                device.draw(dln)
                
                -- draw colum
                x := (i+1) * cell

                pt.set_xy(x,cell//2)
                dln.set_start(pt)
                
                pt.set_xy(x,cell//2 + 3*cell)
                dln.set_final(pt)

                device.draw(dln)

                -- forth
                i := i + 1
            end
        end

The method draw_board draws a black grid on the device. As it can be seen in the code above, a black pen -- made from a black colour object -- is first set; then, lines are drawn on the window background.

Concerning the colour choice, various devices (be it the screen or a printer) have diverse colour capabilities, and it may be necessary to manage a colour palette or check the device colour depth depending on the application. The operating systems nevertheless provides a set of about twenty standard colours that can be used for simple application and should render acceptably on most devices. These colours can be selected thanks to the standard (creation) procedures of classes like RGB_COLOR. More information on colour management is provided in chapter (see section 5).

Every graphics primitive has an associated class inheriting from DRAWABLE. Each class associated with a basic shape or object includes all the parameters associated with the primitive. Devices have a draw procedure which takes a DRAWABLE object to actually display the figure.

The classes POINT and RECTANGLE are general classes used to hold a set of coordinates -- they are not descendants from DRAWABLE -- and are used throughout the library whenever coordinates are required.

The grid can now be filled:

    draw_squares is
            -- Draw all squares.
        local
            i,j : INTEGER
        do
            from i := 1 variant 4 - i until i > 3 loop
                from j := 1 variant 4 - j until j > 3 loop
                    if not model.is_empty(i,j) then
                        draw_square(i,j)
                    end
                    j := j + 1
                end
                i := i + 1
            end
        end

    draw_square(col,row: INTEGER) is
            -- Draw one square.
        require
            ok: model.is_square(col,row)
        local
            pt : POINT
            rect : RECTANGLE
            cell,subcell : INTEGER
            dcircle : DRAWABLE_ELLIPSE
            dline : DRAWABLE_LINE
            clr : RGB_COLOR
            gpen : GEOMETRIC_PEN
            gr : GRAPHICS_ROUTINES
        do
            cell := board_size // 3
            subcell := cell * 7 // 10

            -- cell centre
            !!pt.make_coord(col*cell + cell//2, row*cell)

            !!rect.make
            rect.set_width(subcell)
            rect.set_height(subcell)
            rect.translate_centre(pt)

            if model.is_user(col,row) then
                -- set color/brush/pen
                !!gr
                !!clr.normal_red
                device.set_brush(gr.color_brush(clr))
                device.set_pen(gr.color_pen(clr))

                -- draw circle
                !!dcircle.make
                dcircle.set_bounding_rectangle(rect)
                device.draw(dcircle)

            elseif model.is_computer(col,row) then
                -- set color/brush/pen
                !!clr.normal_blue
                !!gpen.make
                gpen.set_color(clr)
                gpen.set_end_round
                gpen.set_width(cell//8)
                device.set_pen(gpen)

                -- draw cross
                !!dline.make
                dline.set_start(rect.lower)
                dline.set_final(rect.upper)
                device.draw(dline)

                pt.set_xy(rect.lower.x,rect.upper.y)
                dline.set_start(pt)
                pt.set_xy(rect.upper.x,rect.lower.y)
                dline.set_final(pt)
                device.draw(dline)
            end
        end

We know draw a circle -- a kind of ellipse -- and a cross depending on which player owns the square. The appropriate red and blues pens are used. The circle is filled and so an extra brush is required to fill it correctly.

The class GRAPHICS_ROUTINES is a mixin class including some functions useful for common patterns, such as setting both a device's pen and brush with a pen and brush built from a simple solid colour. It is useful for simple shapes, for example a rectangle will be drawn using the current pen for its border while using the current brush for actually filling it.

Finally, the name of the winner is drawn if the game is finished:

    draw_winner is
            -- Draw winner banner.
        require
            ok: model.is_finished
        local
            txt : STRING
            rect : RECTANGLE
            dtext : DRAWABLE_TEXT
            pt : POINT
            clr : RGB_COLOR
        do
            !!clr.black

            if model.is_user_winner then
                txt := "You win!"
            elseif model.is_computer_winner then
                txt := "Computer wins!"
            else
                txt := "Nobody wins."
            end

            !!dtext.make
            dtext.set_color(clr)
            dtext.set_text(txt)

            !!pt.make_coord(5*board_size//6,3*board_size//2)
            rect := device.text_extent(dtext)
            rect.translate_centre(pt)

            dtext.set_position(rect.lower)

            device.draw(dtext)
        end

end -- class

It is important to note that the text object (DRAWABLE_TEXT) does not use the device's pen to draw the letters, but has a simple colour associated with it. The text_extent procedure in GRAPHIC_DEVICE allows to retrieve the size the text would occupy if displayed using the current settings, so that formatting can be done. A possible improvement would be to customise the font and height instead of using the default system font.

The main window class has now been completely reviewed.

1.4 The options dialog

The user should be able to set the game options: who starts each game, the user or the computer, and how clever the computer is going to be. The options menu command displays the following simple dialog:

screenshot of dialog

Dialogs

Dialog boxes using Fenestra are dynamic. They are actually normal windows in which control child windows (controls thereafter) are dynamically created. Dialogs being normal windows, they are naturally modeless, that is they can be used concurrently with their parent windows. A message is usually sent to the target window, if there is one, to apply the settings when the user clicks on the `OK' button or similar. Modal dialogs also exist, but are simply modeless dialog which disable the parent window.

As the dialogs are dynamic, there is no need for a resource file. Dialogs are laid out by creating controls during initialisation -- usually during the when_started dialog box event. Controls can be positioned using absolute coordinates or, more conveniently, using the positioning facility. Thus controls can be adequately positioned relatively to each other.

A more complete discussion of the dialog and controls system can be found later in chapter (see section 4).

class GAME_DIALOG

inherit
    MODELESS_DIALOG
        redefine
            when_started, when_applied
        end

creation
    make

feature { NONE } -- Controls
    
    computer : HEAD_RADIO_BUTTON
    user : RADIO_BUTTON
    list : LISTBOX[GAME_ITEM]

feature { NONE } -- Client
    
    client : VIEW

The dialog class inherits from MODELESS_DIALOG which introduces the events when_started and when_applied among others. This class implements the behaviour and aspect of a dialog box, including the standard keyboard interface (shortcut keys, <TAB> to move to the next control, etc).

The controls whose status has to be tracked are class attributes. client is a reference to the associated instance of the application's class VIEW.

Creating and initialising the controls

feature { PUBLIC_NONE } -- Start
    
    when_started is
            -- Initialise dialog.
        local
            group : STD_GROUP_BOX
            label : STD_LABEL
            ok,cancel : STD_PUSH_BUTTON
            listitem : GAME_ITEM
        do
            -- Set title
            set_text("Game options")

            -- Initialise controls
            !!group.make(Current)
            group.set_label("First move")

            !STD_HEAD_RADIO!computer.make(Current)
            computer.set_label("Com&puter")
            computer.indep_set_width(50)
            group.arrange_first(computer)

            !STD_RADIO_BUTTON!user.make(Current)
            user.set_label("&User")
            user.set_leader(computer)
            user.place_just_under(computer)
            user.same_width(computer)
            group.arrange(user)

            !!label.make(Current)
            label.set_label("&Level:")
            label.indep_set_width(25)
            label.place_under(group)

            !STD_DROPDOWN_LISTBOX[GAME_ITEM]!list.make_unsorted(Current)
            list.place_right(label)

            group.align_right(list)

            !OK_BUTTON!ok.make(Current)
            ok.place_right(group)
            ok.set_label("&OK")

            !CANCEL_BUTTON!cancel.make(Current)
            cancel.set_label("&Cancel")
            cancel.place_under(ok)

            -- Resize dialog to enclose all controls
            arrange

            -- Initialise controls
            client ?= parent
            check good_client: client /= Void end

            -- Initialise radio buttons
            if client.firstmove_computer then
                computer.check_mark
            else
                user.check_mark
            end

            -- Initialise listbox
            !!listitem.make(1,"Easy")
            add_item(listitem)
            !!listitem.make(2,"Normal")
            add_item(listitem)
            !!listitem.make(3,"Difficult")
            add_item(listitem)
            !!listitem.make(4,"Expert")
            add_item(listitem)
        end

feature { NONE }

    add_item(litem : GAME_ITEM) is
        require
            ok: litem /= Void
        do
            list.add_item(litem)
            if litem.level = client.level then
                list.select_item(litem)
            end
        end

After setting the title of the dialog, each control is created. The default position of a control is in the upper left corner of the dialog, so the first control need not be positioned. The other controls are positioned relative to each other, including the radio buttons inside their group box. The first radio button is positioned at the first position of the group box which is resized to enclose the last radio button. A final call to arrange ensures that the dialog nicely encloses its controls.

Two types of radio button are used, the `head' radio button allows the buttons to be managed automatically; that is, only one button can be selected in the group of buttons associated with a head button. This works for both types of selection: those made interactively by the user, and those made on a programmed call to procedures such as computer.check_mark, which unchecks user.

The `OK' and `Cancel' buttons are implemented by the classes OK_BUTTON and CANCEL_BUTTON. These classes are simple push buttons which redirect their main event to, respectively, the dialog box features apply and finish.

The dialog is then initialised with the previous configuration. We first use an assignment attempt to retrieve our client from the parent window reference. It seems the most straightforward way to do it. An alternative way to do it is to redefine parent as a VIEW -- this could be done easily because MODELESS_DIALOG's constructor parent parameter is anchored to parent.

After initialising the radio buttons, the items in the listbox are added. A list box is made of items inheriting from LISTBOX_ITEM which has a deferred feature representing the name of the item, to be displayed in the box. An effective class, LISTBOX_STRING, is provided for simple cases when a simple string is used as an item. In this case, a simple GAME_ITEM class was created to associate an integer, the level, with this string. The method add_item checks the items for the default choice to be correctly presented to the user.

Processing the result

When the user clicks on the `OK' button, apply is called and then the when_applied event in the dialog class occurs. The processing is a fairly straightforward transfer of the dialog state to the corresponding VIEW.

feature { PUBLIC_NONE } -- OK

    when_applied is
            -- Apply settings.
        do
            check selected: list.has_selection end
            client.set_level(list.selected_item.level)

            if computer.is_checked then
                client.set_firstmove_computer
            else
                client.set_firstmove_user
            end
        end

end -- class