chiark / gitweb /
Fix tabbing in help text.
[xtoys] / xgetline.c
1 /* -*-c-*-
2  *
3  * $Id: xgetline.c,v 1.7 1998/12/11 09:53:02 mdw Exp $
4  *
5  * Fetch a line of text from the user
6  *
7  * (c) 1998 Straylight/Edgeware
8  */
9
10 /*----- Licensing notice --------------------------------------------------* 
11  *
12  * This file is part of the Edgeware X tools collection.
13  *
14  * X tools is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  * 
19  * X tools is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  * 
24  * You should have received a copy of the GNU General Public License
25  * along with X tools; if not, write to the Free Software Foundation,
26  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27  */
28
29 /*----- Revision history --------------------------------------------------* 
30  *
31  * $Log: xgetline.c,v $
32  * Revision 1.7  1998/12/11 09:53:02  mdw
33  * Updates for mLib/mgLib.  Support history files for recalling past
34  * entries, using a drop-down list.
35  *
36  * Revision 1.6  1998/12/03 00:56:29  mdw
37  * Set focus on the entry field, rather than leaving things to luck.
38  *
39  * Revision 1.5  1998/12/03 00:39:44  mdw
40  * Force focus when starting up.
41  *
42  * Revision 1.4  1998/11/30 22:36:47  mdw
43  * Tidy up tabbing in help texts very slightly.
44  *
45  * Revision 1.3  1998/11/21 22:30:20  mdw
46  * Support GNU-style long options throughout, and introduce proper help
47  * text to all programs.  Update manual pages to match.
48  *
49  * Revision 1.2  1998/11/18 21:25:30  mdw
50  * Remove bogus `-h' option from the options list.
51  *
52  * Revision 1.1  1998/11/16 23:00:49  mdw
53  * Initial versions.
54  *
55  */
56
57 /*----- Header files ------------------------------------------------------*/
58
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62
63 #include <fcntl.h>
64 #include <unistd.h>
65
66 #include <gtk/gtk.h>
67 #include <gdk/gdkkeysyms.h>
68
69 #include <mLib/alloc.h>
70 #include <mLib/dstr.h>
71 #include <mLib/mdwopt.h>
72 #include <mLib/report.h>
73 #include <mLib/quis.h>
74
75 #include <mgLib/cancel.h>
76 #include <mgLib/mdwfocus.h>
77
78 /*----- Main code ---------------------------------------------------------*/
79
80 /* --- @quit@ --- *
81  *
82  * Arguments:   @GtkWidget *w@ = widget raising the signal
83  *              @gpointer *p@ = pointer to integer result code
84  *
85  * Returns:     ---
86  *
87  * Use:         Sets the result code to zero (failure) and ends the loop.
88  */
89
90 static void quit(GtkWidget *w, gpointer *p)
91 {
92   int *ip = (int *)p;
93   *ip = 0;
94   gtk_main_quit();
95 }
96
97 /* --- @done@ --- *
98  *
99  * Arguments:   @GtkWidget *w@ = widget raising the signal
100  *              @gpointer *p@ = pointer to integer result code
101  *
102  * Returns:     ---
103  *
104  * Use:         Sets the result code nonzero (success) and ends the loop.
105  */
106
107 static void done(GtkWidget *w, gpointer *p)
108 {
109   int *ip = (int *)p;
110   *ip = 1;
111   gtk_main_quit();
112 }
113
114 /* --- @version@ --- *
115  *
116  * Arguments:   @FILE *fp@ = output stream to print the message on
117  *
118  * Returns:     ---
119  *
120  * Use:         Spits out a version message.
121  */
122
123 static void version(FILE *fp)
124 {
125   fprintf(fp, "%s (xtoys version " VERSION ")\n", QUIS);
126 }
127
128 /* --- @usage@ --- *
129  *
130  * Arguments:   @FILE *fp@ = output stream to print the message on
131  *
132  * Returns:     ---
133  *
134  * Use:         Spits out a usage message.
135  */
136
137 static void usage(FILE *fp)
138 {
139   fprintf(fp,
140           "Usage: %s [-in] [-t title] [-p prompt] [-d default]\n"
141           "\t[-l|-H file] [-m max]\n",
142           QUIS);
143 }
144
145 /* --- @main@ --- *
146  *
147  * Arguments:   @int argc@ = number of command line arguments
148  *              @char *argv[]@ = addresses of arguments
149  *
150  * Returns:     Zero if OK, and we read a string; nonzero if the user
151  *              cancelled.
152  *
153  * Use:         Reads a string from the user, and returns it on standard
154  *              output.
155  */
156
157 int main(int argc, char *argv[])
158 {
159   /* --- Configuration variables --- */
160
161   char *prompt = 0;
162   char *dfl = "";
163   char *title = "Input request";
164   int left;
165   unsigned f = 0;
166   int ok = 0;
167
168   const char *list = 0;
169   int histmax = 20;
170   GList *hist;
171
172   enum {
173     f_invis = 1,
174     f_duff = 2,
175     f_history = 4,
176     f_nochoice = 8
177   };
178
179   /* --- User interface bits --- */
180
181   GtkWidget *win;
182   GtkWidget *box;
183   GtkWidget *entry;
184   GtkWidget *btn;
185
186   /* --- Crank up the toolkit --- *
187    *
188    * Have to do this here: GTK snarfs some command line options which my
189    * parser would barf about.
190    */   
191
192   ego(argv[0]);
193   gtk_init(&argc, &argv);
194
195   /* --- Parse options from command line --- */
196
197   for (;;) {
198
199     /* --- Long options structure --- */
200
201     static struct option opt[] = {
202       { "help",         0,                      0,      'h' },
203       { "usage",        0,                      0,      'u' },
204       { "version",      0,                      0,      'v' },
205       { "title",        required_argument,      0,      't' },
206       { "prompt",       required_argument,      0,      'p' },
207       { "default",      required_argument,      0,      'd' },
208       { "password",     0,                      0,      'i' },
209       { "invisible",    0,                      0,      'i' },
210       { "history",      required_argument,      0,      'H' },
211       { "list",         required_argument,      0,      'l' },
212       { "histmax",      required_argument,      0,      'm' },
213       { "no-choice",    0,                      0,      'n' },
214       { 0,              0,                      0,      0 }
215     };
216     int i;
217
218     /* --- Fetch an option --- */
219
220     i = getopt_long(argc, argv, "huv t:p:d:i H:l:m:n", opt, 0);
221     if (i < 0)
222       break;
223
224     /* --- Work out what to do with it --- */
225
226     switch (i) {
227       case 'h':
228         version(stdout);
229         fputs("\n", stdout);
230         usage(stdout);
231         fputs(
232 "\n"
233 "Pops up a small window requesting input from a user, and echoes the\n"
234 "response to stdout, where it can be collected by a shell script.\n"
235 "\n"
236 "Options available are:\n"
237 "\n"
238 "-h, --help             Display this help text\n"
239 "-u, --usage            Display a short usage summary\n"
240 "-v, --version          Display the program's version number\n"
241 "\n"
242 "-i, --invisible\t      Don't show the user's string as it's typed\n"
243 "-t, --title=TITLE      Set the window's title string\n"
244 "-p, --prompt=PROMPT    Set the window's prompt string\n"
245 "-d, --default=DEFAULT  Set the default string already in the window\n"
246 "\n"
247 "-l, --list=FILE        Read FILE into a drop-down list\n"
248 "-n, --no-choice        No free text input: must choose item from list\n"
249 "-H, --history=FILE     As for `--list', but update with new string\n"
250 "-m, --histmax=MAX      Maximum number of items written back to file\n",
251           stdout);
252         exit(0);
253         break;
254       case 'u':
255         usage(stdout);
256         exit(0);
257         break;
258       case 'v':
259         version(stdout);
260         exit(0);
261         break;
262
263       case 't':
264         title = optarg;
265         break;
266       case 'p':
267         prompt = optarg;
268         break;
269       case 'd':
270         dfl = optarg;
271         break;
272       case 'i':
273         f |= f_invis;
274         break;
275
276       case 'l':
277         list = optarg;
278         break;
279       case 'n':
280         f |= f_nochoice;
281         break;
282       case 'H':
283         f |= f_history;
284         list = optarg;
285         break;
286       case 'm':
287         histmax = atoi(optarg);
288         break;
289
290       default:
291         f |= f_duff;
292         break;
293     }
294   }
295
296   if (f & f_duff) {
297     usage(stderr);
298     exit(EXIT_FAILURE);
299   }
300
301   if ((f & f_invis) && list) {
302     die(EXIT_FAILURE,
303         "invisible entry is dumb if you provide a list of alternatives!");
304   }
305
306   if ((f & f_nochoice) && !list)
307     die(EXIT_FAILURE, "nothing to restrict choice to!");
308
309   /* --- Create the main window --- */
310
311   win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
312   gtk_window_set_title(GTK_WINDOW(win), title);
313   gtk_window_position(GTK_WINDOW(win), GTK_WIN_POS_MOUSE);
314   gtk_signal_connect(GTK_OBJECT(win), "destroy",
315                      GTK_SIGNAL_FUNC(quit), &ok);
316
317   /* --- Create the box for laying out the widgets inside --- */
318
319   left = (prompt ? 1 : 0);
320   box = gtk_table_new(left + 2, 1, 0);
321
322   /* --- Maybe create a prompt widget --- */
323
324   if (prompt) {
325     GtkWidget *w = gtk_label_new(prompt);
326     gtk_table_attach(GTK_TABLE(box), w,
327                      0, 1, 0, 1, 0, GTK_EXPAND, 4, 2);
328     gtk_widget_show(w);
329   }
330
331   /* --- Create the entry widget --- */
332
333   if (list) {
334     FILE *fp = fopen(list, "r");
335     GtkWidget *combo;
336
337     /* --- Read the items in from the file --- *
338      *
339      * Inability to open the file is not a disaster.
340      */
341
342     hist = 0;
343     if (fp) {
344       dstr d;
345
346       dstr_create(&d);
347       while (dstr_putline(&d, fp) != EOF) {
348         hist = g_list_append(hist, xstrdup(d.buf));
349         dstr_destroy(&d);
350       }
351       fclose(fp);
352     }
353
354     /* --- Now create a combo box --- */
355
356     combo = gtk_combo_new();
357     entry = GTK_COMBO(combo)->entry;
358     if (hist)
359       gtk_combo_set_popdown_strings(GTK_COMBO(combo), hist);
360
361     /* --- Do other configuring --- */
362
363     if (f & f_nochoice) {
364       gtk_combo_set_value_in_list(GTK_COMBO(combo), 1, 0);
365       gtk_entry_set_editable(GTK_ENTRY(entry), 0);
366     }
367     gtk_combo_set_case_sensitive(GTK_COMBO(combo), 1);
368     gtk_combo_set_use_arrows_always(GTK_COMBO(combo), 1);
369     if (strcmp(dfl, "@") == 0)
370       gtk_entry_set_text(GTK_ENTRY(entry), hist ? (char *)hist->data : "");
371     else
372       gtk_entry_set_text(GTK_ENTRY(entry), dfl);
373
374     /* --- Set the widget in the right place and show it --- */
375
376     gtk_table_attach(GTK_TABLE(box), combo,
377                      left, left + 1, 0, 1,
378                      GTK_EXPAND | GTK_FILL, GTK_EXPAND, 4, 2);
379     gtk_widget_show(combo);
380   } else {
381     entry = gtk_entry_new();
382     gtk_entry_set_text(GTK_ENTRY(entry), dfl);
383     if (f & f_invis)
384       gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
385     gtk_table_attach(GTK_TABLE(box), entry,
386                      left, left + 1, 0, 1,
387                      GTK_EXPAND | GTK_FILL, GTK_EXPAND, 4, 2);
388     gtk_widget_show(entry);
389   }
390
391   /* --- Create the default action widget --- */
392
393   btn = gtk_button_new_with_label("OK");
394   gtk_table_attach(GTK_TABLE(box), btn,
395                    left + 1, left + 2, 0, 1, 0, GTK_EXPAND, 2, 2);
396   GTK_WIDGET_SET_FLAGS(btn, GTK_CAN_DEFAULT);
397   gtk_widget_show(btn);
398
399   /* --- Add the box into the main window --- */
400
401   gtk_container_add(GTK_CONTAINER(win), box);
402   gtk_widget_show(box);
403
404   /* --- Last minute configuration things --- */
405
406   gtk_widget_grab_default(btn);
407   gtk_signal_connect(GTK_OBJECT(btn), "clicked",
408                      GTK_SIGNAL_FUNC(done), &ok);
409   gtk_signal_connect_object(GTK_OBJECT(entry), "activate",
410                             GTK_SIGNAL_FUNC(gtk_widget_activate),
411                             GTK_OBJECT(btn));
412   cancel(GTK_WINDOW(win), 0);
413
414   /* --- Go go go --- */
415
416   gtk_widget_realize(win);
417   mdwfocus(win);
418   gtk_widget_grab_focus(entry);
419   gtk_widget_show(win);
420   gtk_main();
421
422   /* --- Output the result --- */
423
424   if (ok) {
425     char *p = gtk_entry_get_text(GTK_ENTRY(entry));
426
427     /* --- If history is enabled, output a new history file --- *
428      *
429      * If the first entry was accepted verbatim, don't bother.
430      */
431
432     if (f & f_history && !(hist && strcmp(p, hist->data) == 0)) {
433       int fd;
434       FILE *fp;
435       int i;
436       GList *g;
437
438       if ((fd = open(list, O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0)
439         goto fail;
440       if ((fp = fdopen(fd, "w")) == 0) {
441         close(fd);
442         goto fail;
443       }
444
445       fputs(p, fp);
446       fputc('\n', fp);
447
448       for (i = 1, g = hist; (histmax < 1 || i < histmax) && g; g = g->next) {
449         if (strcmp(g->data, p) != 0) {
450           fputs(g->data, fp);
451           fputc('\n', fp);
452           i++;
453         }
454       }
455       fclose(fp);
456     fail:;
457     }
458
459     /* --- Print the result and go away --- */
460       
461     puts(p);
462   }
463
464   return (ok ? EXIT_SUCCESS : EXIT_FAILURE);
465 }
466
467 /*----- That's all, folks -------------------------------------------------*/