// Name : Motif Phonebook // Author : Terrence Ma // Email : terrence@terrence.com // Web : http://www.terrence.com // Date : V1.0 11/16/2002 // Modified : CDE and Motif, form.c p.68 /* * Program: form - This program illustrates the use of the form * widget to organize a group of widgets in an * application. * * Author : Antonino N. Mione * * Date : 18-June-1995 * */ #include #include #include #include #include #include #include /* X and Motif headers */ #include #include #include #include #include #include #include #include #include #include /* * Some commonly used buffer sizes for strings and data. */ #define SMALL_BUF_SIZE 64 #define LARGE_BUF_SIZE 256 #define HUGE_BUF_SIZE 1024 #define MAX_BUTTONS 6 #define MAX_MATCHES 20 /* Some key X, toolkit, and Motif variables */ /* Widget ids */ Widget toplevel; /* toplevel shell widget id */ Widget buttons[ MAX_BUTTONS ]; /* Buttons for functions: Add, Browse, etc. */ Widget namelab, phonelab; /* Labels for name & phone number on form */ Widget nametextf, phonetextf; /* TextFields for name & phone number on form */ Widget rowcol; /* Rowcolum to hold the buttons */ Widget mainw; /* Main window to hold everything */ Widget form; /* Form to hold labels and textfields */ Widget panedw; /* PanedWindow to hold form and rowcolumn */ Widget msgtext; /* Text widget for the message area */ XtAppContext app_context; /* Application context info (used by toolkit) */ /* * Following are button labels and enumerated variables for button * choices. */ char *buttonlabels[] = { "Add", "Browse", "Prev", "Next", "Clear", "Quit" }; enum button_nums { add=1, browse, prev, next, clear, quit }; char *textflabels[] = { "Name", "Phone" }; int buttonsoff[] = { 2, 3 }; /* This says which buttons are off initially */ /* * Following are variables to support database file operations. */ long file_num; /* File number for open phone database file */ char *dbfilename; /* Name of database file */ /* * Structure and variables for matching with the Browse function. */ struct { char name[ SMALL_BUF_SIZE ]; char phone[ SMALL_BUF_SIZE ]; } matchRecords[ MAX_MATCHES ]; int matchRecCount; /* How many records were matched */ int matchCurrRec; /* Current matched record we are viewing */ /* Prototypes */ int startBrowse ( char * ); int addRecord ( char * ); void msgText ( char * ); void displayMatch ( void ); void buttonCallback ( Widget, XtPointer, XtPointer ); void init_widgets ( void ); int main ( int, char ** ); /* * procedure : startBrowse - Searches the phonedb file for names * containing the string passed to it. * It collects the matched records into * an array and displays the first one. * * arguments : searchName - ( in / (char *) ) - string to search for * in phonedb file. * * return : (int) - number of matches found. * * side effects : matchRecs and matchRecCount are filled in. */ int startBrowse ( char *searchName ) { int file_num; /* File number returned from open call */ size_t searchLen; /* Length of string to compare */ char *colonptr; /* Pointer to colon field separator */ char *phonePtr; /* Pointer to phone number in string */ char record[ HUGE_BUF_SIZE ], /* Record from file */ temp_record[ LARGE_BUF_SIZE ]; /* Piece of record from file */ size_t ret_size; /* Size of buffer read from file */ char *nextRec, *prevRec; /* Ptrs to curr & prev records in buffer */ int temp_idx; /* Loop index */ /* * Initialize stuff for the browse. Set the number of records matched and the * current record to 0, set the buffer to a null string, and record the * length of the search text. */ matchRecCount = 0; matchCurrRec = 0; record[ 0 ] = (char) NULL; searchLen = strlen ( searchName ); /* * Open the database file and report an errors. */ file_num = open ( dbfilename, O_RDONLY ); if ( file_num < 0 ) { /* If there was an error, report it and exit */ perror ( "Error openning file\n" ); exit ( 1 ); } /* * For as long as we can, read large chunks of the file, break them first * into records, then fields, and examine the key field for a match. */ while ( True ) { /* * Read a chunk of the file. If we got nothing back, we're done. * If ret_size was less than zero, we got an error. Report it and exit. */ ret_size = read ( file_num, temp_record, LARGE_BUF_SIZE ); if ( ret_size == 0 ) { break; } if ( ret_size < 0 ) { perror ( "Error reading file.\n" ); exit ( 1 ); } /* If we read less than a full buffer, mark the end with a null. */ if ( ret_size < LARGE_BUF_SIZE ) { temp_record[ ret_size ] = '\0'; } /* Tack the new record onto any text left over from a preivous read. */ strncat ( record, temp_record, ret_size ); /* * Find the start of the next record. Then, if the record is not a null * string, look for the search text in it. */ nextRec = strtok ( record, "\n" ); while ( nextRec != (char *) NULL ) { if ( strncmp ( nextRec, searchName, searchLen ) == 0 ) { /* * If we found the search text, break the string into two * components and save them in the matchRecords array. * Also, increment our matched record count. */ colonptr = strchr ( nextRec, ':' ); strncpy ( matchRecords[ matchCurrRec ].name, nextRec, colonptr - nextRec ); phonePtr = colonptr + 1; strncpy ( matchRecords[ matchCurrRec ].phone, phonePtr, strlen ( phonePtr ) ); matchCurrRec++; } /* * Mark our place in the buffer and find the beginning of the next * record. */ prevRec = nextRec; nextRec = strtok ( (char *) NULL, "\n" ); } strcpy ( record, prevRec + strlen ( prevRec ) + 1 ); /* * If we just exceeded the array says, tell user that the * match table is full. */ if ( matchCurrRec >= MAX_MATCHES ) { msgText ( "Exceeded Maximum matches allowed.\n" ); msgText ( "Display will be limited to initial matches.\n" ); break; } } /* * Record the number of records found. Set the current record to 0. * Dislay the first record from the array. */ matchRecCount = matchCurrRec; matchCurrRec = 0; displayMatch (); /* Before we leave, set the previously insenitive buttons to be sensitive */ for ( temp_idx = 0; temp_idx < XtNumber ( buttonsoff ); temp_idx++ ) { XtSetSensitive ( buttons[ buttonsoff [ temp_idx ] ], True ); } close ( file_num ); /* Close the database file and leave */ } /* * procedure : addRecord - adds the string passed to the phonedb file. * * arguments : record ( in / (char *) ) - Record to add to the database. * * return : (int) - Success/Failure of operation. * * side effects : none */ int addRecord ( char *record ) { int file_num; /* File number from open */ size_t rec_size; /* Length of record being added */ size_t ret_size; /* Length of record written to file */ char temp_string[ SMALL_BUF_SIZE ]; /* */ /* * Open the file. If we got an error, report it and exit the application. */ file_num = open ( dbfilename, O_WRONLY | O_CREAT | O_APPEND, (mode_t) 0644 ); if ( file_num < 0 ) { perror ( "Error openning or creating file.\n" ); exit ( EXIT_FAILURE ); } /* * Fetch the size of the record to be written. Create a string to tell * the user what we are doing. Write the message to the message area. * Finally, write the record to the file and close the file. */ rec_size = strlen ( record ); sprintf ( temp_string, "adding %s\n", record ); msgText ( temp_string ); ret_size = write ( file_num, record, rec_size ); close ( file_num ); /* If the return size and record size matched. The write succeeded. */ if ( ret_size == rec_size ) { return True; } else { return False; } } /* * procedure : msgText - Write a message to the message area. * * arguments : msg ( in / (char *) ) - Text to be written to message area. * * return : void * * side effects : */ void msgText ( char *msg ) { XmTextPosition text_pos; /* Last position in text area */ /* * Find last position used in the message area and insert the new message * there. */ text_pos = XmTextGetLastPosition ( msgtext ); XmTextInsert ( msgtext, text_pos, msg ); } /* * procedure : displayMatch - This routine displays the current match * record on the form. * * arguments : none * * return : void * * side effects : */ void displayMatch ( void ) { /* * Stuff the next name and address from the match array into the * TextFields to display them. */ XmTextSetString ( nametextf, matchRecords[ matchCurrRec ].name ); XmTextSetString ( phonetextf, matchRecords[ matchCurrRec ].phone ); } /* * procedure : buttonCallback - Callback when user activates a button. * * arguments : widgetId ( in / (Widget) ) - widgetId of widget causing * the callback. * clientData ( in / (XtPointer) ) - value passed to routine * by the widget. * cbr ( in / (XtPointer) ) - the callbackRecord. This * contains additional information pertinent to the * event causing the callback. * * return : none. * * side effects : The requested action for the callback is performed. */ void buttonCallback ( Widget widgetId, XtPointer clientData, XtPointer cbr ) { /* Cast callback record and client data to the correct type. */ XmPushButtonCallbackStruct *callbackRecord = (XmPushButtonCallbackStruct *) cbr; enum button_nums button_num = (enum button_nums) clientData; char temp_string[ SMALL_BUF_SIZE ]; /* Work area to build strings */ char *tempTextFieldValue; /* Work area for text retrieved from TextField */ char record[ LARGE_BUF_SIZE ]; /* Work area for record to be written */ /* * Select process based on button number. */ switch ( button_num ) { case quit: /* Exit the application */ exit ( 0 ); break; case add: /* Add a record to the file */ /* * Get the value of the name TextField, copy it into the new * record and free the returned value. */ tempTextFieldValue = XmTextFieldGetString ( nametextf ); strcpy ( record, tempTextFieldValue ); XtFree ( tempTextFieldValue ); strcat ( record, ":" ); /* Tack on a field separator */ /* * Get the value of the phone TextField, tack it onto the new * record and free the returned value. Finally, add a newline * to the record. */ tempTextFieldValue = XmTextFieldGetString ( phonetextf ); strcat ( record, tempTextFieldValue ); XtFree ( tempTextFieldValue ); strcat ( record, "\n" ); /* Try adding the record to the file. If an error occurs, report it */ if ( ! addRecord ( record ) ) { msgText ( "Error adding record to phonedb\n" ); } break; case browse: /* Browse the file */ /* Get the value of the name TextField and pass it to startBrowse */ tempTextFieldValue = XmTextFieldGetString ( nametextf ); strcpy ( record, tempTextFieldValue ); startBrowse ( record ); break; case prev: /* Step backward through matched records */ /* Decrement current record num and wrap to end if we drop below 0 */ matchCurrRec--; if ( matchCurrRec < 0 ) { matchCurrRec = matchRecCount - 1; } displayMatch (); /* Go display the new current record */ break; case next: /* Step forward through matched records */ /* Increment current record num and wrap to beginning if we overflow */ matchCurrRec++; if ( matchCurrRec >= matchRecCount ) { matchCurrRec = 0; } displayMatch (); /* Go display the new current record */ break; case clear: /* Clear the TextFields on the form */ /* Just set both name and phone number fields to null strings. */ XmTextFieldSetString ( nametextf, "" ); XmTextFieldSetString ( phonetextf, "" ); /* Tell user what we did. */ strcpy ( temp_string, "clearing fields\n" ); msgText ( temp_string ); break; } return; } /* * procedure : init_widgets - This routine initializes all of the * widgets for the application. * * arguments : none * * return : void * * side effects : interface is created */ void init_widgets ( void ) { Arg args[ 10 ]; /* Argument array for widget creation */ Cardinal arg_count; /* Count of valid arguments */ char tempName[ SMALL_BUF_SIZE ]; /* Work space to build widget names */ int temp_idx; /* Loop index */ int button_num; /* Button num for activate callback */ /* * Create a main window. */ arg_count = 0; mainw = XmCreateMainWindow ( toplevel, "mainw", args, arg_count ); /* * Create a panedwindow within the main window. */ arg_count = 0; panedw = XmCreatePanedWindow ( mainw, "panedw", args, arg_count ); /* * Create and manage a form as the first panel of the panedwindow. */ arg_count = 0; XtSetArg ( args[ arg_count ], XmNhorizontalSpacing, 10 ); arg_count++; form = XmCreateForm ( panedw, "form", args, arg_count ); XtManageChild ( form ); /* * Create a rowcolumn as the second panel of the panedwindow. */ arg_count = 0; XtSetArg ( args[ arg_count ], XmNorientation, XmHORIZONTAL ); arg_count++; rowcol = XmCreateRowColumn ( panedw, "rowcol", args, arg_count ); /* * For each item in buttonlabels, * - generate a name for the new button widget. * - create a button child for the rowcolumn with the array element * as the labelString. * - free the compound string that was generated for the label. * - add an activate callback. * - manage the new button. */ for ( temp_idx = 0; temp_idx < XtNumber ( buttonlabels ); temp_idx++ ) { button_num = temp_idx + 1; strcpy ( tempName, buttonlabels[ temp_idx ] ); arg_count = 0; buttons[ temp_idx ] = XmCreatePushButton ( rowcol, tempName, args, arg_count ); XtAddCallback ( buttons[ temp_idx ], XmNactivateCallback, buttonCallback, (XtPointer) button_num ); XtManageChild ( buttons[ temp_idx ] ); } /* Now that all the children are managed, do the same for the rowcolumn. */ XtManageChild ( rowcol ); /* * Turn off any buttons that we don't want the user to have at startup */ for ( temp_idx = 0; temp_idx < XtNumber ( buttonsoff ); temp_idx++ ) { XtSetSensitive ( buttons[ buttonsoff[ temp_idx ] ], False ); } /* * Set up labels and TextFields in the form. */ /* * Create the label for the name field. Attach it to the form on the * top and left sides. Leave the other sides attached to nothing. */ arg_count = 0; XtSetArg ( args[ arg_count ], XmNtopAttachment, XmATTACH_FORM ); arg_count++; XtSetArg ( args[ arg_count ], XmNrightAttachment, XmATTACH_NONE ); arg_count++; XtSetArg ( args[ arg_count ], XmNleftAttachment, XmATTACH_FORM ); arg_count++; XtSetArg ( args[ arg_count ], XmNbottomAttachment, XmATTACH_NONE ); arg_count++; namelab = XmCreateLabel ( form, "namelab", args, arg_count ); XtManageChild ( namelab ); /* * Create the TextField for the name. Attach this on the top to the * name label. Using XmATTACH_OPPOSITE_WIDGET will line up the two * widgets along their top edge. Also, attach the left side to the name * label as well. Attach the top to the form and the bottom to nothing. */ arg_count = 0; XtSetArg ( args[ arg_count ], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET ); arg_count++; XtSetArg ( args[ arg_count ], XmNtopWidget, namelab ); arg_count++; XtSetArg ( args[ arg_count ], XmNrightAttachment, XmATTACH_FORM ); arg_count++; XtSetArg ( args[ arg_count ], XmNleftAttachment, XmATTACH_WIDGET ); arg_count++; XtSetArg ( args[ arg_count ], XmNleftWidget, namelab ); arg_count++; XtSetArg ( args[ arg_count ], XmNbottomAttachment, XmATTACH_NONE ); arg_count++; nametextf = XmCreateTextField ( form, "nametextf", args, arg_count ); XtManageChild ( nametextf ); /* * Create the phone label. Attach the left side to the form. Attach * the top to a position that is 40 percent down the height of the form. * Don't attach the right or bottom to anything. */ arg_count = 0; XtSetArg ( args[ arg_count ], XmNtopAttachment, XmATTACH_POSITION ); arg_count++; XtSetArg ( args[ arg_count ], XmNtopPosition, 40 ); arg_count++; XtSetArg ( args[ arg_count ], XmNrightAttachment, XmATTACH_NONE ); arg_count++; XtSetArg ( args[ arg_count ], XmNleftAttachment, XmATTACH_FORM ); arg_count++; XtSetArg ( args[ arg_count ], XmNbottomAttachment, XmATTACH_NONE ); arg_count++; phonelab = XmCreateLabel ( form, "phonelab", args, arg_count ); XtManageChild ( phonelab ); /* * Create the TextField for the phone. Attach the top to the phone label * with XmATTACH_OPPOSITE_WIDGET. This will line up the tops of those * two widgets. Also, attach the left side to the same widget. Attach * the right to the form and the bottom to nothing. */ arg_count = 0; XtSetArg ( args[ arg_count ], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET ); arg_count++; XtSetArg ( args[ arg_count ], XmNtopWidget, phonelab ); arg_count++; XtSetArg ( args[ arg_count ], XmNrightAttachment, XmATTACH_FORM ); arg_count++; XtSetArg ( args[ arg_count ], XmNleftAttachment, XmATTACH_WIDGET ); arg_count++; XtSetArg ( args[ arg_count ], XmNleftWidget, phonelab ); arg_count++; XtSetArg ( args[ arg_count ], XmNbottomAttachment, XmATTACH_NONE ); arg_count++; phonetextf = XmCreateTextField ( form, "phonetextf", args, arg_count ); XtManageChild ( phonetextf ); XtManageChild ( panedw ); /* Manage the panedwindow */ /* * Create a text widget for the message area. */ arg_count = 0; XtSetArg ( args[ arg_count ], XmNeditable, False ); arg_count++; msgtext = XmCreateText ( mainw, "msgtext", args, arg_count ); XtManageChild ( msgtext ); /* * Set the various areas for the main window. */ XtVaSetValues ( mainw, XmNmessageWindow, msgtext, XmNworkWindow, panedw, XmNshowSeparator, True, NULL ); XtManageChild ( mainw ); } /* * procedure : main - The main routine. This is where it all begins. * * arguments : argc (in / (int)) - number of command line arguments. * argv (in / (char **)) - pointer to the argument strings. * * return : (int) - status value. success/failure of program * * side effects : lots */ int main ( int argc, char *argv[] ) { /* * In case the app-defaults file is not there, these resources will make * the application usable. */ String fallbacks[] = { "Form.mainw.height: 300", "Form*msgtext.rows: 6", "Form*msgtext.columns: 80", "Form*rowcol.paneMaximum: 40", "Form*form.paneMinimum: 100", "Form*msgtext.editMode: MULTI_LINE_EDIT", "Form*Add.labelString: Add", "Form*Browse.labelString: Browse", "Form*Prev.labelString: Prev", "Form*Next.labelString: Next", "Form*Clear.labelString: Clear", "Form*Quit.labelString: Quit", "Form*namelab.labelString: Name:", "Form*phonelab.labelString: Phone:", "Form*Name.labelString: Name", "Form*Phone.labelString: Phone", (char *) NULL }; char temp_string[ LARGE_BUF_SIZE ]; /* For building char strings */ toplevel = XtAppInitialize ( &app_context, /* Application context */ "Form", /* Application class string */ NULL, /* Options */ 0, /* Number of Options */ &argc, /* Command line arg cnt in/out */ argv, /* Command line args in/out */ fallbacks, /* Fallback (last chance) resources */ NULL, /* Arguments */ 0 ); /* Number of Arguments */ init_widgets (); /* Build the interface */ /* * Build the database filename. Find the home directory and tack on * 'phonedb.txt'. Tell the user the filename. */ dbfilename = getenv ( "HOME" ); strcat ( dbfilename, "/phonedb.txt" ); sprintf ( temp_string, "Phone database is: %s\n", dbfilename ); msgText ( temp_string ); XtRealizeWidget ( toplevel ); /* Realize the widgets (i.e. create the widget data structures */ XtAppMainLoop ( app_context ); /* Jump into the main loop. This never returns. */ }