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