"Fossies" - the Fresh Open Source Software Archive 
Member "tin-2.6.2/src/page.c" (9 Dec 2022, 69299 Bytes) of package /linux/misc/tin-2.6.2.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.1_vs_2.6.2.
1 /*
2 * Project : tin - a Usenet reader
3 * Module : page.c
4 * Author : I. Lea & R. Skrenta
5 * Created : 1991-04-01
6 * Updated : 2022-10-27
7 * Notes :
8 *
9 * Copyright (c) 1991-2023 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_CONNECTION_INFO:
822 XFACE_CLEAR();
823 show_connection_page(PAGE_LEVEL, _(txt_connection_info));
824 draw_page(group->name, 0);
825 break;
826
827 case GLOBAL_TOGGLE_HELP_DISPLAY: /* toggle mini help menu */
828 toggle_mini_help(PAGE_LEVEL);
829 draw_page(group->name, 0);
830 break;
831
832 case GLOBAL_QUIT: /* return to index page */
833 return_to_index:
834 XFACE_CLEAR();
835 i = which_thread(this_resp);
836 if (threadnum)
837 *threadnum = which_response(this_resp);
838
839 return i;
840
841 case GLOBAL_TOGGLE_INVERSE_VIDEO: /* toggle inverse video */
842 toggle_inverse_video();
843 draw_page(group->name, 0);
844 show_inverse_video_status();
845 break;
846
847 #ifdef HAVE_COLOR
848 case GLOBAL_TOGGLE_COLOR: /* toggle color */
849 if (toggle_color()) {
850 draw_page(group->name, 0);
851 show_color_status();
852 }
853 break;
854 #endif /* HAVE_COLOR */
855
856 case PAGE_LIST_THREAD: /* -> thread page that this article is in */
857 XFACE_CLEAR();
858 fixup_thread(this_resp, FALSE);
859 return GRP_GOTOTHREAD;
860
861 case GLOBAL_OPTION_MENU: /* option menu */
862 XFACE_CLEAR();
863 old_artnum = arts[this_resp].artnum;
864 config_page(group->name, signal_context);
865 if ((this_resp = find_artnum(old_artnum)) == -1 || which_thread(this_resp) == -1) { /* We have lost the thread */
866 pos_first_unread_thread();
867 return GRP_EXIT;
868 }
869 fixup_thread(this_resp, FALSE);
870 draw_page(group->name, 0);
871 break;
872
873 case PAGE_NEXT_ARTICLE: /* skip to next article */
874 XFACE_CLEAR();
875 if ((n = next_response(this_resp)) == -1)
876 return (which_thread(this_resp));
877
878 if ((i = load_article(n, group)) < 0)
879 return i;
880 break;
881
882 case PAGE_MARK_THREAD_READ: /* mark rest of thread as read */
883 thd_mark_read(group, this_resp);
884 if ((n = next_unread(next_response(this_resp))) == -1)
885 goto return_to_index;
886 if ((i = load_article(n, group)) < 0) {
887 XFACE_CLEAR();
888 return i;
889 }
890 break;
891
892 case PAGE_NEXT_UNREAD_ARTICLE: /* next unread article */
893 goto page_goto_next_unread;
894
895 case PAGE_PREVIOUS_ARTICLE: /* previous article */
896 XFACE_CLEAR();
897 if ((n = prev_response(this_resp)) == -1)
898 return this_resp;
899
900 if ((i = load_article(n, group)) < 0)
901 return i;
902 break;
903
904 case PAGE_PREVIOUS_UNREAD_ARTICLE: /* previous unread article */
905 if ((n = prev_unread(prev_response(this_resp))) == -1)
906 info_message(_(txt_no_prev_unread_art));
907 else {
908 if ((i = load_article(n, group)) < 0) {
909 XFACE_CLEAR();
910 return i;
911 }
912 }
913 break;
914
915 case GLOBAL_QUIT_TIN: /* quit */
916 XFACE_CLEAR();
917 return GRP_QUIT;
918
919 case PAGE_REPLY_QUOTE: /* reply to author through mail */
920 case PAGE_REPLY_QUOTE_HEADERS:
921 case PAGE_REPLY:
922 XFACE_CLEAR();
923 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);
924 draw_page(group->name, 0);
925 break;
926
927 case PAGE_TAG: /* tag/untag article for saving */
928 tag_article(this_resp);
929 break;
930
931 case PAGE_GROUP_SELECT: /* return to group selection page */
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 (post_hist_page())
959 return GRP_EXIT;
960 else {
961 XFACE_SHOW();
962 }
963 break;
964
965 case MARK_ARTICLE_UNREAD: /* mark article as unread(to return) */
966 art_mark(group, &arts[this_resp], ART_WILL_RETURN);
967 info_message(_(txt_marked_as_unread), _(txt_article_upper));
968 break;
969
970 case PAGE_SKIP_INCLUDED_TEXT: /* skip included text */
971 for (i = j = curr_line; i < artlines; i++) {
972 if (artline[i].flags & (C_QUOTE1 | C_QUOTE2 | C_QUOTE3)) {
973 j = i;
974 break;
975 }
976 }
977
978 for (; j < artlines; j++) {
979 if (!(artline[j].flags & (C_QUOTE1 | C_QUOTE2 | C_QUOTE3)))
980 break;
981 }
982
983 if (j != curr_line) {
984 curr_line = j;
985 draw_page(group->name, 0);
986 }
987 break;
988
989 case GLOBAL_TOGGLE_INFO_LAST_LINE: /* this is _not_ correct, we do not toggle status here */
990 info_message("%s", arts[this_resp].subject);
991 break;
992
993 case PAGE_TOGGLE_HIGHLIGHTING:
994 word_highlight = bool_not(word_highlight);
995 draw_page(group->name, 0);
996 info_message(_(txt_toggled_high), txt_onoff[word_highlight != FALSE ? 1 : 0]);
997 break;
998
999 case PAGE_VIEW_ATTACHMENTS:
1000 XFACE_SUPPRESS();
1001 hide_uue_tmp = hide_uue;
1002 hide_uue = UUE_NO;
1003 resize_article(TRUE, &pgart);
1004 attachment_page(&pgart);
1005 hide_uue = hide_uue_tmp;
1006 resize_article(TRUE, &pgart);
1007 draw_page(group->name, 0);
1008 XFACE_SHOW();
1009 break;
1010
1011 case PAGE_VIEW_URL:
1012 if (!show_raw_article) { /* cooked mode? */
1013 t_bool success;
1014
1015 XFACE_SUPPRESS();
1016 resize_article(FALSE, &pgart); /* unbreak long lines */
1017 success = url_page();
1018 resize_article(TRUE, &pgart); /* rebreak long lines */
1019 draw_page(group->name, 0);
1020 if (!success)
1021 info_message(_(txt_url_done));
1022 XFACE_SHOW();
1023 }
1024 break;
1025
1026 default:
1027 info_message(_(txt_bad_command), PrintFuncKey(key, GLOBAL_HELP, page_keys));
1028 }
1029 }
1030 /* NOTREACHED */
1031 return GRP_ARTUNAVAIL;
1032 }
1033
1034
1035 static void
1036 print_message_page(
1037 FILE *file,
1038 t_lineinfo *messageline,
1039 size_t messagelines,
1040 size_t base_line,
1041 size_t begin,
1042 size_t end,
1043 int help_level)
1044 {
1045 char *line;
1046 char *p;
1047 int bytes;
1048 size_t i = begin;
1049 t_lineinfo *curr;
1050
1051 for (; i < end; i++) {
1052 if (base_line + i >= messagelines) /* ran out of message */
1053 break;
1054
1055 curr = &messageline[base_line + i];
1056
1057 if (fseek(file, curr->offset, SEEK_SET) != 0)
1058 break;
1059
1060 if ((line = tin_fgets(file, FALSE)) == NULL)
1061 break; /* ran out of message */
1062
1063 if ((help_level == INFO_PAGER) && (strwidth(line) >= cCOLS - 1))
1064 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1065 {
1066 char *tmp, *f, *t;
1067
1068 f = tmp = strunc(line, cCOLS - 1);
1069 t = line;
1070 while (*f)
1071 *t++ = *f++;
1072 *t = '\0';
1073 free(tmp);
1074 }
1075 #else
1076 line[cCOLS - 1] = '\0';
1077 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1078
1079 /*
1080 * use the offsets gained while doing line wrapping to
1081 * determine the correct position to truncate the line
1082 */
1083 if ((help_level != INFO_PAGER) && (base_line + i < messagelines - 1)) { /* not last line of message */
1084 bytes = (int) ((curr + 1)->offset - curr->offset);
1085 line[bytes] = '\0';
1086 }
1087
1088 /*
1089 * rotN encoding on body and sig data only
1090 */
1091 if ((rotate != 0) && ((curr->flags & (C_BODY | C_SIG)) || show_raw_article)) {
1092 for (p = line; *p; p++) {
1093 if (*p >= 'A' && *p <= 'Z')
1094 *p = (char) ((*p - 'A' + rotate) % 26 + 'A');
1095 else if (*p >= 'a' && *p <= 'z')
1096 *p = (char) ((*p - 'a' + rotate) % 26 + 'a');
1097 }
1098 }
1099
1100 strip_line(line);
1101
1102 #ifndef USE_CURSES
1103 snprintf(screen[i + (size_t) scroll_region_top].col, (size_t) cCOLS, "%s" cCRLF, line);
1104 #endif /* !USE_CURSES */
1105
1106 MoveCursor((int) (i + (size_t) scroll_region_top), 0);
1107 draw_pager_line(line, curr->flags, show_raw_article);
1108
1109 /*
1110 * Highlight URL's and mail addresses
1111 */
1112 if (tinrc.url_highlight) {
1113 if (curr->flags & C_URL)
1114 #ifdef HAVE_COLOR
1115 highlight_regexes((int) (i + (size_t) scroll_region_top), &url_regex, use_color ? tinrc.col_urls : -1);
1116 #else
1117 highlight_regexes((int) (i + (size_t) scroll_region_top), &url_regex, -1);
1118 #endif /* HAVE_COLOR */
1119
1120 if (curr->flags & C_MAIL)
1121 #ifdef HAVE_COLOR
1122 highlight_regexes((int) (i + (size_t) scroll_region_top), &mail_regex, use_color ? tinrc.col_urls : -1);
1123 #else
1124 highlight_regexes((int) (i + (size_t) scroll_region_top), &mail_regex, -1);
1125 #endif /* HAVE_COLOR */
1126
1127 if (curr->flags & C_NEWS)
1128 #ifdef HAVE_COLOR
1129 highlight_regexes((int) (i + (size_t) scroll_region_top), &news_regex, use_color ? tinrc.col_urls : -1);
1130 #else
1131 highlight_regexes((int) (i + (size_t) scroll_region_top), &news_regex, -1);
1132 #endif /* HAVE_COLOR */
1133 }
1134
1135 /*
1136 * Highlight /slashes/, *stars*, _underscores_ and -strokes-
1137 */
1138 if (word_highlight && (curr->flags & C_BODY) && !(curr->flags & C_CTRLL)) {
1139 #ifdef HAVE_COLOR
1140 highlight_regexes((int) (i + (size_t) scroll_region_top), &slashes_regex, use_color ? tinrc.col_markslash : tinrc.mono_markslash);
1141 highlight_regexes((int) (i + (size_t) scroll_region_top), &stars_regex, use_color ? tinrc.col_markstar : tinrc.mono_markstar);
1142 highlight_regexes((int) (i + (size_t) scroll_region_top), &underscores_regex, use_color ? tinrc.col_markdash : tinrc.mono_markdash);
1143 highlight_regexes((int) (i + (size_t) scroll_region_top), &strokes_regex, use_color ? tinrc.col_markstroke : tinrc.mono_markstroke);
1144 #else
1145 highlight_regexes((int) (i + (size_t) scroll_region_top), &slashes_regex, tinrc.mono_markslash);
1146 highlight_regexes((int) (i + (size_t) scroll_region_top), &stars_regex, tinrc.mono_markstar);
1147 highlight_regexes((int) (i + (size_t) scroll_region_top), &underscores_regex, tinrc.mono_markdash);
1148 highlight_regexes((int) (i + (size_t) scroll_region_top), &strokes_regex, tinrc.mono_markstroke);
1149 #endif /* HAVE_COLOR */
1150 }
1151
1152 /* Blank the screen after a ^L (only occurs when showing cooked) */
1153 if (!reveal_ctrl_l && (curr->flags & C_CTRLL) && (int) (base_line + i) > reveal_ctrl_l_lines) {
1154 CleartoEOS();
1155 break;
1156 }
1157 }
1158
1159 #ifdef HAVE_COLOR
1160 fcol(tinrc.col_text);
1161 #endif /* HAVE_COLOR */
1162
1163 show_mini_help(help_level);
1164 }
1165
1166
1167 /*
1168 * Redraw the current page, curr_line will be the first line displayed
1169 * Everything that calls draw_page() just sets curr_line, this function must
1170 * ensure it is set to something sane
1171 * If part is !=0, then only draw the first (-ve) or last (+ve) few lines
1172 */
1173 void
1174 draw_page(
1175 const char *group,
1176 int part)
1177 {
1178 int start, end; /* 1st, last line to draw */
1179
1180 signal_context = cPage;
1181
1182 /*
1183 * Can't do partial draw if term can't scroll properly
1184 */
1185 if (part && !have_linescroll)
1186 part = 0;
1187
1188 /*
1189 * Ensure curr_line is in bounds
1190 */
1191 if (curr_line < 0)
1192 curr_line = 0; /* Oops - off the top */
1193 else {
1194 if (curr_line > artlines)
1195 curr_line = artlines; /* Oops - off the end */
1196 }
1197
1198 search_line = curr_line; /* Reset search to start from top of display */
1199
1200 scroll_region_top = PAGE_HEADER;
1201
1202 /* Down-scroll, only redraw bottom 'part' lines of screen */
1203 if ((start = (part > 0) ? ARTLINES - part : 0) < 0)
1204 start = 0;
1205
1206 /* Up-scroll, only redraw the top 'part' lines of screen */
1207 if ((end = (part < 0) ? -part : ARTLINES) > ARTLINES)
1208 end = ARTLINES;
1209
1210 /*
1211 * ncurses doesn't clear the scroll area when you scroll by more than the
1212 * window size - force full redraw
1213 */
1214 if ((end - start >= ARTLINES) || (part == 0)) {
1215 ClearScreen();
1216 draw_page_header(group);
1217 } else
1218 MoveCursor(0, 0);
1219
1220 print_message_page(note_fp, artline, (size_t) artlines, (size_t) curr_line, (size_t) start, (size_t) end, PAGE_LEVEL);
1221
1222 /*
1223 * Print an appropriate footer
1224 */
1225 if (curr_line + ARTLINES >= artlines) {
1226 char buf[LEN], *buf2;
1227 int len;
1228
1229 STRCPY(buf, (arts[this_resp].thread != -1) ? _(txt_next_resp) : _(txt_last_resp));
1230 buf2 = strunc(buf, cCOLS - 1);
1231 len = strwidth(buf2);
1232 clear_message();
1233 MoveCursor(cLINES, cCOLS - len - (1 + BLANK_PAGE_COLS));
1234 #ifdef HAVE_COLOR
1235 fcol(tinrc.col_normal);
1236 #endif /* HAVE_COLOR */
1237 StartInverse();
1238 my_fputs(buf2, stdout);
1239 EndInverse();
1240 my_flush();
1241 free(buf2);
1242 } else
1243 draw_percent_mark(curr_line + ARTLINES, artlines);
1244
1245 #ifdef XFACE_ABLE
1246 if (tinrc.use_slrnface && !show_raw_article)
1247 slrnface_display_xface(note_h->xface);
1248 #endif /* XFACE_ABLE */
1249
1250 stow_cursor();
1251 }
1252
1253
1254 /*
1255 * Start external metamail program
1256 */
1257 static void
1258 invoke_metamail(
1259 FILE *fp)
1260 {
1261 char *ptr = tinrc.metamail_prog;
1262 char buf[LEN];
1263 long offset;
1264 FILE *mime_fp;
1265 #ifdef DONT_HAVE_PIPING
1266 char mimefile[PATH_LEN];
1267 int fd_mime;
1268 #endif /* DONT_HAVE_PIPING */
1269
1270 if ((*ptr == '\0') || (!strcmp(ptr, INTERNAL_CMD)) || (getenv("NOMETAMAIL") != NULL))
1271 return;
1272
1273 if ((offset = ftell(fp)) == -1) {
1274 perror_message(_(txt_command_failed), ptr);
1275 return;
1276 }
1277
1278 EndWin();
1279 Raw(FALSE);
1280
1281 #ifdef DONT_HAVE_PIPING
1282 if ((fd_mime = my_tmpfile(mimefile, sizeof(mimefile) - 1, homedir)) == -1) {
1283 perror_message(_(txt_command_failed), ptr);
1284 return;
1285 }
1286 if ((mime_fp = fdopen(fd_mime, "w")))
1287 #else
1288 if ((mime_fp = popen(ptr, "w")))
1289 #endif /* DONT_HAVE_PIPING */
1290 {
1291 rewind(fp);
1292 while (fgets(buf, (int) sizeof(buf), fp) != NULL)
1293 fputs(buf, mime_fp);
1294
1295 fflush(mime_fp);
1296 /* This is needed if we are viewing the raw art */
1297 fseek(fp, offset, SEEK_SET); /* goto old position */
1298
1299 #ifdef DONT_HAVE_PIPING
1300 snprintf(buf, sizeof(buf) - 1, "%s %s", tinrc.metamail_prog, mimefile);
1301 invoke_cmd(buf);
1302 fclose(mime_fp);
1303 unlink(mimefile);
1304 #else
1305 pclose(mime_fp);
1306 #endif /* DONT_HAVE_PIPING */
1307 } else
1308 perror_message(_(txt_command_failed), ptr);
1309
1310 #ifdef USE_CURSES
1311 Raw(TRUE);
1312 InitWin();
1313 #endif /* USE_CURSES */
1314 prompt_continue();
1315 #ifndef USE_CURSES
1316 Raw(TRUE);
1317 InitWin();
1318 #endif /* !USE_CURSES */
1319 }
1320
1321
1322 /*
1323 * PAGE_HEADER defines the size in lines of this header
1324 */
1325 static void
1326 draw_page_header(
1327 const char *group)
1328 {
1329 char *buf, *tmp;
1330 int i;
1331 int whichresp, x_resp;
1332 int len, right_len, center_pos, cur_pos;
1333 size_t line_len;
1334 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1335 wchar_t *fmt_resp = NULL, *fmt_thread, *wtmp, *wtmp2, *wbuf;
1336 #else
1337 char *tmp2;
1338 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1339
1340 whichresp = which_response(this_resp);
1341 if ((i = which_thread(this_resp)) >= 0)
1342 x_resp = num_of_responses(i);
1343 else
1344 x_resp = 0;
1345
1346 line_len = LEN + 1;
1347 buf = my_malloc(line_len);
1348
1349 if (!my_strftime(buf, line_len, curr_group->attribute->date_format, localtime(&arts[this_resp].date))) {
1350 strncpy(buf, BlankIfNull(note_h->date), line_len);
1351 buf[line_len - 1] = '\0';
1352 }
1353
1354 # ifdef HAVE_COLOR
1355 fcol(tinrc.col_head);
1356 # endif /* HAVE_COLOR */
1357
1358 /* first line */
1359 cur_pos = 0;
1360
1361 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1362
1363 /* convert to wide-char format string */
1364 fmt_thread = char2wchar_t(_(txt_thread_x_of_n));
1365
1366 /*
1367 * Determine the needed space for the text at the right hand margin.
1368 * The formatting info (%4s) needs 3 positions but we need 4 positions
1369 * on the screen for each counter.
1370 */
1371 if (fmt_thread)
1372 right_len = wcswidth(fmt_thread, wcslen(fmt_thread)) - 6 + 8;
1373 else
1374 right_len = 0;
1375 FreeIfNeeded(fmt_thread);
1376
1377 /*
1378 * limit right_len to cCOLS / 3
1379 */
1380 if (right_len > cCOLS / 3 + 1)
1381 right_len = cCOLS / 3 + 1;
1382
1383 /* date */
1384 if ((wtmp = char2wchar_t(buf)) != NULL) {
1385 my_fputws(wtmp, stdout);
1386 cur_pos += wcswidth(wtmp, wcslen(wtmp));
1387 free(wtmp);
1388 }
1389
1390 /*
1391 * determine max len for centered group name
1392 * allow one space before and after group name
1393 */
1394 len = cCOLS - 2 * MAX(cur_pos, right_len) - 3;
1395
1396 /* group name */
1397 if ((wtmp = char2wchar_t(group)) != NULL) {
1398 /* wconvert_to_printable(wtmp, FALSE); */
1399 if (tinrc.abbreviate_groupname)
1400 wtmp2 = abbr_wcsgroupname(wtmp, len);
1401 else
1402 wtmp2 = wstrunc(wtmp, len);
1403
1404 if ((i = wcswidth(wtmp2, wcslen(wtmp2))) < len)
1405 len = i;
1406
1407 center_pos = (cCOLS - len) / 2;
1408
1409 /* pad out to left */
1410 for (; cur_pos < center_pos; cur_pos++)
1411 my_fputc(' ', stdout);
1412
1413 my_fputws(wtmp2, stdout);
1414 cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
1415 free(wtmp2);
1416 free(wtmp);
1417 }
1418
1419 /* pad out to right */
1420 for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
1421 my_fputc(' ', stdout);
1422
1423 /* thread info */
1424 /* can't eval tin_ltoa() more than once in a statement due to statics */
1425 strcpy(buf, tin_ltoa(which_thread(this_resp) + 1, 4));
1426 tmp = strunc(_(txt_thread_x_of_n), cCOLS / 3 - 1);
1427 my_printf(tmp, buf, tin_ltoa(grpmenu.max, 4));
1428 free(tmp);
1429
1430 my_fputs(cCRLF, stdout);
1431
1432 # if 0
1433 /* display a ruler for layout checking purposes */
1434 my_fputs("....|....3....|....2....|....1....|....0....|....1....|....2....|....3....|....\n", stdout);
1435 # endif /* 0 */
1436
1437 /*
1438 * second line
1439 */
1440 cur_pos = 0;
1441
1442 /*
1443 * Convert to wide-char format string and determine the needed space
1444 * for the text at the right hand margin. The formatting info (%4s)
1445 * needs 3 positions but we need 4 positions on the screen for each
1446 * counter.
1447 */
1448
1449 right_len = 0;
1450 if (whichresp && (fmt_resp = char2wchar_t(_(txt_art_x_of_n)))) {
1451 right_len = wcswidth(fmt_resp, wcslen(fmt_resp)) - 6 + 8;
1452 } else {
1453 if ((!x_resp && (fmt_resp = char2wchar_t(_(txt_no_responses)))) || (x_resp == 1 && (fmt_resp = char2wchar_t(_(txt_1_resp)))))
1454 right_len = wcswidth(fmt_resp, wcslen(fmt_resp));
1455 else if ((fmt_resp = char2wchar_t(_(txt_x_resp))))
1456 right_len = wcswidth(fmt_resp, wcslen(fmt_resp)) - 3 + 4;
1457 }
1458 FreeIfNeeded(fmt_resp);
1459
1460 /*
1461 * limit right_len to cCOLS / 3
1462 */
1463 if (right_len > cCOLS / 3 + 1)
1464 right_len = cCOLS / 3 + 1;
1465
1466 /* line count */
1467 if (arts[this_resp].line_count < 0)
1468 strcpy(buf, "?");
1469 else
1470 snprintf(buf, line_len, "%-4d", arts[this_resp].line_count);
1471
1472 {
1473 wchar_t *fmt;
1474
1475 if ((wtmp = char2wchar_t(_(txt_lines))) != NULL) {
1476 int tex_space = pgart.tex2iso ? 5 : 0;
1477
1478 fmt = wstrunc(wtmp, cCOLS / 3 - 1 - tex_space);
1479 wtmp = my_realloc(wtmp, sizeof(wchar_t) * line_len);
1480 swprintf(wtmp, line_len, fmt, buf);
1481 my_fputws(wtmp, stdout);
1482 cur_pos += wcswidth(wtmp, wcslen(wtmp));
1483 free(fmt);
1484 free(wtmp);
1485 }
1486 }
1487
1488 # ifdef HAVE_COLOR
1489 fcol(tinrc.col_subject);
1490 # endif /* HAVE_COLOR */
1491
1492 /* tex2iso */
1493 if (pgart.tex2iso) {
1494 if ((wtmp = char2wchar_t(_(txt_tex))) != NULL) {
1495 wtmp2 = wstrunc(wtmp, 5);
1496 my_fputws(wtmp2, stdout);
1497 cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
1498 free(wtmp);
1499 free(wtmp2);
1500 }
1501 }
1502
1503 /* subject */
1504 /*
1505 * TODO: why do we fall back to arts[this_resp].subject if !note_h->subj?
1506 * if !note_h->subj then the article just has no subject, no matter
1507 * what the overview says.
1508 *
1509 * add BiDi handling
1510 */
1511 strncpy(buf, (note_h->subj ? note_h->subj : arts[this_resp].subject), line_len);
1512 buf[line_len - 1] = '\0';
1513 if ((wtmp = char2wchar_t(buf)) != NULL) {
1514 wbuf = wexpand_tab(wtmp, tabwidth);
1515 wtmp2 = wstrunc(wbuf, cCOLS - 2 * right_len - 4);
1516 center_pos = (cCOLS - wcswidth(wtmp2, wcslen(wtmp2))) / 2;
1517
1518 /* pad out to left */
1519 for (; cur_pos < center_pos; cur_pos++)
1520 my_fputc(' ', stdout);
1521
1522 StartInverse();
1523 my_fputws(wtmp2, stdout);
1524 EndInverse();
1525 cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
1526 free(wtmp2);
1527 free(wtmp);
1528 free(wbuf);
1529 }
1530
1531 # ifdef HAVE_COLOR
1532 fcol(tinrc.col_response);
1533 # endif /* HAVE_COLOR */
1534
1535 /* pad out to right */
1536 for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
1537 my_fputc(' ', stdout);
1538
1539 if (whichresp) {
1540 tmp = strunc(_(txt_art_x_of_n), cCOLS / 3 - 1);
1541 my_printf(tmp, whichresp + 1, x_resp + 1);
1542 free(tmp);
1543 } else {
1544 /* TODO: ngettext */
1545 if (!x_resp) {
1546 tmp = strunc(_(txt_no_responses), cCOLS / 3 - 1);
1547 my_printf("%s", tmp);
1548 } else if (x_resp == 1) {
1549 tmp = strunc(_(txt_1_resp), cCOLS / 3 - 1);
1550 my_printf("%s", tmp);
1551 } else {
1552 tmp = strunc(_(txt_x_resp), cCOLS / 3 - 1);
1553 my_printf(tmp, x_resp);
1554 }
1555 free(tmp);
1556 }
1557 my_fputs(cCRLF, stdout);
1558
1559 /*
1560 * third line
1561 */
1562 cur_pos = 0;
1563
1564 # ifdef HAVE_COLOR
1565 fcol(tinrc.col_from);
1566 # endif /* HAVE_COLOR */
1567 /* from */
1568 /*
1569 * TODO: don't use arts[this_resp].name/arts[this_resp].from
1570 * split up note_h->from and use that instead as it might
1571 * be different _if_ the overviews are broken
1572 *
1573 * add BiDi handling
1574 */
1575 {
1576 char *p = idna_decode(arts[this_resp].from);
1577
1578 if (arts[this_resp].name)
1579 snprintf(buf, line_len, "%s <%s>", arts[this_resp].name, p);
1580 else {
1581 strncpy(buf, p, line_len);
1582 buf[line_len - 1] = '\0';
1583 }
1584 free(p);
1585 }
1586
1587 if ((wtmp = char2wchar_t(buf)) != NULL) {
1588 wtmp2 = wstrunc(wtmp, cCOLS - 1);
1589 my_fputws(wtmp2, stdout);
1590 cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
1591 free(wtmp2);
1592 free(wtmp);
1593 }
1594
1595 /*
1596 * Organization
1597 *
1598 * TODO: IDNA decoding, see also comment in
1599 * cook.c:cook_article()
1600 * add BiDi handling
1601 */
1602 if (note_h->org) {
1603 snprintf(buf, line_len, _(txt_at_s), note_h->org);
1604
1605 if ((wtmp = char2wchar_t(buf)) != NULL) {
1606 wbuf = wexpand_tab(wtmp, tabwidth);
1607 wtmp2 = wstrunc(wbuf, cCOLS - cur_pos - 1);
1608
1609 i = cCOLS - wcswidth(wtmp2, wcslen(wtmp2)) - 1;
1610 for (; cur_pos < i; cur_pos++)
1611 my_fputc(' ', stdout);
1612
1613 my_fputws(wtmp2, stdout);
1614 free(wtmp2);
1615 free(wtmp);
1616 free(wbuf);
1617 }
1618 }
1619
1620 #else /* !MULTIBYTE_ABLE || NO_LOCALE */
1621
1622 /*
1623 * determine the needed space for the text at the right hand margin
1624 * the formatting info (%4s) needs 3 positions but we need 4 positions
1625 * on the screen for each counter
1626 */
1627 right_len = strlen(_(txt_thread_x_of_n)) - 6 + 8;
1628
1629 /* date */
1630 my_fputs(buf, stdout);
1631 cur_pos += strlen(buf);
1632
1633 /*
1634 * determine max len for centered group name
1635 * allow one space before and after group name
1636 */
1637 len = cCOLS - 2 * MAX(cur_pos, right_len) - 3;
1638
1639 /* group name */
1640 if (tinrc.abbreviate_groupname)
1641 tmp = abbr_groupname(group, len);
1642 else
1643 tmp = strunc(group, len);
1644
1645 if ((i = strlen(tmp)) < len)
1646 len = i;
1647
1648 center_pos = (cCOLS - len) / 2;
1649
1650 /* pad out to left */
1651 for (; cur_pos < center_pos; cur_pos++)
1652 my_fputc(' ', stdout);
1653
1654 my_fputs(tmp, stdout);
1655 cur_pos += strlen(tmp);
1656 free(tmp);
1657
1658 /* pad out to right */
1659 for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
1660 my_fputc(' ', stdout);
1661
1662 /* thread info */
1663 /* can't eval tin_ltoa() more than once in a statement due to statics */
1664 strcpy(buf, tin_ltoa(which_thread(this_resp) + 1, 4));
1665 my_printf(_(txt_thread_x_of_n), buf, tin_ltoa(grpmenu.max, 4));
1666
1667 my_fputs(cCRLF, stdout);
1668
1669 # if 0
1670 /* display a ruler for layout checking purposes */
1671 my_fputs("....|....3....|....2....|....1....|....0....|....1....|....2....|....3....|....\n", stdout);
1672 # endif /* 0 */
1673
1674 /*
1675 * second line
1676 */
1677 cur_pos = 0;
1678
1679 /*
1680 * determine the needed space for the text at the right hand margin
1681 * the formatting info (%4s) needs 3 positions but we need 4 positions
1682 * on the screen for each counter
1683 */
1684 if (whichresp) {
1685 right_len = strlen(_(txt_art_x_of_n)) - 6 + 8;
1686 } else {
1687 if (!x_resp)
1688 right_len = strlen(_(txt_no_responses));
1689 else if (x_resp == 1)
1690 right_len = strlen(_(txt_1_resp));
1691 else
1692 right_len = strlen(_(txt_x_resp)) - 3 + 4;
1693 }
1694
1695 /* line count */
1696 /* an accurate line count will appear in the footer anymay */
1697 if (arts[this_resp].line_count < 0)
1698 strcpy(buf, "?");
1699 else
1700 snprintf(buf, line_len, "%-4d", arts[this_resp].line_count);
1701
1702 tmp = my_malloc(line_len);
1703 snprintf(tmp, line_len, _(txt_lines), buf);
1704 my_fputs(tmp, stdout);
1705 cur_pos += strlen(tmp);
1706 free(tmp);
1707
1708 # ifdef HAVE_COLOR
1709 fcol(tinrc.col_subject);
1710 # endif /* HAVE_COLOR */
1711
1712 /* tex2iso */
1713 if (pgart.tex2iso) {
1714 my_fputs(_(txt_tex), stdout);
1715 cur_pos += strlen(_(txt_tex));
1716 }
1717
1718 /* subject */
1719 /*
1720 * TODO: why do we fall back to arts[this_resp].subject if !note_h->subj?
1721 * if !note_h->subj then the article just has no subject, no matter
1722 * what the overview says.
1723 */
1724 strncpy(buf, (note_h->subj ? note_h->subj : arts[this_resp].subject), line_len);
1725 buf[line_len - 1] = '\0';
1726
1727 tmp2 = expand_tab(buf, tabwidth);
1728 tmp = strunc(tmp2, cCOLS - 2 * right_len - 3);
1729
1730 center_pos = (cCOLS - strlen(tmp)) / 2;
1731
1732 /* pad out to left */
1733 for (; cur_pos < center_pos; cur_pos++)
1734 my_fputc(' ', stdout);
1735
1736 StartInverse();
1737 my_fputs(tmp, stdout);
1738 EndInverse();
1739 cur_pos += strlen(tmp);
1740 free(tmp);
1741 free(tmp2);
1742
1743 # ifdef HAVE_COLOR
1744 fcol(tinrc.col_response);
1745 # endif /* HAVE_COLOR */
1746
1747 /* pad out to right */
1748 for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
1749 my_fputc(' ', stdout);
1750
1751 if (whichresp)
1752 my_printf(_(txt_art_x_of_n), whichresp + 1, x_resp + 1);
1753 else {
1754 /* TODO: ngettext */
1755 if (!x_resp)
1756 my_printf("%s", _(txt_no_responses));
1757 else if (x_resp == 1)
1758 my_printf("%s", _(txt_1_resp));
1759 else
1760 my_printf(_(txt_x_resp), x_resp);
1761 }
1762 my_fputs(cCRLF, stdout);
1763
1764 /*
1765 * third line
1766 */
1767 cur_pos = 0;
1768
1769 # ifdef HAVE_COLOR
1770 fcol(tinrc.col_from);
1771 # endif /* HAVE_COLOR */
1772 /* from */
1773 /*
1774 * TODO: don't use arts[this_resp].name/arts[this_resp].from
1775 * split up note_h->from and use that instead as it might
1776 * be different _if_ the overviews are broken
1777 */
1778 if (arts[this_resp].name)
1779 snprintf(buf, line_len, "%s <%s>", arts[this_resp].name, arts[this_resp].from);
1780 else {
1781 strncpy(buf, arts[this_resp].from, line_len);
1782 buf[line_len - 1] = '\0';
1783 }
1784
1785 tmp = strunc(buf, cCOLS - 1);
1786 my_fputs(tmp, stdout);
1787 cur_pos += strlen(tmp);
1788 free(tmp);
1789
1790 if (note_h->org && cCOLS - cur_pos - 1 >= (int) strlen(_(txt_at_s)) - 2 + 3) {
1791 /* we have enough space to print at least " at ..." */
1792 snprintf(buf, line_len, _(txt_at_s), note_h->org);
1793
1794 tmp2 = expand_tab(buf, tabwidth);
1795 tmp = strunc(tmp2, cCOLS - cur_pos - 1);
1796 len = cCOLS - (int) strlen(tmp) - 1;
1797 for (; cur_pos < len; cur_pos++)
1798 my_fputc(' ', stdout);
1799 my_fputs(tmp, stdout);
1800 free(tmp);
1801 free(tmp2);
1802 }
1803
1804 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1805
1806 my_fputs(cCRLF, stdout);
1807 my_fputs(cCRLF, stdout);
1808
1809 free(buf);
1810
1811 #ifdef HAVE_COLOR
1812 fcol(tinrc.col_normal);
1813 #endif /* HAVE_COLOR */
1814 }
1815
1816
1817 /*
1818 * Change the pager article context to arts[new_respnum]
1819 * Return GRP_ARTUNAVAIL if article could not be opened
1820 * or GRP_ARTABORT if load of article was interrupted
1821 * or 0 on success
1822 */
1823 static int
1824 load_article(
1825 int new_respnum,
1826 struct t_group *group)
1827 {
1828 static t_bool art_closed = FALSE;
1829
1830 #ifdef DEBUG
1831 if (debug & DEBUG_MISC)
1832 fprintf(stderr, "load_art %s(new=%d, curr=%d)\n", (new_respnum == this_resp && !art_closed) ? "ALREADY OPEN!" : "", new_respnum, this_resp);
1833 #endif /* DEBUG */
1834
1835 if (new_respnum != this_resp || art_closed) {
1836 int ret;
1837
1838 art_close(&pgart); /* close previously opened art in pager */
1839 ret = art_open(TRUE, &arts[new_respnum], group, &pgart, TRUE, _(txt_reading_article));
1840
1841 switch (ret) {
1842 case ART_UNAVAILABLE:
1843 art_mark(group, &arts[new_respnum], ART_READ);
1844 /* prevent retagging as unread in unfilter_articles() */
1845 if (arts[new_respnum].killed == ART_KILLED_UNREAD)
1846 arts[new_respnum].killed = ART_KILLED;
1847 art_closed = TRUE;
1848 wait_message(1, _(txt_art_unavailable));
1849 return GRP_ARTUNAVAIL;
1850
1851 case ART_ABORT:
1852 art_close(&pgart);
1853 art_closed = TRUE;
1854 return GRP_ARTABORT; /* special retcode to stop redrawing screen */
1855
1856 default: /* Normal case */
1857 #if 0 /* Very useful debugging tool */
1858 if (prompt_yn("Fake art unavailable? ", FALSE) == 1) {
1859 art_close(&pgart);
1860 art_mark(group, &arts[new_respnum], ART_READ);
1861 art_closed = TRUE;
1862 return GRP_ARTUNAVAIL;
1863 }
1864 #endif /* 0 */
1865 if (art_closed)
1866 art_closed = FALSE;
1867 if (new_respnum != this_resp) {
1868 /*
1869 * Remember current & previous articles for '-' command
1870 */
1871 last_resp = this_resp;
1872 this_resp = new_respnum; /* Set new art globally */
1873 }
1874 break;
1875 }
1876 } else if (show_all_headers) {
1877 /*
1878 * article is already opened with show_all_headers ON
1879 * -> re-cook it
1880 */
1881 show_all_headers = FALSE;
1882 resize_article(TRUE, &pgart);
1883 }
1884
1885 art_mark(group, &arts[this_resp], ART_READ);
1886
1887 /*
1888 * Change status if art was unread before killing to
1889 * prevent retagging as unread in unfilter_articles()
1890 */
1891 if (arts[this_resp].killed == ART_KILLED_UNREAD)
1892 arts[this_resp].killed = ART_KILLED;
1893
1894 if (pgart.cooked == NULL) { /* harmony corruption */
1895 wait_message(1, _(txt_art_unavailable));
1896 return GRP_ARTUNAVAIL;
1897 }
1898
1899 /*
1900 * Setup to start viewing cooked version
1901 */
1902 show_raw_article = FALSE;
1903 show_all_headers = FALSE;
1904 curr_line = 0;
1905 note_fp = pgart.cooked;
1906 artline = pgart.cookl;
1907 artlines = pgart.cooked_lines;
1908 search_line = 0;
1909 /*
1910 * Reset offsets only if not invoked during 'body search' (srch_lineno != -1)
1911 * otherwise the found string will not be highlighted
1912 */
1913 if (srch_lineno == -1)
1914 reset_srch_offsets();
1915 rotate = 0; /* normal mode, not rot13 */
1916 reveal_ctrl_l = FALSE;
1917 reveal_ctrl_l_lines = -1; /* all ^L's active */
1918 hide_uue = tinrc.hide_uue;
1919
1920 draw_page(group->name, 0);
1921
1922 /*
1923 * Automatically invoke attachment viewing if requested
1924 */
1925 if (!note_h->mime || IS_PLAINTEXT(note_h->ext)) /* Text only article */
1926 return 0;
1927
1928 if (*tinrc.metamail_prog == '\0' || getenv("NOMETAMAIL") != NULL) /* Viewer turned off */
1929 return 0;
1930
1931 if (group->attribute->ask_for_metamail) {
1932 if (prompt_yn(_(txt_use_mime), TRUE) != 1)
1933 return 0;
1934 }
1935
1936 XFACE_SUPPRESS();
1937 if (strcmp(tinrc.metamail_prog, INTERNAL_CMD) == 0) /* Use internal viewer */
1938 decode_save_mime(&pgart, FALSE);
1939 else
1940 invoke_metamail(pgart.raw);
1941 XFACE_SHOW();
1942 return 0;
1943 }
1944
1945
1946 static int
1947 prompt_response(
1948 int ch,
1949 int curr_respnum)
1950 {
1951 int i, num;
1952
1953 clear_message();
1954
1955 if ((num = prompt_num(ch, _(txt_select_art))) < 0) {
1956 clear_message();
1957 return -1;
1958 }
1959
1960 if ((i = which_thread(curr_respnum)) >= 0)
1961 return find_response(i, num - 1);
1962 else
1963 return -1;
1964 }
1965
1966
1967 /*
1968 * Reposition within message as needed, highlight searched string
1969 * This is tied quite closely to the information stored by
1970 * get_search_vectors()
1971 */
1972 static void
1973 process_search(
1974 int *lcurr_line,
1975 size_t message_lines,
1976 size_t screen_lines,
1977 int help_level)
1978 {
1979 int i, start, end;
1980
1981 if ((i = get_search_vectors(&start, &end)) == -1)
1982 return;
1983
1984 /*
1985 * Is matching line off the current view?
1986 * Reposition within article if needed, try to get matched line
1987 * in the middle of the screen
1988 */
1989 if (i < *lcurr_line || i >= (int) ((size_t) *lcurr_line + screen_lines)) {
1990 *lcurr_line = (int) ((size_t) i - (screen_lines / 2));
1991 if (((size_t) *lcurr_line + screen_lines) > message_lines) /* off the end */
1992 *lcurr_line = (int) (message_lines - screen_lines);
1993 /* else pos. is just fine */
1994 }
1995
1996 switch (help_level) {
1997 case PAGE_LEVEL:
1998 draw_page(curr_group->name, 0);
1999 break;
2000
2001 case INFO_PAGER:
2002 display_info_page(0);
2003 break;
2004
2005 default:
2006 break;
2007 }
2008 search_line = i; /* draw_page() resets this to 0 */
2009
2010 /*
2011 * Highlight found string
2012 */
2013 highlight_string(i - *lcurr_line + scroll_region_top, start, end - start);
2014 }
2015
2016
2017 /*
2018 * Implement ^H toggle between cooked and raw views of article
2019 */
2020 void
2021 toggle_raw(
2022 struct t_group *group)
2023 {
2024 if (show_raw_article) {
2025 artline = pgart.cookl;
2026 artlines = pgart.cooked_lines;
2027 note_fp = pgart.cooked;
2028 } else {
2029 static int j; /* Needed on successive invocations */
2030 int chunk = note_h->ext->line_count;
2031
2032 /*
2033 * We do this on the fly, since most of the time it won't be used
2034 */
2035 if (!pgart.rawl) { /* Already done this for this article? */
2036 char *line;
2037 char *p;
2038 long offset;
2039
2040 j = 0;
2041 rewind(pgart.raw);
2042 pgart.rawl = my_malloc(sizeof(t_lineinfo) * (size_t) chunk);
2043 offset = ftell(pgart.raw);
2044
2045 while ((line = tin_fgets(pgart.raw, FALSE)) != NULL) {
2046 int space;
2047 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
2048 int num_bytes;
2049 wchar_t wc;
2050 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
2051
2052 pgart.rawl[j].offset = offset;
2053 pgart.rawl[j].flags = 0;
2054 j++;
2055 if (j >= chunk) {
2056 chunk += 50;
2057 pgart.rawl = my_realloc(pgart.rawl, sizeof(t_lineinfo) * (size_t) chunk);
2058 }
2059
2060 p = line;
2061 while (*p) {
2062 space = cCOLS - 1;
2063
2064 while ((space > 0) && *p) {
2065 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
2066 num_bytes = mbtowc(&wc, p, MB_CUR_MAX);
2067 if (num_bytes != -1 && iswprint((wint_t) wc)) {
2068 if ((space -= wcwidth(wc)) < 0)
2069 break;
2070 p += num_bytes;
2071 offset += num_bytes;
2072 }
2073 #else
2074 if (my_isprint((unsigned char) *p)) {
2075 space--;
2076 p++;
2077 offset++;
2078 }
2079 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
2080 else if (IS_LOCAL_CHARSET("Big5") && (unsigned char) *p >= 0xa1 && (unsigned char) *p <= 0xfe && *(p + 1)) {
2081 /*
2082 * Big5: ASCII chars are handled by the normal code
2083 * check only for 2-byte chars
2084 * TODO: should we also check if the second byte is
2085 * also valid?
2086 */
2087 p += 2;
2088 offset += 2;
2089 space--;
2090 } else {
2091 /*
2092 * the current character can't be displayed print it as
2093 * an octal value (needs 4 columns) see also
2094 * color.c:draw_pager_line()
2095 */
2096 if ((space -= 4) < 0)
2097 break;
2098 offset++;
2099 p++;
2100 }
2101 }
2102 /*
2103 * if we reached the end of the line we don't need to
2104 * remember anything
2105 */
2106 if (*p) {
2107 pgart.rawl[j].offset = offset;
2108 pgart.rawl[j].flags = 0;
2109 if (++j >= chunk) {
2110 chunk += 50;
2111 pgart.rawl = my_realloc(pgart.rawl, sizeof(t_lineinfo) * (size_t) chunk);
2112 }
2113 }
2114 }
2115
2116 /*
2117 * only use ftell's return value here because we didn't
2118 * take the \n into account above.
2119 */
2120 offset = ftell(pgart.raw);
2121 }
2122
2123 pgart.rawl = my_realloc(pgart.rawl, sizeof(t_lineinfo) * (size_t) j);
2124 }
2125 artline = pgart.rawl;
2126 artlines = j;
2127 note_fp = pgart.raw;
2128 }
2129 curr_line = 0;
2130 show_raw_article = bool_not(show_raw_article);
2131 draw_page(group ? group->name : "", 0);
2132 }
2133
2134
2135 /*
2136 * Re-cook an article
2137 */
2138 void
2139 resize_article(
2140 t_bool wrap_lines,
2141 t_openartinfo *artinfo)
2142 {
2143 free(artinfo->cookl);
2144 if (artinfo->cooked)
2145 fclose(artinfo->cooked);
2146
2147 if (!cook_article(wrap_lines, artinfo, hide_uue, show_all_headers))
2148 tin_done(EXIT_FAILURE, _(txt_cook_article_failed_exiting), tin_progname);
2149
2150 show_raw_article = FALSE;
2151 artline = pgart.cookl;
2152 artlines = pgart.cooked_lines;
2153 note_fp = pgart.cooked;
2154 }
2155
2156
2157 /*
2158 * Infopager: simply page files
2159 */
2160 void
2161 info_pager(
2162 FILE *info_fh,
2163 const char *title,
2164 t_bool wrap_at_ends)
2165 {
2166 int offset;
2167 t_function func;
2168
2169 search_line = 0;
2170 reset_srch_offsets();
2171 info_file = info_fh;
2172 info_title = title;
2173 curr_info_line = 0;
2174 preprocess_info_message(info_fh);
2175 if (!info_fh)
2176 return;
2177 set_xclick_off();
2178 display_info_page(0);
2179
2180 forever {
2181 switch (func = handle_keypad(page_left, page_right, page_mouse_action, info_keys)) {
2182 case GLOBAL_ABORT: /* common arrow keys */
2183 break;
2184
2185 case GLOBAL_LINE_UP:
2186 if (num_info_lines <= NOTESLINES) {
2187 info_message(_(txt_begin_of_page));
2188 break;
2189 }
2190 if (curr_info_line == 0) {
2191 if (!wrap_at_ends) {
2192 info_message(_(txt_begin_of_page));
2193 break;
2194 }
2195 curr_info_line = num_info_lines - NOTESLINES;
2196 display_info_page(0);
2197 break;
2198 }
2199 offset = scroll_page(KEYMAP_UP);
2200 curr_info_line += offset;
2201 display_info_page(offset);
2202 break;
2203
2204 case GLOBAL_LINE_DOWN:
2205 if (num_info_lines <= NOTESLINES) {
2206 info_message(_(txt_end_of_page));
2207 break;
2208 }
2209 if (curr_info_line + NOTESLINES >= num_info_lines) {
2210 if (!wrap_at_ends) {
2211 info_message(_(txt_end_of_page));
2212 break;
2213 }
2214 curr_info_line = 0;
2215 display_info_page(0);
2216 break;
2217 }
2218 offset = scroll_page(KEYMAP_DOWN);
2219 curr_info_line += offset;
2220 display_info_page(offset);
2221 break;
2222
2223 case GLOBAL_PAGE_DOWN:
2224 if (num_info_lines <= NOTESLINES) {
2225 info_message(_(txt_end_of_page));
2226 break;
2227 }
2228 if (curr_info_line + NOTESLINES >= num_info_lines) { /* End is already on screen */
2229 if (!wrap_at_ends) {
2230 info_message(_(txt_end_of_page));
2231 break;
2232 }
2233 curr_info_line = 0;
2234 display_info_page(0);
2235 break;
2236 }
2237 curr_info_line += ((tinrc.scroll_lines == -2) ? NOTESLINES / 2 : NOTESLINES);
2238 display_info_page(0);
2239 break;
2240
2241 case GLOBAL_PAGE_UP:
2242 if (num_info_lines <= NOTESLINES) {
2243 info_message(_(txt_begin_of_page));
2244 break;
2245 }
2246 if (curr_info_line == 0) {
2247 if (!wrap_at_ends) {
2248 info_message(_(txt_begin_of_page));
2249 break;
2250 }
2251 curr_info_line = num_info_lines - NOTESLINES;
2252 display_info_page(0);
2253 break;
2254 }
2255 curr_info_line -= ((tinrc.scroll_lines == -2) ? NOTESLINES / 2 : NOTESLINES);
2256 display_info_page(0);
2257 break;
2258
2259 case GLOBAL_FIRST_PAGE:
2260 if (curr_info_line) {
2261 curr_info_line = 0;
2262 display_info_page(0);
2263 }
2264 break;
2265
2266 case GLOBAL_LAST_PAGE:
2267 if (curr_info_line + NOTESLINES != num_info_lines) {
2268 /* Display a full last page for neatness */
2269 curr_info_line = num_info_lines - NOTESLINES;
2270 display_info_page(0);
2271 }
2272 break;
2273
2274 case GLOBAL_TOGGLE_HELP_DISPLAY:
2275 toggle_mini_help(INFO_PAGER);
2276 display_info_page(0);
2277 break;
2278
2279 case GLOBAL_SEARCH_SUBJECT_FORWARD:
2280 case GLOBAL_SEARCH_SUBJECT_BACKWARD:
2281 case GLOBAL_SEARCH_REPEAT:
2282 if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
2283 break;
2284
2285 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)
2286 break;
2287
2288 process_search(&curr_info_line, (size_t) num_info_lines, (size_t) NOTESLINES, INFO_PAGER);
2289 break;
2290
2291 case GLOBAL_QUIT: /* quit */
2292 ClearScreen();
2293 return;
2294
2295 default:
2296 break;
2297 }
2298 }
2299 }
2300
2301
2302 /*
2303 * Redraw the current page, curr_info_line will be the first line displayed
2304 * If part is !=0, then only draw the first (-ve) or last (+ve) few lines
2305 */
2306 void
2307 display_info_page(
2308 int part)
2309 {
2310 int start, end; /* 1st, last line to draw */
2311
2312 signal_context = cInfopager;
2313
2314 /*
2315 * Can't do partial draw if term can't scroll properly
2316 */
2317 if (part && !have_linescroll)
2318 part = 0;
2319
2320 if (curr_info_line < 0)
2321 curr_info_line = 0;
2322 if (curr_info_line >= num_info_lines)
2323 curr_info_line = num_info_lines - 1;
2324
2325 scroll_region_top = INDEX_TOP;
2326
2327 /* Down-scroll, only redraw bottom 'part' lines of screen */
2328 if ((start = (part > 0) ? NOTESLINES - part : 0) < 0)
2329 start = 0;
2330
2331 /* Up-scroll, only redraw the top 'part' lines of screen */
2332 if ((end = (part < 0) ? -part : NOTESLINES) > NOTESLINES)
2333 end = NOTESLINES;
2334
2335 /* Print title */
2336 if ((end - start >= NOTESLINES) || (part == 0)) {
2337 ClearScreen();
2338 center_line(0, TRUE, info_title);
2339 }
2340
2341 print_message_page(info_file, infoline, (size_t) num_info_lines, (size_t) curr_info_line, (size_t) start, (size_t) end, INFO_PAGER);
2342
2343 /* print footer */
2344 draw_percent_mark(curr_info_line + (curr_info_line + NOTESLINES < num_info_lines ? NOTESLINES : num_info_lines - curr_info_line), num_info_lines);
2345 stow_cursor();
2346 }
2347
2348
2349 static void
2350 preprocess_info_message(
2351 FILE *info_fh)
2352 {
2353 int chunk = 50;
2354
2355 FreeAndNull(infoline);
2356 if (!info_fh)
2357 return;
2358
2359 rewind(info_fh);
2360 infoline = my_malloc(sizeof(t_lineinfo) * (size_t) chunk);
2361 num_info_lines = 0;
2362
2363 do {
2364 infoline[num_info_lines].offset = ftell(info_fh);
2365 infoline[num_info_lines].flags = 0;
2366 num_info_lines++;
2367 if (num_info_lines >= chunk) {
2368 chunk += 50;
2369 infoline = my_realloc(infoline, sizeof(t_lineinfo) * (size_t) chunk);
2370 }
2371 } while (tin_fgets(info_fh, FALSE) != NULL);
2372
2373 num_info_lines--;
2374 infoline = my_realloc(infoline, sizeof(t_lineinfo) * (size_t) num_info_lines);
2375 }
2376
2377
2378 /*
2379 * URL menu
2380 */
2381 static t_function
2382 url_left(
2383 void)
2384 {
2385 return GLOBAL_QUIT;
2386 }
2387
2388
2389 static t_function
2390 url_right(
2391 void)
2392 {
2393 return URL_SELECT;
2394 }
2395
2396
2397 static void
2398 show_url_page(
2399 void)
2400 {
2401 int i;
2402
2403 signal_context = cURL;
2404 currmenu = &urlmenu;
2405 mark_offset = 0;
2406
2407 if (urlmenu.curr < 0)
2408 urlmenu.curr = 0;
2409
2410 ClearScreen();
2411 set_first_screen_item();
2412 center_line(0, TRUE, _(txt_url_menu));
2413
2414 for (i = urlmenu.first; i < urlmenu.first + NOTESLINES && i < urlmenu.max; ++i)
2415 build_url_line(i);
2416
2417 show_mini_help(URL_LEVEL);
2418
2419 draw_url_arrow();
2420 }
2421
2422
2423 static t_bool
2424 url_page(
2425 void)
2426 {
2427 char key[MAXKEYLEN];
2428 t_function func;
2429 t_menu *oldmenu = NULL;
2430
2431 if (currmenu)
2432 oldmenu = currmenu;
2433 urlmenu.curr = 0;
2434 urlmenu.max = build_url_list();
2435 if (urlmenu.max == 0)
2436 return FALSE;
2437
2438 clear_note_area();
2439 show_url_page();
2440 set_xclick_off();
2441
2442 forever {
2443 switch ((func = handle_keypad(url_left, url_right, NULL, url_keys))) {
2444 case GLOBAL_QUIT:
2445 free_url_list();
2446 if (oldmenu)
2447 currmenu = oldmenu;
2448 return TRUE;
2449
2450 case DIGIT_1:
2451 case DIGIT_2:
2452 case DIGIT_3:
2453 case DIGIT_4:
2454 case DIGIT_5:
2455 case DIGIT_6:
2456 case DIGIT_7:
2457 case DIGIT_8:
2458 case DIGIT_9:
2459 if (urlmenu.max)
2460 prompt_item_num(func_to_key(func, url_keys), _(txt_url_select));
2461 break;
2462
2463 #ifndef NO_SHELL_ESCAPE
2464 case GLOBAL_SHELL_ESCAPE:
2465 do_shell_escape();
2466 break;
2467 #endif /* !NO_SHELL_ESCAPE */
2468
2469 case GLOBAL_HELP:
2470 show_help_page(URL_LEVEL, _(txt_url_menu_com));
2471 show_url_page();
2472 break;
2473
2474 case GLOBAL_FIRST_PAGE:
2475 top_of_list();
2476 break;
2477
2478 case GLOBAL_LAST_PAGE:
2479 end_of_list();
2480 break;
2481
2482 case GLOBAL_REDRAW_SCREEN:
2483 my_retouch();
2484 show_url_page();
2485 break;
2486
2487 case GLOBAL_LINE_DOWN:
2488 move_down();
2489 break;
2490
2491 case GLOBAL_LINE_UP:
2492 move_up();
2493 break;
2494
2495 case GLOBAL_PAGE_DOWN:
2496 page_down();
2497 break;
2498
2499 case GLOBAL_PAGE_UP:
2500 page_up();
2501 break;
2502
2503 case GLOBAL_SCROLL_DOWN:
2504 scroll_down();
2505 break;
2506
2507 case GLOBAL_SCROLL_UP:
2508 scroll_up();
2509 break;
2510
2511 case GLOBAL_TOGGLE_HELP_DISPLAY:
2512 toggle_mini_help(URL_LEVEL);
2513 show_url_page();
2514 break;
2515
2516 case GLOBAL_TOGGLE_INFO_LAST_LINE:
2517 tinrc.info_in_last_line = bool_not(tinrc.info_in_last_line);
2518 show_url_page();
2519 break;
2520
2521 case URL_SELECT:
2522 if (urlmenu.max) {
2523 if (process_url(urlmenu.curr))
2524 show_url_page();
2525 else
2526 draw_url_arrow();
2527 }
2528 break;
2529
2530 case GLOBAL_SEARCH_SUBJECT_FORWARD:
2531 case GLOBAL_SEARCH_SUBJECT_BACKWARD:
2532 case GLOBAL_SEARCH_REPEAT:
2533 if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
2534 info_message(_(txt_no_prev_search));
2535 else if (urlmenu.max) {
2536 int new_pos, old_pos = urlmenu.curr;
2537
2538 new_pos = generic_search((func == GLOBAL_SEARCH_SUBJECT_FORWARD), (func == GLOBAL_SEARCH_REPEAT), urlmenu.curr, urlmenu.max - 1, URL_LEVEL);
2539 if (new_pos != old_pos)
2540 move_to_item(new_pos);
2541 }
2542 break;
2543
2544 default:
2545 info_message(_(txt_bad_command), PrintFuncKey(key, GLOBAL_HELP, url_keys));
2546 break;
2547 }
2548 }
2549 }
2550
2551
2552 static void
2553 draw_url_arrow(
2554 void)
2555 {
2556 draw_arrow_mark(INDEX_TOP + urlmenu.curr - urlmenu.first);
2557 if (tinrc.info_in_last_line) {
2558 t_url *lptr;
2559
2560 lptr = find_url(urlmenu.curr);
2561 info_message("%s", lptr->url);
2562 } else if (urlmenu.curr == urlmenu.max - 1)
2563 info_message(_(txt_end_of_urls));
2564 }
2565
2566
2567 t_url *
2568 find_url(
2569 int n)
2570 {
2571 t_url *lptr;
2572
2573 lptr = url_list;
2574 while (n-- > 0 && lptr->next)
2575 lptr = lptr->next;
2576
2577 return lptr;
2578 }
2579
2580
2581 static void
2582 build_url_line(
2583 int i)
2584 {
2585 char *sptr;
2586 int len = cCOLS - 9;
2587 t_url *lptr;
2588
2589 #ifdef USE_CURSES
2590 /*
2591 * Allocate line buffer
2592 * make it the same size like in !USE_CURSES case to simplify some code
2593 */
2594 sptr = my_malloc(cCOLS + 2);
2595 #else
2596 sptr = screen[INDEX2SNUM(i)].col;
2597 #endif /* USE_CURSES */
2598
2599 lptr = find_url(i);
2600 snprintf(sptr, (size_t) cCOLS, " %s %-*.*s%s", tin_ltoa(i + 1, 4), len, len, lptr->url, cCRLF);
2601 WriteLine(INDEX2LNUM(i), sptr);
2602
2603 #ifdef USE_CURSES
2604 free(sptr);
2605 #endif /* USE_CURSES */
2606 }
2607
2608
2609 static t_bool
2610 process_url(
2611 int n)
2612 {
2613 char *url, *url_esc;
2614 size_t len;
2615 t_url *lptr;
2616
2617 lptr = find_url(n);
2618 len = strlen(lptr->url) << 1; /* double size; room for editing URL */
2619 url = my_malloc(len + 1);
2620 if (prompt_default_string("URL:", url, (int) len, lptr->url, HIST_URL)) {
2621 if (!*url) { /* Don't try and open nothing */
2622 free(url);
2623 return FALSE;
2624 }
2625 wait_message(2, _(txt_url_open), url);
2626 url_esc = escape_shell_meta(url, no_quote);
2627 len = strlen(url_esc) + strlen(tinrc.url_handler) + 2;
2628 url = my_realloc(url, len);
2629 snprintf(url, len, "%s %s", tinrc.url_handler, url_esc);
2630 invoke_cmd(url);
2631 free(url);
2632 cursoroff();
2633 return TRUE;
2634 }
2635 free(url);
2636 return FALSE;
2637 }
2638
2639
2640 static int
2641 build_url_list(
2642 void)
2643 {
2644 char *ptr;
2645 int i, count = 0;
2646 REGEX_SIZE *offsets = NULL;
2647 t_url *lptr = NULL;
2648
2649 for (i = 0; i < artlines; ++i) {
2650 if (!(artline[i].flags & (C_URL | C_NEWS | C_MAIL)))
2651 continue;
2652
2653 /*
2654 * Line contains a URL, so read it in
2655 */
2656 if (fseek(pgart.cooked, artline[i].offset, SEEK_SET) == -1) /* skip on error */
2657 continue;
2658 if ((ptr = tin_fgets(pgart.cooked, FALSE)) == NULL)
2659 continue;
2660
2661 /*
2662 * Step through, finding URL's
2663 */
2664 forever {
2665 /* any matches left? */
2666 if (match_regex_ex(ptr, (int) strlen(ptr), 0, 0, &url_regex) >= 0) {
2667 offsets = regex_get_ovector_pointer(&url_regex);
2668 } else if (match_regex_ex(ptr, (int) strlen(ptr), 0, 0, &mail_regex) >= 0) {
2669 offsets = regex_get_ovector_pointer(&mail_regex);
2670 } else if (match_regex_ex(ptr, (int) strlen(ptr), 0, 0, &news_regex) >= 0) {
2671 offsets = regex_get_ovector_pointer(&news_regex);
2672 } else {
2673 break;
2674 }
2675
2676 *(ptr + offsets[1]) = '\0';
2677
2678 if (!lptr)
2679 lptr = url_list = my_malloc(sizeof(t_url));
2680 else {
2681 lptr->next = my_malloc(sizeof(t_url));
2682 lptr = lptr->next;
2683 }
2684 lptr->url = my_strdup(ptr + offsets[0]);
2685 lptr->next = NULL;
2686 ++count;
2687
2688 ptr += offsets[1] + 1;
2689 }
2690 }
2691 return count;
2692 }
2693
2694
2695 static void
2696 free_url_list(
2697 void)
2698 {
2699 t_url *p, *q;
2700
2701 for (p = url_list; p != NULL; p = q) {
2702 q = p->next;
2703 free(p->url);
2704 free(p);
2705 }
2706 url_list = NULL;
2707 }
2708
2709
2710 static void
2711 draw_percent_mark(
2712 long cur_num,
2713 long max_num)
2714 {
2715 char buf[32]; /* FIXME: may get truncated with long localized _(txt_more) ... */
2716 int len;
2717
2718 if (NOTESLINES <= 0)
2719 return;
2720
2721 if (cur_num <= 0 && max_num <= 0)
2722 return;
2723
2724 clear_message();
2725 snprintf(buf, sizeof(buf), "%s(%d%%) [%ld/%ld]", _(txt_more), (int) (cur_num * 100 / max_num), cur_num, max_num);
2726 len = strwidth(buf);
2727 MoveCursor(cLINES, cCOLS - len - (1 + BLANK_PAGE_COLS));
2728 #ifdef HAVE_COLOR
2729 fcol(tinrc.col_normal);
2730 #endif /* HAVE_COLOR */
2731 StartInverse();
2732 my_fputs(buf, stdout);
2733 EndInverse();
2734 my_flush();
2735 }