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