4.4 Modal dialogs: prompting for answers

A modal dialog is a dialog that is displayed and blocks the application until the user has finished answering the questions posed in the dialog. Modal dialogs are often used to prompt for values needed to create a new entity in the application or for changing settings.

Modal windows are implemented using the methods `frame<-confirm' and `frame->return'. `Frame<-confirm' invokes `frame->open' if the frame is not visible and then starts reading events and processing them. `Frame->return: value' causes `frame<-confirm' to return with the value passed as argument to ->return. The following code is a very simple example opening a dialog and waiting for the user to enter a name and press RETURN or the Ok button.

ask_name(Name) :-
        new(D, dialog('Prompting for name')),
        send(D, append,
             new(TI, text_item(name, ''))),
        send(D, append,
             button(ok, message(D, return,
        send(D, append,
             button(cancel, message(D, return, @nil))),
        send(D, default_button, ok), % Ok: default button
        get(D, confirm, Answer),     % This blocks!
        send(D, destroy),
        Answer \== @nil,             % canceled
        Name = Answer.

?- ask_name(X).

X = 'Bob Worker'

See also section 10.5 for a discussion on how frames may be related, as well as alternatives for keeping control in Prolog.

4.4.1 Example: a simple editor for multiple fonts

The following example allows the user to select text in an editor and change its appearance. The application is shown in figure 10.

Figure 10 : Very simple WYSIWYG editor

A typical XPCE/Prolog module header. Always make sure to load module library(pce) explicitly if you want to write modules portable to the various Prolog dialects supported by XPCE.
:- module(wysiwyg,
          [ wysiwyg/1           % +File
:- use_module(library(pce)).
:- use_module(library(pce_style_item)).

Create the main window, consisting of a frame holding a dialog window with a button for defining new styles and a menu for setting the style of the selection. Both dialog items use call-back to @prolog.

wysiwyg(File) :-
        new(Fr, frame(File)),
        send(Fr, append, new(D, dialog)),
        send(new(V, view), below, D),
        send(V, font, normal),
        send(D, append,
                    message(@prolog, define_style, Fr))),
        send(D, append,
             menu(style, toggle,
                  and(message(@prolog, set_style, Fr, @arg1),
                      message(V, selection, 0, 0),
                      message(@receiver, clear_selection))),
        append_style(Fr, bold,   style(font := bold)),
        append_style(Fr, italic, style(font := italic)),
        send(V, load, File),
        send(Fr, open).

Set the style for the current selection. Simply pick the selection start and end and make a fragment using the selection parameters and the style-name.

set_style(Fr, Style) :-
        get(Fr, member, view, V),
        get(V, selection, point(Start, End)),
        (   Start == End
        ->  send(Fr, report, warning, 'No selection')
        ;   get(V, text_buffer, TB),
            new(_, fragment(TB, Start, End-Start, Style))

Define a new style and add it to the menu and the view.

define_style(Fr) :-
        ask_style(Fr, Name, Style),
        append_style(Fr, Name, Style).

append_style(Fr, Name, Style) :-
        get(Fr, member, dialog, D),
        get(D, member, style, Menu),
        send(Menu, append, Name),
        send(Menu, active, @on),
        get(Fr, member, view, View),
        send(View, style, Name, Style).

Prompt for the style-name and style-object. Class style_item is defined in the library(pce_style_item). `frame ->transient_for' tells the window manager the dialog is a supporting frame for the main application. `frame <-confirm_centered' opens the frame centered around the given location and starts processing events until `frame ->return' is activated.

ask_style(Fr, Name, Style) :-
        new(D, dialog('Define Style')),
        send(D, append,
             new(N, text_item(name, ''))),
        send(D, append,
             new(S, style_item(style))),
        send(D, append,
             button(ok, message(D, return, ok))),
        send(D, append,
             button(cancel, message(D, return, cancel))),
        send(D, default_button, ok),
        send(D, transient_for, Fr),
        get(D, confirm_centered, Fr?area?center, Answer),
        (   Answer == ok
        ->  get(N, selection, Name),
            (   Name == ''
            ->  send(D, report, error,
                     'Please enter a name'),
            ;   !,
                get(S, selection, Style),
                send(Style, lock_object, @on),
                send(D, destroy)
        ;   !,
            send(D, destroy),