1 /*
2 * Project : tin - a Usenet reader
3 * Module : page.c
4 * Author : I. Lea & R. Skrenta
5 * Created : 1991-04-01
6 * Updated : 2019-07-10
7 * Notes :
8 *
9 * Copyright (c) 1991-2021 Iain Lea <iain@bricbrac.de>, Rich Skrenta <skrenta@pbm.com>
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 *
16 * 1. Redistributions of source code must retain the above copyright notice,
17 * this list of conditions and the following disclaimer.
18 *
19 * 2. Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
22 *
23 * 3. Neither the name of the copyright holder nor the names of its
24 * contributors may be used to endorse or promote products derived from
25 * this software without specific prior written permission.
26 *
27 * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
31 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 * POSSIBILITY OF SUCH DAMAGE.
38 */
39
40
41 #ifndef TIN_H
42 # include "tin.h"
43 #endif /* !TIN_H */
44 #ifndef TCURSES_H
45 # include "tcurses.h"
46 #endif /* !TCURSES_H */
47
48
49 /*
50 * PAGE_HEADER is the size in lines of the article page header
51 * ARTLINES is the number of lines available to display actual article text.
52 */
53 #define PAGE_HEADER 4
54 #define ARTLINES (NOTESLINES - (PAGE_HEADER - INDEX_TOP))
55
56 int curr_line; /* current line in art (indexed from 0) */
57 static FILE *note_fp; /* active stream (raw or cooked) */
58 static int artlines; /* active # of lines in pager */
59 static t_lineinfo *artline; /* active 'lineinfo' data */
60
61 static t_url *url_list;
62
63 t_openartinfo pgart = /* Global context of article open in the pager */
64 {
65 { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, FALSE, NULL},
66 FALSE, 0,
67 NULL, NULL, NULL, NULL,
68 };
69
70 int last_resp; /* previous & current article # in arts[] for '-' command */
71 int this_resp;
72
73 size_t tabwidth = 8;
74
75 static struct t_header *note_h = &pgart.hdr; /* Easy access to article headers */
76
77 static FILE *info_file;
78 static const char *info_title;
79 static int curr_info_line;
80 static int hide_uue; /* set when uuencoded sections are 'hidden' */
81 static int num_info_lines;
82 static int reveal_ctrl_l_lines; /* number of lines (from top) with de-activated ^L */
83 static int rotate; /* 0=normal, 13=rot13 decode */
84 static int scroll_region_top; /* first screen line for displayed message */
85 static int search_line; /* Line to commence next search from */
86 static t_lineinfo *infoline = (t_lineinfo *) 0;
87
88 static t_bool show_all_headers; /* all headers <-> headers in news_headers_to[_not]_display */
89 static t_bool show_raw_article; /* CTRL-H raw <-> cooked article */
90 static t_bool reveal_ctrl_l; /* set when ^L hiding is off */
91
92 /*
93 * Local prototypes
94 */
95 static int build_url_list(void);
96 static int load_article(int new_respnum, struct t_group *group);
97 static int prompt_response(int ch, int curr_respnum);
98 static int scroll_page(int dir);
99 static t_bool deactivate_next_ctrl_l(void);
100 static t_bool activate_last_ctrl_l(void);
101 static t_bool process_url(int n);
102 static t_bool url_page(void);
103 static t_function page_left(void);
104 static t_function page_right(void);
105 static t_function page_mouse_action(t_function (*left_action) (void), t_function (*right_action) (void));
106 static t_function url_left(void);
107 static t_function url_right(void);
108 static void build_url_line(int i);
109 static void draw_page_header(const char *group);
110 static void draw_url_arrow(void);
111 static void free_url_list(void);
112 static void preprocess_info_message(FILE *info_fh);
113 static void print_message_page(FILE *file, t_lineinfo *messageline, size_t messagelines, size_t base_line, size_t begin, size_t end, int help_level);
114 static void process_search(int *lcurr_line, size_t message_lines, size_t screen_lines, int help_level);
115 static void show_url_page(void);
116 static void invoke_metamail(FILE *fp);
117
118 static t_menu urlmenu = { 0, 0, 0, show_url_page, draw_url_arrow, build_url_line };
119
120 #ifdef XFACE_ABLE
121 # define XFACE_SHOW() if (tinrc.use_slrnface) \
122 slrnface_show_xface()
123 # define XFACE_CLEAR() if (tinrc.use_slrnface) \
124 slrnface_clear_xface()
125 # define XFACE_SUPPRESS() if (tinrc.use_slrnface) \
126 slrnface_suppress_xface()
127 #else
128 # define XFACE_SHOW() /*nothing*/
129 # define XFACE_CLEAR() /*nothing*/
130 # define XFACE_SUPPRESS() /*nothing*/
131 #endif /* XFACE_ABLE */
132
133 /*
134 * Scroll visible article part of display down (+ve) or up (-ve)
135 * according to 'dir' (KEYMAP_UP or KEYMAP_DOWN) and tinrc.scroll_lines
136 * >= 1 line count
137 * 0 full page scroll
138 * -1 full page but retain last line of prev page when scrolling
139 * down. Obviously only applies when scrolling down.
140 * -2 half page scroll
141 * Return the offset we scrolled by so that redrawing can be done
142 */
143 static int
scroll_page(int dir)144 scroll_page(
145 int dir)
146 {
147 int i;
148
149 if (tinrc.scroll_lines >= 1)
150 i = tinrc.scroll_lines;
151 else {
152 i = (signal_context == cPage) ? ARTLINES : NOTESLINES;
153 switch (tinrc.scroll_lines) {
154 case 0:
155 break;
156
157 case -1:
158 i--;
159 break;
160
161 case -2:
162 i >>= 1;
163 break;
164 }
165 }
166
167 if (dir == KEYMAP_UP)
168 i = -i;
169
170 #ifdef USE_CURSES
171 scrollok(stdscr, TRUE);
172 #endif /* USE_CURSES */
173 SetScrollRegion(scroll_region_top, NOTESLINES + 1);
174 ScrollScreen(i);
175 SetScrollRegion(0, cLINES);
176 #ifdef USE_CURSES
177 scrollok(stdscr, FALSE);
178 #endif /* USE_CURSES */
179
180 return i;
181 }
182
183
184 /*
185 * Map keypad codes to standard keyboard characters
186 */
187 static t_function
page_left(void)188 page_left(
189 void)
190 {
191 return GLOBAL_QUIT;
192 }
193
194
195 static t_function
page_right(void)196 page_right(
197 void)
198 {
199 return PAGE_NEXT_UNREAD;
200 }
201
202
203 static t_function
page_mouse_action(t_function (* left_action)(void),t_function (* right_action)(void))204 page_mouse_action(
205 t_function (*left_action) (void),
206 t_function (*right_action) (void))
207 {
208 t_function func = NOT_ASSIGNED;
209
210 switch (xmouse) {
211 case MOUSE_BUTTON_1:
212 if (xrow < PAGE_HEADER || xrow >= cLINES - 1)
213 func = GLOBAL_PAGE_DOWN;
214 else
215 func = right_action();
216 break;
217
218 case MOUSE_BUTTON_2:
219 if (xrow < PAGE_HEADER || xrow >= cLINES - 1)
220 func = GLOBAL_PAGE_UP;
221 else
222 func = left_action();
223 break;
224
225 case MOUSE_BUTTON_3:
226 func = SPECIAL_MOUSE_TOGGLE;
227 break;
228
229 default:
230 break;
231 }
232 return func;
233 }
234
235
236 /*
237 * Make hidden part of article after ^L visible.
238 * Returns:
239 * FALSE no ^L found, no changes
240 * TRUE ^L found and displayed page must be updated
241 * (draw_page must be called)
242 */
243 static t_bool
deactivate_next_ctrl_l(void)244 deactivate_next_ctrl_l(
245 void)
246 {
247 int i;
248 int end = curr_line + ARTLINES;
249
250 if (reveal_ctrl_l)
251 return FALSE;
252 if (end > artlines)
253 end = artlines;
254 for (i = reveal_ctrl_l_lines + 1; i < end; i++)
255 if (artline[i].flags & C_CTRLL) {
256 reveal_ctrl_l_lines = i;
257 return TRUE;
258 }
259 reveal_ctrl_l_lines = end - 1;
260 return FALSE;
261 }
262
263
264 /*
265 * Re-hide revealed part of article after last ^L
266 * that is currently displayed.
267 * Returns:
268 * FALSE no ^L found, no changes
269 * TRUE ^L found and displayed page must be updated
270 * (draw_page must be called)
271 */
272 static t_bool
activate_last_ctrl_l(void)273 activate_last_ctrl_l(
274 void)
275 {
276 int i;
277
278 if (reveal_ctrl_l)
279 return FALSE;
280 for (i = reveal_ctrl_l_lines; i >= curr_line; i--)
281 if (artline[i].flags & C_CTRLL) {
282 reveal_ctrl_l_lines = i - 1;
283 return TRUE;
284 }
285 reveal_ctrl_l_lines = curr_line - 1;
286 return FALSE;
287 }
288
289
290 /*
291 * The main routine for viewing articles
292 * Returns:
293 * >=0 normal exit - return a new base[] note
294 * <0 indicates some unusual condition. See GRP_* in tin.h
295 * GRP_QUIT User is doing a 'Q'
296 * GRP_RETSELECT Back to selection level due to 'T' command
297 * GRP_ARTUNAVAIL We didn't make it into the art
298 * don't bother fixing the screen up
299 * GRP_ARTABORT User 'q'uit load of article
300 * GRP_GOTOTHREAD To thread menu due to 'l' command
301 * GRP_NEXT Catchup with 'c'
302 * GRP_NEXTUNREAD " " 'C'
303 */
304 int
show_page(struct t_group * group,int start_respnum,int * threadnum)305 show_page(
306 struct t_group *group,
307 int start_respnum, /* index into arts[] */
308 int *threadnum) /* to allow movement in thread mode */
309 {
310 char buf[LEN];
311 char key[MAXKEYLEN];
312 int i, j, n = 0;
313 int art_type = GROUP_TYPE_NEWS;
314 int hide_uue_tmp;
315 t_artnum old_artnum = T_ARTNUM_CONST(0);
316 t_bool mouse_click_on = TRUE;
317 t_bool repeat_search;
318 t_function func;
319
320 if (group->attribute->mailing_list != NULL)
321 art_type = GROUP_TYPE_MAIL;
322
323 /*
324 * Peek to see if the pager started due to a body search
325 * Stop load_article() changing context again
326 */
327 if (srch_lineno != -1)
328 this_resp = start_respnum;
329
330 if ((i = load_article(start_respnum, group)) < 0)
331 return i;
332
333 if (srch_lineno != -1)
334 process_search(&curr_line, artlines, ARTLINES, PAGE_LEVEL);
335
336 forever {
337 if ((func = handle_keypad(page_left, page_right, page_mouse_action, page_keys)) == GLOBAL_SEARCH_REPEAT) {
338 func = last_search;
339 repeat_search = TRUE;
340 } else
341 repeat_search = FALSE;
342
343 switch (func) {
344 case GLOBAL_ABORT: /* Abort */
345 break;
346
347 case DIGIT_1:
348 case DIGIT_2:
349 case DIGIT_3:
350 case DIGIT_4:
351 case DIGIT_5:
352 case DIGIT_6:
353 case DIGIT_7:
354 case DIGIT_8:
355 case DIGIT_9:
356 if (!HAS_FOLLOWUPS(which_thread(this_resp)))
357 info_message(_(txt_no_responses));
358 else {
359 if ((n = prompt_response(func_to_key(func, page_keys), this_resp)) != -1) {
360 XFACE_CLEAR();
361 if ((i = load_article(n, group)) < 0)
362 return i;
363 }
364 }
365 break;
366
367 #ifndef NO_SHELL_ESCAPE
368 case GLOBAL_SHELL_ESCAPE:
369 XFACE_CLEAR();
370 shell_escape();
371 draw_page(group->name, 0);
372 break;
373 #endif /* !NO_SHELL_ESCAPE */
374
375 case SPECIAL_MOUSE_TOGGLE:
376 if (mouse_click_on)
377 set_xclick_off();
378 else
379 set_xclick_on();
380 mouse_click_on = bool_not(mouse_click_on);
381 break;
382
383 case GLOBAL_PAGE_UP:
384 if (activate_last_ctrl_l())
385 draw_page(group->name, 0);
386 else {
387 if (curr_line == 0)
388 info_message(_(txt_begin_of_art));
389 else {
390 curr_line -= ((tinrc.scroll_lines == -2) ? ARTLINES / 2 : ARTLINES);
391 draw_page(group->name, 0);
392 }
393 }
394 break;
395
396 case GLOBAL_PAGE_DOWN: /* page down or next response */
397 case PAGE_NEXT_UNREAD:
398 if (!((func == PAGE_NEXT_UNREAD) && (tinrc.goto_next_unread & GOTO_NEXT_UNREAD_TAB)) && deactivate_next_ctrl_l())
399 draw_page(group->name, 0);
400 else {
401 if (curr_line + ARTLINES >= artlines) { /* End is already on screen */
402 switch (func) {
403 case PAGE_NEXT_UNREAD: /* <TAB> */
404 goto page_goto_next_unread;
405
406 case GLOBAL_PAGE_DOWN:
407 if (tinrc.goto_next_unread & GOTO_NEXT_UNREAD_PGDN)
408 goto page_goto_next_unread;
409 break;
410
411 default: /* to keep gcc quiet */
412 break;
413 }
414 info_message(_(txt_end_of_art));
415 } else {
416 if ((func == PAGE_NEXT_UNREAD) && (tinrc.goto_next_unread & GOTO_NEXT_UNREAD_TAB))
417 goto page_goto_next_unread;
418
419 curr_line += ((tinrc.scroll_lines == -2) ? ARTLINES / 2 : ARTLINES);
420
421 if (tinrc.scroll_lines == -1) /* formerly show_last_line_prev_page */
422 curr_line--;
423 draw_page(group->name, 0);
424 }
425 }
426 break;
427
428 page_goto_next_unread:
429 XFACE_CLEAR();
430 if ((n = next_unread(next_response(this_resp))) == -1)
431 return (which_thread(this_resp));
432 if ((i = load_article(n, group)) < 0)
433 return i;
434 break;
435
436 case GLOBAL_FIRST_PAGE: /* beginning of article */
437 if (reveal_ctrl_l_lines > -1 || curr_line != 0) {
438 reveal_ctrl_l_lines = -1;
439 curr_line = 0;
440 draw_page(group->name, 0);
441 }
442 break;
443
444 case GLOBAL_LAST_PAGE: /* end of article */
445 if (reveal_ctrl_l_lines < artlines - 1 || curr_line + ARTLINES != artlines) {
446 reveal_ctrl_l_lines = artlines - 1;
447 /* Display a full last page for neatness */
448 curr_line = artlines - ARTLINES;
449 draw_page(group->name, 0);
450 }
451 break;
452
453 case GLOBAL_LINE_UP:
454 if (activate_last_ctrl_l())
455 draw_page(group->name, 0);
456 else {
457 if (curr_line == 0) {
458 info_message(_(txt_begin_of_art));
459 break;
460 }
461
462 i = scroll_page(KEYMAP_UP);
463 curr_line += i;
464 draw_page(group->name, i);
465 }
466 break;
467
468 case GLOBAL_LINE_DOWN:
469 if (deactivate_next_ctrl_l())
470 draw_page(group->name, 0);
471 else {
472 if (curr_line + ARTLINES >= artlines) {
473 info_message(_(txt_end_of_art));
474 break;
475 }
476
477 i = scroll_page(KEYMAP_DOWN);
478 curr_line += i;
479 draw_page(group->name, i);
480 }
481 break;
482
483 case GLOBAL_LAST_VIEWED: /* show last viewed article */
484 if (last_resp < 0 || (which_thread(last_resp) == -1)) {
485 info_message(_(txt_no_last_message));
486 break;
487 }
488 if ((i = load_article(last_resp, group)) < 0) {
489 XFACE_CLEAR();
490 return i;
491 }
492 break;
493
494 case GLOBAL_LOOKUP_MESSAGEID: /* Goto article by Message-ID */
495 if ((n = prompt_msgid()) != ART_UNAVAILABLE) {
496 if ((i = load_article(n, group)) < 0) {
497 XFACE_CLEAR();
498 return i;
499 }
500 }
501 break;
502
503 case PAGE_GOTO_PARENT: /* Goto parent of this article */
504 {
505 struct t_msgid *parent = arts[this_resp].refptr->parent;
506
507 if (parent == NULL) {
508 info_message(_(txt_art_parent_none));
509 break;
510 }
511
512 if (parent->article == ART_UNAVAILABLE) {
513 info_message(_(txt_art_parent_unavail));
514 break;
515 }
516
517 if (arts[parent->article].killed && tinrc.kill_level == KILL_NOTHREAD) {
518 info_message(_(txt_art_parent_killed));
519 break;
520 }
521
522 if ((i = load_article(parent->article, group)) < 0) {
523 XFACE_CLEAR();
524 return i;
525 }
526
527 break;
528 }
529
530 case GLOBAL_PIPE: /* pipe article/thread/tagged arts to command */
531 XFACE_SUPPRESS();
532 feed_articles(FEED_PIPE, PAGE_LEVEL, NOT_ASSIGNED, group, this_resp);
533 XFACE_SHOW();
534 break;
535
536 case PAGE_MAIL: /* mail article/thread/tagged articles to somebody */
537 XFACE_SUPPRESS();
538 feed_articles(FEED_MAIL, PAGE_LEVEL, NOT_ASSIGNED, group, this_resp);
539 XFACE_SHOW();
540 break;
541
542 #ifndef DISABLE_PRINTING
543 case GLOBAL_PRINT: /* output art/thread/tagged arts to printer */
544 XFACE_SUPPRESS();
545 feed_articles(FEED_PRINT, PAGE_LEVEL, NOT_ASSIGNED, group, this_resp);
546 XFACE_SHOW();
547 break;
548 #endif /* !DISABLE_PRINTING */
549
550 case PAGE_REPOST: /* repost current article */
551 if (can_post) {
552 XFACE_SUPPRESS();
553 feed_articles(FEED_REPOST, PAGE_LEVEL, NOT_ASSIGNED, group, this_resp);
554 XFACE_SHOW();
555 } else
556 info_message(_(txt_cannot_post));
557 break;
558
559 case PAGE_SAVE: /* save article/thread/tagged articles */
560 XFACE_SUPPRESS();
561 feed_articles(FEED_SAVE, PAGE_LEVEL, NOT_ASSIGNED, group, this_resp);
562 XFACE_SHOW();
563 break;
564
565 case PAGE_AUTOSAVE: /* Auto-save articles without prompting */
566 if (grpmenu.curr >= 0) {
567 XFACE_SUPPRESS();
568 feed_articles(FEED_AUTOSAVE, PAGE_LEVEL, NOT_ASSIGNED, group, (int) base[grpmenu.curr]);
569 XFACE_SHOW();
570 }
571 break;
572
573 case GLOBAL_SEARCH_REPEAT:
574 info_message(_(txt_no_prev_search));
575 break;
576
577 case GLOBAL_SEARCH_SUBJECT_FORWARD: /* search in article */
578 case GLOBAL_SEARCH_SUBJECT_BACKWARD:
579 if (search_article((func == GLOBAL_SEARCH_SUBJECT_FORWARD), repeat_search, search_line, artlines, artline, reveal_ctrl_l_lines, note_fp) == -1)
580 break;
581
582 if (func == GLOBAL_SEARCH_SUBJECT_BACKWARD && !reveal_ctrl_l) {
583 reveal_ctrl_l_lines = curr_line + ARTLINES - 1;
584 draw_page(group->name, 0);
585 }
586 process_search(&curr_line, artlines, ARTLINES, PAGE_LEVEL);
587 break;
588
589 case GLOBAL_SEARCH_BODY: /* article body search */
590 if ((n = search_body(group, this_resp, repeat_search)) != -1) {
591 this_resp = n; /* Stop load_article() changing context again */
592 if ((i = load_article(n, group)) < 0) {
593 XFACE_CLEAR();
594 return i;
595 }
596 process_search(&curr_line, artlines, ARTLINES, PAGE_LEVEL);
597 }
598 break;
599
600 case PAGE_TOP_THREAD: /* first article in current thread */
601 if (arts[this_resp].prev >= 0) {
602 if ((n = which_thread(this_resp)) >= 0 && base[n] != this_resp) {
603 assert(n < grpmenu.max);
604 if ((i = load_article(base[n], group)) < 0) {
605 XFACE_CLEAR();
606 return i;
607 }
608 }
609 }
610 break;
611
612 case PAGE_BOTTOM_THREAD: /* last article in current thread */
613 for (i = this_resp; i >= 0; i = arts[i].thread)
614 n = i;
615
616 if (n != this_resp) {
617 if ((i = load_article(n, group)) < 0) {
618 XFACE_CLEAR();
619 return i;
620 }
621 }
622 break;
623
624 case PAGE_NEXT_THREAD: /* start of next thread */
625 XFACE_CLEAR();
626 if ((n = next_thread(this_resp)) == -1)
627 return (which_thread(this_resp));
628 if ((i = load_article(n, group)) < 0)
629 return i;
630 break;
631
632 #ifdef HAVE_PGP_GPG
633 case PAGE_PGP_CHECK_ARTICLE:
634 XFACE_SUPPRESS();
635 if (pgp_check_article(&pgart))
636 draw_page(group->name, 0);
637 XFACE_SHOW();
638 break;
639 #endif /* HAVE_PGP_GPG */
640
641 case PAGE_TOGGLE_HEADERS: /* toggle display of all headers */
642 XFACE_CLEAR();
643 show_all_headers = bool_not(show_all_headers);
644 resize_article(TRUE, &pgart); /* Also recooks it.. */
645 curr_line = 0;
646 draw_page(group->name, 0);
647 break;
648
649 case PAGE_TOGGLE_RAW: /* toggle display of whole 'raw' article */
650 XFACE_CLEAR();
651 toggle_raw(group);
652 break;
653
654 case PAGE_TOGGLE_TEX2ISO: /* toggle german TeX to ISO latin1 style conversion */
655 if (((group->attribute->tex2iso_conv) = !(group->attribute->tex2iso_conv)))
656 pgart.tex2iso = is_art_tex_encoded(pgart.raw);
657 else
658 pgart.tex2iso = FALSE;
659
660 resize_article(TRUE, &pgart); /* Also recooks it.. */
661 draw_page(group->name, 0);
662 info_message(_(txt_toggled_tex2iso), txt_onoff[group->attribute->tex2iso_conv != FALSE ? 1 : 0]);
663 break;
664
665 case PAGE_TOGGLE_TABS: /* toggle tab stops 8 vs 4 */
666 tabwidth = (tabwidth == 8) ? 4 : 8;
667 resize_article(TRUE, &pgart); /* Also recooks it.. */
668 draw_page(group->name, 0);
669 info_message(_(txt_toggled_tabwidth), tabwidth);
670 break;
671
672 case PAGE_TOGGLE_UUE: /* toggle display of uuencoded sections */
673 hide_uue = (hide_uue + 1) % (UUE_ALL + 1);
674 resize_article(TRUE, &pgart); /* Also recooks it.. */
675 /*
676 * If we hid uue and are off the end of the article, reposition to
677 * show last page for neatness
678 */
679 if (hide_uue && curr_line + ARTLINES > artlines)
680 curr_line = artlines - ARTLINES;
681 draw_page(group->name, 0);
682 /* TODO: info_message()? */
683 break;
684
685 case PAGE_REVEAL: /* toggle hiding after ^L */
686 reveal_ctrl_l = bool_not(reveal_ctrl_l);
687 if (!reveal_ctrl_l) { /* switched back to active ^L's */
688 reveal_ctrl_l_lines = -1;
689 curr_line = 0;
690 } else
691 reveal_ctrl_l_lines = artlines - 1;
692 draw_page(group->name, 0);
693 /* TODO: info_message()? */
694 break;
695
696 case GLOBAL_QUICK_FILTER_SELECT: /* quickly auto-select article */
697 case GLOBAL_QUICK_FILTER_KILL: /* quickly kill article */
698 if (quick_filter(func, group, &arts[this_resp])) {
699 old_artnum = arts[this_resp].artnum;
700 unfilter_articles(group);
701 filter_articles(group);
702 make_threads(group, FALSE);
703 if ((n = find_artnum(old_artnum)) == -1 || which_thread(n) == -1) /* We have lost the thread */
704 return GRP_KILLED;
705 this_resp = n;
706 draw_page(group->name, 0);
707 info_message((func == GLOBAL_QUICK_FILTER_KILL) ? _(txt_info_add_kill) : _(txt_info_add_select));
708 }
709 break;
710
711 case GLOBAL_MENU_FILTER_SELECT: /* auto-select article menu */
712 case GLOBAL_MENU_FILTER_KILL: /* kill article menu */
713 XFACE_CLEAR();
714 if (filter_menu(func, group, &arts[this_resp])) {
715 old_artnum = arts[this_resp].artnum;
716 unfilter_articles(group);
717 filter_articles(group);
718 make_threads(group, FALSE);
719 if ((n = find_artnum(old_artnum)) == -1 || which_thread(n) == -1) /* We have lost the thread */
720 return GRP_KILLED;
721 this_resp = n;
722 }
723 draw_page(group->name, 0);
724 break;
725
726 case GLOBAL_EDIT_FILTER:
727 XFACE_CLEAR();
728 if (invoke_editor(filter_file, filter_file_offset, NULL)) {
729 old_artnum = arts[this_resp].artnum;
730 unfilter_articles(group);
731 (void) read_filter_file(filter_file);
732 filter_articles(group);
733 make_threads(group, FALSE);
734 if ((n = find_artnum(old_artnum)) == -1 || which_thread(n) == -1) /* We have lost the thread */
735 return GRP_KILLED;
736 this_resp = n;
737 }
738 draw_page(group->name, 0);
739 break;
740
741 case GLOBAL_REDRAW_SCREEN: /* redraw current page of article */
742 my_retouch();
743 draw_page(group->name, 0);
744 break;
745
746 case PAGE_TOGGLE_ROT13: /* toggle rot-13 mode */
747 rotate = rotate ? 0 : 13;
748 draw_page(group->name, 0);
749 info_message(_(txt_toggled_rot13));
750 break;
751
752 case GLOBAL_SEARCH_AUTHOR_FORWARD: /* author search forward */
753 case GLOBAL_SEARCH_AUTHOR_BACKWARD: /* author search backward */
754 if ((n = search(func, this_resp, repeat_search)) < 0)
755 break;
756 if ((i = load_article(n, group)) < 0) {
757 XFACE_CLEAR();
758 return i;
759 }
760 break;
761
762 case CATCHUP: /* catchup - mark read, goto next */
763 case CATCHUP_NEXT_UNREAD: /* goto next unread */
764 if (group->attribute->thread_articles == THREAD_NONE)
765 snprintf(buf, sizeof(buf), _(txt_mark_art_read), (func == CATCHUP_NEXT_UNREAD) ? _(txt_enter_next_unread_art) : "");
766 else
767 snprintf(buf, sizeof(buf), _(txt_mark_thread_read), (func == CATCHUP_NEXT_UNREAD) ? _(txt_enter_next_thread) : "");
768 if ((!TINRC_CONFIRM_ACTION) || prompt_yn(buf, TRUE) == 1) {
769 thd_mark_read(group, base[which_thread(this_resp)]);
770 XFACE_CLEAR();
771 return (func == CATCHUP_NEXT_UNREAD) ? GRP_NEXTUNREAD : GRP_NEXT;
772 }
773 break;
774
775 case MARK_THREAD_UNREAD:
776 thd_mark_unread(group, base[which_thread(this_resp)]);
777 if (group->attribute->thread_articles != THREAD_NONE)
778 info_message(_(txt_marked_as_unread), _(txt_thread_upper));
779 else
780 info_message(_(txt_marked_as_unread), _(txt_article_upper));
781 break;
782
783 case PAGE_CANCEL: /* cancel an article */
784 if (can_post || art_type != GROUP_TYPE_NEWS) {
785 XFACE_SUPPRESS();
786 if (cancel_article(group, &arts[this_resp], this_resp))
787 draw_page(group->name, 0);
788 XFACE_SHOW();
789 } else
790 info_message(_(txt_cannot_post));
791 break;
792
793 case PAGE_EDIT_ARTICLE: /* edit an article (mailgroup only) */
794 XFACE_SUPPRESS();
795 if (art_edit(group, &arts[this_resp]))
796 draw_page(group->name, 0);
797 XFACE_SHOW();
798 break;
799
800 case PAGE_FOLLOWUP_QUOTE: /* post a followup to this article */
801 case PAGE_FOLLOWUP_QUOTE_HEADERS:
802 case PAGE_FOLLOWUP:
803 if (!can_post && art_type == GROUP_TYPE_NEWS) {
804 info_message(_(txt_cannot_post));
805 break;
806 }
807 XFACE_CLEAR();
808 (void) post_response(group->name, this_resp,
809 (func == PAGE_FOLLOWUP_QUOTE || func == PAGE_FOLLOWUP_QUOTE_HEADERS) ? TRUE : FALSE,
810 func == PAGE_FOLLOWUP_QUOTE_HEADERS ? TRUE : FALSE, show_raw_article);
811 draw_page(group->name, 0);
812 break;
813
814 case GLOBAL_HELP: /* help */
815 XFACE_CLEAR();
816 show_help_page(PAGE_LEVEL, _(txt_art_pager_com));
817 draw_page(group->name, 0);
818 break;
819
820 case GLOBAL_TOGGLE_HELP_DISPLAY: /* toggle mini help menu */
821 toggle_mini_help(PAGE_LEVEL);
822 draw_page(group->name, 0);
823 break;
824
825 case GLOBAL_QUIT: /* return to index page */
826 return_to_index:
827 XFACE_CLEAR();
828 i = which_thread(this_resp);
829 if (threadnum)
830 *threadnum = which_response(this_resp);
831
832 return i;
833
834 case GLOBAL_TOGGLE_INVERSE_VIDEO: /* toggle inverse video */
835 toggle_inverse_video();
836 draw_page(group->name, 0);
837 show_inverse_video_status();
838 break;
839
840 #ifdef HAVE_COLOR
841 case GLOBAL_TOGGLE_COLOR: /* toggle color */
842 if (toggle_color()) {
843 draw_page(group->name, 0);
844 show_color_status();
845 }
846 break;
847 #endif /* HAVE_COLOR */
848
849 case PAGE_LIST_THREAD: /* -> thread page that this article is in */
850 XFACE_CLEAR();
851 fixup_thread(this_resp, FALSE);
852 return GRP_GOTOTHREAD;
853
854 case GLOBAL_OPTION_MENU: /* option menu */
855 XFACE_CLEAR();
856 old_artnum = arts[this_resp].artnum;
857 config_page(group->name, signal_context);
858 if ((this_resp = find_artnum(old_artnum)) == -1 || which_thread(this_resp) == -1) { /* We have lost the thread */
859 pos_first_unread_thread();
860 return GRP_EXIT;
861 }
862 fixup_thread(this_resp, FALSE);
863 draw_page(group->name, 0);
864 break;
865
866 case PAGE_NEXT_ARTICLE: /* skip to next article */
867 XFACE_CLEAR();
868 if ((n = next_response(this_resp)) == -1)
869 return (which_thread(this_resp));
870
871 if ((i = load_article(n, group)) < 0)
872 return i;
873 break;
874
875 case PAGE_MARK_THREAD_READ: /* mark rest of thread as read */
876 thd_mark_read(group, this_resp);
877 if ((n = next_unread(next_response(this_resp))) == -1)
878 goto return_to_index;
879 if ((i = load_article(n, group)) < 0) {
880 XFACE_CLEAR();
881 return i;
882 }
883 break;
884
885 case PAGE_NEXT_UNREAD_ARTICLE: /* next unread article */
886 goto page_goto_next_unread;
887
888 case PAGE_PREVIOUS_ARTICLE: /* previous article */
889 XFACE_CLEAR();
890 if ((n = prev_response(this_resp)) == -1)
891 return this_resp;
892
893 if ((i = load_article(n, group)) < 0)
894 return i;
895 break;
896
897 case PAGE_PREVIOUS_UNREAD_ARTICLE: /* previous unread article */
898 if ((n = prev_unread(prev_response(this_resp))) == -1)
899 info_message(_(txt_no_prev_unread_art));
900 else {
901 if ((i = load_article(n, group)) < 0) {
902 XFACE_CLEAR();
903 return i;
904 }
905 }
906 break;
907
908 case GLOBAL_QUIT_TIN: /* quit */
909 XFACE_CLEAR();
910 return GRP_QUIT;
911
912 case PAGE_REPLY_QUOTE: /* reply to author through mail */
913 case PAGE_REPLY_QUOTE_HEADERS:
914 case PAGE_REPLY:
915 XFACE_CLEAR();
916 mail_to_author(group->name, this_resp, (func == PAGE_REPLY_QUOTE || func == PAGE_REPLY_QUOTE_HEADERS) ? TRUE : FALSE, func == PAGE_REPLY_QUOTE_HEADERS ? TRUE : FALSE, show_raw_article);
917 draw_page(group->name, 0);
918 break;
919
920 case PAGE_TAG: /* tag/untag article for saving */
921 tag_article(this_resp);
922 break;
923
924 case PAGE_GROUP_SELECT: /* return to group selection page */
925 #if 0
926 /* Hasn't been used since tin 1.1 PL4 */
927 if (filter_state == FILTERING) {
928 filter_articles(group);
929 make_threads(group, FALSE);
930 }
931 #endif /* 0 */
932 XFACE_CLEAR();
933 return GRP_RETSELECT;
934
935 case GLOBAL_VERSION:
936 info_message(cvers);
937 break;
938
939 case GLOBAL_POST: /* post a basenote */
940 XFACE_SUPPRESS();
941 if (post_article(group->name))
942 draw_page(group->name, 0);
943 XFACE_SHOW();
944 break;
945
946 case GLOBAL_POSTPONED: /* post postponed article */
947 if (can_post || art_type != GROUP_TYPE_NEWS) {
948 XFACE_SUPPRESS();
949 if (pickup_postponed_articles(FALSE, FALSE))
950 draw_page(group->name, 0);
951 XFACE_SHOW();
952 } else
953 info_message(_(txt_cannot_post));
954 break;
955
956 case GLOBAL_DISPLAY_POST_HISTORY: /* display messages posted by user */
957 XFACE_SUPPRESS();
958 if (user_posted_messages())
959 draw_page(group->name, 0);
960 XFACE_SHOW();
961 break;
962
963 case MARK_ARTICLE_UNREAD: /* mark article as unread(to return) */
964 art_mark(group, &arts[this_resp], ART_WILL_RETURN);
965 info_message(_(txt_marked_as_unread), _(txt_article_upper));
966 break;
967
968 case PAGE_SKIP_INCLUDED_TEXT: /* skip included text */
969 for (i = j = curr_line; i < artlines; i++) {
970 if (artline[i].flags & (C_QUOTE1 | C_QUOTE2 | C_QUOTE3)) {
971 j = i;
972 break;
973 }
974 }
975
976 for (; j < artlines; j++) {
977 if (!(artline[j].flags & (C_QUOTE1 | C_QUOTE2 | C_QUOTE3)))
978 break;
979 }
980
981 if (j != curr_line) {
982 curr_line = j;
983 draw_page(group->name, 0);
984 }
985 break;
986
987 case GLOBAL_TOGGLE_INFO_LAST_LINE: /* this is _not_ correct, we do not toggle status here */
988 info_message("%s", arts[this_resp].subject);
989 break;
990
991 case PAGE_TOGGLE_HIGHLIGHTING:
992 word_highlight = bool_not(word_highlight);
993 draw_page(group->name, 0);
994 info_message(_(txt_toggled_high), txt_onoff[word_highlight != FALSE ? 1 : 0]);
995 break;
996
997 case PAGE_VIEW_ATTACHMENTS:
998 XFACE_SUPPRESS();
999 hide_uue_tmp = hide_uue;
1000 hide_uue = UUE_NO;
1001 resize_article(TRUE, &pgart);
1002 attachment_page(&pgart);
1003 hide_uue = hide_uue_tmp;
1004 resize_article(TRUE, &pgart);
1005 draw_page(group->name, 0);
1006 XFACE_SHOW();
1007 break;
1008
1009 case PAGE_VIEW_URL:
1010 if (!show_raw_article) { /* cooked mode? */
1011 t_bool success;
1012
1013 XFACE_SUPPRESS();
1014 resize_article(FALSE, &pgart); /* unbreak long lines */
1015 success = url_page();
1016 resize_article(TRUE, &pgart); /* rebreak long lines */
1017 draw_page(group->name, 0);
1018 if (!success)
1019 info_message(_(txt_url_done));
1020 XFACE_SHOW();
1021 }
1022 break;
1023
1024 default:
1025 info_message(_(txt_bad_command), printascii(key, func_to_key(GLOBAL_HELP, page_keys)));
1026 }
1027 }
1028 /* NOTREACHED */
1029 return GRP_ARTUNAVAIL;
1030 }
1031
1032
1033 static void
print_message_page(FILE * file,t_lineinfo * messageline,size_t messagelines,size_t base_line,size_t begin,size_t end,int help_level)1034 print_message_page(
1035 FILE *file,
1036 t_lineinfo *messageline,
1037 size_t messagelines,
1038 size_t base_line,
1039 size_t begin,
1040 size_t end,
1041 int help_level)
1042 {
1043 char *line;
1044 char *p;
1045 int bytes;
1046 size_t i = begin;
1047 t_lineinfo *curr;
1048
1049 for (; i < end; i++) {
1050 if (base_line + i >= messagelines) /* ran out of message */
1051 break;
1052
1053 curr = &messageline[base_line + i];
1054
1055 if (fseek(file, curr->offset, SEEK_SET) != 0)
1056 break;
1057
1058 if ((line = tin_fgets(file, FALSE)) == NULL)
1059 break; /* ran out of message */
1060
1061 if ((help_level == INFO_PAGER) && (strwidth(line) >= cCOLS - 1))
1062 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1063 {
1064 char *tmp, *f, *t;
1065
1066 f = tmp = strunc(line, cCOLS - 1);
1067 t = line;
1068 while (*f)
1069 *t++ = *f++;
1070 *t = '\0';
1071 free(tmp);
1072 }
1073 #else
1074 line[cCOLS - 1] = '\0';
1075 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1076
1077 /*
1078 * use the offsets gained while doing line wrapping to
1079 * determine the correct position to truncate the line
1080 */
1081 if ((help_level != INFO_PAGER) && (base_line + i < messagelines - 1)) { /* not last line of message */
1082 bytes = (curr + 1)->offset - curr->offset;
1083 line[bytes] = '\0';
1084 }
1085
1086 /*
1087 * rotN encoding on body and sig data only
1088 */
1089 if ((rotate != 0) && ((curr->flags & (C_BODY | C_SIG)) || show_raw_article)) {
1090 for (p = line; *p; p++) {
1091 if (*p >= 'A' && *p <= 'Z')
1092 *p = (*p - 'A' + rotate) % 26 + 'A';
1093 else if (*p >= 'a' && *p <= 'z')
1094 *p = (*p - 'a' + rotate) % 26 + 'a';
1095 }
1096 }
1097
1098 strip_line(line);
1099
1100 #ifndef USE_CURSES
1101 snprintf(screen[i + scroll_region_top].col, cCOLS, "%s" cCRLF, line);
1102 #endif /* !USE_CURSES */
1103
1104 MoveCursor(i + scroll_region_top, 0);
1105 draw_pager_line(line, curr->flags, show_raw_article);
1106
1107 /*
1108 * Highlight URL's and mail addresses
1109 */
1110 if (tinrc.url_highlight) {
1111 if (curr->flags & C_URL)
1112 #ifdef HAVE_COLOR
1113 highlight_regexes(i + scroll_region_top, &url_regex, use_color ? tinrc.col_urls : -1);
1114 #else
1115 highlight_regexes(i + scroll_region_top, &url_regex, -1);
1116 #endif /* HAVE_COLOR */
1117
1118 if (curr->flags & C_MAIL)
1119 #ifdef HAVE_COLOR
1120 highlight_regexes(i + scroll_region_top, &mail_regex, use_color ? tinrc.col_urls : -1);
1121 #else
1122 highlight_regexes(i + scroll_region_top, &mail_regex, -1);
1123 #endif /* HAVE_COLOR */
1124
1125 if (curr->flags & C_NEWS)
1126 #ifdef HAVE_COLOR
1127 highlight_regexes(i + scroll_region_top, &news_regex, use_color ? tinrc.col_urls : -1);
1128 #else
1129 highlight_regexes(i + scroll_region_top, &news_regex, -1);
1130 #endif /* HAVE_COLOR */
1131 }
1132
1133 /*
1134 * Highlight /slashes/, *stars*, _underscores_ and -strokes-
1135 */
1136 if (word_highlight && (curr->flags & C_BODY) && !(curr->flags & C_CTRLL)) {
1137 #ifdef HAVE_COLOR
1138 highlight_regexes(i + scroll_region_top, &slashes_regex, use_color ? tinrc.col_markslash : tinrc.mono_markslash);
1139 highlight_regexes(i + scroll_region_top, &stars_regex, use_color ? tinrc.col_markstar : tinrc.mono_markstar);
1140 highlight_regexes(i + scroll_region_top, &underscores_regex, use_color ? tinrc.col_markdash : tinrc.mono_markdash);
1141 highlight_regexes(i + scroll_region_top, &strokes_regex, use_color ? tinrc.col_markstroke : tinrc.mono_markstroke);
1142 #else
1143 highlight_regexes(i + scroll_region_top, &slashes_regex, tinrc.mono_markslash);
1144 highlight_regexes(i + scroll_region_top, &stars_regex, tinrc.mono_markstar);
1145 highlight_regexes(i + scroll_region_top, &underscores_regex, tinrc.mono_markdash);
1146 highlight_regexes(i + scroll_region_top, &strokes_regex, tinrc.mono_markstroke);
1147 #endif /* HAVE_COLOR */
1148 }
1149
1150 /* Blank the screen after a ^L (only occurs when showing cooked) */
1151 if (!reveal_ctrl_l && (curr->flags & C_CTRLL) && (int) (base_line + i) > reveal_ctrl_l_lines) {
1152 CleartoEOS();
1153 break;
1154 }
1155 }
1156
1157 #ifdef HAVE_COLOR
1158 fcol(tinrc.col_text);
1159 #endif /* HAVE_COLOR */
1160
1161 show_mini_help(help_level);
1162 }
1163
1164
1165 /*
1166 * Redraw the current page, curr_line will be the first line displayed
1167 * Everything that calls draw_page() just sets curr_line, this function must
1168 * ensure it is set to something sane
1169 * If part is !=0, then only draw the first (-ve) or last (+ve) few lines
1170 */
1171 void
draw_page(const char * group,int part)1172 draw_page(
1173 const char *group,
1174 int part)
1175 {
1176 int start, end; /* 1st, last line to draw */
1177
1178 signal_context = cPage;
1179
1180 /*
1181 * Can't do partial draw if term can't scroll properly
1182 */
1183 if (part && !have_linescroll)
1184 part = 0;
1185
1186 /*
1187 * Ensure curr_line is in bounds
1188 */
1189 if (curr_line < 0)
1190 curr_line = 0; /* Oops - off the top */
1191 else {
1192 if (curr_line > artlines)
1193 curr_line = artlines; /* Oops - off the end */
1194 }
1195
1196 search_line = curr_line; /* Reset search to start from top of display */
1197
1198 scroll_region_top = PAGE_HEADER;
1199
1200 /* Down-scroll, only redraw bottom 'part' lines of screen */
1201 if ((start = (part > 0) ? ARTLINES - part : 0) < 0)
1202 start = 0;
1203
1204 /* Up-scroll, only redraw the top 'part' lines of screen */
1205 if ((end = (part < 0) ? -part : ARTLINES) > ARTLINES)
1206 end = ARTLINES;
1207
1208 /*
1209 * ncurses doesn't clear the scroll area when you scroll by more than the
1210 * window size - force full redraw
1211 */
1212 if ((end - start >= ARTLINES) || (part == 0)) {
1213 ClearScreen();
1214 draw_page_header(group);
1215 } else
1216 MoveCursor(0, 0);
1217
1218 print_message_page(note_fp, artline, artlines, curr_line, start, end, PAGE_LEVEL);
1219
1220 /*
1221 * Print an appropriate footer
1222 */
1223 if (curr_line + ARTLINES >= artlines) {
1224 char buf[LEN], *buf2;
1225 int len;
1226
1227 STRCPY(buf, (arts[this_resp].thread != -1) ? _(txt_next_resp) : _(txt_last_resp));
1228 buf2 = strunc(buf, cCOLS - 1);
1229 len = strwidth(buf2);
1230 clear_message();
1231 MoveCursor(cLINES, cCOLS - len - (1 + BLANK_PAGE_COLS));
1232 #ifdef HAVE_COLOR
1233 fcol(tinrc.col_normal);
1234 #endif /* HAVE_COLOR */
1235 StartInverse();
1236 my_fputs(buf2, stdout);
1237 EndInverse();
1238 my_flush();
1239 free(buf2);
1240 } else
1241 draw_percent_mark(curr_line + ARTLINES, artlines);
1242
1243 #ifdef XFACE_ABLE
1244 if (tinrc.use_slrnface && !show_raw_article)
1245 slrnface_display_xface(note_h->xface);
1246 #endif /* XFACE_ABLE */
1247
1248 stow_cursor();
1249 }
1250
1251
1252 /*
1253 * Start external metamail program
1254 */
1255 static void
invoke_metamail(FILE * fp)1256 invoke_metamail(
1257 FILE *fp)
1258 {
1259 char *ptr = tinrc.metamail_prog;
1260 char buf[LEN];
1261 long offset;
1262 FILE *mime_fp;
1263 #ifdef DONT_HAVE_PIPING
1264 char mimefile[PATH_LEN];
1265 int fd_mime;
1266 #endif /* DONT_HAVE_PIPING */
1267
1268 if ((*ptr == '\0') || (!strcmp(ptr, INTERNAL_CMD)) || (getenv("NOMETAMAIL") != NULL))
1269 return;
1270
1271 if ((offset = ftell(fp)) == -1) {
1272 perror_message(_(txt_command_failed), ptr);
1273 return;
1274 }
1275
1276 EndWin();
1277 Raw(FALSE);
1278
1279 #ifdef DONT_HAVE_PIPING
1280 if ((fd_mime = my_tmpfile(mimefile, sizeof(mimefile) - 1, homedir)) == -1) {
1281 perror_message(_(txt_command_failed), ptr);
1282 return;
1283 }
1284 if ((mime_fp = fdopen(fd_mime, "w")))
1285 #else
1286 if ((mime_fp = popen(ptr, "w")))
1287 #endif /* DONT_HAVE_PIPING */
1288 {
1289 rewind(fp);
1290 while (fgets(buf, (int) sizeof(buf), fp) != NULL)
1291 fputs(buf, mime_fp);
1292
1293 fflush(mime_fp);
1294 /* This is needed if we are viewing the raw art */
1295 fseek(fp, offset, SEEK_SET); /* goto old position */
1296
1297 #ifdef DONT_HAVE_PIPING
1298 snprintf(buf, sizeof(buf) - 1, "%s %s", tinrc.metamail_prog, mimefile);
1299 invoke_cmd(buf);
1300 fclose(mime_fp);
1301 unlink(mimefile);
1302 #else
1303 pclose(mime_fp);
1304 #endif /* DONT_HAVE_PIPING */
1305 } else
1306 perror_message(_(txt_command_failed), ptr);
1307
1308 #ifdef USE_CURSES
1309 Raw(TRUE);
1310 InitWin();
1311 #endif /* USE_CURSES */
1312 prompt_continue();
1313 #ifndef USE_CURSES
1314 Raw(TRUE);
1315 InitWin();
1316 #endif /* !USE_CURSES */
1317 }
1318
1319
1320 /*
1321 * PAGE_HEADER defines the size in lines of this header
1322 */
1323 static void
draw_page_header(const char * group)1324 draw_page_header(
1325 const char *group)
1326 {
1327 char *buf, *tmp;
1328 int i;
1329 int whichresp, x_resp;
1330 int len, right_len, center_pos, cur_pos;
1331 size_t line_len;
1332 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1333 wchar_t *fmt_resp, *fmt_thread, *wtmp, *wtmp2, *wbuf;
1334 #else
1335 char *tmp2;
1336 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1337
1338 whichresp = which_response(this_resp);
1339 x_resp = num_of_responses(which_thread(this_resp));
1340
1341 line_len = LEN + 1;
1342 buf = my_malloc(line_len);
1343
1344 if (!my_strftime(buf, line_len, curr_group->attribute->date_format, localtime(&arts[this_resp].date))) {
1345 strncpy(buf, BlankIfNull(note_h->date), line_len);
1346 buf[line_len - 1] = '\0';
1347 }
1348
1349 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1350 /* convert to wide-char format strings */
1351 fmt_thread = char2wchar_t(_(txt_thread_x_of_n));
1352 fmt_resp = char2wchar_t(_(txt_art_x_of_n));
1353
1354 /*
1355 * determine the needed space for the text at the right hand margin
1356 * the formatting info (%4s) needs 3 positions but we need 4 positions
1357 * on the screen for each counter.
1358 */
1359 if (fmt_thread && fmt_resp)
1360 right_len = MAX((wcswidth(fmt_thread, wcslen(fmt_thread)) - 6 + 8), (wcswidth(fmt_resp, wcslen(fmt_resp)) - 6 + 8));
1361 else if (fmt_thread)
1362 right_len = wcswidth(fmt_thread, wcslen(fmt_thread)) - 6 + 8;
1363 else if (fmt_resp)
1364 right_len = wcswidth(fmt_resp, wcslen(fmt_resp)) - 6 + 8;
1365 else
1366 right_len = 0;
1367 FreeIfNeeded(fmt_thread);
1368 FreeIfNeeded(fmt_resp);
1369
1370 /*
1371 * limit right_len to cCOLS / 3
1372 */
1373 if (right_len > cCOLS / 3 + 1)
1374 right_len = cCOLS / 3 + 1;
1375
1376 # ifdef HAVE_COLOR
1377 fcol(tinrc.col_head);
1378 # endif /* HAVE_COLOR */
1379
1380 /*
1381 * first line
1382 */
1383 cur_pos = 0;
1384
1385 /* date */
1386 if ((wtmp = char2wchar_t(buf)) != NULL) {
1387 my_fputws(wtmp, stdout);
1388 cur_pos += wcswidth(wtmp, wcslen(wtmp));
1389 free(wtmp);
1390 }
1391
1392 /*
1393 * determine max len for centered group name
1394 * allow one space before and after group name
1395 */
1396 len = cCOLS - 2 * MAX(cur_pos, right_len) - 3;
1397
1398 /* group name */
1399 if ((wtmp = char2wchar_t(group)) != NULL) {
1400 /* wconvert_to_printable(wtmp, FALSE); */
1401 if (tinrc.abbreviate_groupname)
1402 wtmp2 = abbr_wcsgroupname(wtmp, len);
1403 else
1404 wtmp2 = wstrunc(wtmp, len);
1405
1406 if ((i = wcswidth(wtmp2, wcslen(wtmp2))) < len)
1407 len = i;
1408
1409 center_pos = (cCOLS - len) / 2;
1410
1411 /* pad out to left */
1412 for (; cur_pos < center_pos; cur_pos++)
1413 my_fputc(' ', stdout);
1414
1415 my_fputws(wtmp2, stdout);
1416 cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
1417 free(wtmp2);
1418 free(wtmp);
1419 }
1420
1421 /* pad out to right */
1422 for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
1423 my_fputc(' ', stdout);
1424
1425 /* thread info */
1426 /* can't eval tin_ltoa() more than once in a statement due to statics */
1427 strcpy(buf, tin_ltoa(which_thread(this_resp) + 1, 4));
1428 tmp = strunc(_(txt_thread_x_of_n), cCOLS / 3 - 1);
1429 my_printf(tmp, buf, tin_ltoa(grpmenu.max, 4));
1430 free(tmp);
1431
1432 my_fputs(cCRLF, stdout);
1433
1434 # if 0
1435 /* display a ruler for layout checking purposes */
1436 my_fputs("....|....3....|....2....|....1....|....0....|....1....|....2....|....3....|....\n", stdout);
1437 # endif /* 0 */
1438
1439 /*
1440 * second line
1441 */
1442 cur_pos = 0;
1443
1444 /* line count */
1445 if (arts[this_resp].line_count < 0)
1446 strcpy(buf, "?");
1447 else
1448 snprintf(buf, line_len, "%-4d", arts[this_resp].line_count);
1449
1450 {
1451 wchar_t *fmt;
1452
1453 if ((wtmp = char2wchar_t(_(txt_lines))) != NULL) {
1454 int tex_space = pgart.tex2iso ? 5 : 0;
1455
1456 fmt = wstrunc(wtmp, cCOLS / 3 - 1 - tex_space);
1457 wtmp = my_realloc(wtmp, sizeof(wchar_t) * line_len);
1458 swprintf(wtmp, line_len, fmt, buf);
1459 my_fputws(wtmp, stdout);
1460 cur_pos += wcswidth(wtmp, wcslen(wtmp));
1461 free(fmt);
1462 free(wtmp);
1463 }
1464 }
1465
1466 # ifdef HAVE_COLOR
1467 fcol(tinrc.col_subject);
1468 # endif /* HAVE_COLOR */
1469
1470 /* tex2iso */
1471 if (pgart.tex2iso) {
1472 if ((wtmp = char2wchar_t(_(txt_tex))) != NULL) {
1473 wtmp2 = wstrunc(wtmp, 5);
1474 my_fputws(wtmp2, stdout);
1475 cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
1476 free(wtmp);
1477 free(wtmp2);
1478 }
1479 }
1480
1481 /* subject */
1482 /*
1483 * TODO: why do we fall back to arts[this_resp].subject if !note_h->subj?
1484 * if !note_h->subj then the article just has no subject, no matter
1485 * what the overview says.
1486 *
1487 * add BiDi handling
1488 */
1489 strncpy(buf, (note_h->subj ? note_h->subj : arts[this_resp].subject), line_len);
1490 buf[line_len - 1] = '\0';
1491 if ((wtmp = char2wchar_t(buf)) != NULL) {
1492 wbuf = wexpand_tab(wtmp, tabwidth);
1493 wtmp2 = wstrunc(wbuf, cCOLS - 2 * right_len - 3);
1494 center_pos = (cCOLS - wcswidth(wtmp2, wcslen(wtmp2))) / 2;
1495
1496 /* pad out to left */
1497 for (; cur_pos < center_pos; cur_pos++)
1498 my_fputc(' ', stdout);
1499
1500 StartInverse();
1501 my_fputws(wtmp2, stdout);
1502 EndInverse();
1503 cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
1504 free(wtmp2);
1505 free(wtmp);
1506 free(wbuf);
1507 }
1508
1509 # ifdef HAVE_COLOR
1510 fcol(tinrc.col_response);
1511 # endif /* HAVE_COLOR */
1512
1513 /* pad out to right */
1514 for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
1515 my_fputc(' ', stdout);
1516
1517 if (whichresp) {
1518 tmp = strunc(_(txt_art_x_of_n), cCOLS / 3 - 1);
1519 my_printf(tmp, whichresp + 1, x_resp + 1);
1520 free(tmp);
1521 } else {
1522 /* TODO: ngettext */
1523 if (!x_resp) {
1524 tmp = strunc(_(txt_no_responses), cCOLS / 3 - 1);
1525 my_printf("%s", tmp);
1526 } else if (x_resp == 1) {
1527 tmp = strunc(_(txt_1_resp), cCOLS / 3 - 1);
1528 my_printf("%s", tmp);
1529 } else {
1530 tmp = strunc(_(txt_x_resp), cCOLS / 3 - 1);
1531 my_printf(tmp, x_resp);
1532 }
1533 free(tmp);
1534 }
1535 my_fputs(cCRLF, stdout);
1536
1537 /*
1538 * third line
1539 */
1540 cur_pos = 0;
1541
1542 # ifdef HAVE_COLOR
1543 fcol(tinrc.col_from);
1544 # endif /* HAVE_COLOR */
1545 /* from */
1546 /*
1547 * TODO: don't use arts[this_resp].name/arts[this_resp].from
1548 * split up note_h->from and use that instead as it might
1549 * be different _if_ the overviews are broken
1550 *
1551 * add BiDi handling
1552 */
1553 {
1554 char *p = idna_decode(arts[this_resp].from);
1555
1556 if (arts[this_resp].name)
1557 snprintf(buf, line_len, "%s <%s>", arts[this_resp].name, p);
1558 else {
1559 strncpy(buf, p, line_len);
1560 buf[line_len - 1] = '\0';
1561 }
1562 free(p);
1563 }
1564
1565 if ((wtmp = char2wchar_t(buf)) != NULL) {
1566 wtmp2 = wstrunc(wtmp, cCOLS - 1);
1567 my_fputws(wtmp2, stdout);
1568 cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
1569 free(wtmp2);
1570 free(wtmp);
1571 }
1572
1573 /*
1574 * Organization
1575 *
1576 * TODO: IDNA decoding, see also comment in
1577 * cook.c:cook_article()
1578 * add BiDi handling
1579 */
1580 if (note_h->org) {
1581 snprintf(buf, line_len, _(txt_at_s), note_h->org);
1582
1583 if ((wtmp = char2wchar_t(buf)) != NULL) {
1584 wbuf = wexpand_tab(wtmp, tabwidth);
1585 wtmp2 = wstrunc(wbuf, cCOLS - cur_pos - 1);
1586
1587 i = cCOLS - wcswidth(wtmp2, wcslen(wtmp2)) - 1;
1588 for (; cur_pos < i; cur_pos++)
1589 my_fputc(' ', stdout);
1590
1591 my_fputws(wtmp2, stdout);
1592 free(wtmp2);
1593 free(wtmp);
1594 free(wbuf);
1595 }
1596 }
1597
1598 my_fputs(cCRLF, stdout);
1599 my_fputs(cCRLF, stdout);
1600
1601 #else /* !MULTIBYTE_ABLE || NO_LOCALE */
1602 /*
1603 * determine the needed space for the text at the right hand margin
1604 * the formatting info (%4s) needs 3 positions but we need 4 positions
1605 * on the screen for each counter
1606 */
1607 right_len = MAX((strlen(_(txt_thread_x_of_n)) - 6 + 8), (strlen(_(txt_art_x_of_n)) - 6 + 8));
1608
1609 # ifdef HAVE_COLOR
1610 fcol(tinrc.col_head);
1611 # endif /* HAVE_COLOR */
1612
1613 /*
1614 * first line
1615 */
1616 cur_pos = 0;
1617
1618 /* date */
1619 my_fputs(buf, stdout);
1620 cur_pos += strlen(buf);
1621
1622 /*
1623 * determine max len for centered group name
1624 * allow one space before and after group name
1625 */
1626 len = cCOLS - 2 * MAX(cur_pos, right_len) - 3;
1627
1628 /* group name */
1629 if (tinrc.abbreviate_groupname)
1630 tmp = abbr_groupname(group, len);
1631 else
1632 tmp = strunc(group, len);
1633
1634 if ((i = strlen(tmp)) < len)
1635 len = i;
1636
1637 center_pos = (cCOLS - len) / 2;
1638
1639 /* pad out to left */
1640 for (; cur_pos < center_pos; cur_pos++)
1641 my_fputc(' ', stdout);
1642
1643 my_fputs(tmp, stdout);
1644 cur_pos += strlen(tmp);
1645 free(tmp);
1646
1647 /* pad out to right */
1648 for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
1649 my_fputc(' ', stdout);
1650
1651 /* thread info */
1652 /* can't eval tin_ltoa() more than once in a statement due to statics */
1653 strcpy(buf, tin_ltoa(which_thread(this_resp) + 1, 4));
1654 my_printf(_(txt_thread_x_of_n), buf, tin_ltoa(grpmenu.max, 4));
1655
1656 my_fputs(cCRLF, stdout);
1657
1658 # if 0
1659 /* display a ruler for layout checking purposes */
1660 my_fputs("....|....3....|....2....|....1....|....0....|....1....|....2....|....3....|....\n", stdout);
1661 # endif /* 0 */
1662
1663 /*
1664 * second line
1665 */
1666 cur_pos = 0;
1667
1668 /* line count */
1669 /* an accurate line count will appear in the footer anymay */
1670 if (arts[this_resp].line_count < 0)
1671 strcpy(buf, "?");
1672 else
1673 snprintf(buf, line_len, "%-4d", arts[this_resp].line_count);
1674
1675 tmp = my_malloc(line_len);
1676 snprintf(tmp, line_len, _(txt_lines), buf);
1677 my_fputs(tmp, stdout);
1678 cur_pos += strlen(tmp);
1679 free(tmp);
1680
1681 # ifdef HAVE_COLOR
1682 fcol(tinrc.col_subject);
1683 # endif /* HAVE_COLOR */
1684
1685 /* tex2iso */
1686 if (pgart.tex2iso) {
1687 my_fputs(_(txt_tex), stdout);
1688 cur_pos += strlen(_(txt_tex));
1689 }
1690
1691 /* subject */
1692 /*
1693 * TODO: why do we fall back to arts[this_resp].subject if !note_h->subj?
1694 * if !note_h->subj then the article just has no subject, no matter
1695 * what the overview says.
1696 */
1697 strncpy(buf, (note_h->subj ? note_h->subj : arts[this_resp].subject), line_len);
1698 buf[line_len - 1] = '\0';
1699
1700 tmp2 = expand_tab(buf, tabwidth);
1701 tmp = strunc(tmp2, cCOLS - 2 * right_len - 3);
1702
1703 center_pos = (cCOLS - strlen(tmp)) / 2;
1704
1705 /* pad out to left */
1706 for (; cur_pos < center_pos; cur_pos++)
1707 my_fputc(' ', stdout);
1708
1709 StartInverse();
1710 my_fputs(tmp, stdout);
1711 EndInverse();
1712 cur_pos += strlen(tmp);
1713 free(tmp);
1714 free(tmp2);
1715
1716 # ifdef HAVE_COLOR
1717 fcol(tinrc.col_response);
1718 # endif /* HAVE_COLOR */
1719
1720 /* pad out to right */
1721 for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
1722 my_fputc(' ', stdout);
1723
1724 if (whichresp)
1725 my_printf(_(txt_art_x_of_n), whichresp + 1, x_resp + 1);
1726 else {
1727 /* TODO: ngettext */
1728 if (!x_resp)
1729 my_printf("%s", _(txt_no_responses));
1730 else if (x_resp == 1)
1731 my_printf("%s", _(txt_1_resp));
1732 else
1733 my_printf(_(txt_x_resp), x_resp);
1734 }
1735 my_fputs(cCRLF, stdout);
1736
1737 /*
1738 * third line
1739 */
1740 cur_pos = 0;
1741
1742 # ifdef HAVE_COLOR
1743 fcol(tinrc.col_from);
1744 # endif /* HAVE_COLOR */
1745 /* from */
1746 /*
1747 * TODO: don't use arts[this_resp].name/arts[this_resp].from
1748 * split up note_h->from and use that instead as it might
1749 * be different _if_ the overviews are broken
1750 */
1751 if (arts[this_resp].name)
1752 snprintf(buf, line_len, "%s <%s>", arts[this_resp].name, arts[this_resp].from);
1753 else {
1754 strncpy(buf, arts[this_resp].from, line_len);
1755 buf[line_len - 1] = '\0';
1756 }
1757
1758 tmp = strunc(buf, cCOLS - 1);
1759 my_fputs(tmp, stdout);
1760 cur_pos += strlen(tmp);
1761 free(tmp);
1762
1763 if (note_h->org && cCOLS - cur_pos - 1 >= (int) strlen(_(txt_at_s)) - 2 + 3) {
1764 /* we have enough space to print at least " at ..." */
1765 snprintf(buf, line_len, _(txt_at_s), note_h->org);
1766
1767 tmp2 = expand_tab(buf, tabwidth);
1768 tmp = strunc(tmp2, cCOLS - cur_pos - 1);
1769 len = cCOLS - (int) strlen(tmp) - 1;
1770 for (; cur_pos < len; cur_pos++)
1771 my_fputc(' ', stdout);
1772 my_fputs(tmp, stdout);
1773 free(tmp);
1774 free(tmp2);
1775 }
1776
1777 my_fputs(cCRLF, stdout);
1778 my_fputs(cCRLF, stdout);
1779 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1780 free(buf);
1781
1782 #ifdef HAVE_COLOR
1783 fcol(tinrc.col_normal);
1784 #endif /* HAVE_COLOR */
1785 }
1786
1787
1788 /*
1789 * Change the pager article context to arts[new_respnum]
1790 * Return GRP_ARTUNAVAIL if article could not be opened
1791 * or GRP_ARTABORT if load of article was interrupted
1792 * or 0 on success
1793 */
1794 static int
load_article(int new_respnum,struct t_group * group)1795 load_article(
1796 int new_respnum,
1797 struct t_group *group)
1798 {
1799 static t_bool art_closed = FALSE;
1800
1801 #ifdef DEBUG
1802 if (debug & DEBUG_MISC)
1803 fprintf(stderr, "load_art %s(new=%d, curr=%d)\n", (new_respnum == this_resp && !art_closed) ? "ALREADY OPEN!" : "", new_respnum, this_resp);
1804 #endif /* DEBUG */
1805
1806 if (new_respnum != this_resp || art_closed) {
1807 int ret;
1808
1809 art_close(&pgart); /* close previously opened art in pager */
1810 ret = art_open(TRUE, &arts[new_respnum], group, &pgart, TRUE, _(txt_reading_article));
1811
1812 switch (ret) {
1813 case ART_UNAVAILABLE:
1814 art_mark(group, &arts[new_respnum], ART_READ);
1815 /* prevent retagging as unread in unfilter_articles() */
1816 if (arts[new_respnum].killed == ART_KILLED_UNREAD)
1817 arts[new_respnum].killed = ART_KILLED;
1818 art_closed = TRUE;
1819 wait_message(1, _(txt_art_unavailable));
1820 return GRP_ARTUNAVAIL;
1821
1822 case ART_ABORT:
1823 art_close(&pgart);
1824 art_closed = TRUE;
1825 return GRP_ARTABORT; /* special retcode to stop redrawing screen */
1826
1827 default: /* Normal case */
1828 #if 0 /* Very useful debugging tool */
1829 if (prompt_yn("Fake art unavailable? ", FALSE) == 1) {
1830 art_close(&pgart);
1831 art_mark(group, &arts[new_respnum], ART_READ);
1832 art_closed = TRUE;
1833 return GRP_ARTUNAVAIL;
1834 }
1835 #endif /* 0 */
1836 if (art_closed)
1837 art_closed = FALSE;
1838 if (new_respnum != this_resp) {
1839 /*
1840 * Remember current & previous articles for '-' command
1841 */
1842 last_resp = this_resp;
1843 this_resp = new_respnum; /* Set new art globally */
1844 }
1845 break;
1846 }
1847 } else if (show_all_headers) {
1848 /*
1849 * article is already opened with show_all_headers ON
1850 * -> re-cook it
1851 */
1852 show_all_headers = FALSE;
1853 resize_article(TRUE, &pgart);
1854 }
1855
1856 art_mark(group, &arts[this_resp], ART_READ);
1857
1858 /*
1859 * Change status if art was unread before killing to
1860 * prevent retagging as unread in unfilter_articles()
1861 */
1862 if (arts[this_resp].killed == ART_KILLED_UNREAD)
1863 arts[this_resp].killed = ART_KILLED;
1864
1865 if (pgart.cooked == NULL) { /* harmony corruption */
1866 wait_message(1, _(txt_art_unavailable));
1867 return GRP_ARTUNAVAIL;
1868 }
1869
1870 /*
1871 * Setup to start viewing cooked version
1872 */
1873 show_raw_article = FALSE;
1874 show_all_headers = FALSE;
1875 curr_line = 0;
1876 note_fp = pgart.cooked;
1877 artline = pgart.cookl;
1878 artlines = pgart.cooked_lines;
1879 search_line = 0;
1880 /*
1881 * Reset offsets only if not invoked during 'body search' (srch_lineno != -1)
1882 * otherwise the found string will not be highlighted
1883 */
1884 if (srch_lineno == -1)
1885 reset_srch_offsets();
1886 rotate = 0; /* normal mode, not rot13 */
1887 reveal_ctrl_l = FALSE;
1888 reveal_ctrl_l_lines = -1; /* all ^L's active */
1889 hide_uue = tinrc.hide_uue;
1890
1891 draw_page(group->name, 0);
1892
1893 /*
1894 * Automatically invoke attachment viewing if requested
1895 */
1896 if (!note_h->mime || IS_PLAINTEXT(note_h->ext)) /* Text only article */
1897 return 0;
1898
1899 if (*tinrc.metamail_prog == '\0' || getenv("NOMETAMAIL") != NULL) /* Viewer turned off */
1900 return 0;
1901
1902 if (group->attribute->ask_for_metamail) {
1903 if (prompt_yn(_(txt_use_mime), TRUE) != 1)
1904 return 0;
1905 }
1906
1907 XFACE_SUPPRESS();
1908 if (strcmp(tinrc.metamail_prog, INTERNAL_CMD) == 0) /* Use internal viewer */
1909 decode_save_mime(&pgart, FALSE);
1910 else
1911 invoke_metamail(pgart.raw);
1912 XFACE_SHOW();
1913 return 0;
1914 }
1915
1916
1917 static int
prompt_response(int ch,int curr_respnum)1918 prompt_response(
1919 int ch,
1920 int curr_respnum)
1921 {
1922 int i, num;
1923
1924 clear_message();
1925
1926 if ((num = (prompt_num(ch, _(txt_select_art)) - 1)) == -1) {
1927 clear_message();
1928 return -1;
1929 }
1930
1931 if ((i = which_thread(curr_respnum)) >= 0)
1932 return find_response(i, num);
1933 else
1934 return -1;
1935 }
1936
1937
1938 /*
1939 * Reposition within message as needed, highlight searched string
1940 * This is tied quite closely to the information stored by
1941 * get_search_vectors()
1942 */
1943 static void
process_search(int * lcurr_line,size_t message_lines,size_t screen_lines,int help_level)1944 process_search(
1945 int *lcurr_line,
1946 size_t message_lines,
1947 size_t screen_lines,
1948 int help_level)
1949 {
1950 int i, start, end;
1951
1952 if ((i = get_search_vectors(&start, &end)) == -1)
1953 return;
1954
1955 /*
1956 * Is matching line off the current view?
1957 * Reposition within article if needed, try to get matched line
1958 * in the middle of the screen
1959 */
1960 if (i < *lcurr_line || i >= (int) (*lcurr_line + screen_lines)) {
1961 *lcurr_line = i - (screen_lines / 2);
1962 if (*lcurr_line + screen_lines > message_lines) /* off the end */
1963 *lcurr_line = message_lines - screen_lines;
1964 /* else pos. is just fine */
1965 }
1966
1967 switch (help_level) {
1968 case PAGE_LEVEL:
1969 draw_page(curr_group->name, 0);
1970 break;
1971
1972 case INFO_PAGER:
1973 display_info_page(0);
1974 break;
1975
1976 default:
1977 break;
1978 }
1979 search_line = i; /* draw_page() resets this to 0 */
1980
1981 /*
1982 * Highlight found string
1983 */
1984 highlight_string(i - *lcurr_line + scroll_region_top, start, end - start);
1985 }
1986
1987
1988 /*
1989 * Implement ^H toggle between cooked and raw views of article
1990 */
1991 void
toggle_raw(struct t_group * group)1992 toggle_raw(
1993 struct t_group *group)
1994 {
1995 if (show_raw_article) {
1996 artline = pgart.cookl;
1997 artlines = pgart.cooked_lines;
1998 note_fp = pgart.cooked;
1999 } else {
2000 static int j; /* Needed on successive invocations */
2001 int chunk = note_h->ext->line_count;
2002
2003 /*
2004 * We do this on the fly, since most of the time it won't be used
2005 */
2006 if (!pgart.rawl) { /* Already done this for this article? */
2007 char *line;
2008 char *p;
2009 long offset;
2010
2011 j = 0;
2012 rewind(pgart.raw);
2013 pgart.rawl = my_malloc(sizeof(t_lineinfo) * chunk);
2014 offset = ftell(pgart.raw);
2015
2016 while ((line = tin_fgets(pgart.raw, FALSE)) != NULL) {
2017 int space;
2018 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
2019 int num_bytes;
2020 wchar_t wc;
2021 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
2022
2023 pgart.rawl[j].offset = offset;
2024 pgart.rawl[j].flags = 0;
2025 j++;
2026 if (j >= chunk) {
2027 chunk += 50;
2028 pgart.rawl = my_realloc(pgart.rawl, sizeof(t_lineinfo) * chunk);
2029 }
2030
2031 p = line;
2032 while (*p) {
2033 space = cCOLS - 1;
2034
2035 while ((space > 0) && *p) {
2036 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
2037 num_bytes = mbtowc(&wc, p, MB_CUR_MAX);
2038 if (num_bytes != -1 && iswprint(wc)) {
2039 if ((space -= wcwidth(wc)) < 0)
2040 break;
2041 p += num_bytes;
2042 offset += num_bytes;
2043 }
2044 #else
2045 if (my_isprint((unsigned char) *p)) {
2046 space--;
2047 p++;
2048 offset++;
2049 }
2050 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
2051 else if (IS_LOCAL_CHARSET("Big5") && (unsigned char) *p >= 0xa1 && (unsigned char) *p <= 0xfe && *(p + 1)) {
2052 /*
2053 * Big5: ASCII chars are handled by the normal code
2054 * check only for 2-byte chars
2055 * TODO: should we also check if the second byte is
2056 * also valid?
2057 */
2058 p += 2;
2059 offset += 2;
2060 space--;
2061 } else {
2062 /*
2063 * the current character can't be displayed print it as
2064 * an octal value (needs 4 columns) see also
2065 * color.c:draw_pager_line()
2066 */
2067 if ((space -= 4) < 0)
2068 break;
2069 offset++;
2070 p++;
2071 }
2072 }
2073 /*
2074 * if we reached the end of the line we don't need to
2075 * remember anything
2076 */
2077 if (*p) {
2078 pgart.rawl[j].offset = offset;
2079 pgart.rawl[j].flags = 0;
2080 if (++j >= chunk) {
2081 chunk += 50;
2082 pgart.rawl = my_realloc(pgart.rawl, sizeof(t_lineinfo) * chunk);
2083 }
2084 }
2085 }
2086
2087 /*
2088 * only use ftell's return value here because we didn't
2089 * take the \n into account above.
2090 */
2091 offset = ftell(pgart.raw);
2092 }
2093
2094 pgart.rawl = my_realloc(pgart.rawl, sizeof(t_lineinfo) * j);
2095 }
2096 artline = pgart.rawl;
2097 artlines = j;
2098 note_fp = pgart.raw;
2099 }
2100 curr_line = 0;
2101 show_raw_article = bool_not(show_raw_article);
2102 draw_page(group ? group->name : "", 0);
2103 }
2104
2105
2106 /*
2107 * Re-cook an article
2108 */
2109 void
resize_article(t_bool wrap_lines,t_openartinfo * artinfo)2110 resize_article(
2111 t_bool wrap_lines,
2112 t_openartinfo *artinfo)
2113 {
2114 free(artinfo->cookl);
2115 if (artinfo->cooked)
2116 fclose(artinfo->cooked);
2117
2118 if (!cook_article(wrap_lines, artinfo, hide_uue, show_all_headers))
2119 tin_done(EXIT_FAILURE, _(txt_cook_article_failed_exiting), tin_progname);
2120
2121 show_raw_article = FALSE;
2122 artline = pgart.cookl;
2123 artlines = pgart.cooked_lines;
2124 note_fp = pgart.cooked;
2125 }
2126
2127
2128 /*
2129 * Infopager: simply page files
2130 */
2131 void
info_pager(FILE * info_fh,const char * title,t_bool wrap_at_ends)2132 info_pager(
2133 FILE *info_fh,
2134 const char *title,
2135 t_bool wrap_at_ends)
2136 {
2137 int offset;
2138 t_function func;
2139
2140 search_line = 0;
2141 reset_srch_offsets();
2142 info_file = info_fh;
2143 info_title = title;
2144 curr_info_line = 0;
2145 preprocess_info_message(info_fh);
2146 if (!info_fh)
2147 return;
2148 set_xclick_off();
2149 display_info_page(0);
2150
2151 forever {
2152 switch (func = handle_keypad(page_left, page_right, page_mouse_action, info_keys)) {
2153 case GLOBAL_ABORT: /* common arrow keys */
2154 break;
2155
2156 case GLOBAL_LINE_UP:
2157 if (num_info_lines <= NOTESLINES) {
2158 info_message(_(txt_begin_of_page));
2159 break;
2160 }
2161 if (curr_info_line == 0) {
2162 if (!wrap_at_ends) {
2163 info_message(_(txt_begin_of_page));
2164 break;
2165 }
2166 curr_info_line = num_info_lines - NOTESLINES;
2167 display_info_page(0);
2168 break;
2169 }
2170 offset = scroll_page(KEYMAP_UP);
2171 curr_info_line += offset;
2172 display_info_page(offset);
2173 break;
2174
2175 case GLOBAL_LINE_DOWN:
2176 if (num_info_lines <= NOTESLINES) {
2177 info_message(_(txt_end_of_page));
2178 break;
2179 }
2180 if (curr_info_line + NOTESLINES >= num_info_lines) {
2181 if (!wrap_at_ends) {
2182 info_message(_(txt_end_of_page));
2183 break;
2184 }
2185 curr_info_line = 0;
2186 display_info_page(0);
2187 break;
2188 }
2189 offset = scroll_page(KEYMAP_DOWN);
2190 curr_info_line += offset;
2191 display_info_page(offset);
2192 break;
2193
2194 case GLOBAL_PAGE_DOWN:
2195 if (num_info_lines <= NOTESLINES) {
2196 info_message(_(txt_end_of_page));
2197 break;
2198 }
2199 if (curr_info_line + NOTESLINES >= num_info_lines) { /* End is already on screen */
2200 if (!wrap_at_ends) {
2201 info_message(_(txt_end_of_page));
2202 break;
2203 }
2204 curr_info_line = 0;
2205 display_info_page(0);
2206 break;
2207 }
2208 curr_info_line += ((tinrc.scroll_lines == -2) ? NOTESLINES / 2 : NOTESLINES);
2209 display_info_page(0);
2210 break;
2211
2212 case GLOBAL_PAGE_UP:
2213 if (num_info_lines <= NOTESLINES) {
2214 info_message(_(txt_begin_of_page));
2215 break;
2216 }
2217 if (curr_info_line == 0) {
2218 if (!wrap_at_ends) {
2219 info_message(_(txt_begin_of_page));
2220 break;
2221 }
2222 curr_info_line = num_info_lines - NOTESLINES;
2223 display_info_page(0);
2224 break;
2225 }
2226 curr_info_line -= ((tinrc.scroll_lines == -2) ? NOTESLINES / 2 : NOTESLINES);
2227 display_info_page(0);
2228 break;
2229
2230 case GLOBAL_FIRST_PAGE:
2231 if (curr_info_line) {
2232 curr_info_line = 0;
2233 display_info_page(0);
2234 }
2235 break;
2236
2237 case GLOBAL_LAST_PAGE:
2238 if (curr_info_line + NOTESLINES != num_info_lines) {
2239 /* Display a full last page for neatness */
2240 curr_info_line = num_info_lines - NOTESLINES;
2241 display_info_page(0);
2242 }
2243 break;
2244
2245 case GLOBAL_TOGGLE_HELP_DISPLAY:
2246 toggle_mini_help(INFO_PAGER);
2247 display_info_page(0);
2248 break;
2249
2250 case GLOBAL_SEARCH_SUBJECT_FORWARD:
2251 case GLOBAL_SEARCH_SUBJECT_BACKWARD:
2252 case GLOBAL_SEARCH_REPEAT:
2253 if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
2254 break;
2255
2256 if ((search_article((func == GLOBAL_SEARCH_SUBJECT_FORWARD), (func == GLOBAL_SEARCH_REPEAT), search_line, num_info_lines, infoline, num_info_lines - 1, info_file)) == -1)
2257 break;
2258
2259 process_search(&curr_info_line, num_info_lines, NOTESLINES, INFO_PAGER);
2260 break;
2261
2262 case GLOBAL_QUIT: /* quit */
2263 ClearScreen();
2264 return;
2265
2266 default:
2267 break;
2268 }
2269 }
2270 }
2271
2272
2273 /*
2274 * Redraw the current page, curr_info_line will be the first line displayed
2275 * If part is !=0, then only draw the first (-ve) or last (+ve) few lines
2276 */
2277 void
display_info_page(int part)2278 display_info_page(
2279 int part)
2280 {
2281 int start, end; /* 1st, last line to draw */
2282
2283 signal_context = cInfopager;
2284
2285 /*
2286 * Can't do partial draw if term can't scroll properly
2287 */
2288 if (part && !have_linescroll)
2289 part = 0;
2290
2291 if (curr_info_line < 0)
2292 curr_info_line = 0;
2293 if (curr_info_line >= num_info_lines)
2294 curr_info_line = num_info_lines - 1;
2295
2296 scroll_region_top = INDEX_TOP;
2297
2298 /* Down-scroll, only redraw bottom 'part' lines of screen */
2299 if ((start = (part > 0) ? NOTESLINES - part : 0) < 0)
2300 start = 0;
2301
2302 /* Up-scroll, only redraw the top 'part' lines of screen */
2303 if ((end = (part < 0) ? -part : NOTESLINES) > NOTESLINES)
2304 end = NOTESLINES;
2305
2306 /* Print title */
2307 if ((end - start >= NOTESLINES) || (part == 0)) {
2308 ClearScreen();
2309 center_line(0, TRUE, info_title);
2310 }
2311
2312 print_message_page(info_file, infoline, num_info_lines, curr_info_line, start, end, INFO_PAGER);
2313
2314 /* print footer */
2315 draw_percent_mark(curr_info_line + (curr_info_line + NOTESLINES < num_info_lines ? NOTESLINES : num_info_lines - curr_info_line), num_info_lines);
2316 stow_cursor();
2317 }
2318
2319
2320 static void
preprocess_info_message(FILE * info_fh)2321 preprocess_info_message(
2322 FILE *info_fh)
2323 {
2324 int chunk = 50;
2325
2326 FreeAndNull(infoline);
2327 if (!info_fh)
2328 return;
2329
2330 rewind(info_fh);
2331 infoline = my_malloc(sizeof(t_lineinfo) * chunk);
2332 num_info_lines = 0;
2333
2334 do {
2335 infoline[num_info_lines].offset = ftell(info_fh);
2336 infoline[num_info_lines].flags = 0;
2337 num_info_lines++;
2338 if (num_info_lines >= chunk) {
2339 chunk += 50;
2340 infoline = my_realloc(infoline, sizeof(t_lineinfo) * chunk);
2341 }
2342 } while (tin_fgets(info_fh, FALSE) != NULL);
2343
2344 num_info_lines--;
2345 infoline = my_realloc(infoline, sizeof(t_lineinfo) * num_info_lines);
2346 }
2347
2348
2349 /*
2350 * URL menu
2351 */
2352 static t_function
url_left(void)2353 url_left(
2354 void)
2355 {
2356 return GLOBAL_QUIT;
2357 }
2358
2359
2360 static t_function
url_right(void)2361 url_right(
2362 void)
2363 {
2364 return URL_SELECT;
2365 }
2366
2367
2368 static void
show_url_page(void)2369 show_url_page(
2370 void)
2371 {
2372 int i;
2373
2374 signal_context = cURL;
2375 currmenu = &urlmenu;
2376 mark_offset = 0;
2377
2378 if (urlmenu.curr < 0)
2379 urlmenu.curr = 0;
2380
2381 ClearScreen();
2382 set_first_screen_item();
2383 center_line(0, TRUE, _(txt_url_menu));
2384
2385 for (i = urlmenu.first; i < urlmenu.first + NOTESLINES && i < urlmenu.max; ++i)
2386 build_url_line(i);
2387
2388 show_mini_help(URL_LEVEL);
2389
2390 draw_url_arrow();
2391 }
2392
2393
2394 static t_bool
url_page(void)2395 url_page(
2396 void)
2397 {
2398 char key[MAXKEYLEN];
2399 t_function func;
2400 t_menu *oldmenu = NULL;
2401
2402 if (currmenu)
2403 oldmenu = currmenu;
2404 urlmenu.curr = 0;
2405 urlmenu.max = build_url_list();
2406 if (urlmenu.max == 0)
2407 return FALSE;
2408
2409 clear_note_area();
2410 show_url_page();
2411 set_xclick_off();
2412
2413 forever {
2414 switch ((func = handle_keypad(url_left, url_right, NULL, url_keys))) {
2415 case GLOBAL_QUIT:
2416 free_url_list();
2417 if (oldmenu)
2418 currmenu = oldmenu;
2419 return TRUE;
2420
2421 case DIGIT_1:
2422 case DIGIT_2:
2423 case DIGIT_3:
2424 case DIGIT_4:
2425 case DIGIT_5:
2426 case DIGIT_6:
2427 case DIGIT_7:
2428 case DIGIT_8:
2429 case DIGIT_9:
2430 if (urlmenu.max)
2431 prompt_item_num(func_to_key(func, url_keys), _(txt_url_select));
2432 break;
2433
2434 #ifndef NO_SHELL_ESCAPE
2435 case GLOBAL_SHELL_ESCAPE:
2436 do_shell_escape();
2437 break;
2438 #endif /* !NO_SHELL_ESCAPE */
2439
2440 case GLOBAL_HELP:
2441 show_help_page(URL_LEVEL, _(txt_url_menu_com));
2442 show_url_page();
2443 break;
2444
2445 case GLOBAL_FIRST_PAGE:
2446 top_of_list();
2447 break;
2448
2449 case GLOBAL_LAST_PAGE:
2450 end_of_list();
2451 break;
2452
2453 case GLOBAL_REDRAW_SCREEN:
2454 my_retouch();
2455 show_url_page();
2456 break;
2457
2458 case GLOBAL_LINE_DOWN:
2459 move_down();
2460 break;
2461
2462 case GLOBAL_LINE_UP:
2463 move_up();
2464 break;
2465
2466 case GLOBAL_PAGE_DOWN:
2467 page_down();
2468 break;
2469
2470 case GLOBAL_PAGE_UP:
2471 page_up();
2472 break;
2473
2474 case GLOBAL_SCROLL_DOWN:
2475 scroll_down();
2476 break;
2477
2478 case GLOBAL_SCROLL_UP:
2479 scroll_up();
2480 break;
2481
2482 case GLOBAL_TOGGLE_HELP_DISPLAY:
2483 toggle_mini_help(URL_LEVEL);
2484 show_url_page();
2485 break;
2486
2487 case GLOBAL_TOGGLE_INFO_LAST_LINE:
2488 tinrc.info_in_last_line = bool_not(tinrc.info_in_last_line);
2489 show_url_page();
2490 break;
2491
2492 case URL_SELECT:
2493 if (urlmenu.max) {
2494 if (process_url(urlmenu.curr))
2495 show_url_page();
2496 else
2497 draw_url_arrow();
2498 }
2499 break;
2500
2501 case GLOBAL_SEARCH_SUBJECT_FORWARD:
2502 case GLOBAL_SEARCH_SUBJECT_BACKWARD:
2503 case GLOBAL_SEARCH_REPEAT:
2504 if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
2505 info_message(_(txt_no_prev_search));
2506 else if (urlmenu.max) {
2507 int new_pos, old_pos = urlmenu.curr;
2508
2509 new_pos = generic_search((func == GLOBAL_SEARCH_SUBJECT_FORWARD), (func == GLOBAL_SEARCH_REPEAT), urlmenu.curr, urlmenu.max - 1, URL_LEVEL);
2510 if (new_pos != old_pos)
2511 move_to_item(new_pos);
2512 }
2513 break;
2514
2515 default:
2516 info_message(_(txt_bad_command), printascii(key, func_to_key(GLOBAL_HELP, url_keys)));
2517 break;
2518 }
2519 }
2520 }
2521
2522
2523 static void
draw_url_arrow(void)2524 draw_url_arrow(
2525 void)
2526 {
2527 draw_arrow_mark(INDEX_TOP + urlmenu.curr - urlmenu.first);
2528 if (tinrc.info_in_last_line) {
2529 t_url *lptr;
2530
2531 lptr = find_url(urlmenu.curr);
2532 info_message("%s", lptr->url);
2533 } else if (urlmenu.curr == urlmenu.max - 1)
2534 info_message(_(txt_end_of_urls));
2535 }
2536
2537
2538 t_url *
find_url(int n)2539 find_url(
2540 int n)
2541 {
2542 t_url *lptr;
2543
2544 lptr = url_list;
2545 while (n-- > 0 && lptr->next)
2546 lptr = lptr->next;
2547
2548 return lptr;
2549 }
2550
2551
2552 static void
build_url_line(int i)2553 build_url_line(
2554 int i)
2555 {
2556 char *sptr;
2557 int len = cCOLS - 9;
2558 t_url *lptr;
2559
2560 #ifdef USE_CURSES
2561 /*
2562 * Allocate line buffer
2563 * make it the same size like in !USE_CURSES case to simplify some code
2564 */
2565 sptr = my_malloc(cCOLS + 2);
2566 #else
2567 sptr = screen[INDEX2SNUM(i)].col;
2568 #endif /* USE_CURSES */
2569
2570 lptr = find_url(i);
2571 snprintf(sptr, cCOLS, " %s %-*.*s%s", tin_ltoa(i + 1, 4), len, len, lptr->url, cCRLF);
2572 WriteLine(INDEX2LNUM(i), sptr);
2573
2574 #ifdef USE_CURSES
2575 free(sptr);
2576 #endif /* USE_CURSES */
2577 }
2578
2579
2580 static t_bool
process_url(int n)2581 process_url(
2582 int n)
2583 {
2584 char *url, *url_esc;
2585 size_t len;
2586 t_url *lptr;
2587
2588 lptr = find_url(n);
2589 len = strlen(lptr->url) << 1; /* double size; room for editing URL */
2590 url = my_malloc(len + 1);
2591 if (prompt_default_string("URL:", url, len, lptr->url, HIST_URL)) {
2592 if (!*url) { /* Don't try and open nothing */
2593 free(url);
2594 return FALSE;
2595 }
2596 wait_message(2, _(txt_url_open), url);
2597 url_esc = escape_shell_meta(url, no_quote);
2598 len = strlen(url_esc) + strlen(tinrc.url_handler) + 2;
2599 url = my_realloc(url, len);
2600 snprintf(url, len, "%s %s", tinrc.url_handler, url_esc);
2601 invoke_cmd(url);
2602 free(url);
2603 cursoroff();
2604 return TRUE;
2605 }
2606 free(url);
2607 return FALSE;
2608 }
2609
2610
2611 static int
build_url_list(void)2612 build_url_list(
2613 void)
2614 {
2615 char *ptr;
2616 int i, count = 0;
2617 int offsets[6];
2618 int offsets_size = ARRAY_SIZE(offsets);
2619 t_url *lptr = NULL;
2620
2621 for (i = 0; i < artlines; ++i) {
2622 if (!(artline[i].flags & (C_URL | C_NEWS | C_MAIL)))
2623 continue;
2624
2625 /*
2626 * Line contains a URL, so read it in
2627 */
2628 if (fseek(pgart.cooked, artline[i].offset, SEEK_SET) == -1) /* skip on error */
2629 continue;
2630 if ((ptr = tin_fgets(pgart.cooked, FALSE)) == NULL)
2631 continue;
2632
2633 /*
2634 * Step through, finding URL's
2635 */
2636 forever {
2637 /* any matches left? */
2638 if (pcre_exec(url_regex.re, url_regex.extra, ptr, strlen(ptr), 0, 0, offsets, offsets_size) == PCRE_ERROR_NOMATCH)
2639 if (pcre_exec(mail_regex.re, mail_regex.extra, ptr, strlen(ptr), 0, 0, offsets, offsets_size) == PCRE_ERROR_NOMATCH)
2640 if (pcre_exec(news_regex.re, news_regex.extra, ptr, strlen(ptr), 0, 0, offsets, offsets_size) == PCRE_ERROR_NOMATCH)
2641 break;
2642
2643 *(ptr + offsets[1]) = '\0';
2644
2645 if (!lptr)
2646 lptr = url_list = my_malloc(sizeof(t_url));
2647 else {
2648 lptr->next = my_malloc(sizeof(t_url));
2649 lptr = lptr->next;
2650 }
2651 lptr->url = my_strdup(ptr + offsets[0]);
2652 lptr->next = NULL;
2653 ++count;
2654
2655 ptr += offsets[1] + 1;
2656 }
2657 }
2658 return count;
2659 }
2660
2661
2662 static void
free_url_list(void)2663 free_url_list(
2664 void)
2665 {
2666 t_url *p, *q;
2667
2668 for (p = url_list; p != NULL; p = q) {
2669 q = p->next;
2670 free(p->url);
2671 free(p);
2672 }
2673 url_list = NULL;
2674 }
2675