Listing 1

/* xtxtvw.c

   X11/Motif based text file viewer - example program meant to accompany
   Linux Journal article on Motif development

   the following command should build the application on most linux systems

   gcc -o xtxtvw xtxtvw.c -I/usr/X11R6/include -L/usr/X11R6/lib -lXm -lXt -lX11

   the following command should build the application on many Solaris systems

   gcc -o xtxtvw xtxtvw.c -I/usr/dt/include -L/usr/openwin/lib -lXm -lXt -lX11

*/

#include <stdlib.h>
#include <unistd.h>
#include <X11/Xmu/Editres.h>
#include <Xm/FileSB.h>
#include <Xm/Form.h>
#include <Xm/LabelG.h>
#include <Xm/PushB.h>
#include <Xm/RowColumn.h> /* for the menu widget */
#include <Xm/SelectioB.h>
#include <Xm/Text.h>
#include <Xm/TextF.h>

/* the class is used to identify application resources */
const char *X_APP_CLASS = "XTxtVw";
/* reasonable default values for some essential resources */
const String FALLBACKRES[] =
{
     "*background:   grey"
   , "*forground:    black"
   /* don't use * here otherwise all widgets will have these dimensions */
   , "XTxtVw.width:  400"
   , "XTxtVw.height: 400"
   , NULL
};

/* most applications have only 1 top level shell */
Widget g_topshell;

/* the file selection dialog is invoked from multiple routines
   so it is easiest to make the widget global */
Widget g_filedlg;

/* we use the widget to keep the file name */
Widget g_filenamew;

/* we would like to alter the text display from many routines so... */
Widget g_filetextw;

/* callback routine names are suffixed with CB by convention */
void fileCB(Widget widget, XtPointer client_data, XtPointer call_data);
void readfileCB(Widget widget, XtPointer client_data, XtPointer call_data);
void fileselCB(Widget widget, XtPointer client_data, XtPointer call_data);
void file_okCB(Widget widget, XtPointer client_data, XtPointer call_data);

void setMainTitle(char *fn);

/*---------------------------------------- main */
int
main(int argc, char *argv[])
{
   XtAppContext app;
   Widget       menubar;
   Widget       form;
   Widget       label;
   Widget       browseb;
   Widget       w;
   XmString     xms_file;
   XmString     xms_open;
   XmString     xms_exit;
   XmString     xms_openacc;
   XmString     xms_exitacc;
   char         *fn;
   Arg          args[10];
   int          nargs;

   /* initialize toolkit and create top level shell */

   XtSetLanguageProc(NULL, NULL, NULL);

   g_topshell = XtAppInitialize(&app, X_APP_CLASS, NULL, 0, &argc, argv
    , (String *) FALLBACKRES, NULL, 0);

   /* enable editres support */

   XtAddEventHandler(g_topshell, (EventMask) 0, True
    , (XtEventHandler) _XEditResCheckMessages, 0);

   setMainTitle(NULL);

   /* handle command line arguments that remain - Xt has already taken
      care of any arguments intended for it, what remains is yours */

   if(argc > 1)
      fn = argv[1];

   /* build GUI components */

   form = XmCreateForm(g_topshell, "form", NULL, 0);

   /* build the menu bar */

   /* most of the strings that get displayed by Motif are handled as the
      XmString type in order to support localization */

   xms_file = XmStringCreateLocalized("File");

   /* XmVa* functions require a trailing 'NULL' argument */

   menubar  = XmVaCreateSimpleMenuBar(form, "menubar"
    , XmVaCASCADEBUTTON,  xms_file, 'F'
    , XmNtopAttachment,   XmATTACH_FORM
    , XmNleftAttachment,  XmATTACH_FORM
    , XmNrightAttachment, XmATTACH_FORM
    , NULL);

   /* if Motif Creates or Gets an XmString then you must normally
      explicitly release the associated storage */

   XmStringFree(xms_file);

   /* build the File pulldown menu */

   xms_open    = XmStringCreateLocalized("Open");
   xms_openacc = XmStringCreateLocalized("Ctrl+O");
   xms_exit    = XmStringCreateLocalized("Exit");
   xms_exitacc = XmStringCreateLocalized("Ctrl+X");

   XmVaCreateSimplePulldownMenu(menubar, "filemenu", 0, fileCB
    , XmVaPUSHBUTTON, xms_open, 'O', "Ctrl<Key>O", xms_openacc
    , XmVaSEPARATOR
    , XmVaPUSHBUTTON, xms_exit, 'x', "Ctrl<Key>X", xms_exitacc
    , NULL);

   XmStringFree(xms_open);
   XmStringFree(xms_exit);

   XtManageChild(menubar);

   /* build file selection dialog that will be used by the Browse button */

   g_filedlg = XmCreateFileSelectionDialog(g_topshell, "filesb", NULL, 0);
   XtAddCallback(g_filedlg, XmNokCallback, fileselCB, NULL);
   XtAddCallback(g_filedlg, XmNcancelCallback, fileselCB, NULL);

   w = XmSelectionBoxGetChild(g_filedlg, XmDIALOG_HELP_BUTTON);
   XtUnmanageChild(w);
   w = XmSelectionBoxGetChild(g_filedlg, XmDIALOG_APPLY_BUTTON);
   XtUnmanageChild(w);

   /* build the filename entry/display area */

   label = XtVaCreateManagedWidget("Filename: ", xmLabelGadgetClass, form
    , XmNtopAttachment,  XmATTACH_WIDGET
    , XmNtopWidget,      menubar
    , XmNtopOffset,      10
    , XmNleftAttachment, XmATTACH_FORM
    , NULL);

   g_filenamew = XtVaCreateManagedWidget("filename", xmTextFieldWidgetClass, form
    , XmNtopAttachment,  XmATTACH_WIDGET
    , XmNtopWidget,      menubar
    , XmNtopOffset,      5
    , XmNleftAttachment, XmATTACH_WIDGET
    , XmNleftWidget,     label
    , XmNleftOffset,     2
    , NULL);

   browseb = XtVaCreateManagedWidget("Browse", xmPushButtonWidgetClass, form
    , XmNtopAttachment,   XmATTACH_WIDGET
    , XmNtopWidget,       menubar
    , XmNtopOffset,       6
    , XmNrightAttachment, XmATTACH_FORM
    , XmNrightOffset,     10
    , NULL);

   XtVaSetValues(g_filenamew
    , XmNrightAttachment, XmATTACH_WIDGET
    , XmNrightWidget,     browseb
    , XmNrightOffset,     10
    , NULL);

   /* build the file contents area */

   /* an alternative to using the XmVa*() is to build an argument list
      and passing it to the function. I have noticed that sometimes
      Lesstif/gcc and the XmVa*() functions can choke the compiler
   */

   nargs = 0;
   XtSetArg(args[nargs], XmNtopAttachment,    XmATTACH_WIDGET); nargs++;
   XtSetArg(args[nargs], XmNtopWidget,        g_filenamew); nargs++;
   XtSetArg(args[nargs], XmNleftAttachment,   XmATTACH_FORM); nargs++;
   XtSetArg(args[nargs], XmNrightAttachment,  XmATTACH_FORM); nargs++;
   XtSetArg(args[nargs], XmNbottomAttachment, XmATTACH_FORM); nargs++;
   XtSetArg(args[nargs], XmNeditable,         False); nargs++;
   XtSetArg(args[nargs], XmNeditMode,         XmMULTI_LINE_EDIT); nargs++;
   g_filetextw = XmCreateScrolledText(form, "filetext", args, nargs);

   XtManageChild(g_filetextw);

   /* tie in some callbacks to make this thing live */

   XtAddCallback(g_filenamew, XmNactivateCallback, readfileCB,    NULL);
   XtAddCallback(browseb,  XmNactivateCallback, fileCB, NULL);

   /* start up the X event loop and expose the GUI */

   XtManageChild(form);

   XtRealizeWidget(g_topshell);

   XtAppMainLoop(app);

   return 0;
} /* main */

/*---------------------------------------- fileCB
  file menu callback

  client_data is a reserved for your use normally, call_data is
  typically valued by Motif - see fileselCB
*/
void
fileCB(Widget widget, XtPointer client_data, XtPointer call_data)
{
   static Widget filename;
   int menuitem;

   if(call_data == NULL)
   {
      filename = (Widget) client_data;
      return;
   }

   menuitem = (int) client_data;

   switch(menuitem)
   {
      case 0:  /* open */
         XtManageChild(g_filedlg);
         break;

      case 1: /* exit */
         exit(0);
         break;

      default:
         break;
   } /* switch */

   return;
} /* fileCB */

/*---------------------------------------- fileselCB
  callback used by the action buttons in the file selection dialog

  Motif callbacks usually populate the call_data parameter with a
  widget specific Callback structure, in this case it is a superset
  of XmSelectionBoxCallbackStruct (the first few members are identical)
*/
void
fileselCB(Widget widget, XtPointer client_data, XtPointer call_data)
{
   char *fn;
   int  allswell = 0;
   XmFileSelectionBoxCallbackStruct *cbs;

   cbs = (XmFileSelectionBoxCallbackStruct *) call_data;

   if(cbs->reason == XmCR_OK)
   {
      if(!XmStringGetLtoR(cbs->value, XmFONTLIST_DEFAULT_TAG, &fn))
         return;
      if(strlen(fn) > 0 && access(fn, R_OK) == 0)
      {
         XmTextFieldSetString(g_filenamew, fn);
         allswell = 1;
      }
      XtFree(fn);
   }

   /* dialog will disappear but it is not deallocated - may need it later */

   XtUnmanageChild(g_filedlg);

   if(allswell)
   {
      XmUpdateDisplay(g_topshell);

      readfileCB(NULL, NULL, NULL);
   }

   return;
} /* fileselCB */

/*---------------------------------------- readfileCB
  callback used to trigger file-read
  invoked by user pressing Enter on the filename text field or by
  selecting OK from the file selection box
*/
void
readfileCB(Widget widget, XtPointer client_data, XtPointer call_data)
{
   const int bufsz = 1024;

   FILE *fh;
   char *fn;
   char buf[bufsz];
   char *txt;
   char *msg;
   int  pos = 0;

   /* clear the text display */


   XmTextSetString(g_filetextw, "");

   fn = XmTextFieldGetString(g_filenamew);

   fh = fopen(fn, "r");
   if(fh)
   {
      setMainTitle(fn);

      while(fgets(buf, bufsz, fh))
      {
         XmTextInsert(g_filetextw, pos, buf);
         pos += strlen(buf);
      }

      fclose(fh);
   }
   else
   {
      msg = (char *) malloc(strlen(fn) + 9);
      sprintf(msg, "fopen (%s)", fn);
      perror(msg);
      free(msg);
   }

   XtFree(fn);

   return;
} /* readfileCB */

/*---------------------------------------- setMainTitle
  set the application title - icon titles should also be assigned here
*/
void
setMainTitle(char *fn)
{
   Arg    args[3];
   int    nargs = 0;
   char   title[256];
   char   *p = NULL;

   /* strip any path info from the filename */

   if(fn)
   {
      p = strrchr(fn, '/');
      if(p)
         p++;
      else
         p = fn;
   }

   sprintf(title, "xTxtVw%s%s", (p ? ": " : ""), (p ? p : ""));
   XtSetArg(args[nargs], XmNtitle,    title); nargs++;
   XtSetArg(args[nargs], XmNiconName, title); nargs++;
   XtSetValues(g_topshell, args, nargs);

   return;
} /* setMainTitle */

/* xtxtvw.c */