Next: Text Widget Up: Motif Tutorial Previous: Application Twotic-tac-toe

Application Three, editors

One of the places where you can truly appreciate the ease of Motif programming is in the area of editors. With Motif, a very nice, extremely easy to use, point and click editor is just a widget away. The following code demonstrates:


        #include <X11/Intrinsic.h>
        #include <Xm/Xm.h>
        #include <Xm/Text.h>

        Widget toplevel, text;

        main(int argc, char *argv[])
        {
          Arg al[20];
          int ac;

          toplevel=XtInitialize(argv[0],"",NULL,0,&argc,argv);

          /* set the default size of the window. */ 
          ac=0;
          XtSetArg(al[ac],XmNwidth,200); ac++;
          XtSetArg(al[ac],XmNheight,200); ac++;
          XtSetValues(toplevel,al,ac);
          
          /* create a text widget */
          ac=0;
          XtSetArg(al[ac],XmNeditMode,XmMULTI_LINE_EDIT); ac++;
          text=XtCreateManagedWidget("text",xmTextWidgetClass,
                toplevel,al,ac);

          XtRealizeWidget(toplevel);
          XtMainLoop();
        }

Get this code compiled and running. You can type. You can use the arrow keys. You can use the backspace key. You can even select a region and press the backspace key to delete the region. You can click on an arbitrary insertion point with the mouse and insert text. If you type a line longer than the window can hold, the text will scroll automatically. What more could you ask for?

Well actually, you could ask for scroll bars. So motif provides a scrollable text widget, which combines a scrolled window widget with a text widget. It is created with a convenience function, as shown below:


        #include <X11/Intrinsic.h>
        #include <Xm/Xm.h>
        #include <Xm/Text.h>

        Widget toplevel, text;

        main(int argc, char *argv[])
        {
          Arg al[20];
          int ac;

          toplevel=XtInitialize(argv[0],"",NULL,0,&argc,argv);

          /* set the default size of the window. */ 
          ac=0;
          XtSetArg(al[ac],XmNwidth,200); ac++;
          XtSetArg(al[ac],XmNheight,200); ac++;
          XtSetValues(toplevel,al,ac);
          
          /* create a text widget */
          ac=0;
          XtSetArg(al[ac],XmNeditMode,XmMULTI_LINE_EDIT); ac++;
          text=XmCreateScrolledText(toplevel, "text", al, ac);
          XtManageChild(text);

          XtRealizeWidget(toplevel);
          XtMainLoop();
        }

Obviously, given a widget that does everything for you, it isn't going to be that difficult to create a complete text editor. This is especially true since Motif provides a bunch of convenience routines for working with text widgets. What we'll do is slap together a menu bar with a scrolled text widget, and create an editor. Here's the code:


        #include <stdio.h>
        #include <sys/types.h>
        #include <sys/stat.h>
        #include <X11/Intrinsic.h>
        #include <Xm/Xm.h>
        #include <Xm/Text.h>
        #include <Xm/Form.h>
        #include <Xm/PushB.h>
        #include <Xm/RowColumn.h>
        #include <Xm/CascadeB.h>
        #include <Xm/FileSB.h>

        #define MENU_OPEN       1
        #define MENU_SAVE       2
        #define MENU_QUIT       3

        #define OK              1
        #define CANCEL          2

        Widget toplevel, text;
        Widget open_option, save_option, quit_option;

        Widget open_dialog;
        char *filename=NULL;

        void change_sensitivity(Boolean open_state)
        /* changes the menu sensitivities between open and save state. */
        {
          XtSetSensitive(open_option,open_state);
          XtSetSensitive(save_option,!open_state);
        }

        void openCB(Widget w,
                   int client_data,
                   XmAnyCallbackStruct *call_data)
        /* handles the file selection box callback. */
        {
          XmFileSelectionBoxCallbackStruct *s =
                (XmFileSelectionBoxCallbackStruct *) call_data;
          FILE *f;
          char *file_contents;
          int file_length;
          struct stat stat_val;
          
          if (client_data==CANCEL) /* do nothing if cancel is selected. */
          {
            XtUnmanageChild(open_dialog);
            return;
          }

          if (filename != NULL) /* free up filename if it exists. */
          {
            XtFree(filename);
            filename = NULL;
          }

          /* get the filename from the file selection box */
          XmStringGetLtoR(s->value, XmSTRING_DEFAULT_CHARSET, &filename);

          /* open and read the file. */
          if (stat(filename, &stat_val) == 0)
          {
            file_length = stat_val.st_size;
            if ((f=fopen(filename,"r"))!=NULL)
            {
              /* malloc a place for the string to be read to. */
              file_contents = (char *) XtMalloc((unsigned)file_length);
              *file_contents = '\0';
              /* read the file string */
              fread(file_contents, sizeof(char), file_length, f);
              fclose(f);
              /* give the string to the text widget. */
              XmTextSetString(text, file_contents);
              XtFree(file_contents);
              change_sensitivity(False);
              XtSetSensitive(text,True);
              XmTextSetEditable(text,True);
              XmTextSetInsertionPosition(text,1);
            }
          }
          XtUnmanageChild(open_dialog);
        }

        void handle_save()
        /* saves the edit widget's string to a file. */
        {
          FILE *f;
          char *s=NULL;
          
          if ((f=fopen(filename,"w"))!=NULL)
          {
            /* get the string from the text widget */
            s = (char *)XmTextGetString(text);
            if (s!=NULL) 
            {
              /* write the file. */
              fwrite(s, sizeof(char), strlen(s), f);
              /* make sure the last line is terminated by '\n' 
                 so that vi, compilers, etc. like it. */
              if (s[strlen(s)-1]!='\n')
                 fprintf(f,"\n");
              XtFree(s); 
            }
            fflush(f);
            fclose(f);
            XtSetSensitive(text,False);
            XmTextSetEditable(text,False);
            XmTextSetString(text,"");
            change_sensitivity(True);
          }
        }

        void menuCB(Widget w,
                   int client_data,
                   XmAnyCallbackStruct *call_data)
        /* handles menu options. */
        {
                switch (client_data)
                {
                        case MENU_OPEN:
                                /* make the file selection box appear. */
                                XtManageChild(open_dialog);
                                break;
                        case MENU_SAVE:
                                handle_save();
                                break;
                        case MENU_QUIT:
                                exit(0);
                }
        }

        Widget make_menu_option(char *option_name,int client_data,Widget menu)
        /* see tutorial 6. */
        {
          int ac;
          Arg al[10];
          Widget b;

          ac = 0;
          XtSetArg(al[ac], XmNlabelString,
                XmStringCreateLtoR(option_name,
                XmSTRING_DEFAULT_CHARSET)); ac++;
          b=XtCreateManagedWidget(option_name,xmPushButtonWidgetClass,
                menu,al,ac);
          XtAddCallback (b, XmNactivateCallback, menuCB, client_data);
          return(b);
        }

        Widget make_menu(char *menu_name, Widget menu_bar)
        /* see tutorial 6. */
        {
          int ac;
          Arg al[10];
          Widget menu, cascade;

          ac = 0;
          menu = XmCreatePulldownMenu (menu_bar, menu_name, al, ac);

          ac = 0;
          XtSetArg (al[ac], XmNsubMenuId, menu);  ac++;
          XtSetArg(al[ac], XmNlabelString,
                XmStringCreateLtoR(menu_name, XmSTRING_DEFAULT_CHARSET)); ac++;
          cascade = XmCreateCascadeButton (menu_bar, menu_name, al, ac);
          XtManageChild (cascade); 

          return(menu);
        }

        void create_menus(Widget menu_bar)
        {
          int ac;
          Arg al[10];
          Widget menu;

          menu=make_menu("File",menu_bar);
          open_option = make_menu_option("Open",MENU_OPEN,menu);
          save_option = make_menu_option("Save",MENU_SAVE,menu);
          XtSetSensitive(save_option,False);
          quit_option = make_menu_option("Quit",MENU_QUIT,menu);
        }

        main(int argc, char *argv[])
        {
          Widget toplevel, form, label, menu_bar;
          Arg al[10];
          int ac;

          toplevel=XtInitialize(argv[0],"",NULL,0,&argc,argv);

          /* default window size. */
          ac=0;
          XtSetArg(al[ac],XmNheight,200); ac++;
          XtSetArg(al[ac],XmNwidth,200); ac++;
          XtSetValues(toplevel,al,ac);

          /* create a form widget. */
          ac=0;
          form=XtCreateManagedWidget("form",xmFormWidgetClass,
                toplevel,al,ac);

          /* create a menu bar and attach it to the form. */
          ac=0;
          XtSetArg(al[ac], XmNtopAttachment,    XmATTACH_FORM); ac++;
          XtSetArg(al[ac], XmNrightAttachment, XmATTACH_FORM); ac++;
          XtSetArg(al[ac], XmNleftAttachment,   XmATTACH_FORM); ac++;
          menu_bar=XmCreateMenuBar(form,"menu_bar",al,ac);
          XtManageChild(menu_bar);

          /* create a text widget and attach it to the form. */
          ac=0;
          XtSetArg(al[ac], XmNtopAttachment,    XmATTACH_WIDGET); ac++;
          XtSetArg(al[ac], XmNtopWidget, menu_bar); ac++;
          XtSetArg(al[ac], XmNrightAttachment, XmATTACH_FORM); ac++;
          XtSetArg(al[ac], XmNleftAttachment,   XmATTACH_FORM); ac++;
          XtSetArg(al[ac], XmNbottomAttachment,   XmATTACH_FORM); ac++;
          XtSetArg(al[ac],XmNeditMode,XmMULTI_LINE_EDIT); ac++;
          text=XmCreateScrolledText(form, "text", al, ac);
          XtManageChild(text);
          XtSetSensitive(text,False);
          XmTextSetEditable(text,False);

          create_menus(menu_bar);

          /* create the file selection box used by open option. */
          ac=0;
          XtSetArg(al[ac],XmNmustMatch,True); ac++;
          XtSetArg(al[ac],XmNautoUnmanage,False); ac++;
          open_dialog=XmCreateFileSelectionDialog(toplevel,"open_dialog",al,ac);
          XtAddCallback (open_dialog, XmNokCallback,
                openCB, OK);
          XtAddCallback (open_dialog, XmNcancelCallback,
                openCB, CANCEL);
          XtUnmanageChild(XmSelectionBoxGetChild(open_dialog,
                XmDIALOG_HELP_BUTTON));

          XtRealizeWidget(toplevel);
          XtMainLoop();
        }

This is a faily long piece of code, but don't be intimidated! It is very straightforward.

This code consists of the menu bar from tutorial six, along with a scrollable text widget, both of which have been attached to a form. Note that the attachments in the main procedure for both the menu bar and the text widget are specified AT CREATION. The text widget in particular must have it's attachments specified here or it won't attach correctly. The menu code has been set up so that the client_datatype is an integer, but other than that it's the same as in tutorial six.

The main procedure also creates one new widget, called a "file selection box". This widget gives users an easy way to select files. It activates a callback once the user selects a file, and the file name selected by the user is extracted in a manner identical to that used on a selection box.

The openCB callback routine handles the file selection box callback. It extracts the file name, reads the file into a character array, and then gives this string to the text widget. You can see from this that the text widget thinks of the document being edited as a big array of characters. The reverse is done to save a file: the character array is copied from the text widget and placed in a file. Both openCB and handle_saveare careful to enable and disable the text widget as needed so that the user cannot edit if there is nothing to be edited.

This obviously isn't a complete editor, but it does demonstrate how generally easy it is to create a lot of capability with a little bit of code. For more info on editors, OSF has a good demo program that you should investigate (called xmeditor). You should also peruse some literature that tells you about all of the convenience functions available for working on text widgets.

[Note: on my version of Motif, this code will not display the insertion point cursor on the second or subsequent calls to the "open" menu option. The cursor can be made visible by selecting the File menu but then selecting no option. I don't know if this is a bug or not, but I'm fairly certain that it is.]

[Note: xmeditor, at least the version I had, contains a bunch of very subtle bugs. Don't take it as gospel, but it is a good source for ideas.]




Next: Text Widget Up: Motif Tutorial Previous: Application Twotic-tac-toe


morbe@enstb.enst-bretagne.fr