* New version 2.21.999
[alpine.git] / alpine / mailview.c
blob5a63e90addb79146c89164e4edaa6ee8297e8b0e
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: mailview.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2006-2008 University of Washington
8 * Copyright 2013-2018 Eduardo Chappa
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
21 mailview.c
23 Implements the mailview screen
24 Also includes scrolltool used to display help text
26 ====*/
28 #include "headers.h"
29 #include "mailcmd.h"
30 #include "mailview.h"
31 #include "mailindx.h"
32 #include "mailpart.h"
33 #include "adrbkcmd.h"
34 #include "keymenu.h"
35 #include "status.h"
36 #include "radio.h"
37 #include "help.h"
38 #include "imap.h"
39 #include "reply.h"
40 #include "folder.h"
41 #include "alpine.h"
42 #include "titlebar.h"
43 #include "signal.h"
44 #include "send.h"
45 #include "dispfilt.h"
46 #include "busy.h"
47 #include "smime.h"
48 #include "../pith/conf.h"
49 #include "../pith/filter.h"
50 #include "../pith/msgno.h"
51 #include "../pith/escapes.h"
52 #include "../pith/flag.h"
53 #include "../pith/mimedesc.h"
54 #include "../pith/url.h"
55 #include "../pith/bldaddr.h"
56 #include "../pith/mailcmd.h"
57 #include "../pith/newmail.h"
58 #include "../pith/pipe.h"
59 #include "../pith/thread.h"
60 #include "../pith/util.h"
61 #include "../pith/detoken.h"
62 #include "../pith/editorial.h"
63 #include "../pith/maillist.h"
64 #include "../pith/hist.h"
65 #include "../pith/busy.h"
66 #include "../pith/list.h"
67 #include "../pith/detach.h"
70 /*----------------------------------------------------------------------
71 Saved state for scrolling text
72 ----*/
73 typedef struct scroll_text {
74 SCROLL_S *parms; /* Original text (file, char *, char **) */
75 char **text_lines, /* Lines to display */
76 *fname; /* filename of line offsets in "text" */
77 FILE *findex; /* file pointer to line offsets in "text" */
78 short *line_lengths; /* Length of each line in "text_lines" */
79 long top_text_line, /* index in "text_lines" top displayed line */
80 num_lines; /* number of valid pointers in "text_lines" */
81 int lines_allocated; /* size of "text_lines" array */
82 struct {
83 int length, /* count of displayable lines (== PGSIZE) */
84 width, /* width of displayable lines */
85 start_line, /* line number to start painting on */
86 other_lines; /* # of lines not for scroll text */
87 } screen; /* screen parameters */
88 } SCRLCTRL_S;
91 typedef struct scroll_file {
92 long offset;
93 int len;
94 } SCRLFILE_S;
98 * Struct to help write lines do display as they're decoded
100 struct view_write_s {
101 char *line;
102 int index,
103 screen_line,
104 last_screen_line;
105 #ifdef _WINDOWS
106 long lines;
107 #endif
108 HANDLE_S **handles;
109 STORE_S *store;
110 } *g_view_write;
112 #define LINEBUFSIZ (4096)
114 #define MAX_FUDGE (1024*1024)
117 * Definitions to help scrolltool
119 #define SCROLL_LINES_ABOVE(X) HEADER_ROWS(X)
120 #define SCROLL_LINES_BELOW(X) FOOTER_ROWS(X)
121 #define SCROLL_LINES(X) MAX(((X)->ttyo->screen_rows \
122 - SCROLL_LINES_ABOVE(X) - SCROLL_LINES_BELOW(X)), 0)
123 #define scroll_text_lines() (scroll_state(SS_CUR)->num_lines)
127 * Definitions for various scroll state manager's functions
129 #define SS_NEW 1
130 #define SS_CUR 2
131 #define SS_FREE 3
135 * Handle hints.
137 #define HANDLE_INIT_MSG \
138 _("Selectable items in text -- Use Up/Down Arrows to choose, Return to view")
139 #define HANDLE_ABOVE_ERR \
140 _("No selected item displayed -- Use PrevPage to bring choice into view")
141 #define HANDLE_BELOW_ERR \
142 _("No selected item displayed -- Use NextPage to bring choice into view")
145 #define PGSIZE(X) (ps_global->ttyo->screen_rows - (X)->screen.other_lines)
147 #define TYPICAL_BIG_MESSAGE_LINES 200
151 * Internal prototypes
153 void view_writec_killbuf(void);
154 int view_end_scroll(SCROLL_S *);
155 long format_size_guess(BODY *);
156 int scroll_handle_prompt(HANDLE_S *, int);
157 int scroll_handle_launch(HANDLE_S *, int);
158 int scroll_handle_obscured(HANDLE_S *);
159 HANDLE_S *scroll_handle_in_frame(long);
160 long scroll_handle_reframe(int, int);
161 int handle_on_line(long, int);
162 int handle_on_page(HANDLE_S *, long, long);
163 int scroll_handle_selectable(HANDLE_S *);
164 HANDLE_S *scroll_handle_next_sel(HANDLE_S *);
165 HANDLE_S *scroll_handle_prev_sel(HANDLE_S *);
166 HANDLE_S *scroll_handle_next(HANDLE_S *);
167 HANDLE_S *scroll_handle_prev(HANDLE_S *);
168 HANDLE_S *scroll_handle_boundary(HANDLE_S *, HANDLE_S *(*)(HANDLE_S *));
169 int scroll_handle_column(int, int);
170 int scroll_handle_index(int, int);
171 void scroll_handle_set_loc(POSLIST_S **, int, int);
172 int dot_on_handle(long, int);
173 int url_launch(HANDLE_S *);
174 int url_launch_too_long(int);
175 char *url_external_handler(HANDLE_S *, int);
176 void url_mailto_addr(ADDRESS **, char *);
177 int ical_send_reply(char *);
178 int url_local_phone_home(char *);
179 int url_local_imap(char *);
180 int url_local_nntp(char *);
181 int url_local_news(char *);
182 int url_local_file(char *);
183 static int print_to_printer(SCROLL_S *);
184 int search_text(int, long, int, char **, Pos *, int *);
185 void update_scroll_titlebar(long, int);
186 SCRLCTRL_S *scroll_state(int);
187 void set_scroll_text(SCROLL_S *, long, SCRLCTRL_S *);
188 void redraw_scroll_text(void);
189 void zero_scroll_text(void);
190 void format_scroll_text(void);
191 void ScrollFile(long);
192 long make_file_index(void);
193 char *scroll_file_line(FILE *, char *, SCRLFILE_S *, int *);
194 long scroll_scroll_text(long, HANDLE_S *, int);
195 int search_scroll_text(long, int, char *, Pos *, int *);
196 char *search_scroll_line(char *, char *, int, int);
197 int visible_linelen(int);
198 long doubleclick_handle(SCROLL_S *, HANDLE_S *, int *, int *);
199 #ifdef _WINDOWS
200 int format_message_popup(SCROLL_S *, int);
201 int simple_text_popup(SCROLL_S *, int);
202 int mswin_readscrollbuf(int);
203 int pcpine_do_scroll(int, long);
204 char *pcpine_help_scroll(char *);
205 int pcpine_view_cursor(int, long);
206 #endif
210 /*----------------------------------------------------------------------
211 Format a buffer with the text of the current message for browser
213 Args: ps - pine state structure
215 Result: The scrolltool is called to display the message
217 Loop here viewing mail until the folder changed or a command takes
218 us to another screen. Inside the loop the message text is fetched and
219 formatted into a buffer allocated for it. These are passed to the
220 scrolltool(), that displays the message and executes commands. It
221 returns when it's time to display a different message, when we
222 change folders, when it's time for a different screen, or when
223 there are no more messages available.
224 ---*/
226 void
227 mail_view_screen(struct pine *ps)
229 char last_was_full_header = 0;
230 long last_message_viewed = -1L, raw_msgno, offset = 0L;
231 OtherMenu save_what = FirstMenu;
232 int we_cancel = 0, flags, cmd = 0;
233 int force_prefer = 0;
234 MESSAGECACHE *mc;
235 ENVELOPE *env;
236 BODY *body;
237 STORE_S *store;
238 HANDLE_S *handles = NULL;
239 SCROLL_S scrollargs;
240 SourceType src = CharStar;
242 dprint((1, "\n\n ----- MAIL VIEW -----\n"));
244 ps->prev_screen = mail_view_screen;
245 ps->force_prefer_plain = ps->force_no_prefer_plain = 0;
247 if(ps->ttyo->screen_rows - HEADER_ROWS(ps) - FOOTER_ROWS(ps) < 1){
248 q_status_message(SM_ORDER | SM_DING, 0, 3,
249 _("Screen too small to view message"));
250 ps->next_screen = mail_index_screen;
251 return;
254 /*----------------- Loop viewing messages ------------------*/
255 do {
257 ps->user_says_cancel = 0;
258 ps->some_quoting_was_suppressed = 0;
261 * Check total to make sure there's something to view. Check it
262 * inside the loop to make sure everything wasn't expunged while
263 * we were viewing. If so, make sure we don't just come back.
265 if(mn_get_total(ps->msgmap) <= 0L || !ps->mail_stream){
266 q_status_message(SM_ORDER, 0, 3, _("No messages to read!"));
267 ps->next_screen = mail_index_screen;
268 break;
271 we_cancel = busy_cue(NULL, NULL, 1);
273 if(mn_get_cur(ps->msgmap) <= 0L)
274 mn_set_cur(ps->msgmap,
275 THREADING() ? first_sorted_flagged(F_NONE,
276 ps->mail_stream,
277 0L, FSF_SKIP_CHID)
278 : 1L);
280 raw_msgno = mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap));
281 body = NULL;
282 if(raw_msgno == 0L
283 || !(env = pine_mail_fetchstructure(ps->mail_stream,raw_msgno,&body))
284 || !(raw_msgno > 0L && ps->mail_stream
285 && raw_msgno <= ps->mail_stream->nmsgs
286 && (mc = mail_elt(ps->mail_stream, raw_msgno)))){
287 q_status_message1(SM_ORDER, 3, 3,
288 "Error getting message %s data",
289 comatose(mn_get_cur(ps->msgmap)));
290 dprint((1, "!!!! ERROR fetching %s of msg %ld\n",
291 env ? "elt" : "env", mn_get_cur(ps->msgmap)));
293 ps->next_screen = mail_index_screen;
294 break;
296 else
297 ps->unseen_in_view = !mc->seen;
299 init_handles(&handles);
301 store = so_get(src, NULL, EDIT_ACCESS);
302 so_truncate(store, format_size_guess(body) + 2048);
304 view_writec_init(store, &handles, SCROLL_LINES_ABOVE(ps),
305 SCROLL_LINES_ABOVE(ps) +
306 ps->ttyo->screen_rows - (SCROLL_LINES_ABOVE(ps)
307 + SCROLL_LINES_BELOW(ps)));
309 flags = FM_DISPLAY;
310 if(last_message_viewed != mn_get_cur(ps->msgmap)
311 || last_was_full_header == 2 || cmd == MC_TOGGLE)
312 flags |= FM_NEW_MESS;
314 if(F_OFF(F_QUELL_FULL_HDR_RESET, ps_global)
315 && last_message_viewed != -1L
316 && last_message_viewed != mn_get_cur(ps->msgmap))
317 ps->full_header = 0;
319 if(offset) /* no pre-paint during resize */
320 view_writec_killbuf();
322 #ifdef SMIME
323 /* Attempt to handle S/MIME bodies */
324 if(ps->smime)
325 ps->smime->need_passphrase = 0;
327 if(F_OFF(F_DONT_DO_SMIME, ps_global) && fiddle_smime_message(body, raw_msgno))
328 flags |= FM_NEW_MESS; /* body was changed, force a reload */
329 #endif
331 #ifdef _WINDOWS
332 mswin_noscrollupdate(1);
333 #endif
334 ps->cur_uid_stream = ps->mail_stream;
335 ps->cur_uid = mail_uid(ps->mail_stream, raw_msgno);
336 (void) format_message(raw_msgno, env, body, &handles, flags | force_prefer,
337 view_writec);
338 #ifdef _WINDOWS
339 mswin_noscrollupdate(0);
340 #endif
342 view_writec_destroy();
344 last_message_viewed = mn_get_cur(ps->msgmap);
345 last_was_full_header = ps->full_header;
347 ps->next_screen = SCREEN_FUN_NULL;
349 memset(&scrollargs, 0, sizeof(SCROLL_S));
350 scrollargs.text.text = so_text(store);
351 scrollargs.text.src = src;
352 scrollargs.text.desc = "message";
355 * make first selectable handle the default
357 if(handles){
358 HANDLE_S *hp;
360 hp = handles;
361 while(!scroll_handle_selectable(hp) && hp != NULL)
362 hp = hp->next;
364 if((scrollargs.text.handles = hp) != NULL)
365 scrollargs.body_valid = (hp == handles);
366 else
367 free_handles(&handles);
369 else
370 scrollargs.body_valid = 1;
372 if(offset){ /* resize? preserve paging! */
373 scrollargs.start.on = Offset;
374 scrollargs.start.loc.offset = offset;
375 scrollargs.body_valid = 0;
376 offset = 0L;
379 scrollargs.use_indexline_color = 1;
381 /* TRANSLATORS: a screen title */
382 scrollargs.bar.title = _("MESSAGE TEXT");
383 scrollargs.end_scroll = view_end_scroll;
384 scrollargs.resize_exit = 1;
385 scrollargs.help.text = h_mail_view;
386 scrollargs.help.title = _("HELP FOR MESSAGE TEXT VIEW");
387 scrollargs.keys.menu = &view_keymenu;
388 scrollargs.keys.what = save_what;
389 setbitmap(scrollargs.keys.bitmap);
390 if(F_OFF(F_ENABLE_PIPE, ps_global))
391 clrbitn(VIEW_PIPE_KEY, scrollargs.keys.bitmap);
394 * turn off attachment viewing for raw msg txt, atts
395 * haven't been set up at this point
397 if(ps_global->full_header == 2
398 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
399 clrbitn(VIEW_ATT_KEY, scrollargs.keys.bitmap);
401 if(F_OFF(F_ENABLE_BOUNCE, ps_global))
402 clrbitn(BOUNCE_KEY, scrollargs.keys.bitmap);
404 if(F_OFF(F_ENABLE_FLAG, ps_global))
405 clrbitn(FLAG_KEY, scrollargs.keys.bitmap);
407 if(F_OFF(F_ENABLE_AGG_OPS, ps_global))
408 clrbitn(VIEW_SELECT_KEY, scrollargs.keys.bitmap);
410 if(F_OFF(F_ENABLE_FULL_HDR, ps_global))
411 clrbitn(VIEW_FULL_HEADERS_KEY, scrollargs.keys.bitmap);
413 #ifdef SMIME
414 if(!(ps->smime && ps->smime->need_passphrase))
415 clrbitn(DECRYPT_KEY, scrollargs.keys.bitmap);
417 if(F_ON(F_DONT_DO_SMIME, ps_global)){
418 clrbitn(DECRYPT_KEY, scrollargs.keys.bitmap);
419 clrbitn(SECURITY_KEY, scrollargs.keys.bitmap);
421 #endif
423 if(!handles){
425 * NOTE: the comment below only really makes sense if we
426 * decide to replace the "Attachment Index" with
427 * a model that lets you highlight the attachments
428 * in the header. In a way its consistent, but
429 * would mean more keymenu monkeybusiness since not
430 * all things would be available all the time. No wait.
431 * Then what would "Save" mean; the attachment, url or
432 * message you're currently viewing? Would Save
433 * of a message then only be possible from the message
434 * index? Clumsy. what about arrow-navigation. isn't
435 * that now inconsistent? Maybe next/prev url shouldn't
436 * be bound to the arrow/^N/^P navigation?
438 clrbitn(VIEW_VIEW_HANDLE, scrollargs.keys.bitmap);
439 clrbitn(VIEW_PREV_HANDLE, scrollargs.keys.bitmap);
440 clrbitn(VIEW_NEXT_HANDLE, scrollargs.keys.bitmap);
443 #ifdef _WINDOWS
444 scrollargs.mouse.popup = format_message_popup;
445 #endif
447 if(((cmd = scrolltool(&scrollargs)) == MC_RESIZE
448 || (cmd == MC_FULLHDR && ps_global->full_header == 1))
449 && scrollargs.start.on == Offset)
450 offset = scrollargs.start.loc.offset;
452 if(cmd == MC_TOGGLE && ps->force_prefer_plain == 0 && ps->force_no_prefer_plain == 0){
453 if(F_ON(F_PREFER_PLAIN_TEXT, ps_global))
454 ps->force_no_prefer_plain = 1;
455 else
456 ps->force_prefer_plain = 1;
458 else{
459 ps->force_prefer_plain = ps->force_no_prefer_plain = 0;
463 * We could use the values directly but this is the way it
464 * already worked with the flags, so leave it alone.
466 if(ps->force_prefer_plain == 0 && ps->force_no_prefer_plain == 0)
467 force_prefer = 0;
468 else if(ps->force_prefer_plain)
469 force_prefer = FM_FORCEPREFPLN;
470 else if(ps->force_no_prefer_plain)
471 force_prefer = FM_FORCENOPREFPLN;
473 save_what = scrollargs.keys.what;
474 ps_global->unseen_in_view = 0;
475 so_give(&store); /* free resources associated with store */
476 free_handles(&handles);
477 #ifdef _WINDOWS
478 mswin_destroyicons();
479 #endif
481 while(ps->next_screen == SCREEN_FUN_NULL);
483 if(we_cancel)
484 cancel_busy_cue(-1);
486 ps->force_prefer_plain = ps->force_no_prefer_plain = 0;
488 ps->cur_uid_stream = NULL;
489 ps->cur_uid = 0;
492 * Unless we're going into attachment screen,
493 * start over with full_header.
495 if(F_OFF(F_QUELL_FULL_HDR_RESET, ps_global)
496 && ps->next_screen != attachment_screen)
497 ps->full_header = 0;
503 * view_writec_init - function to create and init struct that manages
504 * writing to the display what we can as soon
505 * as we can.
507 void
508 view_writec_init(STORE_S *store, HANDLE_S **handlesp, int first_line, int last_line)
510 char tmp[MAILTMPLEN];
512 g_view_write = (struct view_write_s *)fs_get(sizeof(struct view_write_s));
513 memset(g_view_write, 0, sizeof(struct view_write_s));
514 g_view_write->store = store;
515 g_view_write->handles = handlesp;
516 g_view_write->screen_line = first_line;
517 g_view_write->last_screen_line = last_line;
519 if(!dfilter_trigger(NULL, tmp, sizeof(tmp))){
520 g_view_write->line = (char *) fs_get(LINEBUFSIZ*sizeof(char));
521 #ifdef _WINDOWS
522 mswin_beginupdate();
523 scroll_setrange(0L, 0L);
524 #endif
526 if(ps_global->VAR_DISPLAY_FILTERS)
527 ClearLines(first_line, last_line - 1);
533 void
534 view_writec_destroy(void)
536 if(g_view_write){
537 if(g_view_write->line && g_view_write->index)
538 view_writec('\n'); /* flush pending output! */
540 while(g_view_write->screen_line < g_view_write->last_screen_line)
541 ClearLine(g_view_write->screen_line++);
543 view_writec_killbuf();
545 fs_give((void **) &g_view_write);
548 #ifdef _WINDOWS
549 mswin_endupdate();
550 #endif
555 void
556 view_writec_killbuf(void)
558 if(g_view_write->line)
559 fs_give((void **) &g_view_write->line);
565 * view_screen_pc - write chars into the final buffer
568 view_writec(int c)
570 static int in_color = 0;
572 if(g_view_write->line){
574 * This only works if the 2nd and 3rd parts of the || don't happen.
575 * The only way it breaks is if we get a really, really long line
576 * because there are oodles of tags in it. In that case we will
577 * wrap incorrectly or split a tag across lines (losing color perhaps)
578 * but we won't crash.
580 if(c == '\n' ||
581 (!in_color &&
582 (char)c != TAG_EMBED &&
583 g_view_write->index >= (LINEBUFSIZ - 20 * (RGBLEN+2))) ||
584 (g_view_write->index >= LINEBUFSIZ - 1)){
585 int rv;
587 in_color = 0;
588 suspend_busy_cue();
589 ClearLine(g_view_write->screen_line);
590 if(c != '\n')
591 g_view_write->line[g_view_write->index++] = (char) c;
593 PutLine0n8b(g_view_write->screen_line++, 0,
594 g_view_write->line, g_view_write->index,
595 g_view_write->handles ? *g_view_write->handles : NULL);
597 resume_busy_cue(0);
598 rv = so_nputs(g_view_write->store,
599 g_view_write->line,
600 g_view_write->index);
601 g_view_write->index = 0;
603 if(g_view_write->screen_line >= g_view_write->last_screen_line){
604 fs_give((void **) &g_view_write->line);
605 fflush(stdout);
608 if(!rv)
609 return(0);
610 else if(c != '\n')
611 return(1);
613 else{
615 * Don't split embedded things over multiple lines. Colors are
616 * the longest tags so use their length.
618 if((char)c == TAG_EMBED)
619 in_color = RGBLEN+1;
620 else if(in_color)
621 in_color--;
623 g_view_write->line[g_view_write->index++] = (char) c;
624 return(1);
627 #ifdef _WINDOWS
628 else if(c == '\n' && g_view_write->lines++ > SCROLL_LINES(ps_global))
629 scroll_setrange(SCROLL_LINES(ps_global),
630 g_view_write->lines + SCROLL_LINES(ps_global));
631 #endif
633 return(so_writec(c, g_view_write->store));
637 view_end_scroll(SCROLL_S *sparms)
639 int done = 0, result, force;
641 if(F_ON(F_ENABLE_SPACE_AS_TAB, ps_global)){
643 if(F_ON(F_ENABLE_TAB_DELETES, ps_global)){
644 long save_msgno;
646 /* Let the TAB advance cur msgno for us */
647 save_msgno = mn_get_cur(ps_global->msgmap);
648 (void) cmd_delete(ps_global, ps_global->msgmap, MCMD_NONE, NULL);
649 mn_set_cur(ps_global->msgmap, save_msgno);
652 /* act like the user hit a TAB */
653 result = process_cmd(ps_global, ps_global->mail_stream,
654 ps_global->msgmap, MC_TAB, View, &force);
656 if(result == 1)
657 done = 1;
660 return(done);
665 * format_size_guess -- Run down the given body summing the text/plain
666 * pieces we're likely to display. It need only
667 * be a guess since this is intended for preallocating
668 * our display buffer...
670 long
671 format_size_guess(struct mail_bodystruct *body)
673 long size = 0L;
674 long extra = 0L;
675 char *free_me = NULL;
677 if(body){
678 if(body->type == TYPEMULTIPART){
679 PART *part;
681 for(part = body->nested.part; part; part = part->next)
682 size += format_size_guess(&part->body);
684 else if(body->type == TYPEMESSAGE
685 && body->subtype && !strucmp(body->subtype, "rfc822"))
686 size = format_size_guess(body->nested.msg->body);
687 else if((!body->type || body->type == TYPETEXT)
688 && (!body->subtype || !strucmp(body->subtype, "plain"))
689 && ((body->disposition.type
690 && !strucmp(body->disposition.type, "inline"))
691 || !(free_me = parameter_val(body->parameter, "name")))){
693 * Handles and colored quotes cause memory overhead. Figure about
694 * 100 bytes per level of quote per line and about 100 bytes per
695 * handle. Make a guess of 1 handle or colored quote per
696 * 10 lines of message. If we guess too low, we'll do a resize in
697 * so_cs_writec. Most of the overhead comes from the colors, so
698 * if we can see we don't do colors or don't have these features
699 * turned on, skip it.
701 if(pico_usingcolor() &&
702 ((ps_global->VAR_QUOTE1_FORE_COLOR &&
703 ps_global->VAR_QUOTE1_BACK_COLOR) ||
704 F_ON(F_VIEW_SEL_URL, ps_global) ||
705 F_ON(F_VIEW_SEL_URL_HOST, ps_global) ||
706 F_ON(F_SCAN_ADDR, ps_global)))
707 extra = MIN(100/10 * body->size.lines, MAX_FUDGE);
709 size = body->size.bytes + extra;
712 if(free_me)
713 fs_give((void **) &free_me);
716 return(size);
721 scroll_handle_prompt(HANDLE_S *handle, int force)
723 char prompt[256], tmp[MAILTMPLEN];
724 int rc, flags, local_h;
725 static ESCKEY_S launch_opts[] = {
726 /* TRANSLATORS: command names, editURL means user gets to edit a URL if they
727 want, editApp is edit application where they edit the application used to
728 view a URL */
729 {'y', 'y', "Y", N_("Yes")},
730 {'n', 'n', "N", N_("No")},
731 {-2, 0, NULL, NULL},
732 {-2, 0, NULL, NULL},
733 {0, 'u', "U", N_("editURL")},
734 {0, 'a', "A", N_("editApp")},
735 {-1, 0, NULL, NULL}};
737 if(handle->type == URL){
738 launch_opts[4].ch = 'u';
740 if((!(local_h = !struncmp(handle->h.url.path, "x-alpine-", 9))
741 || !(local_h = !struncmp(handle->h.url.path, "x-pine-help", 11)))
742 && (handle->h.url.tool
743 || ((local_h = url_local_handler(handle->h.url.path) != NULL)
744 && (handle->h.url.tool = url_external_handler(handle,1)))
745 || (!local_h
746 && (handle->h.url.tool = url_external_handler(handle,0))))){
747 #ifdef _WINDOWS
748 /* if NOT special DDE hack */
749 if(handle->h.url.tool[0] != '*')
750 #endif
751 if(ps_global->vars[V_BROWSER].is_fixed)
752 launch_opts[5].ch = -1;
753 else
754 launch_opts[5].ch = 'a';
756 else{
757 launch_opts[5].ch = -1;
758 if(!local_h){
759 if(ps_global->vars[V_BROWSER].is_fixed){
760 q_status_message(SM_ORDER, 3, 4,
761 _("URL-Viewer is disabled by sys-admin"));
762 return(0);
764 else{
765 /* TRANSLATORS: a question */
766 if(want_to(_("No URL-Viewer application defined. Define now"),
767 'y', 0, NO_HELP, WT_SEQ_SENSITIVE) == 'y'){
768 /* Prompt for the displayer? */
769 tmp[0] = '\0';
770 while(1){
771 flags = OE_APPEND_CURRENT |
772 OE_SEQ_SENSITIVE |
773 OE_KEEP_TRAILING_SPACE;
775 rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
776 sizeof(tmp),
777 _("Web Browser: "),
778 NULL, NO_HELP, &flags);
779 if(rc == 0){
780 if((flags & OE_USER_MODIFIED) && *tmp){
781 if(can_access(tmp, EXECUTE_ACCESS) == 0){
782 int n;
783 char **l;
786 * Save it for next time...
788 for(l = ps_global->VAR_BROWSER, n = 0;
789 l && *l;
790 l++)
791 n++; /* count */
793 l = (char **) fs_get((n+2)*sizeof(char *));
794 for(n = 0;
795 ps_global->VAR_BROWSER
796 && ps_global->VAR_BROWSER[n];
797 n++)
798 l[n] = cpystr(ps_global->VAR_BROWSER[n]);
800 l[n++] = cpystr(tmp);
801 l[n] = NULL;
803 set_variable_list(V_BROWSER, l, TRUE, Main);
804 free_list_array(&l);
806 handle->h.url.tool = cpystr(tmp);
807 break;
809 else{
810 q_status_message1(SM_ORDER | SM_DING, 2, 2,
811 _("Browser not found: %s"),
812 error_description(errno));
813 continue;
816 else
817 return(0);
819 else if(rc == 1 || rc == -1){
820 return(0);
822 else if(rc == 4){
823 if(ps_global->redrawer)
824 (*ps_global->redrawer)();
828 else
829 return(0);
834 else
835 launch_opts[4].ch = -1;
837 if(force
838 || (handle->type == URL
839 && (!struncmp(handle->h.url.path, "x-alpine-", 9)
840 || !struncmp(handle->h.url.path, "x-pine-help", 11))))
841 return(1);
843 while(1){
844 int sc = ps_global->ttyo->screen_cols;
847 * Customize the prompt for mailto, all the other schemes make
848 * sense if you just say View selected URL ...
850 if(handle->type == URL &&
851 !struncmp(handle->h.url.path, "mailto:", 7))
852 snprintf(prompt, sizeof(prompt), "Compose mail to \"%.*s%s\" ? ",
853 (int) MIN(MAX(0,sc - 25), sizeof(prompt)-50), handle->h.url.path+7,
854 (strlen(handle->h.url.path+7) > MAX(0,sc-25)) ? "..." : "");
855 else
856 snprintf(prompt, sizeof(prompt), "View selected %s %s%.*s%s ? ",
857 (handle->type == URL) ? "URL" : "Attachment",
858 (handle->type == URL) ? "\"" : "",
859 (int) MIN(MAX(0,sc-27), sizeof(prompt)-50),
860 (handle->type == URL) ? handle->h.url.path : "",
861 (handle->type == URL)
862 ? ((strlen(handle->h.url.path) > MAX(0,sc-27))
863 ? "...\"" : "\"") : "");
865 prompt[sizeof(prompt)-1] = '\0';
867 switch(radio_buttons(prompt, -FOOTER_ROWS(ps_global),
868 launch_opts, 'y', 'n', NO_HELP, RB_SEQ_SENSITIVE)){
869 case 'y' :
870 return(1);
872 case 'u' :
873 strncpy(tmp, handle->h.url.path, sizeof(tmp)-1);
874 tmp[sizeof(tmp)-1] = '\0';
875 while(1){
876 flags = OE_APPEND_CURRENT |
877 OE_SEQ_SENSITIVE |
878 OE_KEEP_TRAILING_SPACE;
880 rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
881 sizeof(tmp), _("Edit URL: "),
882 NULL, NO_HELP, &flags);
883 if(rc == 0){
884 if(flags & OE_USER_MODIFIED){
885 if(handle->h.url.path)
886 fs_give((void **) &handle->h.url.path);
888 handle->h.url.path = cpystr(tmp);
891 break;
893 else if(rc == 1 || rc == -1){
894 return(0);
896 else if(rc == 4){
897 if(ps_global->redrawer)
898 (*ps_global->redrawer)();
902 continue;
904 case 'a' :
905 if(handle->h.url.tool){
906 strncpy(tmp, handle->h.url.tool, sizeof(tmp)-1);
907 tmp[sizeof(tmp)-1] = '\0';
909 else
910 tmp[0] = '\0';
912 while(1){
913 flags = OE_APPEND_CURRENT |
914 OE_SEQ_SENSITIVE |
915 OE_KEEP_TRAILING_SPACE |
916 OE_DISALLOW_HELP;
918 rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
919 sizeof(tmp), _("Viewer Command: "),
920 NULL, NO_HELP, &flags);
921 if(rc == 0){
922 if(flags & OE_USER_MODIFIED){
923 if(handle->h.url.tool)
924 fs_give((void **) &handle->h.url.tool);
926 handle->h.url.tool = cpystr(tmp);
929 break;
931 else if(rc == 1 || rc == -1){
932 return(0);
934 else if(rc == 4){
935 if(ps_global->redrawer)
936 (*ps_global->redrawer)();
940 continue;
942 case 'n' :
943 default :
944 return(0);
952 scroll_handle_launch(HANDLE_S *handle, int force)
954 switch(handle->type){
955 case URL :
956 if(handle->h.url.path){
957 if(scroll_handle_prompt(handle, force)){
958 if(url_launch(handle)
959 || ps_global->next_screen != SCREEN_FUN_NULL)
960 return(1); /* done with this screen */
962 else
963 return(-1);
966 break;
968 case Attach :
969 if(scroll_handle_prompt(handle, force))
970 display_attachment(mn_m2raw(ps_global->msgmap,
971 mn_get_cur(ps_global->msgmap)),
972 handle->h.attach, DA_FROM_VIEW | DA_DIDPROMPT);
973 else
974 return(-1);
976 break;
978 case Folder :
979 break;
981 case Function :
982 (*handle->h.func.f)(handle->h.func.args.stream,
983 handle->h.func.args.msgmap,
984 handle->h.func.args.msgno);
985 break;
988 default :
989 alpine_panic("Unexpected HANDLE type");
992 return(0);
997 scroll_handle_obscured(HANDLE_S *handle)
999 SCRLCTRL_S *st = scroll_state(SS_CUR);
1001 return(handle_on_page(handle, st->top_text_line,
1002 st->top_text_line + st->screen.length));
1008 * scroll_handle_in_frame -- return handle pointer to visible handle.
1010 HANDLE_S *
1011 scroll_handle_in_frame(long int top_line)
1013 SCRLCTRL_S *st = scroll_state(SS_CUR);
1014 HANDLE_S *hp;
1016 switch(handle_on_page(hp = st->parms->text.handles, top_line,
1017 top_line + st->screen.length)){
1018 case -1 : /* handle above page */
1019 /* Find first handle from top of page */
1020 for(hp = st->parms->text.handles->next; hp; hp = hp->next)
1021 if(scroll_handle_selectable(hp))
1022 switch(handle_on_page(hp, top_line, top_line + st->screen.length)){
1023 case 0 : return(hp);
1024 case 1 : return(NULL);
1025 case -1 : default : break;
1028 break;
1030 case 1 : /* handle below page */
1031 /* Find first handle from top of page */
1032 for(hp = st->parms->text.handles->prev; hp; hp = hp->prev)
1033 if(scroll_handle_selectable(hp))
1034 switch(handle_on_page(hp, top_line, top_line + st->screen.length)){
1035 case 0 : return(hp);
1036 case -1 : return(NULL);
1037 case 1 : default : break;
1040 break;
1042 case 0 :
1043 default :
1044 break;
1047 return(hp);
1051 * scroll_handle_reframe -- adjust display params to display given handle
1053 long
1054 scroll_handle_reframe(int key, int center)
1056 long l, offset, dlines, start_line;
1057 SCRLCTRL_S *st = scroll_state(SS_CUR);
1059 dlines = PGSIZE(st);
1060 offset = (st->parms->text.src == FileStar) ? st->top_text_line : 0;
1061 start_line = st->top_text_line;
1063 if(key < 0)
1064 key = st->parms->text.handles->key;
1066 /* Searc down from the top line */
1067 for(l = start_line; l < st->num_lines; l++) {
1068 if(st->parms->text.src == FileStar && l > offset + dlines)
1069 ScrollFile(offset += dlines);
1071 if(handle_on_line(l - offset, key))
1072 break;
1075 if(l < st->num_lines){
1076 if(l >= dlines + start_line) /* bingo! */
1077 start_line = l - ((center ? (dlines / 2) : dlines) - 1);
1079 else{
1080 if(st->parms->text.src == FileStar) /* wrap offset */
1081 ScrollFile(offset = 0);
1083 for(l = 0; l < start_line; l++) {
1084 if(st->parms->text.src == FileStar && l > offset + dlines)
1085 ScrollFile(offset += dlines);
1087 if(handle_on_line(l - offset, key))
1088 break;
1091 if(l == start_line)
1092 alpine_panic("Internal Error: no handle found");
1093 else
1094 start_line = l;
1097 return(start_line);
1102 handle_on_line(long int line, int goal)
1104 int i, n, key;
1105 SCRLCTRL_S *st = scroll_state(SS_CUR);
1107 for(i = 0; i < st->line_lengths[line]; i++)
1108 if(st->text_lines[line][i] == TAG_EMBED
1109 && st->text_lines[line][++i] == TAG_HANDLE){
1110 for(key = 0, n = st->text_lines[line][++i]; n; n--)
1111 key = (key * 10) + (st->text_lines[line][++i] - '0');
1113 if(key == goal)
1114 return(1);
1117 return(0);
1122 handle_on_page(HANDLE_S *handle, long int first_line, long int last_line)
1124 POSLIST_S *l;
1125 int rv = 0;
1127 if(handle && (l = handle->loc)){
1128 for( ; l; l = l->next)
1129 if(l->where.row < first_line){
1130 if(!rv)
1131 rv = -1;
1133 else if(l->where.row >= last_line){
1134 if(!rv)
1135 rv = 1;
1137 else
1138 return(0); /* found! */
1141 return(rv);
1146 scroll_handle_selectable(HANDLE_S *handle)
1148 return(handle && (handle->type != URL
1149 || (handle->h.url.path && *handle->h.url.path)));
1153 HANDLE_S *
1154 scroll_handle_next_sel(HANDLE_S *handles)
1156 while(handles && !scroll_handle_selectable(handles = handles->next))
1159 return(handles);
1163 HANDLE_S *
1164 scroll_handle_prev_sel(HANDLE_S *handles)
1166 while(handles && !scroll_handle_selectable(handles = handles->prev))
1169 return(handles);
1173 HANDLE_S *
1174 scroll_handle_next(HANDLE_S *handles)
1176 HANDLE_S *next = NULL;
1178 if(scroll_handle_obscured(handles) <= 0){
1179 next = handles;
1180 while((next = scroll_handle_next_sel(next))
1181 && scroll_handle_obscured(next))
1185 return(next);
1190 HANDLE_S *
1191 scroll_handle_prev(HANDLE_S *handles)
1193 HANDLE_S *prev = NULL;
1195 if(scroll_handle_obscured(handles) >= 0){
1196 prev = handles;
1197 while((prev = scroll_handle_prev_sel(prev))
1198 && scroll_handle_obscured(prev))
1202 return(prev);
1206 HANDLE_S *
1207 scroll_handle_boundary(HANDLE_S *handle, HANDLE_S *(*f) (HANDLE_S *))
1209 HANDLE_S *hp, *whp = NULL;
1211 /* Multi-line handle? Punt! */
1212 if(handle && (!(hp = handle)->loc->next))
1213 while((hp = (*f)(hp))
1214 && hp->loc->where.row == handle->loc->where.row)
1215 whp = hp;
1217 return(whp);
1222 scroll_handle_column(int line, int offset)
1224 SCRLCTRL_S *st = scroll_state(SS_CUR);
1225 int i, n, col;
1226 int key, limit;
1228 limit = (offset > -1) ? offset : st->line_lengths[line];
1230 for(i = 0, col = 0; i < limit;){
1231 if(st && st->text_lines && st->text_lines[line])
1232 switch(st->text_lines[line][i]){
1233 case TAG_EMBED:
1234 i++;
1235 switch((i < limit) ? st->text_lines[line][i] : 0){
1236 case TAG_HANDLE:
1237 for(key = 0, n = st->text_lines[line][++i]; n > 0 && i < limit-1; n--)
1238 key = (key * 10) + (st->text_lines[line][++i] - '0');
1240 i++;
1241 break;
1243 case TAG_FGCOLOR :
1244 case TAG_BGCOLOR :
1245 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
1246 break;
1248 case TAG_INVON:
1249 case TAG_INVOFF:
1250 case TAG_BOLDON:
1251 case TAG_BOLDOFF:
1252 case TAG_ULINEON:
1253 case TAG_ULINEOFF:
1254 i++;
1255 break;
1257 default: /* literal embed char */
1258 break;
1261 break;
1263 case TAB:
1264 i++;
1265 while(((++col) & 0x07) != 0) /* add tab's spaces */
1268 break;
1270 default:
1271 col += width_at_this_position((unsigned char*) &st->text_lines[line][i],
1272 st->line_lengths[line] - i);
1273 i++;
1274 break;
1278 return(col);
1283 scroll_handle_index(int row, int column)
1285 SCRLCTRL_S *st = scroll_state(SS_CUR);
1286 int index = 0;
1288 for(index = 0; column > 0;)
1289 switch(st->text_lines[row][index++]){
1290 case TAG_EMBED :
1291 switch(st->text_lines[row][index++]){
1292 case TAG_HANDLE:
1293 index += st->text_lines[row][index] + 1;
1294 break;
1296 case TAG_FGCOLOR :
1297 case TAG_BGCOLOR :
1298 index += RGBLEN;
1299 break;
1301 default :
1302 break;
1305 break;
1307 case TAB : /* add tab's spaces */
1308 while(((--column) & 0x07) != 0)
1311 break;
1313 default :
1314 column--;
1315 break;
1318 return(index);
1322 void
1323 scroll_handle_set_loc(POSLIST_S **lpp, int line, int column)
1325 POSLIST_S *lp;
1327 lp = (POSLIST_S *) fs_get(sizeof(POSLIST_S));
1328 lp->where.row = line;
1329 lp->where.col = column;
1330 lp->next = NULL;
1331 for(; *lpp; lpp = &(*lpp)->next)
1334 *lpp = lp;
1339 dot_on_handle(long int line, int goal)
1341 int i = 0, n, key = 0, column = -1;
1342 SCRLCTRL_S *st = scroll_state(SS_CUR);
1345 if(i >= st->line_lengths[line])
1346 return(0);
1348 switch(st->text_lines[line][i++]){
1349 case TAG_EMBED :
1350 switch(st->text_lines[line][i++]){
1351 case TAG_HANDLE :
1352 for(key = 0, n = st->text_lines[line][i++]; n; n--)
1353 key = (key * 10) + (st->text_lines[line][i++] - '0');
1355 break;
1357 case TAG_FGCOLOR :
1358 case TAG_BGCOLOR :
1359 i += RGBLEN; /* advance past color setting */
1360 break;
1362 case TAG_BOLDOFF :
1363 key = 0;
1364 break;
1367 break;
1369 case TAB :
1370 while(((++column) & 0x07) != 0) /* add tab's spaces */
1373 break;
1375 default :
1376 column += width_at_this_position((unsigned char*) &st->text_lines[line][i-1],
1377 st->line_lengths[line] - (i-1));
1378 break;
1381 while(column < goal);
1383 return(key);
1388 * url_launch - Sniff the given url, see if we can do anything with
1389 * it. If not, hand off to user-defined app.
1393 url_launch(HANDLE_S *handle)
1395 int rv = 0;
1396 url_tool_t f;
1397 #define URL_MAX_LAUNCH (2 * MAILTMPLEN)
1399 if(handle->h.url.tool){
1400 char *toolp, *cmdp, *p, cmd[URL_MAX_LAUNCH + 4];
1401 int mode, copied = 0;
1402 PIPE_S *syspipe;
1404 toolp = handle->h.url.tool;
1406 /* This code used to quote a URL to prevent arbitrary command execution
1407 * through a URL. The plan was to quote the URL with single quotes,
1408 * and this used to work. BUT some shells do not care about quoting
1409 * and interpret some characters regardless of single quotes. The
1410 * simplest solution is to escape those characters, but then some
1411 * shells will see the escape characters and some others will not.
1412 * It's a mess. There are several ways to go around this mess,
1413 * including adding configuration options (one more!?), or forget
1414 * about shells. What we do is to forget about shells, and execute
1415 * the code as a PIPE_NOSHELL.
1418 cmdp = cmd;
1419 while(cmdp-cmd < URL_MAX_LAUNCH)
1420 if((!*toolp && !copied)
1421 || (*toolp == '_' && !strncmp(toolp + 1, "URL_", 4))){
1423 /* implicit _URL_ at end */
1424 if(!*toolp)
1425 *cmdp++ = ' ';
1427 if(cmdp[-1] == '\'') /* unquote old '_URL_' */
1428 cmdp--;
1430 copied = 1;
1431 for(p = handle->h.url.path;
1432 p && *p && cmdp-cmd < URL_MAX_LAUNCH; p++)
1433 *cmdp++ = *p;
1435 *cmdp = '\0';
1437 if(*toolp)
1438 toolp += 5; /* length of "_URL_" */
1439 if(*toolp == '\'')
1440 toolp++;
1442 else
1443 if(!(*cmdp++ = *toolp++))
1444 break;
1446 if(cmdp-cmd >= URL_MAX_LAUNCH)
1447 return(url_launch_too_long(rv));
1449 mode = PIPE_RESET | PIPE_USER | PIPE_RUNNOW | PIPE_NOSHELL ;
1450 if((syspipe = open_system_pipe(cmd, NULL, NULL, mode, 0, pipe_callback, pipe_report_error)) != NULL){
1451 close_system_pipe(&syspipe, NULL, pipe_callback);
1452 q_status_message(SM_ORDER, 0, 4, _("VIEWER command completed"));
1454 else
1455 q_status_message1(SM_ORDER, 3, 4,
1456 /* TRANSLATORS: Cannot start command : <command name> */
1457 _("Cannot start command : %s"), cmd);
1459 else if((f = url_local_handler(handle->h.url.path)) != NULL){
1460 if((*f)(handle->h.url.path) > 1)
1461 rv = 1; /* done! */
1463 else
1464 q_status_message1(SM_ORDER, 2, 2,
1465 _("\"URL-Viewer\" not defined: Can't open %s"),
1466 handle->h.url.path);
1468 return(rv);
1473 url_launch_too_long(int return_value)
1475 q_status_message(SM_ORDER | SM_DING, 3, 3,
1476 "Can't spawn. Command too long.");
1477 return(return_value);
1481 char *
1482 url_external_handler(HANDLE_S *handle, int specific)
1484 char **l, *test, *cmd, *p, *q, *ep;
1485 int i, specific_match;
1487 for(l = ps_global->VAR_BROWSER ; l && *l; l++){
1488 get_pair(*l, &test, &cmd, 0, 1);
1489 dprint((5, "TEST: \"%s\" CMD: \"%s\"\n",
1490 test ? test : "<NULL>", cmd ? cmd : "<NULL>"));
1491 removing_quotes(cmd);
1492 if(valid_filter_command(&cmd)){
1493 specific_match = 0;
1495 if((p = test) != NULL){
1496 while(*p && cmd)
1497 if(*p == '_'){
1498 if(!strncmp(p+1, "TEST(", 5)
1499 && (ep = strstr(p+6, ")_"))){
1500 *ep = '\0';
1502 if(exec_mailcap_test_cmd(p+6) == 0){
1503 p = ep + 2;
1505 else{
1506 dprint((5,"failed handler TEST\n"));
1507 fs_give((void **) &cmd);
1510 else if(!strncmp(p+1, "SCHEME(", 7)
1511 && (ep = strstr(p+8, ")_"))){
1512 *ep = '\0';
1514 p += 8;
1516 if((q = strchr(p, ',')) != NULL)
1517 *q++ = '\0';
1518 else
1519 q = ep;
1520 while(!((i = strlen(p))
1521 && ((p[i-1] == ':'
1522 && handle->h.url.path[i - 1] == ':')
1523 || (p[i-1] != ':'
1524 && handle->h.url.path[i] == ':'))
1525 && !struncmp(handle->h.url.path, p, i))
1526 && *(p = q));
1528 if(*p){
1529 specific_match = 1;
1530 p = ep + 2;
1532 else{
1533 dprint((5,"failed handler SCHEME\n"));
1534 fs_give((void **) &cmd);
1537 else{
1538 dprint((5, "UNKNOWN underscore test\n"));
1539 fs_give((void **) &cmd);
1542 else if(isspace((unsigned char) *p)){
1543 p++;
1545 else{
1546 dprint((1,"bogus handler test: \"%s\"",
1547 test ? test : "?"));
1548 fs_give((void **) &cmd);
1551 fs_give((void **) &test);
1554 if(cmd && (!specific || specific_match))
1555 return(cmd);
1558 if(test)
1559 fs_give((void **) &test);
1561 if(cmd)
1562 fs_give((void **) &cmd);
1565 cmd = NULL;
1567 if(!specific){
1568 cmd = url_os_specified_browser(handle->h.url.path);
1570 * Last chance, anything handling "text/html" in mailcap...
1572 if(!cmd && mailcap_can_display(TYPETEXT, "html", NULL, 0)){
1573 MCAP_CMD_S *mc_cmd;
1575 mc_cmd = mailcap_build_command(TYPETEXT, "html",
1576 NULL, "_URL_", NULL, 0);
1578 * right now URL viewing won't return anything requiring
1579 * special handling
1581 if(mc_cmd){
1582 cmd = mc_cmd->command;
1583 fs_give((void **)&mc_cmd);
1588 return(cmd);
1592 url_tool_t
1593 url_local_handler(char *s)
1595 int i;
1596 static struct url_t {
1597 char *url;
1598 short len;
1599 url_tool_t f;
1600 } handlers[] = {
1601 {"mailto:", 7, url_local_mailto}, /* see url_tool_t def's */
1602 {"imap://", 7, url_local_imap}, /* for explanations */
1603 {"nntp://", 7, url_local_nntp},
1604 {"file://", 7, url_local_file},
1605 #ifdef ENABLE_LDAP
1606 {"ldap://", 7, url_local_ldap},
1607 #endif
1608 {"news:", 5, url_local_news},
1609 {"x-alpine-ical:", 14, ical_send_reply},
1610 {"x-alpine-phone-home:", 20, url_local_phone_home},
1611 {"x-alpine-gripe:", 15, gripe_gripe_to},
1612 {"x-alpine-help:", 14, url_local_helper},
1613 {"x-pine-help:", 12, url_local_helper},
1614 {"x-alpine-config:", 16, url_local_config},
1615 {"x-alpine-cert:", 14, url_local_certdetails},
1616 {"#", 1, url_local_fragment},
1617 {NULL, 0, NULL}
1620 for(i = 0; handlers[i].url ; i++)
1621 if(!struncmp(s, handlers[i].url, handlers[i].len))
1622 return(handlers[i].f);
1624 return(NULL);
1630 * mailto URL digester ala draft-hoffman-mailto-url-02.txt
1633 url_local_mailto(char *url)
1635 return(url_local_mailto_and_atts(url, NULL));
1639 url_local_mailto_and_atts(char *url, PATMT *attachlist)
1641 ENVELOPE *outgoing;
1642 BODY *body = NULL;
1643 REPLY_S fake_reply;
1644 char *sig, *urlp, *p, *hname, *hvalue;
1645 int rv = 0;
1646 long rflags;
1647 int was_a_body = 0, impl, template_len = 0;
1648 char *fcc = NULL;
1649 PAT_STATE dummy;
1650 REDRAFT_POS_S *redraft_pos = NULL;
1651 ACTION_S *role = NULL;
1653 outgoing = mail_newenvelope();
1654 outgoing->message_id = generate_message_id();
1655 body = mail_newbody();
1656 body->type = TYPETEXT;
1657 if((body->contents.text.data = (void *) so_get(PicoText,NULL,EDIT_ACCESS)) != NULL){
1659 * URL format is:
1661 * mailtoURL = "mailto:" [ to ] [ headers ]
1662 * to = #mailbox
1663 * headers = "?" header *( "&" header )
1664 * header = hname "=" hvalue
1665 * hname = *urlc
1666 * hvalue = *urlc
1668 * NOTE2: "from" and "bcc" are intentionally excluded from
1669 * the list of understood "header" fields
1671 if((p = strchr(urlp = cpystr(url+7), '?')) != NULL)
1672 *p++ = '\0'; /* headers? Tie off mailbox */
1674 /* grok mailbox as first "to", then roll thru specific headers */
1675 if(*urlp)
1676 rfc822_parse_adrlist(&outgoing->to,
1677 rfc1738_str(urlp),
1678 ps_global->maildomain);
1680 while(p){
1681 /* Find next "header" */
1682 if((p = strchr(hname = p, '&')) != NULL)
1683 *p++ = '\0'; /* tie off "header" */
1685 if((hvalue = strchr(hname, '=')) != NULL)
1686 *hvalue++ = '\0'; /* tie off hname */
1688 if(!hvalue || !strucmp(hname, "subject")){
1689 char *sub = rfc1738_str(hvalue ? hvalue : hname);
1691 if(outgoing->subject){
1692 int len = strlen(outgoing->subject);
1694 fs_resize((void **) &outgoing->subject,
1695 (len + strlen(sub) + 2) * sizeof(char));
1696 snprintf(outgoing->subject + len, strlen(sub)+2, " %s", sub);
1697 outgoing->subject[len + strlen(sub) + 2 - 1] = '\0';
1699 else
1700 outgoing->subject = cpystr(sub);
1702 else if(!strucmp(hname, "to")){
1703 url_mailto_addr(&outgoing->to, hvalue);
1705 else if(!strucmp(hname, "cc")){
1706 url_mailto_addr(&outgoing->cc, hvalue);
1708 else if(!strucmp(hname, "bcc")){
1709 q_status_message(SM_ORDER, 3, 4,
1710 "\"Bcc\" header in mailto url ignored");
1712 else if(!strucmp(hname, "from")){
1713 q_status_message(SM_ORDER, 3, 4,
1714 "\"From\" header in mailto url ignored");
1716 else if(!strucmp(hname, "body")){
1717 char *sub = rfc1738_str(hvalue ? hvalue : "");
1719 so_puts((STORE_S *)body->contents.text.data, sub);
1720 so_puts((STORE_S *)body->contents.text.data, NEWLINE);
1721 was_a_body++;
1725 fs_give((void **) &urlp);
1727 rflags = ROLE_COMPOSE;
1728 if(nonempty_patterns(rflags, &dummy)){
1729 role = set_role_from_msg(ps_global, rflags, -1L, NULL);
1730 if(confirm_role(rflags, &role))
1731 role = combine_inherited_role(role);
1732 else{ /* cancel */
1733 role = NULL;
1734 cmd_cancelled("Composition");
1735 goto outta_here;
1739 if(role)
1740 q_status_message1(SM_ORDER, 3, 4, "Composing using role \"%s\"",
1741 role->nick);
1743 if(!was_a_body && role && role->template){
1744 char *filtered;
1746 impl = 1; /* leave cursor in header if not explicit */
1747 filtered = detoken(role, NULL, 0, 0, 0, &redraft_pos, &impl);
1748 if(filtered){
1749 if(*filtered){
1750 so_puts((STORE_S *)body->contents.text.data, filtered);
1751 if(impl == 1)
1752 template_len = strlen(filtered);
1755 fs_give((void **)&filtered);
1758 else
1759 impl = 1;
1761 if(!was_a_body && (sig = detoken(role, NULL, 2, 0, 1, &redraft_pos,
1762 &impl))){
1763 if(impl == 2)
1764 redraft_pos->offset += template_len;
1766 if(*sig)
1767 so_puts((STORE_S *)body->contents.text.data, sig);
1769 fs_give((void **)&sig);
1772 memset((void *)&fake_reply, 0, sizeof(fake_reply));
1773 fake_reply.pseudo = 1;
1774 fake_reply.data.pico_flags = (outgoing->subject) ? P_BODY : P_HEADEND;
1777 if(!(role && role->fcc))
1778 fcc = get_fcc_based_on_to(outgoing->to);
1780 if(attachlist)
1781 create_message_body(&body, attachlist, 0);
1783 pine_send(outgoing, &body, "\"MAILTO\" COMPOSE",
1784 role, fcc, &fake_reply, redraft_pos, NULL, NULL, PS_STICKY_TO);
1785 rv++;
1786 ps_global->mangled_screen = 1;
1788 else
1789 q_status_message(SM_ORDER | SM_DING, 3, 4,
1790 _("Can't create space for composer"));
1792 outta_here:
1793 if(outgoing)
1794 mail_free_envelope(&outgoing);
1796 if(body)
1797 pine_free_body(&body);
1799 if(fcc)
1800 fs_give((void **)&fcc);
1802 free_redraft_pos(&redraft_pos);
1803 free_action(&role);
1805 return(rv);
1809 void
1810 url_mailto_addr(struct mail_address **a, char *a_raw)
1812 char *p = cpystr(rfc1738_str(a_raw));
1814 while(*a) /* append to address list */
1815 a = &(*a)->next;
1817 rfc822_parse_adrlist(a, p, ps_global->maildomain);
1818 fs_give((void **) &p);
1823 * imap URL digester ala RFC 2192
1826 url_local_imap(char *url)
1828 char *folder, *mailbox = NULL, *errstr = NULL, *search = NULL,
1829 newfolder[MAILTMPLEN];
1830 int rv;
1831 long i;
1832 imapuid_t uid = 0L, uid_val = 0L;
1833 CONTEXT_S *fake_context;
1834 MESSAGECACHE *mc;
1836 rv = url_imap_folder(url, &folder, &uid, &uid_val, &search, 0);
1837 switch(rv & URL_IMAP_MASK){
1838 case URL_IMAP_IMAILBOXLIST :
1839 /* BUG: deal with lsub tag */
1840 if((fake_context = new_context(folder, NULL)) != NULL){
1841 newfolder[0] = '\0';
1842 if(display_folder_list(&fake_context, newfolder,
1843 0, folders_for_goto))
1844 if(strlen(newfolder) + 1 < MAILTMPLEN)
1845 mailbox = newfolder;
1847 else
1848 errstr = "Problem building URL's folder list";
1850 fs_give((void **) &folder);
1851 free_context(&fake_context);
1853 if(mailbox){
1854 return(1);
1856 else if(errstr)
1857 q_status_message(SM_ORDER|SM_DING, 3, 3, errstr);
1858 else
1859 cmd_cancelled("URL Launch");
1861 break;
1863 case URL_IMAP_IMESSAGEPART :
1864 case URL_IMAP_IMESSAGELIST :
1865 if(ps_global && ps_global->ttyo){
1866 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
1867 ps_global->mangled_footer = 1;
1870 rv = do_broach_folder(folder, NULL, NULL, 0L);
1871 fs_give((void **) &folder);
1872 switch(rv){
1873 case -1 : /* utter failure */
1874 ps_global->next_screen = main_menu_screen;
1875 break;
1877 case 0 : /* same folder reopened */
1878 ps_global->next_screen = mail_index_screen;
1879 break;
1881 case 1 : /* requested folder open */
1882 ps_global->next_screen = mail_index_screen;
1884 if(uid_val && uid_val != ps_global->mail_stream->uid_validity){
1885 /* Complain! */
1886 q_status_message(SM_ORDER|SM_DING, 3, 3,
1887 "Warning! Referenced folder changed since URL recorded");
1890 if(uid){
1892 * Make specified message the currently selected..
1894 for(i = 1L; i <= mn_get_total(ps_global->msgmap); i++)
1895 if(mail_uid(ps_global->mail_stream, i) == uid){
1896 ps_global->next_screen = mail_view_screen;
1897 mn_set_cur(ps_global->msgmap, i);
1898 break;
1901 if(i > mn_get_total(ps_global->msgmap))
1902 q_status_message(SM_ORDER, 2, 3,
1903 "Couldn't find specified article number");
1905 else if(search){
1907 * Select the specified messages
1908 * and present a zoom'd index...
1910 /* BUG: not dealing with CHARSET yet */
1912 /* ANOTHER BUG: mail_criteria is a compatibility routine for IMAP2BIS
1913 * so it doesn't know about IMAP4 search criteria, like SENTSINCE.
1914 * It also doesn't handle literals. */
1916 pine_mail_search_full(ps_global->mail_stream, NULL,
1917 mail_criteria(search),
1918 SE_NOPREFETCH | SE_FREE);
1920 for(i = 1L; i <= mn_get_total(ps_global->msgmap); i++)
1921 if(ps_global->mail_stream
1922 && i <= ps_global->mail_stream->nmsgs
1923 && (mc = mail_elt(ps_global->mail_stream, i))
1924 && mc->searched)
1925 set_lflag(ps_global->mail_stream,
1926 ps_global->msgmap, i, MN_SLCT, 1);
1928 if((i = any_lflagged(ps_global->msgmap, MN_SLCT)) != 0){
1930 q_status_message2(SM_ORDER, 0, 3,
1931 "%s message%s selected",
1932 long2string(i), plural(i));
1933 /* Zoom the index! */
1934 zoom_index(ps_global, ps_global->mail_stream,
1935 ps_global->msgmap, MN_SLCT);
1940 return(1);
1942 default:
1943 case URL_IMAP_ERROR :
1944 break;
1947 return(0);
1952 url_local_nntp(char *url)
1954 char folder[2*MAILTMPLEN], *group;
1955 int group_len;
1956 long i, article_num;
1958 /* no hostport, no url, end of story */
1959 if((group = strchr(url + 7, '/')) != 0){
1960 group++;
1961 for(group_len = 0; group[group_len] && group[group_len] != '/';
1962 group_len++)
1963 if(!rfc1738_group(&group[group_len]))
1964 /* TRANSLATORS: these are errors in news group URLs */
1965 return(url_bogus(url, _("Invalid newsgroup specified")));
1967 if(group_len){
1968 snprintf(folder, sizeof(folder), "{%.*s/nntp}#news.%.*s",
1969 (int) MIN((group - 1) - (url + 7), MAILTMPLEN-20), url + 7,
1970 (int) MIN(group_len, MAILTMPLEN-20), group);
1971 folder[sizeof(folder)-1] = '\0';
1973 else
1974 return(url_bogus(url, _("No newsgroup specified")));
1976 else
1977 return(url_bogus(url, _("No server specified")));
1979 if(ps_global && ps_global->ttyo){
1980 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
1981 ps_global->mangled_footer = 1;
1984 switch(do_broach_folder(rfc1738_str(folder), NULL, NULL, 0L)){
1985 case -1 : /* utter failure */
1986 ps_global->next_screen = main_menu_screen;
1987 break;
1989 case 0 : /* same folder reopened */
1990 ps_global->next_screen = mail_index_screen;
1991 break;
1993 case 1 : /* requested folder open */
1994 ps_global->next_screen = mail_index_screen;
1996 /* grok article number --> c-client UID */
1997 if(group[group_len++] == '/'
1998 && (article_num = atol(&group[group_len]))){
2000 * Make the requested article our current message
2002 for(i = 1; i <= mn_get_nmsgs(ps_global->msgmap); i++)
2003 if(mail_uid(ps_global->mail_stream, i) == article_num){
2004 ps_global->next_screen = mail_view_screen;
2005 if((i = mn_raw2m(ps_global->msgmap, i)) != 0)
2006 mn_set_cur(ps_global->msgmap, i);
2007 break;
2010 if(i == 0 || i > mn_get_total(ps_global->msgmap))
2011 q_status_message(SM_ORDER, 2, 3,
2012 _("Couldn't find specified article number"));
2015 break;
2018 ps_global->redrawer = (void(*)(void))NULL;
2019 return(1);
2024 url_local_news(char *url)
2026 char folder[MAILTMPLEN], *p;
2027 CONTEXT_S *cntxt = NULL;
2030 * NOTE: NO SUPPORT for '*' or message-id
2032 if(*(url+5)){
2033 if(*(url+5) == '/' && *(url+6) == '/')
2034 return(url_local_nntp(url)); /* really meant "nntp://"? */
2036 if(ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0])
2038 * BUG: Only the first NNTP server is tried.
2040 snprintf(folder, sizeof(folder), "{%s/nntp}#news.", ps_global->VAR_NNTP_SERVER[0]);
2041 else
2042 strncpy(folder, "#news.", sizeof(folder));
2044 folder[sizeof(folder)-1] = '\0';
2046 for(p = strncpy(folder + strlen(folder), url + 5, sizeof(folder)-strlen(folder)-1);
2047 *p && rfc1738_group(p);
2048 p++)
2051 if(*p)
2052 return(url_bogus(url, "Invalid newsgroup specified"));
2054 else{ /* fish first group from newsrc */
2055 FOLDER_S *f;
2056 int alphaorder;
2058 folder[0] = '\0';
2060 /* Find first news context */
2061 for(cntxt = ps_global->context_list;
2062 cntxt && !(cntxt->use & CNTXT_NEWS);
2063 cntxt = cntxt->next)
2066 if(cntxt){
2067 if((alphaorder = F_OFF(F_READ_IN_NEWSRC_ORDER, ps_global)) != 0)
2068 (void) F_SET(F_READ_IN_NEWSRC_ORDER, ps_global, 1);
2070 build_folder_list(NULL, cntxt, NULL, NULL, BFL_LSUB);
2071 if((f = folder_entry(0, FOLDERS(cntxt))) != NULL){
2072 strncpy(folder, f->name, sizeof(folder));
2073 folder[sizeof(folder)-1] = '\0';
2076 free_folder_list(cntxt);
2078 if(alphaorder)
2079 (void) F_SET(F_READ_IN_NEWSRC_ORDER, ps_global, 0);
2082 if(folder[0] == '\0'){
2083 q_status_message(SM_ORDER | SM_DING, 3, 3,
2084 "No default newsgroup");
2085 return(0);
2089 if(ps_global && ps_global->ttyo){
2090 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2091 ps_global->mangled_footer = 1;
2094 if(do_broach_folder(rfc1738_str(folder), cntxt, NULL, 0L) < 0)
2095 ps_global->next_screen = main_menu_screen;
2096 else
2097 ps_global->next_screen = mail_index_screen;
2099 ps_global->redrawer = (void(*)(void))NULL;
2101 return(1);
2106 url_local_file(char *file_url)
2108 if(want_to(
2109 /* TRANSLATORS: this is a warning that the file URL can cause programs to run which may
2110 be a security problem. We are asking the user to confirm that they want to do this. */
2111 _("\"file\" URL may cause programs to be run on your system. Run anyway"),
2112 'n', 0, NO_HELP, WT_NORM) == 'y'){
2113 HANDLE_S handle;
2115 /* fake a handle */
2116 handle.h.url.path = file_url;
2117 if((handle.h.url.tool = url_external_handler(&handle, 1))
2118 || (handle.h.url.tool = url_external_handler(&handle, 0))){
2119 url_launch(&handle);
2120 return 1;
2122 else{
2123 q_status_message(SM_ORDER, 0, 4,
2124 _("No viewer for \"file\" URL. VIEWER command cancelled"));
2125 return 0;
2128 q_status_message(SM_ORDER, 0, 4, _("VIEWER command cancelled"));
2129 return 0;
2134 url_local_fragment(char *fragment)
2136 SCRLCTRL_S *st = scroll_state(SS_CUR);
2137 HANDLE_S *hp;
2140 * find a handle with the fragment's name
2142 for(hp = st->parms->text.handles; hp; hp = hp->next)
2143 if(hp->type == URL && hp->h.url.name
2144 && !strcmp(hp->h.url.name, fragment + 1))
2145 break;
2147 if(!hp)
2148 for(hp = st->parms->text.handles->prev; hp; hp = hp->prev)
2149 if(hp->type == URL && hp->h.url.name
2150 && !strcmp(hp->h.url.name, fragment + 1))
2151 break;
2154 * set the top line of the display to contain this line
2156 if(hp && hp->loc){
2157 st->top_text_line = hp->loc->where.row;
2158 ps_global->mangled_body = 1;
2160 else
2161 q_status_message1(SM_ORDER | SM_DING, 0, 3,
2162 "Can't find fragment: %s", fragment);
2164 return(1);
2168 ical_send_reply(char *url)
2170 // ical_compose_reply(url + strlen("x-alpine-ical:"));
2171 return 2;
2176 url_local_phone_home(char *URL)
2178 phone_home(URL + strlen("x-alpine-phone-home:"));
2179 return(2);
2184 * Format editorial comment referencing screen offering
2185 * List-* header supplied commands
2188 rfc2369_editorial(long int msgno, HANDLE_S **handlesp, int flags, int width, gf_io_t pc)
2190 char *p, *hdrp, *hdrs[MLCMD_COUNT + 1],
2191 color[64], buf[2048];
2192 int i, n, rv = TRUE;
2193 HANDLE_S *h = NULL;
2195 if((flags & FM_DISPLAY)
2196 && (hdrp = pine_fetchheader_lines(ps_global->mail_stream, msgno,
2197 NULL, rfc2369_hdrs(hdrs)))){
2198 if(*hdrp){
2199 snprintf(buf, sizeof(buf), "Note: This message contains ");
2200 buf[sizeof(buf)-1] = '\0';
2201 p = buf + strlen(buf);
2203 if(handlesp){
2204 h = new_handle(handlesp);
2205 h->type = Function;
2206 h->h.func.f = rfc2369_display;
2207 h->h.func.args.stream = ps_global->mail_stream;
2208 h->h.func.args.msgmap = ps_global->msgmap;
2209 h->h.func.args.msgno = msgno;
2211 if(!(flags & FM_NOCOLOR)
2212 && handle_start_color(color, sizeof(color), &n, 0)){
2213 if((p-buf)+n < sizeof(buf))
2214 for(i = 0; i < n; i++)
2215 *p++ = color[i];
2217 else if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)){
2218 *p++ = TAG_EMBED;
2219 *p++ = TAG_BOLDON;
2222 if((p-buf)+2 < sizeof(buf)){
2223 *p++ = TAG_EMBED;
2224 *p++ = TAG_HANDLE;
2227 snprintf(p + 1, sizeof(buf)-(p+1-buf), "%d", h->key);
2228 buf[sizeof(buf)-1] = '\0';
2229 *p = strlen(p + 1);
2230 p += (*p + 1);
2233 sstrncpy(&p, "email list management information", sizeof(buf)-(p-buf));
2234 buf[sizeof(buf)-1] = '\0';
2236 if(h){
2237 /* in case it was the current selection */
2238 if((p-buf)+2 < sizeof(buf)){
2239 *p++ = TAG_EMBED;
2240 *p++ = TAG_INVOFF;
2243 if(handle_end_color(color, sizeof(color), &n)){
2244 if((p-buf)+n < sizeof(buf))
2245 for(i = 0; i < n; i++)
2246 *p++ = color[i];
2248 else{
2249 if((p-buf)+2 < sizeof(buf)){
2250 *p++ = TAG_EMBED;
2251 *p++ = TAG_BOLDOFF;
2255 if(p-buf < sizeof(buf))
2256 *p = '\0';
2259 buf[sizeof(buf)-1] = '\0';
2261 rv = (gf_puts(NEWLINE, pc)
2262 && format_editorial(buf, width, flags, handlesp, pc) == NULL
2263 && gf_puts(NEWLINE, pc));
2266 fs_give((void **) &hdrp);
2269 return(rv);
2274 /*----------------------------------------------------------------------
2275 routine for displaying text on the screen.
2277 Args: sparms -- structure of args controlling what happens outside
2278 just the business of managing text scrolling
2280 This displays in three different kinds of text. One is an array of
2281 lines passed in in text_array. The other is a simple long string of
2282 characters passed in in text.
2284 The style determines what some of the error messages will be, and
2285 what commands are available as different things are appropriate for
2286 help text than for message text etc.
2288 ---*/
2291 scrolltool(SCROLL_S *sparms)
2293 register long cur_top_line, num_display_lines;
2294 UCS ch;
2295 int result, done, cmd, found_on, found_on_index,
2296 first_view, force, scroll_lines, km_size,
2297 cursor_row, cursor_col, km_popped;
2298 char *utf8str;
2299 long jn;
2300 struct key_menu *km;
2301 HANDLE_S *next_handle;
2302 bitmap_t bitmap;
2303 OtherMenu what;
2304 Pos whereis_pos;
2306 num_display_lines = SCROLL_LINES(ps_global);
2307 km_popped = 0;
2308 ps_global->mangled_header = 1;
2309 ps_global->mangled_footer = 1;
2310 ps_global->mangled_body = !sparms->body_valid;
2313 what = sparms->keys.what; /* which key menu to display */
2314 cur_top_line = 0;
2315 done = 0;
2316 found_on = -1;
2317 found_on_index = -1;
2318 first_view = 1;
2319 if(sparms->quell_first_view)
2320 first_view = 0;
2322 force = 0;
2323 ch = 'x'; /* for first time through */
2324 whereis_pos.row = 0;
2325 whereis_pos.col = 0;
2326 next_handle = sparms->text.handles;
2328 set_scroll_text(sparms, cur_top_line, scroll_state(SS_NEW));
2329 format_scroll_text();
2331 if((km = sparms->keys.menu) != NULL){
2332 memcpy(bitmap, sparms->keys.bitmap, sizeof(bitmap_t));
2334 else{
2335 setbitmap(bitmap);
2336 km = &simple_text_keymenu;
2337 #ifdef _WINDOWS
2338 sparms->mouse.popup = simple_text_popup;
2339 #endif
2342 if(!sparms->bar.title)
2343 sparms->bar.title = "Text";
2345 if(sparms->bar.style == TitleBarNone){
2346 if(THREADING() && sp_viewing_a_thread(ps_global->mail_stream))
2347 sparms->bar.style = ThrdMsgPercent;
2348 else
2349 sparms->bar.style = MsgTextPercent;
2352 switch(sparms->start.on){
2353 case LastPage :
2354 cur_top_line = MAX(0, scroll_text_lines() - (num_display_lines-2));
2355 if(F_ON(F_SHOW_CURSOR, ps_global)){
2356 whereis_pos.row = scroll_text_lines() - cur_top_line;
2357 found_on = scroll_text_lines() - 1;
2360 break;
2362 case Fragment :
2363 if(sparms->start.loc.frag){
2364 (void) url_local_fragment(sparms->start.loc.frag);
2366 cur_top_line = scroll_state(SS_CUR)->top_text_line;
2368 if(F_ON(F_SHOW_CURSOR, ps_global)){
2369 whereis_pos.row = scroll_text_lines() - cur_top_line;
2370 found_on = scroll_text_lines() - 1;
2374 break;
2376 case Offset :
2377 if(sparms->start.loc.offset){
2378 for(cur_top_line = 0L;
2379 cur_top_line + 1 < scroll_text_lines()
2380 && (sparms->start.loc.offset
2381 -= scroll_handle_column(cur_top_line,
2382 -1)) >= 0;
2383 cur_top_line++)
2387 break;
2389 case Handle :
2390 if(scroll_handle_obscured(sparms->text.handles))
2391 cur_top_line = scroll_handle_reframe(-1, TRUE);
2393 break;
2395 default : /* no-op */
2396 break;
2399 /* prepare for calls below to tell us where to go */
2400 ps_global->next_screen = SCREEN_FUN_NULL;
2402 cancel_busy_cue(-1);
2404 while(!done) {
2405 ps_global->user_says_cancel = 0;
2406 if(km_popped){
2407 km_popped--;
2408 if(km_popped == 0){
2409 clearfooter(ps_global);
2410 ps_global->mangled_body = 1;
2414 if(ps_global->mangled_screen) {
2415 ps_global->mangled_header = 1;
2416 ps_global->mangled_footer = 1;
2417 ps_global->mangled_body = 1;
2420 if(!sparms->quell_newmail && streams_died())
2421 ps_global->mangled_header = 1;
2423 dprint((9, "@@@@ current:%ld\n",
2424 mn_get_cur(ps_global->msgmap)));
2427 /*==================== All Screen painting ====================*/
2428 /*-------------- The title bar ---------------*/
2429 update_scroll_titlebar(cur_top_line, ps_global->mangled_header);
2431 if(ps_global->mangled_screen){
2432 /* this is the only line not cleared by header, body or footer
2433 * repaint calls....
2435 ClearLine(1);
2436 ps_global->mangled_screen = 0;
2439 /*---- Scroll or update the body of the text on the screen -------*/
2440 cur_top_line = scroll_scroll_text(cur_top_line, next_handle,
2441 ps_global->mangled_body);
2442 ps_global->redrawer = redraw_scroll_text;
2443 ps_global->mangled_body = 0;
2445 /*--- Check to see if keymenu might change based on next_handle --*/
2446 if(sparms->text.handles != next_handle)
2447 ps_global->mangled_footer = 1;
2449 if(next_handle)
2450 sparms->text.handles = next_handle;
2452 /*------------- The key menu footer --------------------*/
2453 if(ps_global->mangled_footer || sparms->keys.each_cmd){
2454 if(km_popped){
2455 FOOTER_ROWS(ps_global) = 3;
2456 clearfooter(ps_global);
2459 if(F_ON(F_ARROW_NAV, ps_global)){
2460 menu_clear_binding(km, KEY_LEFT);
2461 if((cmd = menu_clear_binding(km, '<')) != MC_UNKNOWN){
2462 menu_add_binding(km, '<', cmd);
2463 menu_add_binding(km, KEY_LEFT, cmd);
2467 if(F_ON(F_ARROW_NAV, ps_global)){
2468 menu_clear_binding(km, KEY_RIGHT);
2469 if((cmd = menu_clear_binding(km, '>')) != MC_UNKNOWN){
2470 menu_add_binding(km, '>', cmd);
2471 menu_add_binding(km, KEY_RIGHT, cmd);
2475 if(sparms->keys.each_cmd){
2476 (*sparms->keys.each_cmd)(sparms,
2477 scroll_handle_obscured(sparms->text.handles));
2478 memcpy(bitmap, sparms->keys.bitmap, sizeof(bitmap_t));
2481 if(menu_binding_index(km, MC_JUMP) >= 0){
2482 for(cmd = 0; cmd < 10; cmd++)
2483 if(F_ON(F_ENABLE_JUMP, ps_global))
2484 (void) menu_add_binding(km, '0' + cmd, MC_JUMP);
2485 else
2486 (void) menu_clear_binding(km, '0' + cmd);
2489 draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
2490 1-FOOTER_ROWS(ps_global), 0, what);
2491 what = SameMenu;
2492 ps_global->mangled_footer = 0;
2493 if(km_popped){
2494 FOOTER_ROWS(ps_global) = 1;
2495 mark_keymenu_dirty();
2499 if((ps_global->first_time_user || ps_global->show_new_version)
2500 && first_view && sparms->text.handles
2501 && (sparms->text.handles->next || sparms->text.handles->prev)
2502 && !sparms->quell_help)
2503 q_status_message(SM_ORDER, 0, 3, HANDLE_INIT_MSG);
2505 /*============ Check for New Mail and CheckPoint ============*/
2506 if(!sparms->quell_newmail &&
2507 new_mail(force, NM_TIMING(ch), NM_STATUS_MSG) >= 0){
2508 update_scroll_titlebar(cur_top_line, 1);
2509 if(ps_global->mangled_footer)
2510 draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
2511 1-FOOTER_ROWS(ps_global), 0, what);
2513 ps_global->mangled_footer = 0;
2517 * If an expunge of the current message happened during the
2518 * new mail check we want to bail out of here. See mm_expunged.
2520 if(ps_global->next_screen != SCREEN_FUN_NULL){
2521 done = 1;
2522 continue;
2525 if(ps_global->noticed_change_in_unseen){
2526 ps_global->noticed_change_in_unseen = 0; /* redraw only once */
2527 cmd = MC_RESIZE; /* causes cursor to be saved in folder_lister */
2528 done = 1;
2529 continue;
2532 if(first_view && num_display_lines >= scroll_text_lines())
2533 q_status_message1(SM_INFO, 0, 1, "ALL of %s", STYLE_NAME(sparms));
2536 force = 0; /* may not need to next time around */
2537 first_view = 0; /* check_point a priority any more? */
2539 /*==================== Output the status message ==============*/
2540 if(!sparms->no_stat_msg){
2541 if(km_popped){
2542 FOOTER_ROWS(ps_global) = 3;
2543 mark_status_unknown();
2546 display_message(ch);
2547 if(km_popped){
2548 FOOTER_ROWS(ps_global) = 1;
2549 mark_status_unknown();
2553 if(F_ON(F_SHOW_CURSOR, ps_global)){
2554 #ifdef WINDOWS
2555 if(cur_top_line != scroll_state(SS_CUR)->top_text_line)
2556 whereis_pos.row = 0;
2557 #endif
2559 if(whereis_pos.row > 0){
2560 cursor_row = SCROLL_LINES_ABOVE(ps_global)
2561 + whereis_pos.row - 1;
2562 cursor_col = whereis_pos.col;
2564 else{
2565 POSLIST_S *lp = NULL;
2567 if(sparms->text.handles &&
2568 !scroll_handle_obscured(sparms->text.handles)){
2569 SCRLCTRL_S *st = scroll_state(SS_CUR);
2571 for(lp = sparms->text.handles->loc; lp; lp = lp->next)
2572 if(lp->where.row >= st->top_text_line
2573 && lp->where.row < st->top_text_line
2574 + st->screen.length){
2575 cursor_row = lp->where.row - cur_top_line
2576 + SCROLL_LINES_ABOVE(ps_global);
2577 cursor_col = lp->where.col;
2578 break;
2582 if(!lp){
2583 cursor_col = 0;
2584 /* first new line of text */
2585 cursor_row = SCROLL_LINES_ABOVE(ps_global) +
2586 ((cur_top_line == 0) ? 0 : ps_global->viewer_overlap);
2590 else{
2591 cursor_col = 0;
2592 cursor_row = ps_global->ttyo->screen_rows
2593 - SCROLL_LINES_BELOW(ps_global);
2596 MoveCursor(cursor_row, cursor_col);
2598 /*================ Get command and validate =====================*/
2599 #ifdef MOUSE
2600 #ifndef WIN32
2601 if(sparms->text.handles)
2602 #endif
2604 mouse_in_content(KEY_MOUSE, -1, -1, 0x5, 0);
2605 register_mfunc(mouse_in_content, HEADER_ROWS(ps_global), 0,
2606 ps_global->ttyo->screen_rows
2607 - (FOOTER_ROWS(ps_global) + 1),
2608 ps_global->ttyo->screen_cols);
2610 #endif
2611 #ifdef _WINDOWS
2612 mswin_allowcopy(mswin_readscrollbuf);
2613 mswin_setscrollcallback(pcpine_do_scroll);
2615 if(sparms->help.text != NO_HELP)
2616 mswin_sethelptextcallback(pcpine_help_scroll);
2618 if(sparms->text.handles
2619 && sparms->text.handles->type != Folder)
2620 mswin_mousetrackcallback(pcpine_view_cursor);
2622 if(ps_global->prev_screen == mail_view_screen)
2623 mswin_setviewinwindcallback(view_in_new_window);
2624 #endif
2625 ch = (sparms->quell_newmail || read_command_prep()) ? read_command(&utf8str) : NO_OP_COMMAND;
2626 #ifdef MOUSE
2627 #ifndef WIN32
2628 if(sparms->text.handles)
2629 #endif
2630 clear_mfunc(mouse_in_content);
2631 #endif
2632 #ifdef _WINDOWS
2633 mswin_allowcopy(NULL);
2634 mswin_setscrollcallback(NULL);
2635 mswin_sethelptextcallback(NULL);
2636 mswin_mousetrackcallback(NULL);
2637 mswin_setviewinwindcallback(NULL);
2638 cur_top_line = scroll_state(SS_CUR)->top_text_line;
2639 #endif
2641 cmd = menu_command(ch, km);
2643 if(km_popped)
2644 switch(cmd){
2645 case MC_NONE :
2646 case MC_OTHER :
2647 case MC_RESIZE:
2648 case MC_REPAINT :
2649 km_popped++;
2650 break;
2652 default:
2653 clearfooter(ps_global);
2654 break;
2658 /*============= Execute command =======================*/
2659 switch(cmd){
2661 /* ------ Help -------*/
2662 case MC_HELP :
2663 if(FOOTER_ROWS(ps_global) == 1 && km_popped == 0){
2664 km_popped = 2;
2665 ps_global->mangled_footer = 1;
2666 break;
2669 whereis_pos.row = 0;
2670 if(sparms->help.text == NO_HELP){
2671 q_status_message(SM_ORDER, 0, 5,
2672 _("No help text currently available"));
2673 break;
2676 km_size = FOOTER_ROWS(ps_global);
2678 helper(sparms->help.text, sparms->help.title, 0);
2680 if(ps_global->next_screen != main_menu_screen
2681 && km_size == FOOTER_ROWS(ps_global)) {
2682 /* Have to reset because helper uses scroll_text */
2683 num_display_lines = SCROLL_LINES(ps_global);
2684 ps_global->mangled_screen = 1;
2686 else
2687 done = 1;
2689 break;
2692 /*---------- Roll keymenu ------*/
2693 case MC_OTHER :
2694 if(F_OFF(F_USE_FK, ps_global))
2695 warn_other_cmds();
2697 what = NextMenu;
2698 ps_global->mangled_footer = 1;
2699 break;
2702 /* -------- Scroll back one page -----------*/
2703 case MC_PAGEUP :
2704 whereis_pos.row = 0;
2705 if(cur_top_line) {
2706 scroll_lines = MIN(MAX(num_display_lines -
2707 ps_global->viewer_overlap, 1), num_display_lines);
2708 cur_top_line -= scroll_lines;
2709 if(cur_top_line <= 0){
2710 cur_top_line = 0;
2711 q_status_message1(SM_INFO, 0, 1, "START of %s",
2712 STYLE_NAME(sparms));
2715 else{
2716 /* hilite last available handle */
2717 next_handle = NULL;
2718 if(sparms->text.handles){
2719 HANDLE_S *h = sparms->text.handles;
2721 while((h = scroll_handle_prev_sel(h))
2722 && !scroll_handle_obscured(h))
2723 next_handle = h;
2726 if(!next_handle)
2727 q_status_message1(SM_ORDER, 0, 1, _("Already at start of %s"),
2728 STYLE_NAME(sparms));
2733 break;
2736 /*---- Scroll down one page -------*/
2737 case MC_PAGEDN :
2738 if(cur_top_line + num_display_lines < scroll_text_lines()){
2739 whereis_pos.row = 0;
2740 scroll_lines = MIN(MAX(num_display_lines -
2741 ps_global->viewer_overlap, 1), num_display_lines);
2742 cur_top_line += scroll_lines;
2744 if(cur_top_line + num_display_lines >= scroll_text_lines())
2745 q_status_message1(SM_INFO, 0, 1, "END of %s",
2746 STYLE_NAME(sparms));
2748 else if(!sparms->end_scroll
2749 || !(done = (*sparms->end_scroll)(sparms))){
2750 q_status_message1(SM_ORDER, 0, 1, _("Already at end of %s"),
2751 STYLE_NAME(sparms));
2752 /* hilite last available handle */
2753 if(sparms->text.handles){
2754 HANDLE_S *h = sparms->text.handles;
2756 while((h = scroll_handle_next_sel(h)) != NULL)
2757 next_handle = h;
2761 break;
2763 /* scroll to the top page */
2764 case MC_HOMEKEY:
2765 if(cur_top_line){
2766 cur_top_line = 0;
2767 q_status_message1(SM_INFO, 0, 1, "START of %s",
2768 STYLE_NAME(sparms));
2771 next_handle = NULL;
2772 if(sparms->text.handles){
2773 HANDLE_S *h = sparms->text.handles;
2775 while((h = scroll_handle_prev_sel(h)) != NULL)
2776 next_handle = h;
2778 break;
2780 /* scroll to the bottom page */
2781 case MC_ENDKEY:
2782 if(cur_top_line + num_display_lines < scroll_text_lines()){
2783 cur_top_line = scroll_text_lines() - MIN(5, num_display_lines);
2784 q_status_message1(SM_INFO, 0, 1, "END of %s",
2785 STYLE_NAME(sparms));
2788 if(sparms->text.handles){
2789 HANDLE_S *h = sparms->text.handles;
2791 while((h = scroll_handle_next_sel(h)) != NULL)
2792 next_handle = h;
2794 break;
2796 /*------ Scroll down one line -----*/
2797 case MC_CHARDOWN :
2798 next_handle = NULL;
2799 if(sparms->text.handles){
2800 if(sparms->vert_handle){
2801 HANDLE_S *h, *h2;
2802 int i, j, k;
2804 h2 = sparms->text.handles;
2805 if(h2->type == Folder && h2->prev && h2->prev->is_dual_do_open)
2806 h2 = h2->prev;
2808 i = h2->loc->where.row + 1;
2809 j = h2->loc->where.col;
2810 for(h = NULL, k = h2->key;
2811 h2 && (!h
2812 || (h->loc->where.row == h2->loc->where.row));
2813 h2 = h2->next)
2814 /* must be different key */
2815 /* ... below current line */
2816 /* ... pref'bly to left */
2817 if(h2->key != k
2818 && h2->loc->where.row >= i){
2819 if(h2->loc->where.col > j){
2820 if(!h)
2821 h = h2;
2823 break;
2825 else
2826 h = h2;
2829 if(h){
2830 whereis_pos.row = 0;
2831 next_handle = h;
2832 if((result = scroll_handle_obscured(next_handle)) != 0){
2833 long new_top;
2835 if(scroll_handle_obscured(sparms->text.handles)
2836 && result > 0)
2837 next_handle = sparms->text.handles;
2839 ps_global->mangled_body++;
2840 new_top = scroll_handle_reframe(next_handle->key,0);
2841 if(new_top >= 0)
2842 cur_top_line = new_top;
2846 else if(!(ch == ctrl('N') || F_ON(F_FORCE_ARROWS, ps_global)))
2847 next_handle = scroll_handle_next(sparms->text.handles);
2850 if(!next_handle){
2851 if(cur_top_line + num_display_lines < scroll_text_lines()){
2852 whereis_pos.row = 0;
2853 cur_top_line++;
2854 if(cur_top_line + num_display_lines >= scroll_text_lines())
2855 q_status_message1(SM_INFO, 0, 1, "END of %s",
2856 STYLE_NAME(sparms));
2858 else
2859 q_status_message1(SM_ORDER, 0, 1, _("Already at end of %s"),
2860 STYLE_NAME(sparms));
2863 break;
2866 /* ------ Scroll back up one line -------*/
2867 case MC_CHARUP :
2868 next_handle = NULL;
2869 if(sparms->text.handles){
2870 if(sparms->vert_handle){
2871 HANDLE_S *h, *h2;
2872 int i, j, k;
2874 h2 = sparms->text.handles;
2875 if(h2->type == Folder && h2->prev && h2->prev->is_dual_do_open)
2876 h2 = h2->prev;
2878 i = h2->loc->where.row - 1;
2879 j = h2->loc->where.col;
2881 for(h = NULL, k = h2->key;
2882 h2 && (!h
2883 || (h->loc->where.row == h2->loc->where.row));
2884 h2 = h2->prev)
2885 /* must be new key, above current
2886 * line and pref'bly to right
2888 if(h2->key != k
2889 && h2->loc->where.row <= i){
2890 if(h2->loc->where.col < j){
2891 if(!h)
2892 h = h2;
2894 break;
2896 else
2897 h = h2;
2900 if(h){
2901 whereis_pos.row = 0;
2902 next_handle = h;
2903 if((result = scroll_handle_obscured(next_handle)) != 0){
2904 long new_top;
2906 if(scroll_handle_obscured(sparms->text.handles)
2907 && result < 0)
2908 next_handle = sparms->text.handles;
2910 ps_global->mangled_body++;
2911 new_top = scroll_handle_reframe(next_handle->key,0);
2912 if(new_top >= 0)
2913 cur_top_line = new_top;
2917 else if(!(ch == ctrl('P') || F_ON(F_FORCE_ARROWS, ps_global)))
2918 next_handle = scroll_handle_prev(sparms->text.handles);
2921 if(!next_handle){
2922 whereis_pos.row = 0;
2923 if(cur_top_line){
2924 cur_top_line--;
2925 if(cur_top_line == 0)
2926 q_status_message1(SM_INFO, 0, 1, "START of %s",
2927 STYLE_NAME(sparms));
2929 else
2930 q_status_message1(SM_ORDER, 0, 1,
2931 _("Already at start of %s"),
2932 STYLE_NAME(sparms));
2935 break;
2938 case MC_NEXT_HANDLE :
2939 if((next_handle = scroll_handle_next_sel(sparms->text.handles)) != NULL){
2940 whereis_pos.row = 0;
2941 if((result = scroll_handle_obscured(next_handle)) != 0){
2942 long new_top;
2944 if(scroll_handle_obscured(sparms->text.handles)
2945 && result > 0)
2946 next_handle = sparms->text.handles;
2948 ps_global->mangled_body++;
2949 new_top = scroll_handle_reframe(next_handle->key, 0);
2950 if(new_top >= 0)
2951 cur_top_line = new_top;
2954 else{
2955 if(scroll_handle_obscured(sparms->text.handles)){
2956 long new_top;
2958 ps_global->mangled_body++;
2959 if((new_top = scroll_handle_reframe(-1, 0)) >= 0){
2960 whereis_pos.row = 0;
2961 cur_top_line = new_top;
2965 q_status_message1(SM_ORDER, 0, 1,
2966 _("Already on last item in %s"),
2967 STYLE_NAME(sparms));
2970 break;
2973 case MC_PREV_HANDLE :
2974 if((next_handle = scroll_handle_prev_sel(sparms->text.handles)) != NULL){
2975 whereis_pos.row = 0;
2976 if((result = scroll_handle_obscured(next_handle)) != 0){
2977 long new_top;
2979 if(scroll_handle_obscured(sparms->text.handles)
2980 && result < 0)
2981 next_handle = sparms->text.handles;
2983 ps_global->mangled_body++;
2984 new_top = scroll_handle_reframe(next_handle->key, 0);
2985 if(new_top >= 0)
2986 cur_top_line = new_top;
2989 else{
2990 if(scroll_handle_obscured(sparms->text.handles)){
2991 long new_top;
2993 ps_global->mangled_body++;
2994 if((new_top = scroll_handle_reframe(-1, 0)) >= 0){
2995 whereis_pos.row = 0;
2996 cur_top_line = new_top;
3000 q_status_message1(SM_ORDER, 0, 1,
3001 _("Already on first item in %s"),
3002 STYLE_NAME(sparms));
3005 break;
3008 /*------ View the current handle ------*/
3009 case MC_VIEW_HANDLE :
3010 switch(scroll_handle_obscured(sparms->text.handles)){
3011 default :
3012 case 0 :
3013 switch(scroll_handle_launch(sparms->text.handles,
3014 sparms->text.handles->force_display)){
3015 case 1 :
3016 cmd = MC_EXIT; /* propagate */
3017 done = 1;
3018 break;
3020 case -1 :
3021 cmd_cancelled(NULL);
3022 break;
3024 default :
3025 break;
3028 cur_top_line = scroll_state(SS_CUR)->top_text_line;
3029 break;
3031 case 1 :
3032 q_status_message(SM_ORDER, 0, 2, HANDLE_BELOW_ERR);
3033 break;
3035 case -1 :
3036 q_status_message(SM_ORDER, 0, 2, HANDLE_ABOVE_ERR);
3037 break;
3040 break;
3042 /*---------- Search text (where is) ----------*/
3043 case MC_WHEREIS :
3044 ps_global->mangled_footer = 1;
3045 {long start_row;
3046 int start_index, key = 0;
3047 char *report = NULL;
3049 start_row = cur_top_line;
3050 start_index = 0;
3052 if(F_ON(F_SHOW_CURSOR,ps_global)){
3053 if(found_on < 0
3054 || found_on >= scroll_text_lines()
3055 || found_on < cur_top_line
3056 || found_on >= cur_top_line + num_display_lines){
3057 start_row = cur_top_line;
3058 start_index = 0;
3060 else{
3061 if(found_on_index < 0){
3062 start_row = found_on + 1;
3063 start_index = 0;
3065 else{
3066 start_row = found_on;
3067 start_index = found_on_index+1;
3071 else if(sparms->srch_handle){
3072 HANDLE_S *h;
3074 if((h = scroll_handle_next_sel(sparms->text.handles)) != NULL){
3076 * Translate the screen's column into the
3077 * line offset to start on...
3079 * This makes it so search_text never returns -3
3080 * so we don't know it is the same match. That's
3081 * because we start well after the current handle
3082 * (at the next handle) and that causes us to
3083 * think the one we just matched on is a different
3084 * one from before. Can't think of an easy way to
3085 * fix it, though, and it isn't a big deal. We still
3086 * match, we just don't say current line contains
3087 * the only match.
3089 start_row = h->loc->where.row;
3090 start_index = scroll_handle_index(start_row, h->loc->where.col);
3092 else{
3093 /* last handle, start over at top */
3094 start_row = cur_top_line;
3095 start_index = 0;
3098 else{
3099 start_row = (found_on < 0
3100 || found_on >= scroll_text_lines()
3101 || found_on < cur_top_line
3102 || found_on >= cur_top_line + num_display_lines)
3103 ? cur_top_line : found_on + 1,
3104 start_index = 0;
3107 found_on = search_text(-FOOTER_ROWS(ps_global), start_row,
3108 start_index, &report,
3109 &whereis_pos, &found_on_index);
3111 if(found_on == -4){ /* search to top of text */
3112 whereis_pos.row = 0;
3113 whereis_pos.col = 0;
3114 found_on = 0;
3115 if(sparms->text.handles && sparms->srch_handle)
3116 key = 1;
3118 else if(found_on == -5){ /* search to bottom of text */
3119 HANDLE_S *h;
3121 whereis_pos.row = MAX(scroll_text_lines() - 1, 0);
3122 whereis_pos.col = 0;
3123 found_on = whereis_pos.row;
3124 if((h = sparms->text.handles) && sparms->srch_handle)
3126 key = h->key;
3127 while((h = h->next) != NULL);
3129 else if(found_on == -3){
3130 whereis_pos.row = found_on = start_row;
3131 found_on_index = start_index - 1;
3132 q_status_message(SM_ORDER, 1, 3,
3133 _("Current line contains the only match"));
3136 if(found_on >= 0){
3137 result = found_on < cur_top_line;
3138 if(!key)
3139 key = (sparms->text.handles)
3140 ? dot_on_handle(found_on, whereis_pos.col) : 0;
3142 if(F_ON(F_FORCE_LOW_SPEED,ps_global)
3143 || ps_global->low_speed
3144 || F_ON(F_SHOW_CURSOR,ps_global)
3145 || key){
3146 if((found_on >= cur_top_line + num_display_lines ||
3147 found_on < cur_top_line) &&
3148 num_display_lines > ps_global->viewer_overlap){
3149 cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
3150 if(scroll_text_lines()-cur_top_line < 5)
3151 cur_top_line = MAX(0,
3152 scroll_text_lines()-MIN(5,num_display_lines));
3154 /* else leave cur_top_line alone */
3156 else{
3157 cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
3158 if(scroll_text_lines()-cur_top_line < 5)
3159 cur_top_line = MAX(0,
3160 scroll_text_lines()-MIN(5,num_display_lines));
3163 whereis_pos.row = whereis_pos.row - cur_top_line + 1;
3164 if(report)
3165 q_status_message(SM_ORDER, 0, 3, report);
3166 else
3167 q_status_message2(SM_ORDER, 0, 3,
3168 "%sFound on line %s on screen",
3169 result ? "Search wrapped to start. " : "",
3170 int2string(whereis_pos.row));
3172 if(key){
3173 if(sparms->text.handles->key < key)
3174 for(next_handle = sparms->text.handles->next;
3175 next_handle->key != key;
3176 next_handle = next_handle->next)
3178 else
3179 for(next_handle = sparms->text.handles;
3180 next_handle->key != key;
3181 next_handle = next_handle->prev)
3185 else if(found_on == -1)
3186 cmd_cancelled("Search");
3187 else
3188 q_status_message(SM_ORDER, 0, 3, _("Word not found"));
3191 break;
3194 /*-------------- jump command -------------*/
3195 /* NOTE: preempt the process_cmd() version because
3196 * we need to get at the number..
3198 case MC_JUMP :
3199 jn = jump_to(ps_global->msgmap, -FOOTER_ROWS(ps_global), ch,
3200 sparms, View);
3201 if(sparms && sparms->jump_is_debug)
3202 done = 1;
3203 else if(jn > 0 && jn != mn_get_cur(ps_global->msgmap)){
3205 if(mn_total_cur(ps_global->msgmap) > 1L)
3206 mn_reset_cur(ps_global->msgmap, jn);
3207 else
3208 mn_set_cur(ps_global->msgmap, jn);
3210 done = 1;
3212 else
3213 ps_global->mangled_footer = 1;
3215 break;
3218 #ifdef MOUSE
3219 /*-------------- Mouse Event -------------*/
3220 case MC_MOUSE:
3222 MOUSEPRESS mp;
3223 long line;
3224 int key;
3226 mouse_get_last (NULL, &mp);
3227 mp.row -= 2;
3229 /* The clicked line have anything special on it? */
3230 if((line = cur_top_line + mp.row) < scroll_text_lines()
3231 && (key = dot_on_handle(line, mp.col))){
3232 switch(mp.button){
3233 case M_BUTTON_RIGHT :
3234 #ifdef _WINDOWS
3235 if(sparms->mouse.popup){
3236 if(sparms->text.handles->key < key)
3237 for(next_handle = sparms->text.handles->next;
3238 next_handle->key != key;
3239 next_handle = next_handle->next)
3241 else
3242 for(next_handle = sparms->text.handles;
3243 next_handle->key != key;
3244 next_handle = next_handle->prev)
3247 if(sparms->mouse.popup){
3248 cur_top_line = scroll_scroll_text(cur_top_line,
3249 next_handle,
3250 ps_global->mangled_body);
3251 fflush(stdout);
3252 switch((*sparms->mouse.popup)(sparms, key)){
3253 case 1 :
3254 cur_top_line = doubleclick_handle(sparms, next_handle, &cmd, &done);
3255 break;
3257 case 2 :
3258 done++;
3259 break;
3264 #endif
3265 break;
3267 case M_BUTTON_LEFT :
3268 if(sparms->text.handles->key < key)
3269 for(next_handle = sparms->text.handles->next;
3270 next_handle->key != key;
3271 next_handle = next_handle->next)
3273 else
3274 for(next_handle = sparms->text.handles;
3275 next_handle->key != key;
3276 next_handle = next_handle->prev)
3279 if(mp.doubleclick) /* launch url */
3280 cur_top_line = doubleclick_handle(sparms, next_handle, &cmd, &done);
3281 else if(sparms->mouse.click)
3282 (*sparms->mouse.click)(sparms);
3284 break;
3286 case M_BUTTON_MIDDLE : /* NO-OP for now */
3287 break;
3289 default: /* just ignore */
3290 break;
3293 #ifdef _WINDOWS
3294 else if(mp.button == M_BUTTON_RIGHT){
3296 * Toss generic popup on to the screen
3298 if(sparms->mouse.popup)
3299 if((*sparms->mouse.popup)(sparms, 0) == 2){
3300 done++;
3303 #endif
3306 break;
3307 #endif /* MOUSE */
3310 /*-------------- Display Resize -------------*/
3311 case MC_RESIZE :
3312 if(sparms->resize_exit){
3313 long line;
3316 * Figure out char offset of the char in the top left
3317 * corner of the display. Pass it back to the
3318 * fetcher/formatter and have it pass the offset
3319 * back to us...
3321 sparms->start.on = Offset;
3322 for(sparms->start.loc.offset = line = 0L;
3323 line < cur_top_line;
3324 line++)
3325 sparms->start.loc.offset += scroll_handle_column(line, -1);
3327 done = 1;
3328 ClearLine(1);
3329 break;
3331 /* else no reformatting neccessary, fall thru to repaint */
3334 /*-------------- refresh -------------*/
3335 case MC_REPAINT :
3336 num_display_lines = SCROLL_LINES(ps_global);
3337 mark_status_dirty();
3338 mark_keymenu_dirty();
3339 mark_titlebar_dirty();
3340 ps_global->mangled_screen = 1;
3341 force = 1;
3342 break;
3345 /*------- no op timeout to check for new mail ------*/
3346 case MC_NONE :
3347 break;
3350 /*------- Forward displayed text ------*/
3351 case MC_FWDTEXT :
3352 forward_text(ps_global, sparms->text.text, sparms->text.src);
3353 break;
3356 /*----------- Save the displayed text ------------*/
3357 case MC_SAVETEXT :
3358 (void)simple_export(ps_global, sparms->text.text,
3359 sparms->text.src, "text", NULL);
3360 break;
3363 /*----------- Exit this screen ------------*/
3364 case MC_EXIT :
3365 done = 1;
3366 break;
3369 /*----------- Pop back to the Main Menu ------------*/
3370 case MC_MAIN :
3371 ps_global->next_screen = main_menu_screen;
3372 done = 1;
3373 break;
3376 /*----------- Print ------------*/
3377 case MC_PRINTTXT :
3378 print_to_printer(sparms);
3379 break;
3382 /* ------- First handle on Line ------ */
3383 case MC_GOTOBOL :
3384 if(sparms->text.handles){
3385 next_handle = scroll_handle_boundary(sparms->text.handles,
3386 scroll_handle_prev_sel);
3388 break;
3390 /* fall thru as bogus */
3392 /* ------- Last handle on Line ------ */
3393 case MC_GOTOEOL :
3394 if(sparms->text.handles){
3395 next_handle = scroll_handle_boundary(sparms->text.handles,
3396 scroll_handle_next_sel);
3398 break;
3400 /* fall thru as bogus */
3402 /*------- BOGUS INPUT ------*/
3403 case MC_CHARRIGHT :
3404 case MC_CHARLEFT :
3405 case MC_UNKNOWN :
3406 if(sparms->bogus_input)
3407 done = (*sparms->bogus_input)(ch);
3408 else
3409 bogus_command(ch, F_ON(F_USE_FK,ps_global) ? "F1" : "?");
3411 break;
3414 case MC_UTF8:
3415 bogus_utf8_command(utf8str, F_ON(F_USE_FK, ps_global) ? "F1" : "?");
3416 break;
3419 /*------- Standard commands ------*/
3420 default:
3421 whereis_pos.row = 0;
3422 if(sparms->proc.tool)
3423 result = (*sparms->proc.tool)(cmd, ps_global->msgmap, sparms);
3424 else
3425 result = process_cmd(ps_global, ps_global->mail_stream,
3426 ps_global->msgmap, cmd, View, &force);
3428 dprint((7, "PROCESS_CMD return: %d\n", result));
3430 if(ps_global->next_screen != SCREEN_FUN_NULL || result == 1){
3431 done = 1;
3432 if(cmd == MC_FULLHDR){
3433 if(ps_global->full_header == 1){
3434 long line;
3437 * Figure out char offset of the char in the top left
3438 * corner of the display. Pass it back to the
3439 * fetcher/formatter and have it pass the offset
3440 * back to us...
3442 sparms->start.on = Offset;
3443 for(sparms->start.loc.offset = line = 0L;
3444 line < cur_top_line;
3445 line++)
3446 sparms->start.loc.offset +=
3447 scroll_handle_column(line, -1);
3449 else
3450 sparms->start.on = 0;
3452 switch(km->which){
3453 case 0:
3454 sparms->keys.what = FirstMenu;
3455 break;
3456 case 1:
3457 sparms->keys.what = SecondMenu;
3458 break;
3459 case 2:
3460 sparms->keys.what = ThirdMenu;
3461 break;
3462 case 3:
3463 sparms->keys.what = FourthMenu;
3464 break;
3468 else if(!scroll_state(SS_CUR)){
3469 num_display_lines = SCROLL_LINES(ps_global);
3470 ps_global->mangled_screen = 1;
3473 break;
3475 } /* End of switch() */
3477 /* Need to frame some handles? */
3478 if(sparms->text.handles
3479 && ((!next_handle
3480 && handle_on_page(sparms->text.handles, cur_top_line,
3481 cur_top_line + num_display_lines))
3482 || (next_handle
3483 && handle_on_page(next_handle, cur_top_line,
3484 cur_top_line + num_display_lines))))
3485 next_handle = scroll_handle_in_frame(cur_top_line);
3487 } /* End of while() -- loop executing commands */
3489 ps_global->redrawer = NULL; /* next statement makes this invalid! */
3490 zero_scroll_text(); /* very important to zero out on return!!! */
3491 scroll_state(SS_FREE);
3492 if(sparms->bar.color)
3493 free_color_pair(&sparms->bar.color);
3495 #ifdef _WINDOWS
3496 scroll_setrange(0L, 0L);
3497 #endif
3498 return(cmd);
3502 /*----------------------------------------------------------------------
3503 Print text on paper
3505 Args: text -- The text to print out
3506 source -- What type of source text is
3507 message -- Message for open_printer()
3508 Handling of error conditions is very poor.
3510 ----*/
3511 static int
3512 print_to_printer(SCROLL_S *sparms)
3514 char message[64];
3516 snprintf(message, sizeof(message), "%s", STYLE_NAME(sparms));
3517 message[sizeof(message)-1] = '\0';
3519 if(open_printer(message) != 0)
3520 return(-1);
3522 switch(sparms->text.src){
3523 case CharStar :
3524 if(sparms->text.text != (char *)NULL)
3525 print_text((char *)sparms->text.text);
3527 break;
3529 case CharStarStar :
3530 if(sparms->text.text != (char **)NULL){
3531 register char **t;
3533 for(t = sparms->text.text; *t != NULL; t++){
3534 print_text(*t);
3535 print_text(NEWLINE);
3539 break;
3541 case FileStar :
3542 if(sparms->text.text != (FILE *)NULL) {
3543 size_t n;
3544 int i;
3546 fseek((FILE *)sparms->text.text, 0L, 0);
3547 n = SIZEOF_20KBUF - 1;
3548 while((i = fread((void *)tmp_20k_buf, sizeof(char),
3549 n, (FILE *)sparms->text.text)) != 0) {
3550 tmp_20k_buf[i] = '\0';
3551 print_text(tmp_20k_buf);
3555 default :
3556 break;
3559 close_printer();
3560 return(0);
3564 /*----------------------------------------------------------------------
3565 Search text being viewed (help or message)
3567 Args: q_line -- The screen line to prompt for search string on
3568 start_line -- Line number in text to begin search on
3569 start_index -- Where to begin search at in first line of text
3570 cursor_pos -- position of cursor is returned to caller here
3571 (Actually, this isn't really the position of the
3572 cursor because we don't know where we are on the
3573 screen. So row is set to the line number and col
3574 is set to the right column.)
3575 offset_in_line -- Offset where match was found.
3577 Result: returns line number string was found on
3578 -1 for cancel
3579 -2 if not found
3580 -3 if only match is at start_index - 1
3581 -4 if search to first line
3582 -5 if search to last line
3583 ---*/
3585 search_text(int q_line, long int start_line, int start_index, char **report,
3586 Pos *cursor_pos, int *offset_in_line)
3588 char prompt[MAX_SEARCH+50], nsearch_string[MAX_SEARCH+1], *p;
3589 HelpType help;
3590 int rc, flags;
3591 static HISTORY_S *history = NULL;
3592 char search_string[MAX_SEARCH+1];
3593 static ESCKEY_S word_search_key[] = { { 0, 0, "", "" },
3594 {ctrl('Y'), 10, "^Y", N_("First Line")},
3595 {ctrl('V'), 11, "^V", N_("Last Line")},
3596 {KEY_UP, 30, "", ""},
3597 {KEY_DOWN, 31, "", ""},
3598 {-1, 0, NULL, NULL}
3600 #define KU_ST (3) /* index of KEY_UP */
3602 init_hist(&history, HISTSIZE);
3605 * Put the last one used in the default search_string,
3606 * not in nsearch_string.
3608 search_string[0] = '\0';
3609 if((p = get_prev_hist(history, "", 0, NULL)) != NULL){
3610 strncpy(search_string, p, sizeof(search_string));
3611 search_string[sizeof(search_string)-1] = '\0';
3614 snprintf(prompt, sizeof(prompt), _("Word to search for [%s] : "), search_string);
3615 help = NO_HELP;
3616 nsearch_string[0] = '\0';
3618 while(1) {
3619 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE | OE_KEEP_TRAILING_SPACE;
3622 * 2 is really 1 because there will be one real entry and
3623 * one entry of "" because of the get_prev_hist above.
3625 if(items_in_hist(history) > 2){
3626 word_search_key[KU_ST].name = HISTORY_UP_KEYNAME;
3627 word_search_key[KU_ST].label = HISTORY_KEYLABEL;
3628 word_search_key[KU_ST+1].name = HISTORY_DOWN_KEYNAME;
3629 word_search_key[KU_ST+1].label = HISTORY_KEYLABEL;
3631 else{
3632 word_search_key[KU_ST].name = "";
3633 word_search_key[KU_ST].label = "";
3634 word_search_key[KU_ST+1].name = "";
3635 word_search_key[KU_ST+1].label = "";
3638 rc = optionally_enter(nsearch_string, q_line, 0, sizeof(nsearch_string),
3639 prompt, word_search_key, help, &flags);
3641 if(rc == 3) {
3642 help = help == NO_HELP ? h_oe_searchview : NO_HELP;
3643 continue;
3645 else if(rc == 10){
3646 if(report)
3647 *report = _("Searched to First Line.");
3649 return(-4);
3651 else if(rc == 11){
3652 if(report)
3653 *report = _("Searched to Last Line.");
3655 return(-5);
3657 else if(rc == 30){
3658 if((p = get_prev_hist(history, nsearch_string, 0, NULL)) != NULL){
3659 strncpy(nsearch_string, p, sizeof(nsearch_string));
3660 nsearch_string[sizeof(nsearch_string)-1] = '\0';
3662 else
3663 Writechar(BELL, 0);
3665 continue;
3667 else if(rc == 31){
3668 if((p = get_next_hist(history, nsearch_string, 0, NULL)) != NULL){
3669 strncpy(nsearch_string, p, sizeof(nsearch_string));
3670 nsearch_string[sizeof(nsearch_string)-1] = '\0';
3672 else
3673 Writechar(BELL, 0);
3675 continue;
3678 if(rc != 4){ /* 4 is redraw */
3679 save_hist(history, nsearch_string, 0, NULL);
3680 break;
3684 if(rc == 1 || (search_string[0] == '\0' && nsearch_string[0] == '\0'))
3685 return(-1);
3687 if(nsearch_string[0] != '\0'){
3688 strncpy(search_string, nsearch_string, sizeof(search_string)-1);
3689 search_string[sizeof(search_string)-1] = '\0';
3692 rc = search_scroll_text(start_line, start_index, search_string, cursor_pos,
3693 offset_in_line);
3694 return(rc);
3698 /*----------------------------------------------------------------------
3699 Update the scroll tool's titlebar
3701 Args: cur_top_line --
3702 redraw -- flag to force updating
3704 ----*/
3705 void
3706 update_scroll_titlebar(long int cur_top_line, int redraw)
3708 SCRLCTRL_S *st = scroll_state(SS_CUR);
3709 int num_display_lines = SCROLL_LINES(ps_global);
3710 long new_line = (cur_top_line + num_display_lines > st->num_lines)
3711 ? st->num_lines
3712 : cur_top_line + num_display_lines;
3713 long raw_msgno;
3714 COLOR_PAIR *returned_color = NULL;
3715 COLOR_PAIR *titlecolor = NULL;
3716 int colormatch;
3717 SEARCHSET *ss = NULL;
3719 if(st->parms->use_indexline_color
3720 && ps_global->titlebar_color_style != TBAR_COLOR_DEFAULT){
3721 raw_msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
3722 if(raw_msgno > 0L && ps_global->mail_stream
3723 && raw_msgno <= ps_global->mail_stream->nmsgs){
3724 ss = mail_newsearchset();
3725 ss->first = ss->last = (unsigned long) raw_msgno;
3728 if(ss){
3729 PAT_STATE *pstate = NULL;
3731 colormatch = get_index_line_color(ps_global->mail_stream,
3732 ss, &pstate, &returned_color);
3733 mail_free_searchset(&ss);
3736 * This is a bit tricky. If there is a colormatch but returned_color
3737 * is NULL, that means that the user explicitly wanted the
3738 * Normal color used in this index line, so that is what we
3739 * use. If no colormatch then we will use the TITLE color
3740 * instead of Normal.
3742 if(colormatch){
3743 if(returned_color)
3744 titlecolor = returned_color;
3745 else
3746 titlecolor = new_color_pair(ps_global->VAR_NORM_FORE_COLOR,
3747 ps_global->VAR_NORM_BACK_COLOR);
3750 if(titlecolor
3751 && ps_global->titlebar_color_style == TBAR_COLOR_REV_INDEXLINE){
3752 char cbuf[MAXCOLORLEN+1];
3754 strncpy(cbuf, titlecolor->fg, MAXCOLORLEN);
3755 strncpy(titlecolor->fg, titlecolor->bg, MAXCOLORLEN);
3756 strncpy(titlecolor->bg, cbuf, MAXCOLORLEN);
3760 /* Did the color change? */
3761 if((!titlecolor && st->parms->bar.color)
3763 (titlecolor && !st->parms->bar.color)
3765 (titlecolor && st->parms->bar.color
3766 && (strcmp(titlecolor->fg, st->parms->bar.color->fg)
3767 || strcmp(titlecolor->bg, st->parms->bar.color->bg)))){
3769 redraw++;
3770 if(st->parms->bar.color)
3771 free_color_pair(&st->parms->bar.color);
3773 st->parms->bar.color = titlecolor;
3774 titlecolor = NULL;
3777 if(titlecolor)
3778 free_color_pair(&titlecolor);
3782 if(redraw){
3783 set_titlebar(st->parms->bar.title, ps_global->mail_stream,
3784 ps_global->context_current, ps_global->cur_folder,
3785 ps_global->msgmap, 1, st->parms->bar.style,
3786 new_line, st->num_lines, st->parms->bar.color);
3787 ps_global->mangled_header = 0;
3789 else if(st->parms->bar.style == TextPercent)
3790 update_titlebar_lpercent(new_line);
3791 else
3792 update_titlebar_percent(new_line);
3796 /*----------------------------------------------------------------------
3797 manager of global (to this module, anyway) scroll state structures
3800 ----*/
3801 SCRLCTRL_S *
3802 scroll_state(int func)
3804 struct scrollstack {
3805 SCRLCTRL_S s;
3806 struct scrollstack *prev;
3807 } *s;
3808 static struct scrollstack *stack = NULL;
3810 switch(func){
3811 case SS_CUR: /* no op */
3812 break;
3813 case SS_NEW:
3814 s = (struct scrollstack *)fs_get(sizeof(struct scrollstack));
3815 memset((void *)s, 0, sizeof(struct scrollstack));
3816 s->prev = stack;
3817 stack = s;
3818 break;
3819 case SS_FREE:
3820 if(stack){
3821 s = stack->prev;
3822 fs_give((void **)&stack);
3823 stack = s;
3825 break;
3826 default: /* BUG: should complain */
3827 break;
3830 return(stack ? &stack->s : NULL);
3834 /*----------------------------------------------------------------------
3835 Save all the data for scrolling text and paint the screen
3838 ----*/
3839 void
3840 set_scroll_text(SCROLL_S *sparms, long int current_line, SCRLCTRL_S *st)
3842 /* save all the stuff for possible asynchronous redraws */
3843 st->parms = sparms;
3844 st->top_text_line = current_line;
3845 st->screen.start_line = SCROLL_LINES_ABOVE(ps_global);
3846 st->screen.other_lines = SCROLL_LINES_ABOVE(ps_global)
3847 + SCROLL_LINES_BELOW(ps_global);
3848 st->screen.width = -1; /* Force text formatting calculation */
3852 /*----------------------------------------------------------------------
3853 Redraw the text on the screen, possibly reformatting if necessary
3855 Args None
3857 ----*/
3858 void
3859 redraw_scroll_text(void)
3861 int i, offset;
3862 SCRLCTRL_S *st = scroll_state(SS_CUR);
3864 format_scroll_text();
3866 offset = (st->parms->text.src == FileStar) ? 0 : st->top_text_line;
3868 #ifdef _WINDOWS
3869 mswin_beginupdate();
3870 #endif
3871 /*---- Actually display the text on the screen ------*/
3872 for(i = 0; i < st->screen.length; i++){
3873 ClearLine(i + st->screen.start_line);
3874 if((offset + i) < st->num_lines)
3875 PutLine0n8b(i + st->screen.start_line, 0, st->text_lines[offset + i],
3876 st->line_lengths[offset + i], st->parms->text.handles);
3880 fflush(stdout);
3881 #ifdef _WINDOWS
3882 mswin_endupdate();
3883 #endif
3887 /*----------------------------------------------------------------------
3888 Free memory used as scrolling buffers for text on disk. Also mark
3889 text_lines as available
3890 ----*/
3891 void
3892 zero_scroll_text(void)
3894 SCRLCTRL_S *st = scroll_state(SS_CUR);
3895 register int i;
3897 for(i = 0; i < st->lines_allocated; i++)
3898 if(st->parms->text.src == FileStar && st->text_lines[i])
3899 fs_give((void **)&st->text_lines[i]);
3900 else
3901 st->text_lines[i] = NULL;
3903 if(st->parms->text.src == FileStar && st->findex != NULL){
3904 fclose(st->findex);
3905 st->findex = NULL;
3906 if(st->fname){
3907 our_unlink(st->fname);
3908 fs_give((void **)&st->fname);
3912 if(st->text_lines)
3913 fs_give((void **)&st->text_lines);
3915 if(st->line_lengths)
3916 fs_give((void **) &st->line_lengths);
3920 /*----------------------------------------------------------------------
3922 Always format at least 20 chars wide. Wrapping lines would be crazy for
3923 screen widths of 1-20 characters
3924 ----*/
3925 void
3926 format_scroll_text(void)
3928 int i;
3929 char *p, **pp;
3930 SCRLCTRL_S *st = scroll_state(SS_CUR);
3931 register short *ll;
3932 register char **tl, **tl_end;
3934 if(!st || (st->screen.width == (i = ps_global->ttyo->screen_cols)
3935 && st->screen.length == PGSIZE(st)))
3936 return;
3938 st->screen.width = MAX(20, i);
3939 st->screen.length = PGSIZE(st);
3941 if(st->lines_allocated == 0) {
3942 st->lines_allocated = TYPICAL_BIG_MESSAGE_LINES;
3943 st->text_lines = (char **)fs_get(st->lines_allocated *sizeof(char *));
3944 memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
3945 st->line_lengths = (short *)fs_get(st->lines_allocated *sizeof(short));
3948 tl = st->text_lines;
3949 ll = st->line_lengths;
3950 tl_end = &st->text_lines[st->lines_allocated];
3952 if(st->parms->text.src == CharStarStar) {
3953 /*---- original text is already list of lines -----*/
3954 /* The text could be wrapped nicely for narrow screens; for now
3955 it will get truncated as it is displayed */
3956 for(pp = (char **)st->parms->text.text; *pp != NULL;) {
3957 *tl++ = *pp++;
3958 *ll++ = st->screen.width;
3959 if(tl >= tl_end) {
3960 i = tl - st->text_lines;
3961 st->lines_allocated *= 2;
3962 fs_resize((void **)&st->text_lines,
3963 st->lines_allocated * sizeof(char *));
3964 fs_resize((void **)&st->line_lengths,
3965 st->lines_allocated*sizeof(short));
3966 tl = &st->text_lines[i];
3967 ll = &st->line_lengths[i];
3968 tl_end = &st->text_lines[st->lines_allocated];
3972 st->num_lines = tl - st->text_lines;
3974 else if (st->parms->text.src == CharStar) {
3975 /*------ Format the plain text ------*/
3976 for(p = (char *)st->parms->text.text; *p; ) {
3977 *tl = p;
3979 for(; *p && !(*p == RETURN || *p == LINE_FEED); p++)
3982 *ll = p - *tl;
3983 ll++; tl++;
3984 if(tl >= tl_end) {
3985 i = tl - st->text_lines;
3986 st->lines_allocated *= 2;
3987 fs_resize((void **)&st->text_lines,
3988 st->lines_allocated * sizeof(char *));
3989 fs_resize((void **)&st->line_lengths,
3990 st->lines_allocated*sizeof(short));
3991 tl = &st->text_lines[i];
3992 ll = &st->line_lengths[i];
3993 tl_end = &st->text_lines[st->lines_allocated];
3996 if(*p == '\r' && *(p+1) == '\n')
3997 p += 2;
3998 else if(*p == '\n' || *p == '\r')
3999 p++;
4002 st->num_lines = tl - st->text_lines;
4004 else {
4005 /*------ Display text is in a file --------*/
4008 * This is pretty much only useful under DOS where we can't fit
4009 * all of big messages in core at once. This scheme makes
4010 * some simplifying assumptions:
4011 * 1. Lines are on disk just the way we'll display them. That
4012 * is, line breaks and such are left to the function that
4013 * writes the disk file to catch and fix.
4014 * 2. We get away with this mainly because the DOS display isn't
4015 * going to be resized out from under us.
4017 * The idea is to use the already alloc'd array of char * as a
4018 * buffer for sections of what's on disk. We'll set up the first
4019 * few lines here, and read new ones in as needed in
4020 * scroll_scroll_text().
4022 * but first, make sure there are enough buffer lines allocated
4023 * to serve as a place to hold lines from the file.
4025 * Actually, this is also used under windows so the display will
4026 * be resized out from under us. So I changed the following
4027 * to always
4028 * 1. free old text_lines, which may have been allocated
4029 * for a narrow screen.
4030 * 2. insure we have enough text_lines
4031 * 3. reallocate all text_lines that are needed.
4032 * (tom unger 10/26/94)
4035 /* free old text lines, which may be too short. */
4036 for(i = 0; i < st->lines_allocated; i++)
4037 if(st->text_lines[i]) /* clear alloc'd lines */
4038 fs_give((void **)&st->text_lines[i]);
4040 /* Insure we have enough text lines. */
4041 if(st->lines_allocated < (2 * PGSIZE(st)) + 1){
4042 st->lines_allocated = (2 * PGSIZE(st)) + 1; /* resize */
4044 fs_resize((void **)&st->text_lines,
4045 st->lines_allocated * sizeof(char *));
4046 memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
4047 fs_resize((void **)&st->line_lengths,
4048 st->lines_allocated*sizeof(short));
4051 /* reallocate all text lines that are needed. */
4052 for(i = 0; i <= PGSIZE(st); i++)
4053 if(st->text_lines[i] == NULL)
4054 st->text_lines[i] = (char *)fs_get((st->screen.width + 1)
4055 * sizeof(char));
4057 tl = &st->text_lines[i];
4059 st->num_lines = make_file_index();
4061 ScrollFile(st->top_text_line); /* then load them up */
4065 * Efficiency hack. If there are handles, fill in their
4066 * line number field for later...
4068 if(st->parms->text.handles){
4069 long line;
4070 int i, col, n, key;
4071 HANDLE_S *h;
4073 for(line = 0; line < st->num_lines; line++)
4074 for(i = 0, col = 0; i < st->line_lengths[line];)
4075 switch(st->text_lines[line][i]){
4076 case TAG_EMBED:
4077 i++;
4078 switch((i < st->line_lengths[line]) ? st->text_lines[line][i]
4079 : 0){
4080 case TAG_HANDLE:
4081 for(key = 0, n = st->text_lines[line][++i]; n > 0; n--)
4082 key = (key * 10) + (st->text_lines[line][++i] - '0');
4084 i++;
4085 for(h = st->parms->text.handles; h; h = h->next)
4086 if(h->key == key){
4087 scroll_handle_set_loc(&h->loc, line, col);
4088 break;
4091 if(!h) /* anything behind us? */
4092 for(h = st->parms->text.handles->prev; h; h = h->prev)
4093 if(h->key == key){
4094 scroll_handle_set_loc(&h->loc, line, col);
4095 break;
4098 break;
4100 case TAG_FGCOLOR :
4101 case TAG_BGCOLOR :
4102 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
4103 break;
4105 case TAG_INVON:
4106 case TAG_INVOFF:
4107 case TAG_BOLDON:
4108 case TAG_BOLDOFF:
4109 case TAG_ULINEON:
4110 case TAG_ULINEOFF:
4111 i++;
4112 break;
4114 default: /* literal embed char */
4115 break;
4118 break;
4120 case TAB:
4121 i++;
4122 while(((++col) & 0x07) != 0) /* add tab's spaces */
4125 break;
4127 default:
4128 col += width_at_this_position((unsigned char*) &st->text_lines[line][i],
4129 st->line_lengths[line] - i);
4130 i++; /* character count */
4131 break;
4135 #ifdef _WINDOWS
4136 scroll_setrange (st->screen.length, st->num_lines);
4137 #endif
4139 *tl = NULL;
4144 * ScrollFile - scroll text into the st struct file making sure 'line'
4145 * of the file is the one first in the text_lines buffer.
4147 * NOTE: talk about massive potential for tuning...
4148 * Goes without saying this is still under constuction
4150 void
4151 ScrollFile(long int line)
4153 SCRLCTRL_S *st = scroll_state(SS_CUR);
4154 SCRLFILE_S sf;
4155 register int i;
4157 if(line <= 0){ /* reset and load first couple of pages */
4158 fseek((FILE *) st->parms->text.text, 0L, 0);
4159 line = 0L;
4162 if(!st->text_lines)
4163 return;
4165 for(i = 0; i < PGSIZE(st); i++){
4166 /*** do stuff to get the file pointer into the right place ***/
4168 * BOGUS: this is painfully crude right now, but I just want to get
4169 * it going.
4171 * possibly in the near furture, an array of indexes into the
4172 * file that are the offset for the beginning of each line will
4173 * speed things up. Of course, this
4174 * will have limits, so maybe a disk file that is an array
4175 * of indexes is the answer.
4177 if(fseek(st->findex, (size_t)(line++) * sizeof(SCRLFILE_S), 0) < 0
4178 || fread(&sf, sizeof(SCRLFILE_S), (size_t)1, st->findex) != 1
4179 || fseek((FILE *) st->parms->text.text, sf.offset, 0) < 0
4180 || !st->text_lines[i]
4181 || (sf.len && !fgets(st->text_lines[i], sf.len + 1,
4182 (FILE *) st->parms->text.text)))
4183 break;
4185 st->line_lengths[i] = sf.len;
4188 for(; i < PGSIZE(st); i++)
4189 if(st->text_lines[i]){ /* blank out any unused lines */
4190 *st->text_lines[i] = '\0';
4191 st->line_lengths[i] = 0;
4197 * make_file_index - do a single pass over the file containing the text
4198 * to display, recording line lengths and offsets.
4199 * NOTE: This is never really to be used on a real OS with virtual
4200 * memory. This is the whole reason st->findex exists. Don't
4201 * want to waste precious memory on a stupid array that could
4202 * be very large.
4204 long
4205 make_file_index(void)
4207 SCRLCTRL_S *st = scroll_state(SS_CUR);
4208 SCRLFILE_S sf;
4209 long l = 0L;
4210 int state = 0;
4212 if(!st->findex){
4213 if(!st->fname)
4214 st->fname = temp_nam(NULL, "pi");
4216 if(!st->fname || (st->findex = our_fopen(st->fname,"w+b")) == NULL){
4217 if(st->fname){
4218 our_unlink(st->fname);
4219 fs_give((void **)&st->fname);
4222 return(0);
4225 else
4226 fseek(st->findex, 0L, 0);
4228 fseek((FILE *)st->parms->text.text, 0L, 0);
4230 while(1){
4231 sf.len = st->screen.width + 1;
4232 if(scroll_file_line((FILE *) st->parms->text.text,
4233 tmp_20k_buf, &sf, &state)){
4234 fwrite((void *) &sf, sizeof(SCRLFILE_S), (size_t)1, st->findex);
4235 l++;
4237 else
4238 break;
4241 fseek((FILE *)st->parms->text.text, 0L, 0);
4243 return(l);
4247 /*----------------------------------------------------------------------
4248 Get the next line to scroll from the given file
4250 ----*/
4251 char *
4252 scroll_file_line(FILE *fp, char *buf, SCRLFILE_S *sfp, int *wrapt)
4254 register char *s = NULL;
4256 while(1){
4257 if(!s){
4258 sfp->offset = ftell(fp);
4259 if(!(s = fgets(buf, sfp->len, fp)))
4260 return(NULL); /* can't grab a line? */
4263 if(!*s){
4264 *wrapt = 1; /* remember; that we wrapped */
4265 break;
4267 else if(*s == NEWLINE[0] && (!NEWLINE[1] || *(s+1) == NEWLINE[1])){
4268 int empty = (*wrapt && s == buf);
4270 *wrapt = 0; /* turn off wrapped state */
4271 if(empty)
4272 s = NULL; /* get a new line */
4273 else
4274 break; /* done! */
4276 else
4277 s++;
4280 sfp->len = s - buf;
4281 return(buf);
4285 /*----------------------------------------------------------------------
4286 Scroll the text on the screen
4288 Args: new_top_line -- The line to be displayed on top of the screen
4289 redraw -- Flag to force a redraw even if nothing changed
4291 Returns: resulting top line
4292 Note: the returned line number may be less than new_top_line if
4293 reformatting caused the total line count to change.
4295 ----*/
4296 long
4297 scroll_scroll_text(long int new_top_line, HANDLE_S *handle, int redraw)
4299 SCRLCTRL_S *st = scroll_state(SS_CUR);
4300 int num_display_lines, l, top;
4301 POSLIST_S *lp, *lp2;
4303 /* When this is true, we're still on the same page of the display. */
4304 if(st->top_text_line == new_top_line && !redraw){
4305 /* handle changed, so hilite the new handle and unhilite the old */
4306 if(handle && handle != st->parms->text.handles){
4307 top = st->screen.start_line - new_top_line;
4308 /* hilite the new one */
4309 if(!scroll_handle_obscured(handle))
4310 for(lp = handle->loc; lp; lp = lp->next)
4311 if((l = lp->where.row) >= st->top_text_line
4312 && l < st->top_text_line + st->screen.length){
4313 if(st->parms->text.src == FileStar)
4314 l -= new_top_line;
4316 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4317 st->line_lengths[l], handle);
4320 /* unhilite the old one */
4321 if(!scroll_handle_obscured(st->parms->text.handles))
4322 for(lp = st->parms->text.handles->loc; lp; lp = lp->next)
4323 if((l = lp->where.row) >= st->top_text_line
4324 && l < st->top_text_line + st->screen.length){
4325 for(lp2 = handle->loc; lp2; lp2 = lp2->next)
4326 if(l == lp2->where.row)
4327 break;
4329 if(!lp2){
4330 if(st->parms->text.src == FileStar)
4331 l -= new_top_line;
4333 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4334 st->line_lengths[l], handle);
4338 st->parms->text.handles = handle; /* update current */
4341 return(new_top_line);
4344 num_display_lines = PGSIZE(st);
4346 format_scroll_text();
4348 if(st->top_text_line >= st->num_lines) /* don't pop line count */
4349 new_top_line = st->top_text_line = MAX(st->num_lines - 1, 0);
4351 if(st->parms->text.src == FileStar)
4352 ScrollFile(new_top_line); /* set up new st->text_lines */
4354 #ifdef _WINDOWS
4355 scroll_setrange (st->screen.length, st->num_lines);
4356 scroll_setpos (new_top_line);
4357 #endif
4359 /* ---
4360 Check out the scrolling situation. If we want to scroll, but BeginScroll
4361 says we can't then repaint, + 10 is so we repaint most of the time.
4362 ----*/
4363 if(redraw ||
4364 (st->top_text_line - new_top_line + 10 >= num_display_lines ||
4365 new_top_line - st->top_text_line + 10 >= num_display_lines) ||
4366 BeginScroll(st->screen.start_line,
4367 st->screen.start_line + num_display_lines - 1) != 0) {
4368 /* Too much text to scroll, or can't scroll -- just repaint */
4370 if(handle)
4371 st->parms->text.handles = handle;
4373 st->top_text_line = new_top_line;
4374 redraw_scroll_text();
4376 else{
4378 * We're going to scroll the screen, but first we have to make sure
4379 * the old hilited handles are unhilited if they are going to remain
4380 * on the screen.
4382 top = st->screen.start_line - st->top_text_line;
4383 if(handle && handle != st->parms->text.handles
4384 && st->parms->text.handles
4385 && !scroll_handle_obscured(st->parms->text.handles))
4386 for(lp = st->parms->text.handles->loc; lp; lp = lp->next)
4387 if((l = lp->where.row) >= MAX(st->top_text_line,new_top_line)
4388 && l < MIN(st->top_text_line,new_top_line) + st->screen.length){
4389 if(st->parms->text.src == FileStar)
4390 l -= new_top_line;
4392 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4393 st->line_lengths[l], handle);
4396 if(new_top_line > st->top_text_line){
4397 /*------ scroll down ------*/
4398 while(new_top_line > st->top_text_line) {
4399 ScrollRegion(1);
4401 l = (st->parms->text.src == FileStar)
4402 ? num_display_lines - (new_top_line - st->top_text_line)
4403 : st->top_text_line + num_display_lines;
4405 if(l < st->num_lines){
4406 PutLine0n8b(st->screen.start_line + num_display_lines - 1,
4407 0, st->text_lines[l], st->line_lengths[l],
4408 handle ? handle : st->parms->text.handles);
4410 * We clear to the end of line in the right background
4411 * color. If the line was exactly the width of the screen
4412 * then PutLine0n8b will have left _col and _row moved to
4413 * the start of the next row. We don't need or want to clear
4414 * that next row.
4416 if(pico_usingcolor()
4417 && (st->line_lengths[l] < ps_global->ttyo->screen_cols
4418 || visible_linelen(l) < ps_global->ttyo->screen_cols))
4419 CleartoEOLN();
4422 st->top_text_line++;
4425 else{
4426 /*------ scroll up -----*/
4427 while(new_top_line < st->top_text_line) {
4428 ScrollRegion(-1);
4430 st->top_text_line--;
4431 l = (st->parms->text.src == FileStar)
4432 ? st->top_text_line - new_top_line
4433 : st->top_text_line;
4434 PutLine0n8b(st->screen.start_line, 0, st->text_lines[l],
4435 st->line_lengths[l],
4436 handle ? handle : st->parms->text.handles);
4438 * We clear to the end of line in the right background
4439 * color. If the line was exactly the width of the screen
4440 * then PutLine0n8b will have left _col and _row moved to
4441 * the start of the next row. We don't need or want to clear
4442 * that next row.
4444 if(pico_usingcolor()
4445 && (st->line_lengths[l] < ps_global->ttyo->screen_cols
4446 || visible_linelen(l) < ps_global->ttyo->screen_cols))
4447 CleartoEOLN();
4451 EndScroll();
4453 if(handle && handle != st->parms->text.handles){
4454 POSLIST_S *lp;
4456 for(lp = handle->loc; lp; lp = lp->next)
4457 if(lp->where.row >= st->top_text_line
4458 && lp->where.row < st->top_text_line + st->screen.length){
4459 PutLine0n8b(st->screen.start_line
4460 + (lp->where.row - st->top_text_line),
4461 0, st->text_lines[lp->where.row],
4462 st->line_lengths[lp->where.row],
4463 handle);
4467 st->parms->text.handles = handle;
4470 fflush(stdout);
4473 return(new_top_line);
4477 /*---------------------------------------------------------------------
4478 Edit individual char in text so that the entire text doesn't need
4479 to be completely reformatted.
4481 Returns 0 if there were no errors, 1 if we would like the entire
4482 text to be reformatted.
4483 ----*/
4485 ng_scroll_edit(CONTEXT_S *context, int index)
4487 SCRLCTRL_S *st = scroll_state(SS_CUR);
4488 char *ngp, tmp[MAILTMPLEN+10];
4489 int len;
4490 FOLDER_S *f;
4492 if (!(f = folder_entry(index, FOLDERS(context))))
4493 return 1;
4494 if(f->subscribed)
4495 return 0; /* nothing in scroll needs to be changed */
4496 tmp[0] = TAG_HANDLE;
4497 snprintf(tmp+2, sizeof(tmp)-2, "%d", st->parms->text.handles->key);
4498 tmp[sizeof(tmp)-1] = '\0';
4499 tmp[1] = len = strlen(tmp+2);
4500 snprintf(tmp+len+2, sizeof(tmp)-(len+2), "%s ", f->selected ? "[ ]" : "[X]");
4501 tmp[sizeof(tmp)-1] = '\0';
4502 snprintf(tmp+len+6, sizeof(tmp)-(len+6), "%.*s", MAILTMPLEN, f->name);
4503 tmp[sizeof(tmp)-1] = '\0';
4505 ngp = *(st->text_lines);
4507 ngp = strstr(ngp, tmp);
4509 if(!ngp) return 1;
4510 ngp += 3+len;
4512 /* assumption that text is of form "[ ] xxx.xxx" */
4514 if(ngp){
4515 if(*ngp == 'X'){
4516 *ngp = ' ';
4517 return 0;
4519 else if (*ngp == ' '){
4520 *ngp = 'X';
4521 return 0;
4524 return 1;
4528 /*---------------------------------------------------------------------
4529 Similar to ng_scroll_edit, but this is the more general case of
4530 selecting a folder, as opposed to selecting a newsgroup for
4531 subscription while in listmode.
4533 Returns 0 if there were no errors, 1 if we would like the entire
4534 text to be reformatted.
4535 ----*/
4537 folder_select_update(CONTEXT_S *context, int index)
4539 SCRLCTRL_S *st = scroll_state(SS_CUR);
4540 FOLDER_S *f;
4541 char *ngp, tmp[MAILTMPLEN+10];
4542 int len, total, fnum, num_sel = 0;
4544 if (!(f = folder_entry(index, FOLDERS(context))))
4545 return 1;
4546 ngp = *(st->text_lines);
4548 total = folder_total(FOLDERS(context));
4550 for (fnum = 0; num_sel < 2 && fnum < total; fnum++)
4551 if(folder_entry(fnum, FOLDERS(context))->selected)
4552 num_sel++;
4553 if(!num_sel || (f->selected && num_sel == 1))
4554 return 1; /* need to reformat the whole thing */
4556 tmp[0] = TAG_HANDLE;
4557 snprintf(tmp+2, sizeof(tmp)-2, "%d", st->parms->text.handles->key);
4558 tmp[sizeof(tmp)-1] = '\0';
4559 tmp[1] = len = strlen(tmp+2);
4561 ngp = strstr(ngp, tmp);
4562 if(!ngp) return 1;
4564 if(F_ON(F_SELECTED_SHOWN_BOLD, ps_global)){
4565 ngp += 2 + len;
4566 while(*ngp && ngp[0] != TAG_EMBED
4567 && ngp[1] != (f->selected ? TAG_BOLDOFF : TAG_BOLDON)
4568 && *ngp != *(f->name))
4569 ngp++;
4571 if (!(*ngp) || (*ngp == *(f->name)))
4572 return 1;
4573 else {
4574 ngp++;
4575 *ngp = (f->selected ? TAG_BOLDON : TAG_BOLDOFF);
4576 return 0;
4579 else{
4580 while(*ngp != ' ' && *ngp != *(f->name) && *ngp)
4581 ngp++;
4582 if(!(*ngp) || (*ngp == *(f->name)))
4583 return 1;
4584 else {
4585 ngp++;
4586 *ngp = f->selected ? 'X' : ' ';
4587 return 0;
4593 /*---------------------------------------------------------------------
4594 We gotta go through all of the formatted text and add "[ ] " in the right
4595 place. If we don't do this, we must completely reformat the whole text,
4596 which could take a very long time.
4598 Return 1 if we encountered some sort of error and we want to reformat the
4599 whole text, return 0 if everything went as planned.
4601 ASSUMPTION: for this to work, we assume that there are only total
4602 number of handles, numbered 1 through total.
4603 ----*/
4605 scroll_add_listmode(CONTEXT_S *context, int total)
4607 SCRLCTRL_S *st = scroll_state(SS_CUR);
4608 long i;
4609 char *ngp, *ngname, handle_str[MAILTMPLEN];
4610 HANDLE_S *h;
4613 ngp = *(st->text_lines);
4614 h = st->parms->text.handles;
4616 while(h && h->key != 1 && h->prev)
4617 h = h->prev;
4618 if (!h) return 1;
4619 handle_str[0] = TAG_EMBED;
4620 handle_str[1] = TAG_HANDLE;
4621 for(i = 1; i <= total && h; i++, h = h->next){
4622 snprintf(handle_str+3, sizeof(handle_str)-3, "%d", h->key);
4623 handle_str[sizeof(handle_str)-1] = '\0';
4624 handle_str[2] = strlen(handle_str+3);
4625 ngp = strstr(ngp, handle_str);
4626 if(!ngp){
4627 ngp = *(st->text_lines);
4628 if (!ngp)
4629 return 1;
4631 ngname = ngp + strlen(handle_str);
4632 while (strncmp(ngp, " ", 4) && !(*ngp == '\n')
4633 && !(ngp == *(st->text_lines)))
4634 ngp--;
4635 if (strncmp(ngp, " ", 4))
4636 return 1;
4637 while(ngp+4 != ngname && *ngp){
4638 ngp[0] = ngp[4];
4639 ngp++;
4642 if(folder_entry(h->h.f.index, FOLDERS(context))->subscribed){
4643 ngp[0] = 'S';
4644 ngp[1] = 'U';
4645 ngp[2] = 'B';
4647 else{
4648 ngp[0] = '[';
4649 ngp[1] = ' ';
4650 ngp[2] = ']';
4652 ngp[3] = ' ';
4655 return 0;
4660 /*----------------------------------------------------------------------
4661 Search the set scrolling text
4663 Args: start_line -- line to start searching on
4664 start_index -- column to start searching at in first line
4665 word -- string to search for
4666 cursor_pos -- position of cursor is returned to caller here
4667 (Actually, this isn't really the position of the
4668 cursor because we don't know where we are on the
4669 screen. So row is set to the line number and col
4670 is set to the right column.)
4671 offset_in_line -- Offset where match was found.
4673 Returns: the line the word was found on, or -2 if it wasn't found, or
4674 -3 if the only match is at column start_index - 1.
4676 ----*/
4678 search_scroll_text(long int start_line, int start_index, char *word,
4679 Pos *cursor_pos, int *offset_in_line)
4681 SCRLCTRL_S *st = scroll_state(SS_CUR);
4682 char *wh;
4683 long l, offset, dlines;
4684 #define SROW(N) ((N) - offset)
4685 #define SLINE(N) st->text_lines[SROW(N)]
4686 #define SLEN(N) st->line_lengths[SROW(N)]
4688 dlines = PGSIZE(st);
4689 offset = (st->parms->text.src == FileStar) ? st->top_text_line : 0;
4691 if(start_line < st->num_lines){
4692 /* search first line starting at position start_index in */
4693 if((wh = search_scroll_line(SLINE(start_line) + start_index,
4694 word,
4695 SLEN(start_line) - start_index,
4696 st->parms->text.handles != NULL)) != NULL){
4697 cursor_pos->row = start_line;
4698 cursor_pos->col = scroll_handle_column(SROW(start_line),
4699 *offset_in_line = wh - SLINE(start_line));
4700 return(start_line);
4703 if(st->parms->text.src == FileStar)
4704 offset++;
4706 for(l = start_line + 1; l < st->num_lines; l++) {
4707 if(st->parms->text.src == FileStar && l > offset + dlines)
4708 ScrollFile(offset += dlines);
4710 if((wh = search_scroll_line(SLINE(l), word, SLEN(l),
4711 st->parms->text.handles != NULL)) != NULL){
4712 cursor_pos->row = l;
4713 cursor_pos->col = scroll_handle_column(SROW(l),
4714 *offset_in_line = wh - SLINE(l));
4715 return(l);
4719 else
4720 start_line = st->num_lines;
4722 if(st->parms->text.src == FileStar) /* wrap offset */
4723 ScrollFile(offset = 0);
4725 for(l = 0; l < start_line; l++) {
4726 if(st->parms->text.src == FileStar && l > offset + dlines)
4727 ScrollFile(offset += dlines);
4729 if((wh = search_scroll_line(SLINE(l), word, SLEN(l),
4730 st->parms->text.handles != NULL)) != NULL){
4731 cursor_pos->row = l;
4732 cursor_pos->col = scroll_handle_column(SROW(l),
4733 *offset_in_line = wh - SLINE(l));
4734 return(l);
4738 /* search in current line */
4739 if(start_line < st->num_lines
4740 && (wh = search_scroll_line(SLINE(start_line), word,
4741 start_index + strlen(word) - 2,
4742 st->parms->text.handles != NULL)) != NULL){
4743 cursor_pos->row = start_line;
4744 cursor_pos->col = scroll_handle_column(SROW(start_line),
4745 *offset_in_line = wh - SLINE(start_line));
4747 return(start_line);
4750 /* see if the only match is a repeat */
4751 if(start_index > 0 && start_line < st->num_lines
4752 && (wh = search_scroll_line(
4753 SLINE(start_line) + start_index - 1,
4754 word, strlen(word),
4755 st->parms->text.handles != NULL)) != NULL){
4756 cursor_pos->row = start_line;
4757 cursor_pos->col = scroll_handle_column(SROW(start_line),
4758 *offset_in_line = wh - SLINE(start_line));
4759 return(-3);
4762 return(-2);
4766 /*----------------------------------------------------------------------
4767 Search one line of scroll text for given string
4769 Args: haystack -- The string to search in, the larger string
4770 needle -- The string to search for, the smaller string
4771 n -- The max number of chars in haystack to search
4773 Search for first occurrence of needle in the haystack, and return a pointer
4774 into the string haystack when it is found. The search is case independent.
4775 ----*/
4776 char *
4777 search_scroll_line(char *haystack, char *needle, int n, int handles)
4779 char *return_ptr = NULL, *found_it = NULL;
4780 char *haystack_copy, *p, *free_this = NULL, *end;
4781 char buf[1000];
4782 int state = 0, i = 0;
4784 if(n > 0 && haystack){
4785 if(n < sizeof(buf))
4786 haystack_copy = buf;
4787 else
4788 haystack_copy = free_this = (char *) fs_get((n+1) * sizeof(char));
4790 strncpy(haystack_copy, haystack, n);
4791 haystack_copy[n] = '\0';
4794 * We don't want to match text inside embedded tags.
4795 * Replace embedded octets with nulls and convert
4796 * uppercase ascii to lowercase. We should also do
4797 * some sort of canonicalization of UTF-8 but that
4798 * sounds daunting.
4800 for(i = n, p = haystack_copy; i-- > 0 && *p; p++){
4801 if(handles)
4802 switch(state){
4803 case 0 :
4804 if(*p == TAG_EMBED){
4805 *p = '\0';
4806 state = -1;
4807 continue;
4809 else{
4810 /* lower case just ascii chars */
4811 if(!(*p & 0x80) && isupper(*p))
4812 *p = tolower(*p);
4815 break;
4817 case -1 :
4818 state = (*p == TAG_HANDLE)
4819 ? -2
4820 : (*p == TAG_FGCOLOR || *p == TAG_BGCOLOR) ? RGBLEN : 0;
4821 *p = '\0';
4822 continue;
4824 case -2 :
4825 state = *p; /* length of handle's key */
4826 *p = '\0';
4827 continue;
4829 default :
4830 state--;
4831 *p = '\0';
4832 continue;
4837 * The haystack_copy string now looks like
4839 * "chars\0\0\0\0\0\0chars...\0\0\0chars... \0"
4841 * with that final \0 at haystack_copy[n].
4842 * Search each piece one at a time.
4845 end = haystack_copy + n;
4846 p = haystack_copy;
4848 while(p < end && !return_ptr){
4850 /* skip nulls */
4851 while(*p == '\0' && p < end)
4852 p++;
4854 if(*p != '\0')
4855 found_it = srchstr(p, needle);
4857 if(found_it){
4858 /* found it, make result relative to haystack */
4859 return_ptr = haystack + (found_it - haystack_copy);
4862 /* skip to next null */
4863 while(*p != '\0' && p < end)
4864 p++;
4867 if(free_this)
4868 fs_give((void **) &free_this);
4871 return(return_ptr);
4876 * Returns the number of columns taken up by the visible part of the line.
4877 * That is, account for handles and color changes and so forth.
4880 visible_linelen(int line)
4882 SCRLCTRL_S *st = scroll_state(SS_CUR);
4883 int i, n, len = 0;
4885 if(line < 0 || line >= st->num_lines)
4886 return(len);
4888 for(i = 0, len = 0; i < st->line_lengths[line];)
4889 switch(st->text_lines[line][i]){
4891 case TAG_EMBED:
4892 i++;
4893 switch((i < st->line_lengths[line]) ? st->text_lines[line][i] : 0){
4894 case TAG_HANDLE:
4895 i++;
4896 /* skip the length byte plus <length> more bytes */
4897 if(i < st->line_lengths[line]){
4898 n = st->text_lines[line][i];
4899 i++;
4902 if(i < st->line_lengths[line] && n > 0)
4903 i += n;
4905 break;
4907 case TAG_FGCOLOR :
4908 case TAG_BGCOLOR :
4909 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
4910 break;
4912 case TAG_INVON:
4913 case TAG_INVOFF:
4914 case TAG_BOLDON:
4915 case TAG_BOLDOFF:
4916 case TAG_ULINEON:
4917 case TAG_ULINEOFF:
4918 i++;
4919 break;
4921 case TAG_EMBED: /* escaped embed character */
4922 i++;
4923 len++;
4924 break;
4926 default: /* the embed char was literal */
4927 i++;
4928 len += 2;
4929 break;
4932 break;
4934 case TAB:
4935 i++;
4936 while(((++len) & 0x07) != 0) /* add tab's spaces */
4939 break;
4941 default:
4942 i++;
4943 len++;
4944 break;
4947 return(len);
4951 /*----------------------------------------------------------------------
4952 Display the contents of the given file (likely output from some command)
4954 Args: filename -- name of file containing output
4955 title -- title to be used for screen displaying output
4956 alt_msg -- if no output, Q this message instead of the default
4957 mode -- non-zero to display short files in status line
4958 Returns: none
4959 ----*/
4960 void
4961 display_output_file(char *filename, char *title, char *alt_msg, int mode)
4963 STORE_S *in_file = NULL, *out_store = NULL;
4965 if((in_file = so_get(FileStar, filename, READ_ACCESS|READ_FROM_LOCALE))){
4966 if(mode == DOF_BRIEF){
4967 int msg_q = 0, i = 0;
4968 char buf[512], *msg_p[4];
4969 #define MAX_SINGLE_MSG_LEN 60
4971 buf[0] = '\0';
4972 msg_p[0] = buf;
4975 * Might need to do something about CRLFs for Windows.
4977 while(so_fgets(in_file, msg_p[msg_q], sizeof(buf) - (msg_p[msg_q] - buf))
4978 && msg_q < 3
4979 && (i = strlen(msg_p[msg_q])) < MAX_SINGLE_MSG_LEN){
4980 msg_p[msg_q+1] = msg_p[msg_q]+strlen(msg_p[msg_q]);
4981 if (*(msg_p[++msg_q] - 1) == '\n')
4982 *(msg_p[msg_q] - 1) = '\0';
4985 if(msg_q < 3 && i < MAX_SINGLE_MSG_LEN){
4986 if(*msg_p[0])
4987 for(i = 0; i < msg_q; i++)
4988 q_status_message2(SM_ORDER, 3, 4,
4989 "%s Result: %s", title, msg_p[i]);
4990 else
4991 q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
4992 alt_msg
4993 ? alt_msg
4994 : " command completed with no output");
4996 so_give(&in_file);
4997 in_file = NULL;
5000 else if(mode == DOF_EMPTY){
5001 unsigned char c;
5003 if(so_readc(&c, in_file) < 1){
5004 q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
5005 alt_msg
5006 ? alt_msg
5007 : " command completed with no output");
5008 so_give(&in_file);
5009 in_file = NULL;
5014 * We need to translate the file contents from the user's locale
5015 * charset to UTF-8 for use in scrolltool. We get that translation
5016 * from the READ_FROM_LOCALE in the in_file storage object.
5017 * It would be nice to skip this step but scrolltool doesn't use
5018 * the storage object routines to read from the file, so would
5019 * skip the translation step.
5021 if(in_file){
5022 char *errstr;
5023 gf_io_t gc, pc;
5025 if(!(out_store = so_get(CharStar, NULL, EDIT_ACCESS))){
5026 so_give(&in_file);
5027 our_unlink(filename);
5028 q_status_message(SM_ORDER | SM_DING, 3, 3,
5029 _("Error allocating space."));
5030 return;
5033 so_seek(in_file, 0L, 0);
5035 gf_filter_init();
5037 gf_link_filter(gf_wrap,
5038 gf_wrap_filter_opt(ps_global->ttyo->screen_cols - 4,
5039 ps_global->ttyo->screen_cols,
5040 NULL, 0, GFW_NONE));
5042 gf_set_so_readc(&gc, in_file);
5043 gf_set_so_writec(&pc, out_store);
5045 if((errstr = gf_pipe(gc, pc)) != NULL){
5046 so_give(&in_file);
5047 so_give(&out_store);
5048 our_unlink(filename);
5049 q_status_message(SM_ORDER | SM_DING, 3, 3,
5050 _("Error allocating space."));
5051 return;
5054 gf_clear_so_writec(out_store);
5055 gf_clear_so_readc(in_file);
5056 so_give(&in_file);
5059 if(out_store){
5060 SCROLL_S sargs;
5061 char title_buf[64];
5063 snprintf(title_buf, sizeof(title_buf), "HELP FOR %s VIEW", title);
5064 title_buf[sizeof(title_buf)-1] = '\0';
5066 memset(&sargs, 0, sizeof(SCROLL_S));
5067 sargs.text.text = so_text(out_store);
5068 sargs.text.src = CharStar;
5069 sargs.text.desc = "output";
5070 sargs.bar.title = title;
5071 sargs.bar.style = TextPercent;
5072 sargs.help.text = h_simple_text_view;
5073 sargs.help.title = title_buf;
5074 scrolltool(&sargs);
5075 ps_global->mangled_screen = 1;
5076 so_give(&out_store);
5079 our_unlink(filename);
5081 else
5082 dprint((2, "Error reopening %s to get results: %s\n",
5083 filename ? filename : "?", error_description(errno)));
5087 /*--------------------------------------------------------------------
5088 Call the function that will perform the double click operation
5090 Returns: the current top line
5091 --------*/
5092 long
5093 doubleclick_handle(SCROLL_S *sparms, HANDLE_S *next_handle, int *cmd, int *done)
5095 if(sparms->mouse.clickclick){
5096 if((*sparms->mouse.clickclick)(sparms))
5097 *done = 1;
5099 else
5100 switch(scroll_handle_launch(next_handle, TRUE)){
5101 case 1 :
5102 *cmd = MC_EXIT; /* propagate */
5103 *done = 1;
5104 break;
5106 case -1 :
5107 cmd_cancelled("View");
5108 break;
5110 default :
5111 break;
5114 return(scroll_state(SS_CUR)->top_text_line);
5119 #ifdef _WINDOWS
5121 * Just a little something to simplify assignments
5123 #define VIEWPOPUP(p, c, s) { \
5124 (p)->type = tQueue; \
5125 (p)->data.val = c; \
5126 (p)->label.style = lNormal; \
5127 (p)->label.string = s; \
5135 format_message_popup(sparms, in_handle)
5136 SCROLL_S *sparms;
5137 int in_handle;
5139 MPopup fmp_menu[32];
5140 HANDLE_S *h = NULL;
5141 int i = -1, n;
5142 long rawno;
5143 MESSAGECACHE *mc;
5145 /* Reason to offer per message ops? */
5146 if(mn_get_total(ps_global->msgmap) > 0L){
5147 if(in_handle){
5148 SCRLCTRL_S *st = scroll_state(SS_CUR);
5150 switch((h = get_handle(st->parms->text.handles, in_handle))->type){
5151 case Attach :
5152 fmp_menu[++i].type = tIndex;
5153 fmp_menu[i].label.string = "View Attachment";
5154 fmp_menu[i].label.style = lNormal;
5155 fmp_menu[i].data.val = 'X'; /* for local use */
5157 if(h->h.attach
5158 && dispatch_attachment(h->h.attach) != MCD_NONE
5159 && !(h->h.attach->can_display & MCD_EXTERNAL)
5160 && h->h.attach->body
5161 && (h->h.attach->body->type == TYPETEXT
5162 || (h->h.attach->body->type == TYPEMESSAGE
5163 && h->h.attach->body->subtype
5164 && !strucmp(h->h.attach->body->subtype,"rfc822")))){
5165 fmp_menu[++i].type = tIndex;
5166 fmp_menu[i].label.string = "View Attachment in New Window";
5167 fmp_menu[i].label.style = lNormal;
5168 fmp_menu[i].data.val = 'Y'; /* for local use */
5171 fmp_menu[++i].type = tIndex;
5172 fmp_menu[i].label.style = lNormal;
5173 fmp_menu[i].data.val = 'Z'; /* for local use */
5174 msgno_exceptions(ps_global->mail_stream,
5175 mn_m2raw(ps_global->msgmap,
5176 mn_get_cur(ps_global->msgmap)),
5177 h->h.attach->number, &n, FALSE);
5178 fmp_menu[i].label.string = (n & MSG_EX_DELETE)
5179 ? "Undelete Attachment"
5180 : "Delete Attachment";
5181 break;
5183 case URL :
5184 default :
5185 fmp_menu[++i].type = tIndex;
5186 fmp_menu[i].label.string = "View Link";
5187 fmp_menu[i].label.style = lNormal;
5188 fmp_menu[i].data.val = 'X'; /* for local use */
5190 fmp_menu[++i].type = tIndex;
5191 fmp_menu[i].label.string = "Copy Link";
5192 fmp_menu[i].label.style = lNormal;
5193 fmp_menu[i].data.val = 'W'; /* for local use */
5194 break;
5197 fmp_menu[++i].type = tSeparator;
5200 /* Delete or Undelete? That is the question. */
5201 fmp_menu[++i].type = tQueue;
5202 fmp_menu[i].label.style = lNormal;
5203 mc = ((rawno = mn_m2raw(ps_global->msgmap,
5204 mn_get_cur(ps_global->msgmap))) > 0L
5205 && ps_global->mail_stream
5206 && rawno <= ps_global->mail_stream->nmsgs)
5207 ? mail_elt(ps_global->mail_stream, rawno) : NULL;
5208 if(mc && mc->deleted){
5209 fmp_menu[i].data.val = 'U';
5210 fmp_menu[i].label.string = in_handle
5211 ? "&Undelete Message" : "&Undelete";
5213 else{
5214 fmp_menu[i].data.val = 'D';
5215 fmp_menu[i].label.string = in_handle
5216 ? "&Delete Message" : "&Delete";
5219 if(F_ON(F_ENABLE_FLAG, ps_global)){
5220 fmp_menu[++i].type = tSubMenu;
5221 fmp_menu[i].label.string = "Flag";
5222 fmp_menu[i].data.submenu = flag_submenu(mc);
5225 i++;
5226 VIEWPOPUP(&fmp_menu[i], 'S', in_handle ? "&Save Message" : "&Save");
5228 i++;
5229 VIEWPOPUP(&fmp_menu[i], 'E', in_handle ? "&Export Message" : "&Export");
5231 i++;
5232 VIEWPOPUP(&fmp_menu[i], '%', in_handle ? "Print Message" : "Print");
5234 i++;
5235 VIEWPOPUP(&fmp_menu[i], 'R',
5236 in_handle ? "&Reply to Message" : "&Reply");
5238 i++;
5239 VIEWPOPUP(&fmp_menu[i], 'F',
5240 in_handle ? "&Forward Message" : "&Forward");
5242 i++;
5243 VIEWPOPUP(&fmp_menu[i], 'B',
5244 in_handle ? "&Bounce Message" : "&Bounce");
5246 i++;
5247 VIEWPOPUP(&fmp_menu[i], 'T', "&Take Addresses");
5249 fmp_menu[++i].type = tSeparator;
5251 if(mn_get_cur(ps_global->msgmap) < mn_get_total(ps_global->msgmap)){
5252 i++;
5253 VIEWPOPUP(&fmp_menu[i], 'N', "View &Next Message");
5256 if(mn_get_cur(ps_global->msgmap) > 0){
5257 i++;
5258 VIEWPOPUP(&fmp_menu[i], 'P', "View &Prev Message");
5261 if(mn_get_cur(ps_global->msgmap) < mn_get_total(ps_global->msgmap)
5262 || mn_get_cur(ps_global->msgmap) > 0)
5263 fmp_menu[++i].type = tSeparator;
5265 /* Offer the attachment screen? */
5266 for(n = 0; ps_global->atmts && ps_global->atmts[n].description; n++)
5269 if(n > 1){
5270 i++;
5271 VIEWPOPUP(&fmp_menu[i], 'V', "&View Attachment Index");
5275 i++;
5276 VIEWPOPUP(&fmp_menu[i], 'I', "Message &Index");
5278 i++;
5279 VIEWPOPUP(&fmp_menu[i], 'M', "&Main Menu");
5281 fmp_menu[++i].type = tTail;
5283 if((i = mswin_popup(fmp_menu)) >= 0 && in_handle)
5284 switch(fmp_menu[i].data.val){
5285 case 'W' : /* Copy URL to clipboard */
5286 mswin_addclipboard(h->h.url.path);
5287 break;
5289 case 'X' :
5290 return(1); /* return like the user double-clicked */
5292 break;
5294 case 'Y' : /* popup the thing in another window */
5295 display_att_window(h->h.attach);
5296 break;
5298 case 'Z' :
5299 if(h && h->type == Attach){
5300 msgno_exceptions(ps_global->mail_stream,
5301 mn_m2raw(ps_global->msgmap,
5302 mn_get_cur(ps_global->msgmap)),
5303 h->h.attach->number, &n, FALSE);
5304 n ^= MSG_EX_DELETE;
5305 msgno_exceptions(ps_global->mail_stream,
5306 mn_m2raw(ps_global->msgmap,
5307 mn_get_cur(ps_global->msgmap)),
5308 h->h.attach->number, &n, TRUE);
5309 q_status_message2(SM_ORDER, 0, 3, "Attachment %s %s!",
5310 h->h.attach->number,
5311 (n & MSG_EX_DELETE) ? "deleted" : "undeleted");
5313 return(2);
5316 break;
5318 default :
5319 break;
5322 return(0);
5331 simple_text_popup(sparms, in_handle)
5332 SCROLL_S *sparms;
5333 int in_handle;
5335 MPopup simple_menu[12];
5336 int n = 0;
5338 VIEWPOPUP(&simple_menu[n], '%', "Print");
5339 n++;
5341 VIEWPOPUP(&simple_menu[n], 'S', "Save");
5342 n++;
5344 VIEWPOPUP(&simple_menu[n], 'F', "Forward in Email");
5345 n++;
5347 simple_menu[n++].type = tSeparator;
5349 VIEWPOPUP(&simple_menu[n], 'E', "Exit Viewer");
5350 n++;
5352 simple_menu[n].type = tTail;
5354 (void) mswin_popup(simple_menu);
5355 return(0);
5360 /*----------------------------------------------------------------------
5361 Return characters in scroll tool buffer serially
5363 Args: n -- index of char to return
5365 Returns: returns the character at index 'n', or -1 on error or
5366 end of buffer.
5368 ----*/
5370 mswin_readscrollbuf(n)
5371 int n;
5373 SCRLCTRL_S *st = scroll_state(SS_CUR);
5374 int c;
5375 static char **orig = NULL, **l, *p;
5376 static int lastn;
5378 if(!st)
5379 return(-1);
5382 * All of these are mind-numbingly slow at the moment...
5384 switch(st->parms->text.src){
5385 case CharStar :
5386 return((n >= strlen((char *)st->parms->text.text))
5387 ? -1 : ((char *)st->parms->text.text)[n]);
5389 case CharStarStar :
5390 /* BUG? is this test rigorous enough? */
5391 if(orig != (char **)st->parms->text.text || n < lastn){
5392 lastn = n;
5393 if(orig = l = (char **)st->parms->text.text) /* reset l and p */
5394 p = *l;
5396 else{ /* use cached l and p */
5397 c = n; /* and adjust n */
5398 n -= lastn;
5399 lastn = c;
5402 while(l){ /* look for 'n' on each line */
5403 for(; n && *p; n--, p++)
5406 if(n--) /* 'n' found ? */
5407 p = *++l;
5408 else
5409 break;
5412 return((l && *l) ? *p ? *p : '\n' : -1);
5414 case FileStar :
5415 return((fseek((FILE *)st->parms->text.text, (long) n, 0) < 0
5416 || (c = fgetc((FILE *)st->parms->text.text)) == EOF) ? -1 : c);
5418 default:
5419 return(-1);
5425 /*----------------------------------------------------------------------
5426 MSWin scroll callback. Called during scroll message processing.
5430 Args: cmd - what type of scroll operation.
5431 scroll_pos - paramter for operation.
5432 used as position for SCROLL_TO operation.
5434 Returns: TRUE - did the scroll operation.
5435 FALSE - was not able to do the scroll operation.
5436 ----*/
5438 pcpine_do_scroll (cmd, scroll_pos)
5439 int cmd;
5440 long scroll_pos;
5442 SCRLCTRL_S *st = scroll_state(SS_CUR);
5443 HANDLE_S *next_handle;
5444 int paint = FALSE;
5445 int num_display_lines;
5446 int scroll_lines;
5447 char message[64];
5448 long maxscroll;
5451 message[0] = '\0';
5452 maxscroll = st->num_lines;
5453 switch (cmd) {
5454 case MSWIN_KEY_SCROLLUPLINE:
5455 if(st->top_text_line > 0) {
5456 st->top_text_line -= (int) scroll_pos;
5457 paint = TRUE;
5458 if (st->top_text_line <= 0){
5459 snprintf(message, sizeof(message), "START of %.*s",
5460 32, STYLE_NAME(st->parms));
5461 message[sizeof(message)-1] = '\0';
5462 st->top_text_line = 0;
5465 break;
5467 case MSWIN_KEY_SCROLLDOWNLINE:
5468 if(st->top_text_line < maxscroll) {
5469 st->top_text_line += (int) scroll_pos;
5470 paint = TRUE;
5471 if (st->top_text_line >= maxscroll){
5472 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5473 message[sizeof(message)-1] = '\0';
5474 st->top_text_line = maxscroll;
5477 break;
5479 case MSWIN_KEY_SCROLLUPPAGE:
5480 if(st->top_text_line > 0) {
5481 num_display_lines = SCROLL_LINES(ps_global);
5482 scroll_lines = MIN(MAX(num_display_lines -
5483 ps_global->viewer_overlap, 1), num_display_lines);
5484 if (st->top_text_line > scroll_lines)
5485 st->top_text_line -= scroll_lines;
5486 else {
5487 st->top_text_line = 0;
5488 snprintf(message, sizeof(message), "START of %.*s", 32, STYLE_NAME(st->parms));
5489 message[sizeof(message)-1] = '\0';
5491 paint = TRUE;
5493 break;
5495 case MSWIN_KEY_SCROLLDOWNPAGE:
5496 num_display_lines = SCROLL_LINES(ps_global);
5497 if(st->top_text_line < maxscroll) {
5498 scroll_lines = MIN(MAX(num_display_lines -
5499 ps_global->viewer_overlap, 1), num_display_lines);
5500 st->top_text_line += scroll_lines;
5501 if (st->top_text_line >= maxscroll) {
5502 st->top_text_line = maxscroll;
5503 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5504 message[sizeof(message)-1] = '\0';
5506 paint = TRUE;
5508 break;
5510 case MSWIN_KEY_SCROLLTO:
5511 if (st->top_text_line != scroll_pos) {
5512 st->top_text_line = scroll_pos;
5513 if (st->top_text_line == 0)
5514 snprintf(message, sizeof(message), "START of %.*s", 32, STYLE_NAME(st->parms));
5515 else if(st->top_text_line >= maxscroll)
5516 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5518 message[sizeof(message)-1] = '\0';
5519 paint = TRUE;
5521 break;
5524 /* Need to frame some handles? */
5525 if(st->parms->text.handles
5526 && (next_handle = scroll_handle_in_frame(st->top_text_line)))
5527 st->parms->text.handles = next_handle;
5529 if (paint) {
5530 mswin_beginupdate();
5531 update_scroll_titlebar(st->top_text_line, 0);
5532 (void) scroll_scroll_text(st->top_text_line,
5533 st->parms->text.handles, 1);
5534 if (message[0])
5535 q_status_message(SM_INFO, 0, 1, message);
5537 /* Display is always called so that the "START(END) of message"
5538 * message gets removed when no longer at the start(end). */
5539 display_message (KEY_PGDN);
5540 mswin_endupdate();
5543 return (TRUE);
5547 char *
5548 pcpine_help_scroll(title)
5549 char *title;
5551 SCRLCTRL_S *st = scroll_state(SS_CUR);
5553 if(title)
5554 strncpy(title, (st->parms->help.title)
5555 ? st->parms->help.title : "Alpine Help", 256);
5557 return(pcpine_help(st->parms->help.text));
5562 pcpine_view_cursor(col, row)
5563 int col;
5564 long row;
5566 SCRLCTRL_S *st = scroll_state(SS_CUR);
5567 int key;
5568 long line;
5570 return((row >= HEADER_ROWS(ps_global)
5571 && row < HEADER_ROWS(ps_global) + SCROLL_LINES(ps_global)
5572 && (line = (row - 2) + st->top_text_line) < st->num_lines
5573 && (key = dot_on_handle(line, col))
5574 && scroll_handle_selectable(get_handle(st->parms->text.handles,key)))
5575 ? MSWIN_CURSOR_HAND
5576 : MSWIN_CURSOR_ARROW);
5578 #endif /* _WINDOWS */