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:

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:

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