Add a README.DRAGONFLY for contrib/dialog.
[dragonfly.git] / gnu / lib / libdialog / textbox.c
1 /*
2  *  textbox.c -- implements the text box
3  *
4  *  AUTHOR: Savio Lam (lam836@cs.cuhk.hk)
5  *
6  *  This program is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU General Public License
8  *  as published by the Free Software Foundation; either version 2
9  *  of the License, or (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  *
20  * $FreeBSD: src/gnu/lib/libdialog/textbox.c,v 1.18.6.2 2002/06/18 07:59:59 dougb Exp $
21  */
22
23 #include <dialog.h>
24 #include "dialog.priv.h"
25
26
27 static void back_lines(int n);
28 static void print_page(WINDOW *win, int height, int width);
29 static void print_line(WINDOW *win, int row, int width);
30 static unsigned char *get_line(void);
31 static int get_search_term(WINDOW *win, unsigned char *search_term, int height, int width);
32 static void print_position(WINDOW *win, int height, int width);
33
34
35 static int hscroll = 0, fd, file_size, bytes_read, begin_reached = 1,
36            end_reached = 0, page_length;
37 static unsigned char *buf, *page;
38
39
40 /*
41  * Display text from a file in a dialog box.
42  */
43 int dialog_textbox(unsigned char *title, unsigned char *file, int height, int width)
44 {
45   int i, x, y, cur_x, cur_y, fpos, key = 0, dir, temp, temp1;
46 #ifdef HAVE_NCURSES
47   int passed_end;
48 #endif
49   unsigned char search_term[MAX_LEN+1], *tempptr, *found;
50   WINDOW *dialog, *text;
51
52   if (height < 0 || width < 0) {
53     fprintf(stderr, "\nAutosizing is impossible in dialog_textbox().\n");
54     return(-1);
55   }
56
57   search_term[0] = '\0';    /* no search term entered yet */
58
59   /* Open input file for reading */
60   if ((fd = open(file, O_RDONLY)) == -1) {
61     fprintf(stderr, "\nCan't open input file <%s>in dialog_textbox().\n", file);
62     return(-1);
63   }
64   /* Get file size. Actually, 'file_size' is the real file size - 1,
65      since it's only the last byte offset from the beginning */
66   if ((file_size = lseek(fd, 0, SEEK_END)) == -1) {
67     fprintf(stderr, "\nError getting file size in dialog_textbox().\n");
68     return(-1);
69   }
70   /* Restore file pointer to beginning of file after getting file size */
71   if (lseek(fd, 0, SEEK_SET) == -1) {
72     fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n");
73     return(-1);
74   }
75   /* Allocate space for read buffer */
76   if ((buf = malloc(BUF_SIZE+1)) == NULL) {
77     endwin();
78     fprintf(stderr, "\nCan't allocate memory in dialog_textbox().\n");
79     exit(-1);
80   }
81   if ((bytes_read = read(fd, buf, BUF_SIZE)) == -1) {
82     fprintf(stderr, "\nError reading file in dialog_textbox().\n");
83     return(-1);
84   }
85   buf[bytes_read] = '\0';    /* mark end of valid data */
86   page = buf;    /* page is pointer to start of page to be displayed */
87
88   if (width > COLS)
89         width = COLS;
90   if (height > LINES)
91         height = LINES;
92   /* center dialog box on screen */
93   x = DialogX ? DialogX : (COLS - width)/2;
94   y = DialogY ? DialogY : (LINES - height)/2;
95
96 #ifdef HAVE_NCURSES
97   if (use_shadow)
98     draw_shadow(stdscr, y, x, height, width);
99 #endif
100   dialog = newwin(height, width, y, x);
101   if (dialog == NULL) {
102     endwin();
103     fprintf(stderr, "\nnewwin(%d,%d,%d,%d) failed, maybe wrong dims\n", height,width,y,x);
104     exit(1);
105   }
106   keypad(dialog, TRUE);
107
108   /* Create window for text region, used for scrolling text */
109 /*  text = newwin(height-4, width-2, y+1, x+1); */
110   text = subwin(dialog, height-4, width-2, y+1, x+1);
111   if (text == NULL) {
112     endwin();
113     fprintf(stderr, "\nsubwin(dialog,%d,%d,%d,%d) failed, maybe wrong dims\n", height-4,width-2,y+1,x+1);
114     exit(1);
115   }
116   keypad(text, TRUE);
117
118   draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr);
119
120   wattrset(dialog, border_attr);
121   wmove(dialog, height-3, 0);
122   waddch(dialog, ACS_LTEE);
123   for (i = 0; i < width-2; i++)
124     waddch(dialog, ACS_HLINE);
125   wattrset(dialog, dialog_attr);
126   waddch(dialog, ACS_RTEE);
127   wmove(dialog, height-2, 1);
128   for (i = 0; i < width-2; i++)
129     waddch(dialog, ' ');
130
131   if (title != NULL) {
132     wattrset(dialog, title_attr);
133     wmove(dialog, 0, (width - strlen(title))/2 - 1);
134     waddch(dialog, ' ');
135     waddstr(dialog, title);
136     waddch(dialog, ' ');
137   }
138   display_helpline(dialog, height-1, width);
139
140   print_button(dialog, "  OK  ", height-2, width/2-6, TRUE);
141   wnoutrefresh(dialog);
142   getyx(dialog, cur_y, cur_x);    /* Save cursor position */
143
144   /* Print first page of text */
145   attr_clear(text, height-4, width-2, dialog_attr);
146   print_page(text, height-4, width-2);
147   print_position(dialog, height, width);
148   wmove(dialog, cur_y, cur_x);    /* Restore cursor position */
149   wrefresh(dialog);
150
151   while ((key != ESC) && (key != '\n') && (key != '\r') && (key != ' ')) {
152     key = wgetch(dialog);
153     switch (key) {
154       case 'E':    /* Exit */
155       case 'e':
156         delwin(dialog);
157         free(buf);
158         close(fd);
159         return 0;
160       case 'g':    /* First page */
161       case KEY_HOME:
162         if (!begin_reached) {
163           begin_reached = 1;
164           /* First page not in buffer? */
165           if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) {
166             endwin();
167             fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n");
168             exit(-1);
169           }
170           if (fpos > bytes_read) {    /* Yes, we have to read it in */
171             if (lseek(fd, 0, SEEK_SET) == -1) {
172               endwin();
173               fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n");
174               exit(-1);
175             }
176             if ((bytes_read = read(fd, buf, BUF_SIZE)) == -1) {
177               endwin();
178               fprintf(stderr, "\nError reading file in dialog_textbox().\n");
179               exit(-1);
180             }
181             buf[bytes_read] = '\0';
182           }
183           page = buf;
184           print_page(text, height-4, width-2);
185           print_position(dialog, height, width);
186           wmove(dialog, cur_y, cur_x);    /* Restore cursor position */
187           wrefresh(dialog);
188         }
189         break;
190       case 'G':    /* Last page */
191 #ifdef HAVE_NCURSES
192       case KEY_END:
193 #endif
194         end_reached = 1;
195         /* Last page not in buffer? */
196         if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) {
197           endwin();
198           fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n");
199           exit(-1);
200         }
201         if (fpos < file_size) {    /* Yes, we have to read it in */
202           if (lseek(fd, -BUF_SIZE, SEEK_END) == -1) {
203             endwin();
204             fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n");
205             exit(-1);
206           }
207           if ((bytes_read = read(fd, buf, BUF_SIZE)) == -1) {
208             endwin();
209             fprintf(stderr, "\nError reading file in dialog_textbox().\n");
210             exit(-1);
211           }
212           buf[bytes_read] = '\0';
213         }
214         page = buf + bytes_read;
215         back_lines(height-4);
216         print_page(text, height-4, width-2);
217         print_position(dialog, height, width);
218         wmove(dialog, cur_y, cur_x);    /* Restore cursor position */
219         wrefresh(dialog);
220         break;
221       case 'K':    /* Previous line */
222       case 'k':
223       case '\020':      /* ^P */
224       case KEY_UP:
225         if (!begin_reached) {
226           back_lines(page_length+1);
227 #ifdef HAVE_NCURSES
228           /* We don't call print_page() here but use scrolling to ensure
229              faster screen update. However, 'end_reached' and 'page_length'
230              should still be updated, and 'page' should point to start of
231              next page. This is done by calling get_line() in the following
232              'for' loop. */
233           scrollok(text, TRUE);
234           wscrl(text, -1);    /* Scroll text region down one line */
235           scrollok(text, FALSE);
236           page_length = 0;
237           passed_end = 0;
238           for (i = 0; i < height-4; i++) {
239             if (!i) {
240               print_line(text, 0, width-2);    /* print first line of page */
241               wnoutrefresh(text);
242             }
243             else
244               get_line();    /* Called to update 'end_reached' and 'page' */
245             if (!passed_end)
246               page_length++;
247             if (end_reached && !passed_end)
248               passed_end = 1;
249           }
250 #else
251           print_page(text, height-4, width-2);
252 #endif
253           print_position(dialog, height, width);
254           wmove(dialog, cur_y, cur_x);    /* Restore cursor position */
255           wrefresh(dialog);
256         }
257         break;
258       case 'B':    /* Previous page */
259       case 'b':
260       case KEY_PPAGE:
261         if (!begin_reached) {
262           back_lines(page_length + height-4);
263           print_page(text, height-4, width-2);
264           print_position(dialog, height, width);
265           wmove(dialog, cur_y, cur_x);    /* Restore cursor position */
266           wrefresh(dialog);
267         }
268         break;
269       case 'J':    /* Next line */
270       case 'j':
271       case '\016':      /* ^N */
272       case KEY_DOWN:
273         if (!end_reached) {
274           begin_reached = 0;
275           scrollok(text, TRUE);
276           scroll(text);    /* Scroll text region up one line */
277           scrollok(text, FALSE);
278           print_line(text, height-5, width-2);
279 #ifndef HAVE_NCURSES
280           wmove(text, height-5, 0);
281           waddch(text, ' ');
282           wmove(text, height-5, width-3);
283           waddch(text, ' ');
284 #endif
285           wnoutrefresh(text);
286           print_position(dialog, height, width);
287           wmove(dialog, cur_y, cur_x);    /* Restore cursor position */
288           wrefresh(dialog);
289         }
290         break;
291       case 'F':    /* Next page */
292       case 'f':
293       case KEY_NPAGE:
294         if (!end_reached) {
295           begin_reached = 0;
296           print_page(text, height-4, width-2);
297           print_position(dialog, height, width);
298           wmove(dialog, cur_y, cur_x);    /* Restore cursor position */
299           wrefresh(dialog);
300         }
301         break;
302       case '0':    /* Beginning of line */
303       case 'H':    /* Scroll left */
304       case 'h':
305       case KEY_LEFT:
306         if (hscroll > 0) {
307           if (key == '0')
308             hscroll = 0;
309           else
310             hscroll--;
311           /* Reprint current page to scroll horizontally */
312           back_lines(page_length);
313           print_page(text, height-4, width-2);
314           wmove(dialog, cur_y, cur_x);    /* Restore cursor position */
315           wrefresh(dialog);
316         }
317         break;
318       case 'L':    /* Scroll right */
319       case 'l':
320       case KEY_RIGHT:
321         if (hscroll < MAX_LEN) {
322           hscroll++;
323           /* Reprint current page to scroll horizontally */
324           back_lines(page_length);
325           print_page(text, height-4, width-2);
326           wmove(dialog, cur_y, cur_x);    /* Restore cursor position */
327           wrefresh(dialog);
328         }
329         break;
330       case '/':    /* Forward search */
331       case 'n':    /* Repeat forward search */
332       case '?':    /* Backward search */
333       case 'N':    /* Repeat backward search */
334         /* set search direction */
335         dir = (key == '/' || key == 'n') ? 1 : 0;
336         if (dir ? !end_reached : !begin_reached) {
337           if (key == 'n' || key == 'N') {
338             if (search_term[0] == '\0') {    /* No search term yet */
339               fprintf(stderr, "\a");    /* beep */
340               break;
341             }
342           }
343           else    /* Get search term from user */
344             if (get_search_term(text, search_term, height-4, width-2) == -1) {
345               /* ESC pressed in get_search_term(). Reprint page to clear box */
346               wattrset(text, dialog_attr);
347               back_lines(page_length);
348               print_page(text, height-4, width-2);
349               wmove(dialog, cur_y, cur_x);    /* Restore cursor position */
350               wrefresh(dialog);
351               break;
352             }
353           /* Save variables for restoring in case search term can't be found */
354           tempptr = page;
355           temp = begin_reached;
356           temp1 = end_reached;
357           if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) {
358             endwin();
359             fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n");
360             exit(-1);
361           }
362           fpos -= bytes_read;
363           /* update 'page' to point to next (previous) line before
364              forward (backward) searching */
365           back_lines(dir ? page_length-1 : page_length+1);
366           found = NULL;
367           if (dir)    /* Forward search */
368             while((found = strstr(get_line(), search_term)) == NULL) {
369               if (end_reached)
370                 break;
371             }
372           else    /* Backward search */
373             while((found = strstr(get_line(), search_term)) == NULL) {
374               if (begin_reached)
375                 break;
376               back_lines(2);
377             }
378           if (found == NULL) {    /* not found */
379             fprintf(stderr, "\a");    /* beep */
380             /* Restore program state to that before searching */
381             if (lseek(fd, fpos, SEEK_SET) == -1) {
382               endwin();
383               fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n");
384               exit(-1);
385             }
386             if ((bytes_read = read(fd, buf, BUF_SIZE)) == -1) {
387               endwin();
388               fprintf(stderr, "\nError reading file in dialog_textbox().\n");
389               exit(-1);
390             }
391             buf[bytes_read] = '\0';
392             page = tempptr;
393             begin_reached = temp;
394             end_reached = temp1;
395             /* move 'page' to point to start of current page in order to
396                re-print current page. Note that 'page' always points to
397                start of next page, so this is necessary */
398             back_lines(page_length);
399           }
400           else    /* Search term found */
401             back_lines(1);
402           /* Reprint page */
403           wattrset(text, dialog_attr);
404           print_page(text, height-4, width-2);
405           if (found != NULL)
406             print_position(dialog, height, width);
407           wmove(dialog, cur_y, cur_x);    /* Restore cursor position */
408           wrefresh(dialog);
409         }
410         else    /* no need to find */
411           fprintf(stderr, "\a");    /* beep */
412         break;
413       case ESC:
414         break;
415     case KEY_F(1):
416         display_helpfile();
417         break;
418     }
419   }
420
421   delwin(dialog);
422   free(buf);
423   close(fd);
424   return (key == ESC ? -1 : 0);
425 }
426 /* End of dialog_textbox() */
427
428
429 /*
430  * Go back 'n' lines in text file. Called by dialog_textbox().
431  * 'page' will be updated to point to the desired line in 'buf'.
432  */
433 static void back_lines(int n)
434 {
435   int i, fpos;
436
437   begin_reached = 0;
438   /* We have to distinguish between end_reached and !end_reached since at end
439      of file, the line is not ended by a '\n'. The code inside 'if' basically
440      does a '--page' to move one character backward so as to skip '\n' of the
441      previous line */
442   if (!end_reached) {
443     /* Either beginning of buffer or beginning of file reached? */
444     if (page == buf) {
445       if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) {
446         endwin();
447         fprintf(stderr, "\nError moving file pointer in back_lines().\n");
448         exit(-1);
449       }
450       if (fpos > bytes_read) {    /* Not beginning of file yet */
451         /* We've reached beginning of buffer, but not beginning of file yet,
452            so read previous part of file into buffer. Note that we only
453            move backward for BUF_SIZE/2 bytes, but not BUF_SIZE bytes to
454            avoid re-reading again in print_page() later */
455         /* Really possible to move backward BUF_SIZE/2 bytes? */
456         if (fpos < BUF_SIZE/2 + bytes_read) {
457           /* No, move less then */
458           if (lseek(fd, 0, SEEK_SET) == -1) {
459             endwin();
460             fprintf(stderr, "\nError moving file pointer in back_lines().\n");
461             exit(-1);
462           }
463           page = buf + fpos - bytes_read;
464         }
465         else {    /* Move backward BUF_SIZE/2 bytes */
466           if (lseek(fd, -(BUF_SIZE/2 + bytes_read), SEEK_CUR) == -1) {
467             endwin();
468             fprintf(stderr, "\nError moving file pointer in back_lines().\n");
469             exit(-1);
470           }
471           page = buf + BUF_SIZE/2;
472         }
473         if ((bytes_read = read(fd, buf, BUF_SIZE)) == -1) {
474           endwin();
475           fprintf(stderr, "\nError reading file in back_lines().\n");
476           exit(-1);
477         }
478         buf[bytes_read] = '\0';
479       }
480       else {    /* Beginning of file reached */
481         begin_reached = 1;
482         return;
483       }
484     }
485     if (*(--page) != '\n') {    /* '--page' here */
486       /* Something's wrong... */
487       endwin();
488       fprintf(stderr, "\nInternal error in back_lines().\n");
489       exit(-1);
490     }
491   }
492
493   /* Go back 'n' lines */
494   for (i = 0; i < n; i++)
495     do {
496       if (page == buf) {
497         if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) {
498           endwin();
499           fprintf(stderr, "\nError moving file pointer in back_lines().\n");
500           exit(-1);
501         }
502         if (fpos > bytes_read) {
503           /* Really possible to move backward BUF_SIZE/2 bytes? */
504           if (fpos < BUF_SIZE/2 + bytes_read) {
505             /* No, move less then */
506             if (lseek(fd, 0, SEEK_SET) == -1) {
507               endwin();
508               fprintf(stderr, "\nError moving file pointer in back_lines().\n");
509               exit(-1);
510             }
511             page = buf + fpos - bytes_read;
512           }
513           else {    /* Move backward BUF_SIZE/2 bytes */
514             if (lseek(fd, -(BUF_SIZE/2 + bytes_read), SEEK_CUR) == -1) {
515               endwin();
516               fprintf(stderr, "\nError moving file pointer in back_lines().\n");
517               exit(-1);
518             }
519             page = buf + BUF_SIZE/2;
520           }
521           if ((bytes_read = read(fd, buf, BUF_SIZE)) == -1) {
522             endwin();
523             fprintf(stderr, "\nError reading file in back_lines().\n");
524             exit(-1);
525           }
526           buf[bytes_read] = '\0';
527         }
528         else {    /* Beginning of file reached */
529           begin_reached = 1;
530           return;
531         }
532       }
533     } while (*(--page) != '\n');
534   page++;
535 }
536 /* End of back_lines() */
537
538
539 /*
540  * Print a new page of text. Called by dialog_textbox().
541  */
542 static void print_page(WINDOW *win, int height, int width)
543 {
544   int i, passed_end = 0;
545
546   page_length = 0;
547   for (i = 0; i < height; i++) {
548     print_line(win, i, width);
549     if (!passed_end)
550       page_length++;
551     if (end_reached && !passed_end)
552       passed_end = 1;
553   }
554   wnoutrefresh(win);
555 }
556 /* End of print_page() */
557
558
559 /*
560  * Print a new line of text. Called by dialog_textbox() and print_page().
561  */
562 static void print_line(WINDOW *win, int row, int width)
563 {
564   int i, y, x;
565   unsigned char *line;
566
567   line = get_line();
568   line += MIN(strlen(line),hscroll);    /* Scroll horizontally */
569   wmove(win, row, 0);    /* move cursor to correct line */
570   waddch(win,' ');
571 #ifdef HAVE_NCURSES
572   waddnstr(win, line, MIN(strlen(line),width-2));
573 #else
574   line[MIN(strlen(line),width-2)] = '\0';
575   waddstr(win, line);
576 #endif
577
578   getyx(win, y, x);
579   /* Clear 'residue' of previous line */
580   for (i = 0; i < width-x; i++)
581     waddch(win, ' ');
582 }
583 /* End of print_line() */
584
585
586 /*
587  * Return current line of text. Called by dialog_textbox() and print_line().
588  * 'page' should point to start of current line before calling, and will be
589  * updated to point to start of next line.
590  */
591 static unsigned char *get_line(void)
592 {
593   int i = 0, fpos;
594   static unsigned char line[MAX_LEN+1];
595
596   end_reached = 0;
597   while (*page != '\n') {
598     if (*page == '\0') {    /* Either end of file or end of buffer reached */
599       if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) {
600         endwin();
601         fprintf(stderr, "\nError moving file pointer in get_line().\n");
602         exit(-1);
603       }
604       if (fpos < file_size) {    /* Not end of file yet */
605         /* We've reached end of buffer, but not end of file yet, so read next
606            part of file into buffer */
607         if ((bytes_read = read(fd, buf, BUF_SIZE)) == -1) {
608           endwin();
609           fprintf(stderr, "\nError reading file in get_line().\n");
610           exit(-1);
611         }
612         buf[bytes_read] = '\0';
613         page = buf;
614       }
615       else {
616         if (!end_reached)
617           end_reached = 1;
618         break;
619       }
620     }
621     else
622       if (i < MAX_LEN)
623         line[i++] = *(page++);
624       else {
625         if (i == MAX_LEN)  /* Truncate lines longer than MAX_LEN characters */
626           line[i++] = '\0';
627         page++;
628       }
629   }
630   if (i <= MAX_LEN)
631     line[i] = '\0';
632   if (!end_reached)
633     page++;    /* move pass '\n' */
634
635   return line;
636 }
637 /* End of get_line() */
638
639
640 /*
641  * Display a dialog box and get the search term from user
642  */
643 static int get_search_term(WINDOW *win, unsigned char *search_term, int height, int width)
644 {
645   int x, y, key = 0, first,
646       box_height = 3, box_width = 30;
647
648   x = (width - box_width)/2;
649   y = (height - box_height)/2;
650 #ifdef HAVE_NCURSES
651   if (use_shadow)
652     draw_shadow(win, y, x, box_height, box_width);
653 #endif
654   draw_box(win, y, x, box_height, box_width, dialog_attr, searchbox_border_attr);
655   wattrset(win, searchbox_title_attr);
656   wmove(win, y, x+box_width/2-4);
657   waddstr(win, " Search ");
658   wattrset(win, dialog_attr);
659
660   search_term[0] = '\0';
661
662   first = 1;
663   while (key != ESC) {
664     key = line_edit(win, y+1, x+1, -1, box_width-2, searchbox_attr, first, search_term, 0);
665     first = 0;
666     switch (key) {
667       case '\n':
668         if (search_term[0] != '\0')
669           return 0;
670         break;
671       case ESC:
672         break;
673     }
674   }
675
676   return -1;    /* ESC pressed */
677 }
678 /* End of get_search_term() */
679
680
681 /*
682  * Print current position
683  */
684 static void print_position(WINDOW *win, int height, int width)
685 {
686   int fpos, percent;
687
688   if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) {
689     endwin();
690     fprintf(stderr, "\nError moving file pointer in print_position().\n");
691     exit(-1);
692   }
693   wattrset(win, position_indicator_attr);
694   percent = !file_size ? 100 : ((fpos-bytes_read+page-buf)*100)/file_size;
695   wmove(win, height-3, width-9);
696   wprintw(win, "(%3d%%)", percent);
697 }
698 /* End of print_position() */