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 /*======================================================================
19 Implements the mailview screen
20 Also includes scrolltool used to display help text
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
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 */
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 */
88 typedef struct scroll_file
{
95 * Struct to help write lines do display as they're decoded
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
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 *);
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);
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.
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;
233 ENVELOPE
*env
= NULL
;
236 HANDLE_S
*handles
= NULL
;
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
;
252 /*----------------- Loop viewing messages ------------------*/
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
;
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
,
278 raw_msgno
= mn_m2raw(ps
->msgmap
, mn_get_cur(ps
->msgmap
));
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
;
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
)));
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
))
317 if(offset
) /* no pre-paint during resize */
318 view_writec_killbuf();
321 /* Attempt to handle S/MIME bodies */
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 */
330 mswin_noscrollupdate(1);
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
,
337 mswin_noscrollupdate(0);
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
359 while(!scroll_handle_selectable(hp
) && hp
!= NULL
)
362 if((scrollargs
.text
.handles
= hp
) != NULL
)
363 scrollargs
.body_valid
= (hp
== handles
);
365 free_handles(&handles
);
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;
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
);
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
);
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
);
442 scrollargs
.mouse
.popup
= format_message_popup
;
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;
454 ps
->force_prefer_plain
= 1;
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)
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
);
476 mswin_destroyicons();
479 while(ps
->next_screen
== SCREEN_FUN_NULL
);
484 ps
->force_prefer_plain
= ps
->force_no_prefer_plain
= 0;
486 ps
->cur_uid_stream
= NULL
;
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
)
501 * view_writec_init - function to create and init struct that manages
502 * writing to the display what we can as soon
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));
521 scroll_setrange(0L, 0L);
524 if(ps_global
->VAR_DISPLAY_FILTERS
)
525 ClearLines(first_line
, last_line
- 1);
532 view_writec_destroy(void)
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
);
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
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.
580 (char)c
!= TAG_EMBED
&&
581 g_view_write
->index
>= (LINEBUFSIZ
- 20 * (RGBLEN
+2))) ||
582 (g_view_write
->index
>= LINEBUFSIZ
- 1)){
587 ClearLine(g_view_write
->screen_line
);
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
);
596 rv
= so_nputs(g_view_write
->store
,
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
);
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
)
621 g_view_write
->line
[g_view_write
->index
++] = (char) c
;
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
));
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
)){
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
);
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...
669 format_size_guess(struct mail_bodystruct
*body
)
673 char *free_me
= NULL
;
676 if(body
->type
== TYPEMULTIPART
){
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
;
711 fs_give((void **) &free_me
);
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
728 {'y', 'y', "Y", N_("Yes")},
729 {'n', 'n', "N", N_("No")},
732 {-2, 'r', "R", 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)))
747 && (handle
->h
.url
.tool
= url_external_handler(handle
,0))))){
749 /* if NOT special DDE hack */
750 if(handle
->h
.url
.tool
[0] != '*')
752 if(ps_global
->vars
[V_BROWSER
].is_fixed
)
753 launch_opts
[7].ch
= -1;
755 launch_opts
[7].ch
= 'a';
758 launch_opts
[7].ch
= -1;
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"));
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? */
772 flags
= OE_APPEND_CURRENT
|
774 OE_KEEP_TRAILING_SPACE
;
776 rc
= optionally_enter(tmp
, -FOOTER_ROWS(ps_global
), 0,
779 NULL
, NO_HELP
, &flags
);
781 if((flags
& OE_USER_MODIFIED
) && *tmp
){
782 if(can_access(tmp
, EXECUTE_ACCESS
) == 0){
787 * Save it for next time...
789 for(l
= ps_global
->VAR_BROWSER
, n
= 0;
794 l
= (char **) fs_get((n
+2)*sizeof(char *));
796 ps_global
->VAR_BROWSER
797 && ps_global
->VAR_BROWSER
[n
];
799 l
[n
] = cpystr(ps_global
->VAR_BROWSER
[n
]);
801 l
[n
++] = cpystr(tmp
);
804 set_variable_list(V_BROWSER
, l
, TRUE
, Main
);
807 handle
->h
.url
.tool
= cpystr(tmp
);
811 q_status_message1(SM_ORDER
| SM_DING
, 2, 2,
812 _("Browser not found: %s"),
813 error_description(errno
));
820 else if(rc
== 1 || rc
== -1){
824 if(ps_global
->redrawer
)
825 (*ps_global
->redrawer
)();
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? */
847 flags
= OE_APPEND_CURRENT
|
849 OE_KEEP_TRAILING_SPACE
;
851 rc
= optionally_enter(tmp
, -FOOTER_ROWS(ps_global
), 0,
854 NULL
, NO_HELP
, &flags
);
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 */
864 q_status_message1(SM_ORDER
| SM_DING
, 2, 2,
865 _("Image Viewer Not Found: %s"),
866 error_description(errno
));
873 else if(rc
== 1 || rc
== -1){
877 if(ps_global
->redrawer
)
878 (*ps_global
->redrawer
)();
885 if(handle
->type
== 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");
899 launch_opts
[2].ch
= -2; /* skip */
900 launch_opts
[3].ch
= -2; /* skip */
901 external
= images
= -1;
905 || (handle
->type
== URL
906 && (!struncmp(handle
->h
.url
.path
, "x-alpine-", 9)
907 || !struncmp(handle
->h
.url
.path
, "x-pine-help", 11))))
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)){
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))) ? "..." : "");
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");
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
)){
956 return(external
> 0 ? (images
> 0 ? -2 : -1) : 1);
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");
968 launch_opts
[3].label
= images
? N_("Inline imgs") : N_("All images");
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
;
985 ps_global
->next_screen
= prev_screen
;
986 ps_global
->redrawer
= redraw
;
988 role
= combine_inherited_role(role
);
989 ps_global
->reply
.role_chosen
= role
;
990 if(ps_global
->redrawer
) (ps_global
->redrawer
)();
996 strncpy(tmp
, handle
->h
.url
.path
, sizeof(tmp
)-1);
997 tmp
[sizeof(tmp
)-1] = '\0';
999 flags
= OE_APPEND_CURRENT
|
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
);
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
);
1016 else if(rc
== 1 || rc
== -1){
1020 if(ps_global
->redrawer
)
1021 (*ps_global
->redrawer
)();
1028 if(handle
->h
.url
.tool
){
1029 strncpy(tmp
, handle
->h
.url
.tool
, sizeof(tmp
)-1);
1030 tmp
[sizeof(tmp
)-1] = '\0';
1036 flags
= OE_APPEND_CURRENT
|
1038 OE_KEEP_TRAILING_SPACE
|
1041 rc
= optionally_enter(tmp
, -FOOTER_ROWS(ps_global
), 0,
1042 sizeof(tmp
), _("Viewer Command: "),
1043 NULL
, NO_HELP
, &flags
);
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
);
1054 else if(rc
== 1 || rc
== -1){
1058 if(ps_global
->redrawer
)
1059 (*ps_global
->redrawer
)();
1075 scroll_handle_launch(HANDLE_S
*handle
, int force
)
1078 switch(handle
->type
){
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 */
1093 flags
= DA_FROM_VIEW
| DA_DIDPROMPT
;
1094 switch(scroll_handle_prompt(handle
, force
)){
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
);
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
);
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 */
1126 (*handle
->h
.func
.f
)(handle
->h
.func
.args
.stream
,
1127 handle
->h
.func
.args
.msgmap
,
1128 handle
->h
.func
.args
.msgno
);
1133 alpine_panic("Unexpected HANDLE type");
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.
1155 scroll_handle_in_frame(long int top_line
)
1157 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
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;
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;
1195 * scroll_handle_reframe -- adjust display params to display given handle
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
;
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
))
1219 if(l
< st
->num_lines
){
1220 if(l
>= dlines
+ start_line
) /* bingo! */
1221 start_line
= l
- ((center
? (dlines
/ 2) : dlines
) - 1);
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
))
1236 alpine_panic("Internal Error: no handle found");
1246 handle_on_line(long int line
, int goal
)
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');
1266 handle_on_page(HANDLE_S
*handle
, long int first_line
, long int last_line
)
1271 if(handle
&& (l
= handle
->loc
)){
1272 for( ; l
; l
= l
->next
)
1273 if(l
->where
.row
< first_line
){
1277 else if(l
->where
.row
>= last_line
){
1282 return(0); /* found! */
1290 scroll_handle_selectable(HANDLE_S
*handle
)
1292 return(handle
&& (handle
->type
!= URL
1293 || (handle
->h
.url
.path
&& *handle
->h
.url
.path
)));
1298 scroll_handle_next_sel(HANDLE_S
*handles
)
1300 while(handles
&& !scroll_handle_selectable(handles
= handles
->next
))
1308 scroll_handle_prev_sel(HANDLE_S
*handles
)
1310 while(handles
&& !scroll_handle_selectable(handles
= handles
->prev
))
1318 scroll_handle_next(HANDLE_S
*handles
)
1320 HANDLE_S
*next
= NULL
;
1322 if(scroll_handle_obscured(handles
) <= 0){
1324 while((next
= scroll_handle_next_sel(next
))
1325 && scroll_handle_obscured(next
))
1335 scroll_handle_prev(HANDLE_S
*handles
)
1337 HANDLE_S
*prev
= NULL
;
1339 if(scroll_handle_obscured(handles
) >= 0){
1341 while((prev
= scroll_handle_prev_sel(prev
))
1342 && scroll_handle_obscured(prev
))
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
)
1366 scroll_handle_column(int line
, int offset
)
1368 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
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
]){
1379 switch((i
< limit
) ? st
->text_lines
[line
][i
] : 0){
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');
1389 i
+= (RGBLEN
+ 1); /* 1 for TAG, RGBLEN for color */
1401 default: /* literal embed char */
1409 while(((++col
) & 0x07) != 0) /* add tab's spaces */
1415 col
+= width_at_this_position((unsigned char*) &st
->text_lines
[line
][i
],
1416 st
->line_lengths
[line
] - i
);
1427 scroll_handle_index(int row
, int column
)
1429 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
1432 for(index
= 0; column
> 0;)
1433 switch(st
->text_lines
[row
][index
++]){
1435 switch(st
->text_lines
[row
][index
++]){
1437 index
+= st
->text_lines
[row
][index
] + 1;
1451 case TAB
: /* add tab's spaces */
1452 while(((--column
) & 0x07) != 0)
1467 scroll_handle_set_loc(POSLIST_S
**lpp
, int line
, int column
)
1471 lp
= (POSLIST_S
*) fs_get(sizeof(POSLIST_S
));
1472 lp
->where
.row
= line
;
1473 lp
->where
.col
= column
;
1475 for(; *lpp
; lpp
= &(*lpp
)->next
)
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
])
1492 switch(st
->text_lines
[line
][i
++]){
1494 switch(st
->text_lines
[line
][i
++]){
1496 for(key
= 0, n
= st
->text_lines
[line
][i
++]; n
; n
--)
1497 key
= (key
* 10) + (st
->text_lines
[line
][i
++] - '0');
1503 i
+= RGBLEN
; /* advance past color setting */
1514 while(((++column
) & 0x07) != 0) /* add tab's spaces */
1520 column
+= width_at_this_position((unsigned char*) &st
->text_lines
[line
][i
-1],
1521 st
->line_lengths
[line
] - (i
-1));
1525 while(column
< goal
);
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
)
1546 #define URL_MAX_LAUNCH (2 * MAILTMPLEN)
1549 char *cmdp
, *p
, cmd
[URL_MAX_LAUNCH
+ 4];
1550 int mode
, copied
= 0;
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.
1566 while(cmdp
-cmd
< URL_MAX_LAUNCH
)
1567 if((!*toolp
&& !copied
)
1568 || (*toolp
== '_' && !strncmp(toolp
+ 1, "URL_", 4))){
1570 /* implicit _URL_ at end */
1574 if(cmdp
[-1] == '\'') /* unquote old '_URL_' */
1578 for(p
= url
; p
&& *p
&& cmdp
-cmd
< URL_MAX_LAUNCH
; p
++)
1584 toolp
+= 5; /* length of "_URL_" */
1589 if(!(*cmdp
++ = *toolp
++))
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"));
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
){
1610 q_status_message1(SM_ORDER
, 2, 2,
1611 _("\"URL-Viewer\" not defined: Can't open %s"), url
);
1617 imgdata_open(HANDLE_S
*handle
)
1619 return do_imgdata_open(handle
->h
.img
.tool
, handle
->h
.img
.src
);
1623 do_imgdata_open(char *toolp
, char *data
)
1625 gf_io_t pc
, writec
, readc
;
1627 char *tmpfile
= NULL
, *err
= NULL
, *imgdata
, *encoding
;
1629 if(!toolp
) return 1;
1631 encoding
= strchr(data
, ';');
1632 if(encoding
) encoding
++;
1633 imgdata
= strchr(data
, ',');
1634 if(imgdata
) imgdata
++;
1635 tmpfile
= temp_nam(NULL
, "img-data-"); /* create temporary file */
1637 img
= so_get(CharStar
, NULL
, EDIT_ACCESS
); /* allocate a pointer to save data */
1638 so_seek(img
, 0L, 0); /* rewind img to start */
1639 gf_set_so_writec(&writec
, img
); /* set method to write to img in writec */
1640 gf_set_so_readc(&readc
, img
); /* set method to read from img in readc */
1646 && gf_puts(imgdata
, writec
) /* write imgdata to img using writec */
1647 && (so
= so_get(FileStar
, tmpfile
, WRITE_ACCESS
|OWNER_ONLY
)) != NULL
){ /* open temporary file */
1649 so_seek(img
, 0L, 0); /* rewind img to start */
1650 so_seek(so
, 0L, 0); /* rewind so to start */
1651 gf_set_so_writec(&pc
, so
); /* set method to write to so in pc */
1652 gf_filter_init(); /* start a filter */
1653 if(!struncmp(encoding
, "BASE64", 6)) /* link base64 filter */
1654 gf_link_filter(gf_b64_binary
, NULL
);
1656 err
= gf_pipe(readc
, pc
); /* pass data from imgdata to so, reading from readc and writing with pc */
1658 gf_clear_so_writec(so
); /* disassociate so and pc */
1660 if(so_give(&so
)) /* write tmp to disk and free so */
1661 err
= "Error writing image to file";
1663 gf_clear_so_writec(img
); /* disassociate img and writec */
1664 gf_clear_so_readc(img
); /* disassociate img and readc */
1665 so_give(&img
); /* free img */
1667 else err
= "Error creating space for temporary image";
1669 /* toolp tells us that there is a program to execute, which is
1670 * either the image viewer or the mailcap. No matter which, the
1671 * information is now in the mailcap structure, so let's use it
1674 char *subtype
= NULL
;
1675 if(!struncmp(data
, "IMAGE/", 6)){
1677 for(subtype
= data
; data
&& *data
!= ';'; data
++);
1680 if(mailcap_can_display(TYPEIMAGE
, subtype
, NULL
, 0)){
1683 mc_cmd
= mailcap_build_command(TYPEIMAGE
, subtype
,
1684 NULL
, tmpfile
, NULL
, 0);
1685 exec_mailcap_cmd(mc_cmd
, tmpfile
, 0);
1693 q_status_message(SM_ORDER
, 2, 2,
1694 _("\"Image-Viewer\" not defined: Can't open image"));
1696 if(tmpfile
) /* file was deleted by exec_mailcap_cmd */
1697 fs_give((void **) &tmpfile
);
1704 url_launch_too_long(int return_value
)
1706 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
1707 "Can't spawn. Command too long.");
1708 return(return_value
);
1713 url_external_handler(HANDLE_S
*handle
, int specific
)
1715 return get_url_external_handler(handle
->h
.url
.path
, specific
);
1719 get_url_external_handler(char *url
, int specific
)
1721 char **l
, *test
, *cmd
, *p
, *q
, *ep
;
1722 int i
, specific_match
;
1724 for(l
= ps_global
->VAR_BROWSER
; l
&& *l
; l
++){
1725 get_pair(*l
, &test
, &cmd
, 0, 1);
1726 dprint((5, "TEST: \"%s\" CMD: \"%s\"\n",
1727 test
? test
: "<NULL>", cmd
? cmd
: "<NULL>"));
1728 removing_quotes(cmd
);
1729 if(valid_filter_command(&cmd
)){
1732 if((p
= test
) != NULL
){
1735 if(!strncmp(p
+1, "TEST(", 5)
1736 && (ep
= strstr(p
+6, ")_"))){
1739 if(exec_mailcap_test_cmd(p
+6) == 0){
1743 dprint((5,"failed handler TEST\n"));
1744 fs_give((void **) &cmd
);
1747 else if(!strncmp(p
+1, "SCHEME(", 7)
1748 && (ep
= strstr(p
+8, ")_"))){
1753 if((q
= strchr(p
, ',')) != NULL
)
1757 while(!((i
= strlen(p
))
1758 && ((p
[i
-1] == ':' && url
[i
- 1] == ':')
1759 || (p
[i
-1] != ':' && url
[i
] == ':'))
1760 && !struncmp(url
, p
, i
))
1768 dprint((5,"failed handler SCHEME\n"));
1769 fs_give((void **) &cmd
);
1773 dprint((5, "UNKNOWN underscore test\n"));
1774 fs_give((void **) &cmd
);
1777 else if(isspace((unsigned char) *p
)){
1781 dprint((1,"bogus handler test: \"%s\"",
1782 test
? test
: "?"));
1783 fs_give((void **) &cmd
);
1786 fs_give((void **) &test
);
1789 if(cmd
&& (!specific
|| specific_match
))
1794 fs_give((void **) &test
);
1797 fs_give((void **) &cmd
);
1803 cmd
= url_os_specified_browser(url
);
1805 * Last chance, anything handling "text/html" in mailcap...
1807 if(!cmd
&& mailcap_can_display(TYPETEXT
, "html", NULL
, 0)){
1810 mc_cmd
= mailcap_build_command(TYPETEXT
, "html",
1811 NULL
, "_URL_", NULL
, 0);
1813 * right now URL viewing won't return anything requiring
1817 cmd
= mc_cmd
->command
;
1818 fs_give((void **)&mc_cmd
);
1827 img_handler(HANDLE_S
*handle
)
1829 char *src
= handle
&& handle
->h
.img
.src
? handle
->h
.img
.src
: NULL
;
1830 char *cmd
, *subtype
;
1832 if(!src
) return NULL
;
1834 if(ps_global
->VAR_IMAGE_VIEWER
)
1835 cmd
= cpystr(ps_global
->VAR_IMAGE_VIEWER
);
1836 else if(!struncmp(src
, "IMAGE/", 6)){
1838 for(subtype
= src
; src
&& *src
!= ';'; src
++);
1841 if(mailcap_can_display(TYPEIMAGE
, subtype
, NULL
, 0)){
1844 mc_cmd
= mailcap_build_command(TYPEIMAGE
, subtype
,
1845 NULL
, "_IMG_", NULL
, 0);
1847 cmd
= mc_cmd
->command
;
1848 fs_give((void **)&mc_cmd
);
1859 url_local_handler(char *s
)
1862 static struct url_t
{
1867 {"mailto:", 7, url_local_mailto
}, /* see url_tool_t def's */
1868 {"imap://", 7, url_local_imap
}, /* for explanations */
1869 {"nntp://", 7, url_local_nntp
},
1870 {"file://", 7, url_local_file
},
1872 {"ldap://", 7, url_local_ldap
},
1874 {"news:", 5, url_local_news
},
1875 {"x-alpine-ical:", 14, ical_send_reply
},
1876 {"x-alpine-gripe:", 15, gripe_gripe_to
},
1877 {"x-alpine-help:", 14, url_local_helper
},
1878 {"x-pine-help:", 12, url_local_helper
},
1879 {"x-alpine-config:", 16, url_local_config
},
1880 {"x-alpine-cert:", 14, url_local_certdetails
},
1881 {"#", 1, url_local_fragment
},
1885 for(i
= 0; handlers
[i
].url
; i
++)
1886 if(!struncmp(s
, handlers
[i
].url
, handlers
[i
].len
))
1887 return(handlers
[i
].f
);
1895 * mailto URL digester ala draft-hoffman-mailto-url-02.txt
1898 url_local_mailto(char *url
)
1900 return(url_local_mailto_and_atts(url
, NULL
));
1904 url_local_mailto_and_atts(char *url
, PATMT
*attachlist
)
1909 char *sig
, *urlp
, *p
, *hname
, *hvalue
;
1912 int was_a_body
= 0, impl
, template_len
= 0;
1915 REDRAFT_POS_S
*redraft_pos
= NULL
;
1916 ACTION_S
*role
= NULL
;
1918 outgoing
= mail_newenvelope();
1919 body
= mail_newbody();
1920 body
->type
= TYPETEXT
;
1921 if((body
->contents
.text
.data
= (void *) so_get(PicoText
,NULL
,EDIT_ACCESS
)) != NULL
){
1925 * mailtoURL = "mailto:" [ to ] [ headers ]
1927 * headers = "?" header *( "&" header )
1928 * header = hname "=" hvalue
1932 * NOTE2: "from" and "bcc" are intentionally excluded from
1933 * the list of understood "header" fields
1935 if((p
= strchr(urlp
= cpystr(url
+7), '?')) != NULL
)
1936 *p
++ = '\0'; /* headers? Tie off mailbox */
1938 /* grok mailbox as first "to", then roll thru specific headers */
1940 rfc822_parse_adrlist(&outgoing
->to
,
1942 ps_global
->maildomain
);
1945 /* Find next "header" */
1946 if((p
= strchr(hname
= p
, '&')) != NULL
)
1947 *p
++ = '\0'; /* tie off "header" */
1949 if((hvalue
= strchr(hname
, '=')) != NULL
)
1950 *hvalue
++ = '\0'; /* tie off hname */
1952 if(!hvalue
|| !strucmp(hname
, "subject")){
1953 char *sub
= rfc1738_str(hvalue
? hvalue
: hname
);
1955 if(outgoing
->subject
){
1956 int len
= strlen(outgoing
->subject
);
1958 fs_resize((void **) &outgoing
->subject
,
1959 (len
+ strlen(sub
) + 2) * sizeof(char));
1960 snprintf(outgoing
->subject
+ len
, strlen(sub
)+2, " %s", sub
);
1961 outgoing
->subject
[len
+ strlen(sub
) + 2 - 1] = '\0';
1964 outgoing
->subject
= cpystr(sub
);
1966 else if(!strucmp(hname
, "to")){
1967 url_mailto_addr(&outgoing
->to
, hvalue
);
1969 else if(!strucmp(hname
, "cc")){
1970 url_mailto_addr(&outgoing
->cc
, hvalue
);
1972 else if(!strucmp(hname
, "bcc")){
1973 q_status_message(SM_ORDER
, 3, 4,
1974 "\"Bcc\" header in mailto url ignored");
1976 else if(!strucmp(hname
, "from")){
1977 q_status_message(SM_ORDER
, 3, 4,
1978 "\"From\" header in mailto url ignored");
1980 else if(!strucmp(hname
, "body")){
1981 char *sub
= rfc1738_str(hvalue
? hvalue
: "");
1983 so_puts((STORE_S
*)body
->contents
.text
.data
, sub
);
1984 so_puts((STORE_S
*)body
->contents
.text
.data
, NEWLINE
);
1989 fs_give((void **) &urlp
);
1991 if(ps_global
->reply
.role_chosen
== NULL
){
1992 rflags
= ROLE_COMPOSE
;
1993 if(nonempty_patterns(rflags
, &dummy
)){
1994 role
= set_role_from_msg(ps_global
, rflags
, -1L, NULL
);
1995 if(confirm_role(rflags
, &role
))
1996 role
= combine_inherited_role(role
);
1999 cmd_cancelled("Composition");
2005 role
= ps_global
->reply
.role_chosen
;
2008 q_status_message1(SM_ORDER
, 3, 4, "Composing using role \"%s\"",
2010 outgoing
->message_id
= generate_message_id(role
);
2012 if(!was_a_body
&& role
&& role
->template){
2015 impl
= 1; /* leave cursor in header if not explicit */
2016 filtered
= detoken(role
, NULL
, 0, 0, 0, &redraft_pos
, &impl
);
2019 so_puts((STORE_S
*)body
->contents
.text
.data
, filtered
);
2021 template_len
= strlen(filtered
);
2024 fs_give((void **)&filtered
);
2030 if(!was_a_body
&& (sig
= detoken(role
, NULL
, 2, 0, 1, &redraft_pos
,
2033 redraft_pos
->offset
+= template_len
;
2036 so_puts((STORE_S
*)body
->contents
.text
.data
, sig
);
2038 fs_give((void **)&sig
);
2041 memset((void *)&fake_reply
, 0, sizeof(fake_reply
));
2042 fake_reply
.pseudo
= 1;
2043 fake_reply
.data
.pico_flags
= (outgoing
->subject
) ? P_BODY
: P_HEADEND
;
2046 if(!(role
&& role
->fcc
))
2047 fcc
= get_fcc_based_on_to(outgoing
->to
);
2050 create_message_body(&body
, attachlist
, 0);
2052 pine_send(outgoing
, &body
, "\"MAILTO\" COMPOSE",
2053 role
, fcc
, &fake_reply
, redraft_pos
, NULL
, NULL
, PS_STICKY_TO
);
2055 ps_global
->mangled_screen
= 1;
2058 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
2059 _("Can't create space for composer"));
2063 mail_free_envelope(&outgoing
);
2066 pine_free_body(&body
);
2069 fs_give((void **)&fcc
);
2071 free_redraft_pos(&redraft_pos
);
2073 ps_global
->reply
.role_chosen
= NULL
;
2080 url_mailto_addr(struct mail_address
**a
, char *a_raw
)
2082 char *p
= cpystr(rfc1738_str(a_raw
));
2084 while(*a
) /* append to address list */
2087 rfc822_parse_adrlist(a
, p
, ps_global
->maildomain
);
2088 fs_give((void **) &p
);
2093 * imap URL digester ala RFC 2192
2096 url_local_imap(char *url
)
2098 char *folder
, *mailbox
= NULL
, *errstr
= NULL
, *search
= NULL
,
2099 newfolder
[MAILTMPLEN
];
2102 imapuid_t uid
= 0L, uid_val
= 0L;
2103 CONTEXT_S
*fake_context
;
2106 rv
= url_imap_folder(url
, &folder
, &uid
, &uid_val
, &search
, 0);
2107 switch(rv
& URL_IMAP_MASK
){
2108 case URL_IMAP_IMAILBOXLIST
:
2109 /* BUG: deal with lsub tag */
2110 if((fake_context
= new_context(folder
, NULL
)) != NULL
){
2111 newfolder
[0] = '\0';
2112 if(display_folder_list(&fake_context
, newfolder
,
2113 0, folders_for_goto
))
2114 if(strlen(newfolder
) + 1 < MAILTMPLEN
)
2115 mailbox
= newfolder
;
2118 errstr
= "Problem building URL's folder list";
2120 fs_give((void **) &folder
);
2121 free_context(&fake_context
);
2127 q_status_message(SM_ORDER
|SM_DING
, 3, 3, errstr
);
2129 cmd_cancelled("URL Launch");
2133 case URL_IMAP_IMESSAGEPART
:
2134 case URL_IMAP_IMESSAGELIST
:
2135 if(ps_global
&& ps_global
->ttyo
){
2136 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
2137 ps_global
->mangled_footer
= 1;
2140 rv
= do_broach_folder(folder
, NULL
, NULL
, 0L);
2141 fs_give((void **) &folder
);
2143 case -1 : /* utter failure */
2144 ps_global
->next_screen
= main_menu_screen
;
2147 case 0 : /* same folder reopened */
2148 ps_global
->next_screen
= mail_index_screen
;
2151 case 1 : /* requested folder open */
2152 ps_global
->next_screen
= mail_index_screen
;
2154 if(uid_val
&& uid_val
!= ps_global
->mail_stream
->uid_validity
){
2156 q_status_message(SM_ORDER
|SM_DING
, 3, 3,
2157 "Warning! Referenced folder changed since URL recorded");
2162 * Make specified message the currently selected..
2164 for(i
= 1L; i
<= mn_get_total(ps_global
->msgmap
); i
++)
2165 if(mail_uid(ps_global
->mail_stream
, i
) == uid
){
2166 ps_global
->next_screen
= mail_view_screen
;
2167 mn_set_cur(ps_global
->msgmap
, i
);
2171 if(i
> mn_get_total(ps_global
->msgmap
))
2172 q_status_message(SM_ORDER
, 2, 3,
2173 "Couldn't find specified article number");
2177 * Select the specified messages
2178 * and present a zoom'd index...
2180 /* BUG: not dealing with CHARSET yet */
2182 /* ANOTHER BUG: mail_criteria is a compatibility routine for IMAP2BIS
2183 * so it doesn't know about IMAP4 search criteria, like SENTSINCE.
2184 * It also doesn't handle literals. */
2186 pine_mail_search_full(ps_global
->mail_stream
, NULL
,
2187 mail_criteria(search
),
2188 SE_NOPREFETCH
| SE_FREE
);
2190 for(i
= 1L; i
<= mn_get_total(ps_global
->msgmap
); i
++)
2191 if(ps_global
->mail_stream
2192 && i
<= ps_global
->mail_stream
->nmsgs
2193 && (mc
= mail_elt(ps_global
->mail_stream
, i
))
2195 set_lflag(ps_global
->mail_stream
,
2196 ps_global
->msgmap
, i
, MN_SLCT
, 1);
2198 if((i
= any_lflagged(ps_global
->msgmap
, MN_SLCT
)) != 0){
2200 q_status_message2(SM_ORDER
, 0, 3,
2201 "%s message%s selected",
2202 long2string(i
), plural(i
));
2203 /* Zoom the index! */
2204 zoom_index(ps_global
, ps_global
->mail_stream
,
2205 ps_global
->msgmap
, MN_SLCT
);
2213 case URL_IMAP_ERROR
:
2222 url_local_nntp(char *url
)
2224 char folder
[2*MAILTMPLEN
], *group
;
2226 long i
, article_num
;
2228 /* no hostport, no url, end of story */
2229 if((group
= strchr(url
+ 7, '/')) != 0){
2231 for(group_len
= 0; group
[group_len
] && group
[group_len
] != '/';
2233 if(!rfc1738_group(&group
[group_len
]))
2234 /* TRANSLATORS: these are errors in news group URLs */
2235 return(url_bogus(url
, _("Invalid newsgroup specified")));
2238 snprintf(folder
, sizeof(folder
), "{%.*s/nntp}#news.%.*s",
2239 (int) MIN((group
- 1) - (url
+ 7), MAILTMPLEN
-20), url
+ 7,
2240 (int) MIN(group_len
, MAILTMPLEN
-20), group
);
2241 folder
[sizeof(folder
)-1] = '\0';
2244 return(url_bogus(url
, _("No newsgroup specified")));
2247 return(url_bogus(url
, _("No server specified")));
2249 if(ps_global
&& ps_global
->ttyo
){
2250 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
2251 ps_global
->mangled_footer
= 1;
2254 switch(do_broach_folder(rfc1738_str(folder
), NULL
, NULL
, 0L)){
2255 case -1 : /* utter failure */
2256 ps_global
->next_screen
= main_menu_screen
;
2259 case 0 : /* same folder reopened */
2260 ps_global
->next_screen
= mail_index_screen
;
2263 case 1 : /* requested folder open */
2264 ps_global
->next_screen
= mail_index_screen
;
2266 /* grok article number --> c-client UID */
2267 if(group
[group_len
++] == '/'
2268 && (article_num
= atol(&group
[group_len
]))){
2270 * Make the requested article our current message
2272 for(i
= 1; i
<= mn_get_nmsgs(ps_global
->msgmap
); i
++)
2273 if(mail_uid(ps_global
->mail_stream
, i
) == article_num
){
2274 ps_global
->next_screen
= mail_view_screen
;
2275 if((i
= mn_raw2m(ps_global
->msgmap
, i
)) != 0)
2276 mn_set_cur(ps_global
->msgmap
, i
);
2280 if(i
== 0 || i
> mn_get_total(ps_global
->msgmap
))
2281 q_status_message(SM_ORDER
, 2, 3,
2282 _("Couldn't find specified article number"));
2288 ps_global
->redrawer
= (void(*)(void))NULL
;
2294 url_local_news(char *url
)
2296 char folder
[MAILTMPLEN
], *p
;
2297 CONTEXT_S
*cntxt
= NULL
;
2300 * NOTE: NO SUPPORT for '*' or message-id
2303 if(*(url
+5) == '/' && *(url
+6) == '/')
2304 return(url_local_nntp(url
)); /* really meant "nntp://"? */
2306 if(ps_global
->VAR_NNTP_SERVER
&& ps_global
->VAR_NNTP_SERVER
[0])
2308 * BUG: Only the first NNTP server is tried.
2310 snprintf(folder
, sizeof(folder
), "{%s/nntp}#news.", ps_global
->VAR_NNTP_SERVER
[0]);
2312 strncpy(folder
, "#news.", sizeof(folder
));
2314 folder
[sizeof(folder
)-1] = '\0';
2316 for(p
= strncpy(folder
+ strlen(folder
), url
+ 5, sizeof(folder
)-strlen(folder
)-1);
2317 *p
&& rfc1738_group(p
);
2322 return(url_bogus(url
, "Invalid newsgroup specified"));
2324 else{ /* fish first group from newsrc */
2330 /* Find first news context */
2331 for(cntxt
= ps_global
->context_list
;
2332 cntxt
&& !(cntxt
->use
& CNTXT_NEWS
);
2333 cntxt
= cntxt
->next
)
2337 if((alphaorder
= F_OFF(F_READ_IN_NEWSRC_ORDER
, ps_global
)) != 0)
2338 (void) F_SET(F_READ_IN_NEWSRC_ORDER
, ps_global
, 1);
2340 build_folder_list(NULL
, cntxt
, NULL
, NULL
, BFL_LSUB
);
2341 if((f
= folder_entry(0, FOLDERS(cntxt
))) != NULL
){
2342 strncpy(folder
, f
->name
, sizeof(folder
));
2343 folder
[sizeof(folder
)-1] = '\0';
2346 free_folder_list(cntxt
);
2349 (void) F_SET(F_READ_IN_NEWSRC_ORDER
, ps_global
, 0);
2352 if(folder
[0] == '\0'){
2353 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
2354 "No default newsgroup");
2359 if(ps_global
&& ps_global
->ttyo
){
2360 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
2361 ps_global
->mangled_footer
= 1;
2364 if(do_broach_folder(rfc1738_str(folder
), cntxt
, NULL
, 0L) < 0)
2365 ps_global
->next_screen
= main_menu_screen
;
2367 ps_global
->next_screen
= mail_index_screen
;
2369 ps_global
->redrawer
= (void(*)(void))NULL
;
2376 url_local_file(char *file_url
)
2379 /* TRANSLATORS: this is a warning that the file URL can cause programs to run which may
2380 be a security problem. We are asking the user to confirm that they want to do this. */
2381 _("\"file\" URL may cause programs to be run on your system. Run anyway"),
2382 'n', 0, NO_HELP
, WT_NORM
) == 'y'){
2386 handle
.h
.url
.path
= file_url
;
2387 if((handle
.h
.url
.tool
= url_external_handler(&handle
, 1))
2388 || (handle
.h
.url
.tool
= url_external_handler(&handle
, 0))){
2389 url_launch(&handle
);
2393 q_status_message(SM_ORDER
, 0, 4,
2394 _("No viewer for \"file\" URL. VIEWER command cancelled"));
2398 q_status_message(SM_ORDER
, 0, 4, _("VIEWER command cancelled"));
2404 url_local_fragment(char *fragment
)
2406 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
2407 HANDLE_S
*hp
= NULL
;
2410 * find a handle with the fragment's name
2413 for(hp
= st
->parms
->text
.handles
; hp
; hp
= hp
->next
)
2414 if(hp
->type
== URL
&& hp
->h
.url
.name
2415 && !strcmp(hp
->h
.url
.name
, fragment
+ 1))
2419 for(hp
= st
->parms
->text
.handles
->prev
; hp
; hp
= hp
->prev
)
2420 if(hp
->type
== URL
&& hp
->h
.url
.name
2421 && !strcmp(hp
->h
.url
.name
, fragment
+ 1))
2425 * set the top line of the display to contain this line
2428 st
->top_text_line
= hp
->loc
->where
.row
;
2429 ps_global
->mangled_body
= 1;
2432 q_status_message1(SM_ORDER
| SM_DING
, 0, 3,
2433 "Can't find fragment: %s", fragment
);
2439 ical_send_reply(char *url
)
2441 // ical_compose_reply(url + strlen("x-alpine-ical:"));
2447 * Format editorial comment referencing screen offering
2448 * List-* header supplied commands
2451 rfc2369_editorial(long int msgno
, HANDLE_S
**handlesp
, int flags
, int width
, gf_io_t pc
)
2453 char *p
, *hdrp
, *hdrs
[MLCMD_COUNT
+ 1],
2454 color
[64], buf
[2048];
2455 int i
, n
, rv
= TRUE
;
2458 if((flags
& FM_DISPLAY
)
2459 && (hdrp
= pine_fetchheader_lines(ps_global
->mail_stream
, msgno
,
2460 NULL
, rfc2369_hdrs(hdrs
)))){
2462 snprintf(buf
, sizeof(buf
), "Note: This message contains ");
2463 buf
[sizeof(buf
)-1] = '\0';
2464 p
= buf
+ strlen(buf
);
2467 h
= new_handle(handlesp
);
2469 h
->h
.func
.f
= rfc2369_display
;
2470 h
->h
.func
.args
.stream
= ps_global
->mail_stream
;
2471 h
->h
.func
.args
.msgmap
= ps_global
->msgmap
;
2472 h
->h
.func
.args
.msgno
= msgno
;
2474 if(!(flags
& FM_NOCOLOR
)
2475 && handle_start_color(color
, sizeof(color
), &n
, 0)){
2476 if((p
-buf
)+n
< sizeof(buf
))
2477 for(i
= 0; i
< n
; i
++)
2480 else if(F_OFF(F_SLCTBL_ITEM_NOBOLD
, ps_global
)){
2485 if((p
-buf
)+2 < sizeof(buf
)){
2490 snprintf(p
+ 1, sizeof(buf
)-(p
+1-buf
), "%d", h
->key
);
2491 buf
[sizeof(buf
)-1] = '\0';
2496 sstrncpy(&p
, "email list management information", sizeof(buf
)-(p
-buf
));
2497 buf
[sizeof(buf
)-1] = '\0';
2500 /* in case it was the current selection */
2501 if((p
-buf
)+2 < sizeof(buf
)){
2506 if(handle_end_color(color
, sizeof(color
), &n
)){
2507 if((p
-buf
)+n
< sizeof(buf
))
2508 for(i
= 0; i
< n
; i
++)
2512 if((p
-buf
)+2 < sizeof(buf
)){
2518 if(p
-buf
< sizeof(buf
))
2522 buf
[sizeof(buf
)-1] = '\0';
2524 rv
= (gf_puts(NEWLINE
, pc
)
2525 && format_editorial(buf
, width
, flags
, handlesp
, pc
) == NULL
2526 && gf_puts(NEWLINE
, pc
));
2529 fs_give((void **) &hdrp
);
2537 /*----------------------------------------------------------------------
2538 routine for displaying text on the screen.
2540 Args: sparms -- structure of args controlling what happens outside
2541 just the business of managing text scrolling
2543 This displays in three different kinds of text. One is an array of
2544 lines passed in in text_array. The other is a simple long string of
2545 characters passed in in text.
2547 The style determines what some of the error messages will be, and
2548 what commands are available as different things are appropriate for
2549 help text than for message text etc.
2554 scrolltool(SCROLL_S
*sparms
)
2556 register long cur_top_line
, num_display_lines
;
2558 int result
, done
, cmd
, found_on
, found_on_index
,
2559 first_view
, force
, scroll_lines
, km_size
,
2560 cursor_row
, cursor_col
, km_popped
, rv
,
2564 struct key_menu
*km
;
2565 HANDLE_S
*next_handle
;
2570 num_display_lines
= SCROLL_LINES(ps_global
);
2572 ps_global
->mangled_header
= 1;
2573 ps_global
->mangled_footer
= 1;
2574 ps_global
->mangled_body
= !sparms
->body_valid
;
2575 ncols
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 0;
2576 nrows
= ps_global
->ttyo
? ps_global
->ttyo
->screen_rows
: 0;
2578 what
= sparms
->keys
.what
; /* which key menu to display */
2582 found_on_index
= -1;
2584 if(sparms
->quell_first_view
)
2588 ch
= 'x'; /* for first time through */
2589 whereis_pos
.row
= 0;
2590 whereis_pos
.col
= 0;
2591 next_handle
= sparms
->text
.handles
;
2593 set_scroll_text(sparms
, cur_top_line
, scroll_state(SS_NEW
));
2594 format_scroll_text();
2596 if((km
= sparms
->keys
.menu
) != NULL
){
2597 memcpy(bitmap
, sparms
->keys
.bitmap
, sizeof(bitmap_t
));
2601 km
= &simple_text_keymenu
;
2603 sparms
->mouse
.popup
= simple_text_popup
;
2607 if(!sparms
->bar
.title
)
2608 sparms
->bar
.title
= "Text";
2610 if(sparms
->bar
.style
== TitleBarNone
){
2611 if(THREADING() && sp_viewing_a_thread(ps_global
->mail_stream
))
2612 sparms
->bar
.style
= ThrdMsgPercent
;
2614 sparms
->bar
.style
= MsgTextPercent
;
2617 switch(sparms
->start
.on
){
2619 cur_top_line
= MAX(0, scroll_text_lines() - (num_display_lines
-2));
2620 if(F_ON(F_SHOW_CURSOR
, ps_global
)){
2621 whereis_pos
.row
= scroll_text_lines() - cur_top_line
;
2622 found_on
= scroll_text_lines() - 1;
2628 if(sparms
->start
.loc
.frag
){
2629 (void) url_local_fragment(sparms
->start
.loc
.frag
);
2631 cur_top_line
= scroll_state(SS_CUR
)->top_text_line
;
2633 if(F_ON(F_SHOW_CURSOR
, ps_global
)){
2634 whereis_pos
.row
= scroll_text_lines() - cur_top_line
;
2635 found_on
= scroll_text_lines() - 1;
2642 if(sparms
->start
.loc
.offset
){
2643 for(cur_top_line
= 0L;
2644 cur_top_line
+ 1 < scroll_text_lines()
2645 && (sparms
->start
.loc
.offset
2646 -= scroll_handle_column(cur_top_line
,
2655 if(scroll_handle_obscured(sparms
->text
.handles
))
2656 cur_top_line
= scroll_handle_reframe(-1, TRUE
);
2660 default : /* no-op */
2664 /* prepare for calls below to tell us where to go */
2665 ps_global
->next_screen
= SCREEN_FUN_NULL
;
2667 cancel_busy_cue(-1);
2670 ps_global
->user_says_cancel
= 0;
2674 clearfooter(ps_global
);
2675 ps_global
->mangled_body
= 1;
2679 if(ps_global
->mangled_screen
) {
2680 ps_global
->mangled_header
= 1;
2681 ps_global
->mangled_footer
= 1;
2682 ps_global
->mangled_body
= 1;
2685 if(!sparms
->quell_newmail
&& streams_died())
2686 ps_global
->mangled_header
= 1;
2688 dprint((9, "@@@@ current:%ld\n",
2689 mn_get_cur(ps_global
->msgmap
)));
2692 /*==================== All Screen painting ====================*/
2693 /*-------------- The title bar ---------------*/
2694 update_scroll_titlebar(cur_top_line
, ps_global
->mangled_header
);
2696 if(ps_global
->mangled_screen
){
2697 /* this is the only line not cleared by header, body or footer
2701 ps_global
->mangled_screen
= 0;
2704 /*---- Scroll or update the body of the text on the screen -------*/
2705 cur_top_line
= scroll_scroll_text(cur_top_line
, next_handle
,
2706 ps_global
->mangled_body
);
2707 ps_global
->redrawer
= redraw_scroll_text
;
2708 ps_global
->mangled_body
= 0;
2710 /*--- Check to see if keymenu might change based on next_handle --*/
2711 if(sparms
->text
.handles
!= next_handle
)
2712 ps_global
->mangled_footer
= 1;
2715 sparms
->text
.handles
= next_handle
;
2717 /*------------- The key menu footer --------------------*/
2718 if(ps_global
->mangled_footer
|| sparms
->keys
.each_cmd
){
2720 FOOTER_ROWS(ps_global
) = 3;
2721 clearfooter(ps_global
);
2724 if(F_ON(F_ARROW_NAV
, ps_global
)){
2725 menu_clear_binding(km
, KEY_LEFT
);
2726 if((cmd
= menu_clear_binding(km
, '<')) != MC_UNKNOWN
){
2727 menu_add_binding(km
, '<', cmd
);
2728 menu_add_binding(km
, KEY_LEFT
, cmd
);
2732 if(F_ON(F_ARROW_NAV
, ps_global
)){
2733 menu_clear_binding(km
, KEY_RIGHT
);
2734 if((cmd
= menu_clear_binding(km
, '>')) != MC_UNKNOWN
){
2735 menu_add_binding(km
, '>', cmd
);
2736 menu_add_binding(km
, KEY_RIGHT
, cmd
);
2740 if(sparms
->keys
.each_cmd
){
2741 (*sparms
->keys
.each_cmd
)(sparms
,
2742 scroll_handle_obscured(sparms
->text
.handles
));
2743 memcpy(bitmap
, sparms
->keys
.bitmap
, sizeof(bitmap_t
));
2746 if(menu_binding_index(km
, MC_JUMP
) >= 0){
2747 for(cmd
= 0; cmd
< 10; cmd
++)
2748 if(F_ON(F_ENABLE_JUMP
, ps_global
))
2749 (void) menu_add_binding(km
, '0' + cmd
, MC_JUMP
);
2751 (void) menu_clear_binding(km
, '0' + cmd
);
2754 draw_keymenu(km
, bitmap
, ps_global
->ttyo
->screen_cols
,
2755 1-FOOTER_ROWS(ps_global
), 0, what
);
2757 ps_global
->mangled_footer
= 0;
2759 FOOTER_ROWS(ps_global
) = 1;
2760 mark_keymenu_dirty();
2764 if((ps_global
->first_time_user
|| ps_global
->show_new_version
)
2765 && first_view
&& sparms
->text
.handles
2766 && (sparms
->text
.handles
->next
|| sparms
->text
.handles
->prev
)
2767 && !sparms
->quell_help
)
2768 q_status_message(SM_ORDER
, 0, 3, HANDLE_INIT_MSG
);
2770 /*============ Check for New Mail and CheckPoint ============*/
2771 if(!sparms
->quell_newmail
&&
2772 new_mail(force
, NM_TIMING(ch
), NM_STATUS_MSG
) >= 0){
2773 update_scroll_titlebar(cur_top_line
, 1);
2774 if(ps_global
->mangled_footer
)
2775 draw_keymenu(km
, bitmap
, ps_global
->ttyo
->screen_cols
,
2776 1-FOOTER_ROWS(ps_global
), 0, what
);
2778 ps_global
->mangled_footer
= 0;
2782 * If an expunge of the current message happened during the
2783 * new mail check we want to bail out of here. See mm_expunged.
2785 if(ps_global
->next_screen
!= SCREEN_FUN_NULL
){
2790 if(ps_global
->noticed_change_in_unseen
){
2791 ps_global
->noticed_change_in_unseen
= 0; /* redraw only once */
2792 cmd
= MC_RESIZE
; /* causes cursor to be saved in folder_lister */
2797 if(first_view
&& num_display_lines
>= scroll_text_lines())
2798 q_status_message1(SM_INFO
, 0, 1, "ALL of %s", STYLE_NAME(sparms
));
2801 force
= 0; /* may not need to next time around */
2802 first_view
= 0; /* check_point a priority any more? */
2804 /*==================== Output the status message ==============*/
2805 if(!sparms
->no_stat_msg
){
2807 FOOTER_ROWS(ps_global
) = 3;
2808 mark_status_unknown();
2811 display_message(ch
);
2813 FOOTER_ROWS(ps_global
) = 1;
2814 mark_status_unknown();
2818 if(F_ON(F_SHOW_CURSOR
, ps_global
)){
2820 if(cur_top_line
!= scroll_state(SS_CUR
)->top_text_line
)
2821 whereis_pos
.row
= 0;
2824 if(whereis_pos
.row
> 0){
2825 cursor_row
= SCROLL_LINES_ABOVE(ps_global
)
2826 + whereis_pos
.row
- 1;
2827 cursor_col
= whereis_pos
.col
;
2830 POSLIST_S
*lp
= NULL
;
2832 if(sparms
->text
.handles
&&
2833 !scroll_handle_obscured(sparms
->text
.handles
)){
2834 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
2836 for(lp
= sparms
->text
.handles
->loc
; lp
; lp
= lp
->next
)
2837 if(lp
->where
.row
>= st
->top_text_line
2838 && lp
->where
.row
< st
->top_text_line
2839 + st
->screen
.length
){
2840 cursor_row
= lp
->where
.row
- cur_top_line
2841 + SCROLL_LINES_ABOVE(ps_global
);
2842 cursor_col
= lp
->where
.col
;
2849 /* first new line of text */
2850 cursor_row
= SCROLL_LINES_ABOVE(ps_global
) +
2851 ((cur_top_line
== 0) ? 0 : ps_global
->viewer_overlap
);
2857 cursor_row
= ps_global
->ttyo
->screen_rows
2858 - SCROLL_LINES_BELOW(ps_global
);
2861 MoveCursor(cursor_row
, cursor_col
);
2863 /*================ Get command and validate =====================*/
2866 if(sparms
->text
.handles
)
2869 mouse_in_content(KEY_MOUSE
, -1, -1, 0x5, 0);
2870 register_mfunc(mouse_in_content
, HEADER_ROWS(ps_global
), 0,
2871 ps_global
->ttyo
->screen_rows
2872 - (FOOTER_ROWS(ps_global
) + 1),
2873 ps_global
->ttyo
->screen_cols
);
2877 mswin_allowcopy(mswin_readscrollbuf
);
2878 mswin_setscrollcallback(pcpine_do_scroll
);
2880 if(sparms
->help
.text
!= NO_HELP
)
2881 mswin_sethelptextcallback(pcpine_help_scroll
);
2883 if(sparms
->text
.handles
2884 && sparms
->text
.handles
->type
!= Folder
)
2885 mswin_mousetrackcallback(pcpine_view_cursor
);
2887 if(ps_global
->prev_screen
== mail_view_screen
)
2888 mswin_setviewinwindcallback(view_in_new_window
);
2891 if(sparms
->aux_function
2892 && sparms
->aux_condition
2893 && ((rv
= (sparms
->aux_condition
)(sparms
->aux_value
)) == 0))
2894 (sparms
->aux_function
)(sparms
->aux_value
, sparms
->aux_rv_value
);
2897 ch
= (sparms
->decode_aux_rv_value
)(sparms
->aux_value
, sparms
->aux_rv_value
);
2898 if (ch
== NO_OP_COMMAND
) ch
= read_command(&utf8str
);
2900 else if(cmd
== MC_RESIZE
2901 || ps_global
->ttyo
== NULL
2902 || (ps_global
->ttyo
->screen_cols
== ncols
2903 && ps_global
->ttyo
->screen_rows
== nrows
))
2904 ch
= (sparms
->quell_newmail
|| read_command_prep()) ? read_command(&utf8str
) : NO_OP_COMMAND
;
2907 if(sparms
->text
.handles
)
2909 clear_mfunc(mouse_in_content
);
2912 mswin_allowcopy(NULL
);
2913 mswin_setscrollcallback(NULL
);
2914 mswin_sethelptextcallback(NULL
);
2915 mswin_mousetrackcallback(NULL
);
2916 mswin_setviewinwindcallback(NULL
);
2917 cur_top_line
= scroll_state(SS_CUR
)->top_text_line
;
2919 /* we need to check if there was a resize of the screen
2920 * which did not happen in this routine but during a call
2921 * to another routine from this routing, and that routine has no
2922 * way to tell us that a resize happened
2926 && (ps_global
->ttyo
->screen_cols
!= ncols
2927 || ps_global
->ttyo
->screen_rows
!= nrows
))
2930 cmd
= menu_command(ch
, km
);
2932 ncols
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 0;
2933 nrows
= ps_global
->ttyo
? ps_global
->ttyo
->screen_rows
: 0;
2945 clearfooter(ps_global
);
2950 /*============= Execute command =======================*/
2953 /* ------ Help -------*/
2955 if(FOOTER_ROWS(ps_global
) == 1 && km_popped
== 0){
2957 ps_global
->mangled_footer
= 1;
2961 whereis_pos
.row
= 0;
2962 if(sparms
->help
.text
== NO_HELP
){
2963 q_status_message(SM_ORDER
, 0, 5,
2964 _("No help text currently available"));
2968 km_size
= FOOTER_ROWS(ps_global
);
2970 helper(sparms
->help
.text
, sparms
->help
.title
, 0);
2972 if(ps_global
->next_screen
!= main_menu_screen
2973 && km_size
== FOOTER_ROWS(ps_global
)) {
2974 /* Have to reset because helper uses scroll_text */
2975 num_display_lines
= SCROLL_LINES(ps_global
);
2976 ps_global
->mangled_screen
= 1;
2984 /*---------- Roll keymenu ------*/
2986 if(F_OFF(F_USE_FK
, ps_global
))
2990 ps_global
->mangled_footer
= 1;
2994 /* -------- Scroll back one page -----------*/
2996 whereis_pos
.row
= 0;
2998 scroll_lines
= MIN(MAX(num_display_lines
-
2999 ps_global
->viewer_overlap
, 1), num_display_lines
);
3000 cur_top_line
-= scroll_lines
;
3001 if(cur_top_line
<= 0){
3003 q_status_message1(SM_INFO
, 0, 1, "START of %s",
3004 STYLE_NAME(sparms
));
3008 /* hilite last available handle */
3010 if(sparms
->text
.handles
){
3011 HANDLE_S
*h
= sparms
->text
.handles
;
3013 while((h
= scroll_handle_prev_sel(h
))
3014 && !scroll_handle_obscured(h
))
3019 q_status_message1(SM_ORDER
, 0, 1, _("Already at start of %s"),
3020 STYLE_NAME(sparms
));
3028 /*---- Scroll down one page -------*/
3030 if(cur_top_line
+ num_display_lines
< scroll_text_lines()){
3031 whereis_pos
.row
= 0;
3032 scroll_lines
= MIN(MAX(num_display_lines
-
3033 ps_global
->viewer_overlap
, 1), num_display_lines
);
3034 cur_top_line
+= scroll_lines
;
3036 if(cur_top_line
+ num_display_lines
>= scroll_text_lines())
3037 q_status_message1(SM_INFO
, 0, 1, "END of %s",
3038 STYLE_NAME(sparms
));
3040 else if(!sparms
->end_scroll
3041 || !(done
= (*sparms
->end_scroll
)(sparms
))){
3042 q_status_message1(SM_ORDER
, 0, 1, _("Already at end of %s"),
3043 STYLE_NAME(sparms
));
3044 /* hilite last available handle */
3045 if(sparms
->text
.handles
){
3046 HANDLE_S
*h
= sparms
->text
.handles
;
3048 while((h
= scroll_handle_next_sel(h
)) != NULL
)
3055 /* scroll to the top page */
3059 q_status_message1(SM_INFO
, 0, 1, "START of %s",
3060 STYLE_NAME(sparms
));
3064 if(sparms
->text
.handles
){
3065 HANDLE_S
*h
= sparms
->text
.handles
;
3067 while((h
= scroll_handle_prev_sel(h
)) != NULL
)
3072 /* scroll to the bottom page */
3074 if(cur_top_line
+ num_display_lines
< scroll_text_lines()){
3075 cur_top_line
= scroll_text_lines() - MIN(5, num_display_lines
);
3076 q_status_message1(SM_INFO
, 0, 1, "END of %s",
3077 STYLE_NAME(sparms
));
3080 if(sparms
->text
.handles
){
3081 HANDLE_S
*h
= sparms
->text
.handles
;
3083 while((h
= scroll_handle_next_sel(h
)) != NULL
)
3088 /*------ Scroll down one line -----*/
3091 if(sparms
->text
.handles
){
3092 if(sparms
->vert_handle
){
3096 h2
= sparms
->text
.handles
;
3097 if(h2
->type
== Folder
&& h2
->prev
&& h2
->prev
->is_dual_do_open
)
3100 i
= h2
->loc
->where
.row
+ 1;
3101 j
= h2
->loc
->where
.col
;
3102 for(h
= NULL
, k
= h2
->key
;
3104 || (h
->loc
->where
.row
== h2
->loc
->where
.row
));
3106 /* must be different key */
3107 /* ... below current line */
3108 /* ... pref'bly to left */
3110 && h2
->loc
->where
.row
>= i
){
3111 if(h2
->loc
->where
.col
> j
){
3122 whereis_pos
.row
= 0;
3124 if((result
= scroll_handle_obscured(next_handle
)) != 0){
3127 if(scroll_handle_obscured(sparms
->text
.handles
)
3129 next_handle
= sparms
->text
.handles
;
3131 ps_global
->mangled_body
++;
3132 new_top
= scroll_handle_reframe(next_handle
->key
,0);
3134 cur_top_line
= new_top
;
3138 else if(!(ch
== ctrl('N') || F_ON(F_FORCE_ARROWS
, ps_global
)))
3139 next_handle
= scroll_handle_next(sparms
->text
.handles
);
3143 if(cur_top_line
+ num_display_lines
< scroll_text_lines()){
3144 whereis_pos
.row
= 0;
3146 if(cur_top_line
+ num_display_lines
>= scroll_text_lines())
3147 q_status_message1(SM_INFO
, 0, 1, "END of %s",
3148 STYLE_NAME(sparms
));
3151 q_status_message1(SM_ORDER
, 0, 1, _("Already at end of %s"),
3152 STYLE_NAME(sparms
));
3158 /* ------ Scroll back up one line -------*/
3161 if(sparms
->text
.handles
){
3162 if(sparms
->vert_handle
){
3166 h2
= sparms
->text
.handles
;
3167 if(h2
->type
== Folder
&& h2
->prev
&& h2
->prev
->is_dual_do_open
)
3170 i
= h2
->loc
->where
.row
- 1;
3171 j
= h2
->loc
->where
.col
;
3173 for(h
= NULL
, k
= h2
->key
;
3175 || (h
->loc
->where
.row
== h2
->loc
->where
.row
));
3177 /* must be new key, above current
3178 * line and pref'bly to right
3181 && h2
->loc
->where
.row
<= i
){
3182 if(h2
->loc
->where
.col
< j
){
3193 whereis_pos
.row
= 0;
3195 if((result
= scroll_handle_obscured(next_handle
)) != 0){
3198 if(scroll_handle_obscured(sparms
->text
.handles
)
3200 next_handle
= sparms
->text
.handles
;
3202 ps_global
->mangled_body
++;
3203 new_top
= scroll_handle_reframe(next_handle
->key
,0);
3205 cur_top_line
= new_top
;
3209 else if(!(ch
== ctrl('P') || F_ON(F_FORCE_ARROWS
, ps_global
)))
3210 next_handle
= scroll_handle_prev(sparms
->text
.handles
);
3214 whereis_pos
.row
= 0;
3217 if(cur_top_line
== 0)
3218 q_status_message1(SM_INFO
, 0, 1, "START of %s",
3219 STYLE_NAME(sparms
));
3222 q_status_message1(SM_ORDER
, 0, 1,
3223 _("Already at start of %s"),
3224 STYLE_NAME(sparms
));
3230 case MC_NEXT_HANDLE
:
3231 if((next_handle
= scroll_handle_next_sel(sparms
->text
.handles
)) != NULL
){
3232 whereis_pos
.row
= 0;
3233 if((result
= scroll_handle_obscured(next_handle
)) != 0){
3236 if(scroll_handle_obscured(sparms
->text
.handles
)
3238 next_handle
= sparms
->text
.handles
;
3240 ps_global
->mangled_body
++;
3241 new_top
= scroll_handle_reframe(next_handle
->key
, 0);
3243 cur_top_line
= new_top
;
3247 if(scroll_handle_obscured(sparms
->text
.handles
)){
3250 ps_global
->mangled_body
++;
3251 if((new_top
= scroll_handle_reframe(-1, 0)) >= 0){
3252 whereis_pos
.row
= 0;
3253 cur_top_line
= new_top
;
3257 q_status_message1(SM_ORDER
, 0, 1,
3258 _("Already on last item in %s"),
3259 STYLE_NAME(sparms
));
3265 case MC_PREV_HANDLE
:
3266 if((next_handle
= scroll_handle_prev_sel(sparms
->text
.handles
)) != NULL
){
3267 whereis_pos
.row
= 0;
3268 if((result
= scroll_handle_obscured(next_handle
)) != 0){
3271 if(scroll_handle_obscured(sparms
->text
.handles
)
3273 next_handle
= sparms
->text
.handles
;
3275 ps_global
->mangled_body
++;
3276 new_top
= scroll_handle_reframe(next_handle
->key
, 0);
3278 cur_top_line
= new_top
;
3282 if(scroll_handle_obscured(sparms
->text
.handles
)){
3285 ps_global
->mangled_body
++;
3286 if((new_top
= scroll_handle_reframe(-1, 0)) >= 0){
3287 whereis_pos
.row
= 0;
3288 cur_top_line
= new_top
;
3292 q_status_message1(SM_ORDER
, 0, 1,
3293 _("Already on first item in %s"),
3294 STYLE_NAME(sparms
));
3300 /*------ View the current handle ------*/
3301 case MC_VIEW_HANDLE
:
3302 switch(scroll_handle_obscured(sparms
->text
.handles
)){
3305 switch(scroll_handle_launch(sparms
->text
.handles
,
3306 sparms
->text
.handles
->force_display
)){
3308 cmd
= MC_EXIT
; /* propagate */
3313 cmd_cancelled(NULL
);
3320 cur_top_line
= scroll_state(SS_CUR
)->top_text_line
;
3324 q_status_message(SM_ORDER
, 0, 2, HANDLE_BELOW_ERR
);
3328 q_status_message(SM_ORDER
, 0, 2, HANDLE_ABOVE_ERR
);
3334 /*---------- Search text (where is) ----------*/
3336 ps_global
->mangled_footer
= 1;
3338 int start_index
, key
= 0;
3339 char *report
= NULL
;
3341 start_row
= cur_top_line
;
3344 if(F_ON(F_SHOW_CURSOR
,ps_global
)){
3346 || found_on
>= scroll_text_lines()
3347 || found_on
< cur_top_line
3348 || found_on
>= cur_top_line
+ num_display_lines
){
3349 start_row
= cur_top_line
;
3353 if(found_on_index
< 0){
3354 start_row
= found_on
+ 1;
3358 start_row
= found_on
;
3359 start_index
= found_on_index
+1;
3363 else if(sparms
->srch_handle
){
3366 if((h
= scroll_handle_next_sel(sparms
->text
.handles
)) != NULL
){
3368 * Translate the screen's column into the
3369 * line offset to start on...
3371 * This makes it so search_text never returns -3
3372 * so we don't know it is the same match. That's
3373 * because we start well after the current handle
3374 * (at the next handle) and that causes us to
3375 * think the one we just matched on is a different
3376 * one from before. Can't think of an easy way to
3377 * fix it, though, and it isn't a big deal. We still
3378 * match, we just don't say current line contains
3381 start_row
= h
->loc
->where
.row
;
3382 start_index
= scroll_handle_index(start_row
, h
->loc
->where
.col
);
3385 /* last handle, start over at top */
3386 start_row
= cur_top_line
;
3391 start_row
= (found_on
< 0
3392 || found_on
>= scroll_text_lines()
3393 || found_on
< cur_top_line
3394 || found_on
>= cur_top_line
+ num_display_lines
)
3395 ? cur_top_line
: found_on
+ 1,
3399 found_on
= search_text(-FOOTER_ROWS(ps_global
), start_row
,
3400 start_index
, &report
,
3401 &whereis_pos
, &found_on_index
);
3403 if(found_on
== -4){ /* search to top of text */
3404 whereis_pos
.row
= 0;
3405 whereis_pos
.col
= 0;
3407 if(sparms
->text
.handles
&& sparms
->srch_handle
)
3410 else if(found_on
== -5){ /* search to bottom of text */
3413 whereis_pos
.row
= MAX(scroll_text_lines() - 1, 0);
3414 whereis_pos
.col
= 0;
3415 found_on
= whereis_pos
.row
;
3416 if((h
= sparms
->text
.handles
) && sparms
->srch_handle
)
3419 while((h
= h
->next
) != NULL
);
3421 else if(found_on
== -3){
3422 whereis_pos
.row
= found_on
= start_row
;
3423 found_on_index
= start_index
- 1;
3424 q_status_message(SM_ORDER
, 1, 3,
3425 _("Current line contains the only match"));
3429 result
= found_on
< cur_top_line
;
3431 key
= (sparms
->text
.handles
)
3432 ? dot_on_handle(found_on
, whereis_pos
.col
) : 0;
3434 if(F_ON(F_FORCE_LOW_SPEED
,ps_global
)
3435 || ps_global
->low_speed
3436 || F_ON(F_SHOW_CURSOR
,ps_global
)
3438 if((found_on
>= cur_top_line
+ num_display_lines
||
3439 found_on
< cur_top_line
) &&
3440 num_display_lines
> ps_global
->viewer_overlap
){
3441 cur_top_line
= found_on
- ((found_on
> 0) ? 1 : 0);
3442 if(scroll_text_lines()-cur_top_line
< 5)
3443 cur_top_line
= MAX(0,
3444 scroll_text_lines()-MIN(5,num_display_lines
));
3446 /* else leave cur_top_line alone */
3449 cur_top_line
= found_on
- ((found_on
> 0) ? 1 : 0);
3450 if(scroll_text_lines()-cur_top_line
< 5)
3451 cur_top_line
= MAX(0,
3452 scroll_text_lines()-MIN(5,num_display_lines
));
3455 whereis_pos
.row
= whereis_pos
.row
- cur_top_line
+ 1;
3457 q_status_message(SM_ORDER
, 0, 3, report
);
3459 q_status_message2(SM_ORDER
, 0, 3,
3460 "%sFound on line %s on screen",
3461 result
? "Search wrapped to start. " : "",
3462 int2string(whereis_pos
.row
));
3465 if(sparms
->text
.handles
->key
< key
)
3466 for(next_handle
= sparms
->text
.handles
->next
;
3467 next_handle
->key
!= key
;
3468 next_handle
= next_handle
->next
)
3471 for(next_handle
= sparms
->text
.handles
;
3472 next_handle
->key
!= key
;
3473 next_handle
= next_handle
->prev
)
3477 else if(found_on
== -1)
3478 cmd_cancelled("Search");
3480 q_status_message(SM_ORDER
, 0, 3, _("Word not found"));
3486 /*-------------- jump command -------------*/
3487 /* NOTE: preempt the process_cmd() version because
3488 * we need to get at the number..
3491 jn
= jump_to(ps_global
->msgmap
, -FOOTER_ROWS(ps_global
), ch
,
3493 if(sparms
&& sparms
->jump_is_debug
)
3495 else if(jn
> 0 && jn
!= mn_get_cur(ps_global
->msgmap
)){
3497 if(mn_total_cur(ps_global
->msgmap
) > 1L)
3498 mn_reset_cur(ps_global
->msgmap
, jn
);
3500 mn_set_cur(ps_global
->msgmap
, jn
);
3505 ps_global
->mangled_footer
= 1;
3511 /*-------------- Mouse Event -------------*/
3518 mouse_get_last (NULL
, &mp
);
3521 /* The clicked line have anything special on it? */
3522 if((line
= cur_top_line
+ mp
.row
) < scroll_text_lines()
3523 && (key
= dot_on_handle(line
, mp
.col
))){
3525 case M_BUTTON_RIGHT
:
3527 if(sparms
->mouse
.popup
){
3528 if(sparms
->text
.handles
->key
< key
)
3529 for(next_handle
= sparms
->text
.handles
->next
;
3530 next_handle
->key
!= key
;
3531 next_handle
= next_handle
->next
)
3534 for(next_handle
= sparms
->text
.handles
;
3535 next_handle
->key
!= key
;
3536 next_handle
= next_handle
->prev
)
3539 if(sparms
->mouse
.popup
){
3540 cur_top_line
= scroll_scroll_text(cur_top_line
,
3542 ps_global
->mangled_body
);
3544 switch((*sparms
->mouse
.popup
)(sparms
, key
)){
3546 cur_top_line
= doubleclick_handle(sparms
, next_handle
, &cmd
, &done
);
3559 case M_BUTTON_LEFT
:
3560 if(sparms
->text
.handles
->key
< key
)
3561 for(next_handle
= sparms
->text
.handles
->next
;
3562 next_handle
->key
!= key
;
3563 next_handle
= next_handle
->next
)
3566 for(next_handle
= sparms
->text
.handles
;
3567 next_handle
->key
!= key
;
3568 next_handle
= next_handle
->prev
)
3571 if(mp
.doubleclick
) /* launch url */
3572 cur_top_line
= doubleclick_handle(sparms
, next_handle
, &cmd
, &done
);
3573 else if(sparms
->mouse
.click
)
3574 (*sparms
->mouse
.click
)(sparms
);
3578 case M_BUTTON_MIDDLE
: /* NO-OP for now */
3581 default: /* just ignore */
3586 else if(mp
.button
== M_BUTTON_RIGHT
){
3588 * Toss generic popup on to the screen
3590 if(sparms
->mouse
.popup
)
3591 if((*sparms
->mouse
.popup
)(sparms
, 0) == 2){
3602 /*-------------- Display Resize -------------*/
3604 if(sparms
->resize_exit
){
3608 * Figure out char offset of the char in the top left
3609 * corner of the display. Pass it back to the
3610 * fetcher/formatter and have it pass the offset
3613 sparms
->start
.on
= Offset
;
3614 for(sparms
->start
.loc
.offset
= line
= 0L;
3615 line
< cur_top_line
;
3617 sparms
->start
.loc
.offset
+= scroll_handle_column(line
, -1);
3623 /* else no reformatting necessary, fall thru to repaint */
3626 /*-------------- refresh -------------*/
3628 num_display_lines
= SCROLL_LINES(ps_global
);
3629 mark_status_dirty();
3630 mark_keymenu_dirty();
3631 mark_titlebar_dirty();
3632 ps_global
->mangled_screen
= 1;
3637 /*------- no op timeout to check for new mail ------*/
3642 /*------- Forward displayed text ------*/
3644 forward_text(ps_global
, sparms
->text
.text
, sparms
->text
.src
);
3648 /*----------- Save the displayed text ------------*/
3650 (void)simple_export(ps_global
, sparms
->text
.text
,
3651 sparms
->text
.src
, "text", NULL
);
3655 /*----------- Exit this screen ------------*/
3661 /*----------- Pop back to the Main Menu ------------*/
3663 ps_global
->next_screen
= main_menu_screen
;
3668 /*----------- Print ------------*/
3670 print_to_printer(sparms
);
3674 /* ------- First handle on Line ------ */
3676 if(sparms
->text
.handles
){
3677 next_handle
= scroll_handle_boundary(sparms
->text
.handles
,
3678 scroll_handle_prev_sel
);
3682 /* fall thru as bogus */
3684 /* ------- Last handle on Line ------ */
3686 if(sparms
->text
.handles
){
3687 next_handle
= scroll_handle_boundary(sparms
->text
.handles
,
3688 scroll_handle_next_sel
);
3692 /* fall thru as bogus */
3694 /*------- BOGUS INPUT ------*/
3698 if(sparms
->bogus_input
)
3699 done
= (*sparms
->bogus_input
)(ch
);
3701 bogus_command(ch
, F_ON(F_USE_FK
,ps_global
) ? "F1" : "?");
3707 bogus_utf8_command(utf8str
, F_ON(F_USE_FK
, ps_global
) ? "F1" : "?");
3711 /*------- Standard commands ------*/
3713 whereis_pos
.row
= 0;
3714 if(sparms
->proc
.tool
)
3715 result
= (*sparms
->proc
.tool
)(cmd
, ps_global
->msgmap
, sparms
);
3717 result
= process_cmd(ps_global
, ps_global
->mail_stream
,
3718 ps_global
->msgmap
, cmd
, View
, &force
);
3720 dprint((7, "PROCESS_CMD return: %d\n", result
));
3722 if(ps_global
->next_screen
!= SCREEN_FUN_NULL
|| result
== 1){
3724 if(cmd
== MC_FULLHDR
){
3725 if(ps_global
->full_header
== 1){
3729 * Figure out char offset of the char in the top left
3730 * corner of the display. Pass it back to the
3731 * fetcher/formatter and have it pass the offset
3734 sparms
->start
.on
= Offset
;
3735 for(sparms
->start
.loc
.offset
= line
= 0L;
3736 line
< cur_top_line
;
3738 sparms
->start
.loc
.offset
+=
3739 scroll_handle_column(line
, -1);
3742 sparms
->start
.on
= 0;
3746 sparms
->keys
.what
= FirstMenu
;
3749 sparms
->keys
.what
= SecondMenu
;
3752 sparms
->keys
.what
= ThirdMenu
;
3755 sparms
->keys
.what
= FourthMenu
;
3760 else if(!scroll_state(SS_CUR
)){
3761 num_display_lines
= SCROLL_LINES(ps_global
);
3762 ps_global
->mangled_screen
= 1;
3767 } /* End of switch() */
3769 /* Need to frame some handles? */
3770 if(sparms
->text
.handles
3772 && handle_on_page(sparms
->text
.handles
, cur_top_line
,
3773 cur_top_line
+ num_display_lines
))
3775 && handle_on_page(next_handle
, cur_top_line
,
3776 cur_top_line
+ num_display_lines
))))
3777 next_handle
= scroll_handle_in_frame(cur_top_line
);
3779 } /* End of while() -- loop executing commands */
3781 ps_global
->redrawer
= NULL
; /* next statement makes this invalid! */
3782 zero_scroll_text(); /* very important to zero out on return!!! */
3783 scroll_state(SS_FREE
);
3784 if(sparms
->bar
.color
)
3785 free_color_pair(&sparms
->bar
.color
);
3788 scroll_setrange(0L, 0L);
3794 /*----------------------------------------------------------------------
3797 Args: text -- The text to print out
3798 source -- What type of source text is
3799 message -- Message for open_printer()
3800 Handling of error conditions is very poor.
3804 print_to_printer(SCROLL_S
*sparms
)
3808 snprintf(message
, sizeof(message
), "%s", STYLE_NAME(sparms
));
3809 message
[sizeof(message
)-1] = '\0';
3811 if(open_printer(message
) != 0)
3814 switch(sparms
->text
.src
){
3816 if(sparms
->text
.text
!= (char *)NULL
)
3817 print_text((char *)sparms
->text
.text
);
3822 if(sparms
->text
.text
!= (char **)NULL
){
3825 for(t
= sparms
->text
.text
; *t
!= NULL
; t
++){
3827 print_text(NEWLINE
);
3834 if(sparms
->text
.text
!= (FILE *)NULL
) {
3838 fseek((FILE *)sparms
->text
.text
, 0L, 0);
3839 n
= SIZEOF_20KBUF
- 1;
3840 while((i
= fread((void *)tmp_20k_buf
, sizeof(char),
3841 n
, (FILE *)sparms
->text
.text
)) != 0) {
3842 tmp_20k_buf
[i
] = '\0';
3843 print_text(tmp_20k_buf
);
3856 /*----------------------------------------------------------------------
3857 Search text being viewed (help or message)
3859 Args: q_line -- The screen line to prompt for search string on
3860 start_line -- Line number in text to begin search on
3861 start_index -- Where to begin search at in first line of text
3862 cursor_pos -- position of cursor is returned to caller here
3863 (Actually, this isn't really the position of the
3864 cursor because we don't know where we are on the
3865 screen. So row is set to the line number and col
3866 is set to the right column.)
3867 offset_in_line -- Offset where match was found.
3869 Result: returns line number string was found on
3872 -3 if only match is at start_index - 1
3873 -4 if search to first line
3874 -5 if search to last line
3877 search_text(int q_line
, long int start_line
, int start_index
, char **report
,
3878 Pos
*cursor_pos
, int *offset_in_line
)
3880 char prompt
[MAX_SEARCH
+50], nsearch_string
[MAX_SEARCH
+1], *p
;
3883 static HISTORY_S
*history
= NULL
;
3884 char search_string
[MAX_SEARCH
+1];
3885 static ESCKEY_S word_search_key
[] = { { 0, 0, "", "" },
3886 {ctrl('Y'), 10, "^Y", N_("First Line")},
3887 {ctrl('V'), 11, "^V", N_("Last Line")},
3888 {KEY_UP
, 30, "", ""},
3889 {KEY_DOWN
, 31, "", ""},
3892 #define KU_ST (3) /* index of KEY_UP */
3894 init_hist(&history
, HISTSIZE
);
3897 * Put the last one used in the default search_string,
3898 * not in nsearch_string.
3900 search_string
[0] = '\0';
3901 if((p
= get_prev_hist(history
, "", 0, NULL
)) != NULL
){
3902 strncpy(search_string
, p
, sizeof(search_string
));
3903 search_string
[sizeof(search_string
)-1] = '\0';
3906 snprintf(prompt
, sizeof(prompt
), _("Word to search for [%s] : "), search_string
);
3908 nsearch_string
[0] = '\0';
3911 flags
= OE_APPEND_CURRENT
| OE_SEQ_SENSITIVE
| OE_KEEP_TRAILING_SPACE
;
3914 * 2 is really 1 because there will be one real entry and
3915 * one entry of "" because of the get_prev_hist above.
3917 if(items_in_hist(history
) > 2){
3918 word_search_key
[KU_ST
].name
= HISTORY_UP_KEYNAME
;
3919 word_search_key
[KU_ST
].label
= HISTORY_KEYLABEL
;
3920 word_search_key
[KU_ST
+1].name
= HISTORY_DOWN_KEYNAME
;
3921 word_search_key
[KU_ST
+1].label
= HISTORY_KEYLABEL
;
3924 word_search_key
[KU_ST
].name
= "";
3925 word_search_key
[KU_ST
].label
= "";
3926 word_search_key
[KU_ST
+1].name
= "";
3927 word_search_key
[KU_ST
+1].label
= "";
3930 rc
= optionally_enter(nsearch_string
, q_line
, 0, sizeof(nsearch_string
),
3931 prompt
, word_search_key
, help
, &flags
);
3934 help
= help
== NO_HELP
? h_oe_searchview
: NO_HELP
;
3939 *report
= _("Searched to First Line.");
3945 *report
= _("Searched to Last Line.");
3950 if((p
= get_prev_hist(history
, nsearch_string
, 0, NULL
)) != NULL
){
3951 strncpy(nsearch_string
, p
, sizeof(nsearch_string
));
3952 nsearch_string
[sizeof(nsearch_string
)-1] = '\0';
3960 if((p
= get_next_hist(history
, nsearch_string
, 0, NULL
)) != NULL
){
3961 strncpy(nsearch_string
, p
, sizeof(nsearch_string
));
3962 nsearch_string
[sizeof(nsearch_string
)-1] = '\0';
3970 if(rc
!= 4){ /* 4 is redraw */
3971 save_hist(history
, nsearch_string
, 0, NULL
);
3976 if(rc
== 1 || (search_string
[0] == '\0' && nsearch_string
[0] == '\0'))
3979 if(nsearch_string
[0] != '\0'){
3980 strncpy(search_string
, nsearch_string
, sizeof(search_string
)-1);
3981 search_string
[sizeof(search_string
)-1] = '\0';
3984 rc
= search_scroll_text(start_line
, start_index
, search_string
, cursor_pos
,
3990 /*----------------------------------------------------------------------
3991 Update the scroll tool's titlebar
3993 Args: cur_top_line --
3994 redraw -- flag to force updating
3998 update_scroll_titlebar(long int cur_top_line
, int redraw
)
4000 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
4001 int num_display_lines
= SCROLL_LINES(ps_global
);
4002 long new_line
= (cur_top_line
+ num_display_lines
> st
->num_lines
)
4004 : cur_top_line
+ num_display_lines
;
4006 COLOR_PAIR
*returned_color
= NULL
;
4007 COLOR_PAIR
*titlecolor
= NULL
;
4009 SEARCHSET
*ss
= NULL
;
4011 if(st
->parms
->use_indexline_color
4012 && ps_global
->titlebar_color_style
!= TBAR_COLOR_DEFAULT
){
4013 raw_msgno
= mn_m2raw(ps_global
->msgmap
, mn_get_cur(ps_global
->msgmap
));
4014 if(raw_msgno
> 0L && ps_global
->mail_stream
4015 && raw_msgno
<= ps_global
->mail_stream
->nmsgs
){
4016 ss
= mail_newsearchset();
4017 ss
->first
= ss
->last
= (unsigned long) raw_msgno
;
4021 PAT_STATE
*pstate
= NULL
;
4023 colormatch
= get_index_line_color(ps_global
->mail_stream
,
4024 ss
, &pstate
, &returned_color
);
4025 mail_free_searchset(&ss
);
4028 * This is a bit tricky. If there is a colormatch but returned_color
4029 * is NULL, that means that the user explicitly wanted the
4030 * Normal color used in this index line, so that is what we
4031 * use. If no colormatch then we will use the TITLE color
4032 * instead of Normal.
4036 titlecolor
= returned_color
;
4038 titlecolor
= new_color_pair(ps_global
->VAR_NORM_FORE_COLOR
,
4039 ps_global
->VAR_NORM_BACK_COLOR
);
4043 && ps_global
->titlebar_color_style
== TBAR_COLOR_REV_INDEXLINE
){
4044 char cbuf
[MAXCOLORLEN
+1];
4046 strncpy(cbuf
, titlecolor
->fg
, MAXCOLORLEN
);
4047 strncpy(titlecolor
->fg
, titlecolor
->bg
, MAXCOLORLEN
);
4048 strncpy(titlecolor
->bg
, cbuf
, MAXCOLORLEN
);
4052 /* Did the color change? */
4053 if((!titlecolor
&& st
->parms
->bar
.color
)
4055 (titlecolor
&& !st
->parms
->bar
.color
)
4057 (titlecolor
&& st
->parms
->bar
.color
4058 && (strcmp(titlecolor
->fg
, st
->parms
->bar
.color
->fg
)
4059 || strcmp(titlecolor
->bg
, st
->parms
->bar
.color
->bg
)))){
4062 if(st
->parms
->bar
.color
)
4063 free_color_pair(&st
->parms
->bar
.color
);
4065 st
->parms
->bar
.color
= titlecolor
;
4070 free_color_pair(&titlecolor
);
4075 set_titlebar(st
->parms
->bar
.title
, ps_global
->mail_stream
,
4076 ps_global
->context_current
, ps_global
->cur_folder
,
4077 ps_global
->msgmap
, 1, st
->parms
->bar
.style
,
4078 new_line
, st
->num_lines
, st
->parms
->bar
.color
);
4079 ps_global
->mangled_header
= 0;
4081 else if(st
->parms
->bar
.style
== TextPercent
)
4082 update_titlebar_lpercent(new_line
);
4084 update_titlebar_percent(new_line
);
4088 /*----------------------------------------------------------------------
4089 manager of global (to this module, anyway) scroll state structures
4094 scroll_state(int func
)
4096 struct scrollstack
{
4098 struct scrollstack
*prev
;
4100 static struct scrollstack
*stack
= NULL
;
4103 case SS_CUR
: /* no op */
4106 s
= (struct scrollstack
*)fs_get(sizeof(struct scrollstack
));
4107 memset((void *)s
, 0, sizeof(struct scrollstack
));
4114 fs_give((void **)&stack
);
4118 default: /* BUG: should complain */
4122 return(stack
? &stack
->s
: NULL
);
4126 /*----------------------------------------------------------------------
4127 Save all the data for scrolling text and paint the screen
4132 set_scroll_text(SCROLL_S
*sparms
, long int current_line
, SCRLCTRL_S
*st
)
4134 /* save all the stuff for possible asynchronous redraws */
4136 st
->top_text_line
= current_line
;
4137 st
->screen
.start_line
= SCROLL_LINES_ABOVE(ps_global
);
4138 st
->screen
.other_lines
= SCROLL_LINES_ABOVE(ps_global
)
4139 + SCROLL_LINES_BELOW(ps_global
);
4140 st
->screen
.width
= -1; /* Force text formatting calculation */
4144 /*----------------------------------------------------------------------
4145 Redraw the text on the screen, possibly reformatting if necessary
4151 redraw_scroll_text(void)
4154 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
4156 format_scroll_text();
4158 offset
= (st
->parms
->text
.src
== FileStar
) ? 0 : st
->top_text_line
;
4161 mswin_beginupdate();
4163 /*---- Actually display the text on the screen ------*/
4164 for(i
= 0; i
< st
->screen
.length
; i
++){
4165 ClearLine(i
+ st
->screen
.start_line
);
4166 if((offset
+ i
) < st
->num_lines
)
4167 PutLine0n8b(i
+ st
->screen
.start_line
, 0, st
->text_lines
[offset
+ i
],
4168 st
->line_lengths
[offset
+ i
], st
->parms
->text
.handles
);
4179 /*----------------------------------------------------------------------
4180 Free memory used as scrolling buffers for text on disk. Also mark
4181 text_lines as available
4184 zero_scroll_text(void)
4186 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
4189 for(i
= 0; i
< st
->lines_allocated
; i
++)
4190 if(st
->parms
->text
.src
== FileStar
&& st
->text_lines
[i
])
4191 fs_give((void **)&st
->text_lines
[i
]);
4193 st
->text_lines
[i
] = NULL
;
4195 if(st
->parms
->text
.src
== FileStar
&& st
->findex
!= NULL
){
4199 our_unlink(st
->fname
);
4200 fs_give((void **)&st
->fname
);
4205 fs_give((void **)&st
->text_lines
);
4207 if(st
->line_lengths
)
4208 fs_give((void **) &st
->line_lengths
);
4212 /*----------------------------------------------------------------------
4214 Always format at least 20 chars wide. Wrapping lines would be crazy for
4215 screen widths of 1-20 characters
4218 format_scroll_text(void)
4222 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
4224 register char **tl
, **tl_end
;
4226 if(!st
|| (st
->screen
.width
== (i
= ps_global
->ttyo
->screen_cols
)
4227 && st
->screen
.length
== PGSIZE(st
)))
4230 st
->screen
.width
= MAX(20, i
);
4231 st
->screen
.length
= PGSIZE(st
);
4233 if(st
->lines_allocated
== 0) {
4234 st
->lines_allocated
= TYPICAL_BIG_MESSAGE_LINES
;
4235 st
->text_lines
= (char **)fs_get(st
->lines_allocated
*sizeof(char *));
4236 memset(st
->text_lines
, 0, st
->lines_allocated
* sizeof(char *));
4237 st
->line_lengths
= (short *)fs_get(st
->lines_allocated
*sizeof(short));
4240 tl
= st
->text_lines
;
4241 ll
= st
->line_lengths
;
4242 tl_end
= &st
->text_lines
[st
->lines_allocated
];
4244 if(st
->parms
->text
.src
== CharStarStar
) {
4245 /*---- original text is already list of lines -----*/
4246 /* The text could be wrapped nicely for narrow screens; for now
4247 it will get truncated as it is displayed */
4248 for(pp
= (char **)st
->parms
->text
.text
; *pp
!= NULL
;) {
4250 *ll
++ = st
->screen
.width
;
4252 i
= tl
- st
->text_lines
;
4253 st
->lines_allocated
*= 2;
4254 fs_resize((void **)&st
->text_lines
,
4255 st
->lines_allocated
* sizeof(char *));
4256 fs_resize((void **)&st
->line_lengths
,
4257 st
->lines_allocated
*sizeof(short));
4258 tl
= &st
->text_lines
[i
];
4259 ll
= &st
->line_lengths
[i
];
4260 tl_end
= &st
->text_lines
[st
->lines_allocated
];
4264 st
->num_lines
= tl
- st
->text_lines
;
4266 else if (st
->parms
->text
.src
== CharStar
) {
4267 /*------ Format the plain text ------*/
4268 for(p
= (char *)st
->parms
->text
.text
; *p
; ) {
4271 for(; *p
&& !(*p
== RETURN
|| *p
== LINE_FEED
); p
++)
4277 i
= tl
- st
->text_lines
;
4278 st
->lines_allocated
*= 2;
4279 fs_resize((void **)&st
->text_lines
,
4280 st
->lines_allocated
* sizeof(char *));
4281 fs_resize((void **)&st
->line_lengths
,
4282 st
->lines_allocated
*sizeof(short));
4283 tl
= &st
->text_lines
[i
];
4284 ll
= &st
->line_lengths
[i
];
4285 tl_end
= &st
->text_lines
[st
->lines_allocated
];
4288 if(*p
== '\r' && *(p
+1) == '\n')
4290 else if(*p
== '\n' || *p
== '\r')
4294 st
->num_lines
= tl
- st
->text_lines
;
4297 /*------ Display text is in a file --------*/
4300 * This is pretty much only useful under DOS where we can't fit
4301 * all of big messages in core at once. This scheme makes
4302 * some simplifying assumptions:
4303 * 1. Lines are on disk just the way we'll display them. That
4304 * is, line breaks and such are left to the function that
4305 * writes the disk file to catch and fix.
4306 * 2. We get away with this mainly because the DOS display isn't
4307 * going to be resized out from under us.
4309 * The idea is to use the already alloc'd array of char * as a
4310 * buffer for sections of what's on disk. We'll set up the first
4311 * few lines here, and read new ones in as needed in
4312 * scroll_scroll_text().
4314 * but first, make sure there are enough buffer lines allocated
4315 * to serve as a place to hold lines from the file.
4317 * Actually, this is also used under windows so the display will
4318 * be resized out from under us. So I changed the following
4320 * 1. free old text_lines, which may have been allocated
4321 * for a narrow screen.
4322 * 2. insure we have enough text_lines
4323 * 3. reallocate all text_lines that are needed.
4324 * (tom unger 10/26/94)
4327 /* free old text lines, which may be too short. */
4328 for(i
= 0; i
< st
->lines_allocated
; i
++)
4329 if(st
->text_lines
[i
]) /* clear alloc'd lines */
4330 fs_give((void **)&st
->text_lines
[i
]);
4332 /* Insure we have enough text lines. */
4333 if(st
->lines_allocated
< (2 * PGSIZE(st
)) + 1){
4334 st
->lines_allocated
= (2 * PGSIZE(st
)) + 1; /* resize */
4336 fs_resize((void **)&st
->text_lines
,
4337 st
->lines_allocated
* sizeof(char *));
4338 memset(st
->text_lines
, 0, st
->lines_allocated
* sizeof(char *));
4339 fs_resize((void **)&st
->line_lengths
,
4340 st
->lines_allocated
*sizeof(short));
4343 /* reallocate all text lines that are needed. */
4344 for(i
= 0; i
<= PGSIZE(st
); i
++)
4345 if(st
->text_lines
[i
] == NULL
)
4346 st
->text_lines
[i
] = (char *)fs_get((st
->screen
.width
+ 1)
4349 tl
= &st
->text_lines
[i
];
4351 st
->num_lines
= make_file_index();
4353 ScrollFile(st
->top_text_line
); /* then load them up */
4357 * Efficiency hack. If there are handles, fill in their
4358 * line number field for later...
4360 if(st
->parms
->text
.handles
){
4365 for(line
= 0; line
< st
->num_lines
; line
++)
4366 for(i
= 0, col
= 0; i
< st
->line_lengths
[line
];)
4367 switch(st
->text_lines
[line
][i
]){
4370 switch((i
< st
->line_lengths
[line
]) ? st
->text_lines
[line
][i
]
4373 for(key
= 0, n
= st
->text_lines
[line
][++i
]; n
> 0; n
--)
4374 key
= (key
* 10) + (st
->text_lines
[line
][++i
] - '0');
4377 for(h
= st
->parms
->text
.handles
; h
; h
= h
->next
)
4379 scroll_handle_set_loc(&h
->loc
, line
, col
);
4383 if(!h
) /* anything behind us? */
4384 for(h
= st
->parms
->text
.handles
->prev
; h
; h
= h
->prev
)
4386 scroll_handle_set_loc(&h
->loc
, line
, col
);
4394 i
+= (RGBLEN
+ 1); /* 1 for TAG, RGBLEN for color */
4406 default: /* literal embed char */
4414 while(((++col
) & 0x07) != 0) /* add tab's spaces */
4420 col
+= width_at_this_position((unsigned char*) &st
->text_lines
[line
][i
],
4421 st
->line_lengths
[line
] - i
);
4422 i
++; /* character count */
4428 scroll_setrange (st
->screen
.length
, st
->num_lines
);
4436 * ScrollFile - scroll text into the st struct file making sure 'line'
4437 * of the file is the one first in the text_lines buffer.
4439 * NOTE: talk about massive potential for tuning...
4440 * Goes without saying this is still under construction
4443 ScrollFile(long int line
)
4445 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
4449 if(line
<= 0){ /* reset and load first couple of pages */
4450 fseek((FILE *) st
->parms
->text
.text
, 0L, 0);
4457 for(i
= 0; i
< PGSIZE(st
); i
++){
4458 /*** do stuff to get the file pointer into the right place ***/
4460 * BOGUS: this is painfully crude right now, but I just want to get
4463 * possibly in the near future, an array of indexes into the
4464 * file that are the offset for the beginning of each line will
4465 * speed things up. Of course, this
4466 * will have limits, so maybe a disk file that is an array
4467 * of indexes is the answer.
4469 if(fseek(st
->findex
, (size_t)(line
++) * sizeof(SCRLFILE_S
), 0) < 0
4470 || fread(&sf
, sizeof(SCRLFILE_S
), (size_t)1, st
->findex
) != 1
4471 || fseek((FILE *) st
->parms
->text
.text
, sf
.offset
, 0) < 0
4472 || !st
->text_lines
[i
]
4473 || (sf
.len
&& !fgets(st
->text_lines
[i
], sf
.len
+ 1,
4474 (FILE *) st
->parms
->text
.text
)))
4477 st
->line_lengths
[i
] = sf
.len
;
4480 for(; i
< PGSIZE(st
); i
++)
4481 if(st
->text_lines
[i
]){ /* blank out any unused lines */
4482 *st
->text_lines
[i
] = '\0';
4483 st
->line_lengths
[i
] = 0;
4489 * make_file_index - do a single pass over the file containing the text
4490 * to display, recording line lengths and offsets.
4491 * NOTE: This is never really to be used on a real OS with virtual
4492 * memory. This is the whole reason st->findex exists. Don't
4493 * want to waste precious memory on a stupid array that could
4497 make_file_index(void)
4499 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
4506 st
->fname
= temp_nam(NULL
, "pi");
4508 if(!st
->fname
|| (st
->findex
= our_fopen(st
->fname
,"w+b")) == NULL
){
4510 our_unlink(st
->fname
);
4511 fs_give((void **)&st
->fname
);
4518 fseek(st
->findex
, 0L, 0);
4520 fseek((FILE *)st
->parms
->text
.text
, 0L, 0);
4523 sf
.len
= st
->screen
.width
+ 1;
4524 if(scroll_file_line((FILE *) st
->parms
->text
.text
,
4525 tmp_20k_buf
, &sf
, &state
)){
4526 fwrite((void *) &sf
, sizeof(SCRLFILE_S
), (size_t)1, st
->findex
);
4533 fseek((FILE *)st
->parms
->text
.text
, 0L, 0);
4539 /*----------------------------------------------------------------------
4540 Get the next line to scroll from the given file
4544 scroll_file_line(FILE *fp
, char *buf
, SCRLFILE_S
*sfp
, int *wrapt
)
4546 register char *s
= NULL
;
4550 sfp
->offset
= ftell(fp
);
4551 if(!(s
= fgets(buf
, sfp
->len
, fp
)))
4552 return(NULL
); /* can't grab a line? */
4556 *wrapt
= 1; /* remember; that we wrapped */
4559 else if(*s
== NEWLINE
[0] && (!NEWLINE
[1] || *(s
+1) == NEWLINE
[1])){
4560 int empty
= (*wrapt
&& s
== buf
);
4562 *wrapt
= 0; /* turn off wrapped state */
4564 s
= NULL
; /* get a new line */
4577 /*----------------------------------------------------------------------
4578 Scroll the text on the screen
4580 Args: new_top_line -- The line to be displayed on top of the screen
4581 redraw -- Flag to force a redraw even if nothing changed
4583 Returns: resulting top line
4584 Note: the returned line number may be less than new_top_line if
4585 reformatting caused the total line count to change.
4589 scroll_scroll_text(long int new_top_line
, HANDLE_S
*handle
, int redraw
)
4591 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
4592 int num_display_lines
, l
, top
;
4593 POSLIST_S
*lp
, *lp2
;
4595 /* When this is true, we're still on the same page of the display. */
4596 if(st
->top_text_line
== new_top_line
&& !redraw
){
4597 /* handle changed, so hilite the new handle and unhilite the old */
4598 if(handle
&& handle
!= st
->parms
->text
.handles
){
4599 top
= st
->screen
.start_line
- new_top_line
;
4600 /* hilite the new one */
4601 if(!scroll_handle_obscured(handle
))
4602 for(lp
= handle
->loc
; lp
; lp
= lp
->next
)
4603 if((l
= lp
->where
.row
) >= st
->top_text_line
4604 && l
< st
->top_text_line
+ st
->screen
.length
){
4605 if(st
->parms
->text
.src
== FileStar
)
4608 if(F_ON(F_ENABLE_DEL_WHEN_WRITING
, ps_global
))
4609 ClearLine(top
+ lp
->where
.row
);
4610 PutLine0n8b(top
+ lp
->where
.row
, 0, st
->text_lines
[l
],
4611 st
->line_lengths
[l
], handle
);
4614 /* unhilite the old one */
4615 if(!scroll_handle_obscured(st
->parms
->text
.handles
))
4616 for(lp
= st
->parms
->text
.handles
->loc
; lp
; lp
= lp
->next
)
4617 if((l
= lp
->where
.row
) >= st
->top_text_line
4618 && l
< st
->top_text_line
+ st
->screen
.length
){
4619 for(lp2
= handle
->loc
; lp2
; lp2
= lp2
->next
)
4620 if(l
== lp2
->where
.row
)
4624 if(st
->parms
->text
.src
== FileStar
)
4627 if(F_ON(F_ENABLE_DEL_WHEN_WRITING
, ps_global
))
4628 ClearLine(top
+ lp
->where
.row
);
4629 PutLine0n8b(top
+ lp
->where
.row
, 0, st
->text_lines
[l
],
4630 st
->line_lengths
[l
], handle
);
4634 st
->parms
->text
.handles
= handle
; /* update current */
4637 return(new_top_line
);
4640 num_display_lines
= PGSIZE(st
);
4642 format_scroll_text();
4644 if(st
->top_text_line
>= st
->num_lines
) /* don't pop line count */
4645 new_top_line
= st
->top_text_line
= MAX(st
->num_lines
- 1, 0);
4647 if(st
->parms
->text
.src
== FileStar
)
4648 ScrollFile(new_top_line
); /* set up new st->text_lines */
4651 scroll_setrange (st
->screen
.length
, st
->num_lines
);
4652 scroll_setpos (new_top_line
);
4656 Check out the scrolling situation. If we want to scroll, but BeginScroll
4657 says we can't then repaint, + 10 is so we repaint most of the time.
4660 (st
->top_text_line
- new_top_line
+ 10 >= num_display_lines
||
4661 new_top_line
- st
->top_text_line
+ 10 >= num_display_lines
) ||
4662 BeginScroll(st
->screen
.start_line
,
4663 st
->screen
.start_line
+ num_display_lines
- 1) != 0) {
4664 /* Too much text to scroll, or can't scroll -- just repaint */
4667 st
->parms
->text
.handles
= handle
;
4669 st
->top_text_line
= new_top_line
;
4670 redraw_scroll_text();
4674 * We're going to scroll the screen, but first we have to make sure
4675 * the old hilited handles are unhilited if they are going to remain
4678 top
= st
->screen
.start_line
- st
->top_text_line
;
4679 if(handle
&& handle
!= st
->parms
->text
.handles
4680 && st
->parms
->text
.handles
4681 && !scroll_handle_obscured(st
->parms
->text
.handles
))
4682 for(lp
= st
->parms
->text
.handles
->loc
; lp
; lp
= lp
->next
)
4683 if((l
= lp
->where
.row
) >= MAX(st
->top_text_line
,new_top_line
)
4684 && l
< MIN(st
->top_text_line
,new_top_line
) + st
->screen
.length
){
4685 if(st
->parms
->text
.src
== FileStar
)
4688 if(F_ON(F_ENABLE_DEL_WHEN_WRITING
, ps_global
))
4689 ClearLine(top
+ lp
->where
.row
);
4690 PutLine0n8b(top
+ lp
->where
.row
, 0, st
->text_lines
[l
],
4691 st
->line_lengths
[l
], handle
);
4694 if(new_top_line
> st
->top_text_line
){
4695 /*------ scroll down ------*/
4696 while(new_top_line
> st
->top_text_line
) {
4699 l
= (st
->parms
->text
.src
== FileStar
)
4700 ? num_display_lines
- (new_top_line
- st
->top_text_line
)
4701 : st
->top_text_line
+ num_display_lines
;
4703 if(l
< st
->num_lines
){
4704 if(F_ON(F_ENABLE_DEL_WHEN_WRITING
, ps_global
))
4705 ClearLine(st
->screen
.start_line
+ num_display_lines
- 1);
4706 PutLine0n8b(st
->screen
.start_line
+ num_display_lines
- 1,
4707 0, st
->text_lines
[l
], st
->line_lengths
[l
],
4708 handle
? handle
: st
->parms
->text
.handles
);
4710 * We clear to the end of line in the right background
4711 * color. If the line was exactly the width of the screen
4712 * then PutLine0n8b will have left _col and _row moved to
4713 * the start of the next row. We don't need or want to clear
4716 if(pico_usingcolor()
4717 && (st
->line_lengths
[l
] < ps_global
->ttyo
->screen_cols
4718 || visible_linelen(l
) < ps_global
->ttyo
->screen_cols
))
4722 st
->top_text_line
++;
4726 /*------ scroll up -----*/
4727 while(new_top_line
< st
->top_text_line
) {
4730 st
->top_text_line
--;
4731 l
= (st
->parms
->text
.src
== FileStar
)
4732 ? st
->top_text_line
- new_top_line
4733 : st
->top_text_line
;
4734 if(F_ON(F_ENABLE_DEL_WHEN_WRITING
, ps_global
))
4735 ClearLine(st
->screen
.start_line
);
4736 PutLine0n8b(st
->screen
.start_line
, 0, st
->text_lines
[l
],
4737 st
->line_lengths
[l
],
4738 handle
? handle
: st
->parms
->text
.handles
);
4740 * We clear to the end of line in the right background
4741 * color. If the line was exactly the width of the screen
4742 * then PutLine0n8b will have left _col and _row moved to
4743 * the start of the next row. We don't need or want to clear
4746 if(pico_usingcolor()
4747 && (st
->line_lengths
[l
] < ps_global
->ttyo
->screen_cols
4748 || visible_linelen(l
) < ps_global
->ttyo
->screen_cols
))
4755 if(handle
&& handle
!= st
->parms
->text
.handles
){
4758 for(lp
= handle
->loc
; lp
; lp
= lp
->next
)
4759 if(lp
->where
.row
>= st
->top_text_line
4760 && lp
->where
.row
< st
->top_text_line
+ st
->screen
.length
){
4761 if(F_ON(F_ENABLE_DEL_WHEN_WRITING
, ps_global
))
4762 ClearLine(st
->screen
.start_line
+ (lp
->where
.row
- st
->top_text_line
));
4763 PutLine0n8b(st
->screen
.start_line
4764 + (lp
->where
.row
- st
->top_text_line
),
4765 0, st
->text_lines
[lp
->where
.row
],
4766 st
->line_lengths
[lp
->where
.row
],
4771 st
->parms
->text
.handles
= handle
;
4777 return(new_top_line
);
4781 /*---------------------------------------------------------------------
4782 Edit individual char in text so that the entire text doesn't need
4783 to be completely reformatted.
4785 Returns 0 if there were no errors, 1 if we would like the entire
4786 text to be reformatted.
4789 ng_scroll_edit(CONTEXT_S
*context
, int index
)
4791 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
4792 char *ngp
, tmp
[MAILTMPLEN
+10];
4796 if (!(f
= folder_entry(index
, FOLDERS(context
))))
4799 return 0; /* nothing in scroll needs to be changed */
4800 tmp
[0] = TAG_HANDLE
;
4801 snprintf(tmp
+2, sizeof(tmp
)-2, "%d", st
->parms
->text
.handles
->key
);
4802 tmp
[sizeof(tmp
)-1] = '\0';
4803 tmp
[1] = len
= strlen(tmp
+2);
4804 snprintf(tmp
+len
+2, sizeof(tmp
)-(len
+2), "%s ", f
->selected
? "[ ]" : "[X]");
4805 tmp
[sizeof(tmp
)-1] = '\0';
4806 snprintf(tmp
+len
+6, sizeof(tmp
)-(len
+6), "%.*s", MAILTMPLEN
, f
->name
);
4807 tmp
[sizeof(tmp
)-1] = '\0';
4809 ngp
= *(st
->text_lines
);
4811 ngp
= strstr(ngp
, tmp
);
4816 /* assumption that text is of form "[ ] xxx.xxx" */
4823 else if (*ngp
== ' '){
4832 /*---------------------------------------------------------------------
4833 Similar to ng_scroll_edit, but this is the more general case of
4834 selecting a folder, as opposed to selecting a newsgroup for
4835 subscription while in listmode.
4837 Returns 0 if there were no errors, 1 if we would like the entire
4838 text to be reformatted.
4841 folder_select_update(CONTEXT_S
*context
, int index
)
4843 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
4845 char *ngp
, tmp
[MAILTMPLEN
+10];
4846 int len
, total
, fnum
, num_sel
= 0;
4848 if (!(f
= folder_entry(index
, FOLDERS(context
))))
4850 ngp
= *(st
->text_lines
);
4852 total
= folder_total(FOLDERS(context
));
4854 for (fnum
= 0; num_sel
< 2 && fnum
< total
; fnum
++)
4855 if(folder_entry(fnum
, FOLDERS(context
))->selected
)
4857 if(!num_sel
|| (f
->selected
&& num_sel
== 1))
4858 return 1; /* need to reformat the whole thing */
4860 tmp
[0] = TAG_HANDLE
;
4861 snprintf(tmp
+2, sizeof(tmp
)-2, "%d", st
->parms
->text
.handles
->key
);
4862 tmp
[sizeof(tmp
)-1] = '\0';
4863 tmp
[1] = len
= strlen(tmp
+2);
4865 ngp
= strstr(ngp
, tmp
);
4868 if(F_ON(F_SELECTED_SHOWN_BOLD
, ps_global
)){
4870 while(*ngp
&& ngp
[0] != TAG_EMBED
4871 && ngp
[1] != (f
->selected
? TAG_BOLDOFF
: TAG_BOLDON
)
4872 && *ngp
!= *(f
->name
))
4875 if (!(*ngp
) || (*ngp
== *(f
->name
)))
4879 *ngp
= (f
->selected
? TAG_BOLDON
: TAG_BOLDOFF
);
4884 while(*ngp
!= ' ' && *ngp
!= *(f
->name
) && *ngp
)
4886 if(!(*ngp
) || (*ngp
== *(f
->name
)))
4890 *ngp
= f
->selected
? 'X' : ' ';
4897 /*---------------------------------------------------------------------
4898 We gotta go through all of the formatted text and add "[ ] " in the right
4899 place. If we don't do this, we must completely reformat the whole text,
4900 which could take a very long time.
4902 Return 1 if we encountered some sort of error and we want to reformat the
4903 whole text, return 0 if everything went as planned.
4905 ASSUMPTION: for this to work, we assume that there are only total
4906 number of handles, numbered 1 through total.
4909 scroll_add_listmode(CONTEXT_S
*context
, int total
)
4911 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
4913 char *ngp
, *ngname
, handle_str
[MAILTMPLEN
];
4917 ngp
= *(st
->text_lines
);
4918 h
= st
->parms
->text
.handles
;
4920 while(h
&& h
->key
!= 1 && h
->prev
)
4923 handle_str
[0] = TAG_EMBED
;
4924 handle_str
[1] = TAG_HANDLE
;
4925 for(i
= 1; i
<= total
&& h
; i
++, h
= h
->next
){
4926 snprintf(handle_str
+3, sizeof(handle_str
)-3, "%d", h
->key
);
4927 handle_str
[sizeof(handle_str
)-1] = '\0';
4928 handle_str
[2] = strlen(handle_str
+3);
4929 ngp
= strstr(ngp
, handle_str
);
4931 ngp
= *(st
->text_lines
);
4935 ngname
= ngp
+ strlen(handle_str
);
4936 while (strncmp(ngp
, " ", 4) && !(*ngp
== '\n')
4937 && !(ngp
== *(st
->text_lines
)))
4939 if (strncmp(ngp
, " ", 4))
4941 while(ngp
+4 != ngname
&& *ngp
){
4946 if(folder_entry(h
->h
.f
.index
, FOLDERS(context
))->subscribed
){
4964 /*----------------------------------------------------------------------
4965 Search the set scrolling text
4967 Args: start_line -- line to start searching on
4968 start_index -- column to start searching at in first line
4969 word -- string to search for
4970 cursor_pos -- position of cursor is returned to caller here
4971 (Actually, this isn't really the position of the
4972 cursor because we don't know where we are on the
4973 screen. So row is set to the line number and col
4974 is set to the right column.)
4975 offset_in_line -- Offset where match was found.
4977 Returns: the line the word was found on, or -2 if it wasn't found, or
4978 -3 if the only match is at column start_index - 1.
4982 search_scroll_text(long int start_line
, int start_index
, char *word
,
4983 Pos
*cursor_pos
, int *offset_in_line
)
4985 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
4987 long l
, offset
, dlines
;
4988 #define SROW(N) ((N) - offset)
4989 #define SLINE(N) st->text_lines[SROW(N)]
4990 #define SLEN(N) st->line_lengths[SROW(N)]
4992 dlines
= PGSIZE(st
);
4993 offset
= (st
->parms
->text
.src
== FileStar
) ? st
->top_text_line
: 0;
4995 if(start_line
< st
->num_lines
){
4996 /* search first line starting at position start_index in */
4997 if((wh
= search_scroll_line(SLINE(start_line
) + start_index
,
4999 SLEN(start_line
) - start_index
,
5000 st
->parms
->text
.handles
!= NULL
)) != NULL
){
5001 cursor_pos
->row
= start_line
;
5002 cursor_pos
->col
= scroll_handle_column(SROW(start_line
),
5003 *offset_in_line
= wh
- SLINE(start_line
));
5007 if(st
->parms
->text
.src
== FileStar
)
5010 for(l
= start_line
+ 1; l
< st
->num_lines
; l
++) {
5011 if(st
->parms
->text
.src
== FileStar
&& l
> offset
+ dlines
)
5012 ScrollFile(offset
+= dlines
);
5014 if((wh
= search_scroll_line(SLINE(l
), word
, SLEN(l
),
5015 st
->parms
->text
.handles
!= NULL
)) != NULL
){
5016 cursor_pos
->row
= l
;
5017 cursor_pos
->col
= scroll_handle_column(SROW(l
),
5018 *offset_in_line
= wh
- SLINE(l
));
5024 start_line
= st
->num_lines
;
5026 if(st
->parms
->text
.src
== FileStar
) /* wrap offset */
5027 ScrollFile(offset
= 0);
5029 for(l
= 0; l
< start_line
; l
++) {
5030 if(st
->parms
->text
.src
== FileStar
&& l
> offset
+ dlines
)
5031 ScrollFile(offset
+= dlines
);
5033 if((wh
= search_scroll_line(SLINE(l
), word
, SLEN(l
),
5034 st
->parms
->text
.handles
!= NULL
)) != NULL
){
5035 cursor_pos
->row
= l
;
5036 cursor_pos
->col
= scroll_handle_column(SROW(l
),
5037 *offset_in_line
= wh
- SLINE(l
));
5042 /* search in current line */
5043 if(start_line
< st
->num_lines
5044 && (wh
= search_scroll_line(SLINE(start_line
), word
,
5045 start_index
+ strlen(word
) - 2,
5046 st
->parms
->text
.handles
!= NULL
)) != NULL
){
5047 cursor_pos
->row
= start_line
;
5048 cursor_pos
->col
= scroll_handle_column(SROW(start_line
),
5049 *offset_in_line
= wh
- SLINE(start_line
));
5054 /* see if the only match is a repeat */
5055 if(start_index
> 0 && start_line
< st
->num_lines
5056 && (wh
= search_scroll_line(
5057 SLINE(start_line
) + start_index
- 1,
5059 st
->parms
->text
.handles
!= NULL
)) != NULL
){
5060 cursor_pos
->row
= start_line
;
5061 cursor_pos
->col
= scroll_handle_column(SROW(start_line
),
5062 *offset_in_line
= wh
- SLINE(start_line
));
5070 /*----------------------------------------------------------------------
5071 Search one line of scroll text for given string
5073 Args: haystack -- The string to search in, the larger string
5074 needle -- The string to search for, the smaller string
5075 n -- The max number of chars in haystack to search
5077 Search for first occurrence of needle in the haystack, and return a pointer
5078 into the string haystack when it is found. The search is case independent.
5081 search_scroll_line(char *haystack
, char *needle
, int n
, int handles
)
5083 char *return_ptr
= NULL
, *found_it
= NULL
;
5084 char *haystack_copy
, *p
, *free_this
= NULL
, *end
;
5086 int state
= 0, i
= 0;
5088 if(n
> 0 && haystack
){
5090 haystack_copy
= buf
;
5092 haystack_copy
= free_this
= (char *) fs_get((n
+1) * sizeof(char));
5094 strncpy(haystack_copy
, haystack
, n
);
5095 haystack_copy
[n
] = '\0';
5098 * We don't want to match text inside embedded tags.
5099 * Replace embedded octets with nulls and convert
5100 * uppercase ascii to lowercase. We should also do
5101 * some sort of canonicalization of UTF-8 but that
5104 for(i
= n
, p
= haystack_copy
; i
-- > 0 && *p
; p
++){
5108 if(*p
== TAG_EMBED
){
5114 /* lower case just ascii chars */
5115 if(!(*p
& 0x80) && isupper(*p
))
5122 state
= (*p
== TAG_HANDLE
)
5124 : (*p
== TAG_FGCOLOR
|| *p
== TAG_BGCOLOR
) ? RGBLEN
: 0;
5129 state
= *p
; /* length of handle's key */
5141 * The haystack_copy string now looks like
5143 * "chars\0\0\0\0\0\0chars...\0\0\0chars... \0"
5145 * with that final \0 at haystack_copy[n].
5146 * Search each piece one at a time.
5149 end
= haystack_copy
+ n
;
5152 while(p
< end
&& !return_ptr
){
5155 while(*p
== '\0' && p
< end
)
5159 found_it
= srchstr(p
, needle
);
5162 /* found it, make result relative to haystack */
5163 return_ptr
= haystack
+ (found_it
- haystack_copy
);
5166 /* skip to next null */
5167 while(*p
!= '\0' && p
< end
)
5172 fs_give((void **) &free_this
);
5180 * Returns the number of columns taken up by the visible part of the line.
5181 * That is, account for handles and color changes and so forth.
5184 visible_linelen(int line
)
5186 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
5189 if(line
< 0 || line
>= st
->num_lines
)
5192 for(i
= 0, len
= 0; i
< st
->line_lengths
[line
];)
5193 switch(st
->text_lines
[line
][i
]){
5197 switch((i
< st
->line_lengths
[line
]) ? st
->text_lines
[line
][i
] : 0){
5200 n
= 0; /* quell gcc */
5201 /* skip the length byte plus <length> more bytes */
5202 if(i
< st
->line_lengths
[line
]){
5203 n
= st
->text_lines
[line
][i
];
5207 if(i
< st
->line_lengths
[line
] && n
> 0){
5215 i
+= (RGBLEN
+ 1); /* 1 for TAG, RGBLEN for color */
5227 case TAG_EMBED
: /* escaped embed character */
5232 default: /* the embed char was literal */
5242 while(((++len
) & 0x07) != 0) /* add tab's spaces */
5257 /*----------------------------------------------------------------------
5258 Display the contents of the given file (likely output from some command)
5260 Args: filename -- name of file containing output
5261 title -- title to be used for screen displaying output
5262 alt_msg -- if no output, Q this message instead of the default
5263 mode -- non-zero to display short files in status line
5267 display_output_file(char *filename
, char *title
, char *alt_msg
, int mode
)
5269 STORE_S
*in_file
= NULL
, *out_store
= NULL
;
5271 if((in_file
= so_get(FileStar
, filename
, READ_ACCESS
|READ_FROM_LOCALE
))){
5272 if(mode
== DOF_BRIEF
){
5273 int msg_q
= 0, i
= 0;
5274 char buf
[512], *msg_p
[4];
5275 #define MAX_SINGLE_MSG_LEN 60
5281 * Might need to do something about CRLFs for Windows.
5283 while(so_fgets(in_file
, msg_p
[msg_q
], sizeof(buf
) - (msg_p
[msg_q
] - buf
))
5285 && (i
= strlen(msg_p
[msg_q
])) < MAX_SINGLE_MSG_LEN
){
5286 msg_p
[msg_q
+1] = msg_p
[msg_q
]+strlen(msg_p
[msg_q
]);
5287 if (*(msg_p
[++msg_q
] - 1) == '\n')
5288 *(msg_p
[msg_q
] - 1) = '\0';
5291 if(msg_q
< 3 && i
< MAX_SINGLE_MSG_LEN
){
5293 for(i
= 0; i
< msg_q
; i
++)
5294 q_status_message2(SM_ORDER
, 3, 4,
5295 "%s Result: %s", title
, msg_p
[i
]);
5297 q_status_message2(SM_ORDER
, 0, 4, "%s%s", title
,
5300 : " command completed with no output");
5306 else if(mode
== DOF_EMPTY
){
5309 if(so_readc(&c
, in_file
) < 1){
5310 q_status_message2(SM_ORDER
, 0, 4, "%s%s", title
,
5313 : " command completed with no output");
5320 * We need to translate the file contents from the user's locale
5321 * charset to UTF-8 for use in scrolltool. We get that translation
5322 * from the READ_FROM_LOCALE in the in_file storage object.
5323 * It would be nice to skip this step but scrolltool doesn't use
5324 * the storage object routines to read from the file, so would
5325 * skip the translation step.
5331 if(!(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
5333 our_unlink(filename
);
5334 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
5335 _("Error allocating space."));
5339 so_seek(in_file
, 0L, 0);
5343 gf_link_filter(gf_wrap
,
5344 gf_wrap_filter_opt(ps_global
->ttyo
->screen_cols
- 4,
5345 ps_global
->ttyo
->screen_cols
,
5346 NULL
, 0, GFW_NONE
));
5348 gf_set_so_readc(&gc
, in_file
);
5349 gf_set_so_writec(&pc
, out_store
);
5351 if((errstr
= gf_pipe(gc
, pc
)) != NULL
){
5353 so_give(&out_store
);
5354 our_unlink(filename
);
5355 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
5356 _("Error allocating space."));
5360 gf_clear_so_writec(out_store
);
5361 gf_clear_so_readc(in_file
);
5369 snprintf(title_buf
, sizeof(title_buf
), "HELP FOR %s VIEW", title
);
5370 title_buf
[sizeof(title_buf
)-1] = '\0';
5372 memset(&sargs
, 0, sizeof(SCROLL_S
));
5373 sargs
.text
.text
= so_text(out_store
);
5374 sargs
.text
.src
= CharStar
;
5375 sargs
.text
.desc
= "output";
5376 sargs
.bar
.title
= title
;
5377 sargs
.bar
.style
= TextPercent
;
5378 sargs
.help
.text
= h_simple_text_view
;
5379 sargs
.help
.title
= title_buf
;
5381 ps_global
->mangled_screen
= 1;
5382 so_give(&out_store
);
5385 our_unlink(filename
);
5388 dprint((2, "Error reopening %s to get results: %s\n",
5389 filename
? filename
: "?", error_description(errno
)));
5393 /*--------------------------------------------------------------------
5394 Call the function that will perform the double click operation
5396 Returns: the current top line
5399 doubleclick_handle(SCROLL_S
*sparms
, HANDLE_S
*next_handle
, int *cmd
, int *done
)
5401 if(sparms
->mouse
.clickclick
){
5402 if((*sparms
->mouse
.clickclick
)(sparms
))
5406 switch(scroll_handle_launch(next_handle
, TRUE
)){
5408 *cmd
= MC_EXIT
; /* propagate */
5413 cmd_cancelled("View");
5420 return(scroll_state(SS_CUR
)->top_text_line
);
5427 * Just a little something to simplify assignments
5429 #define VIEWPOPUP(p, c, s) { \
5430 (p)->type = tQueue; \
5431 (p)->data.val = c; \
5432 (p)->label.style = lNormal; \
5433 (p)->label.string = s; \
5441 format_message_popup(sparms
, in_handle
)
5445 MPopup fmp_menu
[32];
5451 /* Reason to offer per message ops? */
5452 if(mn_get_total(ps_global
->msgmap
) > 0L){
5454 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
5456 switch((h
= get_handle(st
->parms
->text
.handles
, in_handle
))->type
){
5458 fmp_menu
[++i
].type
= tIndex
;
5459 fmp_menu
[i
].label
.string
= "View Attachment";
5460 fmp_menu
[i
].label
.style
= lNormal
;
5461 fmp_menu
[i
].data
.val
= 'X'; /* for local use */
5464 && dispatch_attachment(h
->h
.attach
) != MCD_NONE
5465 && !(h
->h
.attach
->can_display
& MCD_EXTERNAL
)
5466 && h
->h
.attach
->body
5467 && (h
->h
.attach
->body
->type
== TYPETEXT
5468 || (h
->h
.attach
->body
->type
== TYPEMESSAGE
5469 && h
->h
.attach
->body
->subtype
5470 && !strucmp(h
->h
.attach
->body
->subtype
,"rfc822")))){
5471 fmp_menu
[++i
].type
= tIndex
;
5472 fmp_menu
[i
].label
.string
= "View Attachment in New Window";
5473 fmp_menu
[i
].label
.style
= lNormal
;
5474 fmp_menu
[i
].data
.val
= 'Y'; /* for local use */
5477 fmp_menu
[++i
].type
= tIndex
;
5478 fmp_menu
[i
].label
.style
= lNormal
;
5479 fmp_menu
[i
].data
.val
= 'Z'; /* for local use */
5480 msgno_exceptions(ps_global
->mail_stream
,
5481 mn_m2raw(ps_global
->msgmap
,
5482 mn_get_cur(ps_global
->msgmap
)),
5483 h
->h
.attach
->number
, &n
, FALSE
);
5484 fmp_menu
[i
].label
.string
= (n
& MSG_EX_DELETE
)
5485 ? "Undelete Attachment"
5486 : "Delete Attachment";
5491 fmp_menu
[++i
].type
= tIndex
;
5492 fmp_menu
[i
].label
.string
= "View Link";
5493 fmp_menu
[i
].label
.style
= lNormal
;
5494 fmp_menu
[i
].data
.val
= 'X'; /* for local use */
5496 fmp_menu
[++i
].type
= tIndex
;
5497 fmp_menu
[i
].label
.string
= "Copy Link";
5498 fmp_menu
[i
].label
.style
= lNormal
;
5499 fmp_menu
[i
].data
.val
= 'W'; /* for local use */
5503 fmp_menu
[++i
].type
= tSeparator
;
5506 /* Delete or Undelete? That is the question. */
5507 fmp_menu
[++i
].type
= tQueue
;
5508 fmp_menu
[i
].label
.style
= lNormal
;
5509 mc
= ((rawno
= mn_m2raw(ps_global
->msgmap
,
5510 mn_get_cur(ps_global
->msgmap
))) > 0L
5511 && ps_global
->mail_stream
5512 && rawno
<= ps_global
->mail_stream
->nmsgs
)
5513 ? mail_elt(ps_global
->mail_stream
, rawno
) : NULL
;
5514 if(mc
&& mc
->deleted
){
5515 fmp_menu
[i
].data
.val
= 'U';
5516 fmp_menu
[i
].label
.string
= in_handle
5517 ? "&Undelete Message" : "&Undelete";
5520 fmp_menu
[i
].data
.val
= 'D';
5521 fmp_menu
[i
].label
.string
= in_handle
5522 ? "&Delete Message" : "&Delete";
5525 if(F_ON(F_ENABLE_FLAG
, ps_global
)){
5526 fmp_menu
[++i
].type
= tSubMenu
;
5527 fmp_menu
[i
].label
.string
= "Flag";
5528 fmp_menu
[i
].data
.submenu
= flag_submenu(mc
);
5532 VIEWPOPUP(&fmp_menu
[i
], 'S', in_handle
? "&Save Message" : "&Save");
5535 VIEWPOPUP(&fmp_menu
[i
], 'E', in_handle
? "&Export Message" : "&Export");
5538 VIEWPOPUP(&fmp_menu
[i
], '%', in_handle
? "Print Message" : "Print");
5541 VIEWPOPUP(&fmp_menu
[i
], 'R',
5542 in_handle
? "&Reply to Message" : "&Reply");
5545 VIEWPOPUP(&fmp_menu
[i
], 'F',
5546 in_handle
? "&Forward Message" : "&Forward");
5549 VIEWPOPUP(&fmp_menu
[i
], 'B',
5550 in_handle
? "&Bounce Message" : "&Bounce");
5553 VIEWPOPUP(&fmp_menu
[i
], 'T', "&Take Addresses");
5555 fmp_menu
[++i
].type
= tSeparator
;
5557 if(mn_get_cur(ps_global
->msgmap
) < mn_get_total(ps_global
->msgmap
)){
5559 VIEWPOPUP(&fmp_menu
[i
], 'N', "View &Next Message");
5562 if(mn_get_cur(ps_global
->msgmap
) > 0){
5564 VIEWPOPUP(&fmp_menu
[i
], 'P', "View &Prev Message");
5567 if(mn_get_cur(ps_global
->msgmap
) < mn_get_total(ps_global
->msgmap
)
5568 || mn_get_cur(ps_global
->msgmap
) > 0)
5569 fmp_menu
[++i
].type
= tSeparator
;
5571 /* Offer the attachment screen? */
5572 for(n
= 0; ps_global
->atmts
&& ps_global
->atmts
[n
].description
; n
++)
5577 VIEWPOPUP(&fmp_menu
[i
], 'V', "&View Attachment Index");
5582 VIEWPOPUP(&fmp_menu
[i
], 'I', "Message &Index");
5585 VIEWPOPUP(&fmp_menu
[i
], 'M', "&Main Menu");
5587 fmp_menu
[++i
].type
= tTail
;
5589 if((i
= mswin_popup(fmp_menu
)) >= 0 && in_handle
)
5590 switch(fmp_menu
[i
].data
.val
){
5591 case 'W' : /* Copy URL to clipboard */
5592 mswin_addclipboard(h
->h
.url
.path
);
5596 return(1); /* return like the user double-clicked */
5600 case 'Y' : /* popup the thing in another window */
5601 display_att_window(h
->h
.attach
);
5605 if(h
&& h
->type
== Attach
){
5606 msgno_exceptions(ps_global
->mail_stream
,
5607 mn_m2raw(ps_global
->msgmap
,
5608 mn_get_cur(ps_global
->msgmap
)),
5609 h
->h
.attach
->number
, &n
, FALSE
);
5611 msgno_exceptions(ps_global
->mail_stream
,
5612 mn_m2raw(ps_global
->msgmap
,
5613 mn_get_cur(ps_global
->msgmap
)),
5614 h
->h
.attach
->number
, &n
, TRUE
);
5615 q_status_message2(SM_ORDER
, 0, 3, "Attachment %s %s!",
5616 h
->h
.attach
->number
,
5617 (n
& MSG_EX_DELETE
) ? "deleted" : "undeleted");
5637 simple_text_popup(sparms
, in_handle
)
5641 MPopup simple_menu
[12];
5644 VIEWPOPUP(&simple_menu
[n
], '%', "Print");
5647 VIEWPOPUP(&simple_menu
[n
], 'S', "Save");
5650 VIEWPOPUP(&simple_menu
[n
], 'F', "Forward in Email");
5653 simple_menu
[n
++].type
= tSeparator
;
5655 VIEWPOPUP(&simple_menu
[n
], 'E', "Exit Viewer");
5658 simple_menu
[n
].type
= tTail
;
5660 (void) mswin_popup(simple_menu
);
5666 /*----------------------------------------------------------------------
5667 Return characters in scroll tool buffer serially
5669 Args: n -- index of char to return
5671 Returns: returns the character at index 'n', or -1 on error or
5676 mswin_readscrollbuf(n
)
5679 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
5681 static char **orig
= NULL
, **l
, *p
;
5688 * All of these are mind-numbingly slow at the moment...
5690 switch(st
->parms
->text
.src
){
5692 return((n
>= strlen((char *)st
->parms
->text
.text
))
5693 ? -1 : ((char *)st
->parms
->text
.text
)[n
]);
5696 /* BUG? is this test rigorous enough? */
5697 if(orig
!= (char **)st
->parms
->text
.text
|| n
< lastn
){
5699 if(orig
= l
= (char **)st
->parms
->text
.text
) /* reset l and p */
5702 else{ /* use cached l and p */
5703 c
= n
; /* and adjust n */
5708 while(l
){ /* look for 'n' on each line */
5709 for(; n
&& *p
; n
--, p
++)
5712 if(n
--) /* 'n' found ? */
5718 return((l
&& *l
) ? *p
? *p
: '\n' : -1);
5721 return((fseek((FILE *)st
->parms
->text
.text
, (long) n
, 0) < 0
5722 || (c
= fgetc((FILE *)st
->parms
->text
.text
)) == EOF
) ? -1 : c
);
5731 /*----------------------------------------------------------------------
5732 MSWin scroll callback. Called during scroll message processing.
5736 Args: cmd - what type of scroll operation.
5737 scroll_pos - parameter for operation.
5738 used as position for SCROLL_TO operation.
5740 Returns: TRUE - did the scroll operation.
5741 FALSE - was not able to do the scroll operation.
5744 pcpine_do_scroll (cmd
, scroll_pos
)
5748 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
5749 HANDLE_S
*next_handle
;
5751 int num_display_lines
;
5758 maxscroll
= st
->num_lines
;
5760 case MSWIN_KEY_SCROLLUPLINE
:
5761 if(st
->top_text_line
> 0) {
5762 st
->top_text_line
-= (int) scroll_pos
;
5764 if (st
->top_text_line
<= 0){
5765 snprintf(message
, sizeof(message
), "START of %.*s",
5766 32, STYLE_NAME(st
->parms
));
5767 message
[sizeof(message
)-1] = '\0';
5768 st
->top_text_line
= 0;
5773 case MSWIN_KEY_SCROLLDOWNLINE
:
5774 if(st
->top_text_line
< maxscroll
) {
5775 st
->top_text_line
+= (int) scroll_pos
;
5777 if (st
->top_text_line
>= maxscroll
){
5778 snprintf(message
, sizeof(message
), "END of %.*s", 32, STYLE_NAME(st
->parms
));
5779 message
[sizeof(message
)-1] = '\0';
5780 st
->top_text_line
= maxscroll
;
5785 case MSWIN_KEY_SCROLLUPPAGE
:
5786 if(st
->top_text_line
> 0) {
5787 num_display_lines
= SCROLL_LINES(ps_global
);
5788 scroll_lines
= MIN(MAX(num_display_lines
-
5789 ps_global
->viewer_overlap
, 1), num_display_lines
);
5790 if (st
->top_text_line
> scroll_lines
)
5791 st
->top_text_line
-= scroll_lines
;
5793 st
->top_text_line
= 0;
5794 snprintf(message
, sizeof(message
), "START of %.*s", 32, STYLE_NAME(st
->parms
));
5795 message
[sizeof(message
)-1] = '\0';
5801 case MSWIN_KEY_SCROLLDOWNPAGE
:
5802 num_display_lines
= SCROLL_LINES(ps_global
);
5803 if(st
->top_text_line
< maxscroll
) {
5804 scroll_lines
= MIN(MAX(num_display_lines
-
5805 ps_global
->viewer_overlap
, 1), num_display_lines
);
5806 st
->top_text_line
+= scroll_lines
;
5807 if (st
->top_text_line
>= maxscroll
) {
5808 st
->top_text_line
= maxscroll
;
5809 snprintf(message
, sizeof(message
), "END of %.*s", 32, STYLE_NAME(st
->parms
));
5810 message
[sizeof(message
)-1] = '\0';
5816 case MSWIN_KEY_SCROLLTO
:
5817 if (st
->top_text_line
!= scroll_pos
) {
5818 st
->top_text_line
= scroll_pos
;
5819 if (st
->top_text_line
== 0)
5820 snprintf(message
, sizeof(message
), "START of %.*s", 32, STYLE_NAME(st
->parms
));
5821 else if(st
->top_text_line
>= maxscroll
)
5822 snprintf(message
, sizeof(message
), "END of %.*s", 32, STYLE_NAME(st
->parms
));
5824 message
[sizeof(message
)-1] = '\0';
5830 /* Need to frame some handles? */
5831 if(st
->parms
->text
.handles
5832 && (next_handle
= scroll_handle_in_frame(st
->top_text_line
)))
5833 st
->parms
->text
.handles
= next_handle
;
5836 mswin_beginupdate();
5837 update_scroll_titlebar(st
->top_text_line
, 0);
5838 (void) scroll_scroll_text(st
->top_text_line
,
5839 st
->parms
->text
.handles
, 1);
5841 q_status_message(SM_INFO
, 0, 1, message
);
5843 /* Display is always called so that the "START(END) of message"
5844 * message gets removed when no longer at the start(end). */
5845 display_message (KEY_PGDN
);
5854 pcpine_help_scroll(title
)
5857 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
5860 strncpy(title
, (st
->parms
->help
.title
)
5861 ? st
->parms
->help
.title
: "Alpine Help", 256);
5863 return(pcpine_help(st
->parms
->help
.text
));
5868 pcpine_view_cursor(col
, row
)
5872 SCRLCTRL_S
*st
= scroll_state(SS_CUR
);
5876 return((row
>= HEADER_ROWS(ps_global
)
5877 && row
< HEADER_ROWS(ps_global
) + SCROLL_LINES(ps_global
)
5878 && (line
= (row
- 2) + st
->top_text_line
) < st
->num_lines
5879 && (key
= dot_on_handle(line
, col
))
5880 && scroll_handle_selectable(get_handle(st
->parms
->text
.handles
,key
)))
5882 : MSWIN_CURSOR_ARROW
);
5884 #endif /* _WINDOWS */