Add support for tab-completion when selecting by rule
[alpine.git] / alpine / mailview.c
blob5c886c32078c5266dd05811cb8400889ceb8b484
1 /*
2 * ========================================================================
3 * Copyright 2006-2008 University of Washington
4 * Copyright 2013-2022 Eduardo Chappa
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 /*======================================================================
17 mailview.c
19 Implements the mailview screen
20 Also includes scrolltool used to display help text
22 ====*/
24 #include "headers.h"
25 #include "mailcmd.h"
26 #include "mailview.h"
27 #include "mailindx.h"
28 #include "mailpart.h"
29 #include "adrbkcmd.h"
30 #include "keymenu.h"
31 #include "status.h"
32 #include "radio.h"
33 #include "help.h"
34 #include "imap.h"
35 #include "reply.h"
36 #include "folder.h"
37 #include "alpine.h"
38 #include "titlebar.h"
39 #include "signal.h"
40 #include "send.h"
41 #include "dispfilt.h"
42 #include "busy.h"
43 #include "smime.h"
44 #include "roleconf.h"
45 #include "../pith/conf.h"
46 #include "../pith/filter.h"
47 #include "../pith/msgno.h"
48 #include "../pith/escapes.h"
49 #include "../pith/flag.h"
50 #include "../pith/mimedesc.h"
51 #include "../pith/url.h"
52 #include "../pith/bldaddr.h"
53 #include "../pith/mailcmd.h"
54 #include "../pith/newmail.h"
55 #include "../pith/pipe.h"
56 #include "../pith/thread.h"
57 #include "../pith/util.h"
58 #include "../pith/detoken.h"
59 #include "../pith/editorial.h"
60 #include "../pith/maillist.h"
61 #include "../pith/hist.h"
62 #include "../pith/busy.h"
63 #include "../pith/list.h"
64 #include "../pith/detach.h"
67 /*----------------------------------------------------------------------
68 Saved state for scrolling text
69 ----*/
70 typedef struct scroll_text {
71 SCROLL_S *parms; /* Original text (file, char *, char **) */
72 char **text_lines, /* Lines to display */
73 *fname; /* filename of line offsets in "text" */
74 FILE *findex; /* file pointer to line offsets in "text" */
75 short *line_lengths; /* Length of each line in "text_lines" */
76 long top_text_line, /* index in "text_lines" top displayed line */
77 num_lines; /* number of valid pointers in "text_lines" */
78 int lines_allocated; /* size of "text_lines" array */
79 struct {
80 int length, /* count of displayable lines (== PGSIZE) */
81 width, /* width of displayable lines */
82 start_line, /* line number to start painting on */
83 other_lines; /* # of lines not for scroll text */
84 } screen; /* screen parameters */
85 } SCRLCTRL_S;
88 typedef struct scroll_file {
89 long offset;
90 int len;
91 } SCRLFILE_S;
95 * Struct to help write lines do display as they're decoded
97 struct view_write_s {
98 char *line;
99 int index,
100 screen_line,
101 last_screen_line;
102 #ifdef _WINDOWS
103 long lines;
104 #endif
105 HANDLE_S **handles;
106 STORE_S *store;
107 } *g_view_write;
109 #define LINEBUFSIZ (4096)
111 #define MAX_FUDGE (1024*1024)
114 * Definitions to help scrolltool
116 #define SCROLL_LINES_ABOVE(X) HEADER_ROWS(X)
117 #define SCROLL_LINES_BELOW(X) FOOTER_ROWS(X)
118 #define SCROLL_LINES(X) MAX(((X)->ttyo->screen_rows \
119 - SCROLL_LINES_ABOVE(X) - SCROLL_LINES_BELOW(X)), 0)
120 #define scroll_text_lines() (scroll_state(SS_CUR)->num_lines)
124 * Definitions for various scroll state manager's functions
126 #define SS_NEW 1
127 #define SS_CUR 2
128 #define SS_FREE 3
132 * Handle hints.
134 #define HANDLE_INIT_MSG \
135 _("Selectable items in text -- Use Up/Down Arrows to choose, Return to view")
136 #define HANDLE_ABOVE_ERR \
137 _("No selected item displayed -- Use PrevPage to bring choice into view")
138 #define HANDLE_BELOW_ERR \
139 _("No selected item displayed -- Use NextPage to bring choice into view")
142 #define PGSIZE(X) (ps_global->ttyo->screen_rows - (X)->screen.other_lines)
144 #define TYPICAL_BIG_MESSAGE_LINES 200
148 * Internal prototypes
150 void view_writec_killbuf(void);
151 int view_end_scroll(SCROLL_S *);
152 long format_size_guess(BODY *);
153 int scroll_handle_prompt(HANDLE_S *, int);
154 int scroll_handle_launch(HANDLE_S *, int);
155 int scroll_handle_obscured(HANDLE_S *);
156 HANDLE_S *scroll_handle_in_frame(long);
157 long scroll_handle_reframe(int, int);
158 int handle_on_line(long, int);
159 int handle_on_page(HANDLE_S *, long, long);
160 int scroll_handle_selectable(HANDLE_S *);
161 HANDLE_S *scroll_handle_next_sel(HANDLE_S *);
162 HANDLE_S *scroll_handle_prev_sel(HANDLE_S *);
163 HANDLE_S *scroll_handle_next(HANDLE_S *);
164 HANDLE_S *scroll_handle_prev(HANDLE_S *);
165 HANDLE_S *scroll_handle_boundary(HANDLE_S *, HANDLE_S *(*)(HANDLE_S *));
166 int scroll_handle_column(int, int);
167 int scroll_handle_index(int, int);
168 void scroll_handle_set_loc(POSLIST_S **, int, int);
169 int dot_on_handle(long, int);
170 int imgdata_open(HANDLE_S *);
171 char *img_handler(HANDLE_S *);
172 int url_launch(HANDLE_S *);
173 int url_launch_too_long(int);
174 char *url_external_handler(HANDLE_S *, int);
175 void url_mailto_addr(ADDRESS **, char *);
176 int ical_send_reply(char *);
177 int url_local_imap(char *);
178 int url_local_nntp(char *);
179 int url_local_news(char *);
180 int url_local_file(char *);
181 static int print_to_printer(SCROLL_S *);
182 int search_text(int, long, int, char **, Pos *, int *);
183 void update_scroll_titlebar(long, int);
184 SCRLCTRL_S *scroll_state(int);
185 void set_scroll_text(SCROLL_S *, long, SCRLCTRL_S *);
186 void redraw_scroll_text(void);
187 void zero_scroll_text(void);
188 void format_scroll_text(void);
189 void ScrollFile(long);
190 long make_file_index(void);
191 char *scroll_file_line(FILE *, char *, SCRLFILE_S *, int *);
192 long scroll_scroll_text(long, HANDLE_S *, int);
193 int search_scroll_text(long, int, char *, Pos *, int *);
194 char *search_scroll_line(char *, char *, int, int);
195 int visible_linelen(int);
196 long doubleclick_handle(SCROLL_S *, HANDLE_S *, int *, int *);
197 #ifdef _WINDOWS
198 int format_message_popup(SCROLL_S *, int);
199 int simple_text_popup(SCROLL_S *, int);
200 int mswin_readscrollbuf(int);
201 int pcpine_do_scroll(int, long);
202 char *pcpine_help_scroll(char *);
203 int pcpine_view_cursor(int, long);
204 #endif
208 /*----------------------------------------------------------------------
209 Format a buffer with the text of the current message for browser
211 Args: ps - pine state structure
213 Result: The scrolltool is called to display the message
215 Loop here viewing mail until the folder changed or a command takes
216 us to another screen. Inside the loop the message text is fetched and
217 formatted into a buffer allocated for it. These are passed to the
218 scrolltool(), that displays the message and executes commands. It
219 returns when it's time to display a different message, when we
220 change folders, when it's time for a different screen, or when
221 there are no more messages available.
222 ---*/
224 void
225 mail_view_screen(struct pine *ps)
227 char last_was_full_header = 0;
228 long last_message_viewed = -1L, raw_msgno, offset = 0L;
229 OtherMenu save_what = FirstMenu;
230 int we_cancel = 0, flags, cmd = 0;
231 int force_prefer = 0;
232 MESSAGECACHE *mc;
233 ENVELOPE *env = NULL;
234 BODY *body;
235 STORE_S *store;
236 HANDLE_S *handles = NULL;
237 SCROLL_S scrollargs;
238 SourceType src = CharStar;
240 dprint((1, "\n\n ----- MAIL VIEW -----\n"));
242 ps->prev_screen = mail_view_screen;
243 ps->force_prefer_plain = ps->force_no_prefer_plain = 0;
245 if(ps->ttyo->screen_rows - HEADER_ROWS(ps) - FOOTER_ROWS(ps) < 1){
246 q_status_message(SM_ORDER | SM_DING, 0, 3,
247 _("Screen too small to view message"));
248 ps->next_screen = mail_index_screen;
249 return;
252 /*----------------- Loop viewing messages ------------------*/
253 do {
255 ps->user_says_cancel = 0;
256 ps->some_quoting_was_suppressed = 0;
259 * Check total to make sure there's something to view. Check it
260 * inside the loop to make sure everything wasn't expunged while
261 * we were viewing. If so, make sure we don't just come back.
263 if(mn_get_total(ps->msgmap) <= 0L || !ps->mail_stream){
264 q_status_message(SM_ORDER, 0, 3, _("No messages to read!"));
265 ps->next_screen = mail_index_screen;
266 break;
269 we_cancel = busy_cue(NULL, NULL, 1);
271 if(mn_get_cur(ps->msgmap) <= 0L)
272 mn_set_cur(ps->msgmap,
273 THREADING() ? first_sorted_flagged(F_NONE,
274 ps->mail_stream,
275 0L, FSF_SKIP_CHID)
276 : 1L);
278 raw_msgno = mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap));
279 body = NULL;
280 if(raw_msgno == 0L
281 || !(env = pine_mail_fetchstructure(ps->mail_stream,raw_msgno,&body))
282 || !(raw_msgno > 0L && ps->mail_stream
283 && raw_msgno <= ps->mail_stream->nmsgs
284 && (mc = mail_elt(ps->mail_stream, raw_msgno)))){
285 q_status_message1(SM_ORDER, 3, 3,
286 "Error getting message %s data",
287 comatose(mn_get_cur(ps->msgmap)));
288 dprint((1, "!!!! ERROR fetching %s of msg %ld\n",
289 env ? "elt" : "env", mn_get_cur(ps->msgmap)));
291 ps->next_screen = mail_index_screen;
292 break;
294 else
295 ps->unseen_in_view = !mc->seen;
297 init_handles(&handles);
299 store = so_get(src, NULL, EDIT_ACCESS);
300 so_truncate(store, format_size_guess(body) + 2048);
302 view_writec_init(store, &handles, SCROLL_LINES_ABOVE(ps),
303 SCROLL_LINES_ABOVE(ps) +
304 ps->ttyo->screen_rows - (SCROLL_LINES_ABOVE(ps)
305 + SCROLL_LINES_BELOW(ps)));
307 flags = FM_DISPLAY;
308 if(last_message_viewed != mn_get_cur(ps->msgmap)
309 || last_was_full_header == 2 || cmd == MC_TOGGLE)
310 flags |= FM_NEW_MESS;
312 if(F_OFF(F_QUELL_FULL_HDR_RESET, ps_global)
313 && last_message_viewed != -1L
314 && last_message_viewed != mn_get_cur(ps->msgmap))
315 ps->full_header = 0;
317 if(offset) /* no pre-paint during resize */
318 view_writec_killbuf();
320 #ifdef SMIME
321 /* Attempt to handle S/MIME bodies */
322 if(ps->smime)
323 ps->smime->need_passphrase = 0;
325 if(F_OFF(F_DONT_DO_SMIME, ps_global) && fiddle_smime_message(body, raw_msgno))
326 flags |= FM_NEW_MESS; /* body was changed, force a reload */
327 #endif
329 #ifdef _WINDOWS
330 mswin_noscrollupdate(1);
331 #endif
332 ps->cur_uid_stream = ps->mail_stream;
333 ps->cur_uid = mail_uid(ps->mail_stream, raw_msgno);
334 (void) format_message(raw_msgno, env, body, &handles, flags | force_prefer,
335 view_writec);
336 #ifdef _WINDOWS
337 mswin_noscrollupdate(0);
338 #endif
340 view_writec_destroy();
342 last_message_viewed = mn_get_cur(ps->msgmap);
343 last_was_full_header = ps->full_header;
345 ps->next_screen = SCREEN_FUN_NULL;
347 memset(&scrollargs, 0, sizeof(SCROLL_S));
348 scrollargs.text.text = so_text(store);
349 scrollargs.text.src = src;
350 scrollargs.text.desc = "message";
353 * make first selectable handle the default
355 if(handles){
356 HANDLE_S *hp;
358 hp = handles;
359 while(!scroll_handle_selectable(hp) && hp != NULL)
360 hp = hp->next;
362 if((scrollargs.text.handles = hp) != NULL)
363 scrollargs.body_valid = (hp == handles);
364 else
365 free_handles(&handles);
367 else
368 scrollargs.body_valid = 1;
370 if(offset){ /* resize? preserve paging! */
371 scrollargs.start.on = Offset;
372 scrollargs.start.loc.offset = offset;
373 scrollargs.body_valid = 0;
374 offset = 0L;
377 scrollargs.use_indexline_color = 1;
379 /* TRANSLATORS: a screen title */
380 scrollargs.bar.title = _("MESSAGE TEXT");
381 scrollargs.end_scroll = view_end_scroll;
382 scrollargs.resize_exit = 1;
383 scrollargs.help.text = h_mail_view;
384 scrollargs.help.title = _("HELP FOR MESSAGE TEXT VIEW");
385 scrollargs.keys.menu = &view_keymenu;
386 scrollargs.keys.what = save_what;
387 setbitmap(scrollargs.keys.bitmap);
388 if(F_OFF(F_ENABLE_PIPE, ps_global))
389 clrbitn(VIEW_PIPE_KEY, scrollargs.keys.bitmap);
392 * turn off attachment viewing for raw msg txt, atts
393 * haven't been set up at this point
395 if(ps_global->full_header == 2
396 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
397 clrbitn(VIEW_ATT_KEY, scrollargs.keys.bitmap);
399 if(F_OFF(F_ENABLE_BOUNCE, ps_global))
400 clrbitn(BOUNCE_KEY, scrollargs.keys.bitmap);
402 if(F_OFF(F_ENABLE_FLAG, ps_global))
403 clrbitn(FLAG_KEY, scrollargs.keys.bitmap);
405 if(F_OFF(F_ENABLE_AGG_OPS, ps_global))
406 clrbitn(VIEW_SELECT_KEY, scrollargs.keys.bitmap);
408 if(F_OFF(F_ENABLE_FULL_HDR, ps_global))
409 clrbitn(VIEW_FULL_HEADERS_KEY, scrollargs.keys.bitmap);
411 #ifdef SMIME
412 if(!(ps->smime && ps->smime->need_passphrase))
413 clrbitn(DECRYPT_KEY, scrollargs.keys.bitmap);
415 if(F_ON(F_DONT_DO_SMIME, ps_global)){
416 clrbitn(DECRYPT_KEY, scrollargs.keys.bitmap);
417 clrbitn(SECURITY_KEY, scrollargs.keys.bitmap);
419 #endif
421 if(!handles){
423 * NOTE: the comment below only really makes sense if we
424 * decide to replace the "Attachment Index" with
425 * a model that lets you highlight the attachments
426 * in the header. In a way its consistent, but
427 * would mean more keymenu monkeybusiness since not
428 * all things would be available all the time. No wait.
429 * Then what would "Save" mean; the attachment, url or
430 * message you're currently viewing? Would Save
431 * of a message then only be possible from the message
432 * index? Clumsy. what about arrow-navigation. isn't
433 * that now inconsistent? Maybe next/prev url shouldn't
434 * be bound to the arrow/^N/^P navigation?
436 clrbitn(VIEW_VIEW_HANDLE, scrollargs.keys.bitmap);
437 clrbitn(VIEW_PREV_HANDLE, scrollargs.keys.bitmap);
438 clrbitn(VIEW_NEXT_HANDLE, scrollargs.keys.bitmap);
441 #ifdef _WINDOWS
442 scrollargs.mouse.popup = format_message_popup;
443 #endif
445 if(((cmd = scrolltool(&scrollargs)) == MC_RESIZE
446 || (cmd == MC_FULLHDR && ps_global->full_header == 1))
447 && scrollargs.start.on == Offset)
448 offset = scrollargs.start.loc.offset;
450 if(cmd == MC_TOGGLE && ps->force_prefer_plain == 0 && ps->force_no_prefer_plain == 0){
451 if(F_ON(F_PREFER_PLAIN_TEXT, ps_global))
452 ps->force_no_prefer_plain = 1;
453 else
454 ps->force_prefer_plain = 1;
456 else{
457 ps->force_prefer_plain = ps->force_no_prefer_plain = 0;
461 * We could use the values directly but this is the way it
462 * already worked with the flags, so leave it alone.
464 if(ps->force_prefer_plain == 0 && ps->force_no_prefer_plain == 0)
465 force_prefer = 0;
466 else if(ps->force_prefer_plain)
467 force_prefer = FM_FORCEPREFPLN;
468 else if(ps->force_no_prefer_plain)
469 force_prefer = FM_FORCENOPREFPLN;
471 save_what = scrollargs.keys.what;
472 ps_global->unseen_in_view = 0;
473 so_give(&store); /* free resources associated with store */
474 free_handles(&handles);
475 #ifdef _WINDOWS
476 mswin_destroyicons();
477 #endif
479 while(ps->next_screen == SCREEN_FUN_NULL);
481 if(we_cancel)
482 cancel_busy_cue(-1);
484 ps->force_prefer_plain = ps->force_no_prefer_plain = 0;
486 ps->cur_uid_stream = NULL;
487 ps->cur_uid = 0;
490 * Unless we're going into attachment screen,
491 * start over with full_header.
493 if(F_OFF(F_QUELL_FULL_HDR_RESET, ps_global)
494 && ps->next_screen != attachment_screen)
495 ps->full_header = 0;
501 * view_writec_init - function to create and init struct that manages
502 * writing to the display what we can as soon
503 * as we can.
505 void
506 view_writec_init(STORE_S *store, HANDLE_S **handlesp, int first_line, int last_line)
508 char tmp[MAILTMPLEN];
510 g_view_write = (struct view_write_s *)fs_get(sizeof(struct view_write_s));
511 memset(g_view_write, 0, sizeof(struct view_write_s));
512 g_view_write->store = store;
513 g_view_write->handles = handlesp;
514 g_view_write->screen_line = first_line;
515 g_view_write->last_screen_line = last_line;
517 if(!dfilter_trigger(NULL, tmp, sizeof(tmp))){
518 g_view_write->line = (char *) fs_get(LINEBUFSIZ*sizeof(char));
519 #ifdef _WINDOWS
520 mswin_beginupdate();
521 scroll_setrange(0L, 0L);
522 #endif
524 if(ps_global->VAR_DISPLAY_FILTERS)
525 ClearLines(first_line, last_line - 1);
531 void
532 view_writec_destroy(void)
534 if(g_view_write){
535 if(g_view_write->line && g_view_write->index)
536 view_writec('\n'); /* flush pending output! */
538 while(g_view_write->screen_line < g_view_write->last_screen_line)
539 ClearLine(g_view_write->screen_line++);
541 view_writec_killbuf();
543 fs_give((void **) &g_view_write);
546 #ifdef _WINDOWS
547 mswin_endupdate();
548 #endif
553 void
554 view_writec_killbuf(void)
556 if(g_view_write->line)
557 fs_give((void **) &g_view_write->line);
563 * view_screen_pc - write chars into the final buffer
566 view_writec(int c)
568 static int in_color = 0;
570 if(g_view_write->line){
572 * This only works if the 2nd and 3rd parts of the || don't happen.
573 * The only way it breaks is if we get a really, really long line
574 * because there are oodles of tags in it. In that case we will
575 * wrap incorrectly or split a tag across lines (losing color perhaps)
576 * but we won't crash.
578 if(c == '\n' ||
579 (!in_color &&
580 (char)c != TAG_EMBED &&
581 g_view_write->index >= (LINEBUFSIZ - 20 * (RGBLEN+2))) ||
582 (g_view_write->index >= LINEBUFSIZ - 1)){
583 int rv;
585 in_color = 0;
586 suspend_busy_cue();
587 ClearLine(g_view_write->screen_line);
588 if(c != '\n')
589 g_view_write->line[g_view_write->index++] = (char) c;
591 PutLine0n8b(g_view_write->screen_line++, 0,
592 g_view_write->line, g_view_write->index,
593 g_view_write->handles ? *g_view_write->handles : NULL);
595 resume_busy_cue(0);
596 rv = so_nputs(g_view_write->store,
597 g_view_write->line,
598 g_view_write->index);
599 g_view_write->index = 0;
601 if(g_view_write->screen_line >= g_view_write->last_screen_line){
602 fs_give((void **) &g_view_write->line);
603 fflush(stdout);
606 if(!rv)
607 return(0);
608 else if(c != '\n')
609 return(1);
611 else{
613 * Don't split embedded things over multiple lines. Colors are
614 * the longest tags so use their length.
616 if((char)c == TAG_EMBED)
617 in_color = RGBLEN+1;
618 else if(in_color)
619 in_color--;
621 g_view_write->line[g_view_write->index++] = (char) c;
622 return(1);
625 #ifdef _WINDOWS
626 else if(c == '\n' && g_view_write->lines++ > SCROLL_LINES(ps_global))
627 scroll_setrange(SCROLL_LINES(ps_global),
628 g_view_write->lines + SCROLL_LINES(ps_global));
629 #endif
631 return(so_writec(c, g_view_write->store));
635 view_end_scroll(SCROLL_S *sparms)
637 int done = 0, result, force;
639 if(F_ON(F_ENABLE_SPACE_AS_TAB, ps_global)){
641 if(F_ON(F_ENABLE_TAB_DELETES, ps_global)){
642 long save_msgno;
644 /* Let the TAB advance cur msgno for us */
645 save_msgno = mn_get_cur(ps_global->msgmap);
646 (void) cmd_delete(ps_global, ps_global->msgmap, MCMD_NONE, NULL);
647 mn_set_cur(ps_global->msgmap, save_msgno);
650 /* act like the user hit a TAB */
651 result = process_cmd(ps_global, ps_global->mail_stream,
652 ps_global->msgmap, MC_TAB, View, &force);
654 if(result == 1)
655 done = 1;
658 return(done);
663 * format_size_guess -- Run down the given body summing the text/plain
664 * pieces we're likely to display. It need only
665 * be a guess since this is intended for preallocating
666 * our display buffer...
668 long
669 format_size_guess(struct mail_bodystruct *body)
671 long size = 0L;
672 long extra = 0L;
673 char *free_me = NULL;
675 if(body){
676 if(body->type == TYPEMULTIPART){
677 PART *part;
679 for(part = body->nested.part; part; part = part->next)
680 size += format_size_guess(&part->body);
682 else if(body->type == TYPEMESSAGE
683 && body->subtype && !strucmp(body->subtype, "rfc822"))
684 size = format_size_guess(body->nested.msg->body);
685 else if((!body->type || body->type == TYPETEXT)
686 && (!body->subtype || !strucmp(body->subtype, "plain"))
687 && ((body->disposition.type
688 && !strucmp(body->disposition.type, "inline"))
689 || !(free_me = parameter_val(body->parameter, "name")))){
691 * Handles and colored quotes cause memory overhead. Figure about
692 * 100 bytes per level of quote per line and about 100 bytes per
693 * handle. Make a guess of 1 handle or colored quote per
694 * 10 lines of message. If we guess too low, we'll do a resize in
695 * so_cs_writec. Most of the overhead comes from the colors, so
696 * if we can see we don't do colors or don't have these features
697 * turned on, skip it.
699 if(pico_usingcolor() &&
700 ((ps_global->VAR_QUOTE1_FORE_COLOR &&
701 ps_global->VAR_QUOTE1_BACK_COLOR) ||
702 F_ON(F_VIEW_SEL_URL, ps_global) ||
703 F_ON(F_VIEW_SEL_URL_HOST, ps_global) ||
704 F_ON(F_SCAN_ADDR, ps_global)))
705 extra = MIN(100/10 * body->size.lines, MAX_FUDGE);
707 size = body->size.bytes + extra;
710 if(free_me)
711 fs_give((void **) &free_me);
714 return(size);
719 scroll_handle_prompt(HANDLE_S *handle, int force)
721 char prompt[256], tmp[MAILTMPLEN];
722 int rc, flags, local_h, external, images;
723 ACTION_S *role = NULL;
724 static ESCKEY_S launch_opts[] = {
725 /* TRANSLATORS: command names, editURL means user gets to edit a URL if they
726 want, editApp is edit application where they edit the application used to
727 view a URL */
728 {'y', 'y', "Y", N_("Yes")},
729 {'n', 'n', "N", N_("No")},
730 {0, 'x', "X", ""},
731 {0, 'i', "I", ""},
732 {-2, 'r', "R", NULL},
733 {-2, 0, NULL, NULL},
734 {0, 'u', "U", N_("editURL")},
735 {0, 'a', "A", N_("editApp")},
736 {-1, 0, NULL, NULL}};
738 if(handle->type == URL){
739 launch_opts[6].ch = 'u';
741 if((!(local_h = !struncmp(handle->h.url.path, "x-alpine-", 9))
742 || !(local_h = !struncmp(handle->h.url.path, "x-pine-help", 11)))
743 && (handle->h.url.tool
744 || ((local_h = url_local_handler(handle->h.url.path) != NULL)
745 && (handle->h.url.tool = url_external_handler(handle,1)))
746 || (!local_h
747 && (handle->h.url.tool = url_external_handler(handle,0))))){
748 #ifdef _WINDOWS
749 /* if NOT special DDE hack */
750 if(handle->h.url.tool[0] != '*')
751 #endif
752 if(ps_global->vars[V_BROWSER].is_fixed)
753 launch_opts[7].ch = -1;
754 else
755 launch_opts[7].ch = 'a';
757 else{
758 launch_opts[7].ch = -1;
759 if(!local_h){
760 if(ps_global->vars[V_BROWSER].is_fixed){
761 q_status_message(SM_ORDER, 3, 4,
762 _("URL-Viewer is disabled by sys-admin"));
763 return(0);
765 else{
766 /* TRANSLATORS: a question */
767 if(want_to(_("No URL-Viewer application defined. Define now"),
768 'y', 0, NO_HELP, WT_SEQ_SENSITIVE) == 'y'){
769 /* Prompt for the displayer? */
770 tmp[0] = '\0';
771 while(1){
772 flags = OE_APPEND_CURRENT |
773 OE_SEQ_SENSITIVE |
774 OE_KEEP_TRAILING_SPACE;
776 rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
777 sizeof(tmp),
778 _("Web Browser: "),
779 NULL, NO_HELP, &flags);
780 if(rc == 0){
781 if((flags & OE_USER_MODIFIED) && *tmp){
782 if(can_access(tmp, EXECUTE_ACCESS) == 0){
783 int n;
784 char **l;
787 * Save it for next time...
789 for(l = ps_global->VAR_BROWSER, n = 0;
790 l && *l;
791 l++)
792 n++; /* count */
794 l = (char **) fs_get((n+2)*sizeof(char *));
795 for(n = 0;
796 ps_global->VAR_BROWSER
797 && ps_global->VAR_BROWSER[n];
798 n++)
799 l[n] = cpystr(ps_global->VAR_BROWSER[n]);
801 l[n++] = cpystr(tmp);
802 l[n] = NULL;
804 set_variable_list(V_BROWSER, l, TRUE, Main);
805 free_list_array(&l);
807 handle->h.url.tool = cpystr(tmp);
808 break;
810 else{
811 q_status_message1(SM_ORDER | SM_DING, 2, 2,
812 _("Browser not found: %s"),
813 error_description(errno));
814 continue;
817 else
818 return(0);
820 else if(rc == 1 || rc == -1){
821 return(0);
823 else if(rc == 4){
824 if(ps_global->redrawer)
825 (*ps_global->redrawer)();
829 else
830 return(0);
835 else
836 launch_opts[6].ch = -1;
838 if(handle->type == imgData){
839 if(!handle->h.img.tool
840 && !(handle->h.img.tool = img_handler(handle))){
841 /* TRANSLATORS: a question */
842 if(want_to(_("No Image-Viewer application defined. Define now"),
843 'y', 0, NO_HELP, WT_SEQ_SENSITIVE) == 'y'){
844 /* Prompt for the displayer? */
845 tmp[0] = '\0';
846 while(1){
847 flags = OE_APPEND_CURRENT |
848 OE_SEQ_SENSITIVE |
849 OE_KEEP_TRAILING_SPACE;
851 rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
852 sizeof(tmp),
853 _("Image Viewer: "),
854 NULL, NO_HELP, &flags);
855 if(rc == 0){
856 if((flags & OE_USER_MODIFIED) && *tmp){
857 if(can_access(tmp, EXECUTE_ACCESS) == 0){
858 set_variable(V_IMAGE_VIEWER, tmp, TRUE, TRUE, Main);
859 handle->h.img.tool = cpystr(tmp);
860 mailcap_free(); /* redo the mailcap list */
861 break;
863 else{
864 q_status_message1(SM_ORDER | SM_DING, 2, 2,
865 _("Image Viewer Not Found: %s"),
866 error_description(errno));
867 continue;
870 else
871 return(0);
873 else if(rc == 1 || rc == -1){
874 return(0);
876 else if(rc == 4){
877 if(ps_global->redrawer)
878 (*ps_global->redrawer)();
885 if(handle->type == Attach
886 && handle->h.attach
887 && handle->h.attach->body
888 && handle->h.attach->body->type == TYPETEXT
889 && !strucmp(handle->h.attach->body->subtype, "HTML")){
890 images = F_OFF(F_EXTERNAL_INLINE_IMAGES, ps_global) ? 1 : 0;
891 external = 0; /* default to not using external viewer, set to 1 to make it default */
892 force = 0; /* do not open automatically */
893 launch_opts[2].ch = 'x';
894 launch_opts[2].label = external > 0 ? N_("No eXternal") : N_("External");
895 launch_opts[3].ch = external > 0 ? 'i' : -2;
896 launch_opts[3].label = images ? N_("Inline imgs") : N_("All images");
898 else {
899 launch_opts[2].ch = -2; /* skip */
900 launch_opts[3].ch = -2; /* skip */
901 external = images = -1;
904 if(force
905 || (handle->type == URL
906 && (!struncmp(handle->h.url.path, "x-alpine-", 9)
907 || !struncmp(handle->h.url.path, "x-pine-help", 11))))
908 return(1);
910 while(1){
911 int sc = ps_global->ttyo->screen_cols;
914 * Customize the prompt for mailto, all the other schemes make
915 * sense if you just say View selected URL ...
917 if(handle->type == URL &&
918 !struncmp(handle->h.url.path, "mailto:", 7)){
919 char tmp[128];
920 snprintf(prompt, sizeof(prompt), "Compose mail to \"%.*s%s\"",
921 (int) MIN(MAX(0,sc - (role ? 44 : 25)), sizeof(prompt)-50), handle->h.url.path+7,
922 (strlen(handle->h.url.path+7) > MAX(0,sc-(role ? 44 :25))) ? "..." : "");
924 if(role != NULL){
925 snprintf(tmp, sizeof(tmp), " (using role \"%.*s%s\")",
926 (int) MIN(MAX(0,sc - strlen(prompt) - 19), sizeof(prompt)-strlen(tmp)-50), role->nick,
927 (strlen(role->nick) > MAX(0,sc-strlen(prompt) - 19)) ? "..." : "");
929 strncat(prompt, tmp, sizeof(prompt) - strlen(prompt) - 1);
930 prompt[sizeof(prompt) - 1] = '\0';
933 strncat(prompt, " ? ", sizeof(prompt) - strlen(prompt) - 1);
934 prompt[sizeof(prompt) - 1] = '\0';
936 launch_opts[4].ch = 'r';
937 launch_opts[4].label = N_("setRole");
939 else
940 snprintf(prompt, sizeof(prompt), "View selected %s %s%s%s%.*s%s ? ",
941 (handle->type == URL) ? "URL" : ((handle->type == imgData) ? "Image" : "Attachment"),
942 external > 0 ? "using external viewer " : "",
943 external > 0 ? (images > 0 ? "including all images" : "including inline images only") : "",
944 (handle->type == URL) ? "\"" : "",
945 (int) MIN(MAX(0,sc-27-(external ? (images ? 41 : 50) : 0)), sizeof(prompt)-50),
946 (handle->type == URL) ? handle->h.url.path : "",
947 (handle->type == URL)
948 ? ((strlen(handle->h.url.path) > MAX(0,sc-27 - (external ? (images > 0 ? 41 : 50) : 0)))
949 ? "...\"" : "\"") : "");
951 prompt[sizeof(prompt)-1] = '\0';
953 switch(radio_buttons(prompt, -FOOTER_ROWS(ps_global),
954 launch_opts, 'y', 'n', NO_HELP, RB_SEQ_SENSITIVE)){
955 case 'y' :
956 return(external > 0 ? (images > 0 ? -2 : -1) : 1);
958 case 'x' :
959 external = 1 - external;
960 images = F_OFF(F_EXTERNAL_INLINE_IMAGES, ps_global) ? 1 : 0;
961 launch_opts[2].label = external > 0 ? N_("No eXternal") : N_("External");
962 launch_opts[3].ch = external > 0 ? 'i' : -2;
963 launch_opts[3].label = images ? N_("Inline imgs") : N_("All images");
964 break;
966 case 'i' :
967 images = 1 - images;
968 launch_opts[3].label = images ? N_("Inline imgs") : N_("All images");
969 break;
971 case 'r' :
972 { void (*prev_screen)(struct pine *) = ps_global->prev_screen,
973 (*redraw)(void) = ps_global->redrawer;
975 ps_global->redrawer = NULL;
976 ps_global->next_screen = SCREEN_FUN_NULL;
977 role = ps_global->reply.role_chosen;
978 if(role_select_screen(ps_global, &role, MC_COMPOSE) < 0){
979 cmd_cancelled("Composition");
980 ps_global->next_screen = prev_screen;
981 ps_global->redrawer = redraw;
982 return -1;
985 ps_global->next_screen = prev_screen;
986 ps_global->redrawer = redraw;
987 if(role)
988 role = combine_inherited_role(role);
989 ps_global->reply.role_chosen = role;
990 if(ps_global->redrawer) (ps_global->redrawer)();
991 break;
995 case 'u' :
996 strncpy(tmp, handle->h.url.path, sizeof(tmp)-1);
997 tmp[sizeof(tmp)-1] = '\0';
998 while(1){
999 flags = OE_APPEND_CURRENT |
1000 OE_SEQ_SENSITIVE |
1001 OE_KEEP_TRAILING_SPACE;
1003 rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
1004 sizeof(tmp), _("Edit URL: "),
1005 NULL, NO_HELP, &flags);
1006 if(rc == 0){
1007 if(flags & OE_USER_MODIFIED){
1008 if(handle->h.url.path)
1009 fs_give((void **) &handle->h.url.path);
1011 handle->h.url.path = cpystr(tmp);
1014 break;
1016 else if(rc == 1 || rc == -1){
1017 return(0);
1019 else if(rc == 4){
1020 if(ps_global->redrawer)
1021 (*ps_global->redrawer)();
1025 continue;
1027 case 'a' :
1028 if(handle->h.url.tool){
1029 strncpy(tmp, handle->h.url.tool, sizeof(tmp)-1);
1030 tmp[sizeof(tmp)-1] = '\0';
1032 else
1033 tmp[0] = '\0';
1035 while(1){
1036 flags = OE_APPEND_CURRENT |
1037 OE_SEQ_SENSITIVE |
1038 OE_KEEP_TRAILING_SPACE |
1039 OE_DISALLOW_HELP;
1041 rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
1042 sizeof(tmp), _("Viewer Command: "),
1043 NULL, NO_HELP, &flags);
1044 if(rc == 0){
1045 if(flags & OE_USER_MODIFIED){
1046 if(handle->h.url.tool)
1047 fs_give((void **) &handle->h.url.tool);
1049 handle->h.url.tool = cpystr(tmp);
1052 break;
1054 else if(rc == 1 || rc == -1){
1055 return(0);
1057 else if(rc == 4){
1058 if(ps_global->redrawer)
1059 (*ps_global->redrawer)();
1063 continue;
1065 case 'n' :
1066 default :
1067 return(0);
1075 scroll_handle_launch(HANDLE_S *handle, int force)
1077 int flags;
1078 switch(handle->type){
1079 case URL :
1080 if(handle->h.url.path){
1081 if(scroll_handle_prompt(handle, force)){
1082 if(url_launch(handle)
1083 || ps_global->next_screen != SCREEN_FUN_NULL)
1084 return(1); /* done with this screen */
1086 else
1087 return(-1);
1090 break;
1092 case Attach :
1093 flags = DA_FROM_VIEW | DA_DIDPROMPT;
1094 switch(scroll_handle_prompt(handle, force)){
1095 case 1 : break;
1096 case -2 : flags |= DA_ALLIMAGES;
1097 case -1 : flags |= DA_EXTERNAL; break;
1098 default : return -1;
1100 display_attachment(mn_m2raw(ps_global->msgmap,
1101 mn_get_cur(ps_global->msgmap)), handle->h.attach, flags);
1102 break;
1104 case Folder :
1105 break;
1107 case iCal:
1108 display_vevent_summary(mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap)),
1109 handle->h.ical.attach,
1110 DA_FROM_VIEW | DA_DIDPROMPT, handle->h.ical.depth);
1111 break;
1113 case imgData:
1114 if(handle->h.img.src){
1115 if(scroll_handle_prompt(handle, force)){
1116 if(imgdata_open(handle)
1117 || ps_global->next_screen != SCREEN_FUN_NULL)
1118 return(1); /* done with this screen */
1120 else
1121 return(-1);
1123 break;
1125 case Function :
1126 (*handle->h.func.f)(handle->h.func.args.stream,
1127 handle->h.func.args.msgmap,
1128 handle->h.func.args.msgno);
1129 break;
1132 default :
1133 alpine_panic("Unexpected HANDLE type");
1136 return(0);
1141 scroll_handle_obscured(HANDLE_S *handle)
1143 SCRLCTRL_S *st = scroll_state(SS_CUR);
1145 return(handle_on_page(handle, st->top_text_line,
1146 st->top_text_line + st->screen.length));
1152 * scroll_handle_in_frame -- return handle pointer to visible handle.
1154 HANDLE_S *
1155 scroll_handle_in_frame(long int top_line)
1157 SCRLCTRL_S *st = scroll_state(SS_CUR);
1158 HANDLE_S *hp;
1160 switch(handle_on_page(hp = st->parms->text.handles, top_line,
1161 top_line + st->screen.length)){
1162 case -1 : /* handle above page */
1163 /* Find first handle from top of page */
1164 for(hp = st->parms->text.handles->next; hp; hp = hp->next)
1165 if(scroll_handle_selectable(hp))
1166 switch(handle_on_page(hp, top_line, top_line + st->screen.length)){
1167 case 0 : return(hp);
1168 case 1 : return(NULL);
1169 case -1 : default : break;
1172 break;
1174 case 1 : /* handle below page */
1175 /* Find first handle from top of page */
1176 for(hp = st->parms->text.handles->prev; hp; hp = hp->prev)
1177 if(scroll_handle_selectable(hp))
1178 switch(handle_on_page(hp, top_line, top_line + st->screen.length)){
1179 case 0 : return(hp);
1180 case -1 : return(NULL);
1181 case 1 : default : break;
1184 break;
1186 case 0 :
1187 default :
1188 break;
1191 return(hp);
1195 * scroll_handle_reframe -- adjust display params to display given handle
1197 long
1198 scroll_handle_reframe(int key, int center)
1200 long l, offset, dlines, start_line;
1201 SCRLCTRL_S *st = scroll_state(SS_CUR);
1203 dlines = PGSIZE(st);
1204 offset = (st->parms->text.src == FileStar) ? st->top_text_line : 0;
1205 start_line = st->top_text_line;
1207 if(key < 0)
1208 key = st->parms->text.handles->key;
1210 /* Searc down from the top line */
1211 for(l = start_line; l < st->num_lines; l++) {
1212 if(st->parms->text.src == FileStar && l > offset + dlines)
1213 ScrollFile(offset += dlines);
1215 if(handle_on_line(l - offset, key))
1216 break;
1219 if(l < st->num_lines){
1220 if(l >= dlines + start_line) /* bingo! */
1221 start_line = l - ((center ? (dlines / 2) : dlines) - 1);
1223 else{
1224 if(st->parms->text.src == FileStar) /* wrap offset */
1225 ScrollFile(offset = 0);
1227 for(l = 0; l < start_line; l++) {
1228 if(st->parms->text.src == FileStar && l > offset + dlines)
1229 ScrollFile(offset += dlines);
1231 if(handle_on_line(l - offset, key))
1232 break;
1235 if(l == start_line)
1236 alpine_panic("Internal Error: no handle found");
1237 else
1238 start_line = l;
1241 return(start_line);
1246 handle_on_line(long int line, int goal)
1248 int i, n, key;
1249 SCRLCTRL_S *st = scroll_state(SS_CUR);
1251 for(i = 0; i < st->line_lengths[line]; i++)
1252 if(st->text_lines[line][i] == TAG_EMBED
1253 && st->text_lines[line][++i] == TAG_HANDLE){
1254 for(key = 0, n = st->text_lines[line][++i]; n; n--)
1255 key = (key * 10) + (st->text_lines[line][++i] - '0');
1257 if(key == goal)
1258 return(1);
1261 return(0);
1266 handle_on_page(HANDLE_S *handle, long int first_line, long int last_line)
1268 POSLIST_S *l;
1269 int rv = 0;
1271 if(handle && (l = handle->loc)){
1272 for( ; l; l = l->next)
1273 if(l->where.row < first_line){
1274 if(!rv)
1275 rv = -1;
1277 else if(l->where.row >= last_line){
1278 if(!rv)
1279 rv = 1;
1281 else
1282 return(0); /* found! */
1285 return(rv);
1290 scroll_handle_selectable(HANDLE_S *handle)
1292 return(handle && (handle->type != URL
1293 || (handle->h.url.path && *handle->h.url.path)));
1297 HANDLE_S *
1298 scroll_handle_next_sel(HANDLE_S *handles)
1300 while(handles && !scroll_handle_selectable(handles = handles->next))
1303 return(handles);
1307 HANDLE_S *
1308 scroll_handle_prev_sel(HANDLE_S *handles)
1310 while(handles && !scroll_handle_selectable(handles = handles->prev))
1313 return(handles);
1317 HANDLE_S *
1318 scroll_handle_next(HANDLE_S *handles)
1320 HANDLE_S *next = NULL;
1322 if(scroll_handle_obscured(handles) <= 0){
1323 next = handles;
1324 while((next = scroll_handle_next_sel(next))
1325 && scroll_handle_obscured(next))
1329 return(next);
1334 HANDLE_S *
1335 scroll_handle_prev(HANDLE_S *handles)
1337 HANDLE_S *prev = NULL;
1339 if(scroll_handle_obscured(handles) >= 0){
1340 prev = handles;
1341 while((prev = scroll_handle_prev_sel(prev))
1342 && scroll_handle_obscured(prev))
1346 return(prev);
1350 HANDLE_S *
1351 scroll_handle_boundary(HANDLE_S *handle, HANDLE_S *(*f) (HANDLE_S *))
1353 HANDLE_S *hp, *whp = NULL;
1355 /* Multi-line handle? Punt! */
1356 if(handle && (!(hp = handle)->loc->next))
1357 while((hp = (*f)(hp))
1358 && hp->loc->where.row == handle->loc->where.row)
1359 whp = hp;
1361 return(whp);
1366 scroll_handle_column(int line, int offset)
1368 SCRLCTRL_S *st = scroll_state(SS_CUR);
1369 int i, n, col;
1370 int key, limit;
1372 limit = (offset > -1) ? offset : st->line_lengths[line];
1374 for(i = 0, col = 0; i < limit;){
1375 if(st && st->text_lines && st->text_lines[line])
1376 switch(st->text_lines[line][i]){
1377 case TAG_EMBED:
1378 i++;
1379 switch((i < limit) ? st->text_lines[line][i] : 0){
1380 case TAG_HANDLE:
1381 for(key = 0, n = st->text_lines[line][++i]; n > 0 && i < limit-1; n--)
1382 key = (key * 10) + (st->text_lines[line][++i] - '0');
1384 i++;
1385 break;
1387 case TAG_FGCOLOR :
1388 case TAG_BGCOLOR :
1389 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
1390 break;
1392 case TAG_INVON:
1393 case TAG_INVOFF:
1394 case TAG_BOLDON:
1395 case TAG_BOLDOFF:
1396 case TAG_ULINEON:
1397 case TAG_ULINEOFF:
1398 i++;
1399 break;
1401 default: /* literal embed char */
1402 break;
1405 break;
1407 case TAB:
1408 i++;
1409 while(((++col) & 0x07) != 0) /* add tab's spaces */
1412 break;
1414 default:
1415 col += width_at_this_position((unsigned char*) &st->text_lines[line][i],
1416 st->line_lengths[line] - i);
1417 i++;
1418 break;
1422 return(col);
1427 scroll_handle_index(int row, int column)
1429 SCRLCTRL_S *st = scroll_state(SS_CUR);
1430 int index = 0;
1432 for(index = 0; column > 0;)
1433 switch(st->text_lines[row][index++]){
1434 case TAG_EMBED :
1435 switch(st->text_lines[row][index++]){
1436 case TAG_HANDLE:
1437 index += st->text_lines[row][index] + 1;
1438 break;
1440 case TAG_FGCOLOR :
1441 case TAG_BGCOLOR :
1442 index += RGBLEN;
1443 break;
1445 default :
1446 break;
1449 break;
1451 case TAB : /* add tab's spaces */
1452 while(((--column) & 0x07) != 0)
1455 break;
1457 default :
1458 column--;
1459 break;
1462 return(index);
1466 void
1467 scroll_handle_set_loc(POSLIST_S **lpp, int line, int column)
1469 POSLIST_S *lp;
1471 lp = (POSLIST_S *) fs_get(sizeof(POSLIST_S));
1472 lp->where.row = line;
1473 lp->where.col = column;
1474 lp->next = NULL;
1475 for(; *lpp; lpp = &(*lpp)->next)
1478 *lpp = lp;
1483 dot_on_handle(long int line, int goal)
1485 int i = 0, n, key = 0, column = -1;
1486 SCRLCTRL_S *st = scroll_state(SS_CUR);
1489 if(i >= st->line_lengths[line])
1490 return(0);
1492 switch(st->text_lines[line][i++]){
1493 case TAG_EMBED :
1494 switch(st->text_lines[line][i++]){
1495 case TAG_HANDLE :
1496 for(key = 0, n = st->text_lines[line][i++]; n; n--)
1497 key = (key * 10) + (st->text_lines[line][i++] - '0');
1499 break;
1501 case TAG_FGCOLOR :
1502 case TAG_BGCOLOR :
1503 i += RGBLEN; /* advance past color setting */
1504 break;
1506 case TAG_BOLDOFF :
1507 key = 0;
1508 break;
1511 break;
1513 case TAB :
1514 while(((++column) & 0x07) != 0) /* add tab's spaces */
1517 break;
1519 default :
1520 column += width_at_this_position((unsigned char*) &st->text_lines[line][i-1],
1521 st->line_lengths[line] - (i-1));
1522 break;
1525 while(column < goal);
1527 return(key);
1531 url_launch(HANDLE_S *handle)
1533 return do_url_launch(handle->h.url.tool, handle->h.url.path);
1537 * url_launch - Sniff the given url, see if we can do anything with
1538 * it. If not, hand off to user-defined app.
1542 do_url_launch(char *toolp, char *url)
1544 int rv = 0;
1545 url_tool_t f;
1546 #define URL_MAX_LAUNCH (2 * MAILTMPLEN)
1548 if(toolp){
1549 char *cmdp, *p, cmd[URL_MAX_LAUNCH + 4];
1550 int mode, copied = 0;
1551 PIPE_S *syspipe;
1553 /* This code used to quote a URL to prevent arbitrary command execution
1554 * through a URL. The plan was to quote the URL with single quotes,
1555 * and this used to work. BUT some shells do not care about quoting
1556 * and interpret some characters regardless of single quotes. The
1557 * simplest solution is to escape those characters, but then some
1558 * shells will see the escape characters and some others will not.
1559 * It's a mess. There are several ways to go around this mess,
1560 * including adding configuration options (one more!?), or forget
1561 * about shells. What we do is to forget about shells, and execute
1562 * the code as a PIPE_NOSHELL.
1565 cmdp = cmd;
1566 while(cmdp-cmd < URL_MAX_LAUNCH)
1567 if((!*toolp && !copied)
1568 || (*toolp == '_' && !strncmp(toolp + 1, "URL_", 4))){
1570 /* implicit _URL_ at end */
1571 if(!*toolp)
1572 *cmdp++ = ' ';
1574 if(cmdp[-1] == '\'') /* unquote old '_URL_' */
1575 cmdp--;
1577 copied = 1;
1578 for(p = url; p && *p && cmdp-cmd < URL_MAX_LAUNCH; p++)
1579 *cmdp++ = *p;
1581 *cmdp = '\0';
1583 if(*toolp)
1584 toolp += 5; /* length of "_URL_" */
1585 if(*toolp == '\'')
1586 toolp++;
1588 else
1589 if(!(*cmdp++ = *toolp++))
1590 break;
1592 if(cmdp-cmd >= URL_MAX_LAUNCH)
1593 return(url_launch_too_long(rv));
1595 mode = PIPE_RESET | PIPE_USER | PIPE_RUNNOW | PIPE_NOSHELL ;
1596 if((syspipe = open_system_pipe(cmd, NULL, NULL, mode, 0, pipe_callback, pipe_report_error)) != NULL){
1597 close_system_pipe(&syspipe, NULL, pipe_callback);
1598 q_status_message(SM_ORDER, 0, 4, _("VIEWER command completed"));
1600 else
1601 q_status_message1(SM_ORDER, 3, 4,
1602 /* TRANSLATORS: Cannot start command : <command name> */
1603 _("Cannot start command : %s"), cmd);
1605 else if((f = url_local_handler(url)) != NULL){
1606 if((*f)(url) > 1)
1607 rv = 1; /* done! */
1609 else
1610 q_status_message1(SM_ORDER, 2, 2,
1611 _("\"URL-Viewer\" not defined: Can't open %s"), url);
1613 return(rv);
1617 imgdata_open(HANDLE_S *handle)
1619 return do_imgdata_open(handle->h.img.tool, handle->h.img.src);
1623 do_imgdata_open(char *toolp, char *data)
1625 gf_io_t pc, writec, readc;
1626 STORE_S *so, *img;
1627 char *tmpfile = NULL, *err = NULL, *imgdata, *encoding;
1629 if(!toolp) return 1;
1631 encoding = strchr(data, ';');
1632 if(encoding) encoding++;
1633 imgdata = strchr(data, ',');
1634 if(imgdata) imgdata++;
1635 tmpfile = temp_nam(NULL, "img-data-"); /* create temporary file */
1637 img = so_get(CharStar, NULL, EDIT_ACCESS); /* allocate a pointer to save data */
1638 so_seek(img, 0L, 0); /* rewind img to start */
1639 gf_set_so_writec(&writec, img); /* set method to write to img in writec */
1640 gf_set_so_readc(&readc, img); /* set method to read from img in readc */
1642 if(imgdata
1643 && encoding
1644 && imgdata
1645 && img
1646 && gf_puts(imgdata, writec) /* write imgdata to img using writec */
1647 && (so = so_get(FileStar, tmpfile, WRITE_ACCESS|OWNER_ONLY)) != NULL){ /* open temporary file */
1649 so_seek(img, 0L, 0); /* rewind img to start */
1650 so_seek(so, 0L, 0); /* rewind so to start */
1651 gf_set_so_writec(&pc, so); /* set method to write to so in pc */
1652 gf_filter_init(); /* start a filter */
1653 if(!struncmp(encoding, "BASE64", 6)) /* link base64 filter */
1654 gf_link_filter(gf_b64_binary, NULL);
1656 err = gf_pipe(readc, pc); /* pass data from imgdata to so, reading from readc and writing with pc */
1658 gf_clear_so_writec(so); /* disassociate so and pc */
1660 if(so_give(&so)) /* write tmp to disk and free so */
1661 err = "Error writing image to file";
1663 gf_clear_so_writec(img); /* disassociate img and writec */
1664 gf_clear_so_readc(img); /* disassociate img and readc */
1665 so_give(&img); /* free img */
1667 else err = "Error creating space for temporary image";
1669 /* toolp tells us that there is a program to execute, which is
1670 * either the image viewer or the mailcap. No matter which, the
1671 * information is now in the mailcap structure, so let's use it
1673 if(!err){
1674 char *subtype = NULL;
1675 if(!struncmp(data, "IMAGE/", 6)){
1676 data += 6;
1677 for(subtype = data; data && *data != ';'; data++);
1678 if(data){
1679 *data = '\0';
1680 if(mailcap_can_display(TYPEIMAGE, subtype, NULL, 0)){
1681 MCAP_CMD_S *mc_cmd;
1683 mc_cmd = mailcap_build_command(TYPEIMAGE, subtype,
1684 NULL, tmpfile, NULL, 0);
1685 exec_mailcap_cmd(mc_cmd, tmpfile, 0);
1687 *data = ';';
1688 data = subtype - 6;
1692 else
1693 q_status_message(SM_ORDER, 2, 2,
1694 _("\"Image-Viewer\" not defined: Can't open image"));
1696 if(tmpfile) /* file was deleted by exec_mailcap_cmd */
1697 fs_give((void **) &tmpfile);
1699 return 0;
1704 url_launch_too_long(int return_value)
1706 q_status_message(SM_ORDER | SM_DING, 3, 3,
1707 "Can't spawn. Command too long.");
1708 return(return_value);
1712 char *
1713 url_external_handler(HANDLE_S *handle, int specific)
1715 return get_url_external_handler(handle->h.url.path, specific);
1718 char *
1719 get_url_external_handler(char *url, int specific)
1721 char **l, *test, *cmd, *p, *q, *ep;
1722 int i, specific_match;
1724 for(l = ps_global->VAR_BROWSER ; l && *l; l++){
1725 get_pair(*l, &test, &cmd, 0, 1);
1726 dprint((5, "TEST: \"%s\" CMD: \"%s\"\n",
1727 test ? test : "<NULL>", cmd ? cmd : "<NULL>"));
1728 removing_quotes(cmd);
1729 if(valid_filter_command(&cmd)){
1730 specific_match = 0;
1732 if((p = test) != NULL){
1733 while(*p && cmd)
1734 if(*p == '_'){
1735 if(!strncmp(p+1, "TEST(", 5)
1736 && (ep = strstr(p+6, ")_"))){
1737 *ep = '\0';
1739 if(exec_mailcap_test_cmd(p+6) == 0){
1740 p = ep + 2;
1742 else{
1743 dprint((5,"failed handler TEST\n"));
1744 fs_give((void **) &cmd);
1747 else if(!strncmp(p+1, "SCHEME(", 7)
1748 && (ep = strstr(p+8, ")_"))){
1749 *ep = '\0';
1751 p += 8;
1753 if((q = strchr(p, ',')) != NULL)
1754 *q++ = '\0';
1755 else
1756 q = ep;
1757 while(!((i = strlen(p))
1758 && ((p[i-1] == ':' && url[i - 1] == ':')
1759 || (p[i-1] != ':' && url[i] == ':'))
1760 && !struncmp(url, p, i))
1761 && *(p = q));
1763 if(*p){
1764 specific_match = 1;
1765 p = ep + 2;
1767 else{
1768 dprint((5,"failed handler SCHEME\n"));
1769 fs_give((void **) &cmd);
1772 else{
1773 dprint((5, "UNKNOWN underscore test\n"));
1774 fs_give((void **) &cmd);
1777 else if(isspace((unsigned char) *p)){
1778 p++;
1780 else{
1781 dprint((1,"bogus handler test: \"%s\"",
1782 test ? test : "?"));
1783 fs_give((void **) &cmd);
1786 fs_give((void **) &test);
1789 if(cmd && (!specific || specific_match))
1790 return(cmd);
1793 if(test)
1794 fs_give((void **) &test);
1796 if(cmd)
1797 fs_give((void **) &cmd);
1800 cmd = NULL;
1802 if(!specific){
1803 cmd = url_os_specified_browser(url);
1805 * Last chance, anything handling "text/html" in mailcap...
1807 if(!cmd && mailcap_can_display(TYPETEXT, "html", NULL, 0)){
1808 MCAP_CMD_S *mc_cmd;
1810 mc_cmd = mailcap_build_command(TYPETEXT, "html",
1811 NULL, "_URL_", NULL, 0);
1813 * right now URL viewing won't return anything requiring
1814 * special handling
1816 if(mc_cmd){
1817 cmd = mc_cmd->command;
1818 fs_give((void **)&mc_cmd);
1823 return(cmd);
1826 char *
1827 img_handler(HANDLE_S *handle)
1829 char *src = handle && handle->h.img.src ? handle->h.img.src : NULL;
1830 char *cmd, *subtype;
1832 if(!src) return NULL;
1834 if(ps_global->VAR_IMAGE_VIEWER)
1835 cmd = cpystr(ps_global->VAR_IMAGE_VIEWER);
1836 else if(!struncmp(src, "IMAGE/", 6)){
1837 src += 6;
1838 for(subtype = src; src && *src != ';'; src++);
1839 if(src){
1840 *src = '\0';
1841 if(mailcap_can_display(TYPEIMAGE, subtype, NULL, 0)){
1842 MCAP_CMD_S *mc_cmd;
1844 mc_cmd = mailcap_build_command(TYPEIMAGE, subtype,
1845 NULL, "_IMG_", NULL, 0);
1846 if(mc_cmd){
1847 cmd = mc_cmd->command;
1848 fs_give((void **)&mc_cmd);
1851 *src = ';';
1855 return cmd;
1858 url_tool_t
1859 url_local_handler(char *s)
1861 int i;
1862 static struct url_t {
1863 char *url;
1864 short len;
1865 url_tool_t f;
1866 } handlers[] = {
1867 {"mailto:", 7, url_local_mailto}, /* see url_tool_t def's */
1868 {"imap://", 7, url_local_imap}, /* for explanations */
1869 {"nntp://", 7, url_local_nntp},
1870 {"file://", 7, url_local_file},
1871 #ifdef ENABLE_LDAP
1872 {"ldap://", 7, url_local_ldap},
1873 #endif
1874 {"news:", 5, url_local_news},
1875 {"x-alpine-ical:", 14, ical_send_reply},
1876 {"x-alpine-gripe:", 15, gripe_gripe_to},
1877 {"x-alpine-help:", 14, url_local_helper},
1878 {"x-pine-help:", 12, url_local_helper},
1879 {"x-alpine-config:", 16, url_local_config},
1880 {"x-alpine-cert:", 14, url_local_certdetails},
1881 {"#", 1, url_local_fragment},
1882 {NULL, 0, NULL}
1885 for(i = 0; handlers[i].url ; i++)
1886 if(!struncmp(s, handlers[i].url, handlers[i].len))
1887 return(handlers[i].f);
1889 return(NULL);
1895 * mailto URL digester ala draft-hoffman-mailto-url-02.txt
1898 url_local_mailto(char *url)
1900 return(url_local_mailto_and_atts(url, NULL));
1904 url_local_mailto_and_atts(char *url, PATMT *attachlist)
1906 ENVELOPE *outgoing;
1907 BODY *body = NULL;
1908 REPLY_S fake_reply;
1909 char *sig, *urlp, *p, *hname, *hvalue;
1910 int rv = 0;
1911 long rflags;
1912 int was_a_body = 0, impl, template_len = 0;
1913 char *fcc = NULL;
1914 PAT_STATE dummy;
1915 REDRAFT_POS_S *redraft_pos = NULL;
1916 ACTION_S *role = NULL;
1918 outgoing = mail_newenvelope();
1919 body = mail_newbody();
1920 body->type = TYPETEXT;
1921 if((body->contents.text.data = (void *) so_get(PicoText,NULL,EDIT_ACCESS)) != NULL){
1923 * URL format is:
1925 * mailtoURL = "mailto:" [ to ] [ headers ]
1926 * to = #mailbox
1927 * headers = "?" header *( "&" header )
1928 * header = hname "=" hvalue
1929 * hname = *urlc
1930 * hvalue = *urlc
1932 * NOTE2: "from" and "bcc" are intentionally excluded from
1933 * the list of understood "header" fields
1935 if((p = strchr(urlp = cpystr(url+7), '?')) != NULL)
1936 *p++ = '\0'; /* headers? Tie off mailbox */
1938 /* grok mailbox as first "to", then roll thru specific headers */
1939 if(*urlp)
1940 rfc822_parse_adrlist(&outgoing->to,
1941 rfc1738_str(urlp),
1942 ps_global->maildomain);
1944 while(p){
1945 /* Find next "header" */
1946 if((p = strchr(hname = p, '&')) != NULL)
1947 *p++ = '\0'; /* tie off "header" */
1949 if((hvalue = strchr(hname, '=')) != NULL)
1950 *hvalue++ = '\0'; /* tie off hname */
1952 if(!hvalue || !strucmp(hname, "subject")){
1953 char *sub = rfc1738_str(hvalue ? hvalue : hname);
1955 if(outgoing->subject){
1956 int len = strlen(outgoing->subject);
1958 fs_resize((void **) &outgoing->subject,
1959 (len + strlen(sub) + 2) * sizeof(char));
1960 snprintf(outgoing->subject + len, strlen(sub)+2, " %s", sub);
1961 outgoing->subject[len + strlen(sub) + 2 - 1] = '\0';
1963 else
1964 outgoing->subject = cpystr(sub);
1966 else if(!strucmp(hname, "to")){
1967 url_mailto_addr(&outgoing->to, hvalue);
1969 else if(!strucmp(hname, "cc")){
1970 url_mailto_addr(&outgoing->cc, hvalue);
1972 else if(!strucmp(hname, "bcc")){
1973 q_status_message(SM_ORDER, 3, 4,
1974 "\"Bcc\" header in mailto url ignored");
1976 else if(!strucmp(hname, "from")){
1977 q_status_message(SM_ORDER, 3, 4,
1978 "\"From\" header in mailto url ignored");
1980 else if(!strucmp(hname, "body")){
1981 char *sub = rfc1738_str(hvalue ? hvalue : "");
1983 so_puts((STORE_S *)body->contents.text.data, sub);
1984 so_puts((STORE_S *)body->contents.text.data, NEWLINE);
1985 was_a_body++;
1989 fs_give((void **) &urlp);
1991 if(ps_global->reply.role_chosen == NULL){
1992 rflags = ROLE_COMPOSE;
1993 if(nonempty_patterns(rflags, &dummy)){
1994 role = set_role_from_msg(ps_global, rflags, -1L, NULL);
1995 if(confirm_role(rflags, &role))
1996 role = combine_inherited_role(role);
1997 else{ /* cancel */
1998 role = NULL;
1999 cmd_cancelled("Composition");
2000 goto outta_here;
2004 else
2005 role = ps_global->reply.role_chosen;
2007 if(role)
2008 q_status_message1(SM_ORDER, 3, 4, "Composing using role \"%s\"",
2009 role->nick);
2010 outgoing->message_id = generate_message_id(role);
2012 if(!was_a_body && role && role->template){
2013 char *filtered;
2015 impl = 1; /* leave cursor in header if not explicit */
2016 filtered = detoken(role, NULL, 0, 0, 0, &redraft_pos, &impl);
2017 if(filtered){
2018 if(*filtered){
2019 so_puts((STORE_S *)body->contents.text.data, filtered);
2020 if(impl == 1)
2021 template_len = strlen(filtered);
2024 fs_give((void **)&filtered);
2027 else
2028 impl = 1;
2030 if(!was_a_body && (sig = detoken(role, NULL, 2, 0, 1, &redraft_pos,
2031 &impl))){
2032 if(impl == 2)
2033 redraft_pos->offset += template_len;
2035 if(*sig)
2036 so_puts((STORE_S *)body->contents.text.data, sig);
2038 fs_give((void **)&sig);
2041 memset((void *)&fake_reply, 0, sizeof(fake_reply));
2042 fake_reply.pseudo = 1;
2043 fake_reply.data.pico_flags = (outgoing->subject) ? P_BODY : P_HEADEND;
2046 if(!(role && role->fcc))
2047 fcc = get_fcc_based_on_to(outgoing->to);
2049 if(attachlist)
2050 create_message_body(&body, attachlist, 0);
2052 pine_send(outgoing, &body, "\"MAILTO\" COMPOSE",
2053 role, fcc, &fake_reply, redraft_pos, NULL, NULL, PS_STICKY_TO);
2054 rv++;
2055 ps_global->mangled_screen = 1;
2057 else
2058 q_status_message(SM_ORDER | SM_DING, 3, 4,
2059 _("Can't create space for composer"));
2061 outta_here:
2062 if(outgoing)
2063 mail_free_envelope(&outgoing);
2065 if(body)
2066 pine_free_body(&body);
2068 if(fcc)
2069 fs_give((void **)&fcc);
2071 free_redraft_pos(&redraft_pos);
2072 free_action(&role);
2073 ps_global->reply.role_chosen = NULL;
2075 return(rv);
2079 void
2080 url_mailto_addr(struct mail_address **a, char *a_raw)
2082 char *p = cpystr(rfc1738_str(a_raw));
2084 while(*a) /* append to address list */
2085 a = &(*a)->next;
2087 rfc822_parse_adrlist(a, p, ps_global->maildomain);
2088 fs_give((void **) &p);
2093 * imap URL digester ala RFC 2192
2096 url_local_imap(char *url)
2098 char *folder, *mailbox = NULL, *errstr = NULL, *search = NULL,
2099 newfolder[MAILTMPLEN];
2100 int rv;
2101 long i;
2102 imapuid_t uid = 0L, uid_val = 0L;
2103 CONTEXT_S *fake_context;
2104 MESSAGECACHE *mc;
2106 rv = url_imap_folder(url, &folder, &uid, &uid_val, &search, 0);
2107 switch(rv & URL_IMAP_MASK){
2108 case URL_IMAP_IMAILBOXLIST :
2109 /* BUG: deal with lsub tag */
2110 if((fake_context = new_context(folder, NULL)) != NULL){
2111 newfolder[0] = '\0';
2112 if(display_folder_list(&fake_context, newfolder,
2113 0, folders_for_goto))
2114 if(strlen(newfolder) + 1 < MAILTMPLEN)
2115 mailbox = newfolder;
2117 else
2118 errstr = "Problem building URL's folder list";
2120 fs_give((void **) &folder);
2121 free_context(&fake_context);
2123 if(mailbox){
2124 return(1);
2126 else if(errstr)
2127 q_status_message(SM_ORDER|SM_DING, 3, 3, errstr);
2128 else
2129 cmd_cancelled("URL Launch");
2131 break;
2133 case URL_IMAP_IMESSAGEPART :
2134 case URL_IMAP_IMESSAGELIST :
2135 if(ps_global && ps_global->ttyo){
2136 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2137 ps_global->mangled_footer = 1;
2140 rv = do_broach_folder(folder, NULL, NULL, 0L);
2141 fs_give((void **) &folder);
2142 switch(rv){
2143 case -1 : /* utter failure */
2144 ps_global->next_screen = main_menu_screen;
2145 break;
2147 case 0 : /* same folder reopened */
2148 ps_global->next_screen = mail_index_screen;
2149 break;
2151 case 1 : /* requested folder open */
2152 ps_global->next_screen = mail_index_screen;
2154 if(uid_val && uid_val != ps_global->mail_stream->uid_validity){
2155 /* Complain! */
2156 q_status_message(SM_ORDER|SM_DING, 3, 3,
2157 "Warning! Referenced folder changed since URL recorded");
2160 if(uid){
2162 * Make specified message the currently selected..
2164 for(i = 1L; i <= mn_get_total(ps_global->msgmap); i++)
2165 if(mail_uid(ps_global->mail_stream, i) == uid){
2166 ps_global->next_screen = mail_view_screen;
2167 mn_set_cur(ps_global->msgmap, i);
2168 break;
2171 if(i > mn_get_total(ps_global->msgmap))
2172 q_status_message(SM_ORDER, 2, 3,
2173 "Couldn't find specified article number");
2175 else if(search){
2177 * Select the specified messages
2178 * and present a zoom'd index...
2180 /* BUG: not dealing with CHARSET yet */
2182 /* ANOTHER BUG: mail_criteria is a compatibility routine for IMAP2BIS
2183 * so it doesn't know about IMAP4 search criteria, like SENTSINCE.
2184 * It also doesn't handle literals. */
2186 pine_mail_search_full(ps_global->mail_stream, NULL,
2187 mail_criteria(search),
2188 SE_NOPREFETCH | SE_FREE);
2190 for(i = 1L; i <= mn_get_total(ps_global->msgmap); i++)
2191 if(ps_global->mail_stream
2192 && i <= ps_global->mail_stream->nmsgs
2193 && (mc = mail_elt(ps_global->mail_stream, i))
2194 && mc->searched)
2195 set_lflag(ps_global->mail_stream,
2196 ps_global->msgmap, i, MN_SLCT, 1);
2198 if((i = any_lflagged(ps_global->msgmap, MN_SLCT)) != 0){
2200 q_status_message2(SM_ORDER, 0, 3,
2201 "%s message%s selected",
2202 long2string(i), plural(i));
2203 /* Zoom the index! */
2204 zoom_index(ps_global, ps_global->mail_stream,
2205 ps_global->msgmap, MN_SLCT);
2210 return(1);
2212 default:
2213 case URL_IMAP_ERROR :
2214 break;
2217 return(0);
2222 url_local_nntp(char *url)
2224 char folder[2*MAILTMPLEN], *group;
2225 int group_len;
2226 long i, article_num;
2228 /* no hostport, no url, end of story */
2229 if((group = strchr(url + 7, '/')) != 0){
2230 group++;
2231 for(group_len = 0; group[group_len] && group[group_len] != '/';
2232 group_len++)
2233 if(!rfc1738_group(&group[group_len]))
2234 /* TRANSLATORS: these are errors in news group URLs */
2235 return(url_bogus(url, _("Invalid newsgroup specified")));
2237 if(group_len){
2238 snprintf(folder, sizeof(folder), "{%.*s/nntp}#news.%.*s",
2239 (int) MIN((group - 1) - (url + 7), MAILTMPLEN-20), url + 7,
2240 (int) MIN(group_len, MAILTMPLEN-20), group);
2241 folder[sizeof(folder)-1] = '\0';
2243 else
2244 return(url_bogus(url, _("No newsgroup specified")));
2246 else
2247 return(url_bogus(url, _("No server specified")));
2249 if(ps_global && ps_global->ttyo){
2250 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2251 ps_global->mangled_footer = 1;
2254 switch(do_broach_folder(rfc1738_str(folder), NULL, NULL, 0L)){
2255 case -1 : /* utter failure */
2256 ps_global->next_screen = main_menu_screen;
2257 break;
2259 case 0 : /* same folder reopened */
2260 ps_global->next_screen = mail_index_screen;
2261 break;
2263 case 1 : /* requested folder open */
2264 ps_global->next_screen = mail_index_screen;
2266 /* grok article number --> c-client UID */
2267 if(group[group_len++] == '/'
2268 && (article_num = atol(&group[group_len]))){
2270 * Make the requested article our current message
2272 for(i = 1; i <= mn_get_nmsgs(ps_global->msgmap); i++)
2273 if(mail_uid(ps_global->mail_stream, i) == article_num){
2274 ps_global->next_screen = mail_view_screen;
2275 if((i = mn_raw2m(ps_global->msgmap, i)) != 0)
2276 mn_set_cur(ps_global->msgmap, i);
2277 break;
2280 if(i == 0 || i > mn_get_total(ps_global->msgmap))
2281 q_status_message(SM_ORDER, 2, 3,
2282 _("Couldn't find specified article number"));
2285 break;
2288 ps_global->redrawer = (void(*)(void))NULL;
2289 return(1);
2294 url_local_news(char *url)
2296 char folder[MAILTMPLEN], *p;
2297 CONTEXT_S *cntxt = NULL;
2300 * NOTE: NO SUPPORT for '*' or message-id
2302 if(*(url+5)){
2303 if(*(url+5) == '/' && *(url+6) == '/')
2304 return(url_local_nntp(url)); /* really meant "nntp://"? */
2306 if(ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0])
2308 * BUG: Only the first NNTP server is tried.
2310 snprintf(folder, sizeof(folder), "{%s/nntp}#news.", ps_global->VAR_NNTP_SERVER[0]);
2311 else
2312 strncpy(folder, "#news.", sizeof(folder));
2314 folder[sizeof(folder)-1] = '\0';
2316 for(p = strncpy(folder + strlen(folder), url + 5, sizeof(folder)-strlen(folder)-1);
2317 *p && rfc1738_group(p);
2318 p++)
2321 if(*p)
2322 return(url_bogus(url, "Invalid newsgroup specified"));
2324 else{ /* fish first group from newsrc */
2325 FOLDER_S *f;
2326 int alphaorder;
2328 folder[0] = '\0';
2330 /* Find first news context */
2331 for(cntxt = ps_global->context_list;
2332 cntxt && !(cntxt->use & CNTXT_NEWS);
2333 cntxt = cntxt->next)
2336 if(cntxt){
2337 if((alphaorder = F_OFF(F_READ_IN_NEWSRC_ORDER, ps_global)) != 0)
2338 (void) F_SET(F_READ_IN_NEWSRC_ORDER, ps_global, 1);
2340 build_folder_list(NULL, cntxt, NULL, NULL, BFL_LSUB);
2341 if((f = folder_entry(0, FOLDERS(cntxt))) != NULL){
2342 strncpy(folder, f->name, sizeof(folder));
2343 folder[sizeof(folder)-1] = '\0';
2346 free_folder_list(cntxt);
2348 if(alphaorder)
2349 (void) F_SET(F_READ_IN_NEWSRC_ORDER, ps_global, 0);
2352 if(folder[0] == '\0'){
2353 q_status_message(SM_ORDER | SM_DING, 3, 3,
2354 "No default newsgroup");
2355 return(0);
2359 if(ps_global && ps_global->ttyo){
2360 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2361 ps_global->mangled_footer = 1;
2364 if(do_broach_folder(rfc1738_str(folder), cntxt, NULL, 0L) < 0)
2365 ps_global->next_screen = main_menu_screen;
2366 else
2367 ps_global->next_screen = mail_index_screen;
2369 ps_global->redrawer = (void(*)(void))NULL;
2371 return(1);
2376 url_local_file(char *file_url)
2378 if(want_to(
2379 /* TRANSLATORS: this is a warning that the file URL can cause programs to run which may
2380 be a security problem. We are asking the user to confirm that they want to do this. */
2381 _("\"file\" URL may cause programs to be run on your system. Run anyway"),
2382 'n', 0, NO_HELP, WT_NORM) == 'y'){
2383 HANDLE_S handle;
2385 /* fake a handle */
2386 handle.h.url.path = file_url;
2387 if((handle.h.url.tool = url_external_handler(&handle, 1))
2388 || (handle.h.url.tool = url_external_handler(&handle, 0))){
2389 url_launch(&handle);
2390 return 1;
2392 else{
2393 q_status_message(SM_ORDER, 0, 4,
2394 _("No viewer for \"file\" URL. VIEWER command cancelled"));
2395 return 0;
2398 q_status_message(SM_ORDER, 0, 4, _("VIEWER command cancelled"));
2399 return 0;
2404 url_local_fragment(char *fragment)
2406 SCRLCTRL_S *st = scroll_state(SS_CUR);
2407 HANDLE_S *hp = NULL;
2410 * find a handle with the fragment's name
2412 if(st){
2413 for(hp = st->parms->text.handles; hp; hp = hp->next)
2414 if(hp->type == URL && hp->h.url.name
2415 && !strcmp(hp->h.url.name, fragment + 1))
2416 break;
2418 if(!hp)
2419 for(hp = st->parms->text.handles->prev; hp; hp = hp->prev)
2420 if(hp->type == URL && hp->h.url.name
2421 && !strcmp(hp->h.url.name, fragment + 1))
2422 break;
2425 * set the top line of the display to contain this line
2427 if(hp && hp->loc){
2428 st->top_text_line = hp->loc->where.row;
2429 ps_global->mangled_body = 1;
2431 else
2432 q_status_message1(SM_ORDER | SM_DING, 0, 3,
2433 "Can't find fragment: %s", fragment);
2435 return(1);
2439 ical_send_reply(char *url)
2441 // ical_compose_reply(url + strlen("x-alpine-ical:"));
2442 return 2;
2447 * Format editorial comment referencing screen offering
2448 * List-* header supplied commands
2451 rfc2369_editorial(long int msgno, HANDLE_S **handlesp, int flags, int width, gf_io_t pc)
2453 char *p, *hdrp, *hdrs[MLCMD_COUNT + 1],
2454 color[64], buf[2048];
2455 int i, n, rv = TRUE;
2456 HANDLE_S *h = NULL;
2458 if((flags & FM_DISPLAY)
2459 && (hdrp = pine_fetchheader_lines(ps_global->mail_stream, msgno,
2460 NULL, rfc2369_hdrs(hdrs)))){
2461 if(*hdrp){
2462 snprintf(buf, sizeof(buf), "Note: This message contains ");
2463 buf[sizeof(buf)-1] = '\0';
2464 p = buf + strlen(buf);
2466 if(handlesp){
2467 h = new_handle(handlesp);
2468 h->type = Function;
2469 h->h.func.f = rfc2369_display;
2470 h->h.func.args.stream = ps_global->mail_stream;
2471 h->h.func.args.msgmap = ps_global->msgmap;
2472 h->h.func.args.msgno = msgno;
2474 if(!(flags & FM_NOCOLOR)
2475 && handle_start_color(color, sizeof(color), &n, 0)){
2476 if((p-buf)+n < sizeof(buf))
2477 for(i = 0; i < n; i++)
2478 *p++ = color[i];
2480 else if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)){
2481 *p++ = TAG_EMBED;
2482 *p++ = TAG_BOLDON;
2485 if((p-buf)+2 < sizeof(buf)){
2486 *p++ = TAG_EMBED;
2487 *p++ = TAG_HANDLE;
2490 snprintf(p + 1, sizeof(buf)-(p+1-buf), "%d", h->key);
2491 buf[sizeof(buf)-1] = '\0';
2492 *p = strlen(p + 1);
2493 p += (*p + 1);
2496 sstrncpy(&p, "email list management information", sizeof(buf)-(p-buf));
2497 buf[sizeof(buf)-1] = '\0';
2499 if(h){
2500 /* in case it was the current selection */
2501 if((p-buf)+2 < sizeof(buf)){
2502 *p++ = TAG_EMBED;
2503 *p++ = TAG_INVOFF;
2506 if(handle_end_color(color, sizeof(color), &n)){
2507 if((p-buf)+n < sizeof(buf))
2508 for(i = 0; i < n; i++)
2509 *p++ = color[i];
2511 else{
2512 if((p-buf)+2 < sizeof(buf)){
2513 *p++ = TAG_EMBED;
2514 *p++ = TAG_BOLDOFF;
2518 if(p-buf < sizeof(buf))
2519 *p = '\0';
2522 buf[sizeof(buf)-1] = '\0';
2524 rv = (gf_puts(NEWLINE, pc)
2525 && format_editorial(buf, width, flags, handlesp, pc) == NULL
2526 && gf_puts(NEWLINE, pc));
2529 fs_give((void **) &hdrp);
2532 return(rv);
2537 /*----------------------------------------------------------------------
2538 routine for displaying text on the screen.
2540 Args: sparms -- structure of args controlling what happens outside
2541 just the business of managing text scrolling
2543 This displays in three different kinds of text. One is an array of
2544 lines passed in in text_array. The other is a simple long string of
2545 characters passed in in text.
2547 The style determines what some of the error messages will be, and
2548 what commands are available as different things are appropriate for
2549 help text than for message text etc.
2551 ---*/
2554 scrolltool(SCROLL_S *sparms)
2556 register long cur_top_line, num_display_lines;
2557 UCS ch;
2558 int result, done, cmd, found_on, found_on_index,
2559 first_view, force, scroll_lines, km_size,
2560 cursor_row, cursor_col, km_popped, rv,
2561 nrows, ncols;
2562 char *utf8str;
2563 long jn;
2564 struct key_menu *km;
2565 HANDLE_S *next_handle;
2566 bitmap_t bitmap;
2567 OtherMenu what;
2568 Pos whereis_pos;
2570 num_display_lines = SCROLL_LINES(ps_global);
2571 km_popped = 0;
2572 ps_global->mangled_header = 1;
2573 ps_global->mangled_footer = 1;
2574 ps_global->mangled_body = !sparms->body_valid;
2575 ncols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 0;
2576 nrows = ps_global->ttyo ? ps_global->ttyo->screen_rows : 0;
2578 what = sparms->keys.what; /* which key menu to display */
2579 cur_top_line = 0;
2580 done = 0;
2581 found_on = -1;
2582 found_on_index = -1;
2583 first_view = 1;
2584 if(sparms->quell_first_view)
2585 first_view = 0;
2587 force = 0;
2588 ch = 'x'; /* for first time through */
2589 whereis_pos.row = 0;
2590 whereis_pos.col = 0;
2591 next_handle = sparms->text.handles;
2593 set_scroll_text(sparms, cur_top_line, scroll_state(SS_NEW));
2594 format_scroll_text();
2596 if((km = sparms->keys.menu) != NULL){
2597 memcpy(bitmap, sparms->keys.bitmap, sizeof(bitmap_t));
2599 else{
2600 setbitmap(bitmap);
2601 km = &simple_text_keymenu;
2602 #ifdef _WINDOWS
2603 sparms->mouse.popup = simple_text_popup;
2604 #endif
2607 if(!sparms->bar.title)
2608 sparms->bar.title = "Text";
2610 if(sparms->bar.style == TitleBarNone){
2611 if(THREADING() && sp_viewing_a_thread(ps_global->mail_stream))
2612 sparms->bar.style = ThrdMsgPercent;
2613 else
2614 sparms->bar.style = MsgTextPercent;
2617 switch(sparms->start.on){
2618 case LastPage :
2619 cur_top_line = MAX(0, scroll_text_lines() - (num_display_lines-2));
2620 if(F_ON(F_SHOW_CURSOR, ps_global)){
2621 whereis_pos.row = scroll_text_lines() - cur_top_line;
2622 found_on = scroll_text_lines() - 1;
2625 break;
2627 case Fragment :
2628 if(sparms->start.loc.frag){
2629 (void) url_local_fragment(sparms->start.loc.frag);
2631 cur_top_line = scroll_state(SS_CUR)->top_text_line;
2633 if(F_ON(F_SHOW_CURSOR, ps_global)){
2634 whereis_pos.row = scroll_text_lines() - cur_top_line;
2635 found_on = scroll_text_lines() - 1;
2639 break;
2641 case Offset :
2642 if(sparms->start.loc.offset){
2643 for(cur_top_line = 0L;
2644 cur_top_line + 1 < scroll_text_lines()
2645 && (sparms->start.loc.offset
2646 -= scroll_handle_column(cur_top_line,
2647 -1)) >= 0;
2648 cur_top_line++)
2652 break;
2654 case Handle :
2655 if(scroll_handle_obscured(sparms->text.handles))
2656 cur_top_line = scroll_handle_reframe(-1, TRUE);
2658 break;
2660 default : /* no-op */
2661 break;
2664 /* prepare for calls below to tell us where to go */
2665 ps_global->next_screen = SCREEN_FUN_NULL;
2667 cancel_busy_cue(-1);
2669 while(!done) {
2670 ps_global->user_says_cancel = 0;
2671 if(km_popped){
2672 km_popped--;
2673 if(km_popped == 0){
2674 clearfooter(ps_global);
2675 ps_global->mangled_body = 1;
2679 if(ps_global->mangled_screen) {
2680 ps_global->mangled_header = 1;
2681 ps_global->mangled_footer = 1;
2682 ps_global->mangled_body = 1;
2685 if(!sparms->quell_newmail && streams_died())
2686 ps_global->mangled_header = 1;
2688 dprint((9, "@@@@ current:%ld\n",
2689 mn_get_cur(ps_global->msgmap)));
2692 /*==================== All Screen painting ====================*/
2693 /*-------------- The title bar ---------------*/
2694 update_scroll_titlebar(cur_top_line, ps_global->mangled_header);
2696 if(ps_global->mangled_screen){
2697 /* this is the only line not cleared by header, body or footer
2698 * repaint calls....
2700 ClearLine(1);
2701 ps_global->mangled_screen = 0;
2704 /*---- Scroll or update the body of the text on the screen -------*/
2705 cur_top_line = scroll_scroll_text(cur_top_line, next_handle,
2706 ps_global->mangled_body);
2707 ps_global->redrawer = redraw_scroll_text;
2708 ps_global->mangled_body = 0;
2710 /*--- Check to see if keymenu might change based on next_handle --*/
2711 if(sparms->text.handles != next_handle)
2712 ps_global->mangled_footer = 1;
2714 if(next_handle)
2715 sparms->text.handles = next_handle;
2717 /*------------- The key menu footer --------------------*/
2718 if(ps_global->mangled_footer || sparms->keys.each_cmd){
2719 if(km_popped){
2720 FOOTER_ROWS(ps_global) = 3;
2721 clearfooter(ps_global);
2724 if(F_ON(F_ARROW_NAV, ps_global)){
2725 menu_clear_binding(km, KEY_LEFT);
2726 if((cmd = menu_clear_binding(km, '<')) != MC_UNKNOWN){
2727 menu_add_binding(km, '<', cmd);
2728 menu_add_binding(km, KEY_LEFT, cmd);
2732 if(F_ON(F_ARROW_NAV, ps_global)){
2733 menu_clear_binding(km, KEY_RIGHT);
2734 if((cmd = menu_clear_binding(km, '>')) != MC_UNKNOWN){
2735 menu_add_binding(km, '>', cmd);
2736 menu_add_binding(km, KEY_RIGHT, cmd);
2740 if(sparms->keys.each_cmd){
2741 (*sparms->keys.each_cmd)(sparms,
2742 scroll_handle_obscured(sparms->text.handles));
2743 memcpy(bitmap, sparms->keys.bitmap, sizeof(bitmap_t));
2746 if(menu_binding_index(km, MC_JUMP) >= 0){
2747 for(cmd = 0; cmd < 10; cmd++)
2748 if(F_ON(F_ENABLE_JUMP, ps_global))
2749 (void) menu_add_binding(km, '0' + cmd, MC_JUMP);
2750 else
2751 (void) menu_clear_binding(km, '0' + cmd);
2754 draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
2755 1-FOOTER_ROWS(ps_global), 0, what);
2756 what = SameMenu;
2757 ps_global->mangled_footer = 0;
2758 if(km_popped){
2759 FOOTER_ROWS(ps_global) = 1;
2760 mark_keymenu_dirty();
2764 if((ps_global->first_time_user || ps_global->show_new_version)
2765 && first_view && sparms->text.handles
2766 && (sparms->text.handles->next || sparms->text.handles->prev)
2767 && !sparms->quell_help)
2768 q_status_message(SM_ORDER, 0, 3, HANDLE_INIT_MSG);
2770 /*============ Check for New Mail and CheckPoint ============*/
2771 if(!sparms->quell_newmail &&
2772 new_mail(force, NM_TIMING(ch), NM_STATUS_MSG) >= 0){
2773 update_scroll_titlebar(cur_top_line, 1);
2774 if(ps_global->mangled_footer)
2775 draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
2776 1-FOOTER_ROWS(ps_global), 0, what);
2778 ps_global->mangled_footer = 0;
2782 * If an expunge of the current message happened during the
2783 * new mail check we want to bail out of here. See mm_expunged.
2785 if(ps_global->next_screen != SCREEN_FUN_NULL){
2786 done = 1;
2787 continue;
2790 if(ps_global->noticed_change_in_unseen){
2791 ps_global->noticed_change_in_unseen = 0; /* redraw only once */
2792 cmd = MC_RESIZE; /* causes cursor to be saved in folder_lister */
2793 done = 1;
2794 continue;
2797 if(first_view && num_display_lines >= scroll_text_lines())
2798 q_status_message1(SM_INFO, 0, 1, "ALL of %s", STYLE_NAME(sparms));
2801 force = 0; /* may not need to next time around */
2802 first_view = 0; /* check_point a priority any more? */
2804 /*==================== Output the status message ==============*/
2805 if(!sparms->no_stat_msg){
2806 if(km_popped){
2807 FOOTER_ROWS(ps_global) = 3;
2808 mark_status_unknown();
2811 display_message(ch);
2812 if(km_popped){
2813 FOOTER_ROWS(ps_global) = 1;
2814 mark_status_unknown();
2818 if(F_ON(F_SHOW_CURSOR, ps_global)){
2819 #ifdef WINDOWS
2820 if(cur_top_line != scroll_state(SS_CUR)->top_text_line)
2821 whereis_pos.row = 0;
2822 #endif
2824 if(whereis_pos.row > 0){
2825 cursor_row = SCROLL_LINES_ABOVE(ps_global)
2826 + whereis_pos.row - 1;
2827 cursor_col = whereis_pos.col;
2829 else{
2830 POSLIST_S *lp = NULL;
2832 if(sparms->text.handles &&
2833 !scroll_handle_obscured(sparms->text.handles)){
2834 SCRLCTRL_S *st = scroll_state(SS_CUR);
2836 for(lp = sparms->text.handles->loc; lp; lp = lp->next)
2837 if(lp->where.row >= st->top_text_line
2838 && lp->where.row < st->top_text_line
2839 + st->screen.length){
2840 cursor_row = lp->where.row - cur_top_line
2841 + SCROLL_LINES_ABOVE(ps_global);
2842 cursor_col = lp->where.col;
2843 break;
2847 if(!lp){
2848 cursor_col = 0;
2849 /* first new line of text */
2850 cursor_row = SCROLL_LINES_ABOVE(ps_global) +
2851 ((cur_top_line == 0) ? 0 : ps_global->viewer_overlap);
2855 else{
2856 cursor_col = 0;
2857 cursor_row = ps_global->ttyo->screen_rows
2858 - SCROLL_LINES_BELOW(ps_global);
2861 MoveCursor(cursor_row, cursor_col);
2863 /*================ Get command and validate =====================*/
2864 #ifdef MOUSE
2865 #ifndef WIN32
2866 if(sparms->text.handles)
2867 #endif
2869 mouse_in_content(KEY_MOUSE, -1, -1, 0x5, 0);
2870 register_mfunc(mouse_in_content, HEADER_ROWS(ps_global), 0,
2871 ps_global->ttyo->screen_rows
2872 - (FOOTER_ROWS(ps_global) + 1),
2873 ps_global->ttyo->screen_cols);
2875 #endif
2876 #ifdef _WINDOWS
2877 mswin_allowcopy(mswin_readscrollbuf);
2878 mswin_setscrollcallback(pcpine_do_scroll);
2880 if(sparms->help.text != NO_HELP)
2881 mswin_sethelptextcallback(pcpine_help_scroll);
2883 if(sparms->text.handles
2884 && sparms->text.handles->type != Folder)
2885 mswin_mousetrackcallback(pcpine_view_cursor);
2887 if(ps_global->prev_screen == mail_view_screen)
2888 mswin_setviewinwindcallback(view_in_new_window);
2889 #endif
2890 rv = -1;
2891 if(sparms->aux_function
2892 && sparms->aux_condition
2893 && ((rv = (sparms->aux_condition)(sparms->aux_value)) == 0))
2894 (sparms->aux_function)(sparms->aux_value, sparms->aux_rv_value);
2896 if(rv == 0){
2897 ch = (sparms->decode_aux_rv_value)(sparms->aux_value, sparms->aux_rv_value);
2898 if (ch == NO_OP_COMMAND) ch = read_command(&utf8str);
2900 else if(cmd == MC_RESIZE
2901 || ps_global->ttyo == NULL
2902 || (ps_global->ttyo->screen_cols == ncols
2903 && ps_global->ttyo->screen_rows == nrows))
2904 ch = (sparms->quell_newmail || read_command_prep()) ? read_command(&utf8str) : NO_OP_COMMAND;
2905 #ifdef MOUSE
2906 #ifndef WIN32
2907 if(sparms->text.handles)
2908 #endif
2909 clear_mfunc(mouse_in_content);
2910 #endif
2911 #ifdef _WINDOWS
2912 mswin_allowcopy(NULL);
2913 mswin_setscrollcallback(NULL);
2914 mswin_sethelptextcallback(NULL);
2915 mswin_mousetrackcallback(NULL);
2916 mswin_setviewinwindcallback(NULL);
2917 cur_top_line = scroll_state(SS_CUR)->top_text_line;
2918 #endif
2919 /* we need to check if there was a resize of the screen
2920 * which did not happen in this routine but during a call
2921 * to another routine from this routing, and that routine has no
2922 * way to tell us that a resize happened
2924 if(cmd != MC_RESIZE
2925 && ps_global->ttyo
2926 && (ps_global->ttyo->screen_cols != ncols
2927 || ps_global->ttyo->screen_rows != nrows))
2928 cmd = MC_RESIZE;
2929 else
2930 cmd = menu_command(ch, km);
2932 ncols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 0;
2933 nrows = ps_global->ttyo ? ps_global->ttyo->screen_rows : 0;
2935 if(km_popped)
2936 switch(cmd){
2937 case MC_NONE :
2938 case MC_OTHER :
2939 case MC_RESIZE:
2940 case MC_REPAINT :
2941 km_popped++;
2942 break;
2944 default:
2945 clearfooter(ps_global);
2946 break;
2950 /*============= Execute command =======================*/
2951 switch(cmd){
2953 /* ------ Help -------*/
2954 case MC_HELP :
2955 if(FOOTER_ROWS(ps_global) == 1 && km_popped == 0){
2956 km_popped = 2;
2957 ps_global->mangled_footer = 1;
2958 break;
2961 whereis_pos.row = 0;
2962 if(sparms->help.text == NO_HELP){
2963 q_status_message(SM_ORDER, 0, 5,
2964 _("No help text currently available"));
2965 break;
2968 km_size = FOOTER_ROWS(ps_global);
2970 helper(sparms->help.text, sparms->help.title, 0);
2972 if(ps_global->next_screen != main_menu_screen
2973 && km_size == FOOTER_ROWS(ps_global)) {
2974 /* Have to reset because helper uses scroll_text */
2975 num_display_lines = SCROLL_LINES(ps_global);
2976 ps_global->mangled_screen = 1;
2978 else
2979 done = 1;
2981 break;
2984 /*---------- Roll keymenu ------*/
2985 case MC_OTHER :
2986 if(F_OFF(F_USE_FK, ps_global))
2987 warn_other_cmds();
2989 what = NextMenu;
2990 ps_global->mangled_footer = 1;
2991 break;
2994 /* -------- Scroll back one page -----------*/
2995 case MC_PAGEUP :
2996 whereis_pos.row = 0;
2997 if(cur_top_line) {
2998 scroll_lines = MIN(MAX(num_display_lines -
2999 ps_global->viewer_overlap, 1), num_display_lines);
3000 cur_top_line -= scroll_lines;
3001 if(cur_top_line <= 0){
3002 cur_top_line = 0;
3003 q_status_message1(SM_INFO, 0, 1, "START of %s",
3004 STYLE_NAME(sparms));
3007 else{
3008 /* hilite last available handle */
3009 next_handle = NULL;
3010 if(sparms->text.handles){
3011 HANDLE_S *h = sparms->text.handles;
3013 while((h = scroll_handle_prev_sel(h))
3014 && !scroll_handle_obscured(h))
3015 next_handle = h;
3018 if(!next_handle)
3019 q_status_message1(SM_ORDER, 0, 1, _("Already at start of %s"),
3020 STYLE_NAME(sparms));
3025 break;
3028 /*---- Scroll down one page -------*/
3029 case MC_PAGEDN :
3030 if(cur_top_line + num_display_lines < scroll_text_lines()){
3031 whereis_pos.row = 0;
3032 scroll_lines = MIN(MAX(num_display_lines -
3033 ps_global->viewer_overlap, 1), num_display_lines);
3034 cur_top_line += scroll_lines;
3036 if(cur_top_line + num_display_lines >= scroll_text_lines())
3037 q_status_message1(SM_INFO, 0, 1, "END of %s",
3038 STYLE_NAME(sparms));
3040 else if(!sparms->end_scroll
3041 || !(done = (*sparms->end_scroll)(sparms))){
3042 q_status_message1(SM_ORDER, 0, 1, _("Already at end of %s"),
3043 STYLE_NAME(sparms));
3044 /* hilite last available handle */
3045 if(sparms->text.handles){
3046 HANDLE_S *h = sparms->text.handles;
3048 while((h = scroll_handle_next_sel(h)) != NULL)
3049 next_handle = h;
3053 break;
3055 /* scroll to the top page */
3056 case MC_HOMEKEY:
3057 if(cur_top_line){
3058 cur_top_line = 0;
3059 q_status_message1(SM_INFO, 0, 1, "START of %s",
3060 STYLE_NAME(sparms));
3063 next_handle = NULL;
3064 if(sparms->text.handles){
3065 HANDLE_S *h = sparms->text.handles;
3067 while((h = scroll_handle_prev_sel(h)) != NULL)
3068 next_handle = h;
3070 break;
3072 /* scroll to the bottom page */
3073 case MC_ENDKEY:
3074 if(cur_top_line + num_display_lines < scroll_text_lines()){
3075 cur_top_line = scroll_text_lines() - MIN(5, num_display_lines);
3076 q_status_message1(SM_INFO, 0, 1, "END of %s",
3077 STYLE_NAME(sparms));
3080 if(sparms->text.handles){
3081 HANDLE_S *h = sparms->text.handles;
3083 while((h = scroll_handle_next_sel(h)) != NULL)
3084 next_handle = h;
3086 break;
3088 /*------ Scroll down one line -----*/
3089 case MC_CHARDOWN :
3090 next_handle = NULL;
3091 if(sparms->text.handles){
3092 if(sparms->vert_handle){
3093 HANDLE_S *h, *h2;
3094 int i, j, k;
3096 h2 = sparms->text.handles;
3097 if(h2->type == Folder && h2->prev && h2->prev->is_dual_do_open)
3098 h2 = h2->prev;
3100 i = h2->loc->where.row + 1;
3101 j = h2->loc->where.col;
3102 for(h = NULL, k = h2->key;
3103 h2 && (!h
3104 || (h->loc->where.row == h2->loc->where.row));
3105 h2 = h2->next)
3106 /* must be different key */
3107 /* ... below current line */
3108 /* ... pref'bly to left */
3109 if(h2->key != k
3110 && h2->loc->where.row >= i){
3111 if(h2->loc->where.col > j){
3112 if(!h)
3113 h = h2;
3115 break;
3117 else
3118 h = h2;
3121 if(h){
3122 whereis_pos.row = 0;
3123 next_handle = h;
3124 if((result = scroll_handle_obscured(next_handle)) != 0){
3125 long new_top;
3127 if(scroll_handle_obscured(sparms->text.handles)
3128 && result > 0)
3129 next_handle = sparms->text.handles;
3131 ps_global->mangled_body++;
3132 new_top = scroll_handle_reframe(next_handle->key,0);
3133 if(new_top >= 0)
3134 cur_top_line = new_top;
3138 else if(!(ch == ctrl('N') || F_ON(F_FORCE_ARROWS, ps_global)))
3139 next_handle = scroll_handle_next(sparms->text.handles);
3142 if(!next_handle){
3143 if(cur_top_line + num_display_lines < scroll_text_lines()){
3144 whereis_pos.row = 0;
3145 cur_top_line++;
3146 if(cur_top_line + num_display_lines >= scroll_text_lines())
3147 q_status_message1(SM_INFO, 0, 1, "END of %s",
3148 STYLE_NAME(sparms));
3150 else
3151 q_status_message1(SM_ORDER, 0, 1, _("Already at end of %s"),
3152 STYLE_NAME(sparms));
3155 break;
3158 /* ------ Scroll back up one line -------*/
3159 case MC_CHARUP :
3160 next_handle = NULL;
3161 if(sparms->text.handles){
3162 if(sparms->vert_handle){
3163 HANDLE_S *h, *h2;
3164 int i, j, k;
3166 h2 = sparms->text.handles;
3167 if(h2->type == Folder && h2->prev && h2->prev->is_dual_do_open)
3168 h2 = h2->prev;
3170 i = h2->loc->where.row - 1;
3171 j = h2->loc->where.col;
3173 for(h = NULL, k = h2->key;
3174 h2 && (!h
3175 || (h->loc->where.row == h2->loc->where.row));
3176 h2 = h2->prev)
3177 /* must be new key, above current
3178 * line and pref'bly to right
3180 if(h2->key != k
3181 && h2->loc->where.row <= i){
3182 if(h2->loc->where.col < j){
3183 if(!h)
3184 h = h2;
3186 break;
3188 else
3189 h = h2;
3192 if(h){
3193 whereis_pos.row = 0;
3194 next_handle = h;
3195 if((result = scroll_handle_obscured(next_handle)) != 0){
3196 long new_top;
3198 if(scroll_handle_obscured(sparms->text.handles)
3199 && result < 0)
3200 next_handle = sparms->text.handles;
3202 ps_global->mangled_body++;
3203 new_top = scroll_handle_reframe(next_handle->key,0);
3204 if(new_top >= 0)
3205 cur_top_line = new_top;
3209 else if(!(ch == ctrl('P') || F_ON(F_FORCE_ARROWS, ps_global)))
3210 next_handle = scroll_handle_prev(sparms->text.handles);
3213 if(!next_handle){
3214 whereis_pos.row = 0;
3215 if(cur_top_line){
3216 cur_top_line--;
3217 if(cur_top_line == 0)
3218 q_status_message1(SM_INFO, 0, 1, "START of %s",
3219 STYLE_NAME(sparms));
3221 else
3222 q_status_message1(SM_ORDER, 0, 1,
3223 _("Already at start of %s"),
3224 STYLE_NAME(sparms));
3227 break;
3230 case MC_NEXT_HANDLE :
3231 if((next_handle = scroll_handle_next_sel(sparms->text.handles)) != NULL){
3232 whereis_pos.row = 0;
3233 if((result = scroll_handle_obscured(next_handle)) != 0){
3234 long new_top;
3236 if(scroll_handle_obscured(sparms->text.handles)
3237 && result > 0)
3238 next_handle = sparms->text.handles;
3240 ps_global->mangled_body++;
3241 new_top = scroll_handle_reframe(next_handle->key, 0);
3242 if(new_top >= 0)
3243 cur_top_line = new_top;
3246 else{
3247 if(scroll_handle_obscured(sparms->text.handles)){
3248 long new_top;
3250 ps_global->mangled_body++;
3251 if((new_top = scroll_handle_reframe(-1, 0)) >= 0){
3252 whereis_pos.row = 0;
3253 cur_top_line = new_top;
3257 q_status_message1(SM_ORDER, 0, 1,
3258 _("Already on last item in %s"),
3259 STYLE_NAME(sparms));
3262 break;
3265 case MC_PREV_HANDLE :
3266 if((next_handle = scroll_handle_prev_sel(sparms->text.handles)) != NULL){
3267 whereis_pos.row = 0;
3268 if((result = scroll_handle_obscured(next_handle)) != 0){
3269 long new_top;
3271 if(scroll_handle_obscured(sparms->text.handles)
3272 && result < 0)
3273 next_handle = sparms->text.handles;
3275 ps_global->mangled_body++;
3276 new_top = scroll_handle_reframe(next_handle->key, 0);
3277 if(new_top >= 0)
3278 cur_top_line = new_top;
3281 else{
3282 if(scroll_handle_obscured(sparms->text.handles)){
3283 long new_top;
3285 ps_global->mangled_body++;
3286 if((new_top = scroll_handle_reframe(-1, 0)) >= 0){
3287 whereis_pos.row = 0;
3288 cur_top_line = new_top;
3292 q_status_message1(SM_ORDER, 0, 1,
3293 _("Already on first item in %s"),
3294 STYLE_NAME(sparms));
3297 break;
3300 /*------ View the current handle ------*/
3301 case MC_VIEW_HANDLE :
3302 switch(scroll_handle_obscured(sparms->text.handles)){
3303 default :
3304 case 0 :
3305 switch(scroll_handle_launch(sparms->text.handles,
3306 sparms->text.handles->force_display)){
3307 case 1 :
3308 cmd = MC_EXIT; /* propagate */
3309 done = 1;
3310 break;
3312 case -1 :
3313 cmd_cancelled(NULL);
3314 break;
3316 default :
3317 break;
3320 cur_top_line = scroll_state(SS_CUR)->top_text_line;
3321 break;
3323 case 1 :
3324 q_status_message(SM_ORDER, 0, 2, HANDLE_BELOW_ERR);
3325 break;
3327 case -1 :
3328 q_status_message(SM_ORDER, 0, 2, HANDLE_ABOVE_ERR);
3329 break;
3332 break;
3334 /*---------- Search text (where is) ----------*/
3335 case MC_WHEREIS :
3336 ps_global->mangled_footer = 1;
3337 {long start_row;
3338 int start_index, key = 0;
3339 char *report = NULL;
3341 start_row = cur_top_line;
3342 start_index = 0;
3344 if(F_ON(F_SHOW_CURSOR,ps_global)){
3345 if(found_on < 0
3346 || found_on >= scroll_text_lines()
3347 || found_on < cur_top_line
3348 || found_on >= cur_top_line + num_display_lines){
3349 start_row = cur_top_line;
3350 start_index = 0;
3352 else{
3353 if(found_on_index < 0){
3354 start_row = found_on + 1;
3355 start_index = 0;
3357 else{
3358 start_row = found_on;
3359 start_index = found_on_index+1;
3363 else if(sparms->srch_handle){
3364 HANDLE_S *h;
3366 if((h = scroll_handle_next_sel(sparms->text.handles)) != NULL){
3368 * Translate the screen's column into the
3369 * line offset to start on...
3371 * This makes it so search_text never returns -3
3372 * so we don't know it is the same match. That's
3373 * because we start well after the current handle
3374 * (at the next handle) and that causes us to
3375 * think the one we just matched on is a different
3376 * one from before. Can't think of an easy way to
3377 * fix it, though, and it isn't a big deal. We still
3378 * match, we just don't say current line contains
3379 * the only match.
3381 start_row = h->loc->where.row;
3382 start_index = scroll_handle_index(start_row, h->loc->where.col);
3384 else{
3385 /* last handle, start over at top */
3386 start_row = cur_top_line;
3387 start_index = 0;
3390 else{
3391 start_row = (found_on < 0
3392 || found_on >= scroll_text_lines()
3393 || found_on < cur_top_line
3394 || found_on >= cur_top_line + num_display_lines)
3395 ? cur_top_line : found_on + 1,
3396 start_index = 0;
3399 found_on = search_text(-FOOTER_ROWS(ps_global), start_row,
3400 start_index, &report,
3401 &whereis_pos, &found_on_index);
3403 if(found_on == -4){ /* search to top of text */
3404 whereis_pos.row = 0;
3405 whereis_pos.col = 0;
3406 found_on = 0;
3407 if(sparms->text.handles && sparms->srch_handle)
3408 key = 1;
3410 else if(found_on == -5){ /* search to bottom of text */
3411 HANDLE_S *h;
3413 whereis_pos.row = MAX(scroll_text_lines() - 1, 0);
3414 whereis_pos.col = 0;
3415 found_on = whereis_pos.row;
3416 if((h = sparms->text.handles) && sparms->srch_handle)
3418 key = h->key;
3419 while((h = h->next) != NULL);
3421 else if(found_on == -3){
3422 whereis_pos.row = found_on = start_row;
3423 found_on_index = start_index - 1;
3424 q_status_message(SM_ORDER, 1, 3,
3425 _("Current line contains the only match"));
3428 if(found_on >= 0){
3429 result = found_on < cur_top_line;
3430 if(!key)
3431 key = (sparms->text.handles)
3432 ? dot_on_handle(found_on, whereis_pos.col) : 0;
3434 if(F_ON(F_FORCE_LOW_SPEED,ps_global)
3435 || ps_global->low_speed
3436 || F_ON(F_SHOW_CURSOR,ps_global)
3437 || key){
3438 if((found_on >= cur_top_line + num_display_lines ||
3439 found_on < cur_top_line) &&
3440 num_display_lines > ps_global->viewer_overlap){
3441 cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
3442 if(scroll_text_lines()-cur_top_line < 5)
3443 cur_top_line = MAX(0,
3444 scroll_text_lines()-MIN(5,num_display_lines));
3446 /* else leave cur_top_line alone */
3448 else{
3449 cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
3450 if(scroll_text_lines()-cur_top_line < 5)
3451 cur_top_line = MAX(0,
3452 scroll_text_lines()-MIN(5,num_display_lines));
3455 whereis_pos.row = whereis_pos.row - cur_top_line + 1;
3456 if(report)
3457 q_status_message(SM_ORDER, 0, 3, report);
3458 else
3459 q_status_message2(SM_ORDER, 0, 3,
3460 "%sFound on line %s on screen",
3461 result ? "Search wrapped to start. " : "",
3462 int2string(whereis_pos.row));
3464 if(key){
3465 if(sparms->text.handles->key < key)
3466 for(next_handle = sparms->text.handles->next;
3467 next_handle->key != key;
3468 next_handle = next_handle->next)
3470 else
3471 for(next_handle = sparms->text.handles;
3472 next_handle->key != key;
3473 next_handle = next_handle->prev)
3477 else if(found_on == -1)
3478 cmd_cancelled("Search");
3479 else
3480 q_status_message(SM_ORDER, 0, 3, _("Word not found"));
3483 break;
3486 /*-------------- jump command -------------*/
3487 /* NOTE: preempt the process_cmd() version because
3488 * we need to get at the number..
3490 case MC_JUMP :
3491 jn = jump_to(ps_global->msgmap, -FOOTER_ROWS(ps_global), ch,
3492 sparms, View);
3493 if(sparms && sparms->jump_is_debug)
3494 done = 1;
3495 else if(jn > 0 && jn != mn_get_cur(ps_global->msgmap)){
3497 if(mn_total_cur(ps_global->msgmap) > 1L)
3498 mn_reset_cur(ps_global->msgmap, jn);
3499 else
3500 mn_set_cur(ps_global->msgmap, jn);
3502 done = 1;
3504 else
3505 ps_global->mangled_footer = 1;
3507 break;
3510 #ifdef MOUSE
3511 /*-------------- Mouse Event -------------*/
3512 case MC_MOUSE:
3514 MOUSEPRESS mp;
3515 long line;
3516 int key;
3518 mouse_get_last (NULL, &mp);
3519 mp.row -= 2;
3521 /* The clicked line have anything special on it? */
3522 if((line = cur_top_line + mp.row) < scroll_text_lines()
3523 && (key = dot_on_handle(line, mp.col))){
3524 switch(mp.button){
3525 case M_BUTTON_RIGHT :
3526 #ifdef _WINDOWS
3527 if(sparms->mouse.popup){
3528 if(sparms->text.handles->key < key)
3529 for(next_handle = sparms->text.handles->next;
3530 next_handle->key != key;
3531 next_handle = next_handle->next)
3533 else
3534 for(next_handle = sparms->text.handles;
3535 next_handle->key != key;
3536 next_handle = next_handle->prev)
3539 if(sparms->mouse.popup){
3540 cur_top_line = scroll_scroll_text(cur_top_line,
3541 next_handle,
3542 ps_global->mangled_body);
3543 fflush(stdout);
3544 switch((*sparms->mouse.popup)(sparms, key)){
3545 case 1 :
3546 cur_top_line = doubleclick_handle(sparms, next_handle, &cmd, &done);
3547 break;
3549 case 2 :
3550 done++;
3551 break;
3556 #endif
3557 break;
3559 case M_BUTTON_LEFT :
3560 if(sparms->text.handles->key < key)
3561 for(next_handle = sparms->text.handles->next;
3562 next_handle->key != key;
3563 next_handle = next_handle->next)
3565 else
3566 for(next_handle = sparms->text.handles;
3567 next_handle->key != key;
3568 next_handle = next_handle->prev)
3571 if(mp.doubleclick) /* launch url */
3572 cur_top_line = doubleclick_handle(sparms, next_handle, &cmd, &done);
3573 else if(sparms->mouse.click)
3574 (*sparms->mouse.click)(sparms);
3576 break;
3578 case M_BUTTON_MIDDLE : /* NO-OP for now */
3579 break;
3581 default: /* just ignore */
3582 break;
3585 #ifdef _WINDOWS
3586 else if(mp.button == M_BUTTON_RIGHT){
3588 * Toss generic popup on to the screen
3590 if(sparms->mouse.popup)
3591 if((*sparms->mouse.popup)(sparms, 0) == 2){
3592 done++;
3595 #endif
3598 break;
3599 #endif /* MOUSE */
3602 /*-------------- Display Resize -------------*/
3603 case MC_RESIZE :
3604 if(sparms->resize_exit){
3605 long line;
3608 * Figure out char offset of the char in the top left
3609 * corner of the display. Pass it back to the
3610 * fetcher/formatter and have it pass the offset
3611 * back to us...
3613 sparms->start.on = Offset;
3614 for(sparms->start.loc.offset = line = 0L;
3615 line < cur_top_line;
3616 line++)
3617 sparms->start.loc.offset += scroll_handle_column(line, -1);
3619 done = 1;
3620 ClearLine(1);
3621 break;
3623 /* else no reformatting necessary, fall thru to repaint */
3626 /*-------------- refresh -------------*/
3627 case MC_REPAINT :
3628 num_display_lines = SCROLL_LINES(ps_global);
3629 mark_status_dirty();
3630 mark_keymenu_dirty();
3631 mark_titlebar_dirty();
3632 ps_global->mangled_screen = 1;
3633 force = 1;
3634 break;
3637 /*------- no op timeout to check for new mail ------*/
3638 case MC_NONE :
3639 break;
3642 /*------- Forward displayed text ------*/
3643 case MC_FWDTEXT :
3644 forward_text(ps_global, sparms->text.text, sparms->text.src);
3645 break;
3648 /*----------- Save the displayed text ------------*/
3649 case MC_SAVETEXT :
3650 (void)simple_export(ps_global, sparms->text.text,
3651 sparms->text.src, "text", NULL);
3652 break;
3655 /*----------- Exit this screen ------------*/
3656 case MC_EXIT :
3657 done = 1;
3658 break;
3661 /*----------- Pop back to the Main Menu ------------*/
3662 case MC_MAIN :
3663 ps_global->next_screen = main_menu_screen;
3664 done = 1;
3665 break;
3668 /*----------- Print ------------*/
3669 case MC_PRINTTXT :
3670 print_to_printer(sparms);
3671 break;
3674 /* ------- First handle on Line ------ */
3675 case MC_GOTOBOL :
3676 if(sparms->text.handles){
3677 next_handle = scroll_handle_boundary(sparms->text.handles,
3678 scroll_handle_prev_sel);
3680 break;
3682 /* fall thru as bogus */
3684 /* ------- Last handle on Line ------ */
3685 case MC_GOTOEOL :
3686 if(sparms->text.handles){
3687 next_handle = scroll_handle_boundary(sparms->text.handles,
3688 scroll_handle_next_sel);
3690 break;
3692 /* fall thru as bogus */
3694 /*------- BOGUS INPUT ------*/
3695 case MC_CHARRIGHT :
3696 case MC_CHARLEFT :
3697 case MC_UNKNOWN :
3698 if(sparms->bogus_input)
3699 done = (*sparms->bogus_input)(ch);
3700 else
3701 bogus_command(ch, F_ON(F_USE_FK,ps_global) ? "F1" : "?");
3703 break;
3706 case MC_UTF8:
3707 bogus_utf8_command(utf8str, F_ON(F_USE_FK, ps_global) ? "F1" : "?");
3708 break;
3711 /*------- Standard commands ------*/
3712 default:
3713 whereis_pos.row = 0;
3714 if(sparms->proc.tool)
3715 result = (*sparms->proc.tool)(cmd, ps_global->msgmap, sparms);
3716 else
3717 result = process_cmd(ps_global, ps_global->mail_stream,
3718 ps_global->msgmap, cmd, View, &force);
3720 dprint((7, "PROCESS_CMD return: %d\n", result));
3722 if(ps_global->next_screen != SCREEN_FUN_NULL || result == 1){
3723 done = 1;
3724 if(cmd == MC_FULLHDR){
3725 if(ps_global->full_header == 1){
3726 long line;
3729 * Figure out char offset of the char in the top left
3730 * corner of the display. Pass it back to the
3731 * fetcher/formatter and have it pass the offset
3732 * back to us...
3734 sparms->start.on = Offset;
3735 for(sparms->start.loc.offset = line = 0L;
3736 line < cur_top_line;
3737 line++)
3738 sparms->start.loc.offset +=
3739 scroll_handle_column(line, -1);
3741 else
3742 sparms->start.on = 0;
3744 switch(km->which){
3745 case 0:
3746 sparms->keys.what = FirstMenu;
3747 break;
3748 case 1:
3749 sparms->keys.what = SecondMenu;
3750 break;
3751 case 2:
3752 sparms->keys.what = ThirdMenu;
3753 break;
3754 case 3:
3755 sparms->keys.what = FourthMenu;
3756 break;
3760 else if(!scroll_state(SS_CUR)){
3761 num_display_lines = SCROLL_LINES(ps_global);
3762 ps_global->mangled_screen = 1;
3765 break;
3767 } /* End of switch() */
3769 /* Need to frame some handles? */
3770 if(sparms->text.handles
3771 && ((!next_handle
3772 && handle_on_page(sparms->text.handles, cur_top_line,
3773 cur_top_line + num_display_lines))
3774 || (next_handle
3775 && handle_on_page(next_handle, cur_top_line,
3776 cur_top_line + num_display_lines))))
3777 next_handle = scroll_handle_in_frame(cur_top_line);
3779 } /* End of while() -- loop executing commands */
3781 ps_global->redrawer = NULL; /* next statement makes this invalid! */
3782 zero_scroll_text(); /* very important to zero out on return!!! */
3783 scroll_state(SS_FREE);
3784 if(sparms->bar.color)
3785 free_color_pair(&sparms->bar.color);
3787 #ifdef _WINDOWS
3788 scroll_setrange(0L, 0L);
3789 #endif
3790 return(cmd);
3794 /*----------------------------------------------------------------------
3795 Print text on paper
3797 Args: text -- The text to print out
3798 source -- What type of source text is
3799 message -- Message for open_printer()
3800 Handling of error conditions is very poor.
3802 ----*/
3803 static int
3804 print_to_printer(SCROLL_S *sparms)
3806 char message[64];
3808 snprintf(message, sizeof(message), "%s", STYLE_NAME(sparms));
3809 message[sizeof(message)-1] = '\0';
3811 if(open_printer(message) != 0)
3812 return(-1);
3814 switch(sparms->text.src){
3815 case CharStar :
3816 if(sparms->text.text != (char *)NULL)
3817 print_text((char *)sparms->text.text);
3819 break;
3821 case CharStarStar :
3822 if(sparms->text.text != (char **)NULL){
3823 register char **t;
3825 for(t = sparms->text.text; *t != NULL; t++){
3826 print_text(*t);
3827 print_text(NEWLINE);
3831 break;
3833 case FileStar :
3834 if(sparms->text.text != (FILE *)NULL) {
3835 size_t n;
3836 int i;
3838 fseek((FILE *)sparms->text.text, 0L, 0);
3839 n = SIZEOF_20KBUF - 1;
3840 while((i = fread((void *)tmp_20k_buf, sizeof(char),
3841 n, (FILE *)sparms->text.text)) != 0) {
3842 tmp_20k_buf[i] = '\0';
3843 print_text(tmp_20k_buf);
3847 default :
3848 break;
3851 close_printer();
3852 return(0);
3856 /*----------------------------------------------------------------------
3857 Search text being viewed (help or message)
3859 Args: q_line -- The screen line to prompt for search string on
3860 start_line -- Line number in text to begin search on
3861 start_index -- Where to begin search at in first line of text
3862 cursor_pos -- position of cursor is returned to caller here
3863 (Actually, this isn't really the position of the
3864 cursor because we don't know where we are on the
3865 screen. So row is set to the line number and col
3866 is set to the right column.)
3867 offset_in_line -- Offset where match was found.
3869 Result: returns line number string was found on
3870 -1 for cancel
3871 -2 if not found
3872 -3 if only match is at start_index - 1
3873 -4 if search to first line
3874 -5 if search to last line
3875 ---*/
3877 search_text(int q_line, long int start_line, int start_index, char **report,
3878 Pos *cursor_pos, int *offset_in_line)
3880 char prompt[MAX_SEARCH+50], nsearch_string[MAX_SEARCH+1], *p;
3881 HelpType help;
3882 int rc, flags;
3883 static HISTORY_S *history = NULL;
3884 char search_string[MAX_SEARCH+1];
3885 static ESCKEY_S word_search_key[] = { { 0, 0, "", "" },
3886 {ctrl('Y'), 10, "^Y", N_("First Line")},
3887 {ctrl('V'), 11, "^V", N_("Last Line")},
3888 {KEY_UP, 30, "", ""},
3889 {KEY_DOWN, 31, "", ""},
3890 {-1, 0, NULL, NULL}
3892 #define KU_ST (3) /* index of KEY_UP */
3894 init_hist(&history, HISTSIZE);
3897 * Put the last one used in the default search_string,
3898 * not in nsearch_string.
3900 search_string[0] = '\0';
3901 if((p = get_prev_hist(history, "", 0, NULL)) != NULL){
3902 strncpy(search_string, p, sizeof(search_string));
3903 search_string[sizeof(search_string)-1] = '\0';
3906 snprintf(prompt, sizeof(prompt), _("Word to search for [%s] : "), search_string);
3907 help = NO_HELP;
3908 nsearch_string[0] = '\0';
3910 while(1) {
3911 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE | OE_KEEP_TRAILING_SPACE;
3914 * 2 is really 1 because there will be one real entry and
3915 * one entry of "" because of the get_prev_hist above.
3917 if(items_in_hist(history) > 2){
3918 word_search_key[KU_ST].name = HISTORY_UP_KEYNAME;
3919 word_search_key[KU_ST].label = HISTORY_KEYLABEL;
3920 word_search_key[KU_ST+1].name = HISTORY_DOWN_KEYNAME;
3921 word_search_key[KU_ST+1].label = HISTORY_KEYLABEL;
3923 else{
3924 word_search_key[KU_ST].name = "";
3925 word_search_key[KU_ST].label = "";
3926 word_search_key[KU_ST+1].name = "";
3927 word_search_key[KU_ST+1].label = "";
3930 rc = optionally_enter(nsearch_string, q_line, 0, sizeof(nsearch_string),
3931 prompt, word_search_key, help, &flags);
3933 if(rc == 3) {
3934 help = help == NO_HELP ? h_oe_searchview : NO_HELP;
3935 continue;
3937 else if(rc == 10){
3938 if(report)
3939 *report = _("Searched to First Line.");
3941 return(-4);
3943 else if(rc == 11){
3944 if(report)
3945 *report = _("Searched to Last Line.");
3947 return(-5);
3949 else if(rc == 30){
3950 if((p = get_prev_hist(history, nsearch_string, 0, NULL)) != NULL){
3951 strncpy(nsearch_string, p, sizeof(nsearch_string));
3952 nsearch_string[sizeof(nsearch_string)-1] = '\0';
3954 else
3955 Writechar(BELL, 0);
3957 continue;
3959 else if(rc == 31){
3960 if((p = get_next_hist(history, nsearch_string, 0, NULL)) != NULL){
3961 strncpy(nsearch_string, p, sizeof(nsearch_string));
3962 nsearch_string[sizeof(nsearch_string)-1] = '\0';
3964 else
3965 Writechar(BELL, 0);
3967 continue;
3970 if(rc != 4){ /* 4 is redraw */
3971 save_hist(history, nsearch_string, 0, NULL);
3972 break;
3976 if(rc == 1 || (search_string[0] == '\0' && nsearch_string[0] == '\0'))
3977 return(-1);
3979 if(nsearch_string[0] != '\0'){
3980 strncpy(search_string, nsearch_string, sizeof(search_string)-1);
3981 search_string[sizeof(search_string)-1] = '\0';
3984 rc = search_scroll_text(start_line, start_index, search_string, cursor_pos,
3985 offset_in_line);
3986 return(rc);
3990 /*----------------------------------------------------------------------
3991 Update the scroll tool's titlebar
3993 Args: cur_top_line --
3994 redraw -- flag to force updating
3996 ----*/
3997 void
3998 update_scroll_titlebar(long int cur_top_line, int redraw)
4000 SCRLCTRL_S *st = scroll_state(SS_CUR);
4001 int num_display_lines = SCROLL_LINES(ps_global);
4002 long new_line = (cur_top_line + num_display_lines > st->num_lines)
4003 ? st->num_lines
4004 : cur_top_line + num_display_lines;
4005 long raw_msgno;
4006 COLOR_PAIR *returned_color = NULL;
4007 COLOR_PAIR *titlecolor = NULL;
4008 int colormatch;
4009 SEARCHSET *ss = NULL;
4011 if(st->parms->use_indexline_color
4012 && ps_global->titlebar_color_style != TBAR_COLOR_DEFAULT){
4013 raw_msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
4014 if(raw_msgno > 0L && ps_global->mail_stream
4015 && raw_msgno <= ps_global->mail_stream->nmsgs){
4016 ss = mail_newsearchset();
4017 ss->first = ss->last = (unsigned long) raw_msgno;
4020 if(ss){
4021 PAT_STATE *pstate = NULL;
4023 colormatch = get_index_line_color(ps_global->mail_stream,
4024 ss, &pstate, &returned_color);
4025 mail_free_searchset(&ss);
4028 * This is a bit tricky. If there is a colormatch but returned_color
4029 * is NULL, that means that the user explicitly wanted the
4030 * Normal color used in this index line, so that is what we
4031 * use. If no colormatch then we will use the TITLE color
4032 * instead of Normal.
4034 if(colormatch){
4035 if(returned_color)
4036 titlecolor = returned_color;
4037 else
4038 titlecolor = new_color_pair(ps_global->VAR_NORM_FORE_COLOR,
4039 ps_global->VAR_NORM_BACK_COLOR);
4042 if(titlecolor
4043 && ps_global->titlebar_color_style == TBAR_COLOR_REV_INDEXLINE){
4044 char cbuf[MAXCOLORLEN+1];
4046 strncpy(cbuf, titlecolor->fg, MAXCOLORLEN);
4047 strncpy(titlecolor->fg, titlecolor->bg, MAXCOLORLEN);
4048 strncpy(titlecolor->bg, cbuf, MAXCOLORLEN);
4052 /* Did the color change? */
4053 if((!titlecolor && st->parms->bar.color)
4055 (titlecolor && !st->parms->bar.color)
4057 (titlecolor && st->parms->bar.color
4058 && (strcmp(titlecolor->fg, st->parms->bar.color->fg)
4059 || strcmp(titlecolor->bg, st->parms->bar.color->bg)))){
4061 redraw++;
4062 if(st->parms->bar.color)
4063 free_color_pair(&st->parms->bar.color);
4065 st->parms->bar.color = titlecolor;
4066 titlecolor = NULL;
4069 if(titlecolor)
4070 free_color_pair(&titlecolor);
4074 if(redraw){
4075 set_titlebar(st->parms->bar.title, ps_global->mail_stream,
4076 ps_global->context_current, ps_global->cur_folder,
4077 ps_global->msgmap, 1, st->parms->bar.style,
4078 new_line, st->num_lines, st->parms->bar.color);
4079 ps_global->mangled_header = 0;
4081 else if(st->parms->bar.style == TextPercent)
4082 update_titlebar_lpercent(new_line);
4083 else
4084 update_titlebar_percent(new_line);
4088 /*----------------------------------------------------------------------
4089 manager of global (to this module, anyway) scroll state structures
4092 ----*/
4093 SCRLCTRL_S *
4094 scroll_state(int func)
4096 struct scrollstack {
4097 SCRLCTRL_S s;
4098 struct scrollstack *prev;
4099 } *s;
4100 static struct scrollstack *stack = NULL;
4102 switch(func){
4103 case SS_CUR: /* no op */
4104 break;
4105 case SS_NEW:
4106 s = (struct scrollstack *)fs_get(sizeof(struct scrollstack));
4107 memset((void *)s, 0, sizeof(struct scrollstack));
4108 s->prev = stack;
4109 stack = s;
4110 break;
4111 case SS_FREE:
4112 if(stack){
4113 s = stack->prev;
4114 fs_give((void **)&stack);
4115 stack = s;
4117 break;
4118 default: /* BUG: should complain */
4119 break;
4122 return(stack ? &stack->s : NULL);
4126 /*----------------------------------------------------------------------
4127 Save all the data for scrolling text and paint the screen
4130 ----*/
4131 void
4132 set_scroll_text(SCROLL_S *sparms, long int current_line, SCRLCTRL_S *st)
4134 /* save all the stuff for possible asynchronous redraws */
4135 st->parms = sparms;
4136 st->top_text_line = current_line;
4137 st->screen.start_line = SCROLL_LINES_ABOVE(ps_global);
4138 st->screen.other_lines = SCROLL_LINES_ABOVE(ps_global)
4139 + SCROLL_LINES_BELOW(ps_global);
4140 st->screen.width = -1; /* Force text formatting calculation */
4144 /*----------------------------------------------------------------------
4145 Redraw the text on the screen, possibly reformatting if necessary
4147 Args None
4149 ----*/
4150 void
4151 redraw_scroll_text(void)
4153 int i, offset;
4154 SCRLCTRL_S *st = scroll_state(SS_CUR);
4156 format_scroll_text();
4158 offset = (st->parms->text.src == FileStar) ? 0 : st->top_text_line;
4160 #ifdef _WINDOWS
4161 mswin_beginupdate();
4162 #endif
4163 /*---- Actually display the text on the screen ------*/
4164 for(i = 0; i < st->screen.length; i++){
4165 ClearLine(i + st->screen.start_line);
4166 if((offset + i) < st->num_lines)
4167 PutLine0n8b(i + st->screen.start_line, 0, st->text_lines[offset + i],
4168 st->line_lengths[offset + i], st->parms->text.handles);
4172 fflush(stdout);
4173 #ifdef _WINDOWS
4174 mswin_endupdate();
4175 #endif
4179 /*----------------------------------------------------------------------
4180 Free memory used as scrolling buffers for text on disk. Also mark
4181 text_lines as available
4182 ----*/
4183 void
4184 zero_scroll_text(void)
4186 SCRLCTRL_S *st = scroll_state(SS_CUR);
4187 register int i;
4189 for(i = 0; i < st->lines_allocated; i++)
4190 if(st->parms->text.src == FileStar && st->text_lines[i])
4191 fs_give((void **)&st->text_lines[i]);
4192 else
4193 st->text_lines[i] = NULL;
4195 if(st->parms->text.src == FileStar && st->findex != NULL){
4196 fclose(st->findex);
4197 st->findex = NULL;
4198 if(st->fname){
4199 our_unlink(st->fname);
4200 fs_give((void **)&st->fname);
4204 if(st->text_lines)
4205 fs_give((void **)&st->text_lines);
4207 if(st->line_lengths)
4208 fs_give((void **) &st->line_lengths);
4212 /*----------------------------------------------------------------------
4214 Always format at least 20 chars wide. Wrapping lines would be crazy for
4215 screen widths of 1-20 characters
4216 ----*/
4217 void
4218 format_scroll_text(void)
4220 int i;
4221 char *p, **pp;
4222 SCRLCTRL_S *st = scroll_state(SS_CUR);
4223 register short *ll;
4224 register char **tl, **tl_end;
4226 if(!st || (st->screen.width == (i = ps_global->ttyo->screen_cols)
4227 && st->screen.length == PGSIZE(st)))
4228 return;
4230 st->screen.width = MAX(20, i);
4231 st->screen.length = PGSIZE(st);
4233 if(st->lines_allocated == 0) {
4234 st->lines_allocated = TYPICAL_BIG_MESSAGE_LINES;
4235 st->text_lines = (char **)fs_get(st->lines_allocated *sizeof(char *));
4236 memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
4237 st->line_lengths = (short *)fs_get(st->lines_allocated *sizeof(short));
4240 tl = st->text_lines;
4241 ll = st->line_lengths;
4242 tl_end = &st->text_lines[st->lines_allocated];
4244 if(st->parms->text.src == CharStarStar) {
4245 /*---- original text is already list of lines -----*/
4246 /* The text could be wrapped nicely for narrow screens; for now
4247 it will get truncated as it is displayed */
4248 for(pp = (char **)st->parms->text.text; *pp != NULL;) {
4249 *tl++ = *pp++;
4250 *ll++ = st->screen.width;
4251 if(tl >= tl_end) {
4252 i = tl - st->text_lines;
4253 st->lines_allocated *= 2;
4254 fs_resize((void **)&st->text_lines,
4255 st->lines_allocated * sizeof(char *));
4256 fs_resize((void **)&st->line_lengths,
4257 st->lines_allocated*sizeof(short));
4258 tl = &st->text_lines[i];
4259 ll = &st->line_lengths[i];
4260 tl_end = &st->text_lines[st->lines_allocated];
4264 st->num_lines = tl - st->text_lines;
4266 else if (st->parms->text.src == CharStar) {
4267 /*------ Format the plain text ------*/
4268 for(p = (char *)st->parms->text.text; *p; ) {
4269 *tl = p;
4271 for(; *p && !(*p == RETURN || *p == LINE_FEED); p++)
4274 *ll = p - *tl;
4275 ll++; tl++;
4276 if(tl >= tl_end) {
4277 i = tl - st->text_lines;
4278 st->lines_allocated *= 2;
4279 fs_resize((void **)&st->text_lines,
4280 st->lines_allocated * sizeof(char *));
4281 fs_resize((void **)&st->line_lengths,
4282 st->lines_allocated*sizeof(short));
4283 tl = &st->text_lines[i];
4284 ll = &st->line_lengths[i];
4285 tl_end = &st->text_lines[st->lines_allocated];
4288 if(*p == '\r' && *(p+1) == '\n')
4289 p += 2;
4290 else if(*p == '\n' || *p == '\r')
4291 p++;
4294 st->num_lines = tl - st->text_lines;
4296 else {
4297 /*------ Display text is in a file --------*/
4300 * This is pretty much only useful under DOS where we can't fit
4301 * all of big messages in core at once. This scheme makes
4302 * some simplifying assumptions:
4303 * 1. Lines are on disk just the way we'll display them. That
4304 * is, line breaks and such are left to the function that
4305 * writes the disk file to catch and fix.
4306 * 2. We get away with this mainly because the DOS display isn't
4307 * going to be resized out from under us.
4309 * The idea is to use the already alloc'd array of char * as a
4310 * buffer for sections of what's on disk. We'll set up the first
4311 * few lines here, and read new ones in as needed in
4312 * scroll_scroll_text().
4314 * but first, make sure there are enough buffer lines allocated
4315 * to serve as a place to hold lines from the file.
4317 * Actually, this is also used under windows so the display will
4318 * be resized out from under us. So I changed the following
4319 * to always
4320 * 1. free old text_lines, which may have been allocated
4321 * for a narrow screen.
4322 * 2. insure we have enough text_lines
4323 * 3. reallocate all text_lines that are needed.
4324 * (tom unger 10/26/94)
4327 /* free old text lines, which may be too short. */
4328 for(i = 0; i < st->lines_allocated; i++)
4329 if(st->text_lines[i]) /* clear alloc'd lines */
4330 fs_give((void **)&st->text_lines[i]);
4332 /* Insure we have enough text lines. */
4333 if(st->lines_allocated < (2 * PGSIZE(st)) + 1){
4334 st->lines_allocated = (2 * PGSIZE(st)) + 1; /* resize */
4336 fs_resize((void **)&st->text_lines,
4337 st->lines_allocated * sizeof(char *));
4338 memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
4339 fs_resize((void **)&st->line_lengths,
4340 st->lines_allocated*sizeof(short));
4343 /* reallocate all text lines that are needed. */
4344 for(i = 0; i <= PGSIZE(st); i++)
4345 if(st->text_lines[i] == NULL)
4346 st->text_lines[i] = (char *)fs_get((st->screen.width + 1)
4347 * sizeof(char));
4349 tl = &st->text_lines[i];
4351 st->num_lines = make_file_index();
4353 ScrollFile(st->top_text_line); /* then load them up */
4357 * Efficiency hack. If there are handles, fill in their
4358 * line number field for later...
4360 if(st->parms->text.handles){
4361 long line;
4362 int i, col, n, key;
4363 HANDLE_S *h;
4365 for(line = 0; line < st->num_lines; line++)
4366 for(i = 0, col = 0; i < st->line_lengths[line];)
4367 switch(st->text_lines[line][i]){
4368 case TAG_EMBED:
4369 i++;
4370 switch((i < st->line_lengths[line]) ? st->text_lines[line][i]
4371 : 0){
4372 case TAG_HANDLE:
4373 for(key = 0, n = st->text_lines[line][++i]; n > 0; n--)
4374 key = (key * 10) + (st->text_lines[line][++i] - '0');
4376 i++;
4377 for(h = st->parms->text.handles; h; h = h->next)
4378 if(h->key == key){
4379 scroll_handle_set_loc(&h->loc, line, col);
4380 break;
4383 if(!h) /* anything behind us? */
4384 for(h = st->parms->text.handles->prev; h; h = h->prev)
4385 if(h->key == key){
4386 scroll_handle_set_loc(&h->loc, line, col);
4387 break;
4390 break;
4392 case TAG_FGCOLOR :
4393 case TAG_BGCOLOR :
4394 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
4395 break;
4397 case TAG_INVON:
4398 case TAG_INVOFF:
4399 case TAG_BOLDON:
4400 case TAG_BOLDOFF:
4401 case TAG_ULINEON:
4402 case TAG_ULINEOFF:
4403 i++;
4404 break;
4406 default: /* literal embed char */
4407 break;
4410 break;
4412 case TAB:
4413 i++;
4414 while(((++col) & 0x07) != 0) /* add tab's spaces */
4417 break;
4419 default:
4420 col += width_at_this_position((unsigned char*) &st->text_lines[line][i],
4421 st->line_lengths[line] - i);
4422 i++; /* character count */
4423 break;
4427 #ifdef _WINDOWS
4428 scroll_setrange (st->screen.length, st->num_lines);
4429 #endif
4431 *tl = NULL;
4436 * ScrollFile - scroll text into the st struct file making sure 'line'
4437 * of the file is the one first in the text_lines buffer.
4439 * NOTE: talk about massive potential for tuning...
4440 * Goes without saying this is still under construction
4442 void
4443 ScrollFile(long int line)
4445 SCRLCTRL_S *st = scroll_state(SS_CUR);
4446 SCRLFILE_S sf;
4447 register int i;
4449 if(line <= 0){ /* reset and load first couple of pages */
4450 fseek((FILE *) st->parms->text.text, 0L, 0);
4451 line = 0L;
4454 if(!st->text_lines)
4455 return;
4457 for(i = 0; i < PGSIZE(st); i++){
4458 /*** do stuff to get the file pointer into the right place ***/
4460 * BOGUS: this is painfully crude right now, but I just want to get
4461 * it going.
4463 * possibly in the near future, an array of indexes into the
4464 * file that are the offset for the beginning of each line will
4465 * speed things up. Of course, this
4466 * will have limits, so maybe a disk file that is an array
4467 * of indexes is the answer.
4469 if(fseek(st->findex, (size_t)(line++) * sizeof(SCRLFILE_S), 0) < 0
4470 || fread(&sf, sizeof(SCRLFILE_S), (size_t)1, st->findex) != 1
4471 || fseek((FILE *) st->parms->text.text, sf.offset, 0) < 0
4472 || !st->text_lines[i]
4473 || (sf.len && !fgets(st->text_lines[i], sf.len + 1,
4474 (FILE *) st->parms->text.text)))
4475 break;
4477 st->line_lengths[i] = sf.len;
4480 for(; i < PGSIZE(st); i++)
4481 if(st->text_lines[i]){ /* blank out any unused lines */
4482 *st->text_lines[i] = '\0';
4483 st->line_lengths[i] = 0;
4489 * make_file_index - do a single pass over the file containing the text
4490 * to display, recording line lengths and offsets.
4491 * NOTE: This is never really to be used on a real OS with virtual
4492 * memory. This is the whole reason st->findex exists. Don't
4493 * want to waste precious memory on a stupid array that could
4494 * be very large.
4496 long
4497 make_file_index(void)
4499 SCRLCTRL_S *st = scroll_state(SS_CUR);
4500 SCRLFILE_S sf;
4501 long l = 0L;
4502 int state = 0;
4504 if(!st->findex){
4505 if(!st->fname)
4506 st->fname = temp_nam(NULL, "pi");
4508 if(!st->fname || (st->findex = our_fopen(st->fname,"w+b")) == NULL){
4509 if(st->fname){
4510 our_unlink(st->fname);
4511 fs_give((void **)&st->fname);
4514 return(0);
4517 else
4518 fseek(st->findex, 0L, 0);
4520 fseek((FILE *)st->parms->text.text, 0L, 0);
4522 while(1){
4523 sf.len = st->screen.width + 1;
4524 if(scroll_file_line((FILE *) st->parms->text.text,
4525 tmp_20k_buf, &sf, &state)){
4526 fwrite((void *) &sf, sizeof(SCRLFILE_S), (size_t)1, st->findex);
4527 l++;
4529 else
4530 break;
4533 fseek((FILE *)st->parms->text.text, 0L, 0);
4535 return(l);
4539 /*----------------------------------------------------------------------
4540 Get the next line to scroll from the given file
4542 ----*/
4543 char *
4544 scroll_file_line(FILE *fp, char *buf, SCRLFILE_S *sfp, int *wrapt)
4546 register char *s = NULL;
4548 while(1){
4549 if(!s){
4550 sfp->offset = ftell(fp);
4551 if(!(s = fgets(buf, sfp->len, fp)))
4552 return(NULL); /* can't grab a line? */
4555 if(!*s){
4556 *wrapt = 1; /* remember; that we wrapped */
4557 break;
4559 else if(*s == NEWLINE[0] && (!NEWLINE[1] || *(s+1) == NEWLINE[1])){
4560 int empty = (*wrapt && s == buf);
4562 *wrapt = 0; /* turn off wrapped state */
4563 if(empty)
4564 s = NULL; /* get a new line */
4565 else
4566 break; /* done! */
4568 else
4569 s++;
4572 sfp->len = s - buf;
4573 return(buf);
4577 /*----------------------------------------------------------------------
4578 Scroll the text on the screen
4580 Args: new_top_line -- The line to be displayed on top of the screen
4581 redraw -- Flag to force a redraw even if nothing changed
4583 Returns: resulting top line
4584 Note: the returned line number may be less than new_top_line if
4585 reformatting caused the total line count to change.
4587 ----*/
4588 long
4589 scroll_scroll_text(long int new_top_line, HANDLE_S *handle, int redraw)
4591 SCRLCTRL_S *st = scroll_state(SS_CUR);
4592 int num_display_lines, l, top;
4593 POSLIST_S *lp, *lp2;
4595 /* When this is true, we're still on the same page of the display. */
4596 if(st->top_text_line == new_top_line && !redraw){
4597 /* handle changed, so hilite the new handle and unhilite the old */
4598 if(handle && handle != st->parms->text.handles){
4599 top = st->screen.start_line - new_top_line;
4600 /* hilite the new one */
4601 if(!scroll_handle_obscured(handle))
4602 for(lp = handle->loc; lp; lp = lp->next)
4603 if((l = lp->where.row) >= st->top_text_line
4604 && l < st->top_text_line + st->screen.length){
4605 if(st->parms->text.src == FileStar)
4606 l -= new_top_line;
4608 if(F_ON(F_ENABLE_DEL_WHEN_WRITING, ps_global))
4609 ClearLine(top + lp->where.row);
4610 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4611 st->line_lengths[l], handle);
4614 /* unhilite the old one */
4615 if(!scroll_handle_obscured(st->parms->text.handles))
4616 for(lp = st->parms->text.handles->loc; lp; lp = lp->next)
4617 if((l = lp->where.row) >= st->top_text_line
4618 && l < st->top_text_line + st->screen.length){
4619 for(lp2 = handle->loc; lp2; lp2 = lp2->next)
4620 if(l == lp2->where.row)
4621 break;
4623 if(!lp2){
4624 if(st->parms->text.src == FileStar)
4625 l -= new_top_line;
4627 if(F_ON(F_ENABLE_DEL_WHEN_WRITING, ps_global))
4628 ClearLine(top + lp->where.row);
4629 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4630 st->line_lengths[l], handle);
4634 st->parms->text.handles = handle; /* update current */
4637 return(new_top_line);
4640 num_display_lines = PGSIZE(st);
4642 format_scroll_text();
4644 if(st->top_text_line >= st->num_lines) /* don't pop line count */
4645 new_top_line = st->top_text_line = MAX(st->num_lines - 1, 0);
4647 if(st->parms->text.src == FileStar)
4648 ScrollFile(new_top_line); /* set up new st->text_lines */
4650 #ifdef _WINDOWS
4651 scroll_setrange (st->screen.length, st->num_lines);
4652 scroll_setpos (new_top_line);
4653 #endif
4655 /* ---
4656 Check out the scrolling situation. If we want to scroll, but BeginScroll
4657 says we can't then repaint, + 10 is so we repaint most of the time.
4658 ----*/
4659 if(redraw ||
4660 (st->top_text_line - new_top_line + 10 >= num_display_lines ||
4661 new_top_line - st->top_text_line + 10 >= num_display_lines) ||
4662 BeginScroll(st->screen.start_line,
4663 st->screen.start_line + num_display_lines - 1) != 0) {
4664 /* Too much text to scroll, or can't scroll -- just repaint */
4666 if(handle)
4667 st->parms->text.handles = handle;
4669 st->top_text_line = new_top_line;
4670 redraw_scroll_text();
4672 else{
4674 * We're going to scroll the screen, but first we have to make sure
4675 * the old hilited handles are unhilited if they are going to remain
4676 * on the screen.
4678 top = st->screen.start_line - st->top_text_line;
4679 if(handle && handle != st->parms->text.handles
4680 && st->parms->text.handles
4681 && !scroll_handle_obscured(st->parms->text.handles))
4682 for(lp = st->parms->text.handles->loc; lp; lp = lp->next)
4683 if((l = lp->where.row) >= MAX(st->top_text_line,new_top_line)
4684 && l < MIN(st->top_text_line,new_top_line) + st->screen.length){
4685 if(st->parms->text.src == FileStar)
4686 l -= new_top_line;
4688 if(F_ON(F_ENABLE_DEL_WHEN_WRITING, ps_global))
4689 ClearLine(top + lp->where.row);
4690 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4691 st->line_lengths[l], handle);
4694 if(new_top_line > st->top_text_line){
4695 /*------ scroll down ------*/
4696 while(new_top_line > st->top_text_line) {
4697 ScrollRegion(1);
4699 l = (st->parms->text.src == FileStar)
4700 ? num_display_lines - (new_top_line - st->top_text_line)
4701 : st->top_text_line + num_display_lines;
4703 if(l < st->num_lines){
4704 if(F_ON(F_ENABLE_DEL_WHEN_WRITING, ps_global))
4705 ClearLine(st->screen.start_line + num_display_lines - 1);
4706 PutLine0n8b(st->screen.start_line + num_display_lines - 1,
4707 0, st->text_lines[l], st->line_lengths[l],
4708 handle ? handle : st->parms->text.handles);
4710 * We clear to the end of line in the right background
4711 * color. If the line was exactly the width of the screen
4712 * then PutLine0n8b will have left _col and _row moved to
4713 * the start of the next row. We don't need or want to clear
4714 * that next row.
4716 if(pico_usingcolor()
4717 && (st->line_lengths[l] < ps_global->ttyo->screen_cols
4718 || visible_linelen(l) < ps_global->ttyo->screen_cols))
4719 CleartoEOLN();
4722 st->top_text_line++;
4725 else{
4726 /*------ scroll up -----*/
4727 while(new_top_line < st->top_text_line) {
4728 ScrollRegion(-1);
4730 st->top_text_line--;
4731 l = (st->parms->text.src == FileStar)
4732 ? st->top_text_line - new_top_line
4733 : st->top_text_line;
4734 if(F_ON(F_ENABLE_DEL_WHEN_WRITING, ps_global))
4735 ClearLine(st->screen.start_line);
4736 PutLine0n8b(st->screen.start_line, 0, st->text_lines[l],
4737 st->line_lengths[l],
4738 handle ? handle : st->parms->text.handles);
4740 * We clear to the end of line in the right background
4741 * color. If the line was exactly the width of the screen
4742 * then PutLine0n8b will have left _col and _row moved to
4743 * the start of the next row. We don't need or want to clear
4744 * that next row.
4746 if(pico_usingcolor()
4747 && (st->line_lengths[l] < ps_global->ttyo->screen_cols
4748 || visible_linelen(l) < ps_global->ttyo->screen_cols))
4749 CleartoEOLN();
4753 EndScroll();
4755 if(handle && handle != st->parms->text.handles){
4756 POSLIST_S *lp;
4758 for(lp = handle->loc; lp; lp = lp->next)
4759 if(lp->where.row >= st->top_text_line
4760 && lp->where.row < st->top_text_line + st->screen.length){
4761 if(F_ON(F_ENABLE_DEL_WHEN_WRITING, ps_global))
4762 ClearLine(st->screen.start_line + (lp->where.row - st->top_text_line));
4763 PutLine0n8b(st->screen.start_line
4764 + (lp->where.row - st->top_text_line),
4765 0, st->text_lines[lp->where.row],
4766 st->line_lengths[lp->where.row],
4767 handle);
4771 st->parms->text.handles = handle;
4774 fflush(stdout);
4777 return(new_top_line);
4781 /*---------------------------------------------------------------------
4782 Edit individual char in text so that the entire text doesn't need
4783 to be completely reformatted.
4785 Returns 0 if there were no errors, 1 if we would like the entire
4786 text to be reformatted.
4787 ----*/
4789 ng_scroll_edit(CONTEXT_S *context, int index)
4791 SCRLCTRL_S *st = scroll_state(SS_CUR);
4792 char *ngp, tmp[MAILTMPLEN+10];
4793 int len;
4794 FOLDER_S *f;
4796 if (!(f = folder_entry(index, FOLDERS(context))))
4797 return 1;
4798 if(f->subscribed)
4799 return 0; /* nothing in scroll needs to be changed */
4800 tmp[0] = TAG_HANDLE;
4801 snprintf(tmp+2, sizeof(tmp)-2, "%d", st->parms->text.handles->key);
4802 tmp[sizeof(tmp)-1] = '\0';
4803 tmp[1] = len = strlen(tmp+2);
4804 snprintf(tmp+len+2, sizeof(tmp)-(len+2), "%s ", f->selected ? "[ ]" : "[X]");
4805 tmp[sizeof(tmp)-1] = '\0';
4806 snprintf(tmp+len+6, sizeof(tmp)-(len+6), "%.*s", MAILTMPLEN, f->name);
4807 tmp[sizeof(tmp)-1] = '\0';
4809 ngp = *(st->text_lines);
4811 ngp = strstr(ngp, tmp);
4813 if(!ngp) return 1;
4814 ngp += 3+len;
4816 /* assumption that text is of form "[ ] xxx.xxx" */
4818 if(ngp){
4819 if(*ngp == 'X'){
4820 *ngp = ' ';
4821 return 0;
4823 else if (*ngp == ' '){
4824 *ngp = 'X';
4825 return 0;
4828 return 1;
4832 /*---------------------------------------------------------------------
4833 Similar to ng_scroll_edit, but this is the more general case of
4834 selecting a folder, as opposed to selecting a newsgroup for
4835 subscription while in listmode.
4837 Returns 0 if there were no errors, 1 if we would like the entire
4838 text to be reformatted.
4839 ----*/
4841 folder_select_update(CONTEXT_S *context, int index)
4843 SCRLCTRL_S *st = scroll_state(SS_CUR);
4844 FOLDER_S *f;
4845 char *ngp, tmp[MAILTMPLEN+10];
4846 int len, total, fnum, num_sel = 0;
4848 if (!(f = folder_entry(index, FOLDERS(context))))
4849 return 1;
4850 ngp = *(st->text_lines);
4852 total = folder_total(FOLDERS(context));
4854 for (fnum = 0; num_sel < 2 && fnum < total; fnum++)
4855 if(folder_entry(fnum, FOLDERS(context))->selected)
4856 num_sel++;
4857 if(!num_sel || (f->selected && num_sel == 1))
4858 return 1; /* need to reformat the whole thing */
4860 tmp[0] = TAG_HANDLE;
4861 snprintf(tmp+2, sizeof(tmp)-2, "%d", st->parms->text.handles->key);
4862 tmp[sizeof(tmp)-1] = '\0';
4863 tmp[1] = len = strlen(tmp+2);
4865 ngp = strstr(ngp, tmp);
4866 if(!ngp) return 1;
4868 if(F_ON(F_SELECTED_SHOWN_BOLD, ps_global)){
4869 ngp += 2 + len;
4870 while(*ngp && ngp[0] != TAG_EMBED
4871 && ngp[1] != (f->selected ? TAG_BOLDOFF : TAG_BOLDON)
4872 && *ngp != *(f->name))
4873 ngp++;
4875 if (!(*ngp) || (*ngp == *(f->name)))
4876 return 1;
4877 else {
4878 ngp++;
4879 *ngp = (f->selected ? TAG_BOLDON : TAG_BOLDOFF);
4880 return 0;
4883 else{
4884 while(*ngp != ' ' && *ngp != *(f->name) && *ngp)
4885 ngp++;
4886 if(!(*ngp) || (*ngp == *(f->name)))
4887 return 1;
4888 else {
4889 ngp++;
4890 *ngp = f->selected ? 'X' : ' ';
4891 return 0;
4897 /*---------------------------------------------------------------------
4898 We gotta go through all of the formatted text and add "[ ] " in the right
4899 place. If we don't do this, we must completely reformat the whole text,
4900 which could take a very long time.
4902 Return 1 if we encountered some sort of error and we want to reformat the
4903 whole text, return 0 if everything went as planned.
4905 ASSUMPTION: for this to work, we assume that there are only total
4906 number of handles, numbered 1 through total.
4907 ----*/
4909 scroll_add_listmode(CONTEXT_S *context, int total)
4911 SCRLCTRL_S *st = scroll_state(SS_CUR);
4912 long i;
4913 char *ngp, *ngname, handle_str[MAILTMPLEN];
4914 HANDLE_S *h;
4917 ngp = *(st->text_lines);
4918 h = st->parms->text.handles;
4920 while(h && h->key != 1 && h->prev)
4921 h = h->prev;
4922 if (!h) return 1;
4923 handle_str[0] = TAG_EMBED;
4924 handle_str[1] = TAG_HANDLE;
4925 for(i = 1; i <= total && h; i++, h = h->next){
4926 snprintf(handle_str+3, sizeof(handle_str)-3, "%d", h->key);
4927 handle_str[sizeof(handle_str)-1] = '\0';
4928 handle_str[2] = strlen(handle_str+3);
4929 ngp = strstr(ngp, handle_str);
4930 if(!ngp){
4931 ngp = *(st->text_lines);
4932 if (!ngp)
4933 return 1;
4935 ngname = ngp + strlen(handle_str);
4936 while (strncmp(ngp, " ", 4) && !(*ngp == '\n')
4937 && !(ngp == *(st->text_lines)))
4938 ngp--;
4939 if (strncmp(ngp, " ", 4))
4940 return 1;
4941 while(ngp+4 != ngname && *ngp){
4942 ngp[0] = ngp[4];
4943 ngp++;
4946 if(folder_entry(h->h.f.index, FOLDERS(context))->subscribed){
4947 ngp[0] = 'S';
4948 ngp[1] = 'U';
4949 ngp[2] = 'B';
4951 else{
4952 ngp[0] = '[';
4953 ngp[1] = ' ';
4954 ngp[2] = ']';
4956 ngp[3] = ' ';
4959 return 0;
4964 /*----------------------------------------------------------------------
4965 Search the set scrolling text
4967 Args: start_line -- line to start searching on
4968 start_index -- column to start searching at in first line
4969 word -- string to search for
4970 cursor_pos -- position of cursor is returned to caller here
4971 (Actually, this isn't really the position of the
4972 cursor because we don't know where we are on the
4973 screen. So row is set to the line number and col
4974 is set to the right column.)
4975 offset_in_line -- Offset where match was found.
4977 Returns: the line the word was found on, or -2 if it wasn't found, or
4978 -3 if the only match is at column start_index - 1.
4980 ----*/
4982 search_scroll_text(long int start_line, int start_index, char *word,
4983 Pos *cursor_pos, int *offset_in_line)
4985 SCRLCTRL_S *st = scroll_state(SS_CUR);
4986 char *wh;
4987 long l, offset, dlines;
4988 #define SROW(N) ((N) - offset)
4989 #define SLINE(N) st->text_lines[SROW(N)]
4990 #define SLEN(N) st->line_lengths[SROW(N)]
4992 dlines = PGSIZE(st);
4993 offset = (st->parms->text.src == FileStar) ? st->top_text_line : 0;
4995 if(start_line < st->num_lines){
4996 /* search first line starting at position start_index in */
4997 if((wh = search_scroll_line(SLINE(start_line) + start_index,
4998 word,
4999 SLEN(start_line) - start_index,
5000 st->parms->text.handles != NULL)) != NULL){
5001 cursor_pos->row = start_line;
5002 cursor_pos->col = scroll_handle_column(SROW(start_line),
5003 *offset_in_line = wh - SLINE(start_line));
5004 return(start_line);
5007 if(st->parms->text.src == FileStar)
5008 offset++;
5010 for(l = start_line + 1; l < st->num_lines; l++) {
5011 if(st->parms->text.src == FileStar && l > offset + dlines)
5012 ScrollFile(offset += dlines);
5014 if((wh = search_scroll_line(SLINE(l), word, SLEN(l),
5015 st->parms->text.handles != NULL)) != NULL){
5016 cursor_pos->row = l;
5017 cursor_pos->col = scroll_handle_column(SROW(l),
5018 *offset_in_line = wh - SLINE(l));
5019 return(l);
5023 else
5024 start_line = st->num_lines;
5026 if(st->parms->text.src == FileStar) /* wrap offset */
5027 ScrollFile(offset = 0);
5029 for(l = 0; l < start_line; l++) {
5030 if(st->parms->text.src == FileStar && l > offset + dlines)
5031 ScrollFile(offset += dlines);
5033 if((wh = search_scroll_line(SLINE(l), word, SLEN(l),
5034 st->parms->text.handles != NULL)) != NULL){
5035 cursor_pos->row = l;
5036 cursor_pos->col = scroll_handle_column(SROW(l),
5037 *offset_in_line = wh - SLINE(l));
5038 return(l);
5042 /* search in current line */
5043 if(start_line < st->num_lines
5044 && (wh = search_scroll_line(SLINE(start_line), word,
5045 start_index + strlen(word) - 2,
5046 st->parms->text.handles != NULL)) != NULL){
5047 cursor_pos->row = start_line;
5048 cursor_pos->col = scroll_handle_column(SROW(start_line),
5049 *offset_in_line = wh - SLINE(start_line));
5051 return(start_line);
5054 /* see if the only match is a repeat */
5055 if(start_index > 0 && start_line < st->num_lines
5056 && (wh = search_scroll_line(
5057 SLINE(start_line) + start_index - 1,
5058 word, strlen(word),
5059 st->parms->text.handles != NULL)) != NULL){
5060 cursor_pos->row = start_line;
5061 cursor_pos->col = scroll_handle_column(SROW(start_line),
5062 *offset_in_line = wh - SLINE(start_line));
5063 return(-3);
5066 return(-2);
5070 /*----------------------------------------------------------------------
5071 Search one line of scroll text for given string
5073 Args: haystack -- The string to search in, the larger string
5074 needle -- The string to search for, the smaller string
5075 n -- The max number of chars in haystack to search
5077 Search for first occurrence of needle in the haystack, and return a pointer
5078 into the string haystack when it is found. The search is case independent.
5079 ----*/
5080 char *
5081 search_scroll_line(char *haystack, char *needle, int n, int handles)
5083 char *return_ptr = NULL, *found_it = NULL;
5084 char *haystack_copy, *p, *free_this = NULL, *end;
5085 char buf[1000];
5086 int state = 0, i = 0;
5088 if(n > 0 && haystack){
5089 if(n < sizeof(buf))
5090 haystack_copy = buf;
5091 else
5092 haystack_copy = free_this = (char *) fs_get((n+1) * sizeof(char));
5094 strncpy(haystack_copy, haystack, n);
5095 haystack_copy[n] = '\0';
5098 * We don't want to match text inside embedded tags.
5099 * Replace embedded octets with nulls and convert
5100 * uppercase ascii to lowercase. We should also do
5101 * some sort of canonicalization of UTF-8 but that
5102 * sounds daunting.
5104 for(i = n, p = haystack_copy; i-- > 0 && *p; p++){
5105 if(handles)
5106 switch(state){
5107 case 0 :
5108 if(*p == TAG_EMBED){
5109 *p = '\0';
5110 state = -1;
5111 continue;
5113 else{
5114 /* lower case just ascii chars */
5115 if(!(*p & 0x80) && isupper(*p))
5116 *p = tolower(*p);
5119 break;
5121 case -1 :
5122 state = (*p == TAG_HANDLE)
5123 ? -2
5124 : (*p == TAG_FGCOLOR || *p == TAG_BGCOLOR) ? RGBLEN : 0;
5125 *p = '\0';
5126 continue;
5128 case -2 :
5129 state = *p; /* length of handle's key */
5130 *p = '\0';
5131 continue;
5133 default :
5134 state--;
5135 *p = '\0';
5136 continue;
5141 * The haystack_copy string now looks like
5143 * "chars\0\0\0\0\0\0chars...\0\0\0chars... \0"
5145 * with that final \0 at haystack_copy[n].
5146 * Search each piece one at a time.
5149 end = haystack_copy + n;
5150 p = haystack_copy;
5152 while(p < end && !return_ptr){
5154 /* skip nulls */
5155 while(*p == '\0' && p < end)
5156 p++;
5158 if(*p != '\0')
5159 found_it = srchstr(p, needle);
5161 if(found_it){
5162 /* found it, make result relative to haystack */
5163 return_ptr = haystack + (found_it - haystack_copy);
5166 /* skip to next null */
5167 while(*p != '\0' && p < end)
5168 p++;
5171 if(free_this)
5172 fs_give((void **) &free_this);
5175 return(return_ptr);
5180 * Returns the number of columns taken up by the visible part of the line.
5181 * That is, account for handles and color changes and so forth.
5184 visible_linelen(int line)
5186 SCRLCTRL_S *st = scroll_state(SS_CUR);
5187 int i, n, len = 0;
5189 if(line < 0 || line >= st->num_lines)
5190 return(len);
5192 for(i = 0, len = 0; i < st->line_lengths[line];)
5193 switch(st->text_lines[line][i]){
5195 case TAG_EMBED:
5196 i++;
5197 switch((i < st->line_lengths[line]) ? st->text_lines[line][i] : 0){
5198 case TAG_HANDLE:
5199 i++;
5200 n = 0; /* quell gcc */
5201 /* skip the length byte plus <length> more bytes */
5202 if(i < st->line_lengths[line]){
5203 n = st->text_lines[line][i];
5204 i++;
5207 if(i < st->line_lengths[line] && n > 0){
5208 i += n;
5211 break;
5213 case TAG_FGCOLOR :
5214 case TAG_BGCOLOR :
5215 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
5216 break;
5218 case TAG_INVON:
5219 case TAG_INVOFF:
5220 case TAG_BOLDON:
5221 case TAG_BOLDOFF:
5222 case TAG_ULINEON:
5223 case TAG_ULINEOFF:
5224 i++;
5225 break;
5227 case TAG_EMBED: /* escaped embed character */
5228 i++;
5229 len++;
5230 break;
5232 default: /* the embed char was literal */
5233 i++;
5234 len += 2;
5235 break;
5238 break;
5240 case TAB:
5241 i++;
5242 while(((++len) & 0x07) != 0) /* add tab's spaces */
5245 break;
5247 default:
5248 i++;
5249 len++;
5250 break;
5253 return(len);
5257 /*----------------------------------------------------------------------
5258 Display the contents of the given file (likely output from some command)
5260 Args: filename -- name of file containing output
5261 title -- title to be used for screen displaying output
5262 alt_msg -- if no output, Q this message instead of the default
5263 mode -- non-zero to display short files in status line
5264 Returns: none
5265 ----*/
5266 void
5267 display_output_file(char *filename, char *title, char *alt_msg, int mode)
5269 STORE_S *in_file = NULL, *out_store = NULL;
5271 if((in_file = so_get(FileStar, filename, READ_ACCESS|READ_FROM_LOCALE))){
5272 if(mode == DOF_BRIEF){
5273 int msg_q = 0, i = 0;
5274 char buf[512], *msg_p[4];
5275 #define MAX_SINGLE_MSG_LEN 60
5277 buf[0] = '\0';
5278 msg_p[0] = buf;
5281 * Might need to do something about CRLFs for Windows.
5283 while(so_fgets(in_file, msg_p[msg_q], sizeof(buf) - (msg_p[msg_q] - buf))
5284 && msg_q < 3
5285 && (i = strlen(msg_p[msg_q])) < MAX_SINGLE_MSG_LEN){
5286 msg_p[msg_q+1] = msg_p[msg_q]+strlen(msg_p[msg_q]);
5287 if (*(msg_p[++msg_q] - 1) == '\n')
5288 *(msg_p[msg_q] - 1) = '\0';
5291 if(msg_q < 3 && i < MAX_SINGLE_MSG_LEN){
5292 if(*msg_p[0])
5293 for(i = 0; i < msg_q; i++)
5294 q_status_message2(SM_ORDER, 3, 4,
5295 "%s Result: %s", title, msg_p[i]);
5296 else
5297 q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
5298 alt_msg
5299 ? alt_msg
5300 : " command completed with no output");
5302 so_give(&in_file);
5303 in_file = NULL;
5306 else if(mode == DOF_EMPTY){
5307 unsigned char c;
5309 if(so_readc(&c, in_file) < 1){
5310 q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
5311 alt_msg
5312 ? alt_msg
5313 : " command completed with no output");
5314 so_give(&in_file);
5315 in_file = NULL;
5320 * We need to translate the file contents from the user's locale
5321 * charset to UTF-8 for use in scrolltool. We get that translation
5322 * from the READ_FROM_LOCALE in the in_file storage object.
5323 * It would be nice to skip this step but scrolltool doesn't use
5324 * the storage object routines to read from the file, so would
5325 * skip the translation step.
5327 if(in_file){
5328 char *errstr;
5329 gf_io_t gc, pc;
5331 if(!(out_store = so_get(CharStar, NULL, EDIT_ACCESS))){
5332 so_give(&in_file);
5333 our_unlink(filename);
5334 q_status_message(SM_ORDER | SM_DING, 3, 3,
5335 _("Error allocating space."));
5336 return;
5339 so_seek(in_file, 0L, 0);
5341 gf_filter_init();
5343 gf_link_filter(gf_wrap,
5344 gf_wrap_filter_opt(ps_global->ttyo->screen_cols - 4,
5345 ps_global->ttyo->screen_cols,
5346 NULL, 0, GFW_NONE));
5348 gf_set_so_readc(&gc, in_file);
5349 gf_set_so_writec(&pc, out_store);
5351 if((errstr = gf_pipe(gc, pc)) != NULL){
5352 so_give(&in_file);
5353 so_give(&out_store);
5354 our_unlink(filename);
5355 q_status_message(SM_ORDER | SM_DING, 3, 3,
5356 _("Error allocating space."));
5357 return;
5360 gf_clear_so_writec(out_store);
5361 gf_clear_so_readc(in_file);
5362 so_give(&in_file);
5365 if(out_store){
5366 SCROLL_S sargs;
5367 char title_buf[64];
5369 snprintf(title_buf, sizeof(title_buf), "HELP FOR %s VIEW", title);
5370 title_buf[sizeof(title_buf)-1] = '\0';
5372 memset(&sargs, 0, sizeof(SCROLL_S));
5373 sargs.text.text = so_text(out_store);
5374 sargs.text.src = CharStar;
5375 sargs.text.desc = "output";
5376 sargs.bar.title = title;
5377 sargs.bar.style = TextPercent;
5378 sargs.help.text = h_simple_text_view;
5379 sargs.help.title = title_buf;
5380 scrolltool(&sargs);
5381 ps_global->mangled_screen = 1;
5382 so_give(&out_store);
5385 our_unlink(filename);
5387 else
5388 dprint((2, "Error reopening %s to get results: %s\n",
5389 filename ? filename : "?", error_description(errno)));
5393 /*--------------------------------------------------------------------
5394 Call the function that will perform the double click operation
5396 Returns: the current top line
5397 --------*/
5398 long
5399 doubleclick_handle(SCROLL_S *sparms, HANDLE_S *next_handle, int *cmd, int *done)
5401 if(sparms->mouse.clickclick){
5402 if((*sparms->mouse.clickclick)(sparms))
5403 *done = 1;
5405 else
5406 switch(scroll_handle_launch(next_handle, TRUE)){
5407 case 1 :
5408 *cmd = MC_EXIT; /* propagate */
5409 *done = 1;
5410 break;
5412 case -1 :
5413 cmd_cancelled("View");
5414 break;
5416 default :
5417 break;
5420 return(scroll_state(SS_CUR)->top_text_line);
5425 #ifdef _WINDOWS
5427 * Just a little something to simplify assignments
5429 #define VIEWPOPUP(p, c, s) { \
5430 (p)->type = tQueue; \
5431 (p)->data.val = c; \
5432 (p)->label.style = lNormal; \
5433 (p)->label.string = s; \
5441 format_message_popup(sparms, in_handle)
5442 SCROLL_S *sparms;
5443 int in_handle;
5445 MPopup fmp_menu[32];
5446 HANDLE_S *h = NULL;
5447 int i = -1, n;
5448 long rawno;
5449 MESSAGECACHE *mc;
5451 /* Reason to offer per message ops? */
5452 if(mn_get_total(ps_global->msgmap) > 0L){
5453 if(in_handle){
5454 SCRLCTRL_S *st = scroll_state(SS_CUR);
5456 switch((h = get_handle(st->parms->text.handles, in_handle))->type){
5457 case Attach :
5458 fmp_menu[++i].type = tIndex;
5459 fmp_menu[i].label.string = "View Attachment";
5460 fmp_menu[i].label.style = lNormal;
5461 fmp_menu[i].data.val = 'X'; /* for local use */
5463 if(h->h.attach
5464 && dispatch_attachment(h->h.attach) != MCD_NONE
5465 && !(h->h.attach->can_display & MCD_EXTERNAL)
5466 && h->h.attach->body
5467 && (h->h.attach->body->type == TYPETEXT
5468 || (h->h.attach->body->type == TYPEMESSAGE
5469 && h->h.attach->body->subtype
5470 && !strucmp(h->h.attach->body->subtype,"rfc822")))){
5471 fmp_menu[++i].type = tIndex;
5472 fmp_menu[i].label.string = "View Attachment in New Window";
5473 fmp_menu[i].label.style = lNormal;
5474 fmp_menu[i].data.val = 'Y'; /* for local use */
5477 fmp_menu[++i].type = tIndex;
5478 fmp_menu[i].label.style = lNormal;
5479 fmp_menu[i].data.val = 'Z'; /* for local use */
5480 msgno_exceptions(ps_global->mail_stream,
5481 mn_m2raw(ps_global->msgmap,
5482 mn_get_cur(ps_global->msgmap)),
5483 h->h.attach->number, &n, FALSE);
5484 fmp_menu[i].label.string = (n & MSG_EX_DELETE)
5485 ? "Undelete Attachment"
5486 : "Delete Attachment";
5487 break;
5489 case URL :
5490 default :
5491 fmp_menu[++i].type = tIndex;
5492 fmp_menu[i].label.string = "View Link";
5493 fmp_menu[i].label.style = lNormal;
5494 fmp_menu[i].data.val = 'X'; /* for local use */
5496 fmp_menu[++i].type = tIndex;
5497 fmp_menu[i].label.string = "Copy Link";
5498 fmp_menu[i].label.style = lNormal;
5499 fmp_menu[i].data.val = 'W'; /* for local use */
5500 break;
5503 fmp_menu[++i].type = tSeparator;
5506 /* Delete or Undelete? That is the question. */
5507 fmp_menu[++i].type = tQueue;
5508 fmp_menu[i].label.style = lNormal;
5509 mc = ((rawno = mn_m2raw(ps_global->msgmap,
5510 mn_get_cur(ps_global->msgmap))) > 0L
5511 && ps_global->mail_stream
5512 && rawno <= ps_global->mail_stream->nmsgs)
5513 ? mail_elt(ps_global->mail_stream, rawno) : NULL;
5514 if(mc && mc->deleted){
5515 fmp_menu[i].data.val = 'U';
5516 fmp_menu[i].label.string = in_handle
5517 ? "&Undelete Message" : "&Undelete";
5519 else{
5520 fmp_menu[i].data.val = 'D';
5521 fmp_menu[i].label.string = in_handle
5522 ? "&Delete Message" : "&Delete";
5525 if(F_ON(F_ENABLE_FLAG, ps_global)){
5526 fmp_menu[++i].type = tSubMenu;
5527 fmp_menu[i].label.string = "Flag";
5528 fmp_menu[i].data.submenu = flag_submenu(mc);
5531 i++;
5532 VIEWPOPUP(&fmp_menu[i], 'S', in_handle ? "&Save Message" : "&Save");
5534 i++;
5535 VIEWPOPUP(&fmp_menu[i], 'E', in_handle ? "&Export Message" : "&Export");
5537 i++;
5538 VIEWPOPUP(&fmp_menu[i], '%', in_handle ? "Print Message" : "Print");
5540 i++;
5541 VIEWPOPUP(&fmp_menu[i], 'R',
5542 in_handle ? "&Reply to Message" : "&Reply");
5544 i++;
5545 VIEWPOPUP(&fmp_menu[i], 'F',
5546 in_handle ? "&Forward Message" : "&Forward");
5548 i++;
5549 VIEWPOPUP(&fmp_menu[i], 'B',
5550 in_handle ? "&Bounce Message" : "&Bounce");
5552 i++;
5553 VIEWPOPUP(&fmp_menu[i], 'T', "&Take Addresses");
5555 fmp_menu[++i].type = tSeparator;
5557 if(mn_get_cur(ps_global->msgmap) < mn_get_total(ps_global->msgmap)){
5558 i++;
5559 VIEWPOPUP(&fmp_menu[i], 'N', "View &Next Message");
5562 if(mn_get_cur(ps_global->msgmap) > 0){
5563 i++;
5564 VIEWPOPUP(&fmp_menu[i], 'P', "View &Prev Message");
5567 if(mn_get_cur(ps_global->msgmap) < mn_get_total(ps_global->msgmap)
5568 || mn_get_cur(ps_global->msgmap) > 0)
5569 fmp_menu[++i].type = tSeparator;
5571 /* Offer the attachment screen? */
5572 for(n = 0; ps_global->atmts && ps_global->atmts[n].description; n++)
5575 if(n > 1){
5576 i++;
5577 VIEWPOPUP(&fmp_menu[i], 'V', "&View Attachment Index");
5581 i++;
5582 VIEWPOPUP(&fmp_menu[i], 'I', "Message &Index");
5584 i++;
5585 VIEWPOPUP(&fmp_menu[i], 'M', "&Main Menu");
5587 fmp_menu[++i].type = tTail;
5589 if((i = mswin_popup(fmp_menu)) >= 0 && in_handle)
5590 switch(fmp_menu[i].data.val){
5591 case 'W' : /* Copy URL to clipboard */
5592 mswin_addclipboard(h->h.url.path);
5593 break;
5595 case 'X' :
5596 return(1); /* return like the user double-clicked */
5598 break;
5600 case 'Y' : /* popup the thing in another window */
5601 display_att_window(h->h.attach);
5602 break;
5604 case 'Z' :
5605 if(h && h->type == Attach){
5606 msgno_exceptions(ps_global->mail_stream,
5607 mn_m2raw(ps_global->msgmap,
5608 mn_get_cur(ps_global->msgmap)),
5609 h->h.attach->number, &n, FALSE);
5610 n ^= MSG_EX_DELETE;
5611 msgno_exceptions(ps_global->mail_stream,
5612 mn_m2raw(ps_global->msgmap,
5613 mn_get_cur(ps_global->msgmap)),
5614 h->h.attach->number, &n, TRUE);
5615 q_status_message2(SM_ORDER, 0, 3, "Attachment %s %s!",
5616 h->h.attach->number,
5617 (n & MSG_EX_DELETE) ? "deleted" : "undeleted");
5619 return(2);
5622 break;
5624 default :
5625 break;
5628 return(0);
5637 simple_text_popup(sparms, in_handle)
5638 SCROLL_S *sparms;
5639 int in_handle;
5641 MPopup simple_menu[12];
5642 int n = 0;
5644 VIEWPOPUP(&simple_menu[n], '%', "Print");
5645 n++;
5647 VIEWPOPUP(&simple_menu[n], 'S', "Save");
5648 n++;
5650 VIEWPOPUP(&simple_menu[n], 'F', "Forward in Email");
5651 n++;
5653 simple_menu[n++].type = tSeparator;
5655 VIEWPOPUP(&simple_menu[n], 'E', "Exit Viewer");
5656 n++;
5658 simple_menu[n].type = tTail;
5660 (void) mswin_popup(simple_menu);
5661 return(0);
5666 /*----------------------------------------------------------------------
5667 Return characters in scroll tool buffer serially
5669 Args: n -- index of char to return
5671 Returns: returns the character at index 'n', or -1 on error or
5672 end of buffer.
5674 ----*/
5676 mswin_readscrollbuf(n)
5677 int n;
5679 SCRLCTRL_S *st = scroll_state(SS_CUR);
5680 int c;
5681 static char **orig = NULL, **l, *p;
5682 static int lastn;
5684 if(!st)
5685 return(-1);
5688 * All of these are mind-numbingly slow at the moment...
5690 switch(st->parms->text.src){
5691 case CharStar :
5692 return((n >= strlen((char *)st->parms->text.text))
5693 ? -1 : ((char *)st->parms->text.text)[n]);
5695 case CharStarStar :
5696 /* BUG? is this test rigorous enough? */
5697 if(orig != (char **)st->parms->text.text || n < lastn){
5698 lastn = n;
5699 if(orig = l = (char **)st->parms->text.text) /* reset l and p */
5700 p = *l;
5702 else{ /* use cached l and p */
5703 c = n; /* and adjust n */
5704 n -= lastn;
5705 lastn = c;
5708 while(l){ /* look for 'n' on each line */
5709 for(; n && *p; n--, p++)
5712 if(n--) /* 'n' found ? */
5713 p = *++l;
5714 else
5715 break;
5718 return((l && *l) ? *p ? *p : '\n' : -1);
5720 case FileStar :
5721 return((fseek((FILE *)st->parms->text.text, (long) n, 0) < 0
5722 || (c = fgetc((FILE *)st->parms->text.text)) == EOF) ? -1 : c);
5724 default:
5725 return(-1);
5731 /*----------------------------------------------------------------------
5732 MSWin scroll callback. Called during scroll message processing.
5736 Args: cmd - what type of scroll operation.
5737 scroll_pos - parameter for operation.
5738 used as position for SCROLL_TO operation.
5740 Returns: TRUE - did the scroll operation.
5741 FALSE - was not able to do the scroll operation.
5742 ----*/
5744 pcpine_do_scroll (cmd, scroll_pos)
5745 int cmd;
5746 long scroll_pos;
5748 SCRLCTRL_S *st = scroll_state(SS_CUR);
5749 HANDLE_S *next_handle;
5750 int paint = FALSE;
5751 int num_display_lines;
5752 int scroll_lines;
5753 char message[64];
5754 long maxscroll;
5757 message[0] = '\0';
5758 maxscroll = st->num_lines;
5759 switch (cmd) {
5760 case MSWIN_KEY_SCROLLUPLINE:
5761 if(st->top_text_line > 0) {
5762 st->top_text_line -= (int) scroll_pos;
5763 paint = TRUE;
5764 if (st->top_text_line <= 0){
5765 snprintf(message, sizeof(message), "START of %.*s",
5766 32, STYLE_NAME(st->parms));
5767 message[sizeof(message)-1] = '\0';
5768 st->top_text_line = 0;
5771 break;
5773 case MSWIN_KEY_SCROLLDOWNLINE:
5774 if(st->top_text_line < maxscroll) {
5775 st->top_text_line += (int) scroll_pos;
5776 paint = TRUE;
5777 if (st->top_text_line >= maxscroll){
5778 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5779 message[sizeof(message)-1] = '\0';
5780 st->top_text_line = maxscroll;
5783 break;
5785 case MSWIN_KEY_SCROLLUPPAGE:
5786 if(st->top_text_line > 0) {
5787 num_display_lines = SCROLL_LINES(ps_global);
5788 scroll_lines = MIN(MAX(num_display_lines -
5789 ps_global->viewer_overlap, 1), num_display_lines);
5790 if (st->top_text_line > scroll_lines)
5791 st->top_text_line -= scroll_lines;
5792 else {
5793 st->top_text_line = 0;
5794 snprintf(message, sizeof(message), "START of %.*s", 32, STYLE_NAME(st->parms));
5795 message[sizeof(message)-1] = '\0';
5797 paint = TRUE;
5799 break;
5801 case MSWIN_KEY_SCROLLDOWNPAGE:
5802 num_display_lines = SCROLL_LINES(ps_global);
5803 if(st->top_text_line < maxscroll) {
5804 scroll_lines = MIN(MAX(num_display_lines -
5805 ps_global->viewer_overlap, 1), num_display_lines);
5806 st->top_text_line += scroll_lines;
5807 if (st->top_text_line >= maxscroll) {
5808 st->top_text_line = maxscroll;
5809 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5810 message[sizeof(message)-1] = '\0';
5812 paint = TRUE;
5814 break;
5816 case MSWIN_KEY_SCROLLTO:
5817 if (st->top_text_line != scroll_pos) {
5818 st->top_text_line = scroll_pos;
5819 if (st->top_text_line == 0)
5820 snprintf(message, sizeof(message), "START of %.*s", 32, STYLE_NAME(st->parms));
5821 else if(st->top_text_line >= maxscroll)
5822 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5824 message[sizeof(message)-1] = '\0';
5825 paint = TRUE;
5827 break;
5830 /* Need to frame some handles? */
5831 if(st->parms->text.handles
5832 && (next_handle = scroll_handle_in_frame(st->top_text_line)))
5833 st->parms->text.handles = next_handle;
5835 if (paint) {
5836 mswin_beginupdate();
5837 update_scroll_titlebar(st->top_text_line, 0);
5838 (void) scroll_scroll_text(st->top_text_line,
5839 st->parms->text.handles, 1);
5840 if (message[0])
5841 q_status_message(SM_INFO, 0, 1, message);
5843 /* Display is always called so that the "START(END) of message"
5844 * message gets removed when no longer at the start(end). */
5845 display_message (KEY_PGDN);
5846 mswin_endupdate();
5849 return (TRUE);
5853 char *
5854 pcpine_help_scroll(title)
5855 char *title;
5857 SCRLCTRL_S *st = scroll_state(SS_CUR);
5859 if(title)
5860 strncpy(title, (st->parms->help.title)
5861 ? st->parms->help.title : "Alpine Help", 256);
5863 return(pcpine_help(st->parms->help.text));
5868 pcpine_view_cursor(col, row)
5869 int col;
5870 long row;
5872 SCRLCTRL_S *st = scroll_state(SS_CUR);
5873 int key;
5874 long line;
5876 return((row >= HEADER_ROWS(ps_global)
5877 && row < HEADER_ROWS(ps_global) + SCROLL_LINES(ps_global)
5878 && (line = (row - 2) + st->top_text_line) < st->num_lines
5879 && (key = dot_on_handle(line, col))
5880 && scroll_handle_selectable(get_handle(st->parms->text.handles,key)))
5881 ? MSWIN_CURSOR_HAND
5882 : MSWIN_CURSOR_ARROW);
5884 #endif /* _WINDOWS */