* New alpha version 2.24.1
[alpine.git] / alpine / mailview.c
blobdd4c6a38746e3ba40cd6e4a6dd70afa72728279d
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: mailview.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2006-2008 University of Washington
8 * Copyright 2013-2021 Eduardo Chappa
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
21 mailview.c
23 Implements the mailview screen
24 Also includes scrolltool used to display help text
26 ====*/
28 #include "headers.h"
29 #include "mailcmd.h"
30 #include "mailview.h"
31 #include "mailindx.h"
32 #include "mailpart.h"
33 #include "adrbkcmd.h"
34 #include "keymenu.h"
35 #include "status.h"
36 #include "radio.h"
37 #include "help.h"
38 #include "imap.h"
39 #include "reply.h"
40 #include "folder.h"
41 #include "alpine.h"
42 #include "titlebar.h"
43 #include "signal.h"
44 #include "send.h"
45 #include "dispfilt.h"
46 #include "busy.h"
47 #include "smime.h"
48 #include "roleconf.h"
49 #include "../pith/conf.h"
50 #include "../pith/filter.h"
51 #include "../pith/msgno.h"
52 #include "../pith/escapes.h"
53 #include "../pith/flag.h"
54 #include "../pith/mimedesc.h"
55 #include "../pith/url.h"
56 #include "../pith/bldaddr.h"
57 #include "../pith/mailcmd.h"
58 #include "../pith/newmail.h"
59 #include "../pith/pipe.h"
60 #include "../pith/thread.h"
61 #include "../pith/util.h"
62 #include "../pith/detoken.h"
63 #include "../pith/editorial.h"
64 #include "../pith/maillist.h"
65 #include "../pith/hist.h"
66 #include "../pith/busy.h"
67 #include "../pith/list.h"
68 #include "../pith/detach.h"
71 /*----------------------------------------------------------------------
72 Saved state for scrolling text
73 ----*/
74 typedef struct scroll_text {
75 SCROLL_S *parms; /* Original text (file, char *, char **) */
76 char **text_lines, /* Lines to display */
77 *fname; /* filename of line offsets in "text" */
78 FILE *findex; /* file pointer to line offsets in "text" */
79 short *line_lengths; /* Length of each line in "text_lines" */
80 long top_text_line, /* index in "text_lines" top displayed line */
81 num_lines; /* number of valid pointers in "text_lines" */
82 int lines_allocated; /* size of "text_lines" array */
83 struct {
84 int length, /* count of displayable lines (== PGSIZE) */
85 width, /* width of displayable lines */
86 start_line, /* line number to start painting on */
87 other_lines; /* # of lines not for scroll text */
88 } screen; /* screen parameters */
89 } SCRLCTRL_S;
92 typedef struct scroll_file {
93 long offset;
94 int len;
95 } SCRLFILE_S;
99 * Struct to help write lines do display as they're decoded
101 struct view_write_s {
102 char *line;
103 int index,
104 screen_line,
105 last_screen_line;
106 #ifdef _WINDOWS
107 long lines;
108 #endif
109 HANDLE_S **handles;
110 STORE_S *store;
111 } *g_view_write;
113 #define LINEBUFSIZ (4096)
115 #define MAX_FUDGE (1024*1024)
118 * Definitions to help scrolltool
120 #define SCROLL_LINES_ABOVE(X) HEADER_ROWS(X)
121 #define SCROLL_LINES_BELOW(X) FOOTER_ROWS(X)
122 #define SCROLL_LINES(X) MAX(((X)->ttyo->screen_rows \
123 - SCROLL_LINES_ABOVE(X) - SCROLL_LINES_BELOW(X)), 0)
124 #define scroll_text_lines() (scroll_state(SS_CUR)->num_lines)
128 * Definitions for various scroll state manager's functions
130 #define SS_NEW 1
131 #define SS_CUR 2
132 #define SS_FREE 3
136 * Handle hints.
138 #define HANDLE_INIT_MSG \
139 _("Selectable items in text -- Use Up/Down Arrows to choose, Return to view")
140 #define HANDLE_ABOVE_ERR \
141 _("No selected item displayed -- Use PrevPage to bring choice into view")
142 #define HANDLE_BELOW_ERR \
143 _("No selected item displayed -- Use NextPage to bring choice into view")
146 #define PGSIZE(X) (ps_global->ttyo->screen_rows - (X)->screen.other_lines)
148 #define TYPICAL_BIG_MESSAGE_LINES 200
152 * Internal prototypes
154 void view_writec_killbuf(void);
155 int view_end_scroll(SCROLL_S *);
156 long format_size_guess(BODY *);
157 int scroll_handle_prompt(HANDLE_S *, int);
158 int scroll_handle_launch(HANDLE_S *, int);
159 int scroll_handle_obscured(HANDLE_S *);
160 HANDLE_S *scroll_handle_in_frame(long);
161 long scroll_handle_reframe(int, int);
162 int handle_on_line(long, int);
163 int handle_on_page(HANDLE_S *, long, long);
164 int scroll_handle_selectable(HANDLE_S *);
165 HANDLE_S *scroll_handle_next_sel(HANDLE_S *);
166 HANDLE_S *scroll_handle_prev_sel(HANDLE_S *);
167 HANDLE_S *scroll_handle_next(HANDLE_S *);
168 HANDLE_S *scroll_handle_prev(HANDLE_S *);
169 HANDLE_S *scroll_handle_boundary(HANDLE_S *, HANDLE_S *(*)(HANDLE_S *));
170 int scroll_handle_column(int, int);
171 int scroll_handle_index(int, int);
172 void scroll_handle_set_loc(POSLIST_S **, int, int);
173 int dot_on_handle(long, int);
174 int url_launch(HANDLE_S *);
175 int url_launch_too_long(int);
176 char *url_external_handler(HANDLE_S *, int);
177 void url_mailto_addr(ADDRESS **, char *);
178 int ical_send_reply(char *);
179 int url_local_imap(char *);
180 int url_local_nntp(char *);
181 int url_local_news(char *);
182 int url_local_file(char *);
183 static int print_to_printer(SCROLL_S *);
184 int search_text(int, long, int, char **, Pos *, int *);
185 void update_scroll_titlebar(long, int);
186 SCRLCTRL_S *scroll_state(int);
187 void set_scroll_text(SCROLL_S *, long, SCRLCTRL_S *);
188 void redraw_scroll_text(void);
189 void zero_scroll_text(void);
190 void format_scroll_text(void);
191 void ScrollFile(long);
192 long make_file_index(void);
193 char *scroll_file_line(FILE *, char *, SCRLFILE_S *, int *);
194 long scroll_scroll_text(long, HANDLE_S *, int);
195 int search_scroll_text(long, int, char *, Pos *, int *);
196 char *search_scroll_line(char *, char *, int, int);
197 int visible_linelen(int);
198 long doubleclick_handle(SCROLL_S *, HANDLE_S *, int *, int *);
199 #ifdef _WINDOWS
200 int format_message_popup(SCROLL_S *, int);
201 int simple_text_popup(SCROLL_S *, int);
202 int mswin_readscrollbuf(int);
203 int pcpine_do_scroll(int, long);
204 char *pcpine_help_scroll(char *);
205 int pcpine_view_cursor(int, long);
206 #endif
210 /*----------------------------------------------------------------------
211 Format a buffer with the text of the current message for browser
213 Args: ps - pine state structure
215 Result: The scrolltool is called to display the message
217 Loop here viewing mail until the folder changed or a command takes
218 us to another screen. Inside the loop the message text is fetched and
219 formatted into a buffer allocated for it. These are passed to the
220 scrolltool(), that displays the message and executes commands. It
221 returns when it's time to display a different message, when we
222 change folders, when it's time for a different screen, or when
223 there are no more messages available.
224 ---*/
226 void
227 mail_view_screen(struct pine *ps)
229 char last_was_full_header = 0;
230 long last_message_viewed = -1L, raw_msgno, offset = 0L;
231 OtherMenu save_what = FirstMenu;
232 int we_cancel = 0, flags, cmd = 0;
233 int force_prefer = 0;
234 MESSAGECACHE *mc;
235 ENVELOPE *env;
236 BODY *body;
237 STORE_S *store;
238 HANDLE_S *handles = NULL;
239 SCROLL_S scrollargs;
240 SourceType src = CharStar;
242 dprint((1, "\n\n ----- MAIL VIEW -----\n"));
244 ps->prev_screen = mail_view_screen;
245 ps->force_prefer_plain = ps->force_no_prefer_plain = 0;
247 if(ps->ttyo->screen_rows - HEADER_ROWS(ps) - FOOTER_ROWS(ps) < 1){
248 q_status_message(SM_ORDER | SM_DING, 0, 3,
249 _("Screen too small to view message"));
250 ps->next_screen = mail_index_screen;
251 return;
254 /*----------------- Loop viewing messages ------------------*/
255 do {
257 ps->user_says_cancel = 0;
258 ps->some_quoting_was_suppressed = 0;
261 * Check total to make sure there's something to view. Check it
262 * inside the loop to make sure everything wasn't expunged while
263 * we were viewing. If so, make sure we don't just come back.
265 if(mn_get_total(ps->msgmap) <= 0L || !ps->mail_stream){
266 q_status_message(SM_ORDER, 0, 3, _("No messages to read!"));
267 ps->next_screen = mail_index_screen;
268 break;
271 we_cancel = busy_cue(NULL, NULL, 1);
273 if(mn_get_cur(ps->msgmap) <= 0L)
274 mn_set_cur(ps->msgmap,
275 THREADING() ? first_sorted_flagged(F_NONE,
276 ps->mail_stream,
277 0L, FSF_SKIP_CHID)
278 : 1L);
280 raw_msgno = mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap));
281 body = NULL;
282 if(raw_msgno == 0L
283 || !(env = pine_mail_fetchstructure(ps->mail_stream,raw_msgno,&body))
284 || !(raw_msgno > 0L && ps->mail_stream
285 && raw_msgno <= ps->mail_stream->nmsgs
286 && (mc = mail_elt(ps->mail_stream, raw_msgno)))){
287 q_status_message1(SM_ORDER, 3, 3,
288 "Error getting message %s data",
289 comatose(mn_get_cur(ps->msgmap)));
290 dprint((1, "!!!! ERROR fetching %s of msg %ld\n",
291 env ? "elt" : "env", mn_get_cur(ps->msgmap)));
293 ps->next_screen = mail_index_screen;
294 break;
296 else
297 ps->unseen_in_view = !mc->seen;
299 init_handles(&handles);
301 store = so_get(src, NULL, EDIT_ACCESS);
302 so_truncate(store, format_size_guess(body) + 2048);
304 view_writec_init(store, &handles, SCROLL_LINES_ABOVE(ps),
305 SCROLL_LINES_ABOVE(ps) +
306 ps->ttyo->screen_rows - (SCROLL_LINES_ABOVE(ps)
307 + SCROLL_LINES_BELOW(ps)));
309 flags = FM_DISPLAY;
310 if(last_message_viewed != mn_get_cur(ps->msgmap)
311 || last_was_full_header == 2 || cmd == MC_TOGGLE)
312 flags |= FM_NEW_MESS;
314 if(F_OFF(F_QUELL_FULL_HDR_RESET, ps_global)
315 && last_message_viewed != -1L
316 && last_message_viewed != mn_get_cur(ps->msgmap))
317 ps->full_header = 0;
319 if(offset) /* no pre-paint during resize */
320 view_writec_killbuf();
322 #ifdef SMIME
323 /* Attempt to handle S/MIME bodies */
324 if(ps->smime)
325 ps->smime->need_passphrase = 0;
327 if(F_OFF(F_DONT_DO_SMIME, ps_global) && fiddle_smime_message(body, raw_msgno))
328 flags |= FM_NEW_MESS; /* body was changed, force a reload */
329 #endif
331 #ifdef _WINDOWS
332 mswin_noscrollupdate(1);
333 #endif
334 ps->cur_uid_stream = ps->mail_stream;
335 ps->cur_uid = mail_uid(ps->mail_stream, raw_msgno);
336 (void) format_message(raw_msgno, env, body, &handles, flags | force_prefer,
337 view_writec);
338 #ifdef _WINDOWS
339 mswin_noscrollupdate(0);
340 #endif
342 view_writec_destroy();
344 last_message_viewed = mn_get_cur(ps->msgmap);
345 last_was_full_header = ps->full_header;
347 ps->next_screen = SCREEN_FUN_NULL;
349 memset(&scrollargs, 0, sizeof(SCROLL_S));
350 scrollargs.text.text = so_text(store);
351 scrollargs.text.src = src;
352 scrollargs.text.desc = "message";
355 * make first selectable handle the default
357 if(handles){
358 HANDLE_S *hp;
360 hp = handles;
361 while(!scroll_handle_selectable(hp) && hp != NULL)
362 hp = hp->next;
364 if((scrollargs.text.handles = hp) != NULL)
365 scrollargs.body_valid = (hp == handles);
366 else
367 free_handles(&handles);
369 else
370 scrollargs.body_valid = 1;
372 if(offset){ /* resize? preserve paging! */
373 scrollargs.start.on = Offset;
374 scrollargs.start.loc.offset = offset;
375 scrollargs.body_valid = 0;
376 offset = 0L;
379 scrollargs.use_indexline_color = 1;
381 /* TRANSLATORS: a screen title */
382 scrollargs.bar.title = _("MESSAGE TEXT");
383 scrollargs.end_scroll = view_end_scroll;
384 scrollargs.resize_exit = 1;
385 scrollargs.help.text = h_mail_view;
386 scrollargs.help.title = _("HELP FOR MESSAGE TEXT VIEW");
387 scrollargs.keys.menu = &view_keymenu;
388 scrollargs.keys.what = save_what;
389 setbitmap(scrollargs.keys.bitmap);
390 if(F_OFF(F_ENABLE_PIPE, ps_global))
391 clrbitn(VIEW_PIPE_KEY, scrollargs.keys.bitmap);
394 * turn off attachment viewing for raw msg txt, atts
395 * haven't been set up at this point
397 if(ps_global->full_header == 2
398 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
399 clrbitn(VIEW_ATT_KEY, scrollargs.keys.bitmap);
401 if(F_OFF(F_ENABLE_BOUNCE, ps_global))
402 clrbitn(BOUNCE_KEY, scrollargs.keys.bitmap);
404 if(F_OFF(F_ENABLE_FLAG, ps_global))
405 clrbitn(FLAG_KEY, scrollargs.keys.bitmap);
407 if(F_OFF(F_ENABLE_AGG_OPS, ps_global))
408 clrbitn(VIEW_SELECT_KEY, scrollargs.keys.bitmap);
410 if(F_OFF(F_ENABLE_FULL_HDR, ps_global))
411 clrbitn(VIEW_FULL_HEADERS_KEY, scrollargs.keys.bitmap);
413 #ifdef SMIME
414 if(!(ps->smime && ps->smime->need_passphrase))
415 clrbitn(DECRYPT_KEY, scrollargs.keys.bitmap);
417 if(F_ON(F_DONT_DO_SMIME, ps_global)){
418 clrbitn(DECRYPT_KEY, scrollargs.keys.bitmap);
419 clrbitn(SECURITY_KEY, scrollargs.keys.bitmap);
421 #endif
423 if(!handles){
425 * NOTE: the comment below only really makes sense if we
426 * decide to replace the "Attachment Index" with
427 * a model that lets you highlight the attachments
428 * in the header. In a way its consistent, but
429 * would mean more keymenu monkeybusiness since not
430 * all things would be available all the time. No wait.
431 * Then what would "Save" mean; the attachment, url or
432 * message you're currently viewing? Would Save
433 * of a message then only be possible from the message
434 * index? Clumsy. what about arrow-navigation. isn't
435 * that now inconsistent? Maybe next/prev url shouldn't
436 * be bound to the arrow/^N/^P navigation?
438 clrbitn(VIEW_VIEW_HANDLE, scrollargs.keys.bitmap);
439 clrbitn(VIEW_PREV_HANDLE, scrollargs.keys.bitmap);
440 clrbitn(VIEW_NEXT_HANDLE, scrollargs.keys.bitmap);
443 #ifdef _WINDOWS
444 scrollargs.mouse.popup = format_message_popup;
445 #endif
447 if(((cmd = scrolltool(&scrollargs)) == MC_RESIZE
448 || (cmd == MC_FULLHDR && ps_global->full_header == 1))
449 && scrollargs.start.on == Offset)
450 offset = scrollargs.start.loc.offset;
452 if(cmd == MC_TOGGLE && ps->force_prefer_plain == 0 && ps->force_no_prefer_plain == 0){
453 if(F_ON(F_PREFER_PLAIN_TEXT, ps_global))
454 ps->force_no_prefer_plain = 1;
455 else
456 ps->force_prefer_plain = 1;
458 else{
459 ps->force_prefer_plain = ps->force_no_prefer_plain = 0;
463 * We could use the values directly but this is the way it
464 * already worked with the flags, so leave it alone.
466 if(ps->force_prefer_plain == 0 && ps->force_no_prefer_plain == 0)
467 force_prefer = 0;
468 else if(ps->force_prefer_plain)
469 force_prefer = FM_FORCEPREFPLN;
470 else if(ps->force_no_prefer_plain)
471 force_prefer = FM_FORCENOPREFPLN;
473 save_what = scrollargs.keys.what;
474 ps_global->unseen_in_view = 0;
475 so_give(&store); /* free resources associated with store */
476 free_handles(&handles);
477 #ifdef _WINDOWS
478 mswin_destroyicons();
479 #endif
481 while(ps->next_screen == SCREEN_FUN_NULL);
483 if(we_cancel)
484 cancel_busy_cue(-1);
486 ps->force_prefer_plain = ps->force_no_prefer_plain = 0;
488 ps->cur_uid_stream = NULL;
489 ps->cur_uid = 0;
492 * Unless we're going into attachment screen,
493 * start over with full_header.
495 if(F_OFF(F_QUELL_FULL_HDR_RESET, ps_global)
496 && ps->next_screen != attachment_screen)
497 ps->full_header = 0;
503 * view_writec_init - function to create and init struct that manages
504 * writing to the display what we can as soon
505 * as we can.
507 void
508 view_writec_init(STORE_S *store, HANDLE_S **handlesp, int first_line, int last_line)
510 char tmp[MAILTMPLEN];
512 g_view_write = (struct view_write_s *)fs_get(sizeof(struct view_write_s));
513 memset(g_view_write, 0, sizeof(struct view_write_s));
514 g_view_write->store = store;
515 g_view_write->handles = handlesp;
516 g_view_write->screen_line = first_line;
517 g_view_write->last_screen_line = last_line;
519 if(!dfilter_trigger(NULL, tmp, sizeof(tmp))){
520 g_view_write->line = (char *) fs_get(LINEBUFSIZ*sizeof(char));
521 #ifdef _WINDOWS
522 mswin_beginupdate();
523 scroll_setrange(0L, 0L);
524 #endif
526 if(ps_global->VAR_DISPLAY_FILTERS)
527 ClearLines(first_line, last_line - 1);
533 void
534 view_writec_destroy(void)
536 if(g_view_write){
537 if(g_view_write->line && g_view_write->index)
538 view_writec('\n'); /* flush pending output! */
540 while(g_view_write->screen_line < g_view_write->last_screen_line)
541 ClearLine(g_view_write->screen_line++);
543 view_writec_killbuf();
545 fs_give((void **) &g_view_write);
548 #ifdef _WINDOWS
549 mswin_endupdate();
550 #endif
555 void
556 view_writec_killbuf(void)
558 if(g_view_write->line)
559 fs_give((void **) &g_view_write->line);
565 * view_screen_pc - write chars into the final buffer
568 view_writec(int c)
570 static int in_color = 0;
572 if(g_view_write->line){
574 * This only works if the 2nd and 3rd parts of the || don't happen.
575 * The only way it breaks is if we get a really, really long line
576 * because there are oodles of tags in it. In that case we will
577 * wrap incorrectly or split a tag across lines (losing color perhaps)
578 * but we won't crash.
580 if(c == '\n' ||
581 (!in_color &&
582 (char)c != TAG_EMBED &&
583 g_view_write->index >= (LINEBUFSIZ - 20 * (RGBLEN+2))) ||
584 (g_view_write->index >= LINEBUFSIZ - 1)){
585 int rv;
587 in_color = 0;
588 suspend_busy_cue();
589 ClearLine(g_view_write->screen_line);
590 if(c != '\n')
591 g_view_write->line[g_view_write->index++] = (char) c;
593 PutLine0n8b(g_view_write->screen_line++, 0,
594 g_view_write->line, g_view_write->index,
595 g_view_write->handles ? *g_view_write->handles : NULL);
597 resume_busy_cue(0);
598 rv = so_nputs(g_view_write->store,
599 g_view_write->line,
600 g_view_write->index);
601 g_view_write->index = 0;
603 if(g_view_write->screen_line >= g_view_write->last_screen_line){
604 fs_give((void **) &g_view_write->line);
605 fflush(stdout);
608 if(!rv)
609 return(0);
610 else if(c != '\n')
611 return(1);
613 else{
615 * Don't split embedded things over multiple lines. Colors are
616 * the longest tags so use their length.
618 if((char)c == TAG_EMBED)
619 in_color = RGBLEN+1;
620 else if(in_color)
621 in_color--;
623 g_view_write->line[g_view_write->index++] = (char) c;
624 return(1);
627 #ifdef _WINDOWS
628 else if(c == '\n' && g_view_write->lines++ > SCROLL_LINES(ps_global))
629 scroll_setrange(SCROLL_LINES(ps_global),
630 g_view_write->lines + SCROLL_LINES(ps_global));
631 #endif
633 return(so_writec(c, g_view_write->store));
637 view_end_scroll(SCROLL_S *sparms)
639 int done = 0, result, force;
641 if(F_ON(F_ENABLE_SPACE_AS_TAB, ps_global)){
643 if(F_ON(F_ENABLE_TAB_DELETES, ps_global)){
644 long save_msgno;
646 /* Let the TAB advance cur msgno for us */
647 save_msgno = mn_get_cur(ps_global->msgmap);
648 (void) cmd_delete(ps_global, ps_global->msgmap, MCMD_NONE, NULL);
649 mn_set_cur(ps_global->msgmap, save_msgno);
652 /* act like the user hit a TAB */
653 result = process_cmd(ps_global, ps_global->mail_stream,
654 ps_global->msgmap, MC_TAB, View, &force);
656 if(result == 1)
657 done = 1;
660 return(done);
665 * format_size_guess -- Run down the given body summing the text/plain
666 * pieces we're likely to display. It need only
667 * be a guess since this is intended for preallocating
668 * our display buffer...
670 long
671 format_size_guess(struct mail_bodystruct *body)
673 long size = 0L;
674 long extra = 0L;
675 char *free_me = NULL;
677 if(body){
678 if(body->type == TYPEMULTIPART){
679 PART *part;
681 for(part = body->nested.part; part; part = part->next)
682 size += format_size_guess(&part->body);
684 else if(body->type == TYPEMESSAGE
685 && body->subtype && !strucmp(body->subtype, "rfc822"))
686 size = format_size_guess(body->nested.msg->body);
687 else if((!body->type || body->type == TYPETEXT)
688 && (!body->subtype || !strucmp(body->subtype, "plain"))
689 && ((body->disposition.type
690 && !strucmp(body->disposition.type, "inline"))
691 || !(free_me = parameter_val(body->parameter, "name")))){
693 * Handles and colored quotes cause memory overhead. Figure about
694 * 100 bytes per level of quote per line and about 100 bytes per
695 * handle. Make a guess of 1 handle or colored quote per
696 * 10 lines of message. If we guess too low, we'll do a resize in
697 * so_cs_writec. Most of the overhead comes from the colors, so
698 * if we can see we don't do colors or don't have these features
699 * turned on, skip it.
701 if(pico_usingcolor() &&
702 ((ps_global->VAR_QUOTE1_FORE_COLOR &&
703 ps_global->VAR_QUOTE1_BACK_COLOR) ||
704 F_ON(F_VIEW_SEL_URL, ps_global) ||
705 F_ON(F_VIEW_SEL_URL_HOST, ps_global) ||
706 F_ON(F_SCAN_ADDR, ps_global)))
707 extra = MIN(100/10 * body->size.lines, MAX_FUDGE);
709 size = body->size.bytes + extra;
712 if(free_me)
713 fs_give((void **) &free_me);
716 return(size);
721 scroll_handle_prompt(HANDLE_S *handle, int force)
723 char prompt[256], tmp[MAILTMPLEN];
724 int rc, flags, local_h, external, images;
725 ACTION_S *role = NULL;
726 static ESCKEY_S launch_opts[] = {
727 /* TRANSLATORS: command names, editURL means user gets to edit a URL if they
728 want, editApp is edit application where they edit the application used to
729 view a URL */
730 {'y', 'y', "Y", N_("Yes")},
731 {'n', 'n', "N", N_("No")},
732 {0, 'x', "X", ""},
733 {0, 'i', "I", ""},
734 {-2, 'r', "R", NULL},
735 {-2, 0, NULL, NULL},
736 {0, 'u', "U", N_("editURL")},
737 {0, 'a', "A", N_("editApp")},
738 {-1, 0, NULL, NULL}};
740 if(handle->type == URL){
741 launch_opts[6].ch = 'u';
743 if((!(local_h = !struncmp(handle->h.url.path, "x-alpine-", 9))
744 || !(local_h = !struncmp(handle->h.url.path, "x-pine-help", 11)))
745 && (handle->h.url.tool
746 || ((local_h = url_local_handler(handle->h.url.path) != NULL)
747 && (handle->h.url.tool = url_external_handler(handle,1)))
748 || (!local_h
749 && (handle->h.url.tool = url_external_handler(handle,0))))){
750 #ifdef _WINDOWS
751 /* if NOT special DDE hack */
752 if(handle->h.url.tool[0] != '*')
753 #endif
754 if(ps_global->vars[V_BROWSER].is_fixed)
755 launch_opts[7].ch = -1;
756 else
757 launch_opts[7].ch = 'a';
759 else{
760 launch_opts[7].ch = -1;
761 if(!local_h){
762 if(ps_global->vars[V_BROWSER].is_fixed){
763 q_status_message(SM_ORDER, 3, 4,
764 _("URL-Viewer is disabled by sys-admin"));
765 return(0);
767 else{
768 /* TRANSLATORS: a question */
769 if(want_to(_("No URL-Viewer application defined. Define now"),
770 'y', 0, NO_HELP, WT_SEQ_SENSITIVE) == 'y'){
771 /* Prompt for the displayer? */
772 tmp[0] = '\0';
773 while(1){
774 flags = OE_APPEND_CURRENT |
775 OE_SEQ_SENSITIVE |
776 OE_KEEP_TRAILING_SPACE;
778 rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
779 sizeof(tmp),
780 _("Web Browser: "),
781 NULL, NO_HELP, &flags);
782 if(rc == 0){
783 if((flags & OE_USER_MODIFIED) && *tmp){
784 if(can_access(tmp, EXECUTE_ACCESS) == 0){
785 int n;
786 char **l;
789 * Save it for next time...
791 for(l = ps_global->VAR_BROWSER, n = 0;
792 l && *l;
793 l++)
794 n++; /* count */
796 l = (char **) fs_get((n+2)*sizeof(char *));
797 for(n = 0;
798 ps_global->VAR_BROWSER
799 && ps_global->VAR_BROWSER[n];
800 n++)
801 l[n] = cpystr(ps_global->VAR_BROWSER[n]);
803 l[n++] = cpystr(tmp);
804 l[n] = NULL;
806 set_variable_list(V_BROWSER, l, TRUE, Main);
807 free_list_array(&l);
809 handle->h.url.tool = cpystr(tmp);
810 break;
812 else{
813 q_status_message1(SM_ORDER | SM_DING, 2, 2,
814 _("Browser not found: %s"),
815 error_description(errno));
816 continue;
819 else
820 return(0);
822 else if(rc == 1 || rc == -1){
823 return(0);
825 else if(rc == 4){
826 if(ps_global->redrawer)
827 (*ps_global->redrawer)();
831 else
832 return(0);
837 else
838 launch_opts[6].ch = -1;
840 if(handle->type == Attach
841 && handle->h.attach
842 && handle->h.attach->body
843 && handle->h.attach->body->type == TYPETEXT
844 && !strucmp(handle->h.attach->body->subtype, "HTML")){
845 images = F_OFF(F_EXTERNAL_INLINE_IMAGES, ps_global) ? 1 : 0;
846 external = 0; /* default to not using external viewer, set to 1 to make it default */
847 force = 0; /* do not open automatically */
848 launch_opts[2].ch = 'x';
849 launch_opts[2].label = external > 0 ? N_("No eXternal") : N_("External");
850 launch_opts[3].ch = external > 0 ? 'i' : -2;
851 launch_opts[3].label = images ? N_("Inline imgs") : N_("All images");
853 else {
854 launch_opts[2].ch = -2; /* skip */
855 launch_opts[3].ch = -2; /* skip */
856 external = images = -1;
859 if(force
860 || (handle->type == URL
861 && (!struncmp(handle->h.url.path, "x-alpine-", 9)
862 || !struncmp(handle->h.url.path, "x-pine-help", 11))))
863 return(1);
865 while(1){
866 int sc = ps_global->ttyo->screen_cols;
869 * Customize the prompt for mailto, all the other schemes make
870 * sense if you just say View selected URL ...
872 if(handle->type == URL &&
873 !struncmp(handle->h.url.path, "mailto:", 7)){
874 char tmp[128];
875 snprintf(prompt, sizeof(prompt), "Compose mail to \"%.*s%s\"",
876 (int) MIN(MAX(0,sc - (role ? 44 : 25)), sizeof(prompt)-50), handle->h.url.path+7,
877 (strlen(handle->h.url.path+7) > MAX(0,sc-(role ? 44 :25))) ? "..." : "");
879 if(role != NULL){
880 snprintf(tmp, sizeof(tmp), " (using role \"%.*s%s\")",
881 (int) MIN(MAX(0,sc - strlen(prompt) - 19), sizeof(prompt)-strlen(tmp)-50), role->nick,
882 (strlen(role->nick) > MAX(0,sc-strlen(prompt) - 19)) ? "..." : "");
884 strncat(prompt, tmp, sizeof(prompt) - strlen(prompt) - 1);
885 prompt[sizeof(prompt) - 1] = '\0';
888 strncat(prompt, " ? ", sizeof(prompt) - strlen(prompt) - 1);
889 prompt[sizeof(prompt) - 1] = '\0';
891 launch_opts[4].ch = 'r';
892 launch_opts[4].label = N_("setRole");
894 else
895 snprintf(prompt, sizeof(prompt), "View selected %s %s%s%s%.*s%s ? ",
896 (handle->type == URL) ? "URL" : "Attachment",
897 external > 0 ? "using external viewer " : "",
898 external > 0 ? (images > 0 ? "including all images" : "including inline images only") : "",
899 (handle->type == URL) ? "\"" : "",
900 (int) MIN(MAX(0,sc-27-(external ? (images ? 41 : 50) : 0)), sizeof(prompt)-50),
901 (handle->type == URL) ? handle->h.url.path : "",
902 (handle->type == URL)
903 ? ((strlen(handle->h.url.path) > MAX(0,sc-27 - (external ? (images > 0 ? 41 : 50) : 0)))
904 ? "...\"" : "\"") : "");
906 prompt[sizeof(prompt)-1] = '\0';
908 switch(radio_buttons(prompt, -FOOTER_ROWS(ps_global),
909 launch_opts, 'y', 'n', NO_HELP, RB_SEQ_SENSITIVE)){
910 case 'y' :
911 return(external > 0 ? (images > 0 ? -2 : -1) : 1);
913 case 'x' :
914 external = 1 - external;
915 images = F_OFF(F_EXTERNAL_INLINE_IMAGES, ps_global) ? 1 : 0;
916 launch_opts[2].label = external > 0 ? N_("No eXternal") : N_("External");
917 launch_opts[3].ch = external > 0 ? 'i' : -2;
918 launch_opts[3].label = images ? N_("Inline imgs") : N_("All images");
919 break;
921 case 'i' :
922 images = 1 - images;
923 launch_opts[3].label = images ? N_("Inline imgs") : N_("All images");
924 break;
926 case 'r' :
927 { void (*prev_screen)(struct pine *) = ps_global->prev_screen,
928 (*redraw)(void) = ps_global->redrawer;
930 ps_global->redrawer = NULL;
931 ps_global->next_screen = SCREEN_FUN_NULL;
932 role = ps_global->reply.role_chosen;
933 if(role_select_screen(ps_global, &role, MC_COMPOSE) < 0){
934 cmd_cancelled("Composition");
935 ps_global->next_screen = prev_screen;
936 ps_global->redrawer = redraw;
937 return -1;
940 ps_global->next_screen = prev_screen;
941 ps_global->redrawer = redraw;
942 if(role)
943 role = combine_inherited_role(role);
944 ps_global->reply.role_chosen = role;
945 if(ps_global->redrawer) (ps_global->redrawer)();
946 break;
950 case 'u' :
951 strncpy(tmp, handle->h.url.path, sizeof(tmp)-1);
952 tmp[sizeof(tmp)-1] = '\0';
953 while(1){
954 flags = OE_APPEND_CURRENT |
955 OE_SEQ_SENSITIVE |
956 OE_KEEP_TRAILING_SPACE;
958 rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
959 sizeof(tmp), _("Edit URL: "),
960 NULL, NO_HELP, &flags);
961 if(rc == 0){
962 if(flags & OE_USER_MODIFIED){
963 if(handle->h.url.path)
964 fs_give((void **) &handle->h.url.path);
966 handle->h.url.path = cpystr(tmp);
969 break;
971 else if(rc == 1 || rc == -1){
972 return(0);
974 else if(rc == 4){
975 if(ps_global->redrawer)
976 (*ps_global->redrawer)();
980 continue;
982 case 'a' :
983 if(handle->h.url.tool){
984 strncpy(tmp, handle->h.url.tool, sizeof(tmp)-1);
985 tmp[sizeof(tmp)-1] = '\0';
987 else
988 tmp[0] = '\0';
990 while(1){
991 flags = OE_APPEND_CURRENT |
992 OE_SEQ_SENSITIVE |
993 OE_KEEP_TRAILING_SPACE |
994 OE_DISALLOW_HELP;
996 rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
997 sizeof(tmp), _("Viewer Command: "),
998 NULL, NO_HELP, &flags);
999 if(rc == 0){
1000 if(flags & OE_USER_MODIFIED){
1001 if(handle->h.url.tool)
1002 fs_give((void **) &handle->h.url.tool);
1004 handle->h.url.tool = cpystr(tmp);
1007 break;
1009 else if(rc == 1 || rc == -1){
1010 return(0);
1012 else if(rc == 4){
1013 if(ps_global->redrawer)
1014 (*ps_global->redrawer)();
1018 continue;
1020 case 'n' :
1021 default :
1022 return(0);
1030 scroll_handle_launch(HANDLE_S *handle, int force)
1032 int flags;
1033 switch(handle->type){
1034 case URL :
1035 if(handle->h.url.path){
1036 if(scroll_handle_prompt(handle, force)){
1037 if(url_launch(handle)
1038 || ps_global->next_screen != SCREEN_FUN_NULL)
1039 return(1); /* done with this screen */
1041 else
1042 return(-1);
1045 break;
1047 case Attach :
1048 flags = DA_FROM_VIEW | DA_DIDPROMPT;
1049 switch(scroll_handle_prompt(handle, force)){
1050 case 1 : break;
1051 case -2 : flags |= DA_ALLIMAGES;
1052 case -1 : flags |= DA_EXTERNAL; break;
1053 default : return -1;
1055 display_attachment(mn_m2raw(ps_global->msgmap,
1056 mn_get_cur(ps_global->msgmap)), handle->h.attach, flags);
1057 break;
1059 case Folder :
1060 break;
1062 case iCal:
1063 display_vevent_summary(mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap)),
1064 handle->h.ical.attach,
1065 DA_FROM_VIEW | DA_DIDPROMPT, handle->h.ical.depth);
1066 break;
1068 case Function :
1069 (*handle->h.func.f)(handle->h.func.args.stream,
1070 handle->h.func.args.msgmap,
1071 handle->h.func.args.msgno);
1072 break;
1075 default :
1076 alpine_panic("Unexpected HANDLE type");
1079 return(0);
1084 scroll_handle_obscured(HANDLE_S *handle)
1086 SCRLCTRL_S *st = scroll_state(SS_CUR);
1088 return(handle_on_page(handle, st->top_text_line,
1089 st->top_text_line + st->screen.length));
1095 * scroll_handle_in_frame -- return handle pointer to visible handle.
1097 HANDLE_S *
1098 scroll_handle_in_frame(long int top_line)
1100 SCRLCTRL_S *st = scroll_state(SS_CUR);
1101 HANDLE_S *hp;
1103 switch(handle_on_page(hp = st->parms->text.handles, top_line,
1104 top_line + st->screen.length)){
1105 case -1 : /* handle above page */
1106 /* Find first handle from top of page */
1107 for(hp = st->parms->text.handles->next; hp; hp = hp->next)
1108 if(scroll_handle_selectable(hp))
1109 switch(handle_on_page(hp, top_line, top_line + st->screen.length)){
1110 case 0 : return(hp);
1111 case 1 : return(NULL);
1112 case -1 : default : break;
1115 break;
1117 case 1 : /* handle below page */
1118 /* Find first handle from top of page */
1119 for(hp = st->parms->text.handles->prev; hp; hp = hp->prev)
1120 if(scroll_handle_selectable(hp))
1121 switch(handle_on_page(hp, top_line, top_line + st->screen.length)){
1122 case 0 : return(hp);
1123 case -1 : return(NULL);
1124 case 1 : default : break;
1127 break;
1129 case 0 :
1130 default :
1131 break;
1134 return(hp);
1138 * scroll_handle_reframe -- adjust display params to display given handle
1140 long
1141 scroll_handle_reframe(int key, int center)
1143 long l, offset, dlines, start_line;
1144 SCRLCTRL_S *st = scroll_state(SS_CUR);
1146 dlines = PGSIZE(st);
1147 offset = (st->parms->text.src == FileStar) ? st->top_text_line : 0;
1148 start_line = st->top_text_line;
1150 if(key < 0)
1151 key = st->parms->text.handles->key;
1153 /* Searc down from the top line */
1154 for(l = start_line; l < st->num_lines; l++) {
1155 if(st->parms->text.src == FileStar && l > offset + dlines)
1156 ScrollFile(offset += dlines);
1158 if(handle_on_line(l - offset, key))
1159 break;
1162 if(l < st->num_lines){
1163 if(l >= dlines + start_line) /* bingo! */
1164 start_line = l - ((center ? (dlines / 2) : dlines) - 1);
1166 else{
1167 if(st->parms->text.src == FileStar) /* wrap offset */
1168 ScrollFile(offset = 0);
1170 for(l = 0; l < start_line; l++) {
1171 if(st->parms->text.src == FileStar && l > offset + dlines)
1172 ScrollFile(offset += dlines);
1174 if(handle_on_line(l - offset, key))
1175 break;
1178 if(l == start_line)
1179 alpine_panic("Internal Error: no handle found");
1180 else
1181 start_line = l;
1184 return(start_line);
1189 handle_on_line(long int line, int goal)
1191 int i, n, key;
1192 SCRLCTRL_S *st = scroll_state(SS_CUR);
1194 for(i = 0; i < st->line_lengths[line]; i++)
1195 if(st->text_lines[line][i] == TAG_EMBED
1196 && st->text_lines[line][++i] == TAG_HANDLE){
1197 for(key = 0, n = st->text_lines[line][++i]; n; n--)
1198 key = (key * 10) + (st->text_lines[line][++i] - '0');
1200 if(key == goal)
1201 return(1);
1204 return(0);
1209 handle_on_page(HANDLE_S *handle, long int first_line, long int last_line)
1211 POSLIST_S *l;
1212 int rv = 0;
1214 if(handle && (l = handle->loc)){
1215 for( ; l; l = l->next)
1216 if(l->where.row < first_line){
1217 if(!rv)
1218 rv = -1;
1220 else if(l->where.row >= last_line){
1221 if(!rv)
1222 rv = 1;
1224 else
1225 return(0); /* found! */
1228 return(rv);
1233 scroll_handle_selectable(HANDLE_S *handle)
1235 return(handle && (handle->type != URL
1236 || (handle->h.url.path && *handle->h.url.path)));
1240 HANDLE_S *
1241 scroll_handle_next_sel(HANDLE_S *handles)
1243 while(handles && !scroll_handle_selectable(handles = handles->next))
1246 return(handles);
1250 HANDLE_S *
1251 scroll_handle_prev_sel(HANDLE_S *handles)
1253 while(handles && !scroll_handle_selectable(handles = handles->prev))
1256 return(handles);
1260 HANDLE_S *
1261 scroll_handle_next(HANDLE_S *handles)
1263 HANDLE_S *next = NULL;
1265 if(scroll_handle_obscured(handles) <= 0){
1266 next = handles;
1267 while((next = scroll_handle_next_sel(next))
1268 && scroll_handle_obscured(next))
1272 return(next);
1277 HANDLE_S *
1278 scroll_handle_prev(HANDLE_S *handles)
1280 HANDLE_S *prev = NULL;
1282 if(scroll_handle_obscured(handles) >= 0){
1283 prev = handles;
1284 while((prev = scroll_handle_prev_sel(prev))
1285 && scroll_handle_obscured(prev))
1289 return(prev);
1293 HANDLE_S *
1294 scroll_handle_boundary(HANDLE_S *handle, HANDLE_S *(*f) (HANDLE_S *))
1296 HANDLE_S *hp, *whp = NULL;
1298 /* Multi-line handle? Punt! */
1299 if(handle && (!(hp = handle)->loc->next))
1300 while((hp = (*f)(hp))
1301 && hp->loc->where.row == handle->loc->where.row)
1302 whp = hp;
1304 return(whp);
1309 scroll_handle_column(int line, int offset)
1311 SCRLCTRL_S *st = scroll_state(SS_CUR);
1312 int i, n, col;
1313 int key, limit;
1315 limit = (offset > -1) ? offset : st->line_lengths[line];
1317 for(i = 0, col = 0; i < limit;){
1318 if(st && st->text_lines && st->text_lines[line])
1319 switch(st->text_lines[line][i]){
1320 case TAG_EMBED:
1321 i++;
1322 switch((i < limit) ? st->text_lines[line][i] : 0){
1323 case TAG_HANDLE:
1324 for(key = 0, n = st->text_lines[line][++i]; n > 0 && i < limit-1; n--)
1325 key = (key * 10) + (st->text_lines[line][++i] - '0');
1327 i++;
1328 break;
1330 case TAG_FGCOLOR :
1331 case TAG_BGCOLOR :
1332 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
1333 break;
1335 case TAG_INVON:
1336 case TAG_INVOFF:
1337 case TAG_BOLDON:
1338 case TAG_BOLDOFF:
1339 case TAG_ULINEON:
1340 case TAG_ULINEOFF:
1341 i++;
1342 break;
1344 default: /* literal embed char */
1345 break;
1348 break;
1350 case TAB:
1351 i++;
1352 while(((++col) & 0x07) != 0) /* add tab's spaces */
1355 break;
1357 default:
1358 col += width_at_this_position((unsigned char*) &st->text_lines[line][i],
1359 st->line_lengths[line] - i);
1360 i++;
1361 break;
1365 return(col);
1370 scroll_handle_index(int row, int column)
1372 SCRLCTRL_S *st = scroll_state(SS_CUR);
1373 int index = 0;
1375 for(index = 0; column > 0;)
1376 switch(st->text_lines[row][index++]){
1377 case TAG_EMBED :
1378 switch(st->text_lines[row][index++]){
1379 case TAG_HANDLE:
1380 index += st->text_lines[row][index] + 1;
1381 break;
1383 case TAG_FGCOLOR :
1384 case TAG_BGCOLOR :
1385 index += RGBLEN;
1386 break;
1388 default :
1389 break;
1392 break;
1394 case TAB : /* add tab's spaces */
1395 while(((--column) & 0x07) != 0)
1398 break;
1400 default :
1401 column--;
1402 break;
1405 return(index);
1409 void
1410 scroll_handle_set_loc(POSLIST_S **lpp, int line, int column)
1412 POSLIST_S *lp;
1414 lp = (POSLIST_S *) fs_get(sizeof(POSLIST_S));
1415 lp->where.row = line;
1416 lp->where.col = column;
1417 lp->next = NULL;
1418 for(; *lpp; lpp = &(*lpp)->next)
1421 *lpp = lp;
1426 dot_on_handle(long int line, int goal)
1428 int i = 0, n, key = 0, column = -1;
1429 SCRLCTRL_S *st = scroll_state(SS_CUR);
1432 if(i >= st->line_lengths[line])
1433 return(0);
1435 switch(st->text_lines[line][i++]){
1436 case TAG_EMBED :
1437 switch(st->text_lines[line][i++]){
1438 case TAG_HANDLE :
1439 for(key = 0, n = st->text_lines[line][i++]; n; n--)
1440 key = (key * 10) + (st->text_lines[line][i++] - '0');
1442 break;
1444 case TAG_FGCOLOR :
1445 case TAG_BGCOLOR :
1446 i += RGBLEN; /* advance past color setting */
1447 break;
1449 case TAG_BOLDOFF :
1450 key = 0;
1451 break;
1454 break;
1456 case TAB :
1457 while(((++column) & 0x07) != 0) /* add tab's spaces */
1460 break;
1462 default :
1463 column += width_at_this_position((unsigned char*) &st->text_lines[line][i-1],
1464 st->line_lengths[line] - (i-1));
1465 break;
1468 while(column < goal);
1470 return(key);
1474 url_launch(HANDLE_S *handle)
1476 return do_url_launch(handle->h.url.tool, handle->h.url.path);
1480 * url_launch - Sniff the given url, see if we can do anything with
1481 * it. If not, hand off to user-defined app.
1485 do_url_launch(char *toolp, char *url)
1487 int rv = 0;
1488 url_tool_t f;
1489 #define URL_MAX_LAUNCH (2 * MAILTMPLEN)
1491 if(toolp){
1492 char *cmdp, *p, cmd[URL_MAX_LAUNCH + 4];
1493 int mode, copied = 0;
1494 PIPE_S *syspipe;
1496 /* This code used to quote a URL to prevent arbitrary command execution
1497 * through a URL. The plan was to quote the URL with single quotes,
1498 * and this used to work. BUT some shells do not care about quoting
1499 * and interpret some characters regardless of single quotes. The
1500 * simplest solution is to escape those characters, but then some
1501 * shells will see the escape characters and some others will not.
1502 * It's a mess. There are several ways to go around this mess,
1503 * including adding configuration options (one more!?), or forget
1504 * about shells. What we do is to forget about shells, and execute
1505 * the code as a PIPE_NOSHELL.
1508 cmdp = cmd;
1509 while(cmdp-cmd < URL_MAX_LAUNCH)
1510 if((!*toolp && !copied)
1511 || (*toolp == '_' && !strncmp(toolp + 1, "URL_", 4))){
1513 /* implicit _URL_ at end */
1514 if(!*toolp)
1515 *cmdp++ = ' ';
1517 if(cmdp[-1] == '\'') /* unquote old '_URL_' */
1518 cmdp--;
1520 copied = 1;
1521 for(p = url; p && *p && cmdp-cmd < URL_MAX_LAUNCH; p++)
1522 *cmdp++ = *p;
1524 *cmdp = '\0';
1526 if(*toolp)
1527 toolp += 5; /* length of "_URL_" */
1528 if(*toolp == '\'')
1529 toolp++;
1531 else
1532 if(!(*cmdp++ = *toolp++))
1533 break;
1535 if(cmdp-cmd >= URL_MAX_LAUNCH)
1536 return(url_launch_too_long(rv));
1538 mode = PIPE_RESET | PIPE_USER | PIPE_RUNNOW | PIPE_NOSHELL ;
1539 if((syspipe = open_system_pipe(cmd, NULL, NULL, mode, 0, pipe_callback, pipe_report_error)) != NULL){
1540 close_system_pipe(&syspipe, NULL, pipe_callback);
1541 q_status_message(SM_ORDER, 0, 4, _("VIEWER command completed"));
1543 else
1544 q_status_message1(SM_ORDER, 3, 4,
1545 /* TRANSLATORS: Cannot start command : <command name> */
1546 _("Cannot start command : %s"), cmd);
1548 else if((f = url_local_handler(url)) != NULL){
1549 if((*f)(url) > 1)
1550 rv = 1; /* done! */
1552 else
1553 q_status_message1(SM_ORDER, 2, 2,
1554 _("\"URL-Viewer\" not defined: Can't open %s"), url);
1556 return(rv);
1561 url_launch_too_long(int return_value)
1563 q_status_message(SM_ORDER | SM_DING, 3, 3,
1564 "Can't spawn. Command too long.");
1565 return(return_value);
1569 char *
1570 url_external_handler(HANDLE_S *handle, int specific)
1572 return get_url_external_handler(handle->h.url.path, specific);
1575 char *
1576 get_url_external_handler(char *url, int specific)
1578 char **l, *test, *cmd, *p, *q, *ep;
1579 int i, specific_match;
1581 for(l = ps_global->VAR_BROWSER ; l && *l; l++){
1582 get_pair(*l, &test, &cmd, 0, 1);
1583 dprint((5, "TEST: \"%s\" CMD: \"%s\"\n",
1584 test ? test : "<NULL>", cmd ? cmd : "<NULL>"));
1585 removing_quotes(cmd);
1586 if(valid_filter_command(&cmd)){
1587 specific_match = 0;
1589 if((p = test) != NULL){
1590 while(*p && cmd)
1591 if(*p == '_'){
1592 if(!strncmp(p+1, "TEST(", 5)
1593 && (ep = strstr(p+6, ")_"))){
1594 *ep = '\0';
1596 if(exec_mailcap_test_cmd(p+6) == 0){
1597 p = ep + 2;
1599 else{
1600 dprint((5,"failed handler TEST\n"));
1601 fs_give((void **) &cmd);
1604 else if(!strncmp(p+1, "SCHEME(", 7)
1605 && (ep = strstr(p+8, ")_"))){
1606 *ep = '\0';
1608 p += 8;
1610 if((q = strchr(p, ',')) != NULL)
1611 *q++ = '\0';
1612 else
1613 q = ep;
1614 while(!((i = strlen(p))
1615 && ((p[i-1] == ':' && url[i - 1] == ':')
1616 || (p[i-1] != ':' && url[i] == ':'))
1617 && !struncmp(url, p, i))
1618 && *(p = q));
1620 if(*p){
1621 specific_match = 1;
1622 p = ep + 2;
1624 else{
1625 dprint((5,"failed handler SCHEME\n"));
1626 fs_give((void **) &cmd);
1629 else{
1630 dprint((5, "UNKNOWN underscore test\n"));
1631 fs_give((void **) &cmd);
1634 else if(isspace((unsigned char) *p)){
1635 p++;
1637 else{
1638 dprint((1,"bogus handler test: \"%s\"",
1639 test ? test : "?"));
1640 fs_give((void **) &cmd);
1643 fs_give((void **) &test);
1646 if(cmd && (!specific || specific_match))
1647 return(cmd);
1650 if(test)
1651 fs_give((void **) &test);
1653 if(cmd)
1654 fs_give((void **) &cmd);
1657 cmd = NULL;
1659 if(!specific){
1660 cmd = url_os_specified_browser(url);
1662 * Last chance, anything handling "text/html" in mailcap...
1664 if(!cmd && mailcap_can_display(TYPETEXT, "html", NULL, 0)){
1665 MCAP_CMD_S *mc_cmd;
1667 mc_cmd = mailcap_build_command(TYPETEXT, "html",
1668 NULL, "_URL_", NULL, 0);
1670 * right now URL viewing won't return anything requiring
1671 * special handling
1673 if(mc_cmd){
1674 cmd = mc_cmd->command;
1675 fs_give((void **)&mc_cmd);
1680 return(cmd);
1684 url_tool_t
1685 url_local_handler(char *s)
1687 int i;
1688 static struct url_t {
1689 char *url;
1690 short len;
1691 url_tool_t f;
1692 } handlers[] = {
1693 {"mailto:", 7, url_local_mailto}, /* see url_tool_t def's */
1694 {"imap://", 7, url_local_imap}, /* for explanations */
1695 {"nntp://", 7, url_local_nntp},
1696 {"file://", 7, url_local_file},
1697 #ifdef ENABLE_LDAP
1698 {"ldap://", 7, url_local_ldap},
1699 #endif
1700 {"news:", 5, url_local_news},
1701 {"x-alpine-ical:", 14, ical_send_reply},
1702 {"x-alpine-gripe:", 15, gripe_gripe_to},
1703 {"x-alpine-help:", 14, url_local_helper},
1704 {"x-pine-help:", 12, url_local_helper},
1705 {"x-alpine-config:", 16, url_local_config},
1706 {"x-alpine-cert:", 14, url_local_certdetails},
1707 {"#", 1, url_local_fragment},
1708 {NULL, 0, NULL}
1711 for(i = 0; handlers[i].url ; i++)
1712 if(!struncmp(s, handlers[i].url, handlers[i].len))
1713 return(handlers[i].f);
1715 return(NULL);
1721 * mailto URL digester ala draft-hoffman-mailto-url-02.txt
1724 url_local_mailto(char *url)
1726 return(url_local_mailto_and_atts(url, NULL));
1730 url_local_mailto_and_atts(char *url, PATMT *attachlist)
1732 ENVELOPE *outgoing;
1733 BODY *body = NULL;
1734 REPLY_S fake_reply;
1735 char *sig, *urlp, *p, *hname, *hvalue;
1736 int rv = 0;
1737 long rflags;
1738 int was_a_body = 0, impl, template_len = 0;
1739 char *fcc = NULL;
1740 PAT_STATE dummy;
1741 REDRAFT_POS_S *redraft_pos = NULL;
1742 ACTION_S *role = NULL;
1744 outgoing = mail_newenvelope();
1745 body = mail_newbody();
1746 body->type = TYPETEXT;
1747 if((body->contents.text.data = (void *) so_get(PicoText,NULL,EDIT_ACCESS)) != NULL){
1749 * URL format is:
1751 * mailtoURL = "mailto:" [ to ] [ headers ]
1752 * to = #mailbox
1753 * headers = "?" header *( "&" header )
1754 * header = hname "=" hvalue
1755 * hname = *urlc
1756 * hvalue = *urlc
1758 * NOTE2: "from" and "bcc" are intentionally excluded from
1759 * the list of understood "header" fields
1761 if((p = strchr(urlp = cpystr(url+7), '?')) != NULL)
1762 *p++ = '\0'; /* headers? Tie off mailbox */
1764 /* grok mailbox as first "to", then roll thru specific headers */
1765 if(*urlp)
1766 rfc822_parse_adrlist(&outgoing->to,
1767 rfc1738_str(urlp),
1768 ps_global->maildomain);
1770 while(p){
1771 /* Find next "header" */
1772 if((p = strchr(hname = p, '&')) != NULL)
1773 *p++ = '\0'; /* tie off "header" */
1775 if((hvalue = strchr(hname, '=')) != NULL)
1776 *hvalue++ = '\0'; /* tie off hname */
1778 if(!hvalue || !strucmp(hname, "subject")){
1779 char *sub = rfc1738_str(hvalue ? hvalue : hname);
1781 if(outgoing->subject){
1782 int len = strlen(outgoing->subject);
1784 fs_resize((void **) &outgoing->subject,
1785 (len + strlen(sub) + 2) * sizeof(char));
1786 snprintf(outgoing->subject + len, strlen(sub)+2, " %s", sub);
1787 outgoing->subject[len + strlen(sub) + 2 - 1] = '\0';
1789 else
1790 outgoing->subject = cpystr(sub);
1792 else if(!strucmp(hname, "to")){
1793 url_mailto_addr(&outgoing->to, hvalue);
1795 else if(!strucmp(hname, "cc")){
1796 url_mailto_addr(&outgoing->cc, hvalue);
1798 else if(!strucmp(hname, "bcc")){
1799 q_status_message(SM_ORDER, 3, 4,
1800 "\"Bcc\" header in mailto url ignored");
1802 else if(!strucmp(hname, "from")){
1803 q_status_message(SM_ORDER, 3, 4,
1804 "\"From\" header in mailto url ignored");
1806 else if(!strucmp(hname, "body")){
1807 char *sub = rfc1738_str(hvalue ? hvalue : "");
1809 so_puts((STORE_S *)body->contents.text.data, sub);
1810 so_puts((STORE_S *)body->contents.text.data, NEWLINE);
1811 was_a_body++;
1815 fs_give((void **) &urlp);
1817 if(ps_global->reply.role_chosen == NULL){
1818 rflags = ROLE_COMPOSE;
1819 if(nonempty_patterns(rflags, &dummy)){
1820 role = set_role_from_msg(ps_global, rflags, -1L, NULL);
1821 if(confirm_role(rflags, &role))
1822 role = combine_inherited_role(role);
1823 else{ /* cancel */
1824 role = NULL;
1825 cmd_cancelled("Composition");
1826 goto outta_here;
1830 else
1831 role = ps_global->reply.role_chosen;
1833 if(role)
1834 q_status_message1(SM_ORDER, 3, 4, "Composing using role \"%s\"",
1835 role->nick);
1836 outgoing->message_id = generate_message_id(role);
1838 if(!was_a_body && role && role->template){
1839 char *filtered;
1841 impl = 1; /* leave cursor in header if not explicit */
1842 filtered = detoken(role, NULL, 0, 0, 0, &redraft_pos, &impl);
1843 if(filtered){
1844 if(*filtered){
1845 so_puts((STORE_S *)body->contents.text.data, filtered);
1846 if(impl == 1)
1847 template_len = strlen(filtered);
1850 fs_give((void **)&filtered);
1853 else
1854 impl = 1;
1856 if(!was_a_body && (sig = detoken(role, NULL, 2, 0, 1, &redraft_pos,
1857 &impl))){
1858 if(impl == 2)
1859 redraft_pos->offset += template_len;
1861 if(*sig)
1862 so_puts((STORE_S *)body->contents.text.data, sig);
1864 fs_give((void **)&sig);
1867 memset((void *)&fake_reply, 0, sizeof(fake_reply));
1868 fake_reply.pseudo = 1;
1869 fake_reply.data.pico_flags = (outgoing->subject) ? P_BODY : P_HEADEND;
1872 if(!(role && role->fcc))
1873 fcc = get_fcc_based_on_to(outgoing->to);
1875 if(attachlist)
1876 create_message_body(&body, attachlist, 0);
1878 pine_send(outgoing, &body, "\"MAILTO\" COMPOSE",
1879 role, fcc, &fake_reply, redraft_pos, NULL, NULL, PS_STICKY_TO);
1880 rv++;
1881 ps_global->mangled_screen = 1;
1883 else
1884 q_status_message(SM_ORDER | SM_DING, 3, 4,
1885 _("Can't create space for composer"));
1887 outta_here:
1888 if(outgoing)
1889 mail_free_envelope(&outgoing);
1891 if(body)
1892 pine_free_body(&body);
1894 if(fcc)
1895 fs_give((void **)&fcc);
1897 free_redraft_pos(&redraft_pos);
1898 free_action(&role);
1899 ps_global->reply.role_chosen = NULL;
1901 return(rv);
1905 void
1906 url_mailto_addr(struct mail_address **a, char *a_raw)
1908 char *p = cpystr(rfc1738_str(a_raw));
1910 while(*a) /* append to address list */
1911 a = &(*a)->next;
1913 rfc822_parse_adrlist(a, p, ps_global->maildomain);
1914 fs_give((void **) &p);
1919 * imap URL digester ala RFC 2192
1922 url_local_imap(char *url)
1924 char *folder, *mailbox = NULL, *errstr = NULL, *search = NULL,
1925 newfolder[MAILTMPLEN];
1926 int rv;
1927 long i;
1928 imapuid_t uid = 0L, uid_val = 0L;
1929 CONTEXT_S *fake_context;
1930 MESSAGECACHE *mc;
1932 rv = url_imap_folder(url, &folder, &uid, &uid_val, &search, 0);
1933 switch(rv & URL_IMAP_MASK){
1934 case URL_IMAP_IMAILBOXLIST :
1935 /* BUG: deal with lsub tag */
1936 if((fake_context = new_context(folder, NULL)) != NULL){
1937 newfolder[0] = '\0';
1938 if(display_folder_list(&fake_context, newfolder,
1939 0, folders_for_goto))
1940 if(strlen(newfolder) + 1 < MAILTMPLEN)
1941 mailbox = newfolder;
1943 else
1944 errstr = "Problem building URL's folder list";
1946 fs_give((void **) &folder);
1947 free_context(&fake_context);
1949 if(mailbox){
1950 return(1);
1952 else if(errstr)
1953 q_status_message(SM_ORDER|SM_DING, 3, 3, errstr);
1954 else
1955 cmd_cancelled("URL Launch");
1957 break;
1959 case URL_IMAP_IMESSAGEPART :
1960 case URL_IMAP_IMESSAGELIST :
1961 if(ps_global && ps_global->ttyo){
1962 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
1963 ps_global->mangled_footer = 1;
1966 rv = do_broach_folder(folder, NULL, NULL, 0L);
1967 fs_give((void **) &folder);
1968 switch(rv){
1969 case -1 : /* utter failure */
1970 ps_global->next_screen = main_menu_screen;
1971 break;
1973 case 0 : /* same folder reopened */
1974 ps_global->next_screen = mail_index_screen;
1975 break;
1977 case 1 : /* requested folder open */
1978 ps_global->next_screen = mail_index_screen;
1980 if(uid_val && uid_val != ps_global->mail_stream->uid_validity){
1981 /* Complain! */
1982 q_status_message(SM_ORDER|SM_DING, 3, 3,
1983 "Warning! Referenced folder changed since URL recorded");
1986 if(uid){
1988 * Make specified message the currently selected..
1990 for(i = 1L; i <= mn_get_total(ps_global->msgmap); i++)
1991 if(mail_uid(ps_global->mail_stream, i) == uid){
1992 ps_global->next_screen = mail_view_screen;
1993 mn_set_cur(ps_global->msgmap, i);
1994 break;
1997 if(i > mn_get_total(ps_global->msgmap))
1998 q_status_message(SM_ORDER, 2, 3,
1999 "Couldn't find specified article number");
2001 else if(search){
2003 * Select the specified messages
2004 * and present a zoom'd index...
2006 /* BUG: not dealing with CHARSET yet */
2008 /* ANOTHER BUG: mail_criteria is a compatibility routine for IMAP2BIS
2009 * so it doesn't know about IMAP4 search criteria, like SENTSINCE.
2010 * It also doesn't handle literals. */
2012 pine_mail_search_full(ps_global->mail_stream, NULL,
2013 mail_criteria(search),
2014 SE_NOPREFETCH | SE_FREE);
2016 for(i = 1L; i <= mn_get_total(ps_global->msgmap); i++)
2017 if(ps_global->mail_stream
2018 && i <= ps_global->mail_stream->nmsgs
2019 && (mc = mail_elt(ps_global->mail_stream, i))
2020 && mc->searched)
2021 set_lflag(ps_global->mail_stream,
2022 ps_global->msgmap, i, MN_SLCT, 1);
2024 if((i = any_lflagged(ps_global->msgmap, MN_SLCT)) != 0){
2026 q_status_message2(SM_ORDER, 0, 3,
2027 "%s message%s selected",
2028 long2string(i), plural(i));
2029 /* Zoom the index! */
2030 zoom_index(ps_global, ps_global->mail_stream,
2031 ps_global->msgmap, MN_SLCT);
2036 return(1);
2038 default:
2039 case URL_IMAP_ERROR :
2040 break;
2043 return(0);
2048 url_local_nntp(char *url)
2050 char folder[2*MAILTMPLEN], *group;
2051 int group_len;
2052 long i, article_num;
2054 /* no hostport, no url, end of story */
2055 if((group = strchr(url + 7, '/')) != 0){
2056 group++;
2057 for(group_len = 0; group[group_len] && group[group_len] != '/';
2058 group_len++)
2059 if(!rfc1738_group(&group[group_len]))
2060 /* TRANSLATORS: these are errors in news group URLs */
2061 return(url_bogus(url, _("Invalid newsgroup specified")));
2063 if(group_len){
2064 snprintf(folder, sizeof(folder), "{%.*s/nntp}#news.%.*s",
2065 (int) MIN((group - 1) - (url + 7), MAILTMPLEN-20), url + 7,
2066 (int) MIN(group_len, MAILTMPLEN-20), group);
2067 folder[sizeof(folder)-1] = '\0';
2069 else
2070 return(url_bogus(url, _("No newsgroup specified")));
2072 else
2073 return(url_bogus(url, _("No server specified")));
2075 if(ps_global && ps_global->ttyo){
2076 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2077 ps_global->mangled_footer = 1;
2080 switch(do_broach_folder(rfc1738_str(folder), NULL, NULL, 0L)){
2081 case -1 : /* utter failure */
2082 ps_global->next_screen = main_menu_screen;
2083 break;
2085 case 0 : /* same folder reopened */
2086 ps_global->next_screen = mail_index_screen;
2087 break;
2089 case 1 : /* requested folder open */
2090 ps_global->next_screen = mail_index_screen;
2092 /* grok article number --> c-client UID */
2093 if(group[group_len++] == '/'
2094 && (article_num = atol(&group[group_len]))){
2096 * Make the requested article our current message
2098 for(i = 1; i <= mn_get_nmsgs(ps_global->msgmap); i++)
2099 if(mail_uid(ps_global->mail_stream, i) == article_num){
2100 ps_global->next_screen = mail_view_screen;
2101 if((i = mn_raw2m(ps_global->msgmap, i)) != 0)
2102 mn_set_cur(ps_global->msgmap, i);
2103 break;
2106 if(i == 0 || i > mn_get_total(ps_global->msgmap))
2107 q_status_message(SM_ORDER, 2, 3,
2108 _("Couldn't find specified article number"));
2111 break;
2114 ps_global->redrawer = (void(*)(void))NULL;
2115 return(1);
2120 url_local_news(char *url)
2122 char folder[MAILTMPLEN], *p;
2123 CONTEXT_S *cntxt = NULL;
2126 * NOTE: NO SUPPORT for '*' or message-id
2128 if(*(url+5)){
2129 if(*(url+5) == '/' && *(url+6) == '/')
2130 return(url_local_nntp(url)); /* really meant "nntp://"? */
2132 if(ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0])
2134 * BUG: Only the first NNTP server is tried.
2136 snprintf(folder, sizeof(folder), "{%s/nntp}#news.", ps_global->VAR_NNTP_SERVER[0]);
2137 else
2138 strncpy(folder, "#news.", sizeof(folder));
2140 folder[sizeof(folder)-1] = '\0';
2142 for(p = strncpy(folder + strlen(folder), url + 5, sizeof(folder)-strlen(folder)-1);
2143 *p && rfc1738_group(p);
2144 p++)
2147 if(*p)
2148 return(url_bogus(url, "Invalid newsgroup specified"));
2150 else{ /* fish first group from newsrc */
2151 FOLDER_S *f;
2152 int alphaorder;
2154 folder[0] = '\0';
2156 /* Find first news context */
2157 for(cntxt = ps_global->context_list;
2158 cntxt && !(cntxt->use & CNTXT_NEWS);
2159 cntxt = cntxt->next)
2162 if(cntxt){
2163 if((alphaorder = F_OFF(F_READ_IN_NEWSRC_ORDER, ps_global)) != 0)
2164 (void) F_SET(F_READ_IN_NEWSRC_ORDER, ps_global, 1);
2166 build_folder_list(NULL, cntxt, NULL, NULL, BFL_LSUB);
2167 if((f = folder_entry(0, FOLDERS(cntxt))) != NULL){
2168 strncpy(folder, f->name, sizeof(folder));
2169 folder[sizeof(folder)-1] = '\0';
2172 free_folder_list(cntxt);
2174 if(alphaorder)
2175 (void) F_SET(F_READ_IN_NEWSRC_ORDER, ps_global, 0);
2178 if(folder[0] == '\0'){
2179 q_status_message(SM_ORDER | SM_DING, 3, 3,
2180 "No default newsgroup");
2181 return(0);
2185 if(ps_global && ps_global->ttyo){
2186 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2187 ps_global->mangled_footer = 1;
2190 if(do_broach_folder(rfc1738_str(folder), cntxt, NULL, 0L) < 0)
2191 ps_global->next_screen = main_menu_screen;
2192 else
2193 ps_global->next_screen = mail_index_screen;
2195 ps_global->redrawer = (void(*)(void))NULL;
2197 return(1);
2202 url_local_file(char *file_url)
2204 if(want_to(
2205 /* TRANSLATORS: this is a warning that the file URL can cause programs to run which may
2206 be a security problem. We are asking the user to confirm that they want to do this. */
2207 _("\"file\" URL may cause programs to be run on your system. Run anyway"),
2208 'n', 0, NO_HELP, WT_NORM) == 'y'){
2209 HANDLE_S handle;
2211 /* fake a handle */
2212 handle.h.url.path = file_url;
2213 if((handle.h.url.tool = url_external_handler(&handle, 1))
2214 || (handle.h.url.tool = url_external_handler(&handle, 0))){
2215 url_launch(&handle);
2216 return 1;
2218 else{
2219 q_status_message(SM_ORDER, 0, 4,
2220 _("No viewer for \"file\" URL. VIEWER command cancelled"));
2221 return 0;
2224 q_status_message(SM_ORDER, 0, 4, _("VIEWER command cancelled"));
2225 return 0;
2230 url_local_fragment(char *fragment)
2232 SCRLCTRL_S *st = scroll_state(SS_CUR);
2233 HANDLE_S *hp = NULL;
2236 * find a handle with the fragment's name
2238 if(st){
2239 for(hp = st->parms->text.handles; hp; hp = hp->next)
2240 if(hp->type == URL && hp->h.url.name
2241 && !strcmp(hp->h.url.name, fragment + 1))
2242 break;
2244 if(!hp)
2245 for(hp = st->parms->text.handles->prev; hp; hp = hp->prev)
2246 if(hp->type == URL && hp->h.url.name
2247 && !strcmp(hp->h.url.name, fragment + 1))
2248 break;
2251 * set the top line of the display to contain this line
2253 if(hp && hp->loc){
2254 st->top_text_line = hp->loc->where.row;
2255 ps_global->mangled_body = 1;
2257 else
2258 q_status_message1(SM_ORDER | SM_DING, 0, 3,
2259 "Can't find fragment: %s", fragment);
2261 return(1);
2265 ical_send_reply(char *url)
2267 // ical_compose_reply(url + strlen("x-alpine-ical:"));
2268 return 2;
2273 * Format editorial comment referencing screen offering
2274 * List-* header supplied commands
2277 rfc2369_editorial(long int msgno, HANDLE_S **handlesp, int flags, int width, gf_io_t pc)
2279 char *p, *hdrp, *hdrs[MLCMD_COUNT + 1],
2280 color[64], buf[2048];
2281 int i, n, rv = TRUE;
2282 HANDLE_S *h = NULL;
2284 if((flags & FM_DISPLAY)
2285 && (hdrp = pine_fetchheader_lines(ps_global->mail_stream, msgno,
2286 NULL, rfc2369_hdrs(hdrs)))){
2287 if(*hdrp){
2288 snprintf(buf, sizeof(buf), "Note: This message contains ");
2289 buf[sizeof(buf)-1] = '\0';
2290 p = buf + strlen(buf);
2292 if(handlesp){
2293 h = new_handle(handlesp);
2294 h->type = Function;
2295 h->h.func.f = rfc2369_display;
2296 h->h.func.args.stream = ps_global->mail_stream;
2297 h->h.func.args.msgmap = ps_global->msgmap;
2298 h->h.func.args.msgno = msgno;
2300 if(!(flags & FM_NOCOLOR)
2301 && handle_start_color(color, sizeof(color), &n, 0)){
2302 if((p-buf)+n < sizeof(buf))
2303 for(i = 0; i < n; i++)
2304 *p++ = color[i];
2306 else if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)){
2307 *p++ = TAG_EMBED;
2308 *p++ = TAG_BOLDON;
2311 if((p-buf)+2 < sizeof(buf)){
2312 *p++ = TAG_EMBED;
2313 *p++ = TAG_HANDLE;
2316 snprintf(p + 1, sizeof(buf)-(p+1-buf), "%d", h->key);
2317 buf[sizeof(buf)-1] = '\0';
2318 *p = strlen(p + 1);
2319 p += (*p + 1);
2322 sstrncpy(&p, "email list management information", sizeof(buf)-(p-buf));
2323 buf[sizeof(buf)-1] = '\0';
2325 if(h){
2326 /* in case it was the current selection */
2327 if((p-buf)+2 < sizeof(buf)){
2328 *p++ = TAG_EMBED;
2329 *p++ = TAG_INVOFF;
2332 if(handle_end_color(color, sizeof(color), &n)){
2333 if((p-buf)+n < sizeof(buf))
2334 for(i = 0; i < n; i++)
2335 *p++ = color[i];
2337 else{
2338 if((p-buf)+2 < sizeof(buf)){
2339 *p++ = TAG_EMBED;
2340 *p++ = TAG_BOLDOFF;
2344 if(p-buf < sizeof(buf))
2345 *p = '\0';
2348 buf[sizeof(buf)-1] = '\0';
2350 rv = (gf_puts(NEWLINE, pc)
2351 && format_editorial(buf, width, flags, handlesp, pc) == NULL
2352 && gf_puts(NEWLINE, pc));
2355 fs_give((void **) &hdrp);
2358 return(rv);
2363 /*----------------------------------------------------------------------
2364 routine for displaying text on the screen.
2366 Args: sparms -- structure of args controlling what happens outside
2367 just the business of managing text scrolling
2369 This displays in three different kinds of text. One is an array of
2370 lines passed in in text_array. The other is a simple long string of
2371 characters passed in in text.
2373 The style determines what some of the error messages will be, and
2374 what commands are available as different things are appropriate for
2375 help text than for message text etc.
2377 ---*/
2380 scrolltool(SCROLL_S *sparms)
2382 register long cur_top_line, num_display_lines;
2383 UCS ch;
2384 int result, done, cmd, found_on, found_on_index,
2385 first_view, force, scroll_lines, km_size,
2386 cursor_row, cursor_col, km_popped, rv,
2387 nrows, ncols;
2388 char *utf8str;
2389 long jn;
2390 struct key_menu *km;
2391 HANDLE_S *next_handle;
2392 bitmap_t bitmap;
2393 OtherMenu what;
2394 Pos whereis_pos;
2396 num_display_lines = SCROLL_LINES(ps_global);
2397 km_popped = 0;
2398 ps_global->mangled_header = 1;
2399 ps_global->mangled_footer = 1;
2400 ps_global->mangled_body = !sparms->body_valid;
2401 ncols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 0;
2402 nrows = ps_global->ttyo ? ps_global->ttyo->screen_rows : 0;
2404 what = sparms->keys.what; /* which key menu to display */
2405 cur_top_line = 0;
2406 done = 0;
2407 found_on = -1;
2408 found_on_index = -1;
2409 first_view = 1;
2410 if(sparms->quell_first_view)
2411 first_view = 0;
2413 force = 0;
2414 ch = 'x'; /* for first time through */
2415 whereis_pos.row = 0;
2416 whereis_pos.col = 0;
2417 next_handle = sparms->text.handles;
2419 set_scroll_text(sparms, cur_top_line, scroll_state(SS_NEW));
2420 format_scroll_text();
2422 if((km = sparms->keys.menu) != NULL){
2423 memcpy(bitmap, sparms->keys.bitmap, sizeof(bitmap_t));
2425 else{
2426 setbitmap(bitmap);
2427 km = &simple_text_keymenu;
2428 #ifdef _WINDOWS
2429 sparms->mouse.popup = simple_text_popup;
2430 #endif
2433 if(!sparms->bar.title)
2434 sparms->bar.title = "Text";
2436 if(sparms->bar.style == TitleBarNone){
2437 if(THREADING() && sp_viewing_a_thread(ps_global->mail_stream))
2438 sparms->bar.style = ThrdMsgPercent;
2439 else
2440 sparms->bar.style = MsgTextPercent;
2443 switch(sparms->start.on){
2444 case LastPage :
2445 cur_top_line = MAX(0, scroll_text_lines() - (num_display_lines-2));
2446 if(F_ON(F_SHOW_CURSOR, ps_global)){
2447 whereis_pos.row = scroll_text_lines() - cur_top_line;
2448 found_on = scroll_text_lines() - 1;
2451 break;
2453 case Fragment :
2454 if(sparms->start.loc.frag){
2455 (void) url_local_fragment(sparms->start.loc.frag);
2457 cur_top_line = scroll_state(SS_CUR)->top_text_line;
2459 if(F_ON(F_SHOW_CURSOR, ps_global)){
2460 whereis_pos.row = scroll_text_lines() - cur_top_line;
2461 found_on = scroll_text_lines() - 1;
2465 break;
2467 case Offset :
2468 if(sparms->start.loc.offset){
2469 for(cur_top_line = 0L;
2470 cur_top_line + 1 < scroll_text_lines()
2471 && (sparms->start.loc.offset
2472 -= scroll_handle_column(cur_top_line,
2473 -1)) >= 0;
2474 cur_top_line++)
2478 break;
2480 case Handle :
2481 if(scroll_handle_obscured(sparms->text.handles))
2482 cur_top_line = scroll_handle_reframe(-1, TRUE);
2484 break;
2486 default : /* no-op */
2487 break;
2490 /* prepare for calls below to tell us where to go */
2491 ps_global->next_screen = SCREEN_FUN_NULL;
2493 cancel_busy_cue(-1);
2495 while(!done) {
2496 ps_global->user_says_cancel = 0;
2497 if(km_popped){
2498 km_popped--;
2499 if(km_popped == 0){
2500 clearfooter(ps_global);
2501 ps_global->mangled_body = 1;
2505 if(ps_global->mangled_screen) {
2506 ps_global->mangled_header = 1;
2507 ps_global->mangled_footer = 1;
2508 ps_global->mangled_body = 1;
2511 if(!sparms->quell_newmail && streams_died())
2512 ps_global->mangled_header = 1;
2514 dprint((9, "@@@@ current:%ld\n",
2515 mn_get_cur(ps_global->msgmap)));
2518 /*==================== All Screen painting ====================*/
2519 /*-------------- The title bar ---------------*/
2520 update_scroll_titlebar(cur_top_line, ps_global->mangled_header);
2522 if(ps_global->mangled_screen){
2523 /* this is the only line not cleared by header, body or footer
2524 * repaint calls....
2526 ClearLine(1);
2527 ps_global->mangled_screen = 0;
2530 /*---- Scroll or update the body of the text on the screen -------*/
2531 cur_top_line = scroll_scroll_text(cur_top_line, next_handle,
2532 ps_global->mangled_body);
2533 ps_global->redrawer = redraw_scroll_text;
2534 ps_global->mangled_body = 0;
2536 /*--- Check to see if keymenu might change based on next_handle --*/
2537 if(sparms->text.handles != next_handle)
2538 ps_global->mangled_footer = 1;
2540 if(next_handle)
2541 sparms->text.handles = next_handle;
2543 /*------------- The key menu footer --------------------*/
2544 if(ps_global->mangled_footer || sparms->keys.each_cmd){
2545 if(km_popped){
2546 FOOTER_ROWS(ps_global) = 3;
2547 clearfooter(ps_global);
2550 if(F_ON(F_ARROW_NAV, ps_global)){
2551 menu_clear_binding(km, KEY_LEFT);
2552 if((cmd = menu_clear_binding(km, '<')) != MC_UNKNOWN){
2553 menu_add_binding(km, '<', cmd);
2554 menu_add_binding(km, KEY_LEFT, cmd);
2558 if(F_ON(F_ARROW_NAV, ps_global)){
2559 menu_clear_binding(km, KEY_RIGHT);
2560 if((cmd = menu_clear_binding(km, '>')) != MC_UNKNOWN){
2561 menu_add_binding(km, '>', cmd);
2562 menu_add_binding(km, KEY_RIGHT, cmd);
2566 if(sparms->keys.each_cmd){
2567 (*sparms->keys.each_cmd)(sparms,
2568 scroll_handle_obscured(sparms->text.handles));
2569 memcpy(bitmap, sparms->keys.bitmap, sizeof(bitmap_t));
2572 if(menu_binding_index(km, MC_JUMP) >= 0){
2573 for(cmd = 0; cmd < 10; cmd++)
2574 if(F_ON(F_ENABLE_JUMP, ps_global))
2575 (void) menu_add_binding(km, '0' + cmd, MC_JUMP);
2576 else
2577 (void) menu_clear_binding(km, '0' + cmd);
2580 draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
2581 1-FOOTER_ROWS(ps_global), 0, what);
2582 what = SameMenu;
2583 ps_global->mangled_footer = 0;
2584 if(km_popped){
2585 FOOTER_ROWS(ps_global) = 1;
2586 mark_keymenu_dirty();
2590 if((ps_global->first_time_user || ps_global->show_new_version)
2591 && first_view && sparms->text.handles
2592 && (sparms->text.handles->next || sparms->text.handles->prev)
2593 && !sparms->quell_help)
2594 q_status_message(SM_ORDER, 0, 3, HANDLE_INIT_MSG);
2596 /*============ Check for New Mail and CheckPoint ============*/
2597 if(!sparms->quell_newmail &&
2598 new_mail(force, NM_TIMING(ch), NM_STATUS_MSG) >= 0){
2599 update_scroll_titlebar(cur_top_line, 1);
2600 if(ps_global->mangled_footer)
2601 draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
2602 1-FOOTER_ROWS(ps_global), 0, what);
2604 ps_global->mangled_footer = 0;
2608 * If an expunge of the current message happened during the
2609 * new mail check we want to bail out of here. See mm_expunged.
2611 if(ps_global->next_screen != SCREEN_FUN_NULL){
2612 done = 1;
2613 continue;
2616 if(ps_global->noticed_change_in_unseen){
2617 ps_global->noticed_change_in_unseen = 0; /* redraw only once */
2618 cmd = MC_RESIZE; /* causes cursor to be saved in folder_lister */
2619 done = 1;
2620 continue;
2623 if(first_view && num_display_lines >= scroll_text_lines())
2624 q_status_message1(SM_INFO, 0, 1, "ALL of %s", STYLE_NAME(sparms));
2627 force = 0; /* may not need to next time around */
2628 first_view = 0; /* check_point a priority any more? */
2630 /*==================== Output the status message ==============*/
2631 if(!sparms->no_stat_msg){
2632 if(km_popped){
2633 FOOTER_ROWS(ps_global) = 3;
2634 mark_status_unknown();
2637 display_message(ch);
2638 if(km_popped){
2639 FOOTER_ROWS(ps_global) = 1;
2640 mark_status_unknown();
2644 if(F_ON(F_SHOW_CURSOR, ps_global)){
2645 #ifdef WINDOWS
2646 if(cur_top_line != scroll_state(SS_CUR)->top_text_line)
2647 whereis_pos.row = 0;
2648 #endif
2650 if(whereis_pos.row > 0){
2651 cursor_row = SCROLL_LINES_ABOVE(ps_global)
2652 + whereis_pos.row - 1;
2653 cursor_col = whereis_pos.col;
2655 else{
2656 POSLIST_S *lp = NULL;
2658 if(sparms->text.handles &&
2659 !scroll_handle_obscured(sparms->text.handles)){
2660 SCRLCTRL_S *st = scroll_state(SS_CUR);
2662 for(lp = sparms->text.handles->loc; lp; lp = lp->next)
2663 if(lp->where.row >= st->top_text_line
2664 && lp->where.row < st->top_text_line
2665 + st->screen.length){
2666 cursor_row = lp->where.row - cur_top_line
2667 + SCROLL_LINES_ABOVE(ps_global);
2668 cursor_col = lp->where.col;
2669 break;
2673 if(!lp){
2674 cursor_col = 0;
2675 /* first new line of text */
2676 cursor_row = SCROLL_LINES_ABOVE(ps_global) +
2677 ((cur_top_line == 0) ? 0 : ps_global->viewer_overlap);
2681 else{
2682 cursor_col = 0;
2683 cursor_row = ps_global->ttyo->screen_rows
2684 - SCROLL_LINES_BELOW(ps_global);
2687 MoveCursor(cursor_row, cursor_col);
2689 /*================ Get command and validate =====================*/
2690 #ifdef MOUSE
2691 #ifndef WIN32
2692 if(sparms->text.handles)
2693 #endif
2695 mouse_in_content(KEY_MOUSE, -1, -1, 0x5, 0);
2696 register_mfunc(mouse_in_content, HEADER_ROWS(ps_global), 0,
2697 ps_global->ttyo->screen_rows
2698 - (FOOTER_ROWS(ps_global) + 1),
2699 ps_global->ttyo->screen_cols);
2701 #endif
2702 #ifdef _WINDOWS
2703 mswin_allowcopy(mswin_readscrollbuf);
2704 mswin_setscrollcallback(pcpine_do_scroll);
2706 if(sparms->help.text != NO_HELP)
2707 mswin_sethelptextcallback(pcpine_help_scroll);
2709 if(sparms->text.handles
2710 && sparms->text.handles->type != Folder)
2711 mswin_mousetrackcallback(pcpine_view_cursor);
2713 if(ps_global->prev_screen == mail_view_screen)
2714 mswin_setviewinwindcallback(view_in_new_window);
2715 #endif
2716 rv = -1;
2717 if(sparms->aux_function
2718 && sparms->aux_condition
2719 && ((rv = (sparms->aux_condition)(sparms->aux_value)) == 0))
2720 (sparms->aux_function)(sparms->aux_value, sparms->aux_rv_value);
2722 if(rv == 0){
2723 ch = (sparms->decode_aux_rv_value)(sparms->aux_value, sparms->aux_rv_value);
2724 if (ch == NO_OP_COMMAND) ch = read_command(&utf8str);
2726 else if(cmd == MC_RESIZE
2727 || ps_global->ttyo == NULL
2728 || (ps_global->ttyo->screen_cols == ncols
2729 && ps_global->ttyo->screen_rows == nrows))
2730 ch = (sparms->quell_newmail || read_command_prep()) ? read_command(&utf8str) : NO_OP_COMMAND;
2731 #ifdef MOUSE
2732 #ifndef WIN32
2733 if(sparms->text.handles)
2734 #endif
2735 clear_mfunc(mouse_in_content);
2736 #endif
2737 #ifdef _WINDOWS
2738 mswin_allowcopy(NULL);
2739 mswin_setscrollcallback(NULL);
2740 mswin_sethelptextcallback(NULL);
2741 mswin_mousetrackcallback(NULL);
2742 mswin_setviewinwindcallback(NULL);
2743 cur_top_line = scroll_state(SS_CUR)->top_text_line;
2744 #endif
2745 /* we need to check if there was a resize of the screen
2746 * which did not happen in this routine but during a call
2747 * to another routine from this routing, and that routine has no
2748 * way to tell us that a resize happened
2750 if(cmd != MC_RESIZE
2751 && ps_global->ttyo
2752 && (ps_global->ttyo->screen_cols != ncols
2753 || ps_global->ttyo->screen_rows != nrows))
2754 cmd = MC_RESIZE;
2755 else
2756 cmd = menu_command(ch, km);
2758 ncols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 0;
2759 nrows = ps_global->ttyo ? ps_global->ttyo->screen_rows : 0;
2761 if(km_popped)
2762 switch(cmd){
2763 case MC_NONE :
2764 case MC_OTHER :
2765 case MC_RESIZE:
2766 case MC_REPAINT :
2767 km_popped++;
2768 break;
2770 default:
2771 clearfooter(ps_global);
2772 break;
2776 /*============= Execute command =======================*/
2777 switch(cmd){
2779 /* ------ Help -------*/
2780 case MC_HELP :
2781 if(FOOTER_ROWS(ps_global) == 1 && km_popped == 0){
2782 km_popped = 2;
2783 ps_global->mangled_footer = 1;
2784 break;
2787 whereis_pos.row = 0;
2788 if(sparms->help.text == NO_HELP){
2789 q_status_message(SM_ORDER, 0, 5,
2790 _("No help text currently available"));
2791 break;
2794 km_size = FOOTER_ROWS(ps_global);
2796 helper(sparms->help.text, sparms->help.title, 0);
2798 if(ps_global->next_screen != main_menu_screen
2799 && km_size == FOOTER_ROWS(ps_global)) {
2800 /* Have to reset because helper uses scroll_text */
2801 num_display_lines = SCROLL_LINES(ps_global);
2802 ps_global->mangled_screen = 1;
2804 else
2805 done = 1;
2807 break;
2810 /*---------- Roll keymenu ------*/
2811 case MC_OTHER :
2812 if(F_OFF(F_USE_FK, ps_global))
2813 warn_other_cmds();
2815 what = NextMenu;
2816 ps_global->mangled_footer = 1;
2817 break;
2820 /* -------- Scroll back one page -----------*/
2821 case MC_PAGEUP :
2822 whereis_pos.row = 0;
2823 if(cur_top_line) {
2824 scroll_lines = MIN(MAX(num_display_lines -
2825 ps_global->viewer_overlap, 1), num_display_lines);
2826 cur_top_line -= scroll_lines;
2827 if(cur_top_line <= 0){
2828 cur_top_line = 0;
2829 q_status_message1(SM_INFO, 0, 1, "START of %s",
2830 STYLE_NAME(sparms));
2833 else{
2834 /* hilite last available handle */
2835 next_handle = NULL;
2836 if(sparms->text.handles){
2837 HANDLE_S *h = sparms->text.handles;
2839 while((h = scroll_handle_prev_sel(h))
2840 && !scroll_handle_obscured(h))
2841 next_handle = h;
2844 if(!next_handle)
2845 q_status_message1(SM_ORDER, 0, 1, _("Already at start of %s"),
2846 STYLE_NAME(sparms));
2851 break;
2854 /*---- Scroll down one page -------*/
2855 case MC_PAGEDN :
2856 if(cur_top_line + num_display_lines < scroll_text_lines()){
2857 whereis_pos.row = 0;
2858 scroll_lines = MIN(MAX(num_display_lines -
2859 ps_global->viewer_overlap, 1), num_display_lines);
2860 cur_top_line += scroll_lines;
2862 if(cur_top_line + num_display_lines >= scroll_text_lines())
2863 q_status_message1(SM_INFO, 0, 1, "END of %s",
2864 STYLE_NAME(sparms));
2866 else if(!sparms->end_scroll
2867 || !(done = (*sparms->end_scroll)(sparms))){
2868 q_status_message1(SM_ORDER, 0, 1, _("Already at end of %s"),
2869 STYLE_NAME(sparms));
2870 /* hilite last available handle */
2871 if(sparms->text.handles){
2872 HANDLE_S *h = sparms->text.handles;
2874 while((h = scroll_handle_next_sel(h)) != NULL)
2875 next_handle = h;
2879 break;
2881 /* scroll to the top page */
2882 case MC_HOMEKEY:
2883 if(cur_top_line){
2884 cur_top_line = 0;
2885 q_status_message1(SM_INFO, 0, 1, "START of %s",
2886 STYLE_NAME(sparms));
2889 next_handle = NULL;
2890 if(sparms->text.handles){
2891 HANDLE_S *h = sparms->text.handles;
2893 while((h = scroll_handle_prev_sel(h)) != NULL)
2894 next_handle = h;
2896 break;
2898 /* scroll to the bottom page */
2899 case MC_ENDKEY:
2900 if(cur_top_line + num_display_lines < scroll_text_lines()){
2901 cur_top_line = scroll_text_lines() - MIN(5, num_display_lines);
2902 q_status_message1(SM_INFO, 0, 1, "END of %s",
2903 STYLE_NAME(sparms));
2906 if(sparms->text.handles){
2907 HANDLE_S *h = sparms->text.handles;
2909 while((h = scroll_handle_next_sel(h)) != NULL)
2910 next_handle = h;
2912 break;
2914 /*------ Scroll down one line -----*/
2915 case MC_CHARDOWN :
2916 next_handle = NULL;
2917 if(sparms->text.handles){
2918 if(sparms->vert_handle){
2919 HANDLE_S *h, *h2;
2920 int i, j, k;
2922 h2 = sparms->text.handles;
2923 if(h2->type == Folder && h2->prev && h2->prev->is_dual_do_open)
2924 h2 = h2->prev;
2926 i = h2->loc->where.row + 1;
2927 j = h2->loc->where.col;
2928 for(h = NULL, k = h2->key;
2929 h2 && (!h
2930 || (h->loc->where.row == h2->loc->where.row));
2931 h2 = h2->next)
2932 /* must be different key */
2933 /* ... below current line */
2934 /* ... pref'bly to left */
2935 if(h2->key != k
2936 && h2->loc->where.row >= i){
2937 if(h2->loc->where.col > j){
2938 if(!h)
2939 h = h2;
2941 break;
2943 else
2944 h = h2;
2947 if(h){
2948 whereis_pos.row = 0;
2949 next_handle = h;
2950 if((result = scroll_handle_obscured(next_handle)) != 0){
2951 long new_top;
2953 if(scroll_handle_obscured(sparms->text.handles)
2954 && result > 0)
2955 next_handle = sparms->text.handles;
2957 ps_global->mangled_body++;
2958 new_top = scroll_handle_reframe(next_handle->key,0);
2959 if(new_top >= 0)
2960 cur_top_line = new_top;
2964 else if(!(ch == ctrl('N') || F_ON(F_FORCE_ARROWS, ps_global)))
2965 next_handle = scroll_handle_next(sparms->text.handles);
2968 if(!next_handle){
2969 if(cur_top_line + num_display_lines < scroll_text_lines()){
2970 whereis_pos.row = 0;
2971 cur_top_line++;
2972 if(cur_top_line + num_display_lines >= scroll_text_lines())
2973 q_status_message1(SM_INFO, 0, 1, "END of %s",
2974 STYLE_NAME(sparms));
2976 else
2977 q_status_message1(SM_ORDER, 0, 1, _("Already at end of %s"),
2978 STYLE_NAME(sparms));
2981 break;
2984 /* ------ Scroll back up one line -------*/
2985 case MC_CHARUP :
2986 next_handle = NULL;
2987 if(sparms->text.handles){
2988 if(sparms->vert_handle){
2989 HANDLE_S *h, *h2;
2990 int i, j, k;
2992 h2 = sparms->text.handles;
2993 if(h2->type == Folder && h2->prev && h2->prev->is_dual_do_open)
2994 h2 = h2->prev;
2996 i = h2->loc->where.row - 1;
2997 j = h2->loc->where.col;
2999 for(h = NULL, k = h2->key;
3000 h2 && (!h
3001 || (h->loc->where.row == h2->loc->where.row));
3002 h2 = h2->prev)
3003 /* must be new key, above current
3004 * line and pref'bly to right
3006 if(h2->key != k
3007 && h2->loc->where.row <= i){
3008 if(h2->loc->where.col < j){
3009 if(!h)
3010 h = h2;
3012 break;
3014 else
3015 h = h2;
3018 if(h){
3019 whereis_pos.row = 0;
3020 next_handle = h;
3021 if((result = scroll_handle_obscured(next_handle)) != 0){
3022 long new_top;
3024 if(scroll_handle_obscured(sparms->text.handles)
3025 && result < 0)
3026 next_handle = sparms->text.handles;
3028 ps_global->mangled_body++;
3029 new_top = scroll_handle_reframe(next_handle->key,0);
3030 if(new_top >= 0)
3031 cur_top_line = new_top;
3035 else if(!(ch == ctrl('P') || F_ON(F_FORCE_ARROWS, ps_global)))
3036 next_handle = scroll_handle_prev(sparms->text.handles);
3039 if(!next_handle){
3040 whereis_pos.row = 0;
3041 if(cur_top_line){
3042 cur_top_line--;
3043 if(cur_top_line == 0)
3044 q_status_message1(SM_INFO, 0, 1, "START of %s",
3045 STYLE_NAME(sparms));
3047 else
3048 q_status_message1(SM_ORDER, 0, 1,
3049 _("Already at start of %s"),
3050 STYLE_NAME(sparms));
3053 break;
3056 case MC_NEXT_HANDLE :
3057 if((next_handle = scroll_handle_next_sel(sparms->text.handles)) != NULL){
3058 whereis_pos.row = 0;
3059 if((result = scroll_handle_obscured(next_handle)) != 0){
3060 long new_top;
3062 if(scroll_handle_obscured(sparms->text.handles)
3063 && result > 0)
3064 next_handle = sparms->text.handles;
3066 ps_global->mangled_body++;
3067 new_top = scroll_handle_reframe(next_handle->key, 0);
3068 if(new_top >= 0)
3069 cur_top_line = new_top;
3072 else{
3073 if(scroll_handle_obscured(sparms->text.handles)){
3074 long new_top;
3076 ps_global->mangled_body++;
3077 if((new_top = scroll_handle_reframe(-1, 0)) >= 0){
3078 whereis_pos.row = 0;
3079 cur_top_line = new_top;
3083 q_status_message1(SM_ORDER, 0, 1,
3084 _("Already on last item in %s"),
3085 STYLE_NAME(sparms));
3088 break;
3091 case MC_PREV_HANDLE :
3092 if((next_handle = scroll_handle_prev_sel(sparms->text.handles)) != NULL){
3093 whereis_pos.row = 0;
3094 if((result = scroll_handle_obscured(next_handle)) != 0){
3095 long new_top;
3097 if(scroll_handle_obscured(sparms->text.handles)
3098 && result < 0)
3099 next_handle = sparms->text.handles;
3101 ps_global->mangled_body++;
3102 new_top = scroll_handle_reframe(next_handle->key, 0);
3103 if(new_top >= 0)
3104 cur_top_line = new_top;
3107 else{
3108 if(scroll_handle_obscured(sparms->text.handles)){
3109 long new_top;
3111 ps_global->mangled_body++;
3112 if((new_top = scroll_handle_reframe(-1, 0)) >= 0){
3113 whereis_pos.row = 0;
3114 cur_top_line = new_top;
3118 q_status_message1(SM_ORDER, 0, 1,
3119 _("Already on first item in %s"),
3120 STYLE_NAME(sparms));
3123 break;
3126 /*------ View the current handle ------*/
3127 case MC_VIEW_HANDLE :
3128 switch(scroll_handle_obscured(sparms->text.handles)){
3129 default :
3130 case 0 :
3131 switch(scroll_handle_launch(sparms->text.handles,
3132 sparms->text.handles->force_display)){
3133 case 1 :
3134 cmd = MC_EXIT; /* propagate */
3135 done = 1;
3136 break;
3138 case -1 :
3139 cmd_cancelled(NULL);
3140 break;
3142 default :
3143 break;
3146 cur_top_line = scroll_state(SS_CUR)->top_text_line;
3147 break;
3149 case 1 :
3150 q_status_message(SM_ORDER, 0, 2, HANDLE_BELOW_ERR);
3151 break;
3153 case -1 :
3154 q_status_message(SM_ORDER, 0, 2, HANDLE_ABOVE_ERR);
3155 break;
3158 break;
3160 /*---------- Search text (where is) ----------*/
3161 case MC_WHEREIS :
3162 ps_global->mangled_footer = 1;
3163 {long start_row;
3164 int start_index, key = 0;
3165 char *report = NULL;
3167 start_row = cur_top_line;
3168 start_index = 0;
3170 if(F_ON(F_SHOW_CURSOR,ps_global)){
3171 if(found_on < 0
3172 || found_on >= scroll_text_lines()
3173 || found_on < cur_top_line
3174 || found_on >= cur_top_line + num_display_lines){
3175 start_row = cur_top_line;
3176 start_index = 0;
3178 else{
3179 if(found_on_index < 0){
3180 start_row = found_on + 1;
3181 start_index = 0;
3183 else{
3184 start_row = found_on;
3185 start_index = found_on_index+1;
3189 else if(sparms->srch_handle){
3190 HANDLE_S *h;
3192 if((h = scroll_handle_next_sel(sparms->text.handles)) != NULL){
3194 * Translate the screen's column into the
3195 * line offset to start on...
3197 * This makes it so search_text never returns -3
3198 * so we don't know it is the same match. That's
3199 * because we start well after the current handle
3200 * (at the next handle) and that causes us to
3201 * think the one we just matched on is a different
3202 * one from before. Can't think of an easy way to
3203 * fix it, though, and it isn't a big deal. We still
3204 * match, we just don't say current line contains
3205 * the only match.
3207 start_row = h->loc->where.row;
3208 start_index = scroll_handle_index(start_row, h->loc->where.col);
3210 else{
3211 /* last handle, start over at top */
3212 start_row = cur_top_line;
3213 start_index = 0;
3216 else{
3217 start_row = (found_on < 0
3218 || found_on >= scroll_text_lines()
3219 || found_on < cur_top_line
3220 || found_on >= cur_top_line + num_display_lines)
3221 ? cur_top_line : found_on + 1,
3222 start_index = 0;
3225 found_on = search_text(-FOOTER_ROWS(ps_global), start_row,
3226 start_index, &report,
3227 &whereis_pos, &found_on_index);
3229 if(found_on == -4){ /* search to top of text */
3230 whereis_pos.row = 0;
3231 whereis_pos.col = 0;
3232 found_on = 0;
3233 if(sparms->text.handles && sparms->srch_handle)
3234 key = 1;
3236 else if(found_on == -5){ /* search to bottom of text */
3237 HANDLE_S *h;
3239 whereis_pos.row = MAX(scroll_text_lines() - 1, 0);
3240 whereis_pos.col = 0;
3241 found_on = whereis_pos.row;
3242 if((h = sparms->text.handles) && sparms->srch_handle)
3244 key = h->key;
3245 while((h = h->next) != NULL);
3247 else if(found_on == -3){
3248 whereis_pos.row = found_on = start_row;
3249 found_on_index = start_index - 1;
3250 q_status_message(SM_ORDER, 1, 3,
3251 _("Current line contains the only match"));
3254 if(found_on >= 0){
3255 result = found_on < cur_top_line;
3256 if(!key)
3257 key = (sparms->text.handles)
3258 ? dot_on_handle(found_on, whereis_pos.col) : 0;
3260 if(F_ON(F_FORCE_LOW_SPEED,ps_global)
3261 || ps_global->low_speed
3262 || F_ON(F_SHOW_CURSOR,ps_global)
3263 || key){
3264 if((found_on >= cur_top_line + num_display_lines ||
3265 found_on < cur_top_line) &&
3266 num_display_lines > ps_global->viewer_overlap){
3267 cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
3268 if(scroll_text_lines()-cur_top_line < 5)
3269 cur_top_line = MAX(0,
3270 scroll_text_lines()-MIN(5,num_display_lines));
3272 /* else leave cur_top_line alone */
3274 else{
3275 cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
3276 if(scroll_text_lines()-cur_top_line < 5)
3277 cur_top_line = MAX(0,
3278 scroll_text_lines()-MIN(5,num_display_lines));
3281 whereis_pos.row = whereis_pos.row - cur_top_line + 1;
3282 if(report)
3283 q_status_message(SM_ORDER, 0, 3, report);
3284 else
3285 q_status_message2(SM_ORDER, 0, 3,
3286 "%sFound on line %s on screen",
3287 result ? "Search wrapped to start. " : "",
3288 int2string(whereis_pos.row));
3290 if(key){
3291 if(sparms->text.handles->key < key)
3292 for(next_handle = sparms->text.handles->next;
3293 next_handle->key != key;
3294 next_handle = next_handle->next)
3296 else
3297 for(next_handle = sparms->text.handles;
3298 next_handle->key != key;
3299 next_handle = next_handle->prev)
3303 else if(found_on == -1)
3304 cmd_cancelled("Search");
3305 else
3306 q_status_message(SM_ORDER, 0, 3, _("Word not found"));
3309 break;
3312 /*-------------- jump command -------------*/
3313 /* NOTE: preempt the process_cmd() version because
3314 * we need to get at the number..
3316 case MC_JUMP :
3317 jn = jump_to(ps_global->msgmap, -FOOTER_ROWS(ps_global), ch,
3318 sparms, View);
3319 if(sparms && sparms->jump_is_debug)
3320 done = 1;
3321 else if(jn > 0 && jn != mn_get_cur(ps_global->msgmap)){
3323 if(mn_total_cur(ps_global->msgmap) > 1L)
3324 mn_reset_cur(ps_global->msgmap, jn);
3325 else
3326 mn_set_cur(ps_global->msgmap, jn);
3328 done = 1;
3330 else
3331 ps_global->mangled_footer = 1;
3333 break;
3336 #ifdef MOUSE
3337 /*-------------- Mouse Event -------------*/
3338 case MC_MOUSE:
3340 MOUSEPRESS mp;
3341 long line;
3342 int key;
3344 mouse_get_last (NULL, &mp);
3345 mp.row -= 2;
3347 /* The clicked line have anything special on it? */
3348 if((line = cur_top_line + mp.row) < scroll_text_lines()
3349 && (key = dot_on_handle(line, mp.col))){
3350 switch(mp.button){
3351 case M_BUTTON_RIGHT :
3352 #ifdef _WINDOWS
3353 if(sparms->mouse.popup){
3354 if(sparms->text.handles->key < key)
3355 for(next_handle = sparms->text.handles->next;
3356 next_handle->key != key;
3357 next_handle = next_handle->next)
3359 else
3360 for(next_handle = sparms->text.handles;
3361 next_handle->key != key;
3362 next_handle = next_handle->prev)
3365 if(sparms->mouse.popup){
3366 cur_top_line = scroll_scroll_text(cur_top_line,
3367 next_handle,
3368 ps_global->mangled_body);
3369 fflush(stdout);
3370 switch((*sparms->mouse.popup)(sparms, key)){
3371 case 1 :
3372 cur_top_line = doubleclick_handle(sparms, next_handle, &cmd, &done);
3373 break;
3375 case 2 :
3376 done++;
3377 break;
3382 #endif
3383 break;
3385 case M_BUTTON_LEFT :
3386 if(sparms->text.handles->key < key)
3387 for(next_handle = sparms->text.handles->next;
3388 next_handle->key != key;
3389 next_handle = next_handle->next)
3391 else
3392 for(next_handle = sparms->text.handles;
3393 next_handle->key != key;
3394 next_handle = next_handle->prev)
3397 if(mp.doubleclick) /* launch url */
3398 cur_top_line = doubleclick_handle(sparms, next_handle, &cmd, &done);
3399 else if(sparms->mouse.click)
3400 (*sparms->mouse.click)(sparms);
3402 break;
3404 case M_BUTTON_MIDDLE : /* NO-OP for now */
3405 break;
3407 default: /* just ignore */
3408 break;
3411 #ifdef _WINDOWS
3412 else if(mp.button == M_BUTTON_RIGHT){
3414 * Toss generic popup on to the screen
3416 if(sparms->mouse.popup)
3417 if((*sparms->mouse.popup)(sparms, 0) == 2){
3418 done++;
3421 #endif
3424 break;
3425 #endif /* MOUSE */
3428 /*-------------- Display Resize -------------*/
3429 case MC_RESIZE :
3430 if(sparms->resize_exit){
3431 long line;
3434 * Figure out char offset of the char in the top left
3435 * corner of the display. Pass it back to the
3436 * fetcher/formatter and have it pass the offset
3437 * back to us...
3439 sparms->start.on = Offset;
3440 for(sparms->start.loc.offset = line = 0L;
3441 line < cur_top_line;
3442 line++)
3443 sparms->start.loc.offset += scroll_handle_column(line, -1);
3445 done = 1;
3446 ClearLine(1);
3447 break;
3449 /* else no reformatting necessary, fall thru to repaint */
3452 /*-------------- refresh -------------*/
3453 case MC_REPAINT :
3454 num_display_lines = SCROLL_LINES(ps_global);
3455 mark_status_dirty();
3456 mark_keymenu_dirty();
3457 mark_titlebar_dirty();
3458 ps_global->mangled_screen = 1;
3459 force = 1;
3460 break;
3463 /*------- no op timeout to check for new mail ------*/
3464 case MC_NONE :
3465 break;
3468 /*------- Forward displayed text ------*/
3469 case MC_FWDTEXT :
3470 forward_text(ps_global, sparms->text.text, sparms->text.src);
3471 break;
3474 /*----------- Save the displayed text ------------*/
3475 case MC_SAVETEXT :
3476 (void)simple_export(ps_global, sparms->text.text,
3477 sparms->text.src, "text", NULL);
3478 break;
3481 /*----------- Exit this screen ------------*/
3482 case MC_EXIT :
3483 done = 1;
3484 break;
3487 /*----------- Pop back to the Main Menu ------------*/
3488 case MC_MAIN :
3489 ps_global->next_screen = main_menu_screen;
3490 done = 1;
3491 break;
3494 /*----------- Print ------------*/
3495 case MC_PRINTTXT :
3496 print_to_printer(sparms);
3497 break;
3500 /* ------- First handle on Line ------ */
3501 case MC_GOTOBOL :
3502 if(sparms->text.handles){
3503 next_handle = scroll_handle_boundary(sparms->text.handles,
3504 scroll_handle_prev_sel);
3506 break;
3508 /* fall thru as bogus */
3510 /* ------- Last handle on Line ------ */
3511 case MC_GOTOEOL :
3512 if(sparms->text.handles){
3513 next_handle = scroll_handle_boundary(sparms->text.handles,
3514 scroll_handle_next_sel);
3516 break;
3518 /* fall thru as bogus */
3520 /*------- BOGUS INPUT ------*/
3521 case MC_CHARRIGHT :
3522 case MC_CHARLEFT :
3523 case MC_UNKNOWN :
3524 if(sparms->bogus_input)
3525 done = (*sparms->bogus_input)(ch);
3526 else
3527 bogus_command(ch, F_ON(F_USE_FK,ps_global) ? "F1" : "?");
3529 break;
3532 case MC_UTF8:
3533 bogus_utf8_command(utf8str, F_ON(F_USE_FK, ps_global) ? "F1" : "?");
3534 break;
3537 /*------- Standard commands ------*/
3538 default:
3539 whereis_pos.row = 0;
3540 if(sparms->proc.tool)
3541 result = (*sparms->proc.tool)(cmd, ps_global->msgmap, sparms);
3542 else
3543 result = process_cmd(ps_global, ps_global->mail_stream,
3544 ps_global->msgmap, cmd, View, &force);
3546 dprint((7, "PROCESS_CMD return: %d\n", result));
3548 if(ps_global->next_screen != SCREEN_FUN_NULL || result == 1){
3549 done = 1;
3550 if(cmd == MC_FULLHDR){
3551 if(ps_global->full_header == 1){
3552 long line;
3555 * Figure out char offset of the char in the top left
3556 * corner of the display. Pass it back to the
3557 * fetcher/formatter and have it pass the offset
3558 * back to us...
3560 sparms->start.on = Offset;
3561 for(sparms->start.loc.offset = line = 0L;
3562 line < cur_top_line;
3563 line++)
3564 sparms->start.loc.offset +=
3565 scroll_handle_column(line, -1);
3567 else
3568 sparms->start.on = 0;
3570 switch(km->which){
3571 case 0:
3572 sparms->keys.what = FirstMenu;
3573 break;
3574 case 1:
3575 sparms->keys.what = SecondMenu;
3576 break;
3577 case 2:
3578 sparms->keys.what = ThirdMenu;
3579 break;
3580 case 3:
3581 sparms->keys.what = FourthMenu;
3582 break;
3586 else if(!scroll_state(SS_CUR)){
3587 num_display_lines = SCROLL_LINES(ps_global);
3588 ps_global->mangled_screen = 1;
3591 break;
3593 } /* End of switch() */
3595 /* Need to frame some handles? */
3596 if(sparms->text.handles
3597 && ((!next_handle
3598 && handle_on_page(sparms->text.handles, cur_top_line,
3599 cur_top_line + num_display_lines))
3600 || (next_handle
3601 && handle_on_page(next_handle, cur_top_line,
3602 cur_top_line + num_display_lines))))
3603 next_handle = scroll_handle_in_frame(cur_top_line);
3605 } /* End of while() -- loop executing commands */
3607 ps_global->redrawer = NULL; /* next statement makes this invalid! */
3608 zero_scroll_text(); /* very important to zero out on return!!! */
3609 scroll_state(SS_FREE);
3610 if(sparms->bar.color)
3611 free_color_pair(&sparms->bar.color);
3613 #ifdef _WINDOWS
3614 scroll_setrange(0L, 0L);
3615 #endif
3616 return(cmd);
3620 /*----------------------------------------------------------------------
3621 Print text on paper
3623 Args: text -- The text to print out
3624 source -- What type of source text is
3625 message -- Message for open_printer()
3626 Handling of error conditions is very poor.
3628 ----*/
3629 static int
3630 print_to_printer(SCROLL_S *sparms)
3632 char message[64];
3634 snprintf(message, sizeof(message), "%s", STYLE_NAME(sparms));
3635 message[sizeof(message)-1] = '\0';
3637 if(open_printer(message) != 0)
3638 return(-1);
3640 switch(sparms->text.src){
3641 case CharStar :
3642 if(sparms->text.text != (char *)NULL)
3643 print_text((char *)sparms->text.text);
3645 break;
3647 case CharStarStar :
3648 if(sparms->text.text != (char **)NULL){
3649 register char **t;
3651 for(t = sparms->text.text; *t != NULL; t++){
3652 print_text(*t);
3653 print_text(NEWLINE);
3657 break;
3659 case FileStar :
3660 if(sparms->text.text != (FILE *)NULL) {
3661 size_t n;
3662 int i;
3664 fseek((FILE *)sparms->text.text, 0L, 0);
3665 n = SIZEOF_20KBUF - 1;
3666 while((i = fread((void *)tmp_20k_buf, sizeof(char),
3667 n, (FILE *)sparms->text.text)) != 0) {
3668 tmp_20k_buf[i] = '\0';
3669 print_text(tmp_20k_buf);
3673 default :
3674 break;
3677 close_printer();
3678 return(0);
3682 /*----------------------------------------------------------------------
3683 Search text being viewed (help or message)
3685 Args: q_line -- The screen line to prompt for search string on
3686 start_line -- Line number in text to begin search on
3687 start_index -- Where to begin search at in first line of text
3688 cursor_pos -- position of cursor is returned to caller here
3689 (Actually, this isn't really the position of the
3690 cursor because we don't know where we are on the
3691 screen. So row is set to the line number and col
3692 is set to the right column.)
3693 offset_in_line -- Offset where match was found.
3695 Result: returns line number string was found on
3696 -1 for cancel
3697 -2 if not found
3698 -3 if only match is at start_index - 1
3699 -4 if search to first line
3700 -5 if search to last line
3701 ---*/
3703 search_text(int q_line, long int start_line, int start_index, char **report,
3704 Pos *cursor_pos, int *offset_in_line)
3706 char prompt[MAX_SEARCH+50], nsearch_string[MAX_SEARCH+1], *p;
3707 HelpType help;
3708 int rc, flags;
3709 static HISTORY_S *history = NULL;
3710 char search_string[MAX_SEARCH+1];
3711 static ESCKEY_S word_search_key[] = { { 0, 0, "", "" },
3712 {ctrl('Y'), 10, "^Y", N_("First Line")},
3713 {ctrl('V'), 11, "^V", N_("Last Line")},
3714 {KEY_UP, 30, "", ""},
3715 {KEY_DOWN, 31, "", ""},
3716 {-1, 0, NULL, NULL}
3718 #define KU_ST (3) /* index of KEY_UP */
3720 init_hist(&history, HISTSIZE);
3723 * Put the last one used in the default search_string,
3724 * not in nsearch_string.
3726 search_string[0] = '\0';
3727 if((p = get_prev_hist(history, "", 0, NULL)) != NULL){
3728 strncpy(search_string, p, sizeof(search_string));
3729 search_string[sizeof(search_string)-1] = '\0';
3732 snprintf(prompt, sizeof(prompt), _("Word to search for [%s] : "), search_string);
3733 help = NO_HELP;
3734 nsearch_string[0] = '\0';
3736 while(1) {
3737 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE | OE_KEEP_TRAILING_SPACE;
3740 * 2 is really 1 because there will be one real entry and
3741 * one entry of "" because of the get_prev_hist above.
3743 if(items_in_hist(history) > 2){
3744 word_search_key[KU_ST].name = HISTORY_UP_KEYNAME;
3745 word_search_key[KU_ST].label = HISTORY_KEYLABEL;
3746 word_search_key[KU_ST+1].name = HISTORY_DOWN_KEYNAME;
3747 word_search_key[KU_ST+1].label = HISTORY_KEYLABEL;
3749 else{
3750 word_search_key[KU_ST].name = "";
3751 word_search_key[KU_ST].label = "";
3752 word_search_key[KU_ST+1].name = "";
3753 word_search_key[KU_ST+1].label = "";
3756 rc = optionally_enter(nsearch_string, q_line, 0, sizeof(nsearch_string),
3757 prompt, word_search_key, help, &flags);
3759 if(rc == 3) {
3760 help = help == NO_HELP ? h_oe_searchview : NO_HELP;
3761 continue;
3763 else if(rc == 10){
3764 if(report)
3765 *report = _("Searched to First Line.");
3767 return(-4);
3769 else if(rc == 11){
3770 if(report)
3771 *report = _("Searched to Last Line.");
3773 return(-5);
3775 else if(rc == 30){
3776 if((p = get_prev_hist(history, nsearch_string, 0, NULL)) != NULL){
3777 strncpy(nsearch_string, p, sizeof(nsearch_string));
3778 nsearch_string[sizeof(nsearch_string)-1] = '\0';
3780 else
3781 Writechar(BELL, 0);
3783 continue;
3785 else if(rc == 31){
3786 if((p = get_next_hist(history, nsearch_string, 0, NULL)) != NULL){
3787 strncpy(nsearch_string, p, sizeof(nsearch_string));
3788 nsearch_string[sizeof(nsearch_string)-1] = '\0';
3790 else
3791 Writechar(BELL, 0);
3793 continue;
3796 if(rc != 4){ /* 4 is redraw */
3797 save_hist(history, nsearch_string, 0, NULL);
3798 break;
3802 if(rc == 1 || (search_string[0] == '\0' && nsearch_string[0] == '\0'))
3803 return(-1);
3805 if(nsearch_string[0] != '\0'){
3806 strncpy(search_string, nsearch_string, sizeof(search_string)-1);
3807 search_string[sizeof(search_string)-1] = '\0';
3810 rc = search_scroll_text(start_line, start_index, search_string, cursor_pos,
3811 offset_in_line);
3812 return(rc);
3816 /*----------------------------------------------------------------------
3817 Update the scroll tool's titlebar
3819 Args: cur_top_line --
3820 redraw -- flag to force updating
3822 ----*/
3823 void
3824 update_scroll_titlebar(long int cur_top_line, int redraw)
3826 SCRLCTRL_S *st = scroll_state(SS_CUR);
3827 int num_display_lines = SCROLL_LINES(ps_global);
3828 long new_line = (cur_top_line + num_display_lines > st->num_lines)
3829 ? st->num_lines
3830 : cur_top_line + num_display_lines;
3831 long raw_msgno;
3832 COLOR_PAIR *returned_color = NULL;
3833 COLOR_PAIR *titlecolor = NULL;
3834 int colormatch;
3835 SEARCHSET *ss = NULL;
3837 if(st->parms->use_indexline_color
3838 && ps_global->titlebar_color_style != TBAR_COLOR_DEFAULT){
3839 raw_msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
3840 if(raw_msgno > 0L && ps_global->mail_stream
3841 && raw_msgno <= ps_global->mail_stream->nmsgs){
3842 ss = mail_newsearchset();
3843 ss->first = ss->last = (unsigned long) raw_msgno;
3846 if(ss){
3847 PAT_STATE *pstate = NULL;
3849 colormatch = get_index_line_color(ps_global->mail_stream,
3850 ss, &pstate, &returned_color);
3851 mail_free_searchset(&ss);
3854 * This is a bit tricky. If there is a colormatch but returned_color
3855 * is NULL, that means that the user explicitly wanted the
3856 * Normal color used in this index line, so that is what we
3857 * use. If no colormatch then we will use the TITLE color
3858 * instead of Normal.
3860 if(colormatch){
3861 if(returned_color)
3862 titlecolor = returned_color;
3863 else
3864 titlecolor = new_color_pair(ps_global->VAR_NORM_FORE_COLOR,
3865 ps_global->VAR_NORM_BACK_COLOR);
3868 if(titlecolor
3869 && ps_global->titlebar_color_style == TBAR_COLOR_REV_INDEXLINE){
3870 char cbuf[MAXCOLORLEN+1];
3872 strncpy(cbuf, titlecolor->fg, MAXCOLORLEN);
3873 strncpy(titlecolor->fg, titlecolor->bg, MAXCOLORLEN);
3874 strncpy(titlecolor->bg, cbuf, MAXCOLORLEN);
3878 /* Did the color change? */
3879 if((!titlecolor && st->parms->bar.color)
3881 (titlecolor && !st->parms->bar.color)
3883 (titlecolor && st->parms->bar.color
3884 && (strcmp(titlecolor->fg, st->parms->bar.color->fg)
3885 || strcmp(titlecolor->bg, st->parms->bar.color->bg)))){
3887 redraw++;
3888 if(st->parms->bar.color)
3889 free_color_pair(&st->parms->bar.color);
3891 st->parms->bar.color = titlecolor;
3892 titlecolor = NULL;
3895 if(titlecolor)
3896 free_color_pair(&titlecolor);
3900 if(redraw){
3901 set_titlebar(st->parms->bar.title, ps_global->mail_stream,
3902 ps_global->context_current, ps_global->cur_folder,
3903 ps_global->msgmap, 1, st->parms->bar.style,
3904 new_line, st->num_lines, st->parms->bar.color);
3905 ps_global->mangled_header = 0;
3907 else if(st->parms->bar.style == TextPercent)
3908 update_titlebar_lpercent(new_line);
3909 else
3910 update_titlebar_percent(new_line);
3914 /*----------------------------------------------------------------------
3915 manager of global (to this module, anyway) scroll state structures
3918 ----*/
3919 SCRLCTRL_S *
3920 scroll_state(int func)
3922 struct scrollstack {
3923 SCRLCTRL_S s;
3924 struct scrollstack *prev;
3925 } *s;
3926 static struct scrollstack *stack = NULL;
3928 switch(func){
3929 case SS_CUR: /* no op */
3930 break;
3931 case SS_NEW:
3932 s = (struct scrollstack *)fs_get(sizeof(struct scrollstack));
3933 memset((void *)s, 0, sizeof(struct scrollstack));
3934 s->prev = stack;
3935 stack = s;
3936 break;
3937 case SS_FREE:
3938 if(stack){
3939 s = stack->prev;
3940 fs_give((void **)&stack);
3941 stack = s;
3943 break;
3944 default: /* BUG: should complain */
3945 break;
3948 return(stack ? &stack->s : NULL);
3952 /*----------------------------------------------------------------------
3953 Save all the data for scrolling text and paint the screen
3956 ----*/
3957 void
3958 set_scroll_text(SCROLL_S *sparms, long int current_line, SCRLCTRL_S *st)
3960 /* save all the stuff for possible asynchronous redraws */
3961 st->parms = sparms;
3962 st->top_text_line = current_line;
3963 st->screen.start_line = SCROLL_LINES_ABOVE(ps_global);
3964 st->screen.other_lines = SCROLL_LINES_ABOVE(ps_global)
3965 + SCROLL_LINES_BELOW(ps_global);
3966 st->screen.width = -1; /* Force text formatting calculation */
3970 /*----------------------------------------------------------------------
3971 Redraw the text on the screen, possibly reformatting if necessary
3973 Args None
3975 ----*/
3976 void
3977 redraw_scroll_text(void)
3979 int i, offset;
3980 SCRLCTRL_S *st = scroll_state(SS_CUR);
3982 format_scroll_text();
3984 offset = (st->parms->text.src == FileStar) ? 0 : st->top_text_line;
3986 #ifdef _WINDOWS
3987 mswin_beginupdate();
3988 #endif
3989 /*---- Actually display the text on the screen ------*/
3990 for(i = 0; i < st->screen.length; i++){
3991 ClearLine(i + st->screen.start_line);
3992 if((offset + i) < st->num_lines)
3993 PutLine0n8b(i + st->screen.start_line, 0, st->text_lines[offset + i],
3994 st->line_lengths[offset + i], st->parms->text.handles);
3998 fflush(stdout);
3999 #ifdef _WINDOWS
4000 mswin_endupdate();
4001 #endif
4005 /*----------------------------------------------------------------------
4006 Free memory used as scrolling buffers for text on disk. Also mark
4007 text_lines as available
4008 ----*/
4009 void
4010 zero_scroll_text(void)
4012 SCRLCTRL_S *st = scroll_state(SS_CUR);
4013 register int i;
4015 for(i = 0; i < st->lines_allocated; i++)
4016 if(st->parms->text.src == FileStar && st->text_lines[i])
4017 fs_give((void **)&st->text_lines[i]);
4018 else
4019 st->text_lines[i] = NULL;
4021 if(st->parms->text.src == FileStar && st->findex != NULL){
4022 fclose(st->findex);
4023 st->findex = NULL;
4024 if(st->fname){
4025 our_unlink(st->fname);
4026 fs_give((void **)&st->fname);
4030 if(st->text_lines)
4031 fs_give((void **)&st->text_lines);
4033 if(st->line_lengths)
4034 fs_give((void **) &st->line_lengths);
4038 /*----------------------------------------------------------------------
4040 Always format at least 20 chars wide. Wrapping lines would be crazy for
4041 screen widths of 1-20 characters
4042 ----*/
4043 void
4044 format_scroll_text(void)
4046 int i;
4047 char *p, **pp;
4048 SCRLCTRL_S *st = scroll_state(SS_CUR);
4049 register short *ll;
4050 register char **tl, **tl_end;
4052 if(!st || (st->screen.width == (i = ps_global->ttyo->screen_cols)
4053 && st->screen.length == PGSIZE(st)))
4054 return;
4056 st->screen.width = MAX(20, i);
4057 st->screen.length = PGSIZE(st);
4059 if(st->lines_allocated == 0) {
4060 st->lines_allocated = TYPICAL_BIG_MESSAGE_LINES;
4061 st->text_lines = (char **)fs_get(st->lines_allocated *sizeof(char *));
4062 memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
4063 st->line_lengths = (short *)fs_get(st->lines_allocated *sizeof(short));
4066 tl = st->text_lines;
4067 ll = st->line_lengths;
4068 tl_end = &st->text_lines[st->lines_allocated];
4070 if(st->parms->text.src == CharStarStar) {
4071 /*---- original text is already list of lines -----*/
4072 /* The text could be wrapped nicely for narrow screens; for now
4073 it will get truncated as it is displayed */
4074 for(pp = (char **)st->parms->text.text; *pp != NULL;) {
4075 *tl++ = *pp++;
4076 *ll++ = st->screen.width;
4077 if(tl >= tl_end) {
4078 i = tl - st->text_lines;
4079 st->lines_allocated *= 2;
4080 fs_resize((void **)&st->text_lines,
4081 st->lines_allocated * sizeof(char *));
4082 fs_resize((void **)&st->line_lengths,
4083 st->lines_allocated*sizeof(short));
4084 tl = &st->text_lines[i];
4085 ll = &st->line_lengths[i];
4086 tl_end = &st->text_lines[st->lines_allocated];
4090 st->num_lines = tl - st->text_lines;
4092 else if (st->parms->text.src == CharStar) {
4093 /*------ Format the plain text ------*/
4094 for(p = (char *)st->parms->text.text; *p; ) {
4095 *tl = p;
4097 for(; *p && !(*p == RETURN || *p == LINE_FEED); p++)
4100 *ll = p - *tl;
4101 ll++; tl++;
4102 if(tl >= tl_end) {
4103 i = tl - st->text_lines;
4104 st->lines_allocated *= 2;
4105 fs_resize((void **)&st->text_lines,
4106 st->lines_allocated * sizeof(char *));
4107 fs_resize((void **)&st->line_lengths,
4108 st->lines_allocated*sizeof(short));
4109 tl = &st->text_lines[i];
4110 ll = &st->line_lengths[i];
4111 tl_end = &st->text_lines[st->lines_allocated];
4114 if(*p == '\r' && *(p+1) == '\n')
4115 p += 2;
4116 else if(*p == '\n' || *p == '\r')
4117 p++;
4120 st->num_lines = tl - st->text_lines;
4122 else {
4123 /*------ Display text is in a file --------*/
4126 * This is pretty much only useful under DOS where we can't fit
4127 * all of big messages in core at once. This scheme makes
4128 * some simplifying assumptions:
4129 * 1. Lines are on disk just the way we'll display them. That
4130 * is, line breaks and such are left to the function that
4131 * writes the disk file to catch and fix.
4132 * 2. We get away with this mainly because the DOS display isn't
4133 * going to be resized out from under us.
4135 * The idea is to use the already alloc'd array of char * as a
4136 * buffer for sections of what's on disk. We'll set up the first
4137 * few lines here, and read new ones in as needed in
4138 * scroll_scroll_text().
4140 * but first, make sure there are enough buffer lines allocated
4141 * to serve as a place to hold lines from the file.
4143 * Actually, this is also used under windows so the display will
4144 * be resized out from under us. So I changed the following
4145 * to always
4146 * 1. free old text_lines, which may have been allocated
4147 * for a narrow screen.
4148 * 2. insure we have enough text_lines
4149 * 3. reallocate all text_lines that are needed.
4150 * (tom unger 10/26/94)
4153 /* free old text lines, which may be too short. */
4154 for(i = 0; i < st->lines_allocated; i++)
4155 if(st->text_lines[i]) /* clear alloc'd lines */
4156 fs_give((void **)&st->text_lines[i]);
4158 /* Insure we have enough text lines. */
4159 if(st->lines_allocated < (2 * PGSIZE(st)) + 1){
4160 st->lines_allocated = (2 * PGSIZE(st)) + 1; /* resize */
4162 fs_resize((void **)&st->text_lines,
4163 st->lines_allocated * sizeof(char *));
4164 memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
4165 fs_resize((void **)&st->line_lengths,
4166 st->lines_allocated*sizeof(short));
4169 /* reallocate all text lines that are needed. */
4170 for(i = 0; i <= PGSIZE(st); i++)
4171 if(st->text_lines[i] == NULL)
4172 st->text_lines[i] = (char *)fs_get((st->screen.width + 1)
4173 * sizeof(char));
4175 tl = &st->text_lines[i];
4177 st->num_lines = make_file_index();
4179 ScrollFile(st->top_text_line); /* then load them up */
4183 * Efficiency hack. If there are handles, fill in their
4184 * line number field for later...
4186 if(st->parms->text.handles){
4187 long line;
4188 int i, col, n, key;
4189 HANDLE_S *h;
4191 for(line = 0; line < st->num_lines; line++)
4192 for(i = 0, col = 0; i < st->line_lengths[line];)
4193 switch(st->text_lines[line][i]){
4194 case TAG_EMBED:
4195 i++;
4196 switch((i < st->line_lengths[line]) ? st->text_lines[line][i]
4197 : 0){
4198 case TAG_HANDLE:
4199 for(key = 0, n = st->text_lines[line][++i]; n > 0; n--)
4200 key = (key * 10) + (st->text_lines[line][++i] - '0');
4202 i++;
4203 for(h = st->parms->text.handles; h; h = h->next)
4204 if(h->key == key){
4205 scroll_handle_set_loc(&h->loc, line, col);
4206 break;
4209 if(!h) /* anything behind us? */
4210 for(h = st->parms->text.handles->prev; h; h = h->prev)
4211 if(h->key == key){
4212 scroll_handle_set_loc(&h->loc, line, col);
4213 break;
4216 break;
4218 case TAG_FGCOLOR :
4219 case TAG_BGCOLOR :
4220 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
4221 break;
4223 case TAG_INVON:
4224 case TAG_INVOFF:
4225 case TAG_BOLDON:
4226 case TAG_BOLDOFF:
4227 case TAG_ULINEON:
4228 case TAG_ULINEOFF:
4229 i++;
4230 break;
4232 default: /* literal embed char */
4233 break;
4236 break;
4238 case TAB:
4239 i++;
4240 while(((++col) & 0x07) != 0) /* add tab's spaces */
4243 break;
4245 default:
4246 col += width_at_this_position((unsigned char*) &st->text_lines[line][i],
4247 st->line_lengths[line] - i);
4248 i++; /* character count */
4249 break;
4253 #ifdef _WINDOWS
4254 scroll_setrange (st->screen.length, st->num_lines);
4255 #endif
4257 *tl = NULL;
4262 * ScrollFile - scroll text into the st struct file making sure 'line'
4263 * of the file is the one first in the text_lines buffer.
4265 * NOTE: talk about massive potential for tuning...
4266 * Goes without saying this is still under construction
4268 void
4269 ScrollFile(long int line)
4271 SCRLCTRL_S *st = scroll_state(SS_CUR);
4272 SCRLFILE_S sf;
4273 register int i;
4275 if(line <= 0){ /* reset and load first couple of pages */
4276 fseek((FILE *) st->parms->text.text, 0L, 0);
4277 line = 0L;
4280 if(!st->text_lines)
4281 return;
4283 for(i = 0; i < PGSIZE(st); i++){
4284 /*** do stuff to get the file pointer into the right place ***/
4286 * BOGUS: this is painfully crude right now, but I just want to get
4287 * it going.
4289 * possibly in the near future, an array of indexes into the
4290 * file that are the offset for the beginning of each line will
4291 * speed things up. Of course, this
4292 * will have limits, so maybe a disk file that is an array
4293 * of indexes is the answer.
4295 if(fseek(st->findex, (size_t)(line++) * sizeof(SCRLFILE_S), 0) < 0
4296 || fread(&sf, sizeof(SCRLFILE_S), (size_t)1, st->findex) != 1
4297 || fseek((FILE *) st->parms->text.text, sf.offset, 0) < 0
4298 || !st->text_lines[i]
4299 || (sf.len && !fgets(st->text_lines[i], sf.len + 1,
4300 (FILE *) st->parms->text.text)))
4301 break;
4303 st->line_lengths[i] = sf.len;
4306 for(; i < PGSIZE(st); i++)
4307 if(st->text_lines[i]){ /* blank out any unused lines */
4308 *st->text_lines[i] = '\0';
4309 st->line_lengths[i] = 0;
4315 * make_file_index - do a single pass over the file containing the text
4316 * to display, recording line lengths and offsets.
4317 * NOTE: This is never really to be used on a real OS with virtual
4318 * memory. This is the whole reason st->findex exists. Don't
4319 * want to waste precious memory on a stupid array that could
4320 * be very large.
4322 long
4323 make_file_index(void)
4325 SCRLCTRL_S *st = scroll_state(SS_CUR);
4326 SCRLFILE_S sf;
4327 long l = 0L;
4328 int state = 0;
4330 if(!st->findex){
4331 if(!st->fname)
4332 st->fname = temp_nam(NULL, "pi");
4334 if(!st->fname || (st->findex = our_fopen(st->fname,"w+b")) == NULL){
4335 if(st->fname){
4336 our_unlink(st->fname);
4337 fs_give((void **)&st->fname);
4340 return(0);
4343 else
4344 fseek(st->findex, 0L, 0);
4346 fseek((FILE *)st->parms->text.text, 0L, 0);
4348 while(1){
4349 sf.len = st->screen.width + 1;
4350 if(scroll_file_line((FILE *) st->parms->text.text,
4351 tmp_20k_buf, &sf, &state)){
4352 fwrite((void *) &sf, sizeof(SCRLFILE_S), (size_t)1, st->findex);
4353 l++;
4355 else
4356 break;
4359 fseek((FILE *)st->parms->text.text, 0L, 0);
4361 return(l);
4365 /*----------------------------------------------------------------------
4366 Get the next line to scroll from the given file
4368 ----*/
4369 char *
4370 scroll_file_line(FILE *fp, char *buf, SCRLFILE_S *sfp, int *wrapt)
4372 register char *s = NULL;
4374 while(1){
4375 if(!s){
4376 sfp->offset = ftell(fp);
4377 if(!(s = fgets(buf, sfp->len, fp)))
4378 return(NULL); /* can't grab a line? */
4381 if(!*s){
4382 *wrapt = 1; /* remember; that we wrapped */
4383 break;
4385 else if(*s == NEWLINE[0] && (!NEWLINE[1] || *(s+1) == NEWLINE[1])){
4386 int empty = (*wrapt && s == buf);
4388 *wrapt = 0; /* turn off wrapped state */
4389 if(empty)
4390 s = NULL; /* get a new line */
4391 else
4392 break; /* done! */
4394 else
4395 s++;
4398 sfp->len = s - buf;
4399 return(buf);
4403 /*----------------------------------------------------------------------
4404 Scroll the text on the screen
4406 Args: new_top_line -- The line to be displayed on top of the screen
4407 redraw -- Flag to force a redraw even if nothing changed
4409 Returns: resulting top line
4410 Note: the returned line number may be less than new_top_line if
4411 reformatting caused the total line count to change.
4413 ----*/
4414 long
4415 scroll_scroll_text(long int new_top_line, HANDLE_S *handle, int redraw)
4417 SCRLCTRL_S *st = scroll_state(SS_CUR);
4418 int num_display_lines, l, top;
4419 POSLIST_S *lp, *lp2;
4421 /* When this is true, we're still on the same page of the display. */
4422 if(st->top_text_line == new_top_line && !redraw){
4423 /* handle changed, so hilite the new handle and unhilite the old */
4424 if(handle && handle != st->parms->text.handles){
4425 top = st->screen.start_line - new_top_line;
4426 /* hilite the new one */
4427 if(!scroll_handle_obscured(handle))
4428 for(lp = handle->loc; lp; lp = lp->next)
4429 if((l = lp->where.row) >= st->top_text_line
4430 && l < st->top_text_line + st->screen.length){
4431 if(st->parms->text.src == FileStar)
4432 l -= new_top_line;
4434 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4435 st->line_lengths[l], handle);
4438 /* unhilite the old one */
4439 if(!scroll_handle_obscured(st->parms->text.handles))
4440 for(lp = st->parms->text.handles->loc; lp; lp = lp->next)
4441 if((l = lp->where.row) >= st->top_text_line
4442 && l < st->top_text_line + st->screen.length){
4443 for(lp2 = handle->loc; lp2; lp2 = lp2->next)
4444 if(l == lp2->where.row)
4445 break;
4447 if(!lp2){
4448 if(st->parms->text.src == FileStar)
4449 l -= new_top_line;
4451 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4452 st->line_lengths[l], handle);
4456 st->parms->text.handles = handle; /* update current */
4459 return(new_top_line);
4462 num_display_lines = PGSIZE(st);
4464 format_scroll_text();
4466 if(st->top_text_line >= st->num_lines) /* don't pop line count */
4467 new_top_line = st->top_text_line = MAX(st->num_lines - 1, 0);
4469 if(st->parms->text.src == FileStar)
4470 ScrollFile(new_top_line); /* set up new st->text_lines */
4472 #ifdef _WINDOWS
4473 scroll_setrange (st->screen.length, st->num_lines);
4474 scroll_setpos (new_top_line);
4475 #endif
4477 /* ---
4478 Check out the scrolling situation. If we want to scroll, but BeginScroll
4479 says we can't then repaint, + 10 is so we repaint most of the time.
4480 ----*/
4481 if(redraw ||
4482 (st->top_text_line - new_top_line + 10 >= num_display_lines ||
4483 new_top_line - st->top_text_line + 10 >= num_display_lines) ||
4484 BeginScroll(st->screen.start_line,
4485 st->screen.start_line + num_display_lines - 1) != 0) {
4486 /* Too much text to scroll, or can't scroll -- just repaint */
4488 if(handle)
4489 st->parms->text.handles = handle;
4491 st->top_text_line = new_top_line;
4492 redraw_scroll_text();
4494 else{
4496 * We're going to scroll the screen, but first we have to make sure
4497 * the old hilited handles are unhilited if they are going to remain
4498 * on the screen.
4500 top = st->screen.start_line - st->top_text_line;
4501 if(handle && handle != st->parms->text.handles
4502 && st->parms->text.handles
4503 && !scroll_handle_obscured(st->parms->text.handles))
4504 for(lp = st->parms->text.handles->loc; lp; lp = lp->next)
4505 if((l = lp->where.row) >= MAX(st->top_text_line,new_top_line)
4506 && l < MIN(st->top_text_line,new_top_line) + st->screen.length){
4507 if(st->parms->text.src == FileStar)
4508 l -= new_top_line;
4510 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4511 st->line_lengths[l], handle);
4514 if(new_top_line > st->top_text_line){
4515 /*------ scroll down ------*/
4516 while(new_top_line > st->top_text_line) {
4517 ScrollRegion(1);
4519 l = (st->parms->text.src == FileStar)
4520 ? num_display_lines - (new_top_line - st->top_text_line)
4521 : st->top_text_line + num_display_lines;
4523 if(l < st->num_lines){
4524 PutLine0n8b(st->screen.start_line + num_display_lines - 1,
4525 0, st->text_lines[l], st->line_lengths[l],
4526 handle ? handle : st->parms->text.handles);
4528 * We clear to the end of line in the right background
4529 * color. If the line was exactly the width of the screen
4530 * then PutLine0n8b will have left _col and _row moved to
4531 * the start of the next row. We don't need or want to clear
4532 * that next row.
4534 if(pico_usingcolor()
4535 && (st->line_lengths[l] < ps_global->ttyo->screen_cols
4536 || visible_linelen(l) < ps_global->ttyo->screen_cols))
4537 CleartoEOLN();
4540 st->top_text_line++;
4543 else{
4544 /*------ scroll up -----*/
4545 while(new_top_line < st->top_text_line) {
4546 ScrollRegion(-1);
4548 st->top_text_line--;
4549 l = (st->parms->text.src == FileStar)
4550 ? st->top_text_line - new_top_line
4551 : st->top_text_line;
4552 PutLine0n8b(st->screen.start_line, 0, st->text_lines[l],
4553 st->line_lengths[l],
4554 handle ? handle : st->parms->text.handles);
4556 * We clear to the end of line in the right background
4557 * color. If the line was exactly the width of the screen
4558 * then PutLine0n8b will have left _col and _row moved to
4559 * the start of the next row. We don't need or want to clear
4560 * that next row.
4562 if(pico_usingcolor()
4563 && (st->line_lengths[l] < ps_global->ttyo->screen_cols
4564 || visible_linelen(l) < ps_global->ttyo->screen_cols))
4565 CleartoEOLN();
4569 EndScroll();
4571 if(handle && handle != st->parms->text.handles){
4572 POSLIST_S *lp;
4574 for(lp = handle->loc; lp; lp = lp->next)
4575 if(lp->where.row >= st->top_text_line
4576 && lp->where.row < st->top_text_line + st->screen.length){
4577 PutLine0n8b(st->screen.start_line
4578 + (lp->where.row - st->top_text_line),
4579 0, st->text_lines[lp->where.row],
4580 st->line_lengths[lp->where.row],
4581 handle);
4585 st->parms->text.handles = handle;
4588 fflush(stdout);
4591 return(new_top_line);
4595 /*---------------------------------------------------------------------
4596 Edit individual char in text so that the entire text doesn't need
4597 to be completely reformatted.
4599 Returns 0 if there were no errors, 1 if we would like the entire
4600 text to be reformatted.
4601 ----*/
4603 ng_scroll_edit(CONTEXT_S *context, int index)
4605 SCRLCTRL_S *st = scroll_state(SS_CUR);
4606 char *ngp, tmp[MAILTMPLEN+10];
4607 int len;
4608 FOLDER_S *f;
4610 if (!(f = folder_entry(index, FOLDERS(context))))
4611 return 1;
4612 if(f->subscribed)
4613 return 0; /* nothing in scroll needs to be changed */
4614 tmp[0] = TAG_HANDLE;
4615 snprintf(tmp+2, sizeof(tmp)-2, "%d", st->parms->text.handles->key);
4616 tmp[sizeof(tmp)-1] = '\0';
4617 tmp[1] = len = strlen(tmp+2);
4618 snprintf(tmp+len+2, sizeof(tmp)-(len+2), "%s ", f->selected ? "[ ]" : "[X]");
4619 tmp[sizeof(tmp)-1] = '\0';
4620 snprintf(tmp+len+6, sizeof(tmp)-(len+6), "%.*s", MAILTMPLEN, f->name);
4621 tmp[sizeof(tmp)-1] = '\0';
4623 ngp = *(st->text_lines);
4625 ngp = strstr(ngp, tmp);
4627 if(!ngp) return 1;
4628 ngp += 3+len;
4630 /* assumption that text is of form "[ ] xxx.xxx" */
4632 if(ngp){
4633 if(*ngp == 'X'){
4634 *ngp = ' ';
4635 return 0;
4637 else if (*ngp == ' '){
4638 *ngp = 'X';
4639 return 0;
4642 return 1;
4646 /*---------------------------------------------------------------------
4647 Similar to ng_scroll_edit, but this is the more general case of
4648 selecting a folder, as opposed to selecting a newsgroup for
4649 subscription while in listmode.
4651 Returns 0 if there were no errors, 1 if we would like the entire
4652 text to be reformatted.
4653 ----*/
4655 folder_select_update(CONTEXT_S *context, int index)
4657 SCRLCTRL_S *st = scroll_state(SS_CUR);
4658 FOLDER_S *f;
4659 char *ngp, tmp[MAILTMPLEN+10];
4660 int len, total, fnum, num_sel = 0;
4662 if (!(f = folder_entry(index, FOLDERS(context))))
4663 return 1;
4664 ngp = *(st->text_lines);
4666 total = folder_total(FOLDERS(context));
4668 for (fnum = 0; num_sel < 2 && fnum < total; fnum++)
4669 if(folder_entry(fnum, FOLDERS(context))->selected)
4670 num_sel++;
4671 if(!num_sel || (f->selected && num_sel == 1))
4672 return 1; /* need to reformat the whole thing */
4674 tmp[0] = TAG_HANDLE;
4675 snprintf(tmp+2, sizeof(tmp)-2, "%d", st->parms->text.handles->key);
4676 tmp[sizeof(tmp)-1] = '\0';
4677 tmp[1] = len = strlen(tmp+2);
4679 ngp = strstr(ngp, tmp);
4680 if(!ngp) return 1;
4682 if(F_ON(F_SELECTED_SHOWN_BOLD, ps_global)){
4683 ngp += 2 + len;
4684 while(*ngp && ngp[0] != TAG_EMBED
4685 && ngp[1] != (f->selected ? TAG_BOLDOFF : TAG_BOLDON)
4686 && *ngp != *(f->name))
4687 ngp++;
4689 if (!(*ngp) || (*ngp == *(f->name)))
4690 return 1;
4691 else {
4692 ngp++;
4693 *ngp = (f->selected ? TAG_BOLDON : TAG_BOLDOFF);
4694 return 0;
4697 else{
4698 while(*ngp != ' ' && *ngp != *(f->name) && *ngp)
4699 ngp++;
4700 if(!(*ngp) || (*ngp == *(f->name)))
4701 return 1;
4702 else {
4703 ngp++;
4704 *ngp = f->selected ? 'X' : ' ';
4705 return 0;
4711 /*---------------------------------------------------------------------
4712 We gotta go through all of the formatted text and add "[ ] " in the right
4713 place. If we don't do this, we must completely reformat the whole text,
4714 which could take a very long time.
4716 Return 1 if we encountered some sort of error and we want to reformat the
4717 whole text, return 0 if everything went as planned.
4719 ASSUMPTION: for this to work, we assume that there are only total
4720 number of handles, numbered 1 through total.
4721 ----*/
4723 scroll_add_listmode(CONTEXT_S *context, int total)
4725 SCRLCTRL_S *st = scroll_state(SS_CUR);
4726 long i;
4727 char *ngp, *ngname, handle_str[MAILTMPLEN];
4728 HANDLE_S *h;
4731 ngp = *(st->text_lines);
4732 h = st->parms->text.handles;
4734 while(h && h->key != 1 && h->prev)
4735 h = h->prev;
4736 if (!h) return 1;
4737 handle_str[0] = TAG_EMBED;
4738 handle_str[1] = TAG_HANDLE;
4739 for(i = 1; i <= total && h; i++, h = h->next){
4740 snprintf(handle_str+3, sizeof(handle_str)-3, "%d", h->key);
4741 handle_str[sizeof(handle_str)-1] = '\0';
4742 handle_str[2] = strlen(handle_str+3);
4743 ngp = strstr(ngp, handle_str);
4744 if(!ngp){
4745 ngp = *(st->text_lines);
4746 if (!ngp)
4747 return 1;
4749 ngname = ngp + strlen(handle_str);
4750 while (strncmp(ngp, " ", 4) && !(*ngp == '\n')
4751 && !(ngp == *(st->text_lines)))
4752 ngp--;
4753 if (strncmp(ngp, " ", 4))
4754 return 1;
4755 while(ngp+4 != ngname && *ngp){
4756 ngp[0] = ngp[4];
4757 ngp++;
4760 if(folder_entry(h->h.f.index, FOLDERS(context))->subscribed){
4761 ngp[0] = 'S';
4762 ngp[1] = 'U';
4763 ngp[2] = 'B';
4765 else{
4766 ngp[0] = '[';
4767 ngp[1] = ' ';
4768 ngp[2] = ']';
4770 ngp[3] = ' ';
4773 return 0;
4778 /*----------------------------------------------------------------------
4779 Search the set scrolling text
4781 Args: start_line -- line to start searching on
4782 start_index -- column to start searching at in first line
4783 word -- string to search for
4784 cursor_pos -- position of cursor is returned to caller here
4785 (Actually, this isn't really the position of the
4786 cursor because we don't know where we are on the
4787 screen. So row is set to the line number and col
4788 is set to the right column.)
4789 offset_in_line -- Offset where match was found.
4791 Returns: the line the word was found on, or -2 if it wasn't found, or
4792 -3 if the only match is at column start_index - 1.
4794 ----*/
4796 search_scroll_text(long int start_line, int start_index, char *word,
4797 Pos *cursor_pos, int *offset_in_line)
4799 SCRLCTRL_S *st = scroll_state(SS_CUR);
4800 char *wh;
4801 long l, offset, dlines;
4802 #define SROW(N) ((N) - offset)
4803 #define SLINE(N) st->text_lines[SROW(N)]
4804 #define SLEN(N) st->line_lengths[SROW(N)]
4806 dlines = PGSIZE(st);
4807 offset = (st->parms->text.src == FileStar) ? st->top_text_line : 0;
4809 if(start_line < st->num_lines){
4810 /* search first line starting at position start_index in */
4811 if((wh = search_scroll_line(SLINE(start_line) + start_index,
4812 word,
4813 SLEN(start_line) - start_index,
4814 st->parms->text.handles != NULL)) != NULL){
4815 cursor_pos->row = start_line;
4816 cursor_pos->col = scroll_handle_column(SROW(start_line),
4817 *offset_in_line = wh - SLINE(start_line));
4818 return(start_line);
4821 if(st->parms->text.src == FileStar)
4822 offset++;
4824 for(l = start_line + 1; l < st->num_lines; l++) {
4825 if(st->parms->text.src == FileStar && l > offset + dlines)
4826 ScrollFile(offset += dlines);
4828 if((wh = search_scroll_line(SLINE(l), word, SLEN(l),
4829 st->parms->text.handles != NULL)) != NULL){
4830 cursor_pos->row = l;
4831 cursor_pos->col = scroll_handle_column(SROW(l),
4832 *offset_in_line = wh - SLINE(l));
4833 return(l);
4837 else
4838 start_line = st->num_lines;
4840 if(st->parms->text.src == FileStar) /* wrap offset */
4841 ScrollFile(offset = 0);
4843 for(l = 0; l < start_line; l++) {
4844 if(st->parms->text.src == FileStar && l > offset + dlines)
4845 ScrollFile(offset += dlines);
4847 if((wh = search_scroll_line(SLINE(l), word, SLEN(l),
4848 st->parms->text.handles != NULL)) != NULL){
4849 cursor_pos->row = l;
4850 cursor_pos->col = scroll_handle_column(SROW(l),
4851 *offset_in_line = wh - SLINE(l));
4852 return(l);
4856 /* search in current line */
4857 if(start_line < st->num_lines
4858 && (wh = search_scroll_line(SLINE(start_line), word,
4859 start_index + strlen(word) - 2,
4860 st->parms->text.handles != NULL)) != NULL){
4861 cursor_pos->row = start_line;
4862 cursor_pos->col = scroll_handle_column(SROW(start_line),
4863 *offset_in_line = wh - SLINE(start_line));
4865 return(start_line);
4868 /* see if the only match is a repeat */
4869 if(start_index > 0 && start_line < st->num_lines
4870 && (wh = search_scroll_line(
4871 SLINE(start_line) + start_index - 1,
4872 word, strlen(word),
4873 st->parms->text.handles != NULL)) != NULL){
4874 cursor_pos->row = start_line;
4875 cursor_pos->col = scroll_handle_column(SROW(start_line),
4876 *offset_in_line = wh - SLINE(start_line));
4877 return(-3);
4880 return(-2);
4884 /*----------------------------------------------------------------------
4885 Search one line of scroll text for given string
4887 Args: haystack -- The string to search in, the larger string
4888 needle -- The string to search for, the smaller string
4889 n -- The max number of chars in haystack to search
4891 Search for first occurrence of needle in the haystack, and return a pointer
4892 into the string haystack when it is found. The search is case independent.
4893 ----*/
4894 char *
4895 search_scroll_line(char *haystack, char *needle, int n, int handles)
4897 char *return_ptr = NULL, *found_it = NULL;
4898 char *haystack_copy, *p, *free_this = NULL, *end;
4899 char buf[1000];
4900 int state = 0, i = 0;
4902 if(n > 0 && haystack){
4903 if(n < sizeof(buf))
4904 haystack_copy = buf;
4905 else
4906 haystack_copy = free_this = (char *) fs_get((n+1) * sizeof(char));
4908 strncpy(haystack_copy, haystack, n);
4909 haystack_copy[n] = '\0';
4912 * We don't want to match text inside embedded tags.
4913 * Replace embedded octets with nulls and convert
4914 * uppercase ascii to lowercase. We should also do
4915 * some sort of canonicalization of UTF-8 but that
4916 * sounds daunting.
4918 for(i = n, p = haystack_copy; i-- > 0 && *p; p++){
4919 if(handles)
4920 switch(state){
4921 case 0 :
4922 if(*p == TAG_EMBED){
4923 *p = '\0';
4924 state = -1;
4925 continue;
4927 else{
4928 /* lower case just ascii chars */
4929 if(!(*p & 0x80) && isupper(*p))
4930 *p = tolower(*p);
4933 break;
4935 case -1 :
4936 state = (*p == TAG_HANDLE)
4937 ? -2
4938 : (*p == TAG_FGCOLOR || *p == TAG_BGCOLOR) ? RGBLEN : 0;
4939 *p = '\0';
4940 continue;
4942 case -2 :
4943 state = *p; /* length of handle's key */
4944 *p = '\0';
4945 continue;
4947 default :
4948 state--;
4949 *p = '\0';
4950 continue;
4955 * The haystack_copy string now looks like
4957 * "chars\0\0\0\0\0\0chars...\0\0\0chars... \0"
4959 * with that final \0 at haystack_copy[n].
4960 * Search each piece one at a time.
4963 end = haystack_copy + n;
4964 p = haystack_copy;
4966 while(p < end && !return_ptr){
4968 /* skip nulls */
4969 while(*p == '\0' && p < end)
4970 p++;
4972 if(*p != '\0')
4973 found_it = srchstr(p, needle);
4975 if(found_it){
4976 /* found it, make result relative to haystack */
4977 return_ptr = haystack + (found_it - haystack_copy);
4980 /* skip to next null */
4981 while(*p != '\0' && p < end)
4982 p++;
4985 if(free_this)
4986 fs_give((void **) &free_this);
4989 return(return_ptr);
4994 * Returns the number of columns taken up by the visible part of the line.
4995 * That is, account for handles and color changes and so forth.
4998 visible_linelen(int line)
5000 SCRLCTRL_S *st = scroll_state(SS_CUR);
5001 int i, n, len = 0;
5003 if(line < 0 || line >= st->num_lines)
5004 return(len);
5006 for(i = 0, len = 0; i < st->line_lengths[line];)
5007 switch(st->text_lines[line][i]){
5009 case TAG_EMBED:
5010 i++;
5011 switch((i < st->line_lengths[line]) ? st->text_lines[line][i] : 0){
5012 case TAG_HANDLE:
5013 i++;
5014 /* skip the length byte plus <length> more bytes */
5015 if(i < st->line_lengths[line]){
5016 n = st->text_lines[line][i];
5017 i++;
5020 if(i < st->line_lengths[line] && n > 0)
5021 i += n;
5023 break;
5025 case TAG_FGCOLOR :
5026 case TAG_BGCOLOR :
5027 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
5028 break;
5030 case TAG_INVON:
5031 case TAG_INVOFF:
5032 case TAG_BOLDON:
5033 case TAG_BOLDOFF:
5034 case TAG_ULINEON:
5035 case TAG_ULINEOFF:
5036 i++;
5037 break;
5039 case TAG_EMBED: /* escaped embed character */
5040 i++;
5041 len++;
5042 break;
5044 default: /* the embed char was literal */
5045 i++;
5046 len += 2;
5047 break;
5050 break;
5052 case TAB:
5053 i++;
5054 while(((++len) & 0x07) != 0) /* add tab's spaces */
5057 break;
5059 default:
5060 i++;
5061 len++;
5062 break;
5065 return(len);
5069 /*----------------------------------------------------------------------
5070 Display the contents of the given file (likely output from some command)
5072 Args: filename -- name of file containing output
5073 title -- title to be used for screen displaying output
5074 alt_msg -- if no output, Q this message instead of the default
5075 mode -- non-zero to display short files in status line
5076 Returns: none
5077 ----*/
5078 void
5079 display_output_file(char *filename, char *title, char *alt_msg, int mode)
5081 STORE_S *in_file = NULL, *out_store = NULL;
5083 if((in_file = so_get(FileStar, filename, READ_ACCESS|READ_FROM_LOCALE))){
5084 if(mode == DOF_BRIEF){
5085 int msg_q = 0, i = 0;
5086 char buf[512], *msg_p[4];
5087 #define MAX_SINGLE_MSG_LEN 60
5089 buf[0] = '\0';
5090 msg_p[0] = buf;
5093 * Might need to do something about CRLFs for Windows.
5095 while(so_fgets(in_file, msg_p[msg_q], sizeof(buf) - (msg_p[msg_q] - buf))
5096 && msg_q < 3
5097 && (i = strlen(msg_p[msg_q])) < MAX_SINGLE_MSG_LEN){
5098 msg_p[msg_q+1] = msg_p[msg_q]+strlen(msg_p[msg_q]);
5099 if (*(msg_p[++msg_q] - 1) == '\n')
5100 *(msg_p[msg_q] - 1) = '\0';
5103 if(msg_q < 3 && i < MAX_SINGLE_MSG_LEN){
5104 if(*msg_p[0])
5105 for(i = 0; i < msg_q; i++)
5106 q_status_message2(SM_ORDER, 3, 4,
5107 "%s Result: %s", title, msg_p[i]);
5108 else
5109 q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
5110 alt_msg
5111 ? alt_msg
5112 : " command completed with no output");
5114 so_give(&in_file);
5115 in_file = NULL;
5118 else if(mode == DOF_EMPTY){
5119 unsigned char c;
5121 if(so_readc(&c, in_file) < 1){
5122 q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
5123 alt_msg
5124 ? alt_msg
5125 : " command completed with no output");
5126 so_give(&in_file);
5127 in_file = NULL;
5132 * We need to translate the file contents from the user's locale
5133 * charset to UTF-8 for use in scrolltool. We get that translation
5134 * from the READ_FROM_LOCALE in the in_file storage object.
5135 * It would be nice to skip this step but scrolltool doesn't use
5136 * the storage object routines to read from the file, so would
5137 * skip the translation step.
5139 if(in_file){
5140 char *errstr;
5141 gf_io_t gc, pc;
5143 if(!(out_store = so_get(CharStar, NULL, EDIT_ACCESS))){
5144 so_give(&in_file);
5145 our_unlink(filename);
5146 q_status_message(SM_ORDER | SM_DING, 3, 3,
5147 _("Error allocating space."));
5148 return;
5151 so_seek(in_file, 0L, 0);
5153 gf_filter_init();
5155 gf_link_filter(gf_wrap,
5156 gf_wrap_filter_opt(ps_global->ttyo->screen_cols - 4,
5157 ps_global->ttyo->screen_cols,
5158 NULL, 0, GFW_NONE));
5160 gf_set_so_readc(&gc, in_file);
5161 gf_set_so_writec(&pc, out_store);
5163 if((errstr = gf_pipe(gc, pc)) != NULL){
5164 so_give(&in_file);
5165 so_give(&out_store);
5166 our_unlink(filename);
5167 q_status_message(SM_ORDER | SM_DING, 3, 3,
5168 _("Error allocating space."));
5169 return;
5172 gf_clear_so_writec(out_store);
5173 gf_clear_so_readc(in_file);
5174 so_give(&in_file);
5177 if(out_store){
5178 SCROLL_S sargs;
5179 char title_buf[64];
5181 snprintf(title_buf, sizeof(title_buf), "HELP FOR %s VIEW", title);
5182 title_buf[sizeof(title_buf)-1] = '\0';
5184 memset(&sargs, 0, sizeof(SCROLL_S));
5185 sargs.text.text = so_text(out_store);
5186 sargs.text.src = CharStar;
5187 sargs.text.desc = "output";
5188 sargs.bar.title = title;
5189 sargs.bar.style = TextPercent;
5190 sargs.help.text = h_simple_text_view;
5191 sargs.help.title = title_buf;
5192 scrolltool(&sargs);
5193 ps_global->mangled_screen = 1;
5194 so_give(&out_store);
5197 our_unlink(filename);
5199 else
5200 dprint((2, "Error reopening %s to get results: %s\n",
5201 filename ? filename : "?", error_description(errno)));
5205 /*--------------------------------------------------------------------
5206 Call the function that will perform the double click operation
5208 Returns: the current top line
5209 --------*/
5210 long
5211 doubleclick_handle(SCROLL_S *sparms, HANDLE_S *next_handle, int *cmd, int *done)
5213 if(sparms->mouse.clickclick){
5214 if((*sparms->mouse.clickclick)(sparms))
5215 *done = 1;
5217 else
5218 switch(scroll_handle_launch(next_handle, TRUE)){
5219 case 1 :
5220 *cmd = MC_EXIT; /* propagate */
5221 *done = 1;
5222 break;
5224 case -1 :
5225 cmd_cancelled("View");
5226 break;
5228 default :
5229 break;
5232 return(scroll_state(SS_CUR)->top_text_line);
5237 #ifdef _WINDOWS
5239 * Just a little something to simplify assignments
5241 #define VIEWPOPUP(p, c, s) { \
5242 (p)->type = tQueue; \
5243 (p)->data.val = c; \
5244 (p)->label.style = lNormal; \
5245 (p)->label.string = s; \
5253 format_message_popup(sparms, in_handle)
5254 SCROLL_S *sparms;
5255 int in_handle;
5257 MPopup fmp_menu[32];
5258 HANDLE_S *h = NULL;
5259 int i = -1, n;
5260 long rawno;
5261 MESSAGECACHE *mc;
5263 /* Reason to offer per message ops? */
5264 if(mn_get_total(ps_global->msgmap) > 0L){
5265 if(in_handle){
5266 SCRLCTRL_S *st = scroll_state(SS_CUR);
5268 switch((h = get_handle(st->parms->text.handles, in_handle))->type){
5269 case Attach :
5270 fmp_menu[++i].type = tIndex;
5271 fmp_menu[i].label.string = "View Attachment";
5272 fmp_menu[i].label.style = lNormal;
5273 fmp_menu[i].data.val = 'X'; /* for local use */
5275 if(h->h.attach
5276 && dispatch_attachment(h->h.attach) != MCD_NONE
5277 && !(h->h.attach->can_display & MCD_EXTERNAL)
5278 && h->h.attach->body
5279 && (h->h.attach->body->type == TYPETEXT
5280 || (h->h.attach->body->type == TYPEMESSAGE
5281 && h->h.attach->body->subtype
5282 && !strucmp(h->h.attach->body->subtype,"rfc822")))){
5283 fmp_menu[++i].type = tIndex;
5284 fmp_menu[i].label.string = "View Attachment in New Window";
5285 fmp_menu[i].label.style = lNormal;
5286 fmp_menu[i].data.val = 'Y'; /* for local use */
5289 fmp_menu[++i].type = tIndex;
5290 fmp_menu[i].label.style = lNormal;
5291 fmp_menu[i].data.val = 'Z'; /* for local use */
5292 msgno_exceptions(ps_global->mail_stream,
5293 mn_m2raw(ps_global->msgmap,
5294 mn_get_cur(ps_global->msgmap)),
5295 h->h.attach->number, &n, FALSE);
5296 fmp_menu[i].label.string = (n & MSG_EX_DELETE)
5297 ? "Undelete Attachment"
5298 : "Delete Attachment";
5299 break;
5301 case URL :
5302 default :
5303 fmp_menu[++i].type = tIndex;
5304 fmp_menu[i].label.string = "View Link";
5305 fmp_menu[i].label.style = lNormal;
5306 fmp_menu[i].data.val = 'X'; /* for local use */
5308 fmp_menu[++i].type = tIndex;
5309 fmp_menu[i].label.string = "Copy Link";
5310 fmp_menu[i].label.style = lNormal;
5311 fmp_menu[i].data.val = 'W'; /* for local use */
5312 break;
5315 fmp_menu[++i].type = tSeparator;
5318 /* Delete or Undelete? That is the question. */
5319 fmp_menu[++i].type = tQueue;
5320 fmp_menu[i].label.style = lNormal;
5321 mc = ((rawno = mn_m2raw(ps_global->msgmap,
5322 mn_get_cur(ps_global->msgmap))) > 0L
5323 && ps_global->mail_stream
5324 && rawno <= ps_global->mail_stream->nmsgs)
5325 ? mail_elt(ps_global->mail_stream, rawno) : NULL;
5326 if(mc && mc->deleted){
5327 fmp_menu[i].data.val = 'U';
5328 fmp_menu[i].label.string = in_handle
5329 ? "&Undelete Message" : "&Undelete";
5331 else{
5332 fmp_menu[i].data.val = 'D';
5333 fmp_menu[i].label.string = in_handle
5334 ? "&Delete Message" : "&Delete";
5337 if(F_ON(F_ENABLE_FLAG, ps_global)){
5338 fmp_menu[++i].type = tSubMenu;
5339 fmp_menu[i].label.string = "Flag";
5340 fmp_menu[i].data.submenu = flag_submenu(mc);
5343 i++;
5344 VIEWPOPUP(&fmp_menu[i], 'S', in_handle ? "&Save Message" : "&Save");
5346 i++;
5347 VIEWPOPUP(&fmp_menu[i], 'E', in_handle ? "&Export Message" : "&Export");
5349 i++;
5350 VIEWPOPUP(&fmp_menu[i], '%', in_handle ? "Print Message" : "Print");
5352 i++;
5353 VIEWPOPUP(&fmp_menu[i], 'R',
5354 in_handle ? "&Reply to Message" : "&Reply");
5356 i++;
5357 VIEWPOPUP(&fmp_menu[i], 'F',
5358 in_handle ? "&Forward Message" : "&Forward");
5360 i++;
5361 VIEWPOPUP(&fmp_menu[i], 'B',
5362 in_handle ? "&Bounce Message" : "&Bounce");
5364 i++;
5365 VIEWPOPUP(&fmp_menu[i], 'T', "&Take Addresses");
5367 fmp_menu[++i].type = tSeparator;
5369 if(mn_get_cur(ps_global->msgmap) < mn_get_total(ps_global->msgmap)){
5370 i++;
5371 VIEWPOPUP(&fmp_menu[i], 'N', "View &Next Message");
5374 if(mn_get_cur(ps_global->msgmap) > 0){
5375 i++;
5376 VIEWPOPUP(&fmp_menu[i], 'P', "View &Prev Message");
5379 if(mn_get_cur(ps_global->msgmap) < mn_get_total(ps_global->msgmap)
5380 || mn_get_cur(ps_global->msgmap) > 0)
5381 fmp_menu[++i].type = tSeparator;
5383 /* Offer the attachment screen? */
5384 for(n = 0; ps_global->atmts && ps_global->atmts[n].description; n++)
5387 if(n > 1){
5388 i++;
5389 VIEWPOPUP(&fmp_menu[i], 'V', "&View Attachment Index");
5393 i++;
5394 VIEWPOPUP(&fmp_menu[i], 'I', "Message &Index");
5396 i++;
5397 VIEWPOPUP(&fmp_menu[i], 'M', "&Main Menu");
5399 fmp_menu[++i].type = tTail;
5401 if((i = mswin_popup(fmp_menu)) >= 0 && in_handle)
5402 switch(fmp_menu[i].data.val){
5403 case 'W' : /* Copy URL to clipboard */
5404 mswin_addclipboard(h->h.url.path);
5405 break;
5407 case 'X' :
5408 return(1); /* return like the user double-clicked */
5410 break;
5412 case 'Y' : /* popup the thing in another window */
5413 display_att_window(h->h.attach);
5414 break;
5416 case 'Z' :
5417 if(h && h->type == Attach){
5418 msgno_exceptions(ps_global->mail_stream,
5419 mn_m2raw(ps_global->msgmap,
5420 mn_get_cur(ps_global->msgmap)),
5421 h->h.attach->number, &n, FALSE);
5422 n ^= MSG_EX_DELETE;
5423 msgno_exceptions(ps_global->mail_stream,
5424 mn_m2raw(ps_global->msgmap,
5425 mn_get_cur(ps_global->msgmap)),
5426 h->h.attach->number, &n, TRUE);
5427 q_status_message2(SM_ORDER, 0, 3, "Attachment %s %s!",
5428 h->h.attach->number,
5429 (n & MSG_EX_DELETE) ? "deleted" : "undeleted");
5431 return(2);
5434 break;
5436 default :
5437 break;
5440 return(0);
5449 simple_text_popup(sparms, in_handle)
5450 SCROLL_S *sparms;
5451 int in_handle;
5453 MPopup simple_menu[12];
5454 int n = 0;
5456 VIEWPOPUP(&simple_menu[n], '%', "Print");
5457 n++;
5459 VIEWPOPUP(&simple_menu[n], 'S', "Save");
5460 n++;
5462 VIEWPOPUP(&simple_menu[n], 'F', "Forward in Email");
5463 n++;
5465 simple_menu[n++].type = tSeparator;
5467 VIEWPOPUP(&simple_menu[n], 'E', "Exit Viewer");
5468 n++;
5470 simple_menu[n].type = tTail;
5472 (void) mswin_popup(simple_menu);
5473 return(0);
5478 /*----------------------------------------------------------------------
5479 Return characters in scroll tool buffer serially
5481 Args: n -- index of char to return
5483 Returns: returns the character at index 'n', or -1 on error or
5484 end of buffer.
5486 ----*/
5488 mswin_readscrollbuf(n)
5489 int n;
5491 SCRLCTRL_S *st = scroll_state(SS_CUR);
5492 int c;
5493 static char **orig = NULL, **l, *p;
5494 static int lastn;
5496 if(!st)
5497 return(-1);
5500 * All of these are mind-numbingly slow at the moment...
5502 switch(st->parms->text.src){
5503 case CharStar :
5504 return((n >= strlen((char *)st->parms->text.text))
5505 ? -1 : ((char *)st->parms->text.text)[n]);
5507 case CharStarStar :
5508 /* BUG? is this test rigorous enough? */
5509 if(orig != (char **)st->parms->text.text || n < lastn){
5510 lastn = n;
5511 if(orig = l = (char **)st->parms->text.text) /* reset l and p */
5512 p = *l;
5514 else{ /* use cached l and p */
5515 c = n; /* and adjust n */
5516 n -= lastn;
5517 lastn = c;
5520 while(l){ /* look for 'n' on each line */
5521 for(; n && *p; n--, p++)
5524 if(n--) /* 'n' found ? */
5525 p = *++l;
5526 else
5527 break;
5530 return((l && *l) ? *p ? *p : '\n' : -1);
5532 case FileStar :
5533 return((fseek((FILE *)st->parms->text.text, (long) n, 0) < 0
5534 || (c = fgetc((FILE *)st->parms->text.text)) == EOF) ? -1 : c);
5536 default:
5537 return(-1);
5543 /*----------------------------------------------------------------------
5544 MSWin scroll callback. Called during scroll message processing.
5548 Args: cmd - what type of scroll operation.
5549 scroll_pos - parameter for operation.
5550 used as position for SCROLL_TO operation.
5552 Returns: TRUE - did the scroll operation.
5553 FALSE - was not able to do the scroll operation.
5554 ----*/
5556 pcpine_do_scroll (cmd, scroll_pos)
5557 int cmd;
5558 long scroll_pos;
5560 SCRLCTRL_S *st = scroll_state(SS_CUR);
5561 HANDLE_S *next_handle;
5562 int paint = FALSE;
5563 int num_display_lines;
5564 int scroll_lines;
5565 char message[64];
5566 long maxscroll;
5569 message[0] = '\0';
5570 maxscroll = st->num_lines;
5571 switch (cmd) {
5572 case MSWIN_KEY_SCROLLUPLINE:
5573 if(st->top_text_line > 0) {
5574 st->top_text_line -= (int) scroll_pos;
5575 paint = TRUE;
5576 if (st->top_text_line <= 0){
5577 snprintf(message, sizeof(message), "START of %.*s",
5578 32, STYLE_NAME(st->parms));
5579 message[sizeof(message)-1] = '\0';
5580 st->top_text_line = 0;
5583 break;
5585 case MSWIN_KEY_SCROLLDOWNLINE:
5586 if(st->top_text_line < maxscroll) {
5587 st->top_text_line += (int) scroll_pos;
5588 paint = TRUE;
5589 if (st->top_text_line >= maxscroll){
5590 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5591 message[sizeof(message)-1] = '\0';
5592 st->top_text_line = maxscroll;
5595 break;
5597 case MSWIN_KEY_SCROLLUPPAGE:
5598 if(st->top_text_line > 0) {
5599 num_display_lines = SCROLL_LINES(ps_global);
5600 scroll_lines = MIN(MAX(num_display_lines -
5601 ps_global->viewer_overlap, 1), num_display_lines);
5602 if (st->top_text_line > scroll_lines)
5603 st->top_text_line -= scroll_lines;
5604 else {
5605 st->top_text_line = 0;
5606 snprintf(message, sizeof(message), "START of %.*s", 32, STYLE_NAME(st->parms));
5607 message[sizeof(message)-1] = '\0';
5609 paint = TRUE;
5611 break;
5613 case MSWIN_KEY_SCROLLDOWNPAGE:
5614 num_display_lines = SCROLL_LINES(ps_global);
5615 if(st->top_text_line < maxscroll) {
5616 scroll_lines = MIN(MAX(num_display_lines -
5617 ps_global->viewer_overlap, 1), num_display_lines);
5618 st->top_text_line += scroll_lines;
5619 if (st->top_text_line >= maxscroll) {
5620 st->top_text_line = maxscroll;
5621 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5622 message[sizeof(message)-1] = '\0';
5624 paint = TRUE;
5626 break;
5628 case MSWIN_KEY_SCROLLTO:
5629 if (st->top_text_line != scroll_pos) {
5630 st->top_text_line = scroll_pos;
5631 if (st->top_text_line == 0)
5632 snprintf(message, sizeof(message), "START of %.*s", 32, STYLE_NAME(st->parms));
5633 else if(st->top_text_line >= maxscroll)
5634 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5636 message[sizeof(message)-1] = '\0';
5637 paint = TRUE;
5639 break;
5642 /* Need to frame some handles? */
5643 if(st->parms->text.handles
5644 && (next_handle = scroll_handle_in_frame(st->top_text_line)))
5645 st->parms->text.handles = next_handle;
5647 if (paint) {
5648 mswin_beginupdate();
5649 update_scroll_titlebar(st->top_text_line, 0);
5650 (void) scroll_scroll_text(st->top_text_line,
5651 st->parms->text.handles, 1);
5652 if (message[0])
5653 q_status_message(SM_INFO, 0, 1, message);
5655 /* Display is always called so that the "START(END) of message"
5656 * message gets removed when no longer at the start(end). */
5657 display_message (KEY_PGDN);
5658 mswin_endupdate();
5661 return (TRUE);
5665 char *
5666 pcpine_help_scroll(title)
5667 char *title;
5669 SCRLCTRL_S *st = scroll_state(SS_CUR);
5671 if(title)
5672 strncpy(title, (st->parms->help.title)
5673 ? st->parms->help.title : "Alpine Help", 256);
5675 return(pcpine_help(st->parms->help.text));
5680 pcpine_view_cursor(col, row)
5681 int col;
5682 long row;
5684 SCRLCTRL_S *st = scroll_state(SS_CUR);
5685 int key;
5686 long line;
5688 return((row >= HEADER_ROWS(ps_global)
5689 && row < HEADER_ROWS(ps_global) + SCROLL_LINES(ps_global)
5690 && (line = (row - 2) + st->top_text_line) < st->num_lines
5691 && (key = dot_on_handle(line, col))
5692 && scroll_handle_selectable(get_handle(st->parms->text.handles,key)))
5693 ? MSWIN_CURSOR_HAND
5694 : MSWIN_CURSOR_ARROW);
5696 #endif /* _WINDOWS */