Next Previous Contents

3. The DIALOG class

3.1 Alphabetical function listing

Classe DIALOG, vue publique

3.2 Creating a dialog

Here are the types of dialog you can create

DIALOG::DIALOG()

        PUBLIC DIALOG::DIALOG(
                void)
        

#Specification: DIALOG_LISTE / principles ([dialist.cc,5])

A DIALOG_LISTE is used to present long list such as user accounts. The list generally have a header and each line is organised into cells, generally showing some records. Long list are not that fun to deal with for users. Some mecanism should be provided to help the user browse through it. Unfortunatly not all user interface (HTML, GUI, ncurses) may support the same abilities. For example, in html, transfering long list is really annoying, especially on slow links. This may very well apply to the GUI since it is client server based and the client (written in java) may be remote, over a slow link also. The idea of the DIALOG_LISTE is to give the application a single simple interface and let this object decide what trick is best for which user interface. Currently, the object implement the same trick for all user interface. If there are more than 60 entries (configurable) in the list, a POPUP is created asking for a prefix. This prefix is then used to pick only the item of the large list which match this prefix.

DIALOG_LISTE::DIALOG_LISTE()

        PUBLIC DIALOG_LISTE::DIALOG_LISTE(
                void)
        

3.3 Editing a dialog, a menu or a list

DIALOG::edit()

Multiple field dialog. All field are shown one under each others

        PUBLIC MENU_STATUS DIALOG::edit(
                const char *_title,     // Main title
                const char *_intro,     // Mini help describing the purpose
                // of the dialog
                HELP_FILE&helpfile, // Help text in a file or NULL
                int &nof,   // Start editing on which field
                // Will contain the current field.
                int but_options)        // MENUBUT_xxxxx
        

DIALOG::edit()

Multiple field dialog. All field are shown one under each others

        PUBLIC MENU_STATUS DIALOG::edit(
                const char *_title,     // Main title
                const char *intro,      // Mini help describing the purpose
                // of the dialog
                HELP_FILE&helpfile, // Help text in a file or NULL
                int &nof)   // Start editing on which field
        

DIALOG::edit()

Multiple field dialog. All field are shown one under each others

        PUBLIC MENU_STATUS DIALOG::edit(
                const char *_title,     // Main title
                const char *intro,      // Mini help describing the purpose
                // of the dialog
                HELP_FILE&helpfile) // Help text in a file or NULL
        

DIALOG::editmenu()

Edit a menu

        PUBLIC VIRTUAL MENU_STATUS DIALOG::editmenu(
                const char *title,
                const char *prompt,
                HELP_FILE&helpfile,
                int &sel,   // Will hold the selection or -1 if escape
                // It must already contains the initial selection
                int options)    // MENUBUT_ADD | MENUBUT_INS | MENUBUT_DEL | MENUBUT_SAVE
        

#Specbeg: definitions / MENU_STATUS ([dialog_def.h,97])

        // These are the values returned by DIALOG::edit___() functions.
        // Note that beside MENU_ESCAPE, a call to edit can only return a code
        // associated with a button you have registered.
        enum MENU_STATUS {
            MENU_NULL,
            MENU_ESCAPE,
            MENU_CANCEL,
            MENU_QUIT,
            MENU_SAVE,
            MENU_OK,
            MENU_ACCEPT,
            MENU_DEL,
            MENU_INS,
            MENU_ADD,
            MENU_YES,
            MENU_NO,
            MENU_EDIT,
            MENU_RESET,
            MENU_INTERNAL_TIMEOUT,      // Used to signal a one second delay
                                        // to the dialog editor
            MENU_HELP,              // Help button, used internally
            MENU_MORE,
            MENU_USR1,
            MENU_USR2,
            MENU_USR3,
            MENU_USR4,
            MENU_USR5,
            MENU_USR6,
            MENU_USR7,
        
            MENU_CUT,
            MENU_MESSAGE,
            MENU_DUMP,
        };

3.4 Defining edit fields

DIALOG::newf_chk()

Add a check box field to the dialog.

        PUBLIC FIELD_CHECK *DIALOG::newf_chk(
                const char *prompt,
                char &var,
                const char *title)
        

#Specification: dialog / checkbox ([checkbox.cc,105])

A checkbox is used to toggle an option. It edit a variable from 0 to 1. The checkbox is presented like this

        prompt [ ] some explanation
        prompt [X] some explanation

DIALOG::newf_chkm()

Add a multi value horizontal check box field to the dialog.

        PUBLIC FIELD_CHECK_MULTI *DIALOG::newf_chkm(
                const char *prompt,
                char &var,
                const char *title[])
        

#Specification: dialog / checkbox multi ([checkbox.cc,212])

A checkbox multi is used to toggle an between more than one option but not too many. It edit a variable from 0,1,number of option-1. It is presented like this.

        prompt (o) opt1 ( ) opt2 ( ) opt3
        prompt ( ) opt1 ( ) opt2 ( ) opt3

DIALOG::newf_chkm_col()

Add a multi value horizontal check box field to the dialog. The checkbox follows the heading layout

        PUBLIC FIELD_CHECK_MULTI *DIALOG::newf_chkm_col(
                const char *prompt,
                char &var,
                const char *title[])

DIALOG::newf_chkm_hexnum()

        PUBLIC FIELD_CHECK_MULTI *DIALOG::newf_chkm_hexnum(
                const char *prompt,
                int &var,
                const int vals[],       // Value corresponding to the options
                const char *options[])
        

DIALOG::newf_chkm_hexnum()

        PUBLIC FIELD_CHECK_MULTI *DIALOG::newf_chkm_hexnum(
                const char *prompt,
                int &var,
                const int defvar,
                const int vals[],       // Value corresponding to the options
                const char *options[])

DIALOG::newf_chkm_num()

        PUBLIC FIELD_CHECK_MULTI *DIALOG::newf_chkm_num(
                const char *prompt,
                int &var,
                const int vals[],       // Value corresponding to the options
                const char *options[])
        

DIALOG::newf_chkm_num()

        PUBLIC FIELD_CHECK_MULTI *DIALOG::newf_chkm_num(
                const char *prompt,
                int &var,
                const int defvar,
                const int vals[],       // Value corresponding to the options
                const char *options[])
        

#Specbeg: dialog / sample code / using newf_chkm_num ([samples.cc,509])

        static void sample_chkmvals()
        {
            DIALOG dia;
            // Numerical input
            int val1 = 3000, val2 = 10,val3=256;
            static int vals[]={10,20,30};
            static const char *opts[]={"default","opt20","opt30",NULL};
            dia.newf_chkm_num ("options 1",val1,3600,vals,opts);
            dia.newf_chkm_num ("options 2",val2,10,vals,opts);
            dia.newf_chkm_hexnum ("options 3",val3,10,vals,opts);
            // String input. This combines some options and an extra string
            // value.
            SSTRING str("Default string");
            int val = 1;
            int str_vals[] = {0,1,2};
            const char *titles[] = {"Option 1","Option 2",NULL};
            dia.newf_chkm_str("Prompt",val,str,str_vals,titles);
            int nof = 0;
            while (1){
                MENU_STATUS code = dia.edit ("chkmvals","",help_nil,nof);
                if (code == MENU_ESCAPE || code == MENU_CANCEL){
                    break;
                }else{
                    xconf_notice ("The numeric values are %d %d %x",val1,val2,val3);
                    if (val == 2){
                        xconf_notice("A string was entered :'%s'",str.get());
                    }else{
                        xconf_notice("A numerical input was entered : %d",val);
                    }
                    break;
                }
            }
        }

DIALOG::newf_dbl()

Add a floating point field to the dialog.

        PUBLIC FIELD *DIALOG::newf_dbl(
                const char *prompt,
                double &val,
                int nbdecimals)
        

DIALOG::newf_gauge()

Add a horizontal slider field (integer input) to the dialog.

        PUBLIC FIELD *DIALOG::newf_gauge(
                const char *prompt,
                int &val,
                int range)

DIALOG::newf_head()

        PUBLIC VIRTUAL void DIALOG::newf_head(
                const char *prompt,
                const char *msg)
        

DIALOG::newf_hexnum()

Add an hexadecimal field to the dialog.

        PUBLIC FIELD *DIALOG::newf_hexnum(
                const char *prompt,
                int &val)
        

DIALOG::newf_info()

Add an information field (write protect)

        PUBLIC void DIALOG::newf_info(
                const char *prompt,
                const char *str)
        

DIALOG::newf_list()

Add a selection field to the dialog. The object will be destroyed by the dialog itself. The object is returned so the caller may add options in the pick list. Unlike a combo, the user may not enter anything in it. The value must be in the list.

        PUBLIC FIELD_LIST *DIALOG::newf_list(
                const char *prompt,
                SSTRING&str)
        

DIALOG::newf_octnum()

Add an octal field to the dialog.

        PUBLIC FIELD *DIALOG::newf_octnum(
                const char *prompt,
                int &val)
        

DIALOG::newf_num()

Add a numerical field to the dialog.

        PUBLIC FIELD *DIALOG::newf_num(
                const char *prompt,
                int &val)
        

DIALOG::newf_pass()

Add a password field to the dialog.

        PUBLIC FIELD_PASSWORD *DIALOG::newf_pass(
                const char *prompt,
                SSTRING&str)
        

DIALOG::newf_radio()

Add a check box field to the dialog.

        PUBLIC FIELD_RADIO *DIALOG::newf_radio(
                const char *prompt,
                char &var,
                char instance_val,
                const char *title)

#Specification: dialog / radio field ([radio.cc,11])

A radio field is like a check box, but many fields are semanticly related. When you select one, the others are unselected. radio field are used to present a set of mutually exclusive option. It is possible to have a all radio fields unselected. Each radio field "edit" the save variable. Each one, when selected will set the variable to its own instance value. When all fields are unselected, the value 0xff is set. Here is an example

        The checkbox is presented like this
        Enable PPP ( ) at boot time
                   (o) on demand
                   ( ) never

#Specification: dialog / FIELD_MSG / strategy ([radio.cc,140])

The FIELD_MSG object was created to achieve the radio button widget. When a radio button is selected all other radio button operating on the same variable should unset themselves. To achive this, the radio button dokey() function loaded the address of the edition variable into the msg and loaded the instance value into the msg.int_val. It set msg.is_loaded to 1. This tells the main dialog edit loop to call processmsg for each field, passing to it the msg structure. Each field will compare msg.key to whatever it decide uniquely identify itself. If there is a match, it will process msg.int_val and decide whatever should be done. All radio buttons will redraw themselves if needed. This mecanism achieve a form of simple broadcast to all fields of a dialog.

DIALOG::newf_slider()

Add a horizontal slider field (integer input) to the dialog.

        PUBLIC FIELD *DIALOG::newf_slider(
                const char *prompt,
                int &val,
                int minval,
                int maxval)
        

DIALOG::newf_str()

Add a string field to the dialog.

        PUBLIC FIELD_STRING *DIALOG::newf_str(
                const char *prompt,
                char *str,
                int maxsiz)
        

DIALOG::newf_str()

Add a SSTRING field to the dialog.

        PUBLIC FIELD_SSTRING *DIALOG::newf_str(
                const char *prompt,
                SSTRING&str)
        

DIALOG::newf_str()

Add a SSTRING field to the dialog.

        PUBLIC FIELD_SSTRING *DIALOG::newf_str(
                const char *prompt,
                SSTRING&str,
                int width)      // Suggested field width (in character)
        

DIALOG::newf_textarea()

        PUBLIC void DIALOG::newf_textarea(
                const char *prompt,
                SSTRING&text,
                int width,      // How many visible character horizontally
                // (a hint)
                int height)     // How many lines to show

#Specbeg: dialog / sample code / using textarea ([samples.cc,546])

        static void sample_textarea()
        {
            DIALOG dia;
            SSTRING txt;
            txt.append ("This is the first line.\n");
            txt.append ("This is the second line.\n");
            txt.append ("And the third one.\n");
            dia.newf_textarea ("Some text",txt,60,10);
            int nof = 0;
            while (1){
                MENU_STATUS code = dia.edit ("A text","",help_nil,nof);
                if (code == MENU_ESCAPE || code == MENU_CANCEL){
                    break;
                }else{
                    xconf_notice ("The value is:\n%s",txt.get());
                    break;
                }
            }
        }

3.5 fields with help list and dialog

DIALOG::newf_combo()

Add a combo field to the dialog. The object will be destroyed by the dialog itself. The object is returned so the caller may add options in the pick list.

        PUBLIC FIELD_COMBO *DIALOG::newf_combo(
                const char *prompt,
                SSTRING&str)
        

DIALOG::newf_combo_many()

Add a combo field to the dialog. The object will be destroyed by the dialog itself. The object is returned so the caller may add options in the pick list.

        PUBLIC FIELD_COMBO_MANY *DIALOG::newf_combo_many(
                const char *prompt,
                SSTRING&str)

DIALOG::newf_enum()

Create a FIELD_ENUM field

        PUBLIC FIELD_ENUM *DIALOG::newf_enum(
                const char *prompt,
                int &sel)

#Specbeg: dialog / sample code / help list ([samples.cc,350])

        /*
            Linuxconf supports a special case of help list where the available
            values are presented with a small description. This is used
            for FIELD_COMBO, FIELD_LIST and FIELD_ENUM. FIELD_COMBO comes with
            a help list, but the user may type anything he wants. FIELD_LIST
            restricts the user to the available choice. The edit variable is
            a SSTRING. FIELD_ENUM works like FIELD_LIST, but the variable is
            an integer. The value correspond to the index in the help list.
        
            FIELD_LIST also has support for translation. The value presented
            in the help list may be translated (different  language) and the
            input buffer is automatically switched back and forth between the
            shown (translated) value and the effective one.
        
            Here is a simple dialog showing all cases
        */
        
        static  void sample_helplist()
        {
            DIALOG dia;
            SSTRING path;
            FIELD_COMBO *comb = dia.newf_combo("Enter a path",path);
            comb->addopt ("","Nothing");
            comb->addopt ("/tmp","Temporary files directory");
            comb->addopt ("/var","System data directory");
            comb->addopt ("/etc","Configuration directory");
        
            // Use may pick the paper type in french, but the outcome
            // is one of the value letter or ledger. The variable
            // paper originally is set to letter, but the user will see that
            // it is set to Lettre.
            SSTRING paper ("letter");
            FIELD_LIST *list = dia.newf_list("Paper size",paper);
            // In general, the first argument is a fixed string, while
            // the second and third are translatable string (using MSG_U() macro)
            list->addopt ("","Default","Valeur par default");
            list->addopt ("letter","Lettre","Papier 8.5 x 11 standard");
            list->addopt ("ledger","Legal","Papier légal 8.5 x 14");
        
        
            SSTRING feature("-1");
            list = dia.newf_list("Feature:", feature);
            list->addopt("-1","Not avail.","");
            list->addopt("", "Use default definition", "");
            list->addopt("1", "Enabled", "");
            list->addopt("0", "Disabled", "");
        
            // here, we do not care about the strings. We want to know
            // the index of the selected string.
            int sel = 2;
            FIELD_ENUM *enm = dia.newf_enum ("Printer type",sel);
            enm->addopt ("HP4");
            enm->addopt ("HP5");
            enm->addopt ("HP6");
            enm->addopt ("HP7");
            int nof = 0;
            if (dia.edit ("Selections","",help_nil,nof)==MENU_ACCEPT){
                xconf_notice ("You have selected\n"
                    "Directory path: %s\n"
                    "Paper type    : %s\n"
                    "Feature       : %s\n"
                    "Printer number: %d\n"
                    ,path.get(),paper.get(),feature.get(),sel);
            }
        }

#Specbeg: dialog / sample code / help dialog ([samples.cc,419])

        /*
            You can associate a dialog with a field. Instead of a help list
            triggered by a button at the right end of the field, you trigger
            some code provided by the application. One possible usage is
            a file browser used to help enter a file name or path.
        
            The strategy provides maximum flexibility for the helper dialog.
            It can even cancel the calling dialog.
        
            The strategy is built on top of the inter dialog messaging. Since
            you generally want to signal your own dialog, you are using
            PRIVATE_MESSAGE objects.
        */
        #
        /*
            This is not a real helper dialog to select a path
            but it shows you can connect any dialog you want, any processing
            you want
        */
        static void sample_helper (const char *title, SSTRING &path)
        {
            DIALOG dia;
            char sel = 0;
            static const char *tb[]={
                "/tmp","/var","/root"
            };
            for (int i=0; i<3; i++){
                dia.newf_radio ("",sel,i,tb[i]);
            }
            int nof = 0;
            if (dia.edit (title,"",help_nil,nof)==MENU_ACCEPT){
                path.setfrom (tb[sel]);
            }
        }
        
        static  void sample_helpdialog()
        {
            DIALOG dia;
            SSTRING path1,path2;
            PRIVATE_MESSAGE msg1,msg2;
            dia.newf_str("Enter a path",path1);
            dia.set_helpdia (msg1);
            dia.newf_str("Enter another path",path2);
            dia.set_helpdia (msg2);
        
            int nof = 0;
            while (1){
                MENU_STATUS code = dia.edit ("Paths","",help_nil,nof);
                if (code == MENU_CANCEL || code == MENU_ESCAPE){
                    break;
                }else if (code == MENU_ACCEPT){
                    xconf_notice ("You have selected\n"
                        "Directory path1: %s\n"
                        "Directory path2: %s\n",path1.get(),path2.get());
                    break;
                }else if (code == MENU_MESSAGE){
                    // It is possible to use the same message for several field
                    // if you want, using "nof" to tell which dialog to trigger
                    if (dialog_testmessage (msg1)){
                        sample_helper ("path1",path1);
                        dia.reload (nof);
                    }else if (dialog_testmessage (msg2)){
                        sample_helper ("path2",path2);
                        dia.reload (nof);
                    }
                }
            }
        }

3.6 Defining menu and list entries

DIALOG::new_menuinfo()

Add a new info line in a menu (non selectable)

        PUBLIC void DIALOG::new_menuinfo(
                const char *prompt1,
                const char *prompt2)
        

DIALOG::new_menuitem()

Add a new menu item to a dialog You will have to use the DIALOG::editmenu instead of just DIALOG::edit() or your dialog will lack some buttons and won't be layout properly.

        PUBLIC void DIALOG::new_menuitem(
                const char *prompt1,
                const char *prompt2)
        

DIALOG::new_menuitem()

        PUBLIC void DIALOG::new_menuitem(
                const char *icon,
                const char *prompt1,
                const char *prompt2)
        

DIALOG::new_menuitem()

Add a new menu item to a dialog You will have to use the DIALOG::editmenu instead of just DIALOG::edit() or your dialog will lack some buttons and won't be layout properly.

        PUBLIC void DIALOG::new_menuitem(
                const char *prompt1,
                const SSTRING&prompt2)
        

DIALOG::new_menuitem()

        PUBLIC void DIALOG::new_menuitem(
                const SSTRING&prompt1,
                const SSTRING&prompt2)
        

DIALOG::new_menuitem_parse()

        PRIVATE void DIALOG::new_menuitem_parse(
                const char *item1,
                const char *item2)
        

DIALOG::new_menuitems()

        PUBLIC void DIALOG::new_menuitems(
                const char *items[],
                int item_no)
        

DIALOG::new_menuitems()

Add a bunch of menu option to the menu

        PUBLIC void DIALOG::new_menuitems(
                const char *opt[])
        

DIALOG::new_menuline()

Add a new menu item to a dialog You will have to use the DIALOG::editmenu instead of just DIALOG::edit() or your dialog will lack some buttons and won't be layout properly.

        PUBLIC void DIALOG::new_menuline(
                const char *icon,       // Icon file name (without extension) or NULL
                const char *prompt1,
                const char *prompt2,
                bool may_select)
        

DIALOG::set_menuitem()

Redefine one line of a menu or list

        PUBLIC void DIALOG::set_menuitem(
                int no,
                const char *prompt1,
                const char *prompt2)
        

#Specification: dialog / menus / splitter ([menubox.cc,428])

If the first member of a menu entry is a single '-', this entry is drawn as a full splitter line The second entry will be used as the splitter title.

DIALOG_RECORDS::newf_head()

        PUBLIC void DIALOG_RECORDS::newf_head(
                const char *prompt,
                const char *msg)

DIALOG_RECORDS::setkeyformat()

Record the way the html key (href) are built. The default is to use only the tag (the first argument of the DIALOG::new_menuitem() function). With setkeyformat, you can force usage of the complete line (the two argument). For some dialog, this is the only way to create a unique key. Check out the comment in menubox.cc to understand the implication.

        PUBLIC void DIALOG_RECORDS::setkeyformat(
                HTML_KEY_TYPE key_type)
        

#Specification: DIALOG / FIELD_CLIST / introduction ([clist.cc,1])

clist == Column list: A list of record presented in columns. The FIELD_CLIST type was created 6 years after the rest of this project. The various objects such as DIALOG_RECORDS had for a long time a simple way to deal with record list. They were simple to use. Unfortunatly, they could not scale to handle more complex dialogs. For example, dialog with several lists, field and buttons. So we created the FIELD_CLIST, which is a multi-line field operating under control of a normal DIALOG object. At this point, the DIALOG_RECORDS are still built on the same FIELD_MENU objects. Note that the GUI front-end had a Clist object for a long time. So both FIELD_CLIST and DIALOG_RECORDS maps to the same GUI component.

3.7 Buttons

#Specification: button quit / dismiss or quit / hack ([buttons.cc,145])

The first dialog requesting the QUIT button will get it. It is assumed to be the main menu of the application. All other dialog will get dismiss.

#Specification: dialog / MENUBUT_CANCEL / cancel or dismiss ([buttons.cc,126])

The button MENU_CANCEL is either present as "Cancel" or "Dismiss". If there is no other buttons (beside help) in the dialog, having Cancel is not needed. A Dismiss is more appropriate. So we compare the list of buttons for the dialog. If there is only Cancel, then we show Dismiss. During this process, the buttons Ins, Del and Add are ignored. This means a dialog with Cancel, Del, and Help will be presented as Dismiss, Del, and Help.

#Specification: dialog / MENUBUT_OK / never displayed ([buttons.cc,114])

The button "Ok" is never shown except if it is the only button of the dialog. It is implied by an <enter> on a menu item. Initially it was shown but soon user have started complaining that it was useless. From the outside, it looks like a normal buttons, but deep in dialog/buttons.c, it is simply not shown.

3.8 Defining extra buttons

DIALOG::savewhat()

Record a small help for the Save button

        PUBLIC void DIALOG::savewhat(
                const char *help)
        

DIALOG::delwhat()

Record a small help for the Del button

        PUBLIC void DIALOG::delwhat(
                const char *help)
        

DIALOG::inswhat()

Record a small help for the Ins button

        PUBLIC void DIALOG::inswhat(
                const char *help)
        

DIALOG::addwhat()

Record a small help for the Add button

        PUBLIC void DIALOG::addwhat(
                const char *help)
        

DIALOG::setbutinfo()

        PUBLIC void DIALOG::setbutinfo(
                int id, // MENU_xxxx (Not MENUBUT_xxxx)
                const char *title,      // Name that goes in the button
                const char *icon)       // Icon to use in html mode
        

DIALOG::new_button()

Add a text button which will show up within the dialog area (wherever you want in fact, like any other field) See samples.cc: sample_extrabuttons()

        PUBLIC FIELD_BUTTON_TEXT *DIALOG::new_button(
                const char *str,        // Text of the button
                const char *help,       // bubble test over the icon, optional
                PRIVATE_MESSAGE&msg,
                bool enter_action)
        

DIALOG::new_button()

        PUBLIC FIELD_BUTTON_TEXT *DIALOG::new_button(
                const char *str,        // Text of the button
                const char *help,       // bubble test over the icon, optional
                PRIVATE_MESSAGE&msg)
        

DIALOG::new_button_icon()

Add an icon button which will show up within the dialog area (wherever you want in fact, like any other field) See samples.cc: sample_extrabuttons()

        PUBLIC FIELD_BUTTON_ICON *DIALOG::new_button_icon(
                const char *icon,       // icon name, correponding to a xpm file
                const char *help,       // bubble test over the icon, optional
                PRIVATE_MESSAGE&msg)
        

#Specbeg: definitions / MENUBUT ([dialog_def.h,44])

        /* Optionnal buttons for menubox */
        #define MENUBUT_SAVE    1
        #define MENUBUT_ADD     2
        #define MENUBUT_DEL     4
        #define MENUBUT_INS     8
        // Other button setup internally
        #define MENUBUT_OK      16
        #define MENUBUT_ACCEPT      32
        #define MENUBUT_CANCEL      64
        #define MENUBUT_QUIT        128
        #define MENUBUT_YES     256
        #define MENUBUT_NO      512
        #define MENUBUT_EDIT        1024
        #define MENUBUT_RESET       2048
        #define MENUBUT_MORE        4096
        #define MENUBUT_USR1        8192
        #define MENUBUT_USR2        16384
        #define MENUBUT_USR3        32768
        #define MENUBUT_USR4        (32768<<1)
        #define MENUBUT_USR5        (32768<<2)
        #define MENUBUT_USR6        (32768<<3)
        #define MENUBUT_USR7        (32768<<4)

3.9 Changing the behavior of a field

DIALOG::last_noempty()

Set the last field entered so it does not accept empty input

        PUBLIC void DIALOG::last_noempty(
                void)
        

DIALOG::set_readonly()

Set all field into readonly mode

        PUBLIC void DIALOG::set_readonly(
                void)
        

DIALOG::set_lastreadonly()

Set the last field entered readonly

        PUBLIC void DIALOG::set_lastreadonly(
                void)
        

FIELD_COMBO::addopt()

Add one string option to the combo box pick list

        PUBLIC void FIELD_COMBO::addopt(
                const char *str)
        

FIELD_COMBO::addopt()

Add one string option to the combo box pick list. This time there is two string. The strings will be show in the pick list like this value verbose

        PUBLIC void FIELD_COMBO::addopt(
                const char *value,
                const char *verbose)
        

3.10 Saving the state of the edition

DIALOG::save()

Update all variables with the edition buffer.

        PUBLIC void DIALOG::save(
                void)
        

DIALOG::restore()

Set all variables to its original content.

        PUBLIC void DIALOG::restore(
                void)
        

DIALOG::was_modified()

Return != 0 if anyone field has been modified

        PUBLIC int DIALOG::was_modified(
                void)
        

3.11 Testing the user interface mode

This is done by reading the global variable dialog_mode.

#Specbeg: definitions / DIALOG_MODE ([dialog_def.h,69])

        // User interface mode
        enum DIALOG_MODE{
            DIALOG_CURSES,  // Use Ncurse
            DIALOG_HTML,    // Web mode
            DIALOG_GUI,     // One day maybe
            DIALOG_SILENT,  // Special mode, we don't want anything to be printed
            DIALOG_TREE,    // We are building the global menu tree
            DIALOG_TREEJMP, // We are jumping to a special menu entry of the
                            // global tree.
            DIALOG_GET,     // Called by the "registry" to grab data from fields
            DIALOG_SET,     // Called by the "registry" to put data into the fields
        };
        /*
            Glocal variable telling the current operation mode
        */
        extern DIALOG_MODE dialog_mode;

3.12 HTML principles

#Specification: html mode / general strategy ([html.cc,1018])

Here is a basic explanation of the way linuxconf manage html page while not being that much http/html aware. Some fact:

        -Linuxconf is a classical modal program. Mostly, only one dialog has
         the focus at a time. Further, in linuxconf, there is generally
         only one dialog at once. This is acceptable for admin tasks anyway.
        -html mode work with the concept of hope. You send a page to a
         user and he may click on a button one day or never. You better
         not wait for it. So good for a modal program.
The strategy is simple. Linuxconf is always waiting at the top level of the menu hierachy for an html request. Each request contain a path (using /'s) allowing linuxconf to navigate in the menu hisrarchy up to a certain "level". At this point linuxconf simply draw the dialog or menu and ... get back to the top level. This behavior of always getting back is trigger by returning MENU_ESCAPE, so each part of linuxconf must be "ESCAPE" aware, which is good anyway. So when we get a path, we parse it and store it in a table and note the target_level. Important assumption here: All dialog with input are preceded in the hierarchy by menus. This make sens anyway. This disable support for popup dialog however. So we parse and store the path and lauch linuxconf from the top level menu. While navigating in its code, linuxconf draw menus (not really) and wait for input (not really also). If this menu is not of the proper level, the path information will be used as a key to identify which menu item was choosen. A MENU_OK is then returned and linuxconf continue to navigate further into sub-sub-menus (In fact the exact button returned is contain in the path). At some point, it crosses the target level. At the target level, there is two cases: Either this is GET or a POST. If this is a GET, we draw the dialog and escape away to the main loop. If this is a POST, then we load all the fields of the dialog with the values received from the POST. Based on some special values, we know which buttons was hit and return appropriatly MENU_ACCEPT, MENU_ADD and so on to the application. Three things may happen. Either there is some error message (The application identify those with html_setpopup()) or linuxconf is happy and exited to the previous level menu (Leaving this dialog screen) or linuxconf provide a sub-dialog asking for more info. Most of the logic here is controlled by the DIALOG::edithtml() function.

#Specification: html mode / cut ([html.cc,431])

Complex dialog sequences in "HTML mode" work based on one important assumption: A sequence is made of menu and dialog collecting data and only the last dialog really commits the information. To understand this statement, one must understand the principle behind html support in linuxconf. The html mode is really transparent to application code in linuxconf. Because of this I guess it is not obvious: Linuxconf use a classical programming model and html is a stateless model. Classical programming (or modal) means that linuxconf works like this

        -it draws a menu
        -it wait for a selection
        -it calls a sub-routine to handle this selection from the menu
         handling function.
        -it draws a sub-menu
        -it wait for a selection
        -and so on.
This means that linuxconf is always waiting for a specific input at a specific place in the code. Unfortunatly, while this programming model is by far the simplest, it does not work with html. With html we simply have no clue if the client will ever do anything with the dialog we sent. The principle of the html mode is that linuxconf is always walking from its main function to a given dialog. It draws the dialog (generates HTML) and escapes its way to the main (all intermediate dialog generate MENU_ESCAPE). The dialog drawn encode an action URL allowing linuxconf to rewalk the same path he did to draw the dialog. This time, instead of drawing, it accept the input and proceed further, generating a new dialog (with an associated action URL made of a longer path). Then linuxconf escape the its main. So while the user is visiting various dialog, the action URL grows and grows. It contains the path to rewalk all intermediate dialog up to the "target" dialog and so on. Now, what happen if along the path, one dialog accept the information and update some database. By doing so, the surrounding context may be changed in such a way that the action URL is not adequate to rewalk the path up to the target dialog. Here is an example
        -You visit the user account menu
        -you select the list of normal user accounts
        -You "add" a new one
        -You fill the dialog and accept.
        -Linuxconf commit the new account to disk and calls the
         dialog to set the password.
Now there is a problem. We can't "walk" this path twice. The first time we create the new account, the next time, we can't succeed: The account is already created. The solution to this problem is to avoid commiting anything to disk until the last dialog. Linuxconf has used this for account creation for a long time, waiting for the new password before creating the account in a single step. But there are some cases where it is not possible to do so. For example, linuxconf with PAM support must commit the account to disk and then call PAM to change the password (PAM only operate on existing accounts). The solution in this case (when we can't postpone the commit) is to use the "cut" system. Mostly, there are two players. One set the cut level by doing. html_getcutinfo() This function either return a string or NULL. If it return NULL then the caller proceed normally. If it returns a string, then the caller process this string and select a different action, which is normally to skip one dialog and proceed to another one generally, the one right after the commit. Then further down the path, the dialog doing the commit will register the special string using html_setcutinfo() and will also call an another dialog. This trickery is building a new URL path which will go up to the cutting point, supply the proper MENU_CUT value and the associated info.

#Specification: html mode / input overflow ([html.cc,1599])

If linuxconf receive an http request exceding 20000 bytes, it is silently flushed.

#Specification: html mode / multi user ([html.cc,1540])

A single process is handling all requests, one at a time. At most 200 simultaneous http request may occur. The exceeding ones are silently dropped!

#Specification: html mode / strategy / sub dialog ([varval.cc,8])

The system (linuxconf) loosely remember the last 20 POST it received. Potentially a POST yields a sub-dialog. When receiving a POST, linuxconf record the variables and copy those to the application and return to it with the proper button (as per the POST). The application when receiving an ACCEPT button will return to a previous level (logical level of sub menu). Other button generally yield to sub-dialog. In this case, linuxconf will draw the sub-dialog and escape away to the main loop. The next time the user will post into this sub-dialog, linuxconf will rewalk the path and cross the previous post. At this point, the path will tell that this step was indeed a POST and linuxconf will reactivate the variable collected from the original POST and return the original button. This should recreate the situation yielding to the sub-dialog. This is why old variable-values from old POST have to be remembered.

#Specification: html mode / strategy / back on our feet ([html.cc,652])

Maybe this is an already visited dialog. This has been visited along the path followed while running through tblevel[]. While doing so, we have stamped dialog title in each level (in DIALOG::edithtml()). We will search now to see if we can find this dialog title in tblevel and correct target_level accordingly. This situation happen with the following case:

            -The user is visiting one menu
            -he selects one dialog
            -he fills some fields and click on the "accept" button.
            -A subdialog is poped
            -The user fills it
            -After some sub-sub-dialogs the job is done and we are
             back to the original menu.
All these steps have collected a longer and longer html path. Each level of the path have captured the variable states used to go the next. So we have a very long path, but we have to fall on our feet again with a short path.

#Specification: html / modules / base path ([html.cc,913])

Linuxconf modules may provide their own help file and icons in their own directory hierarchy. Linuxconf search in /usr/lib/linuxconf and then in the module's supplied path.

#Specification: HTML / buttons / generating PNGs ([html.cc,955])

When a png image is missing for a button, it is generated using the GD library. The button file name is used as the text.

#Specification: dialog / html mode / field key ([menubox.cc,39])

Each field (either input field or menu entry) is identified with a key (the name= field of html) which has to be unique in the dialog. For input dialog, this is simply the field title (the prompt) combined with the field number (sequence number in the dialog). This create a somewhat meaningless key, but unique. For menu, we use the text of the menu and this yield also a unique key. Even better, this key is valid even if the menu change (a new option is inserted). In linuxconf, there are two types of menus: The normal menus and the lists. With lists, there is a problem with this strategy because lists are often build with multiple columns information. The first columns represent the ID of a record (user account, host name) and the rest represents some information about the record (some values). Unfortunatly, the values are changing while we edit records and this change the key we have used to generate some html dialogs. Sometime this even change the content of some menus several level higher in the hierarchy. This cause problem with the html strategy of linuxconf. Remember that between each http connection, linuxconf is indeed escaping to its main menu and travel to the target dialog of the request by playing back some scenario. This is working unless the various keys identifying the menus are changing. Hope you are following me. So for lists, we are only using the tag (the first column) or each record to build the key. This is controlled by the DIALOG_LISTE object. It sets the tag_is_key flag in each FIELD_MENU objets.

3.13 HTML pass through

DIALOG::html_body()

Use this function to record preformat HTML information. This function may be called several times and will override the normal formatting of dialog. This is a bypass for dialog which are HTML aware and want to do something "nicer". Using this function once will disable all other field formatting.

        PUBLIC void DIALOG::html_body(
                const char *ctl,
                ...)
        

DIALOG::html_intro()

Use this function to record preformat HTML introduction which will be insert at the beginning of the dialog.

        PUBLIC void DIALOG::html_intro(
                const char *ctl,
                ...)
        

DIALOG::html_end()

Use this function to record preformat HTML end of document which will be append after the body of the dialog.

        PUBLIC void DIALOG::html_end(
                const char *ctl,
                ...)
        

DIALOG::html_top()

Use this function to record preformat HTML top (including <body) which will override most of the top of the html dialog

        PUBLIC void DIALOG::html_top(
                const char *ctl,
                ...)
        

3.14 GUI

DIALOG::set_alt_title()

Record a title string which will be displayed inside the dialog centered. The other title goes on the window. In HTML mode, the standard title in drawn centered unless overriden by this one. This is because the title is not readable by many browser (The top border of the window contain to much).

        PUBLIC void DIALOG::set_alt_title(
                const char *_title)
        

DIALOG::setsidetitle()

        PUBLIC void DIALOG::setsidetitle(
                const char *ctl,
                ...)
        

DIALOG::newline()

Record a line change command. This only affect the GUI. Using this override the normal disposition done for the GUI. You are on your own...

        PUBLIC void DIALOG::newline(
                void)
        

DIALOG::gui_label()

Record a label command. This only affect the GUI. Using this override the normal disposition done for the GUI. You are on your own...

        PUBLIC void DIALOG::gui_label(
                const char *ctl,
                ...)
        

DIALOG::gui_group()

Record a grouline change command. This only affect the GUI. Using this override the normal disposition done for the GUI. You are on your own...

        PUBLIC void DIALOG::gui_group(
                const char *title)
        

DIALOG::gui_dispolast()

Control the disposition of the previous object. The object may occupy several cells horizontally and vertically and may be positionned inside this space.

        PUBLIC void DIALOG::gui_dispolast(
                GUI_H_DISPO dispoh,
                int nbcellh,
                GUI_V_DISPO dispov,
                int nbcellv)
        

DIALOG::gui_end()

Record a end of block command. This only affect the GUI. Using this override the normal disposition done for the GUI. You are on your own...

        PUBLIC void DIALOG::gui_end(
                void)
        

DIALOG::gui_passthrough()

Record a GUI command. This only affect the GUI. Using this override the normal disposition done for the GUI. You are on your own...

        PUBLIC void DIALOG::gui_passthrough(
                int command,
                const char *args,
                ...)
        

DIALOG::gui_passthroughv()

Record a GUI command. This only affect the GUI. Using this override the normal disposition done for the GUI. You are on your own...

        PUBLIC void DIALOG::gui_passthroughv(
                int command,
                const char *args,
                va_list list)
        

#Specification: FIELD_PASSTHROUGH / principle ([pass.cc,1])

Linuxconf supports multiple user interface. From a generic DIALOG interface, it builds HTML, Text and GUI dialogs. These dialogs are uniform, but sometime, one wishes to make the GUI version more appealing. The FIELD_PASSTHROUGH is used to record GUI commands. See <htmlurl url=http://www.solucorp.qc.ca/linuxcong/tech/guiapi > When a single FIELD_PASSTHROUGH is defined in a dialog, linuxconf assume the dialog is layed out manually, so the programmer is free to implement about any disposition he wants. FIELD_PASSTHROUGH has no effect for the HTML and Text interface.

#Specbeg: dialog / sample code / using GUI passthrough ([samples.cc,568])

        /*
            Linuxconf dialogs are generally presented uniformly, using
            a simple layout. Using a single source, one produces a dialog
            operating in text mode, graphical and web. Cool!
        
            But one may wish to invest a little bit more in the graphical
            look of his dialog. The gui_passthrough is there for that. It is
            not terribly difficult to use, but one must understand the GUI
            protocol (using to communicate with the GUI front-end). It is
            documented at
        
            <sgml>
            <htmlurl
                name="http://www.solucorp.qc.ca/linuxconf/tech"
                url="http://www.solucorp.qc.ca/linuxconf/tech"
            >
            </sgml>
        
            The various P_xxxx primitives are defined in proto.h.
        
            Once you use the DIALOG::gui_passthrough(), DIALOG::newline()
            or any DIALOG::gui_xxx() function, you are on your own.
            You control the layout of every field and must use DIALOG::newline()
            on a regular basis.
        
            The following dialog presents two groups. One contains various
            input field. The other contains a single field surrounded by
            labels.
        */
        static void sample_guipassthrough()
        {
            if (dialog_mode != DIALOG_GUI){
                xconf_error ("Only works in GUI mode");
            }else{
                DIALOG dia;
                SSTRING s1,s2,s3,s4;
                dia.gui_group ("Group 1");
                    dia.newf_str ("Field 1",s1,10);
                    dia.newline();
                    // No label in front of the field. s2 will be under
                    // the "Field 1" label. s3 will be under s1
                    dia.newf_str (NULL,s2);
                    dia.newf_str (NULL,s3,10);
                dia.gui_end ();
                dia.gui_group ("Group 2");
                    // First line
                    dia.gui_label ("Label1");
                    dia.gui_label ("Label2");
                    dia.gui_label ("Label3");
                    dia.gui_passthrough (P_Label,"Label4\n");
                    dia.newline();
                    // Second line, 1 label, one field covering 2 columns and
                    // a label.
                    dia.gui_label ("Label1");
                    dia.newf_str (NULL,s4,20);
                    dia.gui_dispolast (GUI_H_CENTER,2,GUI_V_CENTER,1);
                    dia.gui_label ("Label4");
                    dia.newline();
                    dia.gui_label ("Label1");
                    dia.gui_label ("Label2");
                    dia.gui_label ("Label3");
                    dia.gui_label ("Label4");
        
                dia.gui_end();
                int nof = 0;
                while (1){
                    MENU_STATUS code = dia.edit ("GUI layout","",help_nil,nof);
                    if (code == MENU_ESCAPE || code == MENU_CANCEL){
                        break;
                    }else{
                        break;
                    }
                }
            }
        }

#Specification: GUI layout / no label for a field ([diagui.cc,345])

All field definitions are done using function like DIALOG::newf_str(). The first argument is the "prompt" or field title. Sometime, we do not want any field title and do not want any space left in the layout. By sending a NULL, we are telling the layout engine to skip the prompt completly. Now, to avoid little problems here and there in the dialog project, a NULL prompt is recorded as the string "\n".

DIALOG::hide()

Hide the DIALOG from the screen. Only useful in graphic mode. No effect for other mode.

        PUBLIC void DIALOG::hide(
                void)
        

DIALOG::show()

Present the dialog. Return immedialy

        PUBLIC void DIALOG::show(
                const char *_title,     // Main title
                const char *_intro,     // Mini help describing the purpose
                // of the dialog
                HELP_FILE&helpfile, // Help text in a file or NULL
                int &nof,   // Start editing on which field
                // Will contain the current field.
                int but_options)        // MENUBUT_xxxxx
        

DIALOG::setcontext()

Record the GUI context in which this dialog will be inserted

        PUBLIC void DIALOG::setcontext(
                const char *s)
        

diagui_setdefaultctx()

Default context for DIALOGs Record the default context in which dialog may be inserted. A context is a path inside an existing dialog.

        EXPORT void diagui_setdefaultctx(
                const char *s)
        

DIALOG::settype()

Register the dialog type

        PUBLIC void DIALOG::settype(
                DIALOG_TYPE type)

#Specbeg: DIALOG_TYPE ([dialog_def.h,4])

        enum DIALOG_TYPE {
            DIATYPE_STD,
            DIATYPE_ERROR,
            DIATYPE_NOTICE,
            DIATYPE_POPUP,
            DIATYPE_MENUPOPUP,
        };

3.15 GUI threads

uithread()

Register a new thread. The fct function will start the processing for the thread. It return the ID number of the new thread.

        EXPORT int uithread(
                void(*fct)(void *),
                void *data)
        

uithread()

        EXPORT int uithread(
                void(*fct)())
        

#Specbeg: dialog / sample code / UI threads ([samples.cc,253])

        /*
            Linuxconf UI toolkit is based on classical programming. This is
            sometime called modal programming. This kind of programming is
            much simpler as opposed to event driven (such as most GUI toolkit).
            It can be presented by the following pseudo code:
        
            #
            setup input field
            while (1){
                call the edit function
                process the user actions (button press)
            }
            #
        
            In a single function, one can create and handle a complete
            multi-field dialog. Much more simpler (code efficient).
        
            But it has a drawback. Once a dialog has called the edit function
            the program is locked there. So you can't have multiple dialog
            operating at once. This limitation has been solved using
            the User Interface Threads (UI threads).
        
            UI threads are keeping the simplicity of classical programming
            while removing the major limitation. This has been used
            primarily for the linuxconf project. Linuxconf is made
            of many many dialogs, so it pays to have this as simple as possible.
            We believe that this strategy could be used for more GUI oriented
            application as well though.
        
            The following applicaiton shows two independant contexts working
            at once.
        */
        static void sample_context()
        {
            while (1){
                if (dialog_yesno("title","Are you sure",help_nil)==MENU_YES){
                    xconf_notice("Really");
                }else{
                    xconf_notice("This shows");
                    break;
                }
            }
        }
        
        static void sample_thread()
        {
            uithread(sample_context);
            sample_context();
        }

#Specbeg: dialog / sample code / UI threads / passing information ([samples.cc,305])

        /*
            There are two ways to pass information to a thread. The implicit
            way is to use glocal variables (or static variables outside function
            body). Threads live in the same process so have the same access.
        
            The other solution is to pass private data using the uithread
            optional second argument. The argument is a void pointer, so you
            can pretty much pass anything you want this way.
        
            There is a little catch though. The information passed must continue
            to exist, potentially after the calling thread exits. So the void pointer
            can't point to local variable in the caller thread. Well, they can
            but you must be sure the new threads will exits before the caller.
        
            The solution to this problem is to always pass information allocated
            on the heap, using malloc, strdup, or new and let the new thread
            delete it when done. It general, it is better to have the allocator
            handle the de-allocation (cleaner), but in this case, it is much simpler
            this way. Here is an example.
        */
        static void sample_context_with_data(void *data)
        {
            char *msg = (char *)data;
            while (1){
                if (dialog_yesno("title",msg,help_nil)==MENU_YES){
                    xconf_notice("Really");
                }else{
                    xconf_notice("This shows");
                    break;
                }
            }
            free (msg);
        }
        
        static void sample_threaddata()
        {
            uithread(sample_context_with_data,strdup("Are you sure"));
            uithread(sample_context_with_data,strdup("Are you really sure"));
            xconf_notice ("Hello world");
        }

#Specification: uithread / principles ([uithread.cc,1])

uithread means "User Interface Thread". They are less generic threads and potentially less problematic ones as well. They exist so multiple dialog may interact asynchronoulsly. These dialogs are using a very simple programming model called modal programming. uithread allows one to keep this simple model and yet produce fancy GUI software as well. Uithreads are created using the uithread function. This function accept a function pointer and optionally, a void pointer. In the first case, the function pointer is of type void (*fct)(); while in the other case, it is of type void (*fct)(void *) A thread may be in 3 states: Starting, running, and waiting. The following restriction also exists:

So uithreads are simpler to use then ordinary threads. They are less flexible for sure.

#Specification: uithread / stack size ([uithread.cc,156])

All uithread's are created at startup time (using uithread_init itself called by diagui_init...). A fixed amount is created (20). Each thread gets a 100000 bytes stack. Real memory is not really allocated though. This 100000 is arbitrary. A much larger value could probably used I guess (few megs). Allocation thread stack as needed is probably doable, although not as easily I guess (less portable). Input welcome.

#Specification: uithread / GUI mode ([uithread.cc,254])

Uithreads only work in GUI mode. In the other modes (HTML, Text) the uithread function simply call the thread function and wait until it complete. At some point, uithreads could be implement in text mode, but we would need a way for the user to switch between threads, maybe using a task bar at the top of the screen.

3.16 Special dialogs

dialog_textbox()

Display text from a file in a dialog box.

        EXPORT MENU_STATUS dialog_textbox(
                const char *title,
                const char *file)
        

dialog_textbox()

Display text from a table of strings.

        EXPORT MENU_STATUS dialog_textbox(
                const char *title,
                const char *intro,
                HELP_FILE&help,
                const SSTRINGS&strs)
        

dialog_textbox()

Display text from a table of strings.

        EXPORT MENU_STATUS dialog_textbox(
                const char *title,
                const SSTRINGS&strs)

dialog_yesno()

Display a dialog box with two buttons - Yes and No Return MENU_YES, MENU_NO or MENU_ESCAPE

        EXPORT MENU_STATUS dialog_yesno(
                const char *title,
                const char *prompt,
                HELP_FILE&helpfile, /* path of a help text file */
                /* or help_nil */
                bool default_is_yes)
        

dialog_yesno()

Display a dialog box with two buttons - Yes and No Return MENU_YES, MENU_NO or MENU_ESCAPE

        EXPORT MENU_STATUS dialog_yesno(
                const char *title,
                const char *prompt,
                HELP_FILE&helpfile) /* path of a help text file */
                /* or help_nil */

3.17 Field layout

DIALOG::newf_title()

Add a new field/title. A field/title act as a splitter between to logical section of a dialog There are 3 cases: msg contain a string. It will appear centered with horizontal rules on each side. msg is empty. A single horizontal rule will be drawn filling the line. msg is "-". A left justified horizontal rule will be drawn, not filling the line.

        PUBLIC void DIALOG::newf_title(
                const char *pad,        // Name of the pad in the notebook
                // or NULL is this is only a separator
                int level,      // 1 for the main notepad, 2 for a subnotepad
                const char *prompt,
                const char *msg)
        

DIALOG::newf_title()

        PUBLIC void DIALOG::newf_title(
                const char *prompt,
                const char *msg)
        

#Specbeg: dialog / sample code / mapping fields in several pages ([samples.cc,719])

        /*
            Large dialogs are often annoying to use. You must use the scroll
            bar to review the various fields. Dialogs are often splitted in
            several section, with title between each. Using the DIALOG::newf_title
            one can easily enhance the look of a large dialog. It distributes
            the field in pages of a notebook object. Sub-notebook are even supported
            (as seen in the user account dialog, with the various privilege sections).
            The following example presents this.
        */
        static void sample_setpage (DIALOG &dia, SSTRING s[], int start, int stop)
        {
            for (int i=start; i<stop; i++){
                char tmp[10];
                sprintf (tmp,"Field %d",i);
                dia.newf_str (tmp,s[i]);
            }
        }
        static void sample_notebook()
        {
            if (dialog_mode != DIALOG_GUI) xconf_notice ("Better viewed in GUI");
            DIALOG dia;
            SSTRING s[20];
            dia.newf_title ("Page 1",1,"","Section 1");
            sample_setpage (dia,s,0,4);
            dia.newf_title ("Page 2",1,"","Section 2");
            sample_setpage (dia,s,4,6);
            dia.newf_title ("","Split");
            sample_setpage (dia,s,6,8);
            dia.newf_title ("Page 3",1,"","Section 3");
            dia.newf_title ("Sub-Page 3.1",2,"","Section 3.1");
            sample_setpage (dia,s,8,12);
            dia.newf_title ("Sub-Page 3.2",2,"","Section 3.2");
            sample_setpage (dia,s,12,16);
            int nof = 0;
            while (1){
                MENU_STATUS code = dia.edit ("Notebook"
                    ,"Check all the page and the sub-pages",help_nil,nof);
                if (code == MENU_ESCAPE || code == MENU_CANCEL){
                    break;
                }else{
                    break;
                }
            }
        }

3.18 Talking to the GUI front-end directly

diagui_flush()

Flush the command sent to the front-end

        EXPORT void diagui_flush(
                void)
        

diagui_getval()

Return the current value of a field All field have an ID in the java frontend. This id is generally a letter followed by a number.

        EXPORT const char *diagui_getval(
                char prefix,
                int nof)
        

diagui_getval()

        EXPORT const char *diagui_getval(
                const char *diapath,
                char prefix,
                int nof)
        

diagui_getval()

        EXPORT const char *diagui_getval(
                const char *diapath,
                char prefix,
                const char *str)
        

diagui_getval()

        EXPORT const char *diagui_getval(
                char prefix,
                const char *str)
        

diagui_getvals()

Return the current values of a field All field have an ID in the java frontend. This ID is generally a letter followed by a number. Some fields (textarea) are returned by the front-end as multiple lines with the same ID. Return the number of lines places in tb[].

        EXPORT int diagui_getvals(
                const char *diapath,
                char prefix,
                int nof,
                SSTRINGS&tb)
        

diagui_getvals()

        EXPORT int diagui_getvals(
                char prefix,
                int nof,
                SSTRINGS&tb)
        

diagui_quote()

        EXPORT const char *diagui_quote(
                const char *s,
                char tmp[1000])
        

diagui_sendcmd()

Send a command to the user interface server

        EXPORT void diagui_sendcmd(
                int cmd,
                const char *ctl,
                ...)
        

diagui_send_Label()

        void diagui_send_Label(
                const char *str)
        

diagui_sendxpm()

        EXPORT int diagui_sendxpm(
                const char *name,       // Name of the icon to send
                char *name_sent)        // Name selected if this one was missing
        

diagui_sync()

Wait for an event for this uithread. This function is used by dialogs which completly bypass the DIALOG object and build their GUI by talking directly to the GUI front-end (using diagui_sendcmd). Those dialogs are really "on their own". So far only the treemenu module is using that as the tree widget is not integrated in the DIALOG object. This function returns 0 when something was selected in the dialog (on element of the treemenu for example). In that case, action contains the selected item. The function returns > 0 when a button is selected. The value is the ID of the button as passed using diagui_sendcmd().

        EXPORT int diagui_sync(
                const char *dianame,    // Base name of the dialog
                SSTRING&path,       // path of the selected component
                // (button, treemenu)
                SSTRING&action,     // Action associated with the component
                SSTRING&menubar)    // menubar selection
        

diagui_sync()

Wait for an event on a POPEN connection for this uithread.

        EXPORT int diagui_sync(
                POPENFD&po,
                int timeout)
        

3.19 Inter dialog communication and timers

#Specification: inter-dialog messaging / principles ([diagui.cc,1039])

The GUI allows independant (asynchronous, amodal) dialogs. Each dialog (or dialog set) runs in a special thread and we need a way to wake up one dialog from another, so it perform some action. This is done by sending a message. The message may be seen like a broadcast. It is sent, and zero, one or more dialog may react. Messages are sent using the function dialog_sendmessage(). Timers event are built on top of dialog_sendmessage(). The message is sent asynchronouly. This means dialog_sendmessage() return immediatly and when the caller thread yields (when blocking in DIALOG::edit for example), all interested dialogs are woke up. A message is either a string (any string you can think of) or a PRIVATE_MESSAGE. Any dialog may register interest in any string message it wants. So messages sent using string are reserved for application wide events. Dialogs unknown to the sender may wake up. Further, there is no way to make sure two independant pieces of code are not using the same string to notify unrelated dialogs. PRIVATE_MESSAGE exists to solve this. A PRIVATE_MESSAGE is simply a variable which exist solely so we can reference it memory address. Only the piece of code knowing this variable, be it either a static variable or on the heap, can send and receive messages using it. This strategy is completly safe and simple. Unrelated piece of code can't clash. Further, multiple instances of the same dialogs may communicate independantly if they create the PRIVATE_MESSAGE object dynamically.

#Specification: PRIVATE_MESSAGE / principle ([private_msg.h,9])

A private message is simply the address of an object. The object has no content, but its address is unique. In general, private messages are used to communicate between various threads without interference with other threads. Unlike textual messages, private message can't be wrong (spelling error) and may be used to establish communication between a single dialog set. Textual messages are more useful to send broadcast to potentially unknown participants. The class PRIVATE_MESSAGE could have been a void *, but we wanted to have something really type safe, avoiding confusion.

#Specification: inter-dialog messaging / timers / restricted to GUI ([diagui.cc,1245])

The current timer implementation relies on uithread which only work in GUI mode. This will have to be generalised.

dialog_sendmessage()

Wakeup dialogs which are waiting for this message

        EXPORT void dialog_sendmessage(
                const char *msg)
        

dialog_sendmessage()

Wakeup dialogs which are waiting for this private message

        EXPORT void dialog_sendmessage(
                PRIVATE_MESSAGE&msg)
        

dialog_testmessage()

Test if the last message is generated is this one.

        EXPORT bool dialog_testmessage(
                const char *msg)
        

dialog_testmessage()

Test if the last message is generated is this one.

        EXPORT bool dialog_testmessage(
                PRIVATE_MESSAGE&msg)
        

dialog_getmessage()

Return the last message received It may return an empty string if the message was a PRIVATE_MESSAGE

        EXPORT const char *dialog_getmessage(
                void)
        

dialog_getprivmessage()

Return the last private message received. It may return NULL if the message was a textual one.

        EXPORT PRIVATE_MESSAGE *dialog_getprivmessage(
                void)
        

DIALOG::waitfor()

Record a message that must wake up this dialog. The message is sent using dialog_sendmessage().

        PUBLIC void DIALOG::waitfor(
                const char *msg)
        

DIALOG::waitfor()

Record a message that must wake up this dialog. The message is sent using dialog_sendmessage().

        PUBLIC void DIALOG::waitfor(
                PRIVATE_MESSAGE&msg)
        

#Specbeg: dialog / sample code / inter dialog messaging ([samples.cc,78])

        /*
            This creates two independant dialogs which are exchanging
            messages. In this example, the same code is used to create
            the dialog, except that we exchange the message sent and received
            by each dialog.
        */
        
        
        static void sample_dialog(PRIVATE_MESSAGE &send, PRIVATE_MESSAGE &receive)
        {
            DIALOG dia;
            dia.waitfor (receive);
            SSTRING s;
            dia.newf_str ("A string",s);
            dia.setbutinfo (MENU_USR1,"Send","Send");
            int nof = 0;
            while (1){
                MENU_STATUS code = dia.edit ("title","The field grows"
                    ,help_nil,nof
                    ,MENUBUT_CANCEL|MENUBUT_ACCEPT|MENUBUT_USR1);
                if (code == MENU_CANCEL || code == MENU_ESCAPE){
                    break;
                }else if (code == MENU_MESSAGE){
                    if (dialog_testmessage(receive)){
                        dia.save();
                        s.append("X");
                        dia.reload ();
                    }
                }else if (code == MENU_USR1){
                    dialog_sendmessage (send);
                }else{
                    // Do nothing with s, just exit
                    break;
                }
            }
        }
        static PRIVATE_MESSAGE p1,p2;
        
        static void sample_fct (void *)
        {
            sample_dialog (p2,p1);
        }
        static void sample_messages()
        {
            uithread (sample_fct,NULL);
            sample_dialog (p1,p2);
        }

#Specification: inter-dialog messaging / timers ([diagui.cc,1160])

A timer generate and inter-dialog message every N seconds. You can create one shot timer (rearm=false) or continuous timer (rearm=true). Timer may send a string message or a PRIVATE_MESSAGE. All string messages sent have the prefix "timer-". The prefix is inserted in front of the string ID of the timer.

dialog_settimer()

Create a timer which will send a message ever "seconds". Dialog interested in this timer just call the DIALOG::waitfortimer() function and it will receives MENU_MESSAGE events. You test if this was this timer which originate the message with dialog_testtimer().

        EXPORT void dialog_settimer(
                const char *id, // ID of the timer (any string you want)
                int seconds,    // number of seconds between events
                bool rearm)     // Is this a one shot timer or it must rearm itself
        

dialog_settimer()

Create a timer which will send a message ever "seconds". Dialog interested in this timer just call the DIALOG::waitfortimer() function and it will receives MENU_MESSAGE events.

        EXPORT void dialog_settimer(
                PRIVATE_MESSAGE&msg,        // ID of the timer
                int seconds,    // number of seconds between events
                bool rearm)     // Is this a one shot timer or it must rearm itself
        

dialog_deltimer()

Delete a timer

        EXPORT void dialog_deltimer(
                const char *id)
        

dialog_deltimer()

        EXPORT void dialog_deltimer(
                PRIVATE_MESSAGE&msg)
        

dialog_testtimer()

Test if the last message is generated was caused by this timer

        EXPORT bool dialog_testtimer(
                const char *id)
        

dialog_testtimer()

Test if the last message is generated was caused by this timer

        EXPORT bool dialog_testtimer(
                PRIVATE_MESSAGE&msg)
        

DIALOG::waitfortimer()

Record a timer that must wake up this dialog. The timer is create using dialog_settimer().

        PUBLIC void DIALOG::waitfortimer(
                const char *id)
        

DIALOG::waitfortimer()

        PUBLIC void DIALOG::waitfortimer(
                PRIVATE_MESSAGE&priv)
        

#Specbeg: dialog / sample code / timers ([samples.cc,170])

        /*
            This dialog presents a single numeric field, which grows by
            itself every 2 seconds
        */
        static void sample_timer()
        {
            DIALOG dia;
            int num = 0;
            dia.newf_num ("Some value",num);
            PRIVATE_MESSAGE msg;
            dialog_settimer(msg,2,false);
            dia.waitfortimer (msg);
            dia.setbutinfo (MENU_USR1,"stop timer","stop timer");
            dia.setbutinfo (MENU_USR2,"start timer","start timer");
            int nof=0;
            while (1){
                MENU_STATUS code = dia.edit ("title","The field grows"
                    ,help_nil,nof
                    ,MENUBUT_CANCEL|MENUBUT_ACCEPT|MENUBUT_USR1|MENUBUT_USR2);
                if (code == MENU_CANCEL || code == MENU_ESCAPE){
                    break;
                }else if (code == MENU_MESSAGE){
                    if (dialog_testtimer(msg)){
                        num++;
                        dia.reload ();
                    }
                }else if (code == MENU_USR1){
                    dialog_deltimer(msg);
                }else if (code == MENU_USR2){
                    dialog_settimer(msg,2,true);
                }else{
                    xconf_notice ("num = %d",num);
                    break;
                }
            }
            dialog_deltimer(msg);
        }

3.20 updating field content

DIALOG::reload()

Reload some input buffer from the variables.

        PUBLIC void DIALOG::reload(
                int start,
                int end)
        

DIALOG::reload()

Reload all the input buffer from the variables.

        PUBLIC void DIALOG::reload(
                void)
        

DIALOG::reload()

Reload the input buffer from the variable.

        PUBLIC void DIALOG::reload(
                int no)
        

DIALOG::remove_last()

Remove the last entries of the DIALOG, after the "cut" first items

        PUBLIC void DIALOG::remove_last(
                int cut)
        

#Specbeg: dialog / sample code / reloading ([samples.cc,211])

        /*
            This dialog shows how you can change the content of a field
            while the dialog is running.
        
            The dialog present 5 check boxes, initially un-selected.
            Two extra buttons are added: All and None. The All button
            selects all check-boxes and the None button un-select them.
        
            Note that the DIALOG::reload() function accept 0 or one argument.
            The 0 argument version reloads all field input buffer from their
            corresponding variable while the other one reload a single field.
        */
        static void sample_reload()
        {
            DIALOG dia;
            char tb[5];
            for (int i=0; i<5; i++){
                char opt[20];
                sprintf (opt,"Option %d",i+1);
                tb[i] = 0;
                dia.newf_chk (opt,tb[i],"is active");
            }
            dia.setbutinfo (MENU_USR1,"All","All");
            dia.setbutinfo (MENU_USR2,"None","None");
            int nof = 0;
            while (1){
                MENU_STATUS code = dia.edit ("title","",help_nil
                    ,nof,MENUBUT_ACCEPT|MENUBUT_CANCEL|MENUBUT_USR1|MENUBUT_USR2);
                if (code == MENU_CANCEL || code == MENU_ESCAPE){
                    break;
                }else if (code == MENU_USR1 || code == MENU_USR2){
                    memset (tb,code == MENU_USR1 ? 1 : 0, sizeof(tb));
                    dia.reload();
                }else{
                    break;
                }
            }
        }

#Specbeg: dialog / sample code / records ([samples.cc,11])

        /*
            This shows how to handle table of records. This is pretty much
            what linuxconf is doing all around.
        */
        
        class PERSON: public ARRAY_OBJ{
        public:
            SSTRING name;
            SSTRING surname;
            SSTRING tel;
            PERSON(const char *_name, const char *_surname, const char *_tel){
                name.setfrom (_name);
                surname.setfrom (_surname);
                tel.setfrom (_tel);
            }
        };
        
        class PERSONS: public ARRAY{
        public:
            PERSON *getitem(int no) const{
                return (PERSON*)ARRAY::getitem(no);
            }
        };
        
        
        static void sample_records()
        {
            // Fill few records
            PERSONS tb;
            tb.add (new PERSON("Gelinas","Jacques","111-2222"));
            tb.add (new PERSON("Who","John","222-2222"));
            tb.add (new PERSON("Red","Jessy","333-2222"));
            //
            DIALOG_RECORDS dia;
            dia.newf_head ("","Name\tSurname\tTelephone");
            int nof = 0;
            while (1){
                for (int i=0; i<tb.getnb(); i++){
                    PERSON *p = tb.getitem(i);
                    char buf[100];
                    snprintf (buf,sizeof(buf)-1,"%s\t%s",p->surname.get(),p->tel.get());
                    dia.set_menuitem (i,p->name.get(),buf);
                }
                // Remove extra fields
                // dia.getnb() includes the header
                dia.remove_last (tb.getnb()+1);
                MENU_STATUS code = dia.editmenu ("title","introduction",help_nil
                    ,nof,MENUBUT_ADD|MENUBUT_DEL);
                if (code == MENU_QUIT || code == MENU_ESCAPE){
                    break;
                }else if (code == MENU_DEL){
                    if (xconf_delok()){
                        tb.remove_del (0);
                    }
                }else if (code == MENU_ADD){
                    static int add=1;   // Add arbitrary fields
                    char buf[10];
                    sprintf (buf,"%d",add++);
                    tb.add (new PERSON(buf,buf,buf));
                }else{
                    xconf_notice ("Edit field %d",nof);
                }
            }
        }

3.21 Misc

DIALOG::getmenustr()

Return the second string of a menu item

        PUBLIC const char *DIALOG::getmenustr(
                int choice)
        

DIALOG::addhelp()

Add a secondary help to the dialog.

        PUBLIC void DIALOG::addhelp(
                HELP_FILE&help,
                const char *title)
        

DIALOG::setheight_hint()

Make sure the dialog is as tall as possible in text mode. This is mainly use by the treemenu dialog, to avoid visual effect each time one expand a branch.

        PUBLIC void DIALOG::setheight_hint(
                void)
        

dialog_parseuioptions()

Parse the various User interface options and remove them from argv[]. Return the number of option left in argv[]. argv[0] is left untouched.

        EXPORT int dialog_parseuioptions(
                int argc,
                char *argv[])
        

dialog_settimeout()

Specify the timeout for the next input in seconds. The timeout reset itself. It means that the first key pressed by the user will desactivated the timeout feature until dialog_settimeout() is called again. This function is generally called only once at the beginning of a program which must continue by itself if the user do not interact (there is no user). If the timeout expire before a key is depressed, retcod is returned. retcod is generally MENU_ESCAPE or something equivalent.

        void dialog_settimeout(
                int nbsec,
                MENU_STATUS retcod,
                bool rearm)     // Reactivate itself a each input
        

3.22 Some examples

#Specbeg: dialog / sample code / adding special buttons ([samples.cc,1075])

        /*
            The following code presents two way to add buttons to a dialog.
            The UI toolkit defines a standard way to place buttons at the bottom
            of a dialog. Further, it defines standard buttons such as save, cancel,
            reset and so on. You can add user defined button at the bottom of the
            dialog as well as user defined buttons anywhere in the dialog. This
            functionality (adding buttons anywhere) better fit the GUI model.
        */
        
        static void sample_extrabuttons()
        {
            DIALOG dia;
            SSTRING f1,f2;
            PRIVATE_MESSAGE m1,m2;
            int field_f1 = dia.getnb(); // Get the index of this field
            dia.newf_str ("Field 1",f1);
            dia.new_button ("reset field 1","some help about this button",m1,true);
            dia.newline();
            int field_f2 = dia.getnb(); // Get the index of this field
            dia.newf_str ("Field 2",f2);
            dia.new_button_icon ("xquit","some help about this button",m2);
            dia.new_button_icon ("qmark","some help about this button",m2);
            dia.new_button_icon ("uparrow","some help about this button",m2);
            dia.new_button_icon ("downarrow","some help about this button",m2);
            dia.newline();
            // Adding a "default" button at the bottom
            // You assign one MENU_USR_ to your button
            dia.setbutinfo (MENU_USR1,"default","icon-default");
            int nof = 0;
            while (1){
                MENU_STATUS code = dia.edit("extrabuttons","",help_nil,nof
                    ,MENUBUT_CANCEL|MENUBUT_ACCEPT|MENUBUT_USR1);
                if (code == MENU_CANCEL || code == MENU_ESCAPE){
                    break;
                }else if (code == MENU_ACCEPT){
                    xconf_notice ("f1=%s\nf2=%s",f1.get(),f2.get());
                    break;
                }else if (code == MENU_USR1){
                    f1.setfrom ("default for field 1");
                    f2.setfrom ("default for field 2");
                    dia.reload();
                }else if (code == MENU_MESSAGE){
                    if (dialog_testmessage(m1)){
                        f1.setfrom ("");
                        dia.reload(field_f1);
                    }else if (dialog_testmessage(m2)){
                        f2.setfrom ("");
                        dia.reload (field_f2);
                    }
                }
            }
        }

#Specbeg: dialog / sample code / changing dialog intro ([samples.cc,1568])

        /*
            It is possible to change the dialog intro section from one
            call of DIALOG::edit to the other. Here is a sample code doing this
        */
        
        static void sample_changeintro()
        {
            DIALOG dia;
            SSTRING intro;
            intro.setfrom ("This is the intro");
            dia.setbutinfo (MENU_USR1,"reset","reset");
            int nof = 0;
            while (1){
                MENU_STATUS code = dia.edit ("change intro",intro.get(),help_nil
                    ,nof
                    ,MENUBUT_CANCEL|MENUBUT_ACCEPT|MENUBUT_USR1);
                if (code == MENU_ESCAPE || code == MENU_CANCEL){
                    break;
                }else if (code == MENU_USR1){
                    intro.setempty();
                }else if (code == MENU_ACCEPT){
                    intro.append ("\nOne more line");
                }
            }
        }

#Specbeg: dialog / sample code / Changing the text or icon of a button ([samples.cc,1130])

        /*
            The following code shows how you can change the label or the icon
            of a button, on the fly.
        */
        
        static void sample_changebuttons()
        {
            // The following dialog presents a gauge which grow one step every one
            // or two seconds. Using the little icon, you can stop or start the
            // animation. Using the text field, you can control the rate.
            DIALOG dia;
            int gauge = 0;
            PRIVATE_MESSAGE m1,m2;
            dia.newf_gauge ("",gauge,100);
            FIELD_BUTTON_ICON *button_stop = dia.new_button_icon ("stop"
                ,"some help about this button",m1);
            dia.newline();
            static const char *button_labels[]={
                "Every seconds",
                "Every two seconds"
            };
            int rate = 0;
            FIELD_BUTTON_TEXT *button_rate = dia.new_button (button_labels[0]
                ,"some help about this button",m2);
            dia.newline();
            // We define the timer
            PRIVATE_MESSAGE timer;
            dialog_settimer (timer,1,true);
            dia.waitfor (timer);
            int nof = 0;
            bool stopped = false;
            while (1){
                MENU_STATUS code = dia.edit("changebuttons","",help_nil,nof
                    ,MENUBUT_CANCEL);
                if (code == MENU_CANCEL || code == MENU_ESCAPE){
                    break;
                }else if (code == MENU_MESSAGE){
                    if (dialog_testmessage(timer)){
                        gauge = (gauge+5)%100;
                        dia.reload();
                    }else if (dialog_testmessage(m1)){
                        if (stopped){
                            dialog_settimer (timer,rate+1,true);
                            stopped = false;
                            button_stop->seticon ("stop");
                        }else{
                            button_stop->seticon ("run");
                            dialog_deltimer (timer);
                            stopped = true;
                        }
                    }else if (dialog_testmessage(m2)){
                        rate = (rate+1)%2;
                        button_rate->settext (button_labels[rate]);
                        if (!stopped){
                            dialog_deltimer (timer);
                            dialog_settimer (timer,rate+1,true);
                        }
                    }
                }
            }
        }

#Specbeg: dialog / sample code / following notebook page focus ([samples.cc,1437])

        /*
            This is specific to GUI mode.
        
            In complex dialog, we may want to map several independant dialogs
            (documents) in a notebook. It might be important to know
            which document has the visibility focus so we can direct
            some action properly. For example, if we have a pull-down
            menu and use the save menu option, we have to know which
            one will be saved.
        
            Note that this kind of application are better handled with the
            TLMP framework object...
        
            The page focus concept only inform about the page number currently
            shown. We have to translate this so we know which document
            is concerned.
        */
        struct PAGE_INFO{
            SSTRING path;
            PRIVATE_MESSAGE reload;
            PRIVATE_MESSAGE quit;
            PRIVATE_MESSAGE save;
            PAGE_INFO(const char *_path){
                path.setfrom (_path);
            }
        };
        
        static void sample_pagedocument(void *data)
        {
            PAGE_INFO *info = (PAGE_INFO*)data;
            DIALOG dia;
            dia.waitfor (info->reload);
            dia.waitfor (info->quit);
            dia.waitfor (info->save);
            dia.setcontext ("main.book");
            SSTRING txt;
            dia.newf_textarea (NULL,txt,40,5);
            int nof = 0;
            while (1){
                MENU_STATUS code = dia.edit (info->path.get(),"",help_nil,nof,0);
                if (code == MENU_ESCAPE || code == MENU_CANCEL){
                    break;
                }else if (code == MENU_MESSAGE){
                    if (dialog_testmessage (info->quit)){
                        delete info;
                        break;
                    }else if (dialog_testmessage (info->reload)){
                        txt.setfromf ("This is Document %s\n",info->path.get());
                        dia.reload();
                    }else if (dialog_testmessage(info->save)){
                        xconf_notice ("Saving %s\n",info->path.get());
                    }
                }
            }
        }
        
        static void sample_adddocument (
            PAGE_INFO *info[],
            int &nbdocument,
            int &alloc)
        {
            char tmp[100];
            sprintf (tmp,"/tmp/document-%d",alloc++);
            PAGE_INFO *page = new PAGE_INFO (tmp);
            info[nbdocument++] = page;
            uithread (sample_pagedocument,page);
        }
        
        static void sample_pagefocus()
        {
            diagui_sendcmd (P_MainForm,"main\n");
        
            // Create a small menu bar
        
            diagui_sendcmd (P_Menubar,"\n");
            diagui_sendcmd (P_Submenu,"File\n");
            diagui_sendcmd (P_Menuentry,"1 open\n");
            diagui_sendcmd (P_Menuentry,"2 reload\n");
            diagui_sendcmd (P_Menuentry,"3 save\n");
            diagui_sendcmd (P_Menuentry,"4 close document\n");
            diagui_sendcmd (P_Menuentry,"5 quit\n");
            diagui_sendcmd (P_End,"\n");
            diagui_sendcmd (P_End,"\n");
        
            diagui_sendcmd (P_Book,"book $focus=B200\n");
            diagui_sendcmd (P_End,"\n");
        
            // This is the array which will link page number to document
            PAGE_INFO *info[10];
            int nbdocument = 0;
            int alloc = 0;      // Use to format the document name /tmp/document-%d
            // Create 3 dummy documents
            sample_adddocument (info,nbdocument,alloc);
            sample_adddocument (info,nbdocument,alloc);
            sample_adddocument (info,nbdocument,alloc);
            diagui_sendcmd (P_End,"\n");
            int document = 0;
            while (1){
                SSTRING path,action,menu;
                int button = diagui_sync ("main",path,action,menu);
                // fprintf (stderr,"sync %d :%s: :%s: :%s:\n",button,path.get(),action.get(),menu.get());
                if (menu.is_filled()){
                    int id = menu.getval();
                    if (id == 1){
                        sample_adddocument (info,nbdocument,alloc);
                    }else if (id == 2){
                        dialog_sendmessage (info[document]->reload);
                    }else if (id == 3){
                        dialog_sendmessage (info[document]->save);
                    }else if (id == 4){
                        if (document < nbdocument){
                            dialog_sendmessage (info[document]->quit);
                            // We forget the document
                            memcpy (info+document,info+document+1
                                ,(10-document-1)*sizeof(PAGE_INFO*));
                            nbdocument--;
                        }
                    }else if (id == 5){
                        break;
                    }
                }else if (button == 200){
                    document = atoi(diajava_getextrareport());
                    fprintf (stderr,"The page having focus is now %d\n",document);
                }
            }
            diagui_sendcmd (P_Delete,"main\n");
            diagui_flush();
        }

#Specbeg: dialog / sample code / graphical drawing primitives ([samples.cc,925])

        /*
            This example shows how to use the GUI protocol to produce
            arbitraty drawings in a window
        */
        
        static void sample_variousdraw(
            int offx,
            int offy,
            const char *dcstatus)
        {
            diagui_sendcmd (P_Drawline,"%d %d %d %d $dc=%s\n",offx,offy,1000+offx,1000+offy,dcstatus);
            diagui_sendcmd (P_Drawrect,"%d %d %d %d $dc=%s\n",10+offx,1+offy
                ,30+offx,30+offy,dcstatus);
            diagui_sendcmd (P_Fillrect,"%d %d 30 70 $dc=%s\n",10+offx,40+offy,dcstatus);
            diagui_sendcmd (P_Drawarc,"%d %d %d %d %d %d $dc=%s\n"
                ,50+offx,100+offy,200+offx,100+offy,100+offx,150+offy,dcstatus);
        }
        
        static void sample_drawings()
        {
            if (dialog_mode != DIALOG_GUI){
                xconf_notice ("Only in GUI mode!");
                return;
            }
            // We define a drawing context and some pens, fonts and brushes
            // We see that these definitions are global. Unrelated to any
            // dialogs (forms)
            const char *dcwhite;
            const char *dcstatus;
            {
                const char *fontstatus = guiid_setfont (24,GFONT_ID_MODERN,GFONT_STYLE_SLANT,GFONT_WEIGHT_DEFAULT,false);
                const char *penstatus = guiid_setpen ("blue",1,GPEN_STYLE_SOLID);
                const char *brushstatus = guiid_setbrush ("blue",GBRUSH_STYLE_SOLID);
                dcstatus = guiid_setdc (fontstatus,penstatus,brushstatus);
                const char *penwhite = guiid_setpen ("white");
                const char *brushwhite = guiid_setbrush ("white");
                dcwhite = guiid_setdc (NULL,penwhite,brushwhite);
            }
            // We will create a dialog with two fields and some graphics under
            DIALOG dia;
            SSTRING s1,s2;
            dia.newf_str ("Field1",s1);
            dia.newline();
            dia.newf_str ("Field2",s2);
            dia.newline();
            // Now we create a sub-form and draw in it.
            // Since the form does not contain anything other object
            // we have to force the size
            dia.gui_passthrough (P_Form,"draw $w=200 h=200");
            dia.gui_passthrough (P_Clear,"$dc=%s\n",dcwhite);
            dia.gui_passthrough (P_Drawline,"0 0 1000 1000 $dc=%s\n",dcstatus);
            dia.gui_passthrough (P_Drawrect,"10 1 30 30 $dc=%s\n",dcstatus);
            dia.gui_passthrough (P_Fillrect,"10 40 30 70 $dc=%s\n",dcstatus);
            dia.gui_passthrough (P_Drawarc,"180 10 130 10 150 10 $dc=%s\n",dcstatus);
            dia.gui_passthrough (P_Drawarc,"50 100 200 100 100 150 $dc=%s\n",dcstatus);
            dia.gui_end();
            dia.gui_dispolast (GUI_H_CENTER,2,GUI_V_TOP,1);
            int nof = 0;
            int offx = 0;
            int offy = 0;
            dia.setbutinfo (MENU_USR1,"raise","raise");
            dia.setbutinfo (MENU_USR2,"right","right");
            while (1){
                MENU_STATUS code = dia.edit ("Sample drawing","",help_nil
                    ,nof,MENUBUT_USR1|MENUBUT_USR2|MENUBUT_ACCEPT|MENUBUT_CANCEL);
                if (code == MENU_CANCEL || code == MENU_ESCAPE){
                    break;
                }else if (code == MENU_USR1 || code == MENU_USR2){
                    char tmp[200];
                    dia.setguiname(tmp);
                    diagui_sendcmd (P_Setcontext,"%s.panel.draw\n",tmp);
                    diagui_sendcmd (P_Clear,"$dc=%s\n",dcwhite);
                    if (code == MENU_USR1){
                        offy--;
                    }else{
                        offx++;
                    }
                    sample_variousdraw(offx ,offy,dcstatus);
                    diagui_sendcmd (P_Refresh,"\n");
                    diagui_sendcmd (P_End,"\n");
                }else if (code == MENU_ACCEPT){
                    break;
                }
            }
        }

#Specbeg: dialog / sample code / graphical popup menus ([samples.cc,1013])

        /*
            This shows how to create popup menus
        */
        static void sample_popup(int id)
        {
            DIALOG_MENUPOPUP dia;
            char recordid[15];
            sprintf (recordid,"record %d",id);
            dia.new_menuitem ("create","option1");
            dia.new_menuitem ("create","option2");
            dia.new_menuitem ("","-");      // A separator
            dia.newf_title ("icon1",1,"","Sub-menu3");
            dia.new_menuitem ("create","option3-1");
            dia.new_menuitem ("create","option3-2");
            dia.newf_title ("icon2",1,"","Sub-menu4");
            dia.new_menuitem ("create","option4-1");
            dia.new_menuitem ("create","option4-2");
            dia.newf_title ("","");     // Trick to get back to the first level
            dia.new_menuitem ("create","option5");
            dia.new_menuitem ("create","option6");
            int nof= 0;
            while (1){
                MENU_STATUS code = dia.editmenu (recordid, nof);
                if (code == MENU_QUIT || code == MENU_ESCAPE){
                    break;
                }else{
                    xconf_notice ("You have selected option %d",nof);
                    break;
                }
            }
        }
        
        /*
            Here is a sample dialog presenting records. Clicking on a record
            brings a popup
        */
        static void sample_usepopup()
        {
            DIALOG_RECORDS dia;
            dia.newf_head ("","Name\tPhone\tZip");
            int nof=0;
            while (1){
                for (int i=0; i<5; i++){
                    char tmp[10];
                    sprintf (tmp,"user%d",i+1);
                    dia.set_menuitem (i,tmp,"111-2222\txxx-yyy");
                }
                dia.remove_last (5+1);
                MENU_STATUS code = dia.editmenu ("User list"
                    ,"Click on a record to bring a popup menu"
                    ,help_nil,nof,0);
                if (code == MENU_ESCAPE || code == MENU_QUIT){
                    break;
                }else{
                    sample_popup(nof);
                }
            }
        }

#Specbeg: dialog / sample code / GUI fonts ([samples.cc,1224])

        /*
            The following code presents the various fonts available.
        */
        static void sample_guifonts()
        {
            DIALOG dia;
            for (GFONT_ID id=GFONT_ID_DEFAULT; id <= GFONT_ID_TELETYPE; id=(GFONT_ID)(id+1)){
                for (GFONT_STYLE style=GFONT_STYLE_DEFAULT; style <= GFONT_STYLE_ITALIC; style=(GFONT_STYLE)(style+1)){
                    for (GFONT_WEIGHT weight=GFONT_WEIGHT_DEFAULT; weight <= GFONT_WEIGHT_LIGHT; weight=(GFONT_WEIGHT)(weight+1)){
                        const char *font = guiid_setfont (20,id,style,weight,false);
                        const char *dc = guiid_setdc (font,NULL,NULL);
                        dia.gui_label ("\"text %d %d %d\" $dc=%s",id,style,weight,dc);
                        dia.newline();
                    }
                }
            }
            int nof = 0;
            dia.edit("GUI fonts","",help_nil,nof);
        }

#Specbeg: dialog / sample code / inserting fields ([samples.cc,1597])

        /*
            It is possible to insert new fields in a running dialog.
        */
        
        static void sample_insert_fields()
        {
            DIALOG dia;
            SSTRING f1,f2,f3,other;
            dia.newf_str ("Field 1",f1);
            dia.newf_str ("Field 2",f2);
            dia.newf_title ("","new fields go here");
            int insert_pos = dia.getnb();
            int original_pos = insert_pos;
            dia.newf_title ("","Other fields");
            dia.newf_str ("last field",f3);
            dia.setbutinfo (MENU_USR1,"more fields","more fields");
            dia.setbutinfo (MENU_USR2,"remove fields","remove fields");
            int nof = 0;
            while (1){
                MENU_STATUS code = dia.edit ("insert fields"
                    ,"You can insert and remove fields",help_nil
                    ,nof
                    ,MENUBUT_CANCEL|MENUBUT_ACCEPT|MENUBUT_USR1|MENUBUT_USR2);
                if (code == MENU_ESCAPE || code == MENU_CANCEL){
                    break;
                }else if (code == MENU_USR1){
                    dia.set_nextfield (insert_pos++);
                    // We are using the same edit variable, this is a sample only
                    dia.newf_str ("Another field",other);
                }else if (code == MENU_USR2){
                    if (insert_pos > original_pos){
                        dia.remove_del (--insert_pos);
                    }
                }else if (code == MENU_ACCEPT){
                    break;
                }
            }
        }

#Specbeg: dialog / sample code / private messaging ([samples.cc,129])

        /*
            This creates 4 dialogs, grouped two by two. Each dialog
            may send a message to its corresponding dialog. The two
            pairs are independant. Yet they are using exactly the same
            code. This shows how PRIVATE_MESSAGE may be used to
            achieve private communication.
        */
        
        struct PMSGS{
            PRIVATE_MESSAGE p1;
            PRIVATE_MESSAGE p2;
        };
        
        static void sample_fct1 (void *data)
        {
            PMSGS *p = (PMSGS*)data;
            sample_dialog (p->p2,p->p1);
        }
        static void sample_fct2 (void *data)
        {
            PMSGS *p = (PMSGS*)data;
            sample_dialog (p->p1,p->p2);
        }
        /*
            This function creates two independant dialog which are exchanging
            messages.
        */
        static void sample_privates()
        {
            PMSGS group1;
            PMSGS group2;
            uithread (sample_fct1,&group1);
            uithread (sample_fct2,&group1);
            uithread (sample_fct1,&group2);
            sample_dialog (group2.p1,group2.p2);
        }

#Specbeg: dialog / sample code / tuning field GUI look ([samples.cc,1196])

        /*
            Using the DIALOG::set_guiparms, you can pass extra GUI parameters.
            You must know which parameters apply to which widget. This
            is documented (or will be :-) ) in ../doc/guiapi.sgml.
        
            The current example shows how you can affect the look
            of richtext field
        */
        
        static void sample_guiparms()
        {
            DIALOG_TEXTBOX dia;
            static const char *dcstatus = NULL;
            if (dcstatus == NULL){
                const char *fontstatus = guiid_setfont (24,GFONT_ID_MODERN,GFONT_STYLE_SLANT,GFONT_WEIGHT_BOLD,false);
                const char *penstatus = guiid_setpen ("blue");
                const char *brushstatus = guiid_setbrush ("blue");
                dcstatus = guiid_setdc (fontstatus,penstatus,brushstatus);
            }
            dia.newf_text ("","Using blue font");
            dia.set_guiparms ("dc=%s",dcstatus);
            dia.newf_text ("","Using normal font");
            dia.newf_text ("","");
            dia.edit ("guiparms test","",help_nil);
        }

#Specbeg: dialog / sample code / using auto_newline ([samples.cc,646])

        /*
            We can invest a little in the graphical appearance of a dialog
            by using DIALOG::newline() and the various DIALOG::gui_passthrough()
            (DIALOG::gui_label,DIALOG::gui_end,DIALOG::gui_group,
            DIALOG::gui_dispolast). But once you use any of those, the layout
            manager turn of its auto-newline feature. Now, if it was just to
            enhance one page of a complex notebook dialog, this is annoying.
            Having to insert DIALOG::newline() everywhere is a little
            too much work.
        
            The function DIALOG::auto_newline() lets you control the layout
            manager. If you use this function only once in a dialog, the layout
            manager will listen. If you use it only once in a dialog, the layout
            manager start in auto-newline mode and let you switch it off and
            on.
        
            DIALOG::auto_newline() inserts a dummy field in the dialog, so
            you may have to deal with that if you expect to jump to a specific
            field.
        
            DIALOG::auto_newline() has no effect in text or HTML mode.
        */
        static void sample_autonewline()
        {
            // Auto-newline is on by default if there is no DIALOG::newline
            // or DIALOG::gui, or if DIALOG::auto_newline() is used once.
            DIALOG dia;
            dia.newf_title ("simple page",1,"","simple page");
            SSTRING s;
            dia.newf_str ("field 1",s);
            dia.newf_str ("field 2",s);
            dia.newf_str ("field 3",s);
            dia.newf_str ("field 4",s);
            dia.newf_title ("complex page",1,"","complex page");
            // Ok, now the complex layout, we turn off auto-layout
            dia.auto_newline(false);
            dia.gui_label("");
            dia.gui_label("Column1");
            dia.gui_label("Column2");
            dia.gui_label("Column3");
            dia.newline();
            for (int i=0; i<4; i++){
                dia.gui_label ("Row%d",i+1);
                for (int j=0; j<3; j++){
                    dia.newf_str (NULL,s,10);
                }
                dia.newline();
            }
            // Back to auto-newline mode
            dia.auto_newline(true);
            dia.newf_title ("simple page",1,"","simple page");
            dia.newf_str ("field 1",s);
            dia.newf_str ("field 2",s);
            dia.newf_str ("field 3",s);
            dia.newf_str ("field 4",s);
            int nof = 0;
            while (1){
                MENU_STATUS code = dia.edit ("Using auto-newline"
                    ,"The first page use a simple layout\n"
                     "but the second is present as a grid"
                    ,help_nil,nof);
                if (code == MENU_ESCAPE || code == MENU_CANCEL){
                    break;
                }else{
                    break;
                }
            }
        }

#Specbeg: dialog / sample code / using clist ([samples.cc,1247])

        /*
            Using the DIALOG::newf_clist, one can create dialog with multiple
            clist and fields as needed. This is unlike the DIALOG_LISTE and
            DIALOG_RECORDS which are dealing with a single list dialog (no extra
            fields).
        
            At this point, the FIELD_CLIST is only debugged for the GUI mode, but
            could work in text mode at some point. It probably won't work in HTML
            mode.
        */
        
        static void sample_clist()
        {
            DIALOG dia;
            PRIVATE_MESSAGE msg1;
            int sel1 = 0;
            FIELD_CLIST *clist1 = dia.newf_clist ("",10,msg1,sel1);
            clist1->setheader ("col1\tcol2");
            for (int i=0; i<20; i++){
                clist1->setrecordf (i,"liste1\tline%d",i);
            }
            SSTRING f;
            dia.newf_str ("Field1",f);
            PRIVATE_MESSAGE msg2;
            int sel2 = 0;
            FIELD_CLIST *clist2 = dia.newf_clist ("",10,msg2,sel2);
            clist2->setheader ("col1\tcol2");
            for (int i=0; i<20; i++){
                clist2->setrecordf (i,"liste2\tline%d",i);
            }
            dia.newline();
            int nof = 0;
            while (1){
                MENU_STATUS code = dia.edit ("2 clists","",help_nil,nof);
                if (code == MENU_CANCEL || code == MENU_ESCAPE){
                    break;
                }else if (code == MENU_MESSAGE){
                    if (dialog_testmessage (msg1)){
                        f.setfromf ("liste1,line%d",sel1);
                    }else if (dialog_testmessage (msg2)){
                        f.setfromf ("liste2,line%d",sel2);
                    }else{
                        fprintf (stderr,"Unknown message %d %d %d\n",nof,sel1,sel2);
                    }
                    dia.reload();
                }else{
                    f.setfromf ("ok %d",nof);
                    dia.reload();
                }
            }
        }

#Specbeg: dialog / sample code / using inputgrid ([samples.cc,1301])

        /*
            The inputgrid facility only works in GUI mode. It allows the application
            to capture mouse selection in a form. You define a grid and any
            mouse selection (click) within that grid will be reported like a button
            using a PRIVATE_MESSAGE.
        
            Using the $track=1 extra parameter, one can also follow the mouse
            movement over a grid. The following code will show both.
        */
        static void sample_inputgrid()
        {
            DIALOG dia;
            dia.set_formparms ("w=400 h=200");
            // First we draw two grid. Not useful for the input grid, only
            // to show what is going on.
            for (int i=0; i<2; i++){
                int startx = i*200+10;
                int endx = i*200+110;
                int starty = 10;
                int endy = 110;
                for (int j=0; j<6; j++){
                    int x = startx + j*20;
                    int y = starty + j*20;
                    dia.gui_passthrough (P_Drawline,"%d %d %d %d",x,starty,x,endy);
                    dia.gui_passthrough (P_Drawline,"%d %d %d %d",startx,y,endx,y);
                }
            }
            PRIVATE_MESSAGE msg1,msg2;
            dia.new_inputgrid (10,10,20,20,5,5,msg1);
            dia.new_inputgrid (210,10,20,20,5,5,msg2);
            dia.set_guiparms ("track=1");
            int nof = 0;
            while (1){
                MENU_STATUS code = dia.edit ("2 inputgrid","",help_nil,nof);
                if (code == MENU_CANCEL || code == MENU_ESCAPE){
                    break;
                }else if (code == MENU_MESSAGE){
                    UISTATE st;
                    diajava_lastmousestate(st);
                    if (dialog_testmessage (msg1)){
                        fprintf (stderr,"click in left area, cell %d,%d\n"
                            ,(st.x-10)/20,(st.y-10)/20);
                    }else if (dialog_testmessage (msg2)){
                        if (st.leftb || st.middleb || st.rightb){
                            fprintf (stderr,"click in right area, cell %d,%d\n"
                                ,(st.x-210)/20,(st.y-10)/20);
                        }else if (st.x < 210 || st.x >= 310
                            || st.y < 10 || st.y >= 110){
                            fprintf (stderr,"Leaving right area\n");
                        }else{
                            fprintf (stderr,"Entering in right area, cell %d,%d\n"
                                ,(st.x-210)/20,(st.y-10)/20);
                        }
                    }
                }
            }
        }

#Specbeg: dialog / sample code / using request_dump ([samples.cc,1361])

        /*
            This is specific to GUI mode.
        
            In general, dialog operates in standalone mode until the use hit
            one of their button. At this point, the form content is sent to the
            application. Sometime, you have to independant dialog which are
            tied logically. An action in one may request some test on the current
            content of the fields in the other dialog. The only way to know
            the current content of those fields is to request the GUI front-end
            to perform a dump of the dialog, even if the user has not hit
            any button.
        
            The following example present a small text editor with a companion
            control dialog. Each time one hit the "add" button in the control
            dialog, one line is added to the text editor. Yet, the current
            content of the text has to be preserved.
        */
        struct EDIT_INFO{
            PRIVATE_MESSAGE msg;
            SSTRING line;
        };
        
        static void sample_texteditor(void *data)
        {
            EDIT_INFO *info = (EDIT_INFO*)data;
            DIALOG dia;
            dia.waitfor (info->msg);
            SSTRING txt;
            dia.newf_textarea (NULL,txt,70,25);
            int nof = 0;
            while (1){
                MENU_STATUS code = dia.edit ("Small editor"
                    ,"Type something here\n"
                     "Do not hit any button in this dialog\n"
                     "Then hit a button in the editor control"
                    ,help_nil,nof);
                if (code == MENU_ESCAPE || code == MENU_CANCEL){
                    break;
                }else if (code == MENU_DUMP){
                    dia.save();
                    txt.appendf ("%s\n",info->line.get());
                    dia.reload();
                }else if (code == MENU_MESSAGE){
                    if (dialog_testmessage(info->msg)){
                        dia.request_dump();
                    }
                }else if (code == MENU_ACCEPT){
                    xconf_notice ("The text is now\n%s",txt.get());
                }
            }
        }
        
        static void sample_dump()
        {
            if (dialog_mode != DIALOG_GUI){
                xconf_notice ("Only in GUI mode!");
                return;
            }
        
            EDIT_INFO info;
            uithread (sample_texteditor,&info);
            DIALOG dia;
            dia.newf_str ("New text line",info.line);
            int nof = 0;
            while (1){
                MENU_STATUS code = dia.edit ("Editor control","",help_nil,nof);
                if (code == MENU_ESCAPE || code == MENU_CANCEL){
                    break;
                }else if (code == MENU_ACCEPT){
                    dialog_sendmessage (info.msg);
                }
            }
        }

#Specbeg: dialog / sample code / using the tree menu ([samples.cc,894])

        static void sample_tree()
        {
            if (dialog_mode != DIALOG_GUI){
                xconf_notice ("Only in GUI mode");
            }else{
                DIALOG dia;
                dia.gui_passthrough(P_Treemenu,"tree $mode=1");
                for (int i=0; i<3; i++){
                    dia.gui_passthrough (P_Treesub,"1 \"\" dir%d",i);
                    for (int j=0; j<4; j++){
                        dia.gui_passthrough (P_Treeelem,"\"\" file-%d-%d",i,j);
                    }
                    dia.gui_end();
                }
                dia.gui_end();
                int nof = 0;
                while (1){
                    MENU_STATUS code = dia.edit ("A tree","",help_nil,nof);
                    if (code == MENU_CANCEL || code == MENU_ESCAPE){
                        break;
                    }else if (code == MENU_OK){
                        xconf_notice ("ok :%s:",diagui_getlast_actionid());
                    }
                }
            }
        }

#Specbeg: dialog / sample code / using the virtual registry ([samples.cc,766])

        /*
            The virtual registry allows one part of an application to export
            some variables to other parts of the application, potentially
            accross shared object (which can't share C++ variable).
        
            Setting and getting values with the registry is very easy.
            The registry is mapped on the user interface. This is especially
            useful for Linuxconf since it needs all kinds of dialog to manipulate
            the various configuration. Reusing this code is the key. As you
            will see below, publishing variables tied to a dialog is simple.
        
            In the following example, we present a small dialog which edits
            two field of a configuration file /tmp/sample.data. This
            configuration file is a shell like file. It may look like
        
            <sgml>
            <tscreen><verb>
                # Some comments
                var1="Some value"
                # more comments
                other_variable="value"
                var2="value"
            </verb></tscreen>
            </sgml>
        
            The small dialog will only touch var1 and var2 and preserve the comments
            and the ordering of sample.data. This is really the goal of linuxconf
            to provide some user interface for various configuration file. The
            bulk of the code goes like that
        
            <sgml>
            <itemize>
            <item>Parsing the configuration files.
            <item>Presenting the dialog.
            <item>Updating the configuration file.
            </itemize>
            </sgml>
        
            Sometime, it is done using several functions, various C++ classes.
            It can be complex. The goal of the registry is to be able to reuse
            this functionality
        
        */
        static void sample_registrydialog()
        {
            DIALOG dia;
            SSTRING s1,s2;
            CONFIG_FILE f_config ("/tmp/sample.data",help_nil,CONFIGF_OPTIONAL);
            VIEWITEMS items;
            items.read (f_config);
            char tmp[1000];
            s1.setfrom (items.locateval ("var1",tmp));
            s2.setfrom (items.locateval ("var2",tmp));
            dia.newf_str (MSG_U(F_SAMPLEFIELD1,"Sample field1"),s1);
            dia.newf_str (MSG_U(F_SAMPLEFIELD2,"Sample field2"),s2);
            int nof = 0;
            while (1){
                MENU_STATUS code = dia.edit ("Sample data","",help_nil,nof);
                if (code == MENU_ESCAPE || code == MENU_CANCEL){
                    break;
                }else{
                    // We must update the file
                    items.update ("var1",s1);
                    items.update ("var2",s2);
                    items.write (f_config,NULL);
                    break;
                }
            }
        }
        
        /*
            Here we publish the two variables. As you can see, this is
            very simple. Each variable (var1, var2) is associated with
            a dialog ID (NULL here and most of the time), a field prompt
            and a trigger function. This function simply calls the dialog
        */
        #include "modregister.h"
        
        static REGISTER_VARIABLE_LOOKUP_MSG sample_var_list1[]={
            {"var1",NULL,P_MSG_R(F_SAMPLEFIELD1),sample_registrydialog,NULL},
            {"var2",NULL,P_MSG_R(F_SAMPLEFIELD2),sample_registrydialog,NULL},
            { NULL }
        };
        
        static REGISTER_VARIABLES sample_registry1("sample",sample_var_list1);
        
        /*
            This function perform the following sequence
        
            <sgml>
            <itemize>
            <item>Execute the dialog so you look at it.
            <item>Execute it again so you can  see that your input was saved.
            <item>Retrieve var1 and var2 using the registry.
            <item>Setting var1 and var2 to new values using the registry.
            <item>Execute the dialog so you can see the new values.
            </itemize>
            </sgml>
        */
        static void sample_useregistry()
        {
            xconf_notice ("Ok, we call the dialog to edit /tmp/sample.data\n"
                "Enter some values and accept");
            sample_registrydialog();
            xconf_notice ("We call it again to see if the data was saved properly");
            sample_registrydialog();
            // Now we will retrieve the values
            master_registry.start_session();
            SSTRING v1 (master_registry.get("sample.var1"));
            SSTRING v2 (master_registry.get("sample.var2"));
            master_registry.end_session();
            xconf_notice ("Using the virtual registry, we get\n"
                "var1=%s\n"
                "var2=%s\n"
                "\n"
                "We will put new values now"
                ,v1.get(),v2.get());
            master_registry.start_session();
            master_registry.set("sample.var1","New value for var1");
            master_registry.set("sample.var2","New value for var2");
            master_registry.end_session();
            xconf_notice ("Ok, the values are updated, now we visit the dialog again");
            sample_registrydialog();
        }

3.23 Miscellaneous specifications

#Specification: GUI / layout / first section ([diagui.cc,642])

It is possible to create a dialog with a first visible section followed by a notebook containing "less" visible sections (options). This is done by putting DIALOG::newf_title() calls after the first section. We can also do a normal notebook dialog where all sections (pages) are selectable with the page tab. This is done by starting the dialog with DIALOG::newf_title (Each page starts with this). For the first case (When the dialog does not start with the newf_title() function), linuxconf puts the first section inside of a Form centered horizontally in the dialog.

#Specification: FIELD_SHEET / principle ([sheet.cc,1])

A FIELD_SHEET is an array of text field. It may have several lines and columns. It may also have a "grow" button at the bottom allowing user to enlarge the array (add more lines). Further, it has a title line, one title for each column.

#Specification: F3 key / escape ([term.cc,70])

F3 was not used in Linuxconf and some people claims it is fairly common as an equivalent for the escape key. So linuxconf support both! Comments are welcome.

#Specification: dialogue / single field case ([term.cc,113])

A <enter> in a dialogue with a single field exit as the first button was selected (Generally <Accept>) If the dialog contain a <Ok> button it does the same whatever the number of fields. This is a special case for menu dialog.

#Specification: dialog / colors / xterm ([diaetc.cc,129])

Linuxconf running on color xterm is difficult to use (read). So it default to back & white

#Specification: curse mode / no tty available ([diaetc.cc,60])

If handle 0 is not a tty, linuxconf will print an error message and quit. It will try to print it trying the following list

            Message sent to handle 2 if it is a tty.
            Message sent to handle 1 if it is a tty.
            Message sent to /dev/console if it can be opened.
            Message sent to /dev/tty1 if it can be opened.

#Specification: CMDSOCK / via inetd ([cmdsock.cc,134])

A server using CMDSOCK can be started with inetd. We specify -1 as the port number CMDSOCK will then use the handle 0 to wait for connections.

#Specification: textbox / underlining and highlit ([textbox.cc,64])

Text may contain underlining commands and highliting commands built using the backspace character.

        Highlit  : X^HX
        Underline: X^H_

#Specbeg: MENU_CONTEXT ([dialog_def.h,14])

        enum MENU_CONTEXT{
            MENU_UNKNOWN,       // Special menu which is unknown to modules
                                // or is not yet module aware
            MENU_NETWORK_CLIENT,// Client section of the network menu
            MENU_NETWORK_SERVER,// Server section of the network menu
            MENU_NETWORK_MISC,  // Misc section of the network menu
            MENU_NETWORK_BOOT,  // Boot services section of the network menu
            MENU_MAIN_CONFIG,   // Config section of the main linuxconf menu
            MENU_MAIN_CONTROL,  // Control section of the main linuxconf menu
            MENU_CTRL_FILE,     // Control file and systems
            MENU_USER_STD,      // Standard user account section
            MENU_USER_SPC,      // Special user account section
            MENU_USER_POLICIES, // Policies in user account section
            MENU_USER_ALIAS,    // Aliases in user account section
            MENU_CONTROL_PANEL, // Control panel
            MENU_LOGS,          // All the logs
            MENU_NETWORK_FIREWALL,  // Firewalling
            MENU_SYS_STATUS,        // System status
            MENU_MAILCONF,      // Basic section of the mailconf main menu
            MENU_MISCSERV,      // Misc services menu
            MENU_FSCONF,        // File system configuration
            MENU_MAIN_STATUS,   // status section of the main linuxconf menu
            MENU_GURUS,         // Helpers to configure from scratch common
                                // services (or set of services)
            MENU_BOOT,          // Boot mode and configuration
            MENU_HARDWARE,      // Peripheral section of the main menu
        };

3.24 Internal functions


Next Previous Contents