* Changes in the source code of Alpine to define internal prototypes
[alpine.git] / alpine / mailview.c
blobb4a6c0d664babd3ab0c1baa99cf5badaabfa17a1
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_o_t pc, writec;
1626 gf_i_t readc;
1627 STORE_S *so, *img;
1628 char *tmpfile = NULL, *err = NULL, *imgdata, *encoding;
1630 if(!toolp) return 1;
1632 encoding = strchr(data, ';');
1633 if(encoding) encoding++;
1634 imgdata = strchr(data, ',');
1635 if(imgdata) imgdata++;
1636 tmpfile = temp_nam(NULL, "img-data-"); /* create temporary file */
1638 img = so_get(CharStar, NULL, EDIT_ACCESS); /* allocate a pointer to save data */
1639 so_seek(img, 0L, 0); /* rewind img to start */
1640 gf_set_so_writec(&writec, img); /* set method to write to img in writec */
1641 gf_set_so_readc(&readc, img); /* set method to read from img in readc */
1643 if(imgdata
1644 && encoding
1645 && imgdata
1646 && img
1647 && gf_puts(imgdata, writec) /* write imgdata to img using writec */
1648 && (so = so_get(FileStar, tmpfile, WRITE_ACCESS|OWNER_ONLY)) != NULL){ /* open temporary file */
1650 so_seek(img, 0L, 0); /* rewind img to start */
1651 so_seek(so, 0L, 0); /* rewind so to start */
1652 gf_set_so_writec(&pc, so); /* set method to write to so in pc */
1653 gf_filter_init(); /* start a filter */
1654 if(!struncmp(encoding, "BASE64", 6)) /* link base64 filter */
1655 gf_link_filter(gf_b64_binary, NULL);
1657 err = gf_pipe(readc, pc); /* pass data from imgdata to so, reading from readc and writing with pc */
1659 gf_clear_so_writec(so); /* disassociate so and pc */
1661 if(so_give(&so)) /* write tmp to disk and free so */
1662 err = "Error writing image to file";
1664 gf_clear_so_writec(img); /* disassociate img and writec */
1665 gf_clear_so_readc(img); /* disassociate img and readc */
1666 so_give(&img); /* free img */
1668 else err = "Error creating space for temporary image";
1670 /* toolp tells us that there is a program to execute, which is
1671 * either the image viewer or the mailcap. No matter which, the
1672 * information is now in the mailcap structure, so let's use it
1674 if(!err){
1675 char *subtype = NULL;
1676 if(!struncmp(data, "IMAGE/", 6)){
1677 data += 6;
1678 for(subtype = data; data && *data != ';'; data++);
1679 if(data){
1680 *data = '\0';
1681 if(mailcap_can_display(TYPEIMAGE, subtype, NULL, 0)){
1682 MCAP_CMD_S *mc_cmd;
1684 mc_cmd = mailcap_build_command(TYPEIMAGE, subtype,
1685 NULL, tmpfile, NULL, 0);
1686 exec_mailcap_cmd(mc_cmd, tmpfile, 0);
1688 *data = ';';
1689 data = subtype - 6;
1693 else
1694 q_status_message(SM_ORDER, 2, 2,
1695 _("\"Image-Viewer\" not defined: Can't open image"));
1697 if(tmpfile) /* file was deleted by exec_mailcap_cmd */
1698 fs_give((void **) &tmpfile);
1700 return 0;
1705 url_launch_too_long(int return_value)
1707 q_status_message(SM_ORDER | SM_DING, 3, 3,
1708 "Can't spawn. Command too long.");
1709 return(return_value);
1713 char *
1714 url_external_handler(HANDLE_S *handle, int specific)
1716 return get_url_external_handler(handle->h.url.path, specific);
1719 char *
1720 get_url_external_handler(char *url, int specific)
1722 char **l, *test, *cmd, *p, *q, *ep;
1723 int i, specific_match;
1725 for(l = ps_global->VAR_BROWSER ; l && *l; l++){
1726 get_pair(*l, &test, &cmd, 0, 1);
1727 dprint((5, "TEST: \"%s\" CMD: \"%s\"\n",
1728 test ? test : "<NULL>", cmd ? cmd : "<NULL>"));
1729 removing_quotes(cmd);
1730 if(valid_filter_command(&cmd)){
1731 specific_match = 0;
1733 if((p = test) != NULL){
1734 while(*p && cmd)
1735 if(*p == '_'){
1736 if(!strncmp(p+1, "TEST(", 5)
1737 && (ep = strstr(p+6, ")_"))){
1738 *ep = '\0';
1740 if(exec_mailcap_test_cmd(p+6) == 0){
1741 p = ep + 2;
1743 else{
1744 dprint((5,"failed handler TEST\n"));
1745 fs_give((void **) &cmd);
1748 else if(!strncmp(p+1, "SCHEME(", 7)
1749 && (ep = strstr(p+8, ")_"))){
1750 *ep = '\0';
1752 p += 8;
1754 if((q = strchr(p, ',')) != NULL)
1755 *q++ = '\0';
1756 else
1757 q = ep;
1758 while(!((i = strlen(p))
1759 && ((p[i-1] == ':' && url[i - 1] == ':')
1760 || (p[i-1] != ':' && url[i] == ':'))
1761 && !struncmp(url, p, i))
1762 && *(p = q));
1764 if(*p){
1765 specific_match = 1;
1766 p = ep + 2;
1768 else{
1769 dprint((5,"failed handler SCHEME\n"));
1770 fs_give((void **) &cmd);
1773 else{
1774 dprint((5, "UNKNOWN underscore test\n"));
1775 fs_give((void **) &cmd);
1778 else if(isspace((unsigned char) *p)){
1779 p++;
1781 else{
1782 dprint((1,"bogus handler test: \"%s\"",
1783 test ? test : "?"));
1784 fs_give((void **) &cmd);
1787 fs_give((void **) &test);
1790 if(cmd && (!specific || specific_match))
1791 return(cmd);
1794 if(test)
1795 fs_give((void **) &test);
1797 if(cmd)
1798 fs_give((void **) &cmd);
1801 cmd = NULL;
1803 if(!specific){
1804 cmd = url_os_specified_browser(url);
1806 * Last chance, anything handling "text/html" in mailcap...
1808 if(!cmd && mailcap_can_display(TYPETEXT, "html", NULL, 0)){
1809 MCAP_CMD_S *mc_cmd;
1811 mc_cmd = mailcap_build_command(TYPETEXT, "html",
1812 NULL, "_URL_", NULL, 0);
1814 * right now URL viewing won't return anything requiring
1815 * special handling
1817 if(mc_cmd){
1818 cmd = mc_cmd->command;
1819 fs_give((void **)&mc_cmd);
1824 return(cmd);
1827 char *
1828 img_handler(HANDLE_S *handle)
1830 char *src = handle && handle->h.img.src ? handle->h.img.src : NULL;
1831 char *cmd, *subtype;
1833 if(!src) return NULL;
1835 if(ps_global->VAR_IMAGE_VIEWER)
1836 cmd = cpystr(ps_global->VAR_IMAGE_VIEWER);
1837 else if(!struncmp(src, "IMAGE/", 6)){
1838 src += 6;
1839 for(subtype = src; src && *src != ';'; src++);
1840 if(src){
1841 *src = '\0';
1842 if(mailcap_can_display(TYPEIMAGE, subtype, NULL, 0)){
1843 MCAP_CMD_S *mc_cmd;
1845 mc_cmd = mailcap_build_command(TYPEIMAGE, subtype,
1846 NULL, "_IMG_", NULL, 0);
1847 if(mc_cmd){
1848 cmd = mc_cmd->command;
1849 fs_give((void **)&mc_cmd);
1852 *src = ';';
1856 return cmd;
1859 url_tool_t
1860 url_local_handler(char *s)
1862 int i;
1863 static struct url_t {
1864 char *url;
1865 short len;
1866 url_tool_t f;
1867 } handlers[] = {
1868 {"mailto:", 7, url_local_mailto}, /* see url_tool_t def's */
1869 {"imap://", 7, url_local_imap}, /* for explanations */
1870 {"nntp://", 7, url_local_nntp},
1871 {"file://", 7, url_local_file},
1872 #ifdef ENABLE_LDAP
1873 {"ldap://", 7, url_local_ldap},
1874 #endif
1875 {"news:", 5, url_local_news},
1876 {"x-alpine-ical:", 14, ical_send_reply},
1877 {"x-alpine-gripe:", 15, gripe_gripe_to},
1878 {"x-alpine-help:", 14, url_local_helper},
1879 {"x-pine-help:", 12, url_local_helper},
1880 {"x-alpine-config:", 16, url_local_config},
1881 {"x-alpine-cert:", 14, url_local_certdetails},
1882 {"#", 1, url_local_fragment},
1883 {NULL, 0, NULL}
1886 for(i = 0; handlers[i].url ; i++)
1887 if(!struncmp(s, handlers[i].url, handlers[i].len))
1888 return(handlers[i].f);
1890 return(NULL);
1896 * mailto URL digester ala draft-hoffman-mailto-url-02.txt
1899 url_local_mailto(char *url)
1901 return(url_local_mailto_and_atts(url, NULL));
1905 url_local_mailto_and_atts(char *url, PATMT *attachlist)
1907 ENVELOPE *outgoing;
1908 BODY *body = NULL;
1909 REPLY_S fake_reply;
1910 char *sig, *urlp, *p, *hname, *hvalue;
1911 int rv = 0;
1912 long rflags;
1913 int was_a_body = 0, impl, template_len = 0;
1914 char *fcc = NULL;
1915 PAT_STATE dummy;
1916 REDRAFT_POS_S *redraft_pos = NULL;
1917 ACTION_S *role = NULL;
1919 outgoing = mail_newenvelope();
1920 body = mail_newbody();
1921 body->type = TYPETEXT;
1922 if((body->contents.text.data = (void *) so_get(PicoText,NULL,EDIT_ACCESS)) != NULL){
1924 * URL format is:
1926 * mailtoURL = "mailto:" [ to ] [ headers ]
1927 * to = #mailbox
1928 * headers = "?" header *( "&" header )
1929 * header = hname "=" hvalue
1930 * hname = *urlc
1931 * hvalue = *urlc
1933 * NOTE2: "from" and "bcc" are intentionally excluded from
1934 * the list of understood "header" fields
1936 if((p = strchr(urlp = cpystr(url+7), '?')) != NULL)
1937 *p++ = '\0'; /* headers? Tie off mailbox */
1939 /* grok mailbox as first "to", then roll thru specific headers */
1940 if(*urlp)
1941 rfc822_parse_adrlist(&outgoing->to,
1942 rfc1738_str(urlp),
1943 ps_global->maildomain);
1945 while(p){
1946 /* Find next "header" */
1947 if((p = strchr(hname = p, '&')) != NULL)
1948 *p++ = '\0'; /* tie off "header" */
1950 if((hvalue = strchr(hname, '=')) != NULL)
1951 *hvalue++ = '\0'; /* tie off hname */
1953 if(!hvalue || !strucmp(hname, "subject")){
1954 char *sub = rfc1738_str(hvalue ? hvalue : hname);
1956 if(outgoing->subject){
1957 int len = strlen(outgoing->subject);
1959 fs_resize((void **) &outgoing->subject,
1960 (len + strlen(sub) + 2) * sizeof(char));
1961 snprintf(outgoing->subject + len, strlen(sub)+2, " %s", sub);
1962 outgoing->subject[len + strlen(sub) + 2 - 1] = '\0';
1964 else
1965 outgoing->subject = cpystr(sub);
1967 else if(!strucmp(hname, "to")){
1968 url_mailto_addr(&outgoing->to, hvalue);
1970 else if(!strucmp(hname, "cc")){
1971 url_mailto_addr(&outgoing->cc, hvalue);
1973 else if(!strucmp(hname, "bcc")){
1974 q_status_message(SM_ORDER, 3, 4,
1975 "\"Bcc\" header in mailto url ignored");
1977 else if(!strucmp(hname, "from")){
1978 q_status_message(SM_ORDER, 3, 4,
1979 "\"From\" header in mailto url ignored");
1981 else if(!strucmp(hname, "body")){
1982 char *sub = rfc1738_str(hvalue ? hvalue : "");
1984 so_puts((STORE_S *)body->contents.text.data, sub);
1985 so_puts((STORE_S *)body->contents.text.data, NEWLINE);
1986 was_a_body++;
1990 fs_give((void **) &urlp);
1992 if(ps_global->reply.role_chosen == NULL){
1993 rflags = ROLE_COMPOSE;
1994 if(nonempty_patterns(rflags, &dummy)){
1995 role = set_role_from_msg(ps_global, rflags, -1L, NULL);
1996 if(confirm_role(rflags, &role))
1997 role = combine_inherited_role(role);
1998 else{ /* cancel */
1999 role = NULL;
2000 cmd_cancelled("Composition");
2001 goto outta_here;
2005 else
2006 role = ps_global->reply.role_chosen;
2008 if(role)
2009 q_status_message1(SM_ORDER, 3, 4, "Composing using role \"%s\"",
2010 role->nick);
2011 outgoing->message_id = generate_message_id(role);
2013 if(!was_a_body && role && role->template){
2014 char *filtered;
2016 impl = 1; /* leave cursor in header if not explicit */
2017 filtered = detoken(role, NULL, 0, 0, 0, &redraft_pos, &impl);
2018 if(filtered){
2019 if(*filtered){
2020 so_puts((STORE_S *)body->contents.text.data, filtered);
2021 if(impl == 1)
2022 template_len = strlen(filtered);
2025 fs_give((void **)&filtered);
2028 else
2029 impl = 1;
2031 if(!was_a_body && (sig = detoken(role, NULL, 2, 0, 1, &redraft_pos,
2032 &impl))){
2033 if(impl == 2)
2034 redraft_pos->offset += template_len;
2036 if(*sig)
2037 so_puts((STORE_S *)body->contents.text.data, sig);
2039 fs_give((void **)&sig);
2042 memset((void *)&fake_reply, 0, sizeof(fake_reply));
2043 fake_reply.pseudo = 1;
2044 fake_reply.data.pico_flags = (outgoing->subject) ? P_BODY : P_HEADEND;
2047 if(!(role && role->fcc))
2048 fcc = get_fcc_based_on_to(outgoing->to);
2050 if(attachlist)
2051 create_message_body(&body, attachlist, 0);
2053 pine_send(outgoing, &body, "\"MAILTO\" COMPOSE",
2054 role, fcc, &fake_reply, redraft_pos, NULL, NULL, PS_STICKY_TO);
2055 rv++;
2056 ps_global->mangled_screen = 1;
2058 else
2059 q_status_message(SM_ORDER | SM_DING, 3, 4,
2060 _("Can't create space for composer"));
2062 outta_here:
2063 if(outgoing)
2064 mail_free_envelope(&outgoing);
2066 if(body)
2067 pine_free_body(&body);
2069 if(fcc)
2070 fs_give((void **)&fcc);
2072 free_redraft_pos(&redraft_pos);
2073 free_action(&role);
2074 ps_global->reply.role_chosen = NULL;
2076 return(rv);
2080 void
2081 url_mailto_addr(struct mail_address **a, char *a_raw)
2083 char *p = cpystr(rfc1738_str(a_raw));
2085 while(*a) /* append to address list */
2086 a = &(*a)->next;
2088 rfc822_parse_adrlist(a, p, ps_global->maildomain);
2089 fs_give((void **) &p);
2094 * imap URL digester ala RFC 2192
2097 url_local_imap(char *url)
2099 char *folder, *mailbox = NULL, *errstr = NULL, *search = NULL,
2100 newfolder[MAILTMPLEN];
2101 int rv;
2102 long i;
2103 imapuid_t uid = 0L, uid_val = 0L;
2104 CONTEXT_S *fake_context;
2105 MESSAGECACHE *mc;
2107 rv = url_imap_folder(url, &folder, &uid, &uid_val, &search, 0);
2108 switch(rv & URL_IMAP_MASK){
2109 case URL_IMAP_IMAILBOXLIST :
2110 /* BUG: deal with lsub tag */
2111 if((fake_context = new_context(folder, NULL)) != NULL){
2112 newfolder[0] = '\0';
2113 if(display_folder_list(&fake_context, newfolder,
2114 0, folders_for_goto))
2115 if(strlen(newfolder) + 1 < MAILTMPLEN)
2116 mailbox = newfolder;
2118 else
2119 errstr = "Problem building URL's folder list";
2121 fs_give((void **) &folder);
2122 free_context(&fake_context);
2124 if(mailbox){
2125 return(1);
2127 else if(errstr)
2128 q_status_message(SM_ORDER|SM_DING, 3, 3, errstr);
2129 else
2130 cmd_cancelled("URL Launch");
2132 break;
2134 case URL_IMAP_IMESSAGEPART :
2135 case URL_IMAP_IMESSAGELIST :
2136 if(ps_global && ps_global->ttyo){
2137 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2138 ps_global->mangled_footer = 1;
2141 rv = do_broach_folder(folder, NULL, NULL, 0L);
2142 fs_give((void **) &folder);
2143 switch(rv){
2144 case -1 : /* utter failure */
2145 ps_global->next_screen = main_menu_screen;
2146 break;
2148 case 0 : /* same folder reopened */
2149 ps_global->next_screen = mail_index_screen;
2150 break;
2152 case 1 : /* requested folder open */
2153 ps_global->next_screen = mail_index_screen;
2155 if(uid_val && uid_val != ps_global->mail_stream->uid_validity){
2156 /* Complain! */
2157 q_status_message(SM_ORDER|SM_DING, 3, 3,
2158 "Warning! Referenced folder changed since URL recorded");
2161 if(uid){
2163 * Make specified message the currently selected..
2165 for(i = 1L; i <= mn_get_total(ps_global->msgmap); i++)
2166 if(mail_uid(ps_global->mail_stream, i) == uid){
2167 ps_global->next_screen = mail_view_screen;
2168 mn_set_cur(ps_global->msgmap, i);
2169 break;
2172 if(i > mn_get_total(ps_global->msgmap))
2173 q_status_message(SM_ORDER, 2, 3,
2174 "Couldn't find specified article number");
2176 else if(search){
2178 * Select the specified messages
2179 * and present a zoom'd index...
2181 /* BUG: not dealing with CHARSET yet */
2183 /* ANOTHER BUG: mail_criteria is a compatibility routine for IMAP2BIS
2184 * so it doesn't know about IMAP4 search criteria, like SENTSINCE.
2185 * It also doesn't handle literals. */
2187 pine_mail_search_full(ps_global->mail_stream, NULL,
2188 mail_criteria(search),
2189 SE_NOPREFETCH | SE_FREE);
2191 for(i = 1L; i <= mn_get_total(ps_global->msgmap); i++)
2192 if(ps_global->mail_stream
2193 && i <= ps_global->mail_stream->nmsgs
2194 && (mc = mail_elt(ps_global->mail_stream, i))
2195 && mc->searched)
2196 set_lflag(ps_global->mail_stream,
2197 ps_global->msgmap, i, MN_SLCT, 1);
2199 if((i = any_lflagged(ps_global->msgmap, MN_SLCT)) != 0){
2201 q_status_message2(SM_ORDER, 0, 3,
2202 "%s message%s selected",
2203 long2string(i), plural(i));
2204 /* Zoom the index! */
2205 zoom_index(ps_global, ps_global->mail_stream,
2206 ps_global->msgmap, MN_SLCT);
2211 return(1);
2213 default:
2214 case URL_IMAP_ERROR :
2215 break;
2218 return(0);
2223 url_local_nntp(char *url)
2225 char folder[2*MAILTMPLEN], *group;
2226 int group_len;
2227 long i, article_num;
2229 /* no hostport, no url, end of story */
2230 if((group = strchr(url + 7, '/')) != 0){
2231 group++;
2232 for(group_len = 0; group[group_len] && group[group_len] != '/';
2233 group_len++)
2234 if(!rfc1738_group(&group[group_len]))
2235 /* TRANSLATORS: these are errors in news group URLs */
2236 return(url_bogus(url, _("Invalid newsgroup specified")));
2238 if(group_len){
2239 snprintf(folder, sizeof(folder), "{%.*s/nntp}#news.%.*s",
2240 (int) MIN((group - 1) - (url + 7), MAILTMPLEN-20), url + 7,
2241 (int) MIN(group_len, MAILTMPLEN-20), group);
2242 folder[sizeof(folder)-1] = '\0';
2244 else
2245 return(url_bogus(url, _("No newsgroup specified")));
2247 else
2248 return(url_bogus(url, _("No server specified")));
2250 if(ps_global && ps_global->ttyo){
2251 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2252 ps_global->mangled_footer = 1;
2255 switch(do_broach_folder(rfc1738_str(folder), NULL, NULL, 0L)){
2256 case -1 : /* utter failure */
2257 ps_global->next_screen = main_menu_screen;
2258 break;
2260 case 0 : /* same folder reopened */
2261 ps_global->next_screen = mail_index_screen;
2262 break;
2264 case 1 : /* requested folder open */
2265 ps_global->next_screen = mail_index_screen;
2267 /* grok article number --> c-client UID */
2268 if(group[group_len++] == '/'
2269 && (article_num = atol(&group[group_len]))){
2271 * Make the requested article our current message
2273 for(i = 1; i <= mn_get_nmsgs(ps_global->msgmap); i++)
2274 if(mail_uid(ps_global->mail_stream, i) == article_num){
2275 ps_global->next_screen = mail_view_screen;
2276 if((i = mn_raw2m(ps_global->msgmap, i)) != 0)
2277 mn_set_cur(ps_global->msgmap, i);
2278 break;
2281 if(i == 0 || i > mn_get_total(ps_global->msgmap))
2282 q_status_message(SM_ORDER, 2, 3,
2283 _("Couldn't find specified article number"));
2286 break;
2289 ps_global->redrawer = (void(*)(void))NULL;
2290 return(1);
2295 url_local_news(char *url)
2297 char folder[MAILTMPLEN], *p;
2298 CONTEXT_S *cntxt = NULL;
2301 * NOTE: NO SUPPORT for '*' or message-id
2303 if(*(url+5)){
2304 if(*(url+5) == '/' && *(url+6) == '/')
2305 return(url_local_nntp(url)); /* really meant "nntp://"? */
2307 if(ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0])
2309 * BUG: Only the first NNTP server is tried.
2311 snprintf(folder, sizeof(folder), "{%s/nntp}#news.", ps_global->VAR_NNTP_SERVER[0]);
2312 else
2313 strncpy(folder, "#news.", sizeof(folder));
2315 folder[sizeof(folder)-1] = '\0';
2317 for(p = strncpy(folder + strlen(folder), url + 5, sizeof(folder)-strlen(folder)-1);
2318 *p && rfc1738_group(p);
2319 p++)
2322 if(*p)
2323 return(url_bogus(url, "Invalid newsgroup specified"));
2325 else{ /* fish first group from newsrc */
2326 FOLDER_S *f;
2327 int alphaorder;
2329 folder[0] = '\0';
2331 /* Find first news context */
2332 for(cntxt = ps_global->context_list;
2333 cntxt && !(cntxt->use & CNTXT_NEWS);
2334 cntxt = cntxt->next)
2337 if(cntxt){
2338 if((alphaorder = F_OFF(F_READ_IN_NEWSRC_ORDER, ps_global)) != 0)
2339 (void) F_SET(F_READ_IN_NEWSRC_ORDER, ps_global, 1);
2341 build_folder_list(NULL, cntxt, NULL, NULL, BFL_LSUB);
2342 if((f = folder_entry(0, FOLDERS(cntxt))) != NULL){
2343 strncpy(folder, f->name, sizeof(folder));
2344 folder[sizeof(folder)-1] = '\0';
2347 free_folder_list(cntxt);
2349 if(alphaorder)
2350 (void) F_SET(F_READ_IN_NEWSRC_ORDER, ps_global, 0);
2353 if(folder[0] == '\0'){
2354 q_status_message(SM_ORDER | SM_DING, 3, 3,
2355 "No default newsgroup");
2356 return(0);
2360 if(ps_global && ps_global->ttyo){
2361 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2362 ps_global->mangled_footer = 1;
2365 if(do_broach_folder(rfc1738_str(folder), cntxt, NULL, 0L) < 0)
2366 ps_global->next_screen = main_menu_screen;
2367 else
2368 ps_global->next_screen = mail_index_screen;
2370 ps_global->redrawer = (void(*)(void))NULL;
2372 return(1);
2377 url_local_file(char *file_url)
2379 if(want_to(
2380 /* TRANSLATORS: this is a warning that the file URL can cause programs to run which may
2381 be a security problem. We are asking the user to confirm that they want to do this. */
2382 _("\"file\" URL may cause programs to be run on your system. Run anyway"),
2383 'n', 0, NO_HELP, WT_NORM) == 'y'){
2384 HANDLE_S handle;
2386 /* fake a handle */
2387 handle.h.url.path = file_url;
2388 if((handle.h.url.tool = url_external_handler(&handle, 1))
2389 || (handle.h.url.tool = url_external_handler(&handle, 0))){
2390 url_launch(&handle);
2391 return 1;
2393 else{
2394 q_status_message(SM_ORDER, 0, 4,
2395 _("No viewer for \"file\" URL. VIEWER command cancelled"));
2396 return 0;
2399 q_status_message(SM_ORDER, 0, 4, _("VIEWER command cancelled"));
2400 return 0;
2405 url_local_fragment(char *fragment)
2407 SCRLCTRL_S *st = scroll_state(SS_CUR);
2408 HANDLE_S *hp = NULL;
2411 * find a handle with the fragment's name
2413 if(st){
2414 for(hp = st->parms->text.handles; hp; hp = hp->next)
2415 if(hp->type == URL && hp->h.url.name
2416 && !strcmp(hp->h.url.name, fragment + 1))
2417 break;
2419 if(!hp)
2420 for(hp = st->parms->text.handles->prev; hp; hp = hp->prev)
2421 if(hp->type == URL && hp->h.url.name
2422 && !strcmp(hp->h.url.name, fragment + 1))
2423 break;
2426 * set the top line of the display to contain this line
2428 if(hp && hp->loc){
2429 st->top_text_line = hp->loc->where.row;
2430 ps_global->mangled_body = 1;
2432 else
2433 q_status_message1(SM_ORDER | SM_DING, 0, 3,
2434 "Can't find fragment: %s", fragment);
2436 return(1);
2440 ical_send_reply(char *url)
2442 // ical_compose_reply(url + strlen("x-alpine-ical:"));
2443 return 2;
2448 * Format editorial comment referencing screen offering
2449 * List-* header supplied commands
2452 rfc2369_editorial(long int msgno, HANDLE_S **handlesp, int flags, int width, gf_o_t pc)
2454 char *p, *hdrp, *hdrs[MLCMD_COUNT + 1],
2455 color[64], buf[2048];
2456 int i, n, rv = TRUE;
2457 HANDLE_S *h = NULL;
2459 if((flags & FM_DISPLAY)
2460 && (hdrp = pine_fetchheader_lines(ps_global->mail_stream, msgno,
2461 NULL, rfc2369_hdrs(hdrs)))){
2462 if(*hdrp){
2463 snprintf(buf, sizeof(buf), "Note: This message contains ");
2464 buf[sizeof(buf)-1] = '\0';
2465 p = buf + strlen(buf);
2467 if(handlesp){
2468 h = new_handle(handlesp);
2469 h->type = Function;
2470 h->h.func.f = rfc2369_display;
2471 h->h.func.args.stream = ps_global->mail_stream;
2472 h->h.func.args.msgmap = ps_global->msgmap;
2473 h->h.func.args.msgno = msgno;
2475 if(!(flags & FM_NOCOLOR)
2476 && handle_start_color(color, sizeof(color), &n, 0)){
2477 if((p-buf)+n < sizeof(buf))
2478 for(i = 0; i < n; i++)
2479 *p++ = color[i];
2481 else if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)){
2482 *p++ = TAG_EMBED;
2483 *p++ = TAG_BOLDON;
2486 if((p-buf)+2 < sizeof(buf)){
2487 *p++ = TAG_EMBED;
2488 *p++ = TAG_HANDLE;
2491 snprintf(p + 1, sizeof(buf)-(p+1-buf), "%d", h->key);
2492 buf[sizeof(buf)-1] = '\0';
2493 *p = strlen(p + 1);
2494 p += (*p + 1);
2497 sstrncpy(&p, "email list management information", sizeof(buf)-(p-buf));
2498 buf[sizeof(buf)-1] = '\0';
2500 if(h){
2501 /* in case it was the current selection */
2502 if((p-buf)+2 < sizeof(buf)){
2503 *p++ = TAG_EMBED;
2504 *p++ = TAG_INVOFF;
2507 if(handle_end_color(color, sizeof(color), &n)){
2508 if((p-buf)+n < sizeof(buf))
2509 for(i = 0; i < n; i++)
2510 *p++ = color[i];
2512 else{
2513 if((p-buf)+2 < sizeof(buf)){
2514 *p++ = TAG_EMBED;
2515 *p++ = TAG_BOLDOFF;
2519 if(p-buf < sizeof(buf))
2520 *p = '\0';
2523 buf[sizeof(buf)-1] = '\0';
2525 rv = (gf_puts(NEWLINE, pc)
2526 && format_editorial(buf, width, flags, handlesp, pc) == NULL
2527 && gf_puts(NEWLINE, pc));
2530 fs_give((void **) &hdrp);
2533 return(rv);
2538 /*----------------------------------------------------------------------
2539 routine for displaying text on the screen.
2541 Args: sparms -- structure of args controlling what happens outside
2542 just the business of managing text scrolling
2544 This displays in three different kinds of text. One is an array of
2545 lines passed in in text_array. The other is a simple long string of
2546 characters passed in in text.
2548 The style determines what some of the error messages will be, and
2549 what commands are available as different things are appropriate for
2550 help text than for message text etc.
2552 ---*/
2555 scrolltool(SCROLL_S *sparms)
2557 register long cur_top_line, num_display_lines;
2558 UCS ch;
2559 int result, done, cmd, found_on, found_on_index,
2560 first_view, force, scroll_lines, km_size,
2561 cursor_row, cursor_col, km_popped, rv,
2562 nrows, ncols;
2563 char *utf8str;
2564 long jn;
2565 struct key_menu *km;
2566 HANDLE_S *next_handle;
2567 bitmap_t bitmap;
2568 OtherMenu what;
2569 Pos whereis_pos;
2571 num_display_lines = SCROLL_LINES(ps_global);
2572 km_popped = 0;
2573 ps_global->mangled_header = 1;
2574 ps_global->mangled_footer = 1;
2575 ps_global->mangled_body = !sparms->body_valid;
2576 ncols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 0;
2577 nrows = ps_global->ttyo ? ps_global->ttyo->screen_rows : 0;
2579 what = sparms->keys.what; /* which key menu to display */
2580 cur_top_line = 0;
2581 done = 0;
2582 found_on = -1;
2583 found_on_index = -1;
2584 first_view = 1;
2585 if(sparms->quell_first_view)
2586 first_view = 0;
2588 force = 0;
2589 ch = 'x'; /* for first time through */
2590 whereis_pos.row = 0;
2591 whereis_pos.col = 0;
2592 next_handle = sparms->text.handles;
2594 set_scroll_text(sparms, cur_top_line, scroll_state(SS_NEW));
2595 format_scroll_text();
2597 if((km = sparms->keys.menu) != NULL){
2598 memcpy(bitmap, sparms->keys.bitmap, sizeof(bitmap_t));
2600 else{
2601 setbitmap(bitmap);
2602 km = &simple_text_keymenu;
2603 #ifdef _WINDOWS
2604 sparms->mouse.popup = simple_text_popup;
2605 #endif
2608 if(!sparms->bar.title)
2609 sparms->bar.title = "Text";
2611 if(sparms->bar.style == TitleBarNone){
2612 if(THREADING() && sp_viewing_a_thread(ps_global->mail_stream))
2613 sparms->bar.style = ThrdMsgPercent;
2614 else
2615 sparms->bar.style = MsgTextPercent;
2618 switch(sparms->start.on){
2619 case LastPage :
2620 cur_top_line = MAX(0, scroll_text_lines() - (num_display_lines-2));
2621 if(F_ON(F_SHOW_CURSOR, ps_global)){
2622 whereis_pos.row = scroll_text_lines() - cur_top_line;
2623 found_on = scroll_text_lines() - 1;
2626 break;
2628 case Fragment :
2629 if(sparms->start.loc.frag){
2630 (void) url_local_fragment(sparms->start.loc.frag);
2632 cur_top_line = scroll_state(SS_CUR)->top_text_line;
2634 if(F_ON(F_SHOW_CURSOR, ps_global)){
2635 whereis_pos.row = scroll_text_lines() - cur_top_line;
2636 found_on = scroll_text_lines() - 1;
2640 break;
2642 case Offset :
2643 if(sparms->start.loc.offset){
2644 for(cur_top_line = 0L;
2645 cur_top_line + 1 < scroll_text_lines()
2646 && (sparms->start.loc.offset
2647 -= scroll_handle_column(cur_top_line,
2648 -1)) >= 0;
2649 cur_top_line++)
2653 break;
2655 case Handle :
2656 if(scroll_handle_obscured(sparms->text.handles))
2657 cur_top_line = scroll_handle_reframe(-1, TRUE);
2659 break;
2661 default : /* no-op */
2662 break;
2665 /* prepare for calls below to tell us where to go */
2666 ps_global->next_screen = SCREEN_FUN_NULL;
2668 cancel_busy_cue(-1);
2670 while(!done) {
2671 ps_global->user_says_cancel = 0;
2672 if(km_popped){
2673 km_popped--;
2674 if(km_popped == 0){
2675 clearfooter(ps_global);
2676 ps_global->mangled_body = 1;
2680 if(ps_global->mangled_screen) {
2681 ps_global->mangled_header = 1;
2682 ps_global->mangled_footer = 1;
2683 ps_global->mangled_body = 1;
2686 if(!sparms->quell_newmail && streams_died())
2687 ps_global->mangled_header = 1;
2689 dprint((9, "@@@@ current:%ld\n",
2690 mn_get_cur(ps_global->msgmap)));
2693 /*==================== All Screen painting ====================*/
2694 /*-------------- The title bar ---------------*/
2695 update_scroll_titlebar(cur_top_line, ps_global->mangled_header);
2697 if(ps_global->mangled_screen){
2698 /* this is the only line not cleared by header, body or footer
2699 * repaint calls....
2701 ClearLine(1);
2702 ps_global->mangled_screen = 0;
2705 /*---- Scroll or update the body of the text on the screen -------*/
2706 cur_top_line = scroll_scroll_text(cur_top_line, next_handle,
2707 ps_global->mangled_body);
2708 ps_global->redrawer = redraw_scroll_text;
2709 ps_global->mangled_body = 0;
2711 /*--- Check to see if keymenu might change based on next_handle --*/
2712 if(sparms->text.handles != next_handle)
2713 ps_global->mangled_footer = 1;
2715 if(next_handle)
2716 sparms->text.handles = next_handle;
2718 /*------------- The key menu footer --------------------*/
2719 if(ps_global->mangled_footer || sparms->keys.each_cmd){
2720 if(km_popped){
2721 FOOTER_ROWS(ps_global) = 3;
2722 clearfooter(ps_global);
2725 if(F_ON(F_ARROW_NAV, ps_global)){
2726 menu_clear_binding(km, KEY_LEFT);
2727 if((cmd = menu_clear_binding(km, '<')) != MC_UNKNOWN){
2728 menu_add_binding(km, '<', cmd);
2729 menu_add_binding(km, KEY_LEFT, cmd);
2733 if(F_ON(F_ARROW_NAV, ps_global)){
2734 menu_clear_binding(km, KEY_RIGHT);
2735 if((cmd = menu_clear_binding(km, '>')) != MC_UNKNOWN){
2736 menu_add_binding(km, '>', cmd);
2737 menu_add_binding(km, KEY_RIGHT, cmd);
2741 if(sparms->keys.each_cmd){
2742 (*sparms->keys.each_cmd)(sparms,
2743 scroll_handle_obscured(sparms->text.handles));
2744 memcpy(bitmap, sparms->keys.bitmap, sizeof(bitmap_t));
2747 if(menu_binding_index(km, MC_JUMP) >= 0){
2748 for(cmd = 0; cmd < 10; cmd++)
2749 if(F_ON(F_ENABLE_JUMP, ps_global))
2750 (void) menu_add_binding(km, '0' + cmd, MC_JUMP);
2751 else
2752 (void) menu_clear_binding(km, '0' + cmd);
2755 draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
2756 1-FOOTER_ROWS(ps_global), 0, what);
2757 what = SameMenu;
2758 ps_global->mangled_footer = 0;
2759 if(km_popped){
2760 FOOTER_ROWS(ps_global) = 1;
2761 mark_keymenu_dirty();
2765 if((ps_global->first_time_user || ps_global->show_new_version)
2766 && first_view && sparms->text.handles
2767 && (sparms->text.handles->next || sparms->text.handles->prev)
2768 && !sparms->quell_help)
2769 q_status_message(SM_ORDER, 0, 3, HANDLE_INIT_MSG);
2771 /*============ Check for New Mail and CheckPoint ============*/
2772 if(!sparms->quell_newmail &&
2773 new_mail(force, NM_TIMING(ch), NM_STATUS_MSG) >= 0){
2774 update_scroll_titlebar(cur_top_line, 1);
2775 if(ps_global->mangled_footer)
2776 draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
2777 1-FOOTER_ROWS(ps_global), 0, what);
2779 ps_global->mangled_footer = 0;
2783 * If an expunge of the current message happened during the
2784 * new mail check we want to bail out of here. See mm_expunged.
2786 if(ps_global->next_screen != SCREEN_FUN_NULL){
2787 done = 1;
2788 continue;
2791 if(ps_global->noticed_change_in_unseen){
2792 ps_global->noticed_change_in_unseen = 0; /* redraw only once */
2793 cmd = MC_RESIZE; /* causes cursor to be saved in folder_lister */
2794 done = 1;
2795 continue;
2798 if(first_view && num_display_lines >= scroll_text_lines())
2799 q_status_message1(SM_INFO, 0, 1, "ALL of %s", STYLE_NAME(sparms));
2802 force = 0; /* may not need to next time around */
2803 first_view = 0; /* check_point a priority any more? */
2805 /*==================== Output the status message ==============*/
2806 if(!sparms->no_stat_msg){
2807 if(km_popped){
2808 FOOTER_ROWS(ps_global) = 3;
2809 mark_status_unknown();
2812 display_message(ch);
2813 if(km_popped){
2814 FOOTER_ROWS(ps_global) = 1;
2815 mark_status_unknown();
2819 if(F_ON(F_SHOW_CURSOR, ps_global)){
2820 #ifdef WINDOWS
2821 if(cur_top_line != scroll_state(SS_CUR)->top_text_line)
2822 whereis_pos.row = 0;
2823 #endif
2825 if(whereis_pos.row > 0){
2826 cursor_row = SCROLL_LINES_ABOVE(ps_global)
2827 + whereis_pos.row - 1;
2828 cursor_col = whereis_pos.col;
2830 else{
2831 POSLIST_S *lp = NULL;
2833 if(sparms->text.handles &&
2834 !scroll_handle_obscured(sparms->text.handles)){
2835 SCRLCTRL_S *st = scroll_state(SS_CUR);
2837 for(lp = sparms->text.handles->loc; lp; lp = lp->next)
2838 if(lp->where.row >= st->top_text_line
2839 && lp->where.row < st->top_text_line
2840 + st->screen.length){
2841 cursor_row = lp->where.row - cur_top_line
2842 + SCROLL_LINES_ABOVE(ps_global);
2843 cursor_col = lp->where.col;
2844 break;
2848 if(!lp){
2849 cursor_col = 0;
2850 /* first new line of text */
2851 cursor_row = SCROLL_LINES_ABOVE(ps_global) +
2852 ((cur_top_line == 0) ? 0 : ps_global->viewer_overlap);
2856 else{
2857 cursor_col = 0;
2858 cursor_row = ps_global->ttyo->screen_rows
2859 - SCROLL_LINES_BELOW(ps_global);
2862 MoveCursor(cursor_row, cursor_col);
2864 /*================ Get command and validate =====================*/
2865 #ifdef MOUSE
2866 #ifndef WIN32
2867 if(sparms->text.handles)
2868 #endif
2870 mouse_in_content(KEY_MOUSE, -1, -1, 0x5, 0);
2871 register_mfunc(mouse_in_content, HEADER_ROWS(ps_global), 0,
2872 ps_global->ttyo->screen_rows
2873 - (FOOTER_ROWS(ps_global) + 1),
2874 ps_global->ttyo->screen_cols);
2876 #endif
2877 #ifdef _WINDOWS
2878 mswin_allowcopy(mswin_readscrollbuf);
2879 mswin_setscrollcallback(pcpine_do_scroll);
2881 if(sparms->help.text != NO_HELP)
2882 mswin_sethelptextcallback(pcpine_help_scroll);
2884 if(sparms->text.handles
2885 && sparms->text.handles->type != Folder)
2886 mswin_mousetrackcallback(pcpine_view_cursor);
2888 if(ps_global->prev_screen == mail_view_screen)
2889 mswin_setviewinwindcallback(view_in_new_window);
2890 #endif
2891 rv = -1;
2892 if(sparms->aux_function
2893 && sparms->aux_condition
2894 && ((rv = (sparms->aux_condition)(sparms->aux_value)) == 0))
2895 (sparms->aux_function)(sparms->aux_value, sparms->aux_rv_value);
2897 if(rv == 0){
2898 ch = (sparms->decode_aux_rv_value)(sparms->aux_value, sparms->aux_rv_value);
2899 if (ch == NO_OP_COMMAND) ch = read_command(&utf8str);
2901 else if(cmd == MC_RESIZE
2902 || ps_global->ttyo == NULL
2903 || (ps_global->ttyo->screen_cols == ncols
2904 && ps_global->ttyo->screen_rows == nrows))
2905 ch = (sparms->quell_newmail || read_command_prep()) ? read_command(&utf8str) : NO_OP_COMMAND;
2906 #ifdef MOUSE
2907 #ifndef WIN32
2908 if(sparms->text.handles)
2909 #endif
2910 clear_mfunc(mouse_in_content);
2911 #endif
2912 #ifdef _WINDOWS
2913 mswin_allowcopy(NULL);
2914 mswin_setscrollcallback(NULL);
2915 mswin_sethelptextcallback(NULL);
2916 mswin_mousetrackcallback(NULL);
2917 mswin_setviewinwindcallback(NULL);
2918 cur_top_line = scroll_state(SS_CUR)->top_text_line;
2919 #endif
2920 /* we need to check if there was a resize of the screen
2921 * which did not happen in this routine but during a call
2922 * to another routine from this routing, and that routine has no
2923 * way to tell us that a resize happened
2925 if(cmd != MC_RESIZE
2926 && ps_global->ttyo
2927 && (ps_global->ttyo->screen_cols != ncols
2928 || ps_global->ttyo->screen_rows != nrows))
2929 cmd = MC_RESIZE;
2930 else
2931 cmd = menu_command(ch, km);
2933 ncols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 0;
2934 nrows = ps_global->ttyo ? ps_global->ttyo->screen_rows : 0;
2936 if(km_popped)
2937 switch(cmd){
2938 case MC_NONE :
2939 case MC_OTHER :
2940 case MC_RESIZE:
2941 case MC_REPAINT :
2942 km_popped++;
2943 break;
2945 default:
2946 clearfooter(ps_global);
2947 break;
2951 /*============= Execute command =======================*/
2952 switch(cmd){
2954 /* ------ Help -------*/
2955 case MC_HELP :
2956 if(FOOTER_ROWS(ps_global) == 1 && km_popped == 0){
2957 km_popped = 2;
2958 ps_global->mangled_footer = 1;
2959 break;
2962 whereis_pos.row = 0;
2963 if(sparms->help.text == NO_HELP){
2964 q_status_message(SM_ORDER, 0, 5,
2965 _("No help text currently available"));
2966 break;
2969 km_size = FOOTER_ROWS(ps_global);
2971 helper(sparms->help.text, sparms->help.title, 0);
2973 if(ps_global->next_screen != main_menu_screen
2974 && km_size == FOOTER_ROWS(ps_global)) {
2975 /* Have to reset because helper uses scroll_text */
2976 num_display_lines = SCROLL_LINES(ps_global);
2977 ps_global->mangled_screen = 1;
2979 else
2980 done = 1;
2982 break;
2985 /*---------- Roll keymenu ------*/
2986 case MC_OTHER :
2987 if(F_OFF(F_USE_FK, ps_global))
2988 warn_other_cmds();
2990 what = NextMenu;
2991 ps_global->mangled_footer = 1;
2992 break;
2995 /* -------- Scroll back one page -----------*/
2996 case MC_PAGEUP :
2997 whereis_pos.row = 0;
2998 if(cur_top_line) {
2999 scroll_lines = MIN(MAX(num_display_lines -
3000 ps_global->viewer_overlap, 1), num_display_lines);
3001 cur_top_line -= scroll_lines;
3002 if(cur_top_line <= 0){
3003 cur_top_line = 0;
3004 q_status_message1(SM_INFO, 0, 1, "START of %s",
3005 STYLE_NAME(sparms));
3008 else{
3009 /* hilite last available handle */
3010 next_handle = NULL;
3011 if(sparms->text.handles){
3012 HANDLE_S *h = sparms->text.handles;
3014 while((h = scroll_handle_prev_sel(h))
3015 && !scroll_handle_obscured(h))
3016 next_handle = h;
3019 if(!next_handle)
3020 q_status_message1(SM_ORDER, 0, 1, _("Already at start of %s"),
3021 STYLE_NAME(sparms));
3026 break;
3029 /*---- Scroll down one page -------*/
3030 case MC_PAGEDN :
3031 if(cur_top_line + num_display_lines < scroll_text_lines()){
3032 whereis_pos.row = 0;
3033 scroll_lines = MIN(MAX(num_display_lines -
3034 ps_global->viewer_overlap, 1), num_display_lines);
3035 cur_top_line += scroll_lines;
3037 if(cur_top_line + num_display_lines >= scroll_text_lines())
3038 q_status_message1(SM_INFO, 0, 1, "END of %s",
3039 STYLE_NAME(sparms));
3041 else if(!sparms->end_scroll
3042 || !(done = (*sparms->end_scroll)(sparms))){
3043 q_status_message1(SM_ORDER, 0, 1, _("Already at end of %s"),
3044 STYLE_NAME(sparms));
3045 /* hilite last available handle */
3046 if(sparms->text.handles){
3047 HANDLE_S *h = sparms->text.handles;
3049 while((h = scroll_handle_next_sel(h)) != NULL)
3050 next_handle = h;
3054 break;
3056 /* scroll to the top page */
3057 case MC_HOMEKEY:
3058 if(cur_top_line){
3059 cur_top_line = 0;
3060 q_status_message1(SM_INFO, 0, 1, "START of %s",
3061 STYLE_NAME(sparms));
3064 next_handle = NULL;
3065 if(sparms->text.handles){
3066 HANDLE_S *h = sparms->text.handles;
3068 while((h = scroll_handle_prev_sel(h)) != NULL)
3069 next_handle = h;
3071 break;
3073 /* scroll to the bottom page */
3074 case MC_ENDKEY:
3075 if(cur_top_line + num_display_lines < scroll_text_lines()){
3076 cur_top_line = scroll_text_lines() - MIN(5, num_display_lines);
3077 q_status_message1(SM_INFO, 0, 1, "END of %s",
3078 STYLE_NAME(sparms));
3081 if(sparms->text.handles){
3082 HANDLE_S *h = sparms->text.handles;
3084 while((h = scroll_handle_next_sel(h)) != NULL)
3085 next_handle = h;
3087 break;
3089 /*------ Scroll down one line -----*/
3090 case MC_CHARDOWN :
3091 next_handle = NULL;
3092 if(sparms->text.handles){
3093 if(sparms->vert_handle){
3094 HANDLE_S *h, *h2;
3095 int i, j, k;
3097 h2 = sparms->text.handles;
3098 if(h2->type == Folder && h2->prev && h2->prev->is_dual_do_open)
3099 h2 = h2->prev;
3101 i = h2->loc->where.row + 1;
3102 j = h2->loc->where.col;
3103 for(h = NULL, k = h2->key;
3104 h2 && (!h
3105 || (h->loc->where.row == h2->loc->where.row));
3106 h2 = h2->next)
3107 /* must be different key */
3108 /* ... below current line */
3109 /* ... pref'bly to left */
3110 if(h2->key != k
3111 && h2->loc->where.row >= i){
3112 if(h2->loc->where.col > j){
3113 if(!h)
3114 h = h2;
3116 break;
3118 else
3119 h = h2;
3122 if(h){
3123 whereis_pos.row = 0;
3124 next_handle = h;
3125 if((result = scroll_handle_obscured(next_handle)) != 0){
3126 long new_top;
3128 if(scroll_handle_obscured(sparms->text.handles)
3129 && result > 0)
3130 next_handle = sparms->text.handles;
3132 ps_global->mangled_body++;
3133 new_top = scroll_handle_reframe(next_handle->key,0);
3134 if(new_top >= 0)
3135 cur_top_line = new_top;
3139 else if(!(ch == ctrl('N') || F_ON(F_FORCE_ARROWS, ps_global)))
3140 next_handle = scroll_handle_next(sparms->text.handles);
3143 if(!next_handle){
3144 if(cur_top_line + num_display_lines < scroll_text_lines()){
3145 whereis_pos.row = 0;
3146 cur_top_line++;
3147 if(cur_top_line + num_display_lines >= scroll_text_lines())
3148 q_status_message1(SM_INFO, 0, 1, "END of %s",
3149 STYLE_NAME(sparms));
3151 else
3152 q_status_message1(SM_ORDER, 0, 1, _("Already at end of %s"),
3153 STYLE_NAME(sparms));
3156 break;
3159 /* ------ Scroll back up one line -------*/
3160 case MC_CHARUP :
3161 next_handle = NULL;
3162 if(sparms->text.handles){
3163 if(sparms->vert_handle){
3164 HANDLE_S *h, *h2;
3165 int i, j, k;
3167 h2 = sparms->text.handles;
3168 if(h2->type == Folder && h2->prev && h2->prev->is_dual_do_open)
3169 h2 = h2->prev;
3171 i = h2->loc->where.row - 1;
3172 j = h2->loc->where.col;
3174 for(h = NULL, k = h2->key;
3175 h2 && (!h
3176 || (h->loc->where.row == h2->loc->where.row));
3177 h2 = h2->prev)
3178 /* must be new key, above current
3179 * line and pref'bly to right
3181 if(h2->key != k
3182 && h2->loc->where.row <= i){
3183 if(h2->loc->where.col < j){
3184 if(!h)
3185 h = h2;
3187 break;
3189 else
3190 h = h2;
3193 if(h){
3194 whereis_pos.row = 0;
3195 next_handle = h;
3196 if((result = scroll_handle_obscured(next_handle)) != 0){
3197 long new_top;
3199 if(scroll_handle_obscured(sparms->text.handles)
3200 && result < 0)
3201 next_handle = sparms->text.handles;
3203 ps_global->mangled_body++;
3204 new_top = scroll_handle_reframe(next_handle->key,0);
3205 if(new_top >= 0)
3206 cur_top_line = new_top;
3210 else if(!(ch == ctrl('P') || F_ON(F_FORCE_ARROWS, ps_global)))
3211 next_handle = scroll_handle_prev(sparms->text.handles);
3214 if(!next_handle){
3215 whereis_pos.row = 0;
3216 if(cur_top_line){
3217 cur_top_line--;
3218 if(cur_top_line == 0)
3219 q_status_message1(SM_INFO, 0, 1, "START of %s",
3220 STYLE_NAME(sparms));
3222 else
3223 q_status_message1(SM_ORDER, 0, 1,
3224 _("Already at start of %s"),
3225 STYLE_NAME(sparms));
3228 break;
3231 case MC_NEXT_HANDLE :
3232 if((next_handle = scroll_handle_next_sel(sparms->text.handles)) != NULL){
3233 whereis_pos.row = 0;
3234 if((result = scroll_handle_obscured(next_handle)) != 0){
3235 long new_top;
3237 if(scroll_handle_obscured(sparms->text.handles)
3238 && result > 0)
3239 next_handle = sparms->text.handles;
3241 ps_global->mangled_body++;
3242 new_top = scroll_handle_reframe(next_handle->key, 0);
3243 if(new_top >= 0)
3244 cur_top_line = new_top;
3247 else{
3248 if(scroll_handle_obscured(sparms->text.handles)){
3249 long new_top;
3251 ps_global->mangled_body++;
3252 if((new_top = scroll_handle_reframe(-1, 0)) >= 0){
3253 whereis_pos.row = 0;
3254 cur_top_line = new_top;
3258 q_status_message1(SM_ORDER, 0, 1,
3259 _("Already on last item in %s"),
3260 STYLE_NAME(sparms));
3263 break;
3266 case MC_PREV_HANDLE :
3267 if((next_handle = scroll_handle_prev_sel(sparms->text.handles)) != NULL){
3268 whereis_pos.row = 0;
3269 if((result = scroll_handle_obscured(next_handle)) != 0){
3270 long new_top;
3272 if(scroll_handle_obscured(sparms->text.handles)
3273 && result < 0)
3274 next_handle = sparms->text.handles;
3276 ps_global->mangled_body++;
3277 new_top = scroll_handle_reframe(next_handle->key, 0);
3278 if(new_top >= 0)
3279 cur_top_line = new_top;
3282 else{
3283 if(scroll_handle_obscured(sparms->text.handles)){
3284 long new_top;
3286 ps_global->mangled_body++;
3287 if((new_top = scroll_handle_reframe(-1, 0)) >= 0){
3288 whereis_pos.row = 0;
3289 cur_top_line = new_top;
3293 q_status_message1(SM_ORDER, 0, 1,
3294 _("Already on first item in %s"),
3295 STYLE_NAME(sparms));
3298 break;
3301 /*------ View the current handle ------*/
3302 case MC_VIEW_HANDLE :
3303 switch(scroll_handle_obscured(sparms->text.handles)){
3304 default :
3305 case 0 :
3306 switch(scroll_handle_launch(sparms->text.handles,
3307 sparms->text.handles->force_display)){
3308 case 1 :
3309 cmd = MC_EXIT; /* propagate */
3310 done = 1;
3311 break;
3313 case -1 :
3314 cmd_cancelled(NULL);
3315 break;
3317 default :
3318 break;
3321 cur_top_line = scroll_state(SS_CUR)->top_text_line;
3322 break;
3324 case 1 :
3325 q_status_message(SM_ORDER, 0, 2, HANDLE_BELOW_ERR);
3326 break;
3328 case -1 :
3329 q_status_message(SM_ORDER, 0, 2, HANDLE_ABOVE_ERR);
3330 break;
3333 break;
3335 /*---------- Search text (where is) ----------*/
3336 case MC_WHEREIS :
3337 ps_global->mangled_footer = 1;
3338 {long start_row;
3339 int start_index, key = 0;
3340 char *report = NULL;
3342 start_row = cur_top_line;
3343 start_index = 0;
3345 if(F_ON(F_SHOW_CURSOR,ps_global)){
3346 if(found_on < 0
3347 || found_on >= scroll_text_lines()
3348 || found_on < cur_top_line
3349 || found_on >= cur_top_line + num_display_lines){
3350 start_row = cur_top_line;
3351 start_index = 0;
3353 else{
3354 if(found_on_index < 0){
3355 start_row = found_on + 1;
3356 start_index = 0;
3358 else{
3359 start_row = found_on;
3360 start_index = found_on_index+1;
3364 else if(sparms->srch_handle){
3365 HANDLE_S *h;
3367 if((h = scroll_handle_next_sel(sparms->text.handles)) != NULL){
3369 * Translate the screen's column into the
3370 * line offset to start on...
3372 * This makes it so search_text never returns -3
3373 * so we don't know it is the same match. That's
3374 * because we start well after the current handle
3375 * (at the next handle) and that causes us to
3376 * think the one we just matched on is a different
3377 * one from before. Can't think of an easy way to
3378 * fix it, though, and it isn't a big deal. We still
3379 * match, we just don't say current line contains
3380 * the only match.
3382 start_row = h->loc->where.row;
3383 start_index = scroll_handle_index(start_row, h->loc->where.col);
3385 else{
3386 /* last handle, start over at top */
3387 start_row = cur_top_line;
3388 start_index = 0;
3391 else{
3392 start_row = (found_on < 0
3393 || found_on >= scroll_text_lines()
3394 || found_on < cur_top_line
3395 || found_on >= cur_top_line + num_display_lines)
3396 ? cur_top_line : found_on + 1,
3397 start_index = 0;
3400 found_on = search_text(-FOOTER_ROWS(ps_global), start_row,
3401 start_index, &report,
3402 &whereis_pos, &found_on_index);
3404 if(found_on == -4){ /* search to top of text */
3405 whereis_pos.row = 0;
3406 whereis_pos.col = 0;
3407 found_on = 0;
3408 if(sparms->text.handles && sparms->srch_handle)
3409 key = 1;
3411 else if(found_on == -5){ /* search to bottom of text */
3412 HANDLE_S *h;
3414 whereis_pos.row = MAX(scroll_text_lines() - 1, 0);
3415 whereis_pos.col = 0;
3416 found_on = whereis_pos.row;
3417 if((h = sparms->text.handles) && sparms->srch_handle)
3419 key = h->key;
3420 while((h = h->next) != NULL);
3422 else if(found_on == -3){
3423 whereis_pos.row = found_on = start_row;
3424 found_on_index = start_index - 1;
3425 q_status_message(SM_ORDER, 1, 3,
3426 _("Current line contains the only match"));
3429 if(found_on >= 0){
3430 result = found_on < cur_top_line;
3431 if(!key)
3432 key = (sparms->text.handles)
3433 ? dot_on_handle(found_on, whereis_pos.col) : 0;
3435 if(F_ON(F_FORCE_LOW_SPEED,ps_global)
3436 || ps_global->low_speed
3437 || F_ON(F_SHOW_CURSOR,ps_global)
3438 || key){
3439 if((found_on >= cur_top_line + num_display_lines ||
3440 found_on < cur_top_line) &&
3441 num_display_lines > ps_global->viewer_overlap){
3442 cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
3443 if(scroll_text_lines()-cur_top_line < 5)
3444 cur_top_line = MAX(0,
3445 scroll_text_lines()-MIN(5,num_display_lines));
3447 /* else leave cur_top_line alone */
3449 else{
3450 cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
3451 if(scroll_text_lines()-cur_top_line < 5)
3452 cur_top_line = MAX(0,
3453 scroll_text_lines()-MIN(5,num_display_lines));
3456 whereis_pos.row = whereis_pos.row - cur_top_line + 1;
3457 if(report)
3458 q_status_message(SM_ORDER, 0, 3, report);
3459 else
3460 q_status_message2(SM_ORDER, 0, 3,
3461 "%sFound on line %s on screen",
3462 result ? "Search wrapped to start. " : "",
3463 int2string(whereis_pos.row));
3465 if(key){
3466 if(sparms->text.handles->key < key)
3467 for(next_handle = sparms->text.handles->next;
3468 next_handle->key != key;
3469 next_handle = next_handle->next)
3471 else
3472 for(next_handle = sparms->text.handles;
3473 next_handle->key != key;
3474 next_handle = next_handle->prev)
3478 else if(found_on == -1)
3479 cmd_cancelled("Search");
3480 else
3481 q_status_message(SM_ORDER, 0, 3, _("Word not found"));
3484 break;
3487 /*-------------- jump command -------------*/
3488 /* NOTE: preempt the process_cmd() version because
3489 * we need to get at the number..
3491 case MC_JUMP :
3492 jn = jump_to(ps_global->msgmap, -FOOTER_ROWS(ps_global), ch,
3493 sparms, View);
3494 if(sparms && sparms->jump_is_debug)
3495 done = 1;
3496 else if(jn > 0 && jn != mn_get_cur(ps_global->msgmap)){
3498 if(mn_total_cur(ps_global->msgmap) > 1L)
3499 mn_reset_cur(ps_global->msgmap, jn);
3500 else
3501 mn_set_cur(ps_global->msgmap, jn);
3503 done = 1;
3505 else
3506 ps_global->mangled_footer = 1;
3508 break;
3511 #ifdef MOUSE
3512 /*-------------- Mouse Event -------------*/
3513 case MC_MOUSE:
3515 MOUSEPRESS mp;
3516 long line;
3517 int key;
3519 mouse_get_last (NULL, &mp);
3520 mp.row -= 2;
3522 /* The clicked line have anything special on it? */
3523 if((line = cur_top_line + mp.row) < scroll_text_lines()
3524 && (key = dot_on_handle(line, mp.col))){
3525 switch(mp.button){
3526 case M_BUTTON_RIGHT :
3527 #ifdef _WINDOWS
3528 if(sparms->mouse.popup){
3529 if(sparms->text.handles->key < key)
3530 for(next_handle = sparms->text.handles->next;
3531 next_handle->key != key;
3532 next_handle = next_handle->next)
3534 else
3535 for(next_handle = sparms->text.handles;
3536 next_handle->key != key;
3537 next_handle = next_handle->prev)
3540 if(sparms->mouse.popup){
3541 cur_top_line = scroll_scroll_text(cur_top_line,
3542 next_handle,
3543 ps_global->mangled_body);
3544 fflush(stdout);
3545 switch((*sparms->mouse.popup)(sparms, key)){
3546 case 1 :
3547 cur_top_line = doubleclick_handle(sparms, next_handle, &cmd, &done);
3548 break;
3550 case 2 :
3551 done++;
3552 break;
3557 #endif
3558 break;
3560 case M_BUTTON_LEFT :
3561 if(sparms->text.handles->key < key)
3562 for(next_handle = sparms->text.handles->next;
3563 next_handle->key != key;
3564 next_handle = next_handle->next)
3566 else
3567 for(next_handle = sparms->text.handles;
3568 next_handle->key != key;
3569 next_handle = next_handle->prev)
3572 if(mp.doubleclick) /* launch url */
3573 cur_top_line = doubleclick_handle(sparms, next_handle, &cmd, &done);
3574 else if(sparms->mouse.click)
3575 (*sparms->mouse.click)(sparms);
3577 break;
3579 case M_BUTTON_MIDDLE : /* NO-OP for now */
3580 break;
3582 default: /* just ignore */
3583 break;
3586 #ifdef _WINDOWS
3587 else if(mp.button == M_BUTTON_RIGHT){
3589 * Toss generic popup on to the screen
3591 if(sparms->mouse.popup)
3592 if((*sparms->mouse.popup)(sparms, 0) == 2){
3593 done++;
3596 #endif
3599 break;
3600 #endif /* MOUSE */
3603 /*-------------- Display Resize -------------*/
3604 case MC_RESIZE :
3605 if(sparms->resize_exit){
3606 long line;
3609 * Figure out char offset of the char in the top left
3610 * corner of the display. Pass it back to the
3611 * fetcher/formatter and have it pass the offset
3612 * back to us...
3614 sparms->start.on = Offset;
3615 for(sparms->start.loc.offset = line = 0L;
3616 line < cur_top_line;
3617 line++)
3618 sparms->start.loc.offset += scroll_handle_column(line, -1);
3620 done = 1;
3621 ClearLine(1);
3622 break;
3624 /* else no reformatting necessary, fall thru to repaint */
3627 /*-------------- refresh -------------*/
3628 case MC_REPAINT :
3629 num_display_lines = SCROLL_LINES(ps_global);
3630 mark_status_dirty();
3631 mark_keymenu_dirty();
3632 mark_titlebar_dirty();
3633 ps_global->mangled_screen = 1;
3634 force = 1;
3635 break;
3638 /*------- no op timeout to check for new mail ------*/
3639 case MC_NONE :
3640 break;
3643 /*------- Forward displayed text ------*/
3644 case MC_FWDTEXT :
3645 forward_text(ps_global, sparms->text.text, sparms->text.src);
3646 break;
3649 /*----------- Save the displayed text ------------*/
3650 case MC_SAVETEXT :
3651 (void)simple_export(ps_global, sparms->text.text,
3652 sparms->text.src, "text", NULL);
3653 break;
3656 /*----------- Exit this screen ------------*/
3657 case MC_EXIT :
3658 done = 1;
3659 break;
3662 /*----------- Pop back to the Main Menu ------------*/
3663 case MC_MAIN :
3664 ps_global->next_screen = main_menu_screen;
3665 done = 1;
3666 break;
3669 /*----------- Print ------------*/
3670 case MC_PRINTTXT :
3671 print_to_printer(sparms);
3672 break;
3675 /* ------- First handle on Line ------ */
3676 case MC_GOTOBOL :
3677 if(sparms->text.handles){
3678 next_handle = scroll_handle_boundary(sparms->text.handles,
3679 scroll_handle_prev_sel);
3681 break;
3683 /* fall thru as bogus */
3685 /* ------- Last handle on Line ------ */
3686 case MC_GOTOEOL :
3687 if(sparms->text.handles){
3688 next_handle = scroll_handle_boundary(sparms->text.handles,
3689 scroll_handle_next_sel);
3691 break;
3693 /* fall thru as bogus */
3695 /*------- BOGUS INPUT ------*/
3696 case MC_CHARRIGHT :
3697 case MC_CHARLEFT :
3698 case MC_UNKNOWN :
3699 if(sparms->bogus_input)
3700 done = (*sparms->bogus_input)(ch);
3701 else
3702 bogus_command(ch, F_ON(F_USE_FK,ps_global) ? "F1" : "?");
3704 break;
3707 case MC_UTF8:
3708 bogus_utf8_command(utf8str, F_ON(F_USE_FK, ps_global) ? "F1" : "?");
3709 break;
3712 /*------- Standard commands ------*/
3713 default:
3714 whereis_pos.row = 0;
3715 if(sparms->proc.tool)
3716 result = (*sparms->proc.tool)(cmd, ps_global->msgmap, sparms);
3717 else
3718 result = process_cmd(ps_global, ps_global->mail_stream,
3719 ps_global->msgmap, cmd, View, &force);
3721 dprint((7, "PROCESS_CMD return: %d\n", result));
3723 if(ps_global->next_screen != SCREEN_FUN_NULL || result == 1){
3724 done = 1;
3725 if(cmd == MC_FULLHDR){
3726 if(ps_global->full_header == 1){
3727 long line;
3730 * Figure out char offset of the char in the top left
3731 * corner of the display. Pass it back to the
3732 * fetcher/formatter and have it pass the offset
3733 * back to us...
3735 sparms->start.on = Offset;
3736 for(sparms->start.loc.offset = line = 0L;
3737 line < cur_top_line;
3738 line++)
3739 sparms->start.loc.offset +=
3740 scroll_handle_column(line, -1);
3742 else
3743 sparms->start.on = 0;
3745 switch(km->which){
3746 case 0:
3747 sparms->keys.what = FirstMenu;
3748 break;
3749 case 1:
3750 sparms->keys.what = SecondMenu;
3751 break;
3752 case 2:
3753 sparms->keys.what = ThirdMenu;
3754 break;
3755 case 3:
3756 sparms->keys.what = FourthMenu;
3757 break;
3761 else if(!scroll_state(SS_CUR)){
3762 num_display_lines = SCROLL_LINES(ps_global);
3763 ps_global->mangled_screen = 1;
3766 break;
3768 } /* End of switch() */
3770 /* Need to frame some handles? */
3771 if(sparms->text.handles
3772 && ((!next_handle
3773 && handle_on_page(sparms->text.handles, cur_top_line,
3774 cur_top_line + num_display_lines))
3775 || (next_handle
3776 && handle_on_page(next_handle, cur_top_line,
3777 cur_top_line + num_display_lines))))
3778 next_handle = scroll_handle_in_frame(cur_top_line);
3780 } /* End of while() -- loop executing commands */
3782 ps_global->redrawer = NULL; /* next statement makes this invalid! */
3783 zero_scroll_text(); /* very important to zero out on return!!! */
3784 scroll_state(SS_FREE);
3785 if(sparms->bar.color)
3786 free_color_pair(&sparms->bar.color);
3788 #ifdef _WINDOWS
3789 scroll_setrange(0L, 0L);
3790 #endif
3791 return(cmd);
3795 /*----------------------------------------------------------------------
3796 Print text on paper
3798 Args: text -- The text to print out
3799 source -- What type of source text is
3800 message -- Message for open_printer()
3801 Handling of error conditions is very poor.
3803 ----*/
3804 static int
3805 print_to_printer(SCROLL_S *sparms)
3807 char message[64];
3809 snprintf(message, sizeof(message), "%s", STYLE_NAME(sparms));
3810 message[sizeof(message)-1] = '\0';
3812 if(open_printer(message) != 0)
3813 return(-1);
3815 switch(sparms->text.src){
3816 case CharStar :
3817 if(sparms->text.text != (char *)NULL)
3818 print_text((char *)sparms->text.text);
3820 break;
3822 case CharStarStar :
3823 if(sparms->text.text != (char **)NULL){
3824 register char **t;
3826 for(t = sparms->text.text; *t != NULL; t++){
3827 print_text(*t);
3828 print_text(NEWLINE);
3832 break;
3834 case FileStar :
3835 if(sparms->text.text != (FILE *)NULL) {
3836 size_t n;
3837 int i;
3839 fseek((FILE *)sparms->text.text, 0L, 0);
3840 n = SIZEOF_20KBUF - 1;
3841 while((i = fread((void *)tmp_20k_buf, sizeof(char),
3842 n, (FILE *)sparms->text.text)) != 0) {
3843 tmp_20k_buf[i] = '\0';
3844 print_text(tmp_20k_buf);
3848 default :
3849 break;
3852 close_printer();
3853 return(0);
3857 /*----------------------------------------------------------------------
3858 Search text being viewed (help or message)
3860 Args: q_line -- The screen line to prompt for search string on
3861 start_line -- Line number in text to begin search on
3862 start_index -- Where to begin search at in first line of text
3863 cursor_pos -- position of cursor is returned to caller here
3864 (Actually, this isn't really the position of the
3865 cursor because we don't know where we are on the
3866 screen. So row is set to the line number and col
3867 is set to the right column.)
3868 offset_in_line -- Offset where match was found.
3870 Result: returns line number string was found on
3871 -1 for cancel
3872 -2 if not found
3873 -3 if only match is at start_index - 1
3874 -4 if search to first line
3875 -5 if search to last line
3876 ---*/
3878 search_text(int q_line, long int start_line, int start_index, char **report,
3879 Pos *cursor_pos, int *offset_in_line)
3881 char prompt[MAX_SEARCH+50], nsearch_string[MAX_SEARCH+1], *p;
3882 HelpType help;
3883 int rc, flags;
3884 static HISTORY_S *history = NULL;
3885 char search_string[MAX_SEARCH+1];
3886 static ESCKEY_S word_search_key[] = { { 0, 0, "", "" },
3887 {ctrl('Y'), 10, "^Y", N_("First Line")},
3888 {ctrl('V'), 11, "^V", N_("Last Line")},
3889 {KEY_UP, 30, "", ""},
3890 {KEY_DOWN, 31, "", ""},
3891 {-1, 0, NULL, NULL}
3893 #define KU_ST (3) /* index of KEY_UP */
3895 init_hist(&history, HISTSIZE);
3898 * Put the last one used in the default search_string,
3899 * not in nsearch_string.
3901 search_string[0] = '\0';
3902 if((p = get_prev_hist(history, "", 0, NULL)) != NULL){
3903 strncpy(search_string, p, sizeof(search_string));
3904 search_string[sizeof(search_string)-1] = '\0';
3907 snprintf(prompt, sizeof(prompt), _("Word to search for [%s] : "), search_string);
3908 help = NO_HELP;
3909 nsearch_string[0] = '\0';
3911 while(1) {
3912 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE | OE_KEEP_TRAILING_SPACE;
3915 * 2 is really 1 because there will be one real entry and
3916 * one entry of "" because of the get_prev_hist above.
3918 if(items_in_hist(history) > 2){
3919 word_search_key[KU_ST].name = HISTORY_UP_KEYNAME;
3920 word_search_key[KU_ST].label = HISTORY_KEYLABEL;
3921 word_search_key[KU_ST+1].name = HISTORY_DOWN_KEYNAME;
3922 word_search_key[KU_ST+1].label = HISTORY_KEYLABEL;
3924 else{
3925 word_search_key[KU_ST].name = "";
3926 word_search_key[KU_ST].label = "";
3927 word_search_key[KU_ST+1].name = "";
3928 word_search_key[KU_ST+1].label = "";
3931 rc = optionally_enter(nsearch_string, q_line, 0, sizeof(nsearch_string),
3932 prompt, word_search_key, help, &flags);
3934 if(rc == 3) {
3935 help = help == NO_HELP ? h_oe_searchview : NO_HELP;
3936 continue;
3938 else if(rc == 10){
3939 if(report)
3940 *report = _("Searched to First Line.");
3942 return(-4);
3944 else if(rc == 11){
3945 if(report)
3946 *report = _("Searched to Last Line.");
3948 return(-5);
3950 else if(rc == 30){
3951 if((p = get_prev_hist(history, nsearch_string, 0, NULL)) != NULL){
3952 strncpy(nsearch_string, p, sizeof(nsearch_string));
3953 nsearch_string[sizeof(nsearch_string)-1] = '\0';
3955 else
3956 Writechar(BELL, 0);
3958 continue;
3960 else if(rc == 31){
3961 if((p = get_next_hist(history, nsearch_string, 0, NULL)) != NULL){
3962 strncpy(nsearch_string, p, sizeof(nsearch_string));
3963 nsearch_string[sizeof(nsearch_string)-1] = '\0';
3965 else
3966 Writechar(BELL, 0);
3968 continue;
3971 if(rc != 4){ /* 4 is redraw */
3972 save_hist(history, nsearch_string, 0, NULL);
3973 break;
3977 if(rc == 1 || (search_string[0] == '\0' && nsearch_string[0] == '\0'))
3978 return(-1);
3980 if(nsearch_string[0] != '\0'){
3981 strncpy(search_string, nsearch_string, sizeof(search_string)-1);
3982 search_string[sizeof(search_string)-1] = '\0';
3985 rc = search_scroll_text(start_line, start_index, search_string, cursor_pos,
3986 offset_in_line);
3987 return(rc);
3991 /*----------------------------------------------------------------------
3992 Update the scroll tool's titlebar
3994 Args: cur_top_line --
3995 redraw -- flag to force updating
3997 ----*/
3998 void
3999 update_scroll_titlebar(long int cur_top_line, int redraw)
4001 SCRLCTRL_S *st = scroll_state(SS_CUR);
4002 int num_display_lines = SCROLL_LINES(ps_global);
4003 long new_line = (cur_top_line + num_display_lines > st->num_lines)
4004 ? st->num_lines
4005 : cur_top_line + num_display_lines;
4006 long raw_msgno;
4007 COLOR_PAIR *returned_color = NULL;
4008 COLOR_PAIR *titlecolor = NULL;
4009 int colormatch;
4010 SEARCHSET *ss = NULL;
4012 if(st->parms->use_indexline_color
4013 && ps_global->titlebar_color_style != TBAR_COLOR_DEFAULT){
4014 raw_msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
4015 if(raw_msgno > 0L && ps_global->mail_stream
4016 && raw_msgno <= ps_global->mail_stream->nmsgs){
4017 ss = mail_newsearchset();
4018 ss->first = ss->last = (unsigned long) raw_msgno;
4021 if(ss){
4022 PAT_STATE *pstate = NULL;
4024 colormatch = get_index_line_color(ps_global->mail_stream,
4025 ss, &pstate, &returned_color);
4026 mail_free_searchset(&ss);
4029 * This is a bit tricky. If there is a colormatch but returned_color
4030 * is NULL, that means that the user explicitly wanted the
4031 * Normal color used in this index line, so that is what we
4032 * use. If no colormatch then we will use the TITLE color
4033 * instead of Normal.
4035 if(colormatch){
4036 if(returned_color)
4037 titlecolor = returned_color;
4038 else
4039 titlecolor = new_color_pair(ps_global->VAR_NORM_FORE_COLOR,
4040 ps_global->VAR_NORM_BACK_COLOR);
4043 if(titlecolor
4044 && ps_global->titlebar_color_style == TBAR_COLOR_REV_INDEXLINE){
4045 char cbuf[MAXCOLORLEN+1];
4047 strncpy(cbuf, titlecolor->fg, MAXCOLORLEN);
4048 strncpy(titlecolor->fg, titlecolor->bg, MAXCOLORLEN);
4049 strncpy(titlecolor->bg, cbuf, MAXCOLORLEN);
4053 /* Did the color change? */
4054 if((!titlecolor && st->parms->bar.color)
4056 (titlecolor && !st->parms->bar.color)
4058 (titlecolor && st->parms->bar.color
4059 && (strcmp(titlecolor->fg, st->parms->bar.color->fg)
4060 || strcmp(titlecolor->bg, st->parms->bar.color->bg)))){
4062 redraw++;
4063 if(st->parms->bar.color)
4064 free_color_pair(&st->parms->bar.color);
4066 st->parms->bar.color = titlecolor;
4067 titlecolor = NULL;
4070 if(titlecolor)
4071 free_color_pair(&titlecolor);
4075 if(redraw){
4076 set_titlebar(st->parms->bar.title, ps_global->mail_stream,
4077 ps_global->context_current, ps_global->cur_folder,
4078 ps_global->msgmap, 1, st->parms->bar.style,
4079 new_line, st->num_lines, st->parms->bar.color);
4080 ps_global->mangled_header = 0;
4082 else if(st->parms->bar.style == TextPercent)
4083 update_titlebar_lpercent(new_line);
4084 else
4085 update_titlebar_percent(new_line);
4089 /*----------------------------------------------------------------------
4090 manager of global (to this module, anyway) scroll state structures
4093 ----*/
4094 SCRLCTRL_S *
4095 scroll_state(int func)
4097 struct scrollstack {
4098 SCRLCTRL_S s;
4099 struct scrollstack *prev;
4100 } *s;
4101 static struct scrollstack *stack = NULL;
4103 switch(func){
4104 case SS_CUR: /* no op */
4105 break;
4106 case SS_NEW:
4107 s = (struct scrollstack *)fs_get(sizeof(struct scrollstack));
4108 memset((void *)s, 0, sizeof(struct scrollstack));
4109 s->prev = stack;
4110 stack = s;
4111 break;
4112 case SS_FREE:
4113 if(stack){
4114 s = stack->prev;
4115 fs_give((void **)&stack);
4116 stack = s;
4118 break;
4119 default: /* BUG: should complain */
4120 break;
4123 return(stack ? &stack->s : NULL);
4127 /*----------------------------------------------------------------------
4128 Save all the data for scrolling text and paint the screen
4131 ----*/
4132 void
4133 set_scroll_text(SCROLL_S *sparms, long int current_line, SCRLCTRL_S *st)
4135 /* save all the stuff for possible asynchronous redraws */
4136 st->parms = sparms;
4137 st->top_text_line = current_line;
4138 st->screen.start_line = SCROLL_LINES_ABOVE(ps_global);
4139 st->screen.other_lines = SCROLL_LINES_ABOVE(ps_global)
4140 + SCROLL_LINES_BELOW(ps_global);
4141 st->screen.width = -1; /* Force text formatting calculation */
4145 /*----------------------------------------------------------------------
4146 Redraw the text on the screen, possibly reformatting if necessary
4148 Args None
4150 ----*/
4151 void
4152 redraw_scroll_text(void)
4154 int i, offset;
4155 SCRLCTRL_S *st = scroll_state(SS_CUR);
4157 format_scroll_text();
4159 offset = (st->parms->text.src == FileStar) ? 0 : st->top_text_line;
4161 #ifdef _WINDOWS
4162 mswin_beginupdate();
4163 #endif
4164 /*---- Actually display the text on the screen ------*/
4165 for(i = 0; i < st->screen.length; i++){
4166 ClearLine(i + st->screen.start_line);
4167 if((offset + i) < st->num_lines)
4168 PutLine0n8b(i + st->screen.start_line, 0, st->text_lines[offset + i],
4169 st->line_lengths[offset + i], st->parms->text.handles);
4173 fflush(stdout);
4174 #ifdef _WINDOWS
4175 mswin_endupdate();
4176 #endif
4180 /*----------------------------------------------------------------------
4181 Free memory used as scrolling buffers for text on disk. Also mark
4182 text_lines as available
4183 ----*/
4184 void
4185 zero_scroll_text(void)
4187 SCRLCTRL_S *st = scroll_state(SS_CUR);
4188 register int i;
4190 for(i = 0; i < st->lines_allocated; i++)
4191 if(st->parms->text.src == FileStar && st->text_lines[i])
4192 fs_give((void **)&st->text_lines[i]);
4193 else
4194 st->text_lines[i] = NULL;
4196 if(st->parms->text.src == FileStar && st->findex != NULL){
4197 fclose(st->findex);
4198 st->findex = NULL;
4199 if(st->fname){
4200 our_unlink(st->fname);
4201 fs_give((void **)&st->fname);
4205 if(st->text_lines)
4206 fs_give((void **)&st->text_lines);
4208 if(st->line_lengths)
4209 fs_give((void **) &st->line_lengths);
4213 /*----------------------------------------------------------------------
4215 Always format at least 20 chars wide. Wrapping lines would be crazy for
4216 screen widths of 1-20 characters
4217 ----*/
4218 void
4219 format_scroll_text(void)
4221 int i;
4222 char *p, **pp;
4223 SCRLCTRL_S *st = scroll_state(SS_CUR);
4224 register short *ll;
4225 register char **tl, **tl_end;
4227 if(!st || (st->screen.width == (i = ps_global->ttyo->screen_cols)
4228 && st->screen.length == PGSIZE(st)))
4229 return;
4231 st->screen.width = MAX(20, i);
4232 st->screen.length = PGSIZE(st);
4234 if(st->lines_allocated == 0) {
4235 st->lines_allocated = TYPICAL_BIG_MESSAGE_LINES;
4236 st->text_lines = (char **)fs_get(st->lines_allocated *sizeof(char *));
4237 memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
4238 st->line_lengths = (short *)fs_get(st->lines_allocated *sizeof(short));
4241 tl = st->text_lines;
4242 ll = st->line_lengths;
4243 tl_end = &st->text_lines[st->lines_allocated];
4245 if(st->parms->text.src == CharStarStar) {
4246 /*---- original text is already list of lines -----*/
4247 /* The text could be wrapped nicely for narrow screens; for now
4248 it will get truncated as it is displayed */
4249 for(pp = (char **)st->parms->text.text; *pp != NULL;) {
4250 *tl++ = *pp++;
4251 *ll++ = st->screen.width;
4252 if(tl >= tl_end) {
4253 i = tl - st->text_lines;
4254 st->lines_allocated *= 2;
4255 fs_resize((void **)&st->text_lines,
4256 st->lines_allocated * sizeof(char *));
4257 fs_resize((void **)&st->line_lengths,
4258 st->lines_allocated*sizeof(short));
4259 tl = &st->text_lines[i];
4260 ll = &st->line_lengths[i];
4261 tl_end = &st->text_lines[st->lines_allocated];
4265 st->num_lines = tl - st->text_lines;
4267 else if (st->parms->text.src == CharStar) {
4268 /*------ Format the plain text ------*/
4269 for(p = (char *)st->parms->text.text; *p; ) {
4270 *tl = p;
4272 for(; *p && !(*p == RETURN || *p == LINE_FEED); p++)
4275 *ll = p - *tl;
4276 ll++; tl++;
4277 if(tl >= tl_end) {
4278 i = tl - st->text_lines;
4279 st->lines_allocated *= 2;
4280 fs_resize((void **)&st->text_lines,
4281 st->lines_allocated * sizeof(char *));
4282 fs_resize((void **)&st->line_lengths,
4283 st->lines_allocated*sizeof(short));
4284 tl = &st->text_lines[i];
4285 ll = &st->line_lengths[i];
4286 tl_end = &st->text_lines[st->lines_allocated];
4289 if(*p == '\r' && *(p+1) == '\n')
4290 p += 2;
4291 else if(*p == '\n' || *p == '\r')
4292 p++;
4295 st->num_lines = tl - st->text_lines;
4297 else {
4298 /*------ Display text is in a file --------*/
4301 * This is pretty much only useful under DOS where we can't fit
4302 * all of big messages in core at once. This scheme makes
4303 * some simplifying assumptions:
4304 * 1. Lines are on disk just the way we'll display them. That
4305 * is, line breaks and such are left to the function that
4306 * writes the disk file to catch and fix.
4307 * 2. We get away with this mainly because the DOS display isn't
4308 * going to be resized out from under us.
4310 * The idea is to use the already alloc'd array of char * as a
4311 * buffer for sections of what's on disk. We'll set up the first
4312 * few lines here, and read new ones in as needed in
4313 * scroll_scroll_text().
4315 * but first, make sure there are enough buffer lines allocated
4316 * to serve as a place to hold lines from the file.
4318 * Actually, this is also used under windows so the display will
4319 * be resized out from under us. So I changed the following
4320 * to always
4321 * 1. free old text_lines, which may have been allocated
4322 * for a narrow screen.
4323 * 2. insure we have enough text_lines
4324 * 3. reallocate all text_lines that are needed.
4325 * (tom unger 10/26/94)
4328 /* free old text lines, which may be too short. */
4329 for(i = 0; i < st->lines_allocated; i++)
4330 if(st->text_lines[i]) /* clear alloc'd lines */
4331 fs_give((void **)&st->text_lines[i]);
4333 /* Insure we have enough text lines. */
4334 if(st->lines_allocated < (2 * PGSIZE(st)) + 1){
4335 st->lines_allocated = (2 * PGSIZE(st)) + 1; /* resize */
4337 fs_resize((void **)&st->text_lines,
4338 st->lines_allocated * sizeof(char *));
4339 memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
4340 fs_resize((void **)&st->line_lengths,
4341 st->lines_allocated*sizeof(short));
4344 /* reallocate all text lines that are needed. */
4345 for(i = 0; i <= PGSIZE(st); i++)
4346 if(st->text_lines[i] == NULL)
4347 st->text_lines[i] = (char *)fs_get((st->screen.width + 1)
4348 * sizeof(char));
4350 tl = &st->text_lines[i];
4352 st->num_lines = make_file_index();
4354 ScrollFile(st->top_text_line); /* then load them up */
4358 * Efficiency hack. If there are handles, fill in their
4359 * line number field for later...
4361 if(st->parms->text.handles){
4362 long line;
4363 int i, col, n, key;
4364 HANDLE_S *h;
4366 for(line = 0; line < st->num_lines; line++)
4367 for(i = 0, col = 0; i < st->line_lengths[line];)
4368 switch(st->text_lines[line][i]){
4369 case TAG_EMBED:
4370 i++;
4371 switch((i < st->line_lengths[line]) ? st->text_lines[line][i]
4372 : 0){
4373 case TAG_HANDLE:
4374 for(key = 0, n = st->text_lines[line][++i]; n > 0; n--)
4375 key = (key * 10) + (st->text_lines[line][++i] - '0');
4377 i++;
4378 for(h = st->parms->text.handles; h; h = h->next)
4379 if(h->key == key){
4380 scroll_handle_set_loc(&h->loc, line, col);
4381 break;
4384 if(!h) /* anything behind us? */
4385 for(h = st->parms->text.handles->prev; h; h = h->prev)
4386 if(h->key == key){
4387 scroll_handle_set_loc(&h->loc, line, col);
4388 break;
4391 break;
4393 case TAG_FGCOLOR :
4394 case TAG_BGCOLOR :
4395 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
4396 break;
4398 case TAG_INVON:
4399 case TAG_INVOFF:
4400 case TAG_BOLDON:
4401 case TAG_BOLDOFF:
4402 case TAG_ULINEON:
4403 case TAG_ULINEOFF:
4404 i++;
4405 break;
4407 default: /* literal embed char */
4408 break;
4411 break;
4413 case TAB:
4414 i++;
4415 while(((++col) & 0x07) != 0) /* add tab's spaces */
4418 break;
4420 default:
4421 col += width_at_this_position((unsigned char*) &st->text_lines[line][i],
4422 st->line_lengths[line] - i);
4423 i++; /* character count */
4424 break;
4428 #ifdef _WINDOWS
4429 scroll_setrange (st->screen.length, st->num_lines);
4430 #endif
4432 *tl = NULL;
4437 * ScrollFile - scroll text into the st struct file making sure 'line'
4438 * of the file is the one first in the text_lines buffer.
4440 * NOTE: talk about massive potential for tuning...
4441 * Goes without saying this is still under construction
4443 void
4444 ScrollFile(long int line)
4446 SCRLCTRL_S *st = scroll_state(SS_CUR);
4447 SCRLFILE_S sf;
4448 register int i;
4450 if(line <= 0){ /* reset and load first couple of pages */
4451 fseek((FILE *) st->parms->text.text, 0L, 0);
4452 line = 0L;
4455 if(!st->text_lines)
4456 return;
4458 for(i = 0; i < PGSIZE(st); i++){
4459 /*** do stuff to get the file pointer into the right place ***/
4461 * BOGUS: this is painfully crude right now, but I just want to get
4462 * it going.
4464 * possibly in the near future, an array of indexes into the
4465 * file that are the offset for the beginning of each line will
4466 * speed things up. Of course, this
4467 * will have limits, so maybe a disk file that is an array
4468 * of indexes is the answer.
4470 if(fseek(st->findex, (size_t)(line++) * sizeof(SCRLFILE_S), 0) < 0
4471 || fread(&sf, sizeof(SCRLFILE_S), (size_t)1, st->findex) != 1
4472 || fseek((FILE *) st->parms->text.text, sf.offset, 0) < 0
4473 || !st->text_lines[i]
4474 || (sf.len && !fgets(st->text_lines[i], sf.len + 1,
4475 (FILE *) st->parms->text.text)))
4476 break;
4478 st->line_lengths[i] = sf.len;
4481 for(; i < PGSIZE(st); i++)
4482 if(st->text_lines[i]){ /* blank out any unused lines */
4483 *st->text_lines[i] = '\0';
4484 st->line_lengths[i] = 0;
4490 * make_file_index - do a single pass over the file containing the text
4491 * to display, recording line lengths and offsets.
4492 * NOTE: This is never really to be used on a real OS with virtual
4493 * memory. This is the whole reason st->findex exists. Don't
4494 * want to waste precious memory on a stupid array that could
4495 * be very large.
4497 long
4498 make_file_index(void)
4500 SCRLCTRL_S *st = scroll_state(SS_CUR);
4501 SCRLFILE_S sf;
4502 long l = 0L;
4503 int state = 0;
4505 if(!st->findex){
4506 if(!st->fname)
4507 st->fname = temp_nam(NULL, "pi");
4509 if(!st->fname || (st->findex = our_fopen(st->fname,"w+b")) == NULL){
4510 if(st->fname){
4511 our_unlink(st->fname);
4512 fs_give((void **)&st->fname);
4515 return(0);
4518 else
4519 fseek(st->findex, 0L, 0);
4521 fseek((FILE *)st->parms->text.text, 0L, 0);
4523 while(1){
4524 sf.len = st->screen.width + 1;
4525 if(scroll_file_line((FILE *) st->parms->text.text,
4526 tmp_20k_buf, &sf, &state)){
4527 fwrite((void *) &sf, sizeof(SCRLFILE_S), (size_t)1, st->findex);
4528 l++;
4530 else
4531 break;
4534 fseek((FILE *)st->parms->text.text, 0L, 0);
4536 return(l);
4540 /*----------------------------------------------------------------------
4541 Get the next line to scroll from the given file
4543 ----*/
4544 char *
4545 scroll_file_line(FILE *fp, char *buf, SCRLFILE_S *sfp, int *wrapt)
4547 register char *s = NULL;
4549 while(1){
4550 if(!s){
4551 sfp->offset = ftell(fp);
4552 if(!(s = fgets(buf, sfp->len, fp)))
4553 return(NULL); /* can't grab a line? */
4556 if(!*s){
4557 *wrapt = 1; /* remember; that we wrapped */
4558 break;
4560 else if(*s == NEWLINE[0] && (!NEWLINE[1] || *(s+1) == NEWLINE[1])){
4561 int empty = (*wrapt && s == buf);
4563 *wrapt = 0; /* turn off wrapped state */
4564 if(empty)
4565 s = NULL; /* get a new line */
4566 else
4567 break; /* done! */
4569 else
4570 s++;
4573 sfp->len = s - buf;
4574 return(buf);
4578 /*----------------------------------------------------------------------
4579 Scroll the text on the screen
4581 Args: new_top_line -- The line to be displayed on top of the screen
4582 redraw -- Flag to force a redraw even if nothing changed
4584 Returns: resulting top line
4585 Note: the returned line number may be less than new_top_line if
4586 reformatting caused the total line count to change.
4588 ----*/
4589 long
4590 scroll_scroll_text(long int new_top_line, HANDLE_S *handle, int redraw)
4592 SCRLCTRL_S *st = scroll_state(SS_CUR);
4593 int num_display_lines, l, top;
4594 POSLIST_S *lp, *lp2;
4596 /* When this is true, we're still on the same page of the display. */
4597 if(st->top_text_line == new_top_line && !redraw){
4598 /* handle changed, so hilite the new handle and unhilite the old */
4599 if(handle && handle != st->parms->text.handles){
4600 top = st->screen.start_line - new_top_line;
4601 /* hilite the new one */
4602 if(!scroll_handle_obscured(handle))
4603 for(lp = handle->loc; lp; lp = lp->next)
4604 if((l = lp->where.row) >= st->top_text_line
4605 && l < st->top_text_line + st->screen.length){
4606 if(st->parms->text.src == FileStar)
4607 l -= new_top_line;
4609 if(F_ON(F_ENABLE_DEL_WHEN_WRITING, ps_global))
4610 ClearLine(top + lp->where.row);
4611 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4612 st->line_lengths[l], handle);
4615 /* unhilite the old one */
4616 if(!scroll_handle_obscured(st->parms->text.handles))
4617 for(lp = st->parms->text.handles->loc; lp; lp = lp->next)
4618 if((l = lp->where.row) >= st->top_text_line
4619 && l < st->top_text_line + st->screen.length){
4620 for(lp2 = handle->loc; lp2; lp2 = lp2->next)
4621 if(l == lp2->where.row)
4622 break;
4624 if(!lp2){
4625 if(st->parms->text.src == FileStar)
4626 l -= new_top_line;
4628 if(F_ON(F_ENABLE_DEL_WHEN_WRITING, ps_global))
4629 ClearLine(top + lp->where.row);
4630 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4631 st->line_lengths[l], handle);
4635 st->parms->text.handles = handle; /* update current */
4638 return(new_top_line);
4641 num_display_lines = PGSIZE(st);
4643 format_scroll_text();
4645 if(st->top_text_line >= st->num_lines) /* don't pop line count */
4646 new_top_line = st->top_text_line = MAX(st->num_lines - 1, 0);
4648 if(st->parms->text.src == FileStar)
4649 ScrollFile(new_top_line); /* set up new st->text_lines */
4651 #ifdef _WINDOWS
4652 scroll_setrange (st->screen.length, st->num_lines);
4653 scroll_setpos (new_top_line);
4654 #endif
4656 /* ---
4657 Check out the scrolling situation. If we want to scroll, but BeginScroll
4658 says we can't then repaint, + 10 is so we repaint most of the time.
4659 ----*/
4660 if(redraw ||
4661 (st->top_text_line - new_top_line + 10 >= num_display_lines ||
4662 new_top_line - st->top_text_line + 10 >= num_display_lines) ||
4663 BeginScroll(st->screen.start_line,
4664 st->screen.start_line + num_display_lines - 1) != 0) {
4665 /* Too much text to scroll, or can't scroll -- just repaint */
4667 if(handle)
4668 st->parms->text.handles = handle;
4670 st->top_text_line = new_top_line;
4671 redraw_scroll_text();
4673 else{
4675 * We're going to scroll the screen, but first we have to make sure
4676 * the old hilited handles are unhilited if they are going to remain
4677 * on the screen.
4679 top = st->screen.start_line - st->top_text_line;
4680 if(handle && handle != st->parms->text.handles
4681 && st->parms->text.handles
4682 && !scroll_handle_obscured(st->parms->text.handles))
4683 for(lp = st->parms->text.handles->loc; lp; lp = lp->next)
4684 if((l = lp->where.row) >= MAX(st->top_text_line,new_top_line)
4685 && l < MIN(st->top_text_line,new_top_line) + st->screen.length){
4686 if(st->parms->text.src == FileStar)
4687 l -= new_top_line;
4689 if(F_ON(F_ENABLE_DEL_WHEN_WRITING, ps_global))
4690 ClearLine(top + lp->where.row);
4691 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4692 st->line_lengths[l], handle);
4695 if(new_top_line > st->top_text_line){
4696 /*------ scroll down ------*/
4697 while(new_top_line > st->top_text_line) {
4698 ScrollRegion(1);
4700 l = (st->parms->text.src == FileStar)
4701 ? num_display_lines - (new_top_line - st->top_text_line)
4702 : st->top_text_line + num_display_lines;
4704 if(l < st->num_lines){
4705 if(F_ON(F_ENABLE_DEL_WHEN_WRITING, ps_global))
4706 ClearLine(st->screen.start_line + num_display_lines - 1);
4707 PutLine0n8b(st->screen.start_line + num_display_lines - 1,
4708 0, st->text_lines[l], st->line_lengths[l],
4709 handle ? handle : st->parms->text.handles);
4711 * We clear to the end of line in the right background
4712 * color. If the line was exactly the width of the screen
4713 * then PutLine0n8b will have left _col and _row moved to
4714 * the start of the next row. We don't need or want to clear
4715 * that next row.
4717 if(pico_usingcolor()
4718 && (st->line_lengths[l] < ps_global->ttyo->screen_cols
4719 || visible_linelen(l) < ps_global->ttyo->screen_cols))
4720 CleartoEOLN();
4723 st->top_text_line++;
4726 else{
4727 /*------ scroll up -----*/
4728 while(new_top_line < st->top_text_line) {
4729 ScrollRegion(-1);
4731 st->top_text_line--;
4732 l = (st->parms->text.src == FileStar)
4733 ? st->top_text_line - new_top_line
4734 : st->top_text_line;
4735 if(F_ON(F_ENABLE_DEL_WHEN_WRITING, ps_global))
4736 ClearLine(st->screen.start_line);
4737 PutLine0n8b(st->screen.start_line, 0, st->text_lines[l],
4738 st->line_lengths[l],
4739 handle ? handle : st->parms->text.handles);
4741 * We clear to the end of line in the right background
4742 * color. If the line was exactly the width of the screen
4743 * then PutLine0n8b will have left _col and _row moved to
4744 * the start of the next row. We don't need or want to clear
4745 * that next row.
4747 if(pico_usingcolor()
4748 && (st->line_lengths[l] < ps_global->ttyo->screen_cols
4749 || visible_linelen(l) < ps_global->ttyo->screen_cols))
4750 CleartoEOLN();
4754 EndScroll();
4756 if(handle && handle != st->parms->text.handles){
4757 POSLIST_S *lp;
4759 for(lp = handle->loc; lp; lp = lp->next)
4760 if(lp->where.row >= st->top_text_line
4761 && lp->where.row < st->top_text_line + st->screen.length){
4762 if(F_ON(F_ENABLE_DEL_WHEN_WRITING, ps_global))
4763 ClearLine(st->screen.start_line + (lp->where.row - st->top_text_line));
4764 PutLine0n8b(st->screen.start_line
4765 + (lp->where.row - st->top_text_line),
4766 0, st->text_lines[lp->where.row],
4767 st->line_lengths[lp->where.row],
4768 handle);
4772 st->parms->text.handles = handle;
4775 fflush(stdout);
4778 return(new_top_line);
4782 /*---------------------------------------------------------------------
4783 Edit individual char in text so that the entire text doesn't need
4784 to be completely reformatted.
4786 Returns 0 if there were no errors, 1 if we would like the entire
4787 text to be reformatted.
4788 ----*/
4790 ng_scroll_edit(CONTEXT_S *context, int index)
4792 SCRLCTRL_S *st = scroll_state(SS_CUR);
4793 char *ngp, tmp[MAILTMPLEN+10];
4794 int len;
4795 FOLDER_S *f;
4797 if (!(f = folder_entry(index, FOLDERS(context))))
4798 return 1;
4799 if(f->subscribed)
4800 return 0; /* nothing in scroll needs to be changed */
4801 tmp[0] = TAG_HANDLE;
4802 snprintf(tmp+2, sizeof(tmp)-2, "%d", st->parms->text.handles->key);
4803 tmp[sizeof(tmp)-1] = '\0';
4804 tmp[1] = len = strlen(tmp+2);
4805 snprintf(tmp+len+2, sizeof(tmp)-(len+2), "%s ", f->selected ? "[ ]" : "[X]");
4806 tmp[sizeof(tmp)-1] = '\0';
4807 snprintf(tmp+len+6, sizeof(tmp)-(len+6), "%.*s", MAILTMPLEN, f->name);
4808 tmp[sizeof(tmp)-1] = '\0';
4810 ngp = *(st->text_lines);
4812 ngp = strstr(ngp, tmp);
4814 if(!ngp) return 1;
4815 ngp += 3+len;
4817 /* assumption that text is of form "[ ] xxx.xxx" */
4819 if(ngp){
4820 if(*ngp == 'X'){
4821 *ngp = ' ';
4822 return 0;
4824 else if (*ngp == ' '){
4825 *ngp = 'X';
4826 return 0;
4829 return 1;
4833 /*---------------------------------------------------------------------
4834 Similar to ng_scroll_edit, but this is the more general case of
4835 selecting a folder, as opposed to selecting a newsgroup for
4836 subscription while in listmode.
4838 Returns 0 if there were no errors, 1 if we would like the entire
4839 text to be reformatted.
4840 ----*/
4842 folder_select_update(CONTEXT_S *context, int index)
4844 SCRLCTRL_S *st = scroll_state(SS_CUR);
4845 FOLDER_S *f;
4846 char *ngp, tmp[MAILTMPLEN+10];
4847 int len, total, fnum, num_sel = 0;
4849 if (!(f = folder_entry(index, FOLDERS(context))))
4850 return 1;
4851 ngp = *(st->text_lines);
4853 total = folder_total(FOLDERS(context));
4855 for (fnum = 0; num_sel < 2 && fnum < total; fnum++)
4856 if(folder_entry(fnum, FOLDERS(context))->selected)
4857 num_sel++;
4858 if(!num_sel || (f->selected && num_sel == 1))
4859 return 1; /* need to reformat the whole thing */
4861 tmp[0] = TAG_HANDLE;
4862 snprintf(tmp+2, sizeof(tmp)-2, "%d", st->parms->text.handles->key);
4863 tmp[sizeof(tmp)-1] = '\0';
4864 tmp[1] = len = strlen(tmp+2);
4866 ngp = strstr(ngp, tmp);
4867 if(!ngp) return 1;
4869 if(F_ON(F_SELECTED_SHOWN_BOLD, ps_global)){
4870 ngp += 2 + len;
4871 while(*ngp && ngp[0] != TAG_EMBED
4872 && ngp[1] != (f->selected ? TAG_BOLDOFF : TAG_BOLDON)
4873 && *ngp != *(f->name))
4874 ngp++;
4876 if (!(*ngp) || (*ngp == *(f->name)))
4877 return 1;
4878 else {
4879 ngp++;
4880 *ngp = (f->selected ? TAG_BOLDON : TAG_BOLDOFF);
4881 return 0;
4884 else{
4885 while(*ngp != ' ' && *ngp != *(f->name) && *ngp)
4886 ngp++;
4887 if(!(*ngp) || (*ngp == *(f->name)))
4888 return 1;
4889 else {
4890 ngp++;
4891 *ngp = f->selected ? 'X' : ' ';
4892 return 0;
4898 /*---------------------------------------------------------------------
4899 We gotta go through all of the formatted text and add "[ ] " in the right
4900 place. If we don't do this, we must completely reformat the whole text,
4901 which could take a very long time.
4903 Return 1 if we encountered some sort of error and we want to reformat the
4904 whole text, return 0 if everything went as planned.
4906 ASSUMPTION: for this to work, we assume that there are only total
4907 number of handles, numbered 1 through total.
4908 ----*/
4910 scroll_add_listmode(CONTEXT_S *context, int total)
4912 SCRLCTRL_S *st = scroll_state(SS_CUR);
4913 long i;
4914 char *ngp, *ngname, handle_str[MAILTMPLEN];
4915 HANDLE_S *h;
4918 ngp = *(st->text_lines);
4919 h = st->parms->text.handles;
4921 while(h && h->key != 1 && h->prev)
4922 h = h->prev;
4923 if (!h) return 1;
4924 handle_str[0] = TAG_EMBED;
4925 handle_str[1] = TAG_HANDLE;
4926 for(i = 1; i <= total && h; i++, h = h->next){
4927 snprintf(handle_str+3, sizeof(handle_str)-3, "%d", h->key);
4928 handle_str[sizeof(handle_str)-1] = '\0';
4929 handle_str[2] = strlen(handle_str+3);
4930 ngp = strstr(ngp, handle_str);
4931 if(!ngp){
4932 ngp = *(st->text_lines);
4933 if (!ngp)
4934 return 1;
4936 ngname = ngp + strlen(handle_str);
4937 while (strncmp(ngp, " ", 4) && !(*ngp == '\n')
4938 && !(ngp == *(st->text_lines)))
4939 ngp--;
4940 if (strncmp(ngp, " ", 4))
4941 return 1;
4942 while(ngp+4 != ngname && *ngp){
4943 ngp[0] = ngp[4];
4944 ngp++;
4947 if(folder_entry(h->h.f.index, FOLDERS(context))->subscribed){
4948 ngp[0] = 'S';
4949 ngp[1] = 'U';
4950 ngp[2] = 'B';
4952 else{
4953 ngp[0] = '[';
4954 ngp[1] = ' ';
4955 ngp[2] = ']';
4957 ngp[3] = ' ';
4960 return 0;
4965 /*----------------------------------------------------------------------
4966 Search the set scrolling text
4968 Args: start_line -- line to start searching on
4969 start_index -- column to start searching at in first line
4970 word -- string to search for
4971 cursor_pos -- position of cursor is returned to caller here
4972 (Actually, this isn't really the position of the
4973 cursor because we don't know where we are on the
4974 screen. So row is set to the line number and col
4975 is set to the right column.)
4976 offset_in_line -- Offset where match was found.
4978 Returns: the line the word was found on, or -2 if it wasn't found, or
4979 -3 if the only match is at column start_index - 1.
4981 ----*/
4983 search_scroll_text(long int start_line, int start_index, char *word,
4984 Pos *cursor_pos, int *offset_in_line)
4986 SCRLCTRL_S *st = scroll_state(SS_CUR);
4987 char *wh;
4988 long l, offset, dlines;
4989 #define SROW(N) ((N) - offset)
4990 #define SLINE(N) st->text_lines[SROW(N)]
4991 #define SLEN(N) st->line_lengths[SROW(N)]
4993 dlines = PGSIZE(st);
4994 offset = (st->parms->text.src == FileStar) ? st->top_text_line : 0;
4996 if(start_line < st->num_lines){
4997 /* search first line starting at position start_index in */
4998 if((wh = search_scroll_line(SLINE(start_line) + start_index,
4999 word,
5000 SLEN(start_line) - start_index,
5001 st->parms->text.handles != NULL)) != NULL){
5002 cursor_pos->row = start_line;
5003 cursor_pos->col = scroll_handle_column(SROW(start_line),
5004 *offset_in_line = wh - SLINE(start_line));
5005 return(start_line);
5008 if(st->parms->text.src == FileStar)
5009 offset++;
5011 for(l = start_line + 1; l < st->num_lines; l++) {
5012 if(st->parms->text.src == FileStar && l > offset + dlines)
5013 ScrollFile(offset += dlines);
5015 if((wh = search_scroll_line(SLINE(l), word, SLEN(l),
5016 st->parms->text.handles != NULL)) != NULL){
5017 cursor_pos->row = l;
5018 cursor_pos->col = scroll_handle_column(SROW(l),
5019 *offset_in_line = wh - SLINE(l));
5020 return(l);
5024 else
5025 start_line = st->num_lines;
5027 if(st->parms->text.src == FileStar) /* wrap offset */
5028 ScrollFile(offset = 0);
5030 for(l = 0; l < start_line; l++) {
5031 if(st->parms->text.src == FileStar && l > offset + dlines)
5032 ScrollFile(offset += dlines);
5034 if((wh = search_scroll_line(SLINE(l), word, SLEN(l),
5035 st->parms->text.handles != NULL)) != NULL){
5036 cursor_pos->row = l;
5037 cursor_pos->col = scroll_handle_column(SROW(l),
5038 *offset_in_line = wh - SLINE(l));
5039 return(l);
5043 /* search in current line */
5044 if(start_line < st->num_lines
5045 && (wh = search_scroll_line(SLINE(start_line), word,
5046 start_index + strlen(word) - 2,
5047 st->parms->text.handles != NULL)) != NULL){
5048 cursor_pos->row = start_line;
5049 cursor_pos->col = scroll_handle_column(SROW(start_line),
5050 *offset_in_line = wh - SLINE(start_line));
5052 return(start_line);
5055 /* see if the only match is a repeat */
5056 if(start_index > 0 && start_line < st->num_lines
5057 && (wh = search_scroll_line(
5058 SLINE(start_line) + start_index - 1,
5059 word, strlen(word),
5060 st->parms->text.handles != NULL)) != NULL){
5061 cursor_pos->row = start_line;
5062 cursor_pos->col = scroll_handle_column(SROW(start_line),
5063 *offset_in_line = wh - SLINE(start_line));
5064 return(-3);
5067 return(-2);
5071 /*----------------------------------------------------------------------
5072 Search one line of scroll text for given string
5074 Args: haystack -- The string to search in, the larger string
5075 needle -- The string to search for, the smaller string
5076 n -- The max number of chars in haystack to search
5078 Search for first occurrence of needle in the haystack, and return a pointer
5079 into the string haystack when it is found. The search is case independent.
5080 ----*/
5081 char *
5082 search_scroll_line(char *haystack, char *needle, int n, int handles)
5084 char *return_ptr = NULL, *found_it = NULL;
5085 char *haystack_copy, *p, *free_this = NULL, *end;
5086 char buf[1000];
5087 int state = 0, i = 0;
5089 if(n > 0 && haystack){
5090 if(n < sizeof(buf))
5091 haystack_copy = buf;
5092 else
5093 haystack_copy = free_this = (char *) fs_get((n+1) * sizeof(char));
5095 strncpy(haystack_copy, haystack, n);
5096 haystack_copy[n] = '\0';
5099 * We don't want to match text inside embedded tags.
5100 * Replace embedded octets with nulls and convert
5101 * uppercase ascii to lowercase. We should also do
5102 * some sort of canonicalization of UTF-8 but that
5103 * sounds daunting.
5105 for(i = n, p = haystack_copy; i-- > 0 && *p; p++){
5106 if(handles)
5107 switch(state){
5108 case 0 :
5109 if(*p == TAG_EMBED){
5110 *p = '\0';
5111 state = -1;
5112 continue;
5114 else{
5115 /* lower case just ascii chars */
5116 if(!(*p & 0x80) && isupper(*p))
5117 *p = tolower(*p);
5120 break;
5122 case -1 :
5123 state = (*p == TAG_HANDLE)
5124 ? -2
5125 : (*p == TAG_FGCOLOR || *p == TAG_BGCOLOR) ? RGBLEN : 0;
5126 *p = '\0';
5127 continue;
5129 case -2 :
5130 state = *p; /* length of handle's key */
5131 *p = '\0';
5132 continue;
5134 default :
5135 state--;
5136 *p = '\0';
5137 continue;
5142 * The haystack_copy string now looks like
5144 * "chars\0\0\0\0\0\0chars...\0\0\0chars... \0"
5146 * with that final \0 at haystack_copy[n].
5147 * Search each piece one at a time.
5150 end = haystack_copy + n;
5151 p = haystack_copy;
5153 while(p < end && !return_ptr){
5155 /* skip nulls */
5156 while(*p == '\0' && p < end)
5157 p++;
5159 if(*p != '\0')
5160 found_it = srchstr(p, needle);
5162 if(found_it){
5163 /* found it, make result relative to haystack */
5164 return_ptr = haystack + (found_it - haystack_copy);
5167 /* skip to next null */
5168 while(*p != '\0' && p < end)
5169 p++;
5172 if(free_this)
5173 fs_give((void **) &free_this);
5176 return(return_ptr);
5181 * Returns the number of columns taken up by the visible part of the line.
5182 * That is, account for handles and color changes and so forth.
5185 visible_linelen(int line)
5187 SCRLCTRL_S *st = scroll_state(SS_CUR);
5188 int i, n, len = 0;
5190 if(line < 0 || line >= st->num_lines)
5191 return(len);
5193 for(i = 0, len = 0; i < st->line_lengths[line];)
5194 switch(st->text_lines[line][i]){
5196 case TAG_EMBED:
5197 i++;
5198 switch((i < st->line_lengths[line]) ? st->text_lines[line][i] : 0){
5199 case TAG_HANDLE:
5200 i++;
5201 n = 0; /* quell gcc */
5202 /* skip the length byte plus <length> more bytes */
5203 if(i < st->line_lengths[line]){
5204 n = st->text_lines[line][i];
5205 i++;
5208 if(i < st->line_lengths[line] && n > 0){
5209 i += n;
5212 break;
5214 case TAG_FGCOLOR :
5215 case TAG_BGCOLOR :
5216 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
5217 break;
5219 case TAG_INVON:
5220 case TAG_INVOFF:
5221 case TAG_BOLDON:
5222 case TAG_BOLDOFF:
5223 case TAG_ULINEON:
5224 case TAG_ULINEOFF:
5225 i++;
5226 break;
5228 case TAG_EMBED: /* escaped embed character */
5229 i++;
5230 len++;
5231 break;
5233 default: /* the embed char was literal */
5234 i++;
5235 len += 2;
5236 break;
5239 break;
5241 case TAB:
5242 i++;
5243 while(((++len) & 0x07) != 0) /* add tab's spaces */
5246 break;
5248 default:
5249 i++;
5250 len++;
5251 break;
5254 return(len);
5258 /*----------------------------------------------------------------------
5259 Display the contents of the given file (likely output from some command)
5261 Args: filename -- name of file containing output
5262 title -- title to be used for screen displaying output
5263 alt_msg -- if no output, Q this message instead of the default
5264 mode -- non-zero to display short files in status line
5265 Returns: none
5266 ----*/
5267 void
5268 display_output_file(char *filename, char *title, char *alt_msg, int mode)
5270 STORE_S *in_file = NULL, *out_store = NULL;
5272 if((in_file = so_get(FileStar, filename, READ_ACCESS|READ_FROM_LOCALE))){
5273 if(mode == DOF_BRIEF){
5274 int msg_q = 0, i = 0;
5275 char buf[512], *msg_p[4];
5276 #define MAX_SINGLE_MSG_LEN 60
5278 buf[0] = '\0';
5279 msg_p[0] = buf;
5282 * Might need to do something about CRLFs for Windows.
5284 while(so_fgets(in_file, msg_p[msg_q], sizeof(buf) - (msg_p[msg_q] - buf))
5285 && msg_q < 3
5286 && (i = strlen(msg_p[msg_q])) < MAX_SINGLE_MSG_LEN){
5287 msg_p[msg_q+1] = msg_p[msg_q]+strlen(msg_p[msg_q]);
5288 if (*(msg_p[++msg_q] - 1) == '\n')
5289 *(msg_p[msg_q] - 1) = '\0';
5292 if(msg_q < 3 && i < MAX_SINGLE_MSG_LEN){
5293 if(*msg_p[0])
5294 for(i = 0; i < msg_q; i++)
5295 q_status_message2(SM_ORDER, 3, 4,
5296 "%s Result: %s", title, msg_p[i]);
5297 else
5298 q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
5299 alt_msg
5300 ? alt_msg
5301 : " command completed with no output");
5303 so_give(&in_file);
5304 in_file = NULL;
5307 else if(mode == DOF_EMPTY){
5308 unsigned char c;
5310 if(so_readc(&c, in_file) < 1){
5311 q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
5312 alt_msg
5313 ? alt_msg
5314 : " command completed with no output");
5315 so_give(&in_file);
5316 in_file = NULL;
5321 * We need to translate the file contents from the user's locale
5322 * charset to UTF-8 for use in scrolltool. We get that translation
5323 * from the READ_FROM_LOCALE in the in_file storage object.
5324 * It would be nice to skip this step but scrolltool doesn't use
5325 * the storage object routines to read from the file, so would
5326 * skip the translation step.
5328 if(in_file){
5329 char *errstr;
5330 gf_i_t gc;
5331 gf_o_t pc;
5333 if(!(out_store = so_get(CharStar, NULL, EDIT_ACCESS))){
5334 so_give(&in_file);
5335 our_unlink(filename);
5336 q_status_message(SM_ORDER | SM_DING, 3, 3,
5337 _("Error allocating space."));
5338 return;
5341 so_seek(in_file, 0L, 0);
5343 gf_filter_init();
5345 gf_link_filter(gf_wrap,
5346 gf_wrap_filter_opt(ps_global->ttyo->screen_cols - 4,
5347 ps_global->ttyo->screen_cols,
5348 NULL, 0, GFW_NONE));
5350 gf_set_so_readc(&gc, in_file);
5351 gf_set_so_writec(&pc, out_store);
5353 if((errstr = gf_pipe(gc, pc)) != NULL){
5354 so_give(&in_file);
5355 so_give(&out_store);
5356 our_unlink(filename);
5357 q_status_message(SM_ORDER | SM_DING, 3, 3,
5358 _("Error allocating space."));
5359 return;
5362 gf_clear_so_writec(out_store);
5363 gf_clear_so_readc(in_file);
5364 so_give(&in_file);
5367 if(out_store){
5368 SCROLL_S sargs;
5369 char title_buf[64];
5371 snprintf(title_buf, sizeof(title_buf), "HELP FOR %s VIEW", title);
5372 title_buf[sizeof(title_buf)-1] = '\0';
5374 memset(&sargs, 0, sizeof(SCROLL_S));
5375 sargs.text.text = so_text(out_store);
5376 sargs.text.src = CharStar;
5377 sargs.text.desc = "output";
5378 sargs.bar.title = title;
5379 sargs.bar.style = TextPercent;
5380 sargs.help.text = h_simple_text_view;
5381 sargs.help.title = title_buf;
5382 scrolltool(&sargs);
5383 ps_global->mangled_screen = 1;
5384 so_give(&out_store);
5387 our_unlink(filename);
5389 else
5390 dprint((2, "Error reopening %s to get results: %s\n",
5391 filename ? filename : "?", error_description(errno)));
5395 /*--------------------------------------------------------------------
5396 Call the function that will perform the double click operation
5398 Returns: the current top line
5399 --------*/
5400 long
5401 doubleclick_handle(SCROLL_S *sparms, HANDLE_S *next_handle, int *cmd, int *done)
5403 if(sparms->mouse.clickclick){
5404 if((*sparms->mouse.clickclick)(sparms))
5405 *done = 1;
5407 else
5408 switch(scroll_handle_launch(next_handle, TRUE)){
5409 case 1 :
5410 *cmd = MC_EXIT; /* propagate */
5411 *done = 1;
5412 break;
5414 case -1 :
5415 cmd_cancelled("View");
5416 break;
5418 default :
5419 break;
5422 return(scroll_state(SS_CUR)->top_text_line);
5427 #ifdef _WINDOWS
5429 * Just a little something to simplify assignments
5431 #define VIEWPOPUP(p, c, s) { \
5432 (p)->type = tQueue; \
5433 (p)->data.val = c; \
5434 (p)->label.style = lNormal; \
5435 (p)->label.string = s; \
5443 format_message_popup(sparms, in_handle)
5444 SCROLL_S *sparms;
5445 int in_handle;
5447 MPopup fmp_menu[32];
5448 HANDLE_S *h = NULL;
5449 int i = -1, n;
5450 long rawno;
5451 MESSAGECACHE *mc;
5453 /* Reason to offer per message ops? */
5454 if(mn_get_total(ps_global->msgmap) > 0L){
5455 if(in_handle){
5456 SCRLCTRL_S *st = scroll_state(SS_CUR);
5458 switch((h = get_handle(st->parms->text.handles, in_handle))->type){
5459 case Attach :
5460 fmp_menu[++i].type = tIndex;
5461 fmp_menu[i].label.string = "View Attachment";
5462 fmp_menu[i].label.style = lNormal;
5463 fmp_menu[i].data.val = 'X'; /* for local use */
5465 if(h->h.attach
5466 && dispatch_attachment(h->h.attach) != MCD_NONE
5467 && !(h->h.attach->can_display & MCD_EXTERNAL)
5468 && h->h.attach->body
5469 && (h->h.attach->body->type == TYPETEXT
5470 || (h->h.attach->body->type == TYPEMESSAGE
5471 && h->h.attach->body->subtype
5472 && !strucmp(h->h.attach->body->subtype,"rfc822")))){
5473 fmp_menu[++i].type = tIndex;
5474 fmp_menu[i].label.string = "View Attachment in New Window";
5475 fmp_menu[i].label.style = lNormal;
5476 fmp_menu[i].data.val = 'Y'; /* for local use */
5479 fmp_menu[++i].type = tIndex;
5480 fmp_menu[i].label.style = lNormal;
5481 fmp_menu[i].data.val = 'Z'; /* for local use */
5482 msgno_exceptions(ps_global->mail_stream,
5483 mn_m2raw(ps_global->msgmap,
5484 mn_get_cur(ps_global->msgmap)),
5485 h->h.attach->number, &n, FALSE);
5486 fmp_menu[i].label.string = (n & MSG_EX_DELETE)
5487 ? "Undelete Attachment"
5488 : "Delete Attachment";
5489 break;
5491 case URL :
5492 default :
5493 fmp_menu[++i].type = tIndex;
5494 fmp_menu[i].label.string = "View Link";
5495 fmp_menu[i].label.style = lNormal;
5496 fmp_menu[i].data.val = 'X'; /* for local use */
5498 fmp_menu[++i].type = tIndex;
5499 fmp_menu[i].label.string = "Copy Link";
5500 fmp_menu[i].label.style = lNormal;
5501 fmp_menu[i].data.val = 'W'; /* for local use */
5502 break;
5505 fmp_menu[++i].type = tSeparator;
5508 /* Delete or Undelete? That is the question. */
5509 fmp_menu[++i].type = tQueue;
5510 fmp_menu[i].label.style = lNormal;
5511 mc = ((rawno = mn_m2raw(ps_global->msgmap,
5512 mn_get_cur(ps_global->msgmap))) > 0L
5513 && ps_global->mail_stream
5514 && rawno <= ps_global->mail_stream->nmsgs)
5515 ? mail_elt(ps_global->mail_stream, rawno) : NULL;
5516 if(mc && mc->deleted){
5517 fmp_menu[i].data.val = 'U';
5518 fmp_menu[i].label.string = in_handle
5519 ? "&Undelete Message" : "&Undelete";
5521 else{
5522 fmp_menu[i].data.val = 'D';
5523 fmp_menu[i].label.string = in_handle
5524 ? "&Delete Message" : "&Delete";
5527 if(F_ON(F_ENABLE_FLAG, ps_global)){
5528 fmp_menu[++i].type = tSubMenu;
5529 fmp_menu[i].label.string = "Flag";
5530 fmp_menu[i].data.submenu = flag_submenu(mc);
5533 i++;
5534 VIEWPOPUP(&fmp_menu[i], 'S', in_handle ? "&Save Message" : "&Save");
5536 i++;
5537 VIEWPOPUP(&fmp_menu[i], 'E', in_handle ? "&Export Message" : "&Export");
5539 i++;
5540 VIEWPOPUP(&fmp_menu[i], '%', in_handle ? "Print Message" : "Print");
5542 i++;
5543 VIEWPOPUP(&fmp_menu[i], 'R',
5544 in_handle ? "&Reply to Message" : "&Reply");
5546 i++;
5547 VIEWPOPUP(&fmp_menu[i], 'F',
5548 in_handle ? "&Forward Message" : "&Forward");
5550 i++;
5551 VIEWPOPUP(&fmp_menu[i], 'B',
5552 in_handle ? "&Bounce Message" : "&Bounce");
5554 i++;
5555 VIEWPOPUP(&fmp_menu[i], 'T', "&Take Addresses");
5557 fmp_menu[++i].type = tSeparator;
5559 if(mn_get_cur(ps_global->msgmap) < mn_get_total(ps_global->msgmap)){
5560 i++;
5561 VIEWPOPUP(&fmp_menu[i], 'N', "View &Next Message");
5564 if(mn_get_cur(ps_global->msgmap) > 0){
5565 i++;
5566 VIEWPOPUP(&fmp_menu[i], 'P', "View &Prev Message");
5569 if(mn_get_cur(ps_global->msgmap) < mn_get_total(ps_global->msgmap)
5570 || mn_get_cur(ps_global->msgmap) > 0)
5571 fmp_menu[++i].type = tSeparator;
5573 /* Offer the attachment screen? */
5574 for(n = 0; ps_global->atmts && ps_global->atmts[n].description; n++)
5577 if(n > 1){
5578 i++;
5579 VIEWPOPUP(&fmp_menu[i], 'V', "&View Attachment Index");
5583 i++;
5584 VIEWPOPUP(&fmp_menu[i], 'I', "Message &Index");
5586 i++;
5587 VIEWPOPUP(&fmp_menu[i], 'M', "&Main Menu");
5589 fmp_menu[++i].type = tTail;
5591 if((i = mswin_popup(fmp_menu)) >= 0 && in_handle)
5592 switch(fmp_menu[i].data.val){
5593 case 'W' : /* Copy URL to clipboard */
5594 mswin_addclipboard(h->h.url.path);
5595 break;
5597 case 'X' :
5598 return(1); /* return like the user double-clicked */
5600 break;
5602 case 'Y' : /* popup the thing in another window */
5603 display_att_window(h->h.attach);
5604 break;
5606 case 'Z' :
5607 if(h && h->type == Attach){
5608 msgno_exceptions(ps_global->mail_stream,
5609 mn_m2raw(ps_global->msgmap,
5610 mn_get_cur(ps_global->msgmap)),
5611 h->h.attach->number, &n, FALSE);
5612 n ^= MSG_EX_DELETE;
5613 msgno_exceptions(ps_global->mail_stream,
5614 mn_m2raw(ps_global->msgmap,
5615 mn_get_cur(ps_global->msgmap)),
5616 h->h.attach->number, &n, TRUE);
5617 q_status_message2(SM_ORDER, 0, 3, "Attachment %s %s!",
5618 h->h.attach->number,
5619 (n & MSG_EX_DELETE) ? "deleted" : "undeleted");
5621 return(2);
5624 break;
5626 default :
5627 break;
5630 return(0);
5639 simple_text_popup(sparms, in_handle)
5640 SCROLL_S *sparms;
5641 int in_handle;
5643 MPopup simple_menu[12];
5644 int n = 0;
5646 VIEWPOPUP(&simple_menu[n], '%', "Print");
5647 n++;
5649 VIEWPOPUP(&simple_menu[n], 'S', "Save");
5650 n++;
5652 VIEWPOPUP(&simple_menu[n], 'F', "Forward in Email");
5653 n++;
5655 simple_menu[n++].type = tSeparator;
5657 VIEWPOPUP(&simple_menu[n], 'E', "Exit Viewer");
5658 n++;
5660 simple_menu[n].type = tTail;
5662 (void) mswin_popup(simple_menu);
5663 return(0);
5668 /*----------------------------------------------------------------------
5669 Return characters in scroll tool buffer serially
5671 Args: n -- index of char to return
5673 Returns: returns the character at index 'n', or -1 on error or
5674 end of buffer.
5676 ----*/
5678 mswin_readscrollbuf(n)
5679 int n;
5681 SCRLCTRL_S *st = scroll_state(SS_CUR);
5682 int c;
5683 static char **orig = NULL, **l, *p;
5684 static int lastn;
5686 if(!st)
5687 return(-1);
5690 * All of these are mind-numbingly slow at the moment...
5692 switch(st->parms->text.src){
5693 case CharStar :
5694 return((n >= strlen((char *)st->parms->text.text))
5695 ? -1 : ((char *)st->parms->text.text)[n]);
5697 case CharStarStar :
5698 /* BUG? is this test rigorous enough? */
5699 if(orig != (char **)st->parms->text.text || n < lastn){
5700 lastn = n;
5701 if(orig = l = (char **)st->parms->text.text) /* reset l and p */
5702 p = *l;
5704 else{ /* use cached l and p */
5705 c = n; /* and adjust n */
5706 n -= lastn;
5707 lastn = c;
5710 while(l){ /* look for 'n' on each line */
5711 for(; n && *p; n--, p++)
5714 if(n--) /* 'n' found ? */
5715 p = *++l;
5716 else
5717 break;
5720 return((l && *l) ? *p ? *p : '\n' : -1);
5722 case FileStar :
5723 return((fseek((FILE *)st->parms->text.text, (long) n, 0) < 0
5724 || (c = fgetc((FILE *)st->parms->text.text)) == EOF) ? -1 : c);
5726 default:
5727 return(-1);
5733 /*----------------------------------------------------------------------
5734 MSWin scroll callback. Called during scroll message processing.
5738 Args: cmd - what type of scroll operation.
5739 scroll_pos - parameter for operation.
5740 used as position for SCROLL_TO operation.
5742 Returns: TRUE - did the scroll operation.
5743 FALSE - was not able to do the scroll operation.
5744 ----*/
5746 pcpine_do_scroll (cmd, scroll_pos)
5747 int cmd;
5748 long scroll_pos;
5750 SCRLCTRL_S *st = scroll_state(SS_CUR);
5751 HANDLE_S *next_handle;
5752 int paint = FALSE;
5753 int num_display_lines;
5754 int scroll_lines;
5755 char message[64];
5756 long maxscroll;
5759 message[0] = '\0';
5760 maxscroll = st->num_lines;
5761 switch (cmd) {
5762 case MSWIN_KEY_SCROLLUPLINE:
5763 if(st->top_text_line > 0) {
5764 st->top_text_line -= (int) scroll_pos;
5765 paint = TRUE;
5766 if (st->top_text_line <= 0){
5767 snprintf(message, sizeof(message), "START of %.*s",
5768 32, STYLE_NAME(st->parms));
5769 message[sizeof(message)-1] = '\0';
5770 st->top_text_line = 0;
5773 break;
5775 case MSWIN_KEY_SCROLLDOWNLINE:
5776 if(st->top_text_line < maxscroll) {
5777 st->top_text_line += (int) scroll_pos;
5778 paint = TRUE;
5779 if (st->top_text_line >= maxscroll){
5780 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5781 message[sizeof(message)-1] = '\0';
5782 st->top_text_line = maxscroll;
5785 break;
5787 case MSWIN_KEY_SCROLLUPPAGE:
5788 if(st->top_text_line > 0) {
5789 num_display_lines = SCROLL_LINES(ps_global);
5790 scroll_lines = MIN(MAX(num_display_lines -
5791 ps_global->viewer_overlap, 1), num_display_lines);
5792 if (st->top_text_line > scroll_lines)
5793 st->top_text_line -= scroll_lines;
5794 else {
5795 st->top_text_line = 0;
5796 snprintf(message, sizeof(message), "START of %.*s", 32, STYLE_NAME(st->parms));
5797 message[sizeof(message)-1] = '\0';
5799 paint = TRUE;
5801 break;
5803 case MSWIN_KEY_SCROLLDOWNPAGE:
5804 num_display_lines = SCROLL_LINES(ps_global);
5805 if(st->top_text_line < maxscroll) {
5806 scroll_lines = MIN(MAX(num_display_lines -
5807 ps_global->viewer_overlap, 1), num_display_lines);
5808 st->top_text_line += scroll_lines;
5809 if (st->top_text_line >= maxscroll) {
5810 st->top_text_line = maxscroll;
5811 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5812 message[sizeof(message)-1] = '\0';
5814 paint = TRUE;
5816 break;
5818 case MSWIN_KEY_SCROLLTO:
5819 if (st->top_text_line != scroll_pos) {
5820 st->top_text_line = scroll_pos;
5821 if (st->top_text_line == 0)
5822 snprintf(message, sizeof(message), "START of %.*s", 32, STYLE_NAME(st->parms));
5823 else if(st->top_text_line >= maxscroll)
5824 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5826 message[sizeof(message)-1] = '\0';
5827 paint = TRUE;
5829 break;
5832 /* Need to frame some handles? */
5833 if(st->parms->text.handles
5834 && (next_handle = scroll_handle_in_frame(st->top_text_line)))
5835 st->parms->text.handles = next_handle;
5837 if (paint) {
5838 mswin_beginupdate();
5839 update_scroll_titlebar(st->top_text_line, 0);
5840 (void) scroll_scroll_text(st->top_text_line,
5841 st->parms->text.handles, 1);
5842 if (message[0])
5843 q_status_message(SM_INFO, 0, 1, message);
5845 /* Display is always called so that the "START(END) of message"
5846 * message gets removed when no longer at the start(end). */
5847 display_message (KEY_PGDN);
5848 mswin_endupdate();
5851 return (TRUE);
5855 char *
5856 pcpine_help_scroll(title)
5857 char *title;
5859 SCRLCTRL_S *st = scroll_state(SS_CUR);
5861 if(title)
5862 strncpy(title, (st->parms->help.title)
5863 ? st->parms->help.title : "Alpine Help", 256);
5865 return(pcpine_help(st->parms->help.text));
5870 pcpine_view_cursor(col, row)
5871 int col;
5872 long row;
5874 SCRLCTRL_S *st = scroll_state(SS_CUR);
5875 int key;
5876 long line;
5878 return((row >= HEADER_ROWS(ps_global)
5879 && row < HEADER_ROWS(ps_global) + SCROLL_LINES(ps_global)
5880 && (line = (row - 2) + st->top_text_line) < st->num_lines
5881 && (key = dot_on_handle(line, col))
5882 && scroll_handle_selectable(get_handle(st->parms->text.handles,key)))
5883 ? MSWIN_CURSOR_HAND
5884 : MSWIN_CURSOR_ARROW);
5886 #endif /* _WINDOWS */