Fix potential bugs
[vlc.git] / lxdialog / textbox.c
1 /*
2  *  textbox.c -- implements the text box
3  *
4  *  ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk)
5  *  MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com)
6  *
7  *  This program is free software; you can redistribute it and/or
8  *  modify it under the terms of the GNU General Public License
9  *  as published by the Free Software Foundation; either version 2
10  *  of the License, or (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  */
21
22 #include "dialog.h"
23
24 static void back_lines (int n);
25 static void print_page (WINDOW * win, int height, int width);
26 static void print_line (WINDOW * win, int row, int width);
27 static char *get_line (void);
28 static void print_position (WINDOW * win, int height, int width);
29
30 static int hscroll = 0, fd, file_size, bytes_read;
31 static int begin_reached = 1, end_reached = 0, page_length;
32 static char *buf, *page;
33
34 /*
35  * Display text from a file in a dialog box.
36  */
37 int
38 dialog_textbox (const char *title, const char *file, int height, int width)
39 {
40     int i, x, y, cur_x, cur_y, fpos, key = 0;
41     int passed_end;
42     char search_term[MAX_LEN + 1];
43     WINDOW *dialog, *text;
44
45     search_term[0] = '\0';      /* no search term entered yet */
46
47     /* Open input file for reading */
48     if ((fd = open (file, O_RDONLY)) == -1) {
49         endwin ();
50         fprintf (stderr,
51                  "\nCan't open input file in dialog_textbox().\n");
52         exit (-1);
53     }
54     /* Get file size. Actually, 'file_size' is the real file size - 1,
55        since it's only the last byte offset from the beginning */
56     if ((file_size = lseek (fd, 0, SEEK_END)) == -1) {
57         endwin ();
58         fprintf (stderr, "\nError getting file size in dialog_textbox().\n");
59         exit (-1);
60     }
61     /* Restore file pointer to beginning of file after getting file size */
62     if (lseek (fd, 0, SEEK_SET) == -1) {
63         endwin ();
64         fprintf (stderr, "\nError moving file pointer in dialog_textbox().\n");
65         exit (-1);
66     }
67     /* Allocate space for read buffer */
68     if ((buf = malloc (BUF_SIZE + 1)) == NULL) {
69         endwin ();
70         fprintf (stderr, "\nCan't allocate memory in dialog_textbox().\n");
71         exit (-1);
72     }
73     if ((bytes_read = read (fd, buf, BUF_SIZE)) == -1) {
74         endwin ();
75         fprintf (stderr, "\nError reading file in dialog_textbox().\n");
76         exit (-1);
77     }
78     buf[bytes_read] = '\0';     /* mark end of valid data */
79     page = buf;                 /* page is pointer to start of page to be displayed */
80
81     /* center dialog box on screen */
82     x = (COLS - width) / 2;
83     y = (LINES - height) / 2;
84
85
86     draw_shadow (stdscr, y, x, height, width);
87
88     dialog = newwin (height, width, y, x);
89     keypad (dialog, TRUE);
90
91     /* Create window for text region, used for scrolling text */
92     text = subwin (dialog, height - 4, width - 2, y + 1, x + 1);
93     wattrset (text, dialog_attr);
94     wbkgdset (text, dialog_attr & A_COLOR);
95
96     keypad (text, TRUE);
97
98     /* register the new window, along with its borders */
99     draw_box (dialog, 0, 0, height, width, dialog_attr, border_attr);
100
101     wattrset (dialog, border_attr);
102     mvwaddch (dialog, height-3, 0, ACS_LTEE);
103     for (i = 0; i < width - 2; i++)
104         waddch (dialog, ACS_HLINE);
105     wattrset (dialog, dialog_attr);
106     wbkgdset (dialog, dialog_attr & A_COLOR);
107     waddch (dialog, ACS_RTEE);
108
109     if (title != NULL && strlen(title) >= width-2 ) {
110         /* truncate long title -- mec */
111         char * title2 = malloc(width-2+1);
112         memcpy( title2, title, width-2 );
113         title2[width-2] = '\0';
114         title = title2;
115     }
116
117     if (title != NULL) {
118         wattrset (dialog, title_attr);
119         mvwaddch (dialog, 0, (width - strlen(title))/2 - 1, ' ');
120         waddstr (dialog, (char *)title);
121         waddch (dialog, ' ');
122     }
123     print_button (dialog, " Exit ", height - 2, width / 2 - 4, TRUE);
124     wnoutrefresh (dialog);
125     getyx (dialog, cur_y, cur_x);       /* Save cursor position */
126
127     /* Print first page of text */
128     attr_clear (text, height - 4, width - 2, dialog_attr);
129     print_page (text, height - 4, width - 2);
130     print_position (dialog, height, width);
131     wmove (dialog, cur_y, cur_x);       /* Restore cursor position */
132     wrefresh (dialog);
133
134     while ((key != ESC) && (key != '\n')) {
135         key = wgetch (dialog);
136         switch (key) {
137         case 'E':               /* Exit */
138         case 'e':
139         case 'X':
140         case 'x':
141             delwin (dialog);
142             free (buf);
143             close (fd);
144             return 0;
145         case 'g':               /* First page */
146         case KEY_HOME:
147             if (!begin_reached) {
148                 begin_reached = 1;
149                 /* First page not in buffer? */
150                 if ((fpos = lseek (fd, 0, SEEK_CUR)) == -1) {
151                     endwin ();
152                     fprintf (stderr,
153                       "\nError moving file pointer in dialog_textbox().\n");
154                     exit (-1);
155                 }
156                 if (fpos > bytes_read) {        /* Yes, we have to read it in */
157                     if (lseek (fd, 0, SEEK_SET) == -1) {
158                         endwin ();
159                         fprintf (stderr, "\nError moving file pointer in "
160                                  "dialog_textbox().\n");
161                         exit (-1);
162                     }
163                     if ((bytes_read = read (fd, buf, BUF_SIZE)) == -1) {
164                         endwin ();
165                         fprintf (stderr,
166                              "\nError reading file in dialog_textbox().\n");
167                         exit (-1);
168                     }
169                     buf[bytes_read] = '\0';
170                 }
171                 page = buf;
172                 print_page (text, height - 4, width - 2);
173                 print_position (dialog, height, width);
174                 wmove (dialog, cur_y, cur_x);   /* Restore cursor position */
175                 wrefresh (dialog);
176             }
177             break;
178         case 'G':               /* Last page */
179         case KEY_END:
180
181             end_reached = 1;
182             /* Last page not in buffer? */
183             if ((fpos = lseek (fd, 0, SEEK_CUR)) == -1) {
184                 endwin ();
185                 fprintf (stderr,
186                       "\nError moving file pointer in dialog_textbox().\n");
187                 exit (-1);
188             }
189             if (fpos < file_size) {     /* Yes, we have to read it in */
190                 if (lseek (fd, -BUF_SIZE, SEEK_END) == -1) {
191                     endwin ();
192                     fprintf (stderr,
193                       "\nError moving file pointer in dialog_textbox().\n");
194                     exit (-1);
195                 }
196                 if ((bytes_read = read (fd, buf, BUF_SIZE)) == -1) {
197                     endwin ();
198                     fprintf (stderr,
199                              "\nError reading file in dialog_textbox().\n");
200                     exit (-1);
201                 }
202                 buf[bytes_read] = '\0';
203             }
204             page = buf + bytes_read;
205             back_lines (height - 4);
206             print_page (text, height - 4, width - 2);
207             print_position (dialog, height, width);
208             wmove (dialog, cur_y, cur_x);       /* Restore cursor position */
209             wrefresh (dialog);
210             break;
211         case 'K':               /* Previous line */
212         case 'k':
213         case KEY_UP:
214             if (!begin_reached) {
215                 back_lines (page_length + 1);
216
217                 /* We don't call print_page() here but use scrolling to ensure
218                    faster screen update. However, 'end_reached' and
219                    'page_length' should still be updated, and 'page' should
220                    point to start of next page. This is done by calling
221                    get_line() in the following 'for' loop. */
222                 scrollok (text, TRUE);
223                 wscrl (text, -1);       /* Scroll text region down one line */
224                 scrollok (text, FALSE);
225                 page_length = 0;
226                 passed_end = 0;
227                 for (i = 0; i < height - 4; i++) {
228                     if (!i) {
229                         /* print first line of page */
230                         print_line (text, 0, width - 2);
231                         wnoutrefresh (text);
232                     } else
233                         /* Called to update 'end_reached' and 'page' */
234                         get_line ();
235                     if (!passed_end)
236                         page_length++;
237                     if (end_reached && !passed_end)
238                         passed_end = 1;
239                 }
240
241                 print_position (dialog, height, width);
242                 wmove (dialog, cur_y, cur_x);   /* Restore cursor position */
243                 wrefresh (dialog);
244             }
245             break;
246         case 'B':               /* Previous page */
247         case 'b':
248         case KEY_PPAGE:
249             if (begin_reached)
250                 break;
251             back_lines (page_length + height - 4);
252             print_page (text, height - 4, width - 2);
253             print_position (dialog, height, width);
254             wmove (dialog, cur_y, cur_x);
255             wrefresh (dialog);
256             break;
257         case 'J':               /* Next line */
258         case 'j':
259         case KEY_DOWN:
260             if (!end_reached) {
261                 begin_reached = 0;
262                 scrollok (text, TRUE);
263                 scroll (text);  /* Scroll text region up one line */
264                 scrollok (text, FALSE);
265                 print_line (text, height - 5, width - 2);
266                 wnoutrefresh (text);
267                 print_position (dialog, height, width);
268                 wmove (dialog, cur_y, cur_x);   /* Restore cursor position */
269                 wrefresh (dialog);
270             }
271             break;
272         case KEY_NPAGE:         /* Next page */
273         case ' ':
274             if (end_reached)
275                 break;
276
277             begin_reached = 0;
278             print_page (text, height - 4, width - 2);
279             print_position (dialog, height, width);
280             wmove (dialog, cur_y, cur_x);
281             wrefresh (dialog);
282             break;
283         case '0':               /* Beginning of line */
284         case 'H':               /* Scroll left */
285         case 'h':
286         case KEY_LEFT:
287             if (hscroll <= 0)
288                 break;
289
290             if (key == '0')
291                 hscroll = 0;
292             else
293                 hscroll--;
294             /* Reprint current page to scroll horizontally */
295             back_lines (page_length);
296             print_page (text, height - 4, width - 2);
297             wmove (dialog, cur_y, cur_x);
298             wrefresh (dialog);
299             break;
300         case 'L':               /* Scroll right */
301         case 'l':
302         case KEY_RIGHT:
303             if (hscroll >= MAX_LEN)
304                 break;
305             hscroll++;
306             /* Reprint current page to scroll horizontally */
307             back_lines (page_length);
308             print_page (text, height - 4, width - 2);
309             wmove (dialog, cur_y, cur_x);
310             wrefresh (dialog);
311             break;
312         case ESC:
313             break;
314         }
315     }
316
317     delwin (dialog);
318     free (buf);
319     close (fd);
320     return -1;                  /* ESC pressed */
321 }
322
323 /*
324  * Go back 'n' lines in text file. Called by dialog_textbox().
325  * 'page' will be updated to point to the desired line in 'buf'.
326  */
327 static void
328 back_lines (int n)
329 {
330     int i, fpos;
331
332     begin_reached = 0;
333     /* We have to distinguish between end_reached and !end_reached
334        since at end of file, the line is not ended by a '\n'.
335        The code inside 'if' basically does a '--page' to move one
336        character backward so as to skip '\n' of the previous line */
337     if (!end_reached) {
338         /* Either beginning of buffer or beginning of file reached? */
339         if (page == buf) {
340             if ((fpos = lseek (fd, 0, SEEK_CUR)) == -1) {
341                 endwin ();
342                 fprintf (stderr, "\nError moving file pointer in "
343                          "back_lines().\n");
344                 exit (-1);
345             }
346             if (fpos > bytes_read) {    /* Not beginning of file yet */
347                 /* We've reached beginning of buffer, but not beginning of
348                    file yet, so read previous part of file into buffer.
349                    Note that we only move backward for BUF_SIZE/2 bytes,
350                    but not BUF_SIZE bytes to avoid re-reading again in
351                    print_page() later */
352                 /* Really possible to move backward BUF_SIZE/2 bytes? */
353                 if (fpos < BUF_SIZE / 2 + bytes_read) {
354                     /* No, move less then */
355                     if (lseek (fd, 0, SEEK_SET) == -1) {
356                         endwin ();
357                         fprintf (stderr, "\nError moving file pointer in "
358                                  "back_lines().\n");
359                         exit (-1);
360                     }
361                     page = buf + fpos - bytes_read;
362                 } else {        /* Move backward BUF_SIZE/2 bytes */
363                     if (lseek (fd, -(BUF_SIZE / 2 + bytes_read), SEEK_CUR)
364                         == -1) {
365                         endwin ();
366                         fprintf (stderr, "\nError moving file pointer "
367                                  "in back_lines().\n");
368                         exit (-1);
369                     }
370                     page = buf + BUF_SIZE / 2;
371                 }
372                 if ((bytes_read = read (fd, buf, BUF_SIZE)) == -1) {
373                     endwin ();
374                     fprintf (stderr, "\nError reading file in back_lines().\n");
375                     exit (-1);
376                 }
377                 buf[bytes_read] = '\0';
378             } else {            /* Beginning of file reached */
379                 begin_reached = 1;
380                 return;
381             }
382         }
383         if (*(--page) != '\n') {        /* '--page' here */
384             /* Something's wrong... */
385             endwin ();
386             fprintf (stderr, "\nInternal error in back_lines().\n");
387             exit (-1);
388         }
389     }
390     /* Go back 'n' lines */
391     for (i = 0; i < n; i++)
392         do {
393             if (page == buf) {
394                 if ((fpos = lseek (fd, 0, SEEK_CUR)) == -1) {
395                     endwin ();
396                     fprintf (stderr,
397                           "\nError moving file pointer in back_lines().\n");
398                     exit (-1);
399                 }
400                 if (fpos > bytes_read) {
401                     /* Really possible to move backward BUF_SIZE/2 bytes? */
402                     if (fpos < BUF_SIZE / 2 + bytes_read) {
403                         /* No, move less then */
404                         if (lseek (fd, 0, SEEK_SET) == -1) {
405                             endwin ();
406                             fprintf (stderr, "\nError moving file pointer "
407                                      "in back_lines().\n");
408                             exit (-1);
409                         }
410                         page = buf + fpos - bytes_read;
411                     } else {    /* Move backward BUF_SIZE/2 bytes */
412                         if (lseek (fd, -(BUF_SIZE / 2 + bytes_read),
413                                    SEEK_CUR) == -1) {
414                             endwin ();
415                             fprintf (stderr, "\nError moving file pointer"
416                                      " in back_lines().\n");
417                             exit (-1);
418                         }
419                         page = buf + BUF_SIZE / 2;
420                     }
421                     if ((bytes_read = read (fd, buf, BUF_SIZE)) == -1) {
422                         endwin ();
423                         fprintf (stderr, "\nError reading file in "
424                                  "back_lines().\n");
425                         exit (-1);
426                     }
427                     buf[bytes_read] = '\0';
428                 } else {        /* Beginning of file reached */
429                     begin_reached = 1;
430                     return;
431                 }
432             }
433         } while (*(--page) != '\n');
434     page++;
435 }
436
437 /*
438  * Print a new page of text. Called by dialog_textbox().
439  */
440 static void
441 print_page (WINDOW * win, int height, int width)
442 {
443     int i, passed_end = 0;
444
445     page_length = 0;
446     for (i = 0; i < height; i++) {
447         print_line (win, i, width);
448         if (!passed_end)
449             page_length++;
450         if (end_reached && !passed_end)
451             passed_end = 1;
452     }
453     wnoutrefresh (win);
454 }
455
456 /*
457  * Print a new line of text. Called by dialog_textbox() and print_page().
458  */
459 static void
460 print_line (WINDOW * win, int row, int width)
461 {
462     int y, x;
463     char *line;
464
465     line = get_line ();
466     line += MIN (strlen (line), hscroll);       /* Scroll horizontally */
467     wmove (win, row, 0);        /* move cursor to correct line */
468     waddch (win, ' ');
469     waddnstr (win, line, MIN (strlen (line), width - 2));
470
471     getyx (win, y, x);
472     /* Clear 'residue' of previous line */
473 #if OLD_NCURSES
474     {
475         int i;
476         for (i = 0; i < width - x; i++)
477             waddch (win, ' ');
478     }
479 #else
480     wclrtoeol(win);
481 #endif
482 }
483
484 /*
485  * Return current line of text. Called by dialog_textbox() and print_line().
486  * 'page' should point to start of current line before calling, and will be
487  * updated to point to start of next line.
488  */
489 static char *
490 get_line (void)
491 {
492     int i = 0, fpos;
493     static char line[MAX_LEN + 1];
494
495     end_reached = 0;
496     while (*page != '\n') {
497         if (*page == '\0') {
498             /* Either end of file or end of buffer reached */
499             if ((fpos = lseek (fd, 0, SEEK_CUR)) == -1) {
500                 endwin ();
501                 fprintf (stderr, "\nError moving file pointer in "
502                          "get_line().\n");
503                 exit (-1);
504             }
505             if (fpos < file_size) {     /* Not end of file yet */
506                 /* We've reached end of buffer, but not end of file yet,
507                    so read next part of file into buffer */
508                 if ((bytes_read = read (fd, buf, BUF_SIZE)) == -1) {
509                     endwin ();
510                     fprintf (stderr, "\nError reading file in get_line().\n");
511                     exit (-1);
512                 }
513                 buf[bytes_read] = '\0';
514                 page = buf;
515             } else {
516                 if (!end_reached)
517                     end_reached = 1;
518                 break;
519             }
520         } else if (i < MAX_LEN)
521             line[i++] = *(page++);
522         else {
523             /* Truncate lines longer than MAX_LEN characters */
524             if (i == MAX_LEN)
525                 line[i++] = '\0';
526             page++;
527         }
528     }
529     if (i <= MAX_LEN)
530         line[i] = '\0';
531     if (!end_reached)
532         page++;                 /* move pass '\n' */
533
534     return line;
535 }
536
537 /*
538  * Print current position
539  */
540 static void
541 print_position (WINDOW * win, int height, int width)
542 {
543     int fpos, percent;
544
545     if ((fpos = lseek (fd, 0, SEEK_CUR)) == -1) {
546         endwin ();
547         fprintf (stderr, "\nError moving file pointer in print_position().\n");
548         exit (-1);
549     }
550     wattrset (win, position_indicator_attr);
551     wbkgdset (win, position_indicator_attr & A_COLOR);
552     percent = !file_size ?
553         100 : ((fpos - bytes_read + page - buf) * 100) / file_size;
554     wmove (win, height - 3, width - 9);
555     wprintw (win, "(%3d%%)", percent);
556 }