* Patches from MichaƂ Dardas and Mateusz Kocielski from LogicalTrust
[alpine.git] / alpine / mailview.c
blob0738c376497dc6aadd0302ce7041e2380812cf3f
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 iCal:
982 display_vevent_summary(mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap)),
983 handle->h.ical.attach,
984 DA_FROM_VIEW | DA_DIDPROMPT, handle->h.ical.depth);
985 break;
987 case Function :
988 (*handle->h.func.f)(handle->h.func.args.stream,
989 handle->h.func.args.msgmap,
990 handle->h.func.args.msgno);
991 break;
994 default :
995 alpine_panic("Unexpected HANDLE type");
998 return(0);
1003 scroll_handle_obscured(HANDLE_S *handle)
1005 SCRLCTRL_S *st = scroll_state(SS_CUR);
1007 return(handle_on_page(handle, st->top_text_line,
1008 st->top_text_line + st->screen.length));
1014 * scroll_handle_in_frame -- return handle pointer to visible handle.
1016 HANDLE_S *
1017 scroll_handle_in_frame(long int top_line)
1019 SCRLCTRL_S *st = scroll_state(SS_CUR);
1020 HANDLE_S *hp;
1022 switch(handle_on_page(hp = st->parms->text.handles, top_line,
1023 top_line + st->screen.length)){
1024 case -1 : /* handle above page */
1025 /* Find first handle from top of page */
1026 for(hp = st->parms->text.handles->next; hp; hp = hp->next)
1027 if(scroll_handle_selectable(hp))
1028 switch(handle_on_page(hp, top_line, top_line + st->screen.length)){
1029 case 0 : return(hp);
1030 case 1 : return(NULL);
1031 case -1 : default : break;
1034 break;
1036 case 1 : /* handle below page */
1037 /* Find first handle from top of page */
1038 for(hp = st->parms->text.handles->prev; hp; hp = hp->prev)
1039 if(scroll_handle_selectable(hp))
1040 switch(handle_on_page(hp, top_line, top_line + st->screen.length)){
1041 case 0 : return(hp);
1042 case -1 : return(NULL);
1043 case 1 : default : break;
1046 break;
1048 case 0 :
1049 default :
1050 break;
1053 return(hp);
1057 * scroll_handle_reframe -- adjust display params to display given handle
1059 long
1060 scroll_handle_reframe(int key, int center)
1062 long l, offset, dlines, start_line;
1063 SCRLCTRL_S *st = scroll_state(SS_CUR);
1065 dlines = PGSIZE(st);
1066 offset = (st->parms->text.src == FileStar) ? st->top_text_line : 0;
1067 start_line = st->top_text_line;
1069 if(key < 0)
1070 key = st->parms->text.handles->key;
1072 /* Searc down from the top line */
1073 for(l = start_line; l < st->num_lines; l++) {
1074 if(st->parms->text.src == FileStar && l > offset + dlines)
1075 ScrollFile(offset += dlines);
1077 if(handle_on_line(l - offset, key))
1078 break;
1081 if(l < st->num_lines){
1082 if(l >= dlines + start_line) /* bingo! */
1083 start_line = l - ((center ? (dlines / 2) : dlines) - 1);
1085 else{
1086 if(st->parms->text.src == FileStar) /* wrap offset */
1087 ScrollFile(offset = 0);
1089 for(l = 0; l < start_line; l++) {
1090 if(st->parms->text.src == FileStar && l > offset + dlines)
1091 ScrollFile(offset += dlines);
1093 if(handle_on_line(l - offset, key))
1094 break;
1097 if(l == start_line)
1098 alpine_panic("Internal Error: no handle found");
1099 else
1100 start_line = l;
1103 return(start_line);
1108 handle_on_line(long int line, int goal)
1110 int i, n, key;
1111 SCRLCTRL_S *st = scroll_state(SS_CUR);
1113 for(i = 0; i < st->line_lengths[line]; i++)
1114 if(st->text_lines[line][i] == TAG_EMBED
1115 && st->text_lines[line][++i] == TAG_HANDLE){
1116 for(key = 0, n = st->text_lines[line][++i]; n; n--)
1117 key = (key * 10) + (st->text_lines[line][++i] - '0');
1119 if(key == goal)
1120 return(1);
1123 return(0);
1128 handle_on_page(HANDLE_S *handle, long int first_line, long int last_line)
1130 POSLIST_S *l;
1131 int rv = 0;
1133 if(handle && (l = handle->loc)){
1134 for( ; l; l = l->next)
1135 if(l->where.row < first_line){
1136 if(!rv)
1137 rv = -1;
1139 else if(l->where.row >= last_line){
1140 if(!rv)
1141 rv = 1;
1143 else
1144 return(0); /* found! */
1147 return(rv);
1152 scroll_handle_selectable(HANDLE_S *handle)
1154 return(handle && (handle->type != URL
1155 || (handle->h.url.path && *handle->h.url.path)));
1159 HANDLE_S *
1160 scroll_handle_next_sel(HANDLE_S *handles)
1162 while(handles && !scroll_handle_selectable(handles = handles->next))
1165 return(handles);
1169 HANDLE_S *
1170 scroll_handle_prev_sel(HANDLE_S *handles)
1172 while(handles && !scroll_handle_selectable(handles = handles->prev))
1175 return(handles);
1179 HANDLE_S *
1180 scroll_handle_next(HANDLE_S *handles)
1182 HANDLE_S *next = NULL;
1184 if(scroll_handle_obscured(handles) <= 0){
1185 next = handles;
1186 while((next = scroll_handle_next_sel(next))
1187 && scroll_handle_obscured(next))
1191 return(next);
1196 HANDLE_S *
1197 scroll_handle_prev(HANDLE_S *handles)
1199 HANDLE_S *prev = NULL;
1201 if(scroll_handle_obscured(handles) >= 0){
1202 prev = handles;
1203 while((prev = scroll_handle_prev_sel(prev))
1204 && scroll_handle_obscured(prev))
1208 return(prev);
1212 HANDLE_S *
1213 scroll_handle_boundary(HANDLE_S *handle, HANDLE_S *(*f) (HANDLE_S *))
1215 HANDLE_S *hp, *whp = NULL;
1217 /* Multi-line handle? Punt! */
1218 if(handle && (!(hp = handle)->loc->next))
1219 while((hp = (*f)(hp))
1220 && hp->loc->where.row == handle->loc->where.row)
1221 whp = hp;
1223 return(whp);
1228 scroll_handle_column(int line, int offset)
1230 SCRLCTRL_S *st = scroll_state(SS_CUR);
1231 int i, n, col;
1232 int key, limit;
1234 limit = (offset > -1) ? offset : st->line_lengths[line];
1236 for(i = 0, col = 0; i < limit;){
1237 if(st && st->text_lines && st->text_lines[line])
1238 switch(st->text_lines[line][i]){
1239 case TAG_EMBED:
1240 i++;
1241 switch((i < limit) ? st->text_lines[line][i] : 0){
1242 case TAG_HANDLE:
1243 for(key = 0, n = st->text_lines[line][++i]; n > 0 && i < limit-1; n--)
1244 key = (key * 10) + (st->text_lines[line][++i] - '0');
1246 i++;
1247 break;
1249 case TAG_FGCOLOR :
1250 case TAG_BGCOLOR :
1251 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
1252 break;
1254 case TAG_INVON:
1255 case TAG_INVOFF:
1256 case TAG_BOLDON:
1257 case TAG_BOLDOFF:
1258 case TAG_ULINEON:
1259 case TAG_ULINEOFF:
1260 i++;
1261 break;
1263 default: /* literal embed char */
1264 break;
1267 break;
1269 case TAB:
1270 i++;
1271 while(((++col) & 0x07) != 0) /* add tab's spaces */
1274 break;
1276 default:
1277 col += width_at_this_position((unsigned char*) &st->text_lines[line][i],
1278 st->line_lengths[line] - i);
1279 i++;
1280 break;
1284 return(col);
1289 scroll_handle_index(int row, int column)
1291 SCRLCTRL_S *st = scroll_state(SS_CUR);
1292 int index = 0;
1294 for(index = 0; column > 0;)
1295 switch(st->text_lines[row][index++]){
1296 case TAG_EMBED :
1297 switch(st->text_lines[row][index++]){
1298 case TAG_HANDLE:
1299 index += st->text_lines[row][index] + 1;
1300 break;
1302 case TAG_FGCOLOR :
1303 case TAG_BGCOLOR :
1304 index += RGBLEN;
1305 break;
1307 default :
1308 break;
1311 break;
1313 case TAB : /* add tab's spaces */
1314 while(((--column) & 0x07) != 0)
1317 break;
1319 default :
1320 column--;
1321 break;
1324 return(index);
1328 void
1329 scroll_handle_set_loc(POSLIST_S **lpp, int line, int column)
1331 POSLIST_S *lp;
1333 lp = (POSLIST_S *) fs_get(sizeof(POSLIST_S));
1334 lp->where.row = line;
1335 lp->where.col = column;
1336 lp->next = NULL;
1337 for(; *lpp; lpp = &(*lpp)->next)
1340 *lpp = lp;
1345 dot_on_handle(long int line, int goal)
1347 int i = 0, n, key = 0, column = -1;
1348 SCRLCTRL_S *st = scroll_state(SS_CUR);
1351 if(i >= st->line_lengths[line])
1352 return(0);
1354 switch(st->text_lines[line][i++]){
1355 case TAG_EMBED :
1356 switch(st->text_lines[line][i++]){
1357 case TAG_HANDLE :
1358 for(key = 0, n = st->text_lines[line][i++]; n; n--)
1359 key = (key * 10) + (st->text_lines[line][i++] - '0');
1361 break;
1363 case TAG_FGCOLOR :
1364 case TAG_BGCOLOR :
1365 i += RGBLEN; /* advance past color setting */
1366 break;
1368 case TAG_BOLDOFF :
1369 key = 0;
1370 break;
1373 break;
1375 case TAB :
1376 while(((++column) & 0x07) != 0) /* add tab's spaces */
1379 break;
1381 default :
1382 column += width_at_this_position((unsigned char*) &st->text_lines[line][i-1],
1383 st->line_lengths[line] - (i-1));
1384 break;
1387 while(column < goal);
1389 return(key);
1394 * url_launch - Sniff the given url, see if we can do anything with
1395 * it. If not, hand off to user-defined app.
1399 url_launch(HANDLE_S *handle)
1401 int rv = 0;
1402 url_tool_t f;
1403 #define URL_MAX_LAUNCH (2 * MAILTMPLEN)
1405 if(handle->h.url.tool){
1406 char *toolp, *cmdp, *p, cmd[URL_MAX_LAUNCH + 4];
1407 int mode, copied = 0;
1408 PIPE_S *syspipe;
1410 toolp = handle->h.url.tool;
1412 /* This code used to quote a URL to prevent arbitrary command execution
1413 * through a URL. The plan was to quote the URL with single quotes,
1414 * and this used to work. BUT some shells do not care about quoting
1415 * and interpret some characters regardless of single quotes. The
1416 * simplest solution is to escape those characters, but then some
1417 * shells will see the escape characters and some others will not.
1418 * It's a mess. There are several ways to go around this mess,
1419 * including adding configuration options (one more!?), or forget
1420 * about shells. What we do is to forget about shells, and execute
1421 * the code as a PIPE_NOSHELL.
1424 cmdp = cmd;
1425 while(cmdp-cmd < URL_MAX_LAUNCH)
1426 if((!*toolp && !copied)
1427 || (*toolp == '_' && !strncmp(toolp + 1, "URL_", 4))){
1429 /* implicit _URL_ at end */
1430 if(!*toolp)
1431 *cmdp++ = ' ';
1433 if(cmdp[-1] == '\'') /* unquote old '_URL_' */
1434 cmdp--;
1436 copied = 1;
1437 for(p = handle->h.url.path;
1438 p && *p && cmdp-cmd < URL_MAX_LAUNCH; p++)
1439 *cmdp++ = *p;
1441 *cmdp = '\0';
1443 if(*toolp)
1444 toolp += 5; /* length of "_URL_" */
1445 if(*toolp == '\'')
1446 toolp++;
1448 else
1449 if(!(*cmdp++ = *toolp++))
1450 break;
1452 if(cmdp-cmd >= URL_MAX_LAUNCH)
1453 return(url_launch_too_long(rv));
1455 mode = PIPE_RESET | PIPE_USER | PIPE_RUNNOW | PIPE_NOSHELL ;
1456 if((syspipe = open_system_pipe(cmd, NULL, NULL, mode, 0, pipe_callback, pipe_report_error)) != NULL){
1457 close_system_pipe(&syspipe, NULL, pipe_callback);
1458 q_status_message(SM_ORDER, 0, 4, _("VIEWER command completed"));
1460 else
1461 q_status_message1(SM_ORDER, 3, 4,
1462 /* TRANSLATORS: Cannot start command : <command name> */
1463 _("Cannot start command : %s"), cmd);
1465 else if((f = url_local_handler(handle->h.url.path)) != NULL){
1466 if((*f)(handle->h.url.path) > 1)
1467 rv = 1; /* done! */
1469 else
1470 q_status_message1(SM_ORDER, 2, 2,
1471 _("\"URL-Viewer\" not defined: Can't open %s"),
1472 handle->h.url.path);
1474 return(rv);
1479 url_launch_too_long(int return_value)
1481 q_status_message(SM_ORDER | SM_DING, 3, 3,
1482 "Can't spawn. Command too long.");
1483 return(return_value);
1487 char *
1488 url_external_handler(HANDLE_S *handle, int specific)
1490 char **l, *test, *cmd, *p, *q, *ep;
1491 int i, specific_match;
1493 for(l = ps_global->VAR_BROWSER ; l && *l; l++){
1494 get_pair(*l, &test, &cmd, 0, 1);
1495 dprint((5, "TEST: \"%s\" CMD: \"%s\"\n",
1496 test ? test : "<NULL>", cmd ? cmd : "<NULL>"));
1497 removing_quotes(cmd);
1498 if(valid_filter_command(&cmd)){
1499 specific_match = 0;
1501 if((p = test) != NULL){
1502 while(*p && cmd)
1503 if(*p == '_'){
1504 if(!strncmp(p+1, "TEST(", 5)
1505 && (ep = strstr(p+6, ")_"))){
1506 *ep = '\0';
1508 if(exec_mailcap_test_cmd(p+6) == 0){
1509 p = ep + 2;
1511 else{
1512 dprint((5,"failed handler TEST\n"));
1513 fs_give((void **) &cmd);
1516 else if(!strncmp(p+1, "SCHEME(", 7)
1517 && (ep = strstr(p+8, ")_"))){
1518 *ep = '\0';
1520 p += 8;
1522 if((q = strchr(p, ',')) != NULL)
1523 *q++ = '\0';
1524 else
1525 q = ep;
1526 while(!((i = strlen(p))
1527 && ((p[i-1] == ':'
1528 && handle->h.url.path[i - 1] == ':')
1529 || (p[i-1] != ':'
1530 && handle->h.url.path[i] == ':'))
1531 && !struncmp(handle->h.url.path, p, i))
1532 && *(p = q));
1534 if(*p){
1535 specific_match = 1;
1536 p = ep + 2;
1538 else{
1539 dprint((5,"failed handler SCHEME\n"));
1540 fs_give((void **) &cmd);
1543 else{
1544 dprint((5, "UNKNOWN underscore test\n"));
1545 fs_give((void **) &cmd);
1548 else if(isspace((unsigned char) *p)){
1549 p++;
1551 else{
1552 dprint((1,"bogus handler test: \"%s\"",
1553 test ? test : "?"));
1554 fs_give((void **) &cmd);
1557 fs_give((void **) &test);
1560 if(cmd && (!specific || specific_match))
1561 return(cmd);
1564 if(test)
1565 fs_give((void **) &test);
1567 if(cmd)
1568 fs_give((void **) &cmd);
1571 cmd = NULL;
1573 if(!specific){
1574 cmd = url_os_specified_browser(handle->h.url.path);
1576 * Last chance, anything handling "text/html" in mailcap...
1578 if(!cmd && mailcap_can_display(TYPETEXT, "html", NULL, 0)){
1579 MCAP_CMD_S *mc_cmd;
1581 mc_cmd = mailcap_build_command(TYPETEXT, "html",
1582 NULL, "_URL_", NULL, 0);
1584 * right now URL viewing won't return anything requiring
1585 * special handling
1587 if(mc_cmd){
1588 cmd = mc_cmd->command;
1589 fs_give((void **)&mc_cmd);
1594 return(cmd);
1598 url_tool_t
1599 url_local_handler(char *s)
1601 int i;
1602 static struct url_t {
1603 char *url;
1604 short len;
1605 url_tool_t f;
1606 } handlers[] = {
1607 {"mailto:", 7, url_local_mailto}, /* see url_tool_t def's */
1608 {"imap://", 7, url_local_imap}, /* for explanations */
1609 {"nntp://", 7, url_local_nntp},
1610 {"file://", 7, url_local_file},
1611 #ifdef ENABLE_LDAP
1612 {"ldap://", 7, url_local_ldap},
1613 #endif
1614 {"news:", 5, url_local_news},
1615 {"x-alpine-ical:", 14, ical_send_reply},
1616 {"x-alpine-phone-home:", 20, url_local_phone_home},
1617 {"x-alpine-gripe:", 15, gripe_gripe_to},
1618 {"x-alpine-help:", 14, url_local_helper},
1619 {"x-pine-help:", 12, url_local_helper},
1620 {"x-alpine-config:", 16, url_local_config},
1621 {"x-alpine-cert:", 14, url_local_certdetails},
1622 {"#", 1, url_local_fragment},
1623 {NULL, 0, NULL}
1626 for(i = 0; handlers[i].url ; i++)
1627 if(!struncmp(s, handlers[i].url, handlers[i].len))
1628 return(handlers[i].f);
1630 return(NULL);
1636 * mailto URL digester ala draft-hoffman-mailto-url-02.txt
1639 url_local_mailto(char *url)
1641 return(url_local_mailto_and_atts(url, NULL));
1645 url_local_mailto_and_atts(char *url, PATMT *attachlist)
1647 ENVELOPE *outgoing;
1648 BODY *body = NULL;
1649 REPLY_S fake_reply;
1650 char *sig, *urlp, *p, *hname, *hvalue;
1651 int rv = 0;
1652 long rflags;
1653 int was_a_body = 0, impl, template_len = 0;
1654 char *fcc = NULL;
1655 PAT_STATE dummy;
1656 REDRAFT_POS_S *redraft_pos = NULL;
1657 ACTION_S *role = NULL;
1659 outgoing = mail_newenvelope();
1660 outgoing->message_id = generate_message_id();
1661 body = mail_newbody();
1662 body->type = TYPETEXT;
1663 if((body->contents.text.data = (void *) so_get(PicoText,NULL,EDIT_ACCESS)) != NULL){
1665 * URL format is:
1667 * mailtoURL = "mailto:" [ to ] [ headers ]
1668 * to = #mailbox
1669 * headers = "?" header *( "&" header )
1670 * header = hname "=" hvalue
1671 * hname = *urlc
1672 * hvalue = *urlc
1674 * NOTE2: "from" and "bcc" are intentionally excluded from
1675 * the list of understood "header" fields
1677 if((p = strchr(urlp = cpystr(url+7), '?')) != NULL)
1678 *p++ = '\0'; /* headers? Tie off mailbox */
1680 /* grok mailbox as first "to", then roll thru specific headers */
1681 if(*urlp)
1682 rfc822_parse_adrlist(&outgoing->to,
1683 rfc1738_str(urlp),
1684 ps_global->maildomain);
1686 while(p){
1687 /* Find next "header" */
1688 if((p = strchr(hname = p, '&')) != NULL)
1689 *p++ = '\0'; /* tie off "header" */
1691 if((hvalue = strchr(hname, '=')) != NULL)
1692 *hvalue++ = '\0'; /* tie off hname */
1694 if(!hvalue || !strucmp(hname, "subject")){
1695 char *sub = rfc1738_str(hvalue ? hvalue : hname);
1697 if(outgoing->subject){
1698 int len = strlen(outgoing->subject);
1700 fs_resize((void **) &outgoing->subject,
1701 (len + strlen(sub) + 2) * sizeof(char));
1702 snprintf(outgoing->subject + len, strlen(sub)+2, " %s", sub);
1703 outgoing->subject[len + strlen(sub) + 2 - 1] = '\0';
1705 else
1706 outgoing->subject = cpystr(sub);
1708 else if(!strucmp(hname, "to")){
1709 url_mailto_addr(&outgoing->to, hvalue);
1711 else if(!strucmp(hname, "cc")){
1712 url_mailto_addr(&outgoing->cc, hvalue);
1714 else if(!strucmp(hname, "bcc")){
1715 q_status_message(SM_ORDER, 3, 4,
1716 "\"Bcc\" header in mailto url ignored");
1718 else if(!strucmp(hname, "from")){
1719 q_status_message(SM_ORDER, 3, 4,
1720 "\"From\" header in mailto url ignored");
1722 else if(!strucmp(hname, "body")){
1723 char *sub = rfc1738_str(hvalue ? hvalue : "");
1725 so_puts((STORE_S *)body->contents.text.data, sub);
1726 so_puts((STORE_S *)body->contents.text.data, NEWLINE);
1727 was_a_body++;
1731 fs_give((void **) &urlp);
1733 rflags = ROLE_COMPOSE;
1734 if(nonempty_patterns(rflags, &dummy)){
1735 role = set_role_from_msg(ps_global, rflags, -1L, NULL);
1736 if(confirm_role(rflags, &role))
1737 role = combine_inherited_role(role);
1738 else{ /* cancel */
1739 role = NULL;
1740 cmd_cancelled("Composition");
1741 goto outta_here;
1745 if(role)
1746 q_status_message1(SM_ORDER, 3, 4, "Composing using role \"%s\"",
1747 role->nick);
1749 if(!was_a_body && role && role->template){
1750 char *filtered;
1752 impl = 1; /* leave cursor in header if not explicit */
1753 filtered = detoken(role, NULL, 0, 0, 0, &redraft_pos, &impl);
1754 if(filtered){
1755 if(*filtered){
1756 so_puts((STORE_S *)body->contents.text.data, filtered);
1757 if(impl == 1)
1758 template_len = strlen(filtered);
1761 fs_give((void **)&filtered);
1764 else
1765 impl = 1;
1767 if(!was_a_body && (sig = detoken(role, NULL, 2, 0, 1, &redraft_pos,
1768 &impl))){
1769 if(impl == 2)
1770 redraft_pos->offset += template_len;
1772 if(*sig)
1773 so_puts((STORE_S *)body->contents.text.data, sig);
1775 fs_give((void **)&sig);
1778 memset((void *)&fake_reply, 0, sizeof(fake_reply));
1779 fake_reply.pseudo = 1;
1780 fake_reply.data.pico_flags = (outgoing->subject) ? P_BODY : P_HEADEND;
1783 if(!(role && role->fcc))
1784 fcc = get_fcc_based_on_to(outgoing->to);
1786 if(attachlist)
1787 create_message_body(&body, attachlist, 0);
1789 pine_send(outgoing, &body, "\"MAILTO\" COMPOSE",
1790 role, fcc, &fake_reply, redraft_pos, NULL, NULL, PS_STICKY_TO);
1791 rv++;
1792 ps_global->mangled_screen = 1;
1794 else
1795 q_status_message(SM_ORDER | SM_DING, 3, 4,
1796 _("Can't create space for composer"));
1798 outta_here:
1799 if(outgoing)
1800 mail_free_envelope(&outgoing);
1802 if(body)
1803 pine_free_body(&body);
1805 if(fcc)
1806 fs_give((void **)&fcc);
1808 free_redraft_pos(&redraft_pos);
1809 free_action(&role);
1811 return(rv);
1815 void
1816 url_mailto_addr(struct mail_address **a, char *a_raw)
1818 char *p = cpystr(rfc1738_str(a_raw));
1820 while(*a) /* append to address list */
1821 a = &(*a)->next;
1823 rfc822_parse_adrlist(a, p, ps_global->maildomain);
1824 fs_give((void **) &p);
1829 * imap URL digester ala RFC 2192
1832 url_local_imap(char *url)
1834 char *folder, *mailbox = NULL, *errstr = NULL, *search = NULL,
1835 newfolder[MAILTMPLEN];
1836 int rv;
1837 long i;
1838 imapuid_t uid = 0L, uid_val = 0L;
1839 CONTEXT_S *fake_context;
1840 MESSAGECACHE *mc;
1842 rv = url_imap_folder(url, &folder, &uid, &uid_val, &search, 0);
1843 switch(rv & URL_IMAP_MASK){
1844 case URL_IMAP_IMAILBOXLIST :
1845 /* BUG: deal with lsub tag */
1846 if((fake_context = new_context(folder, NULL)) != NULL){
1847 newfolder[0] = '\0';
1848 if(display_folder_list(&fake_context, newfolder,
1849 0, folders_for_goto))
1850 if(strlen(newfolder) + 1 < MAILTMPLEN)
1851 mailbox = newfolder;
1853 else
1854 errstr = "Problem building URL's folder list";
1856 fs_give((void **) &folder);
1857 free_context(&fake_context);
1859 if(mailbox){
1860 return(1);
1862 else if(errstr)
1863 q_status_message(SM_ORDER|SM_DING, 3, 3, errstr);
1864 else
1865 cmd_cancelled("URL Launch");
1867 break;
1869 case URL_IMAP_IMESSAGEPART :
1870 case URL_IMAP_IMESSAGELIST :
1871 if(ps_global && ps_global->ttyo){
1872 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
1873 ps_global->mangled_footer = 1;
1876 rv = do_broach_folder(folder, NULL, NULL, 0L);
1877 fs_give((void **) &folder);
1878 switch(rv){
1879 case -1 : /* utter failure */
1880 ps_global->next_screen = main_menu_screen;
1881 break;
1883 case 0 : /* same folder reopened */
1884 ps_global->next_screen = mail_index_screen;
1885 break;
1887 case 1 : /* requested folder open */
1888 ps_global->next_screen = mail_index_screen;
1890 if(uid_val && uid_val != ps_global->mail_stream->uid_validity){
1891 /* Complain! */
1892 q_status_message(SM_ORDER|SM_DING, 3, 3,
1893 "Warning! Referenced folder changed since URL recorded");
1896 if(uid){
1898 * Make specified message the currently selected..
1900 for(i = 1L; i <= mn_get_total(ps_global->msgmap); i++)
1901 if(mail_uid(ps_global->mail_stream, i) == uid){
1902 ps_global->next_screen = mail_view_screen;
1903 mn_set_cur(ps_global->msgmap, i);
1904 break;
1907 if(i > mn_get_total(ps_global->msgmap))
1908 q_status_message(SM_ORDER, 2, 3,
1909 "Couldn't find specified article number");
1911 else if(search){
1913 * Select the specified messages
1914 * and present a zoom'd index...
1916 /* BUG: not dealing with CHARSET yet */
1918 /* ANOTHER BUG: mail_criteria is a compatibility routine for IMAP2BIS
1919 * so it doesn't know about IMAP4 search criteria, like SENTSINCE.
1920 * It also doesn't handle literals. */
1922 pine_mail_search_full(ps_global->mail_stream, NULL,
1923 mail_criteria(search),
1924 SE_NOPREFETCH | SE_FREE);
1926 for(i = 1L; i <= mn_get_total(ps_global->msgmap); i++)
1927 if(ps_global->mail_stream
1928 && i <= ps_global->mail_stream->nmsgs
1929 && (mc = mail_elt(ps_global->mail_stream, i))
1930 && mc->searched)
1931 set_lflag(ps_global->mail_stream,
1932 ps_global->msgmap, i, MN_SLCT, 1);
1934 if((i = any_lflagged(ps_global->msgmap, MN_SLCT)) != 0){
1936 q_status_message2(SM_ORDER, 0, 3,
1937 "%s message%s selected",
1938 long2string(i), plural(i));
1939 /* Zoom the index! */
1940 zoom_index(ps_global, ps_global->mail_stream,
1941 ps_global->msgmap, MN_SLCT);
1946 return(1);
1948 default:
1949 case URL_IMAP_ERROR :
1950 break;
1953 return(0);
1958 url_local_nntp(char *url)
1960 char folder[2*MAILTMPLEN], *group;
1961 int group_len;
1962 long i, article_num;
1964 /* no hostport, no url, end of story */
1965 if((group = strchr(url + 7, '/')) != 0){
1966 group++;
1967 for(group_len = 0; group[group_len] && group[group_len] != '/';
1968 group_len++)
1969 if(!rfc1738_group(&group[group_len]))
1970 /* TRANSLATORS: these are errors in news group URLs */
1971 return(url_bogus(url, _("Invalid newsgroup specified")));
1973 if(group_len){
1974 snprintf(folder, sizeof(folder), "{%.*s/nntp}#news.%.*s",
1975 (int) MIN((group - 1) - (url + 7), MAILTMPLEN-20), url + 7,
1976 (int) MIN(group_len, MAILTMPLEN-20), group);
1977 folder[sizeof(folder)-1] = '\0';
1979 else
1980 return(url_bogus(url, _("No newsgroup specified")));
1982 else
1983 return(url_bogus(url, _("No server specified")));
1985 if(ps_global && ps_global->ttyo){
1986 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
1987 ps_global->mangled_footer = 1;
1990 switch(do_broach_folder(rfc1738_str(folder), NULL, NULL, 0L)){
1991 case -1 : /* utter failure */
1992 ps_global->next_screen = main_menu_screen;
1993 break;
1995 case 0 : /* same folder reopened */
1996 ps_global->next_screen = mail_index_screen;
1997 break;
1999 case 1 : /* requested folder open */
2000 ps_global->next_screen = mail_index_screen;
2002 /* grok article number --> c-client UID */
2003 if(group[group_len++] == '/'
2004 && (article_num = atol(&group[group_len]))){
2006 * Make the requested article our current message
2008 for(i = 1; i <= mn_get_nmsgs(ps_global->msgmap); i++)
2009 if(mail_uid(ps_global->mail_stream, i) == article_num){
2010 ps_global->next_screen = mail_view_screen;
2011 if((i = mn_raw2m(ps_global->msgmap, i)) != 0)
2012 mn_set_cur(ps_global->msgmap, i);
2013 break;
2016 if(i == 0 || i > mn_get_total(ps_global->msgmap))
2017 q_status_message(SM_ORDER, 2, 3,
2018 _("Couldn't find specified article number"));
2021 break;
2024 ps_global->redrawer = (void(*)(void))NULL;
2025 return(1);
2030 url_local_news(char *url)
2032 char folder[MAILTMPLEN], *p;
2033 CONTEXT_S *cntxt = NULL;
2036 * NOTE: NO SUPPORT for '*' or message-id
2038 if(*(url+5)){
2039 if(*(url+5) == '/' && *(url+6) == '/')
2040 return(url_local_nntp(url)); /* really meant "nntp://"? */
2042 if(ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0])
2044 * BUG: Only the first NNTP server is tried.
2046 snprintf(folder, sizeof(folder), "{%s/nntp}#news.", ps_global->VAR_NNTP_SERVER[0]);
2047 else
2048 strncpy(folder, "#news.", sizeof(folder));
2050 folder[sizeof(folder)-1] = '\0';
2052 for(p = strncpy(folder + strlen(folder), url + 5, sizeof(folder)-strlen(folder)-1);
2053 *p && rfc1738_group(p);
2054 p++)
2057 if(*p)
2058 return(url_bogus(url, "Invalid newsgroup specified"));
2060 else{ /* fish first group from newsrc */
2061 FOLDER_S *f;
2062 int alphaorder;
2064 folder[0] = '\0';
2066 /* Find first news context */
2067 for(cntxt = ps_global->context_list;
2068 cntxt && !(cntxt->use & CNTXT_NEWS);
2069 cntxt = cntxt->next)
2072 if(cntxt){
2073 if((alphaorder = F_OFF(F_READ_IN_NEWSRC_ORDER, ps_global)) != 0)
2074 (void) F_SET(F_READ_IN_NEWSRC_ORDER, ps_global, 1);
2076 build_folder_list(NULL, cntxt, NULL, NULL, BFL_LSUB);
2077 if((f = folder_entry(0, FOLDERS(cntxt))) != NULL){
2078 strncpy(folder, f->name, sizeof(folder));
2079 folder[sizeof(folder)-1] = '\0';
2082 free_folder_list(cntxt);
2084 if(alphaorder)
2085 (void) F_SET(F_READ_IN_NEWSRC_ORDER, ps_global, 0);
2088 if(folder[0] == '\0'){
2089 q_status_message(SM_ORDER | SM_DING, 3, 3,
2090 "No default newsgroup");
2091 return(0);
2095 if(ps_global && ps_global->ttyo){
2096 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2097 ps_global->mangled_footer = 1;
2100 if(do_broach_folder(rfc1738_str(folder), cntxt, NULL, 0L) < 0)
2101 ps_global->next_screen = main_menu_screen;
2102 else
2103 ps_global->next_screen = mail_index_screen;
2105 ps_global->redrawer = (void(*)(void))NULL;
2107 return(1);
2112 url_local_file(char *file_url)
2114 if(want_to(
2115 /* TRANSLATORS: this is a warning that the file URL can cause programs to run which may
2116 be a security problem. We are asking the user to confirm that they want to do this. */
2117 _("\"file\" URL may cause programs to be run on your system. Run anyway"),
2118 'n', 0, NO_HELP, WT_NORM) == 'y'){
2119 HANDLE_S handle;
2121 /* fake a handle */
2122 handle.h.url.path = file_url;
2123 if((handle.h.url.tool = url_external_handler(&handle, 1))
2124 || (handle.h.url.tool = url_external_handler(&handle, 0))){
2125 url_launch(&handle);
2126 return 1;
2128 else{
2129 q_status_message(SM_ORDER, 0, 4,
2130 _("No viewer for \"file\" URL. VIEWER command cancelled"));
2131 return 0;
2134 q_status_message(SM_ORDER, 0, 4, _("VIEWER command cancelled"));
2135 return 0;
2140 url_local_fragment(char *fragment)
2142 SCRLCTRL_S *st = scroll_state(SS_CUR);
2143 HANDLE_S *hp = NULL;
2146 * find a handle with the fragment's name
2148 if(st){
2149 for(hp = st->parms->text.handles; hp; hp = hp->next)
2150 if(hp->type == URL && hp->h.url.name
2151 && !strcmp(hp->h.url.name, fragment + 1))
2152 break;
2154 if(!hp)
2155 for(hp = st->parms->text.handles->prev; hp; hp = hp->prev)
2156 if(hp->type == URL && hp->h.url.name
2157 && !strcmp(hp->h.url.name, fragment + 1))
2158 break;
2161 * set the top line of the display to contain this line
2163 if(hp && hp->loc){
2164 st->top_text_line = hp->loc->where.row;
2165 ps_global->mangled_body = 1;
2167 else
2168 q_status_message1(SM_ORDER | SM_DING, 0, 3,
2169 "Can't find fragment: %s", fragment);
2171 return(1);
2175 ical_send_reply(char *url)
2177 // ical_compose_reply(url + strlen("x-alpine-ical:"));
2178 return 2;
2183 url_local_phone_home(char *URL)
2185 phone_home(URL + strlen("x-alpine-phone-home:"));
2186 return(2);
2191 * Format editorial comment referencing screen offering
2192 * List-* header supplied commands
2195 rfc2369_editorial(long int msgno, HANDLE_S **handlesp, int flags, int width, gf_io_t pc)
2197 char *p, *hdrp, *hdrs[MLCMD_COUNT + 1],
2198 color[64], buf[2048];
2199 int i, n, rv = TRUE;
2200 HANDLE_S *h = NULL;
2202 if((flags & FM_DISPLAY)
2203 && (hdrp = pine_fetchheader_lines(ps_global->mail_stream, msgno,
2204 NULL, rfc2369_hdrs(hdrs)))){
2205 if(*hdrp){
2206 snprintf(buf, sizeof(buf), "Note: This message contains ");
2207 buf[sizeof(buf)-1] = '\0';
2208 p = buf + strlen(buf);
2210 if(handlesp){
2211 h = new_handle(handlesp);
2212 h->type = Function;
2213 h->h.func.f = rfc2369_display;
2214 h->h.func.args.stream = ps_global->mail_stream;
2215 h->h.func.args.msgmap = ps_global->msgmap;
2216 h->h.func.args.msgno = msgno;
2218 if(!(flags & FM_NOCOLOR)
2219 && handle_start_color(color, sizeof(color), &n, 0)){
2220 if((p-buf)+n < sizeof(buf))
2221 for(i = 0; i < n; i++)
2222 *p++ = color[i];
2224 else if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)){
2225 *p++ = TAG_EMBED;
2226 *p++ = TAG_BOLDON;
2229 if((p-buf)+2 < sizeof(buf)){
2230 *p++ = TAG_EMBED;
2231 *p++ = TAG_HANDLE;
2234 snprintf(p + 1, sizeof(buf)-(p+1-buf), "%d", h->key);
2235 buf[sizeof(buf)-1] = '\0';
2236 *p = strlen(p + 1);
2237 p += (*p + 1);
2240 sstrncpy(&p, "email list management information", sizeof(buf)-(p-buf));
2241 buf[sizeof(buf)-1] = '\0';
2243 if(h){
2244 /* in case it was the current selection */
2245 if((p-buf)+2 < sizeof(buf)){
2246 *p++ = TAG_EMBED;
2247 *p++ = TAG_INVOFF;
2250 if(handle_end_color(color, sizeof(color), &n)){
2251 if((p-buf)+n < sizeof(buf))
2252 for(i = 0; i < n; i++)
2253 *p++ = color[i];
2255 else{
2256 if((p-buf)+2 < sizeof(buf)){
2257 *p++ = TAG_EMBED;
2258 *p++ = TAG_BOLDOFF;
2262 if(p-buf < sizeof(buf))
2263 *p = '\0';
2266 buf[sizeof(buf)-1] = '\0';
2268 rv = (gf_puts(NEWLINE, pc)
2269 && format_editorial(buf, width, flags, handlesp, pc) == NULL
2270 && gf_puts(NEWLINE, pc));
2273 fs_give((void **) &hdrp);
2276 return(rv);
2281 /*----------------------------------------------------------------------
2282 routine for displaying text on the screen.
2284 Args: sparms -- structure of args controlling what happens outside
2285 just the business of managing text scrolling
2287 This displays in three different kinds of text. One is an array of
2288 lines passed in in text_array. The other is a simple long string of
2289 characters passed in in text.
2291 The style determines what some of the error messages will be, and
2292 what commands are available as different things are appropriate for
2293 help text than for message text etc.
2295 ---*/
2298 scrolltool(SCROLL_S *sparms)
2300 register long cur_top_line, num_display_lines;
2301 UCS ch;
2302 int result, done, cmd, found_on, found_on_index,
2303 first_view, force, scroll_lines, km_size,
2304 cursor_row, cursor_col, km_popped;
2305 char *utf8str;
2306 long jn;
2307 struct key_menu *km;
2308 HANDLE_S *next_handle;
2309 bitmap_t bitmap;
2310 OtherMenu what;
2311 Pos whereis_pos;
2313 num_display_lines = SCROLL_LINES(ps_global);
2314 km_popped = 0;
2315 ps_global->mangled_header = 1;
2316 ps_global->mangled_footer = 1;
2317 ps_global->mangled_body = !sparms->body_valid;
2320 what = sparms->keys.what; /* which key menu to display */
2321 cur_top_line = 0;
2322 done = 0;
2323 found_on = -1;
2324 found_on_index = -1;
2325 first_view = 1;
2326 if(sparms->quell_first_view)
2327 first_view = 0;
2329 force = 0;
2330 ch = 'x'; /* for first time through */
2331 whereis_pos.row = 0;
2332 whereis_pos.col = 0;
2333 next_handle = sparms->text.handles;
2335 set_scroll_text(sparms, cur_top_line, scroll_state(SS_NEW));
2336 format_scroll_text();
2338 if((km = sparms->keys.menu) != NULL){
2339 memcpy(bitmap, sparms->keys.bitmap, sizeof(bitmap_t));
2341 else{
2342 setbitmap(bitmap);
2343 km = &simple_text_keymenu;
2344 #ifdef _WINDOWS
2345 sparms->mouse.popup = simple_text_popup;
2346 #endif
2349 if(!sparms->bar.title)
2350 sparms->bar.title = "Text";
2352 if(sparms->bar.style == TitleBarNone){
2353 if(THREADING() && sp_viewing_a_thread(ps_global->mail_stream))
2354 sparms->bar.style = ThrdMsgPercent;
2355 else
2356 sparms->bar.style = MsgTextPercent;
2359 switch(sparms->start.on){
2360 case LastPage :
2361 cur_top_line = MAX(0, scroll_text_lines() - (num_display_lines-2));
2362 if(F_ON(F_SHOW_CURSOR, ps_global)){
2363 whereis_pos.row = scroll_text_lines() - cur_top_line;
2364 found_on = scroll_text_lines() - 1;
2367 break;
2369 case Fragment :
2370 if(sparms->start.loc.frag){
2371 (void) url_local_fragment(sparms->start.loc.frag);
2373 cur_top_line = scroll_state(SS_CUR)->top_text_line;
2375 if(F_ON(F_SHOW_CURSOR, ps_global)){
2376 whereis_pos.row = scroll_text_lines() - cur_top_line;
2377 found_on = scroll_text_lines() - 1;
2381 break;
2383 case Offset :
2384 if(sparms->start.loc.offset){
2385 for(cur_top_line = 0L;
2386 cur_top_line + 1 < scroll_text_lines()
2387 && (sparms->start.loc.offset
2388 -= scroll_handle_column(cur_top_line,
2389 -1)) >= 0;
2390 cur_top_line++)
2394 break;
2396 case Handle :
2397 if(scroll_handle_obscured(sparms->text.handles))
2398 cur_top_line = scroll_handle_reframe(-1, TRUE);
2400 break;
2402 default : /* no-op */
2403 break;
2406 /* prepare for calls below to tell us where to go */
2407 ps_global->next_screen = SCREEN_FUN_NULL;
2409 cancel_busy_cue(-1);
2411 while(!done) {
2412 ps_global->user_says_cancel = 0;
2413 if(km_popped){
2414 km_popped--;
2415 if(km_popped == 0){
2416 clearfooter(ps_global);
2417 ps_global->mangled_body = 1;
2421 if(ps_global->mangled_screen) {
2422 ps_global->mangled_header = 1;
2423 ps_global->mangled_footer = 1;
2424 ps_global->mangled_body = 1;
2427 if(!sparms->quell_newmail && streams_died())
2428 ps_global->mangled_header = 1;
2430 dprint((9, "@@@@ current:%ld\n",
2431 mn_get_cur(ps_global->msgmap)));
2434 /*==================== All Screen painting ====================*/
2435 /*-------------- The title bar ---------------*/
2436 update_scroll_titlebar(cur_top_line, ps_global->mangled_header);
2438 if(ps_global->mangled_screen){
2439 /* this is the only line not cleared by header, body or footer
2440 * repaint calls....
2442 ClearLine(1);
2443 ps_global->mangled_screen = 0;
2446 /*---- Scroll or update the body of the text on the screen -------*/
2447 cur_top_line = scroll_scroll_text(cur_top_line, next_handle,
2448 ps_global->mangled_body);
2449 ps_global->redrawer = redraw_scroll_text;
2450 ps_global->mangled_body = 0;
2452 /*--- Check to see if keymenu might change based on next_handle --*/
2453 if(sparms->text.handles != next_handle)
2454 ps_global->mangled_footer = 1;
2456 if(next_handle)
2457 sparms->text.handles = next_handle;
2459 /*------------- The key menu footer --------------------*/
2460 if(ps_global->mangled_footer || sparms->keys.each_cmd){
2461 if(km_popped){
2462 FOOTER_ROWS(ps_global) = 3;
2463 clearfooter(ps_global);
2466 if(F_ON(F_ARROW_NAV, ps_global)){
2467 menu_clear_binding(km, KEY_LEFT);
2468 if((cmd = menu_clear_binding(km, '<')) != MC_UNKNOWN){
2469 menu_add_binding(km, '<', cmd);
2470 menu_add_binding(km, KEY_LEFT, cmd);
2474 if(F_ON(F_ARROW_NAV, ps_global)){
2475 menu_clear_binding(km, KEY_RIGHT);
2476 if((cmd = menu_clear_binding(km, '>')) != MC_UNKNOWN){
2477 menu_add_binding(km, '>', cmd);
2478 menu_add_binding(km, KEY_RIGHT, cmd);
2482 if(sparms->keys.each_cmd){
2483 (*sparms->keys.each_cmd)(sparms,
2484 scroll_handle_obscured(sparms->text.handles));
2485 memcpy(bitmap, sparms->keys.bitmap, sizeof(bitmap_t));
2488 if(menu_binding_index(km, MC_JUMP) >= 0){
2489 for(cmd = 0; cmd < 10; cmd++)
2490 if(F_ON(F_ENABLE_JUMP, ps_global))
2491 (void) menu_add_binding(km, '0' + cmd, MC_JUMP);
2492 else
2493 (void) menu_clear_binding(km, '0' + cmd);
2496 draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
2497 1-FOOTER_ROWS(ps_global), 0, what);
2498 what = SameMenu;
2499 ps_global->mangled_footer = 0;
2500 if(km_popped){
2501 FOOTER_ROWS(ps_global) = 1;
2502 mark_keymenu_dirty();
2506 if((ps_global->first_time_user || ps_global->show_new_version)
2507 && first_view && sparms->text.handles
2508 && (sparms->text.handles->next || sparms->text.handles->prev)
2509 && !sparms->quell_help)
2510 q_status_message(SM_ORDER, 0, 3, HANDLE_INIT_MSG);
2512 /*============ Check for New Mail and CheckPoint ============*/
2513 if(!sparms->quell_newmail &&
2514 new_mail(force, NM_TIMING(ch), NM_STATUS_MSG) >= 0){
2515 update_scroll_titlebar(cur_top_line, 1);
2516 if(ps_global->mangled_footer)
2517 draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
2518 1-FOOTER_ROWS(ps_global), 0, what);
2520 ps_global->mangled_footer = 0;
2524 * If an expunge of the current message happened during the
2525 * new mail check we want to bail out of here. See mm_expunged.
2527 if(ps_global->next_screen != SCREEN_FUN_NULL){
2528 done = 1;
2529 continue;
2532 if(ps_global->noticed_change_in_unseen){
2533 ps_global->noticed_change_in_unseen = 0; /* redraw only once */
2534 cmd = MC_RESIZE; /* causes cursor to be saved in folder_lister */
2535 done = 1;
2536 continue;
2539 if(first_view && num_display_lines >= scroll_text_lines())
2540 q_status_message1(SM_INFO, 0, 1, "ALL of %s", STYLE_NAME(sparms));
2543 force = 0; /* may not need to next time around */
2544 first_view = 0; /* check_point a priority any more? */
2546 /*==================== Output the status message ==============*/
2547 if(!sparms->no_stat_msg){
2548 if(km_popped){
2549 FOOTER_ROWS(ps_global) = 3;
2550 mark_status_unknown();
2553 display_message(ch);
2554 if(km_popped){
2555 FOOTER_ROWS(ps_global) = 1;
2556 mark_status_unknown();
2560 if(F_ON(F_SHOW_CURSOR, ps_global)){
2561 #ifdef WINDOWS
2562 if(cur_top_line != scroll_state(SS_CUR)->top_text_line)
2563 whereis_pos.row = 0;
2564 #endif
2566 if(whereis_pos.row > 0){
2567 cursor_row = SCROLL_LINES_ABOVE(ps_global)
2568 + whereis_pos.row - 1;
2569 cursor_col = whereis_pos.col;
2571 else{
2572 POSLIST_S *lp = NULL;
2574 if(sparms->text.handles &&
2575 !scroll_handle_obscured(sparms->text.handles)){
2576 SCRLCTRL_S *st = scroll_state(SS_CUR);
2578 for(lp = sparms->text.handles->loc; lp; lp = lp->next)
2579 if(lp->where.row >= st->top_text_line
2580 && lp->where.row < st->top_text_line
2581 + st->screen.length){
2582 cursor_row = lp->where.row - cur_top_line
2583 + SCROLL_LINES_ABOVE(ps_global);
2584 cursor_col = lp->where.col;
2585 break;
2589 if(!lp){
2590 cursor_col = 0;
2591 /* first new line of text */
2592 cursor_row = SCROLL_LINES_ABOVE(ps_global) +
2593 ((cur_top_line == 0) ? 0 : ps_global->viewer_overlap);
2597 else{
2598 cursor_col = 0;
2599 cursor_row = ps_global->ttyo->screen_rows
2600 - SCROLL_LINES_BELOW(ps_global);
2603 MoveCursor(cursor_row, cursor_col);
2605 /*================ Get command and validate =====================*/
2606 #ifdef MOUSE
2607 #ifndef WIN32
2608 if(sparms->text.handles)
2609 #endif
2611 mouse_in_content(KEY_MOUSE, -1, -1, 0x5, 0);
2612 register_mfunc(mouse_in_content, HEADER_ROWS(ps_global), 0,
2613 ps_global->ttyo->screen_rows
2614 - (FOOTER_ROWS(ps_global) + 1),
2615 ps_global->ttyo->screen_cols);
2617 #endif
2618 #ifdef _WINDOWS
2619 mswin_allowcopy(mswin_readscrollbuf);
2620 mswin_setscrollcallback(pcpine_do_scroll);
2622 if(sparms->help.text != NO_HELP)
2623 mswin_sethelptextcallback(pcpine_help_scroll);
2625 if(sparms->text.handles
2626 && sparms->text.handles->type != Folder)
2627 mswin_mousetrackcallback(pcpine_view_cursor);
2629 if(ps_global->prev_screen == mail_view_screen)
2630 mswin_setviewinwindcallback(view_in_new_window);
2631 #endif
2632 ch = (sparms->quell_newmail || read_command_prep()) ? read_command(&utf8str) : NO_OP_COMMAND;
2633 #ifdef MOUSE
2634 #ifndef WIN32
2635 if(sparms->text.handles)
2636 #endif
2637 clear_mfunc(mouse_in_content);
2638 #endif
2639 #ifdef _WINDOWS
2640 mswin_allowcopy(NULL);
2641 mswin_setscrollcallback(NULL);
2642 mswin_sethelptextcallback(NULL);
2643 mswin_mousetrackcallback(NULL);
2644 mswin_setviewinwindcallback(NULL);
2645 cur_top_line = scroll_state(SS_CUR)->top_text_line;
2646 #endif
2648 cmd = menu_command(ch, km);
2650 if(km_popped)
2651 switch(cmd){
2652 case MC_NONE :
2653 case MC_OTHER :
2654 case MC_RESIZE:
2655 case MC_REPAINT :
2656 km_popped++;
2657 break;
2659 default:
2660 clearfooter(ps_global);
2661 break;
2665 /*============= Execute command =======================*/
2666 switch(cmd){
2668 /* ------ Help -------*/
2669 case MC_HELP :
2670 if(FOOTER_ROWS(ps_global) == 1 && km_popped == 0){
2671 km_popped = 2;
2672 ps_global->mangled_footer = 1;
2673 break;
2676 whereis_pos.row = 0;
2677 if(sparms->help.text == NO_HELP){
2678 q_status_message(SM_ORDER, 0, 5,
2679 _("No help text currently available"));
2680 break;
2683 km_size = FOOTER_ROWS(ps_global);
2685 helper(sparms->help.text, sparms->help.title, 0);
2687 if(ps_global->next_screen != main_menu_screen
2688 && km_size == FOOTER_ROWS(ps_global)) {
2689 /* Have to reset because helper uses scroll_text */
2690 num_display_lines = SCROLL_LINES(ps_global);
2691 ps_global->mangled_screen = 1;
2693 else
2694 done = 1;
2696 break;
2699 /*---------- Roll keymenu ------*/
2700 case MC_OTHER :
2701 if(F_OFF(F_USE_FK, ps_global))
2702 warn_other_cmds();
2704 what = NextMenu;
2705 ps_global->mangled_footer = 1;
2706 break;
2709 /* -------- Scroll back one page -----------*/
2710 case MC_PAGEUP :
2711 whereis_pos.row = 0;
2712 if(cur_top_line) {
2713 scroll_lines = MIN(MAX(num_display_lines -
2714 ps_global->viewer_overlap, 1), num_display_lines);
2715 cur_top_line -= scroll_lines;
2716 if(cur_top_line <= 0){
2717 cur_top_line = 0;
2718 q_status_message1(SM_INFO, 0, 1, "START of %s",
2719 STYLE_NAME(sparms));
2722 else{
2723 /* hilite last available handle */
2724 next_handle = NULL;
2725 if(sparms->text.handles){
2726 HANDLE_S *h = sparms->text.handles;
2728 while((h = scroll_handle_prev_sel(h))
2729 && !scroll_handle_obscured(h))
2730 next_handle = h;
2733 if(!next_handle)
2734 q_status_message1(SM_ORDER, 0, 1, _("Already at start of %s"),
2735 STYLE_NAME(sparms));
2740 break;
2743 /*---- Scroll down one page -------*/
2744 case MC_PAGEDN :
2745 if(cur_top_line + num_display_lines < scroll_text_lines()){
2746 whereis_pos.row = 0;
2747 scroll_lines = MIN(MAX(num_display_lines -
2748 ps_global->viewer_overlap, 1), num_display_lines);
2749 cur_top_line += scroll_lines;
2751 if(cur_top_line + num_display_lines >= scroll_text_lines())
2752 q_status_message1(SM_INFO, 0, 1, "END of %s",
2753 STYLE_NAME(sparms));
2755 else if(!sparms->end_scroll
2756 || !(done = (*sparms->end_scroll)(sparms))){
2757 q_status_message1(SM_ORDER, 0, 1, _("Already at end of %s"),
2758 STYLE_NAME(sparms));
2759 /* hilite last available handle */
2760 if(sparms->text.handles){
2761 HANDLE_S *h = sparms->text.handles;
2763 while((h = scroll_handle_next_sel(h)) != NULL)
2764 next_handle = h;
2768 break;
2770 /* scroll to the top page */
2771 case MC_HOMEKEY:
2772 if(cur_top_line){
2773 cur_top_line = 0;
2774 q_status_message1(SM_INFO, 0, 1, "START of %s",
2775 STYLE_NAME(sparms));
2778 next_handle = NULL;
2779 if(sparms->text.handles){
2780 HANDLE_S *h = sparms->text.handles;
2782 while((h = scroll_handle_prev_sel(h)) != NULL)
2783 next_handle = h;
2785 break;
2787 /* scroll to the bottom page */
2788 case MC_ENDKEY:
2789 if(cur_top_line + num_display_lines < scroll_text_lines()){
2790 cur_top_line = scroll_text_lines() - MIN(5, num_display_lines);
2791 q_status_message1(SM_INFO, 0, 1, "END of %s",
2792 STYLE_NAME(sparms));
2795 if(sparms->text.handles){
2796 HANDLE_S *h = sparms->text.handles;
2798 while((h = scroll_handle_next_sel(h)) != NULL)
2799 next_handle = h;
2801 break;
2803 /*------ Scroll down one line -----*/
2804 case MC_CHARDOWN :
2805 next_handle = NULL;
2806 if(sparms->text.handles){
2807 if(sparms->vert_handle){
2808 HANDLE_S *h, *h2;
2809 int i, j, k;
2811 h2 = sparms->text.handles;
2812 if(h2->type == Folder && h2->prev && h2->prev->is_dual_do_open)
2813 h2 = h2->prev;
2815 i = h2->loc->where.row + 1;
2816 j = h2->loc->where.col;
2817 for(h = NULL, k = h2->key;
2818 h2 && (!h
2819 || (h->loc->where.row == h2->loc->where.row));
2820 h2 = h2->next)
2821 /* must be different key */
2822 /* ... below current line */
2823 /* ... pref'bly to left */
2824 if(h2->key != k
2825 && h2->loc->where.row >= i){
2826 if(h2->loc->where.col > j){
2827 if(!h)
2828 h = h2;
2830 break;
2832 else
2833 h = h2;
2836 if(h){
2837 whereis_pos.row = 0;
2838 next_handle = h;
2839 if((result = scroll_handle_obscured(next_handle)) != 0){
2840 long new_top;
2842 if(scroll_handle_obscured(sparms->text.handles)
2843 && result > 0)
2844 next_handle = sparms->text.handles;
2846 ps_global->mangled_body++;
2847 new_top = scroll_handle_reframe(next_handle->key,0);
2848 if(new_top >= 0)
2849 cur_top_line = new_top;
2853 else if(!(ch == ctrl('N') || F_ON(F_FORCE_ARROWS, ps_global)))
2854 next_handle = scroll_handle_next(sparms->text.handles);
2857 if(!next_handle){
2858 if(cur_top_line + num_display_lines < scroll_text_lines()){
2859 whereis_pos.row = 0;
2860 cur_top_line++;
2861 if(cur_top_line + num_display_lines >= scroll_text_lines())
2862 q_status_message1(SM_INFO, 0, 1, "END of %s",
2863 STYLE_NAME(sparms));
2865 else
2866 q_status_message1(SM_ORDER, 0, 1, _("Already at end of %s"),
2867 STYLE_NAME(sparms));
2870 break;
2873 /* ------ Scroll back up one line -------*/
2874 case MC_CHARUP :
2875 next_handle = NULL;
2876 if(sparms->text.handles){
2877 if(sparms->vert_handle){
2878 HANDLE_S *h, *h2;
2879 int i, j, k;
2881 h2 = sparms->text.handles;
2882 if(h2->type == Folder && h2->prev && h2->prev->is_dual_do_open)
2883 h2 = h2->prev;
2885 i = h2->loc->where.row - 1;
2886 j = h2->loc->where.col;
2888 for(h = NULL, k = h2->key;
2889 h2 && (!h
2890 || (h->loc->where.row == h2->loc->where.row));
2891 h2 = h2->prev)
2892 /* must be new key, above current
2893 * line and pref'bly to right
2895 if(h2->key != k
2896 && h2->loc->where.row <= i){
2897 if(h2->loc->where.col < j){
2898 if(!h)
2899 h = h2;
2901 break;
2903 else
2904 h = h2;
2907 if(h){
2908 whereis_pos.row = 0;
2909 next_handle = h;
2910 if((result = scroll_handle_obscured(next_handle)) != 0){
2911 long new_top;
2913 if(scroll_handle_obscured(sparms->text.handles)
2914 && result < 0)
2915 next_handle = sparms->text.handles;
2917 ps_global->mangled_body++;
2918 new_top = scroll_handle_reframe(next_handle->key,0);
2919 if(new_top >= 0)
2920 cur_top_line = new_top;
2924 else if(!(ch == ctrl('P') || F_ON(F_FORCE_ARROWS, ps_global)))
2925 next_handle = scroll_handle_prev(sparms->text.handles);
2928 if(!next_handle){
2929 whereis_pos.row = 0;
2930 if(cur_top_line){
2931 cur_top_line--;
2932 if(cur_top_line == 0)
2933 q_status_message1(SM_INFO, 0, 1, "START of %s",
2934 STYLE_NAME(sparms));
2936 else
2937 q_status_message1(SM_ORDER, 0, 1,
2938 _("Already at start of %s"),
2939 STYLE_NAME(sparms));
2942 break;
2945 case MC_NEXT_HANDLE :
2946 if((next_handle = scroll_handle_next_sel(sparms->text.handles)) != NULL){
2947 whereis_pos.row = 0;
2948 if((result = scroll_handle_obscured(next_handle)) != 0){
2949 long new_top;
2951 if(scroll_handle_obscured(sparms->text.handles)
2952 && result > 0)
2953 next_handle = sparms->text.handles;
2955 ps_global->mangled_body++;
2956 new_top = scroll_handle_reframe(next_handle->key, 0);
2957 if(new_top >= 0)
2958 cur_top_line = new_top;
2961 else{
2962 if(scroll_handle_obscured(sparms->text.handles)){
2963 long new_top;
2965 ps_global->mangled_body++;
2966 if((new_top = scroll_handle_reframe(-1, 0)) >= 0){
2967 whereis_pos.row = 0;
2968 cur_top_line = new_top;
2972 q_status_message1(SM_ORDER, 0, 1,
2973 _("Already on last item in %s"),
2974 STYLE_NAME(sparms));
2977 break;
2980 case MC_PREV_HANDLE :
2981 if((next_handle = scroll_handle_prev_sel(sparms->text.handles)) != NULL){
2982 whereis_pos.row = 0;
2983 if((result = scroll_handle_obscured(next_handle)) != 0){
2984 long new_top;
2986 if(scroll_handle_obscured(sparms->text.handles)
2987 && result < 0)
2988 next_handle = sparms->text.handles;
2990 ps_global->mangled_body++;
2991 new_top = scroll_handle_reframe(next_handle->key, 0);
2992 if(new_top >= 0)
2993 cur_top_line = new_top;
2996 else{
2997 if(scroll_handle_obscured(sparms->text.handles)){
2998 long new_top;
3000 ps_global->mangled_body++;
3001 if((new_top = scroll_handle_reframe(-1, 0)) >= 0){
3002 whereis_pos.row = 0;
3003 cur_top_line = new_top;
3007 q_status_message1(SM_ORDER, 0, 1,
3008 _("Already on first item in %s"),
3009 STYLE_NAME(sparms));
3012 break;
3015 /*------ View the current handle ------*/
3016 case MC_VIEW_HANDLE :
3017 switch(scroll_handle_obscured(sparms->text.handles)){
3018 default :
3019 case 0 :
3020 switch(scroll_handle_launch(sparms->text.handles,
3021 sparms->text.handles->force_display)){
3022 case 1 :
3023 cmd = MC_EXIT; /* propagate */
3024 done = 1;
3025 break;
3027 case -1 :
3028 cmd_cancelled(NULL);
3029 break;
3031 default :
3032 break;
3035 cur_top_line = scroll_state(SS_CUR)->top_text_line;
3036 break;
3038 case 1 :
3039 q_status_message(SM_ORDER, 0, 2, HANDLE_BELOW_ERR);
3040 break;
3042 case -1 :
3043 q_status_message(SM_ORDER, 0, 2, HANDLE_ABOVE_ERR);
3044 break;
3047 break;
3049 /*---------- Search text (where is) ----------*/
3050 case MC_WHEREIS :
3051 ps_global->mangled_footer = 1;
3052 {long start_row;
3053 int start_index, key = 0;
3054 char *report = NULL;
3056 start_row = cur_top_line;
3057 start_index = 0;
3059 if(F_ON(F_SHOW_CURSOR,ps_global)){
3060 if(found_on < 0
3061 || found_on >= scroll_text_lines()
3062 || found_on < cur_top_line
3063 || found_on >= cur_top_line + num_display_lines){
3064 start_row = cur_top_line;
3065 start_index = 0;
3067 else{
3068 if(found_on_index < 0){
3069 start_row = found_on + 1;
3070 start_index = 0;
3072 else{
3073 start_row = found_on;
3074 start_index = found_on_index+1;
3078 else if(sparms->srch_handle){
3079 HANDLE_S *h;
3081 if((h = scroll_handle_next_sel(sparms->text.handles)) != NULL){
3083 * Translate the screen's column into the
3084 * line offset to start on...
3086 * This makes it so search_text never returns -3
3087 * so we don't know it is the same match. That's
3088 * because we start well after the current handle
3089 * (at the next handle) and that causes us to
3090 * think the one we just matched on is a different
3091 * one from before. Can't think of an easy way to
3092 * fix it, though, and it isn't a big deal. We still
3093 * match, we just don't say current line contains
3094 * the only match.
3096 start_row = h->loc->where.row;
3097 start_index = scroll_handle_index(start_row, h->loc->where.col);
3099 else{
3100 /* last handle, start over at top */
3101 start_row = cur_top_line;
3102 start_index = 0;
3105 else{
3106 start_row = (found_on < 0
3107 || found_on >= scroll_text_lines()
3108 || found_on < cur_top_line
3109 || found_on >= cur_top_line + num_display_lines)
3110 ? cur_top_line : found_on + 1,
3111 start_index = 0;
3114 found_on = search_text(-FOOTER_ROWS(ps_global), start_row,
3115 start_index, &report,
3116 &whereis_pos, &found_on_index);
3118 if(found_on == -4){ /* search to top of text */
3119 whereis_pos.row = 0;
3120 whereis_pos.col = 0;
3121 found_on = 0;
3122 if(sparms->text.handles && sparms->srch_handle)
3123 key = 1;
3125 else if(found_on == -5){ /* search to bottom of text */
3126 HANDLE_S *h;
3128 whereis_pos.row = MAX(scroll_text_lines() - 1, 0);
3129 whereis_pos.col = 0;
3130 found_on = whereis_pos.row;
3131 if((h = sparms->text.handles) && sparms->srch_handle)
3133 key = h->key;
3134 while((h = h->next) != NULL);
3136 else if(found_on == -3){
3137 whereis_pos.row = found_on = start_row;
3138 found_on_index = start_index - 1;
3139 q_status_message(SM_ORDER, 1, 3,
3140 _("Current line contains the only match"));
3143 if(found_on >= 0){
3144 result = found_on < cur_top_line;
3145 if(!key)
3146 key = (sparms->text.handles)
3147 ? dot_on_handle(found_on, whereis_pos.col) : 0;
3149 if(F_ON(F_FORCE_LOW_SPEED,ps_global)
3150 || ps_global->low_speed
3151 || F_ON(F_SHOW_CURSOR,ps_global)
3152 || key){
3153 if((found_on >= cur_top_line + num_display_lines ||
3154 found_on < cur_top_line) &&
3155 num_display_lines > ps_global->viewer_overlap){
3156 cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
3157 if(scroll_text_lines()-cur_top_line < 5)
3158 cur_top_line = MAX(0,
3159 scroll_text_lines()-MIN(5,num_display_lines));
3161 /* else leave cur_top_line alone */
3163 else{
3164 cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
3165 if(scroll_text_lines()-cur_top_line < 5)
3166 cur_top_line = MAX(0,
3167 scroll_text_lines()-MIN(5,num_display_lines));
3170 whereis_pos.row = whereis_pos.row - cur_top_line + 1;
3171 if(report)
3172 q_status_message(SM_ORDER, 0, 3, report);
3173 else
3174 q_status_message2(SM_ORDER, 0, 3,
3175 "%sFound on line %s on screen",
3176 result ? "Search wrapped to start. " : "",
3177 int2string(whereis_pos.row));
3179 if(key){
3180 if(sparms->text.handles->key < key)
3181 for(next_handle = sparms->text.handles->next;
3182 next_handle->key != key;
3183 next_handle = next_handle->next)
3185 else
3186 for(next_handle = sparms->text.handles;
3187 next_handle->key != key;
3188 next_handle = next_handle->prev)
3192 else if(found_on == -1)
3193 cmd_cancelled("Search");
3194 else
3195 q_status_message(SM_ORDER, 0, 3, _("Word not found"));
3198 break;
3201 /*-------------- jump command -------------*/
3202 /* NOTE: preempt the process_cmd() version because
3203 * we need to get at the number..
3205 case MC_JUMP :
3206 jn = jump_to(ps_global->msgmap, -FOOTER_ROWS(ps_global), ch,
3207 sparms, View);
3208 if(sparms && sparms->jump_is_debug)
3209 done = 1;
3210 else if(jn > 0 && jn != mn_get_cur(ps_global->msgmap)){
3212 if(mn_total_cur(ps_global->msgmap) > 1L)
3213 mn_reset_cur(ps_global->msgmap, jn);
3214 else
3215 mn_set_cur(ps_global->msgmap, jn);
3217 done = 1;
3219 else
3220 ps_global->mangled_footer = 1;
3222 break;
3225 #ifdef MOUSE
3226 /*-------------- Mouse Event -------------*/
3227 case MC_MOUSE:
3229 MOUSEPRESS mp;
3230 long line;
3231 int key;
3233 mouse_get_last (NULL, &mp);
3234 mp.row -= 2;
3236 /* The clicked line have anything special on it? */
3237 if((line = cur_top_line + mp.row) < scroll_text_lines()
3238 && (key = dot_on_handle(line, mp.col))){
3239 switch(mp.button){
3240 case M_BUTTON_RIGHT :
3241 #ifdef _WINDOWS
3242 if(sparms->mouse.popup){
3243 if(sparms->text.handles->key < key)
3244 for(next_handle = sparms->text.handles->next;
3245 next_handle->key != key;
3246 next_handle = next_handle->next)
3248 else
3249 for(next_handle = sparms->text.handles;
3250 next_handle->key != key;
3251 next_handle = next_handle->prev)
3254 if(sparms->mouse.popup){
3255 cur_top_line = scroll_scroll_text(cur_top_line,
3256 next_handle,
3257 ps_global->mangled_body);
3258 fflush(stdout);
3259 switch((*sparms->mouse.popup)(sparms, key)){
3260 case 1 :
3261 cur_top_line = doubleclick_handle(sparms, next_handle, &cmd, &done);
3262 break;
3264 case 2 :
3265 done++;
3266 break;
3271 #endif
3272 break;
3274 case M_BUTTON_LEFT :
3275 if(sparms->text.handles->key < key)
3276 for(next_handle = sparms->text.handles->next;
3277 next_handle->key != key;
3278 next_handle = next_handle->next)
3280 else
3281 for(next_handle = sparms->text.handles;
3282 next_handle->key != key;
3283 next_handle = next_handle->prev)
3286 if(mp.doubleclick) /* launch url */
3287 cur_top_line = doubleclick_handle(sparms, next_handle, &cmd, &done);
3288 else if(sparms->mouse.click)
3289 (*sparms->mouse.click)(sparms);
3291 break;
3293 case M_BUTTON_MIDDLE : /* NO-OP for now */
3294 break;
3296 default: /* just ignore */
3297 break;
3300 #ifdef _WINDOWS
3301 else if(mp.button == M_BUTTON_RIGHT){
3303 * Toss generic popup on to the screen
3305 if(sparms->mouse.popup)
3306 if((*sparms->mouse.popup)(sparms, 0) == 2){
3307 done++;
3310 #endif
3313 break;
3314 #endif /* MOUSE */
3317 /*-------------- Display Resize -------------*/
3318 case MC_RESIZE :
3319 if(sparms->resize_exit){
3320 long line;
3323 * Figure out char offset of the char in the top left
3324 * corner of the display. Pass it back to the
3325 * fetcher/formatter and have it pass the offset
3326 * back to us...
3328 sparms->start.on = Offset;
3329 for(sparms->start.loc.offset = line = 0L;
3330 line < cur_top_line;
3331 line++)
3332 sparms->start.loc.offset += scroll_handle_column(line, -1);
3334 done = 1;
3335 ClearLine(1);
3336 break;
3338 /* else no reformatting neccessary, fall thru to repaint */
3341 /*-------------- refresh -------------*/
3342 case MC_REPAINT :
3343 num_display_lines = SCROLL_LINES(ps_global);
3344 mark_status_dirty();
3345 mark_keymenu_dirty();
3346 mark_titlebar_dirty();
3347 ps_global->mangled_screen = 1;
3348 force = 1;
3349 break;
3352 /*------- no op timeout to check for new mail ------*/
3353 case MC_NONE :
3354 break;
3357 /*------- Forward displayed text ------*/
3358 case MC_FWDTEXT :
3359 forward_text(ps_global, sparms->text.text, sparms->text.src);
3360 break;
3363 /*----------- Save the displayed text ------------*/
3364 case MC_SAVETEXT :
3365 (void)simple_export(ps_global, sparms->text.text,
3366 sparms->text.src, "text", NULL);
3367 break;
3370 /*----------- Exit this screen ------------*/
3371 case MC_EXIT :
3372 done = 1;
3373 break;
3376 /*----------- Pop back to the Main Menu ------------*/
3377 case MC_MAIN :
3378 ps_global->next_screen = main_menu_screen;
3379 done = 1;
3380 break;
3383 /*----------- Print ------------*/
3384 case MC_PRINTTXT :
3385 print_to_printer(sparms);
3386 break;
3389 /* ------- First handle on Line ------ */
3390 case MC_GOTOBOL :
3391 if(sparms->text.handles){
3392 next_handle = scroll_handle_boundary(sparms->text.handles,
3393 scroll_handle_prev_sel);
3395 break;
3397 /* fall thru as bogus */
3399 /* ------- Last handle on Line ------ */
3400 case MC_GOTOEOL :
3401 if(sparms->text.handles){
3402 next_handle = scroll_handle_boundary(sparms->text.handles,
3403 scroll_handle_next_sel);
3405 break;
3407 /* fall thru as bogus */
3409 /*------- BOGUS INPUT ------*/
3410 case MC_CHARRIGHT :
3411 case MC_CHARLEFT :
3412 case MC_UNKNOWN :
3413 if(sparms->bogus_input)
3414 done = (*sparms->bogus_input)(ch);
3415 else
3416 bogus_command(ch, F_ON(F_USE_FK,ps_global) ? "F1" : "?");
3418 break;
3421 case MC_UTF8:
3422 bogus_utf8_command(utf8str, F_ON(F_USE_FK, ps_global) ? "F1" : "?");
3423 break;
3426 /*------- Standard commands ------*/
3427 default:
3428 whereis_pos.row = 0;
3429 if(sparms->proc.tool)
3430 result = (*sparms->proc.tool)(cmd, ps_global->msgmap, sparms);
3431 else
3432 result = process_cmd(ps_global, ps_global->mail_stream,
3433 ps_global->msgmap, cmd, View, &force);
3435 dprint((7, "PROCESS_CMD return: %d\n", result));
3437 if(ps_global->next_screen != SCREEN_FUN_NULL || result == 1){
3438 done = 1;
3439 if(cmd == MC_FULLHDR){
3440 if(ps_global->full_header == 1){
3441 long line;
3444 * Figure out char offset of the char in the top left
3445 * corner of the display. Pass it back to the
3446 * fetcher/formatter and have it pass the offset
3447 * back to us...
3449 sparms->start.on = Offset;
3450 for(sparms->start.loc.offset = line = 0L;
3451 line < cur_top_line;
3452 line++)
3453 sparms->start.loc.offset +=
3454 scroll_handle_column(line, -1);
3456 else
3457 sparms->start.on = 0;
3459 switch(km->which){
3460 case 0:
3461 sparms->keys.what = FirstMenu;
3462 break;
3463 case 1:
3464 sparms->keys.what = SecondMenu;
3465 break;
3466 case 2:
3467 sparms->keys.what = ThirdMenu;
3468 break;
3469 case 3:
3470 sparms->keys.what = FourthMenu;
3471 break;
3475 else if(!scroll_state(SS_CUR)){
3476 num_display_lines = SCROLL_LINES(ps_global);
3477 ps_global->mangled_screen = 1;
3480 break;
3482 } /* End of switch() */
3484 /* Need to frame some handles? */
3485 if(sparms->text.handles
3486 && ((!next_handle
3487 && handle_on_page(sparms->text.handles, cur_top_line,
3488 cur_top_line + num_display_lines))
3489 || (next_handle
3490 && handle_on_page(next_handle, cur_top_line,
3491 cur_top_line + num_display_lines))))
3492 next_handle = scroll_handle_in_frame(cur_top_line);
3494 } /* End of while() -- loop executing commands */
3496 ps_global->redrawer = NULL; /* next statement makes this invalid! */
3497 zero_scroll_text(); /* very important to zero out on return!!! */
3498 scroll_state(SS_FREE);
3499 if(sparms->bar.color)
3500 free_color_pair(&sparms->bar.color);
3502 #ifdef _WINDOWS
3503 scroll_setrange(0L, 0L);
3504 #endif
3505 return(cmd);
3509 /*----------------------------------------------------------------------
3510 Print text on paper
3512 Args: text -- The text to print out
3513 source -- What type of source text is
3514 message -- Message for open_printer()
3515 Handling of error conditions is very poor.
3517 ----*/
3518 static int
3519 print_to_printer(SCROLL_S *sparms)
3521 char message[64];
3523 snprintf(message, sizeof(message), "%s", STYLE_NAME(sparms));
3524 message[sizeof(message)-1] = '\0';
3526 if(open_printer(message) != 0)
3527 return(-1);
3529 switch(sparms->text.src){
3530 case CharStar :
3531 if(sparms->text.text != (char *)NULL)
3532 print_text((char *)sparms->text.text);
3534 break;
3536 case CharStarStar :
3537 if(sparms->text.text != (char **)NULL){
3538 register char **t;
3540 for(t = sparms->text.text; *t != NULL; t++){
3541 print_text(*t);
3542 print_text(NEWLINE);
3546 break;
3548 case FileStar :
3549 if(sparms->text.text != (FILE *)NULL) {
3550 size_t n;
3551 int i;
3553 fseek((FILE *)sparms->text.text, 0L, 0);
3554 n = SIZEOF_20KBUF - 1;
3555 while((i = fread((void *)tmp_20k_buf, sizeof(char),
3556 n, (FILE *)sparms->text.text)) != 0) {
3557 tmp_20k_buf[i] = '\0';
3558 print_text(tmp_20k_buf);
3562 default :
3563 break;
3566 close_printer();
3567 return(0);
3571 /*----------------------------------------------------------------------
3572 Search text being viewed (help or message)
3574 Args: q_line -- The screen line to prompt for search string on
3575 start_line -- Line number in text to begin search on
3576 start_index -- Where to begin search at in first line of text
3577 cursor_pos -- position of cursor is returned to caller here
3578 (Actually, this isn't really the position of the
3579 cursor because we don't know where we are on the
3580 screen. So row is set to the line number and col
3581 is set to the right column.)
3582 offset_in_line -- Offset where match was found.
3584 Result: returns line number string was found on
3585 -1 for cancel
3586 -2 if not found
3587 -3 if only match is at start_index - 1
3588 -4 if search to first line
3589 -5 if search to last line
3590 ---*/
3592 search_text(int q_line, long int start_line, int start_index, char **report,
3593 Pos *cursor_pos, int *offset_in_line)
3595 char prompt[MAX_SEARCH+50], nsearch_string[MAX_SEARCH+1], *p;
3596 HelpType help;
3597 int rc, flags;
3598 static HISTORY_S *history = NULL;
3599 char search_string[MAX_SEARCH+1];
3600 static ESCKEY_S word_search_key[] = { { 0, 0, "", "" },
3601 {ctrl('Y'), 10, "^Y", N_("First Line")},
3602 {ctrl('V'), 11, "^V", N_("Last Line")},
3603 {KEY_UP, 30, "", ""},
3604 {KEY_DOWN, 31, "", ""},
3605 {-1, 0, NULL, NULL}
3607 #define KU_ST (3) /* index of KEY_UP */
3609 init_hist(&history, HISTSIZE);
3612 * Put the last one used in the default search_string,
3613 * not in nsearch_string.
3615 search_string[0] = '\0';
3616 if((p = get_prev_hist(history, "", 0, NULL)) != NULL){
3617 strncpy(search_string, p, sizeof(search_string));
3618 search_string[sizeof(search_string)-1] = '\0';
3621 snprintf(prompt, sizeof(prompt), _("Word to search for [%s] : "), search_string);
3622 help = NO_HELP;
3623 nsearch_string[0] = '\0';
3625 while(1) {
3626 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE | OE_KEEP_TRAILING_SPACE;
3629 * 2 is really 1 because there will be one real entry and
3630 * one entry of "" because of the get_prev_hist above.
3632 if(items_in_hist(history) > 2){
3633 word_search_key[KU_ST].name = HISTORY_UP_KEYNAME;
3634 word_search_key[KU_ST].label = HISTORY_KEYLABEL;
3635 word_search_key[KU_ST+1].name = HISTORY_DOWN_KEYNAME;
3636 word_search_key[KU_ST+1].label = HISTORY_KEYLABEL;
3638 else{
3639 word_search_key[KU_ST].name = "";
3640 word_search_key[KU_ST].label = "";
3641 word_search_key[KU_ST+1].name = "";
3642 word_search_key[KU_ST+1].label = "";
3645 rc = optionally_enter(nsearch_string, q_line, 0, sizeof(nsearch_string),
3646 prompt, word_search_key, help, &flags);
3648 if(rc == 3) {
3649 help = help == NO_HELP ? h_oe_searchview : NO_HELP;
3650 continue;
3652 else if(rc == 10){
3653 if(report)
3654 *report = _("Searched to First Line.");
3656 return(-4);
3658 else if(rc == 11){
3659 if(report)
3660 *report = _("Searched to Last Line.");
3662 return(-5);
3664 else if(rc == 30){
3665 if((p = get_prev_hist(history, nsearch_string, 0, NULL)) != NULL){
3666 strncpy(nsearch_string, p, sizeof(nsearch_string));
3667 nsearch_string[sizeof(nsearch_string)-1] = '\0';
3669 else
3670 Writechar(BELL, 0);
3672 continue;
3674 else if(rc == 31){
3675 if((p = get_next_hist(history, nsearch_string, 0, NULL)) != NULL){
3676 strncpy(nsearch_string, p, sizeof(nsearch_string));
3677 nsearch_string[sizeof(nsearch_string)-1] = '\0';
3679 else
3680 Writechar(BELL, 0);
3682 continue;
3685 if(rc != 4){ /* 4 is redraw */
3686 save_hist(history, nsearch_string, 0, NULL);
3687 break;
3691 if(rc == 1 || (search_string[0] == '\0' && nsearch_string[0] == '\0'))
3692 return(-1);
3694 if(nsearch_string[0] != '\0'){
3695 strncpy(search_string, nsearch_string, sizeof(search_string)-1);
3696 search_string[sizeof(search_string)-1] = '\0';
3699 rc = search_scroll_text(start_line, start_index, search_string, cursor_pos,
3700 offset_in_line);
3701 return(rc);
3705 /*----------------------------------------------------------------------
3706 Update the scroll tool's titlebar
3708 Args: cur_top_line --
3709 redraw -- flag to force updating
3711 ----*/
3712 void
3713 update_scroll_titlebar(long int cur_top_line, int redraw)
3715 SCRLCTRL_S *st = scroll_state(SS_CUR);
3716 int num_display_lines = SCROLL_LINES(ps_global);
3717 long new_line = (cur_top_line + num_display_lines > st->num_lines)
3718 ? st->num_lines
3719 : cur_top_line + num_display_lines;
3720 long raw_msgno;
3721 COLOR_PAIR *returned_color = NULL;
3722 COLOR_PAIR *titlecolor = NULL;
3723 int colormatch;
3724 SEARCHSET *ss = NULL;
3726 if(st->parms->use_indexline_color
3727 && ps_global->titlebar_color_style != TBAR_COLOR_DEFAULT){
3728 raw_msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
3729 if(raw_msgno > 0L && ps_global->mail_stream
3730 && raw_msgno <= ps_global->mail_stream->nmsgs){
3731 ss = mail_newsearchset();
3732 ss->first = ss->last = (unsigned long) raw_msgno;
3735 if(ss){
3736 PAT_STATE *pstate = NULL;
3738 colormatch = get_index_line_color(ps_global->mail_stream,
3739 ss, &pstate, &returned_color);
3740 mail_free_searchset(&ss);
3743 * This is a bit tricky. If there is a colormatch but returned_color
3744 * is NULL, that means that the user explicitly wanted the
3745 * Normal color used in this index line, so that is what we
3746 * use. If no colormatch then we will use the TITLE color
3747 * instead of Normal.
3749 if(colormatch){
3750 if(returned_color)
3751 titlecolor = returned_color;
3752 else
3753 titlecolor = new_color_pair(ps_global->VAR_NORM_FORE_COLOR,
3754 ps_global->VAR_NORM_BACK_COLOR);
3757 if(titlecolor
3758 && ps_global->titlebar_color_style == TBAR_COLOR_REV_INDEXLINE){
3759 char cbuf[MAXCOLORLEN+1];
3761 strncpy(cbuf, titlecolor->fg, MAXCOLORLEN);
3762 strncpy(titlecolor->fg, titlecolor->bg, MAXCOLORLEN);
3763 strncpy(titlecolor->bg, cbuf, MAXCOLORLEN);
3767 /* Did the color change? */
3768 if((!titlecolor && st->parms->bar.color)
3770 (titlecolor && !st->parms->bar.color)
3772 (titlecolor && st->parms->bar.color
3773 && (strcmp(titlecolor->fg, st->parms->bar.color->fg)
3774 || strcmp(titlecolor->bg, st->parms->bar.color->bg)))){
3776 redraw++;
3777 if(st->parms->bar.color)
3778 free_color_pair(&st->parms->bar.color);
3780 st->parms->bar.color = titlecolor;
3781 titlecolor = NULL;
3784 if(titlecolor)
3785 free_color_pair(&titlecolor);
3789 if(redraw){
3790 set_titlebar(st->parms->bar.title, ps_global->mail_stream,
3791 ps_global->context_current, ps_global->cur_folder,
3792 ps_global->msgmap, 1, st->parms->bar.style,
3793 new_line, st->num_lines, st->parms->bar.color);
3794 ps_global->mangled_header = 0;
3796 else if(st->parms->bar.style == TextPercent)
3797 update_titlebar_lpercent(new_line);
3798 else
3799 update_titlebar_percent(new_line);
3803 /*----------------------------------------------------------------------
3804 manager of global (to this module, anyway) scroll state structures
3807 ----*/
3808 SCRLCTRL_S *
3809 scroll_state(int func)
3811 struct scrollstack {
3812 SCRLCTRL_S s;
3813 struct scrollstack *prev;
3814 } *s;
3815 static struct scrollstack *stack = NULL;
3817 switch(func){
3818 case SS_CUR: /* no op */
3819 break;
3820 case SS_NEW:
3821 s = (struct scrollstack *)fs_get(sizeof(struct scrollstack));
3822 memset((void *)s, 0, sizeof(struct scrollstack));
3823 s->prev = stack;
3824 stack = s;
3825 break;
3826 case SS_FREE:
3827 if(stack){
3828 s = stack->prev;
3829 fs_give((void **)&stack);
3830 stack = s;
3832 break;
3833 default: /* BUG: should complain */
3834 break;
3837 return(stack ? &stack->s : NULL);
3841 /*----------------------------------------------------------------------
3842 Save all the data for scrolling text and paint the screen
3845 ----*/
3846 void
3847 set_scroll_text(SCROLL_S *sparms, long int current_line, SCRLCTRL_S *st)
3849 /* save all the stuff for possible asynchronous redraws */
3850 st->parms = sparms;
3851 st->top_text_line = current_line;
3852 st->screen.start_line = SCROLL_LINES_ABOVE(ps_global);
3853 st->screen.other_lines = SCROLL_LINES_ABOVE(ps_global)
3854 + SCROLL_LINES_BELOW(ps_global);
3855 st->screen.width = -1; /* Force text formatting calculation */
3859 /*----------------------------------------------------------------------
3860 Redraw the text on the screen, possibly reformatting if necessary
3862 Args None
3864 ----*/
3865 void
3866 redraw_scroll_text(void)
3868 int i, offset;
3869 SCRLCTRL_S *st = scroll_state(SS_CUR);
3871 format_scroll_text();
3873 offset = (st->parms->text.src == FileStar) ? 0 : st->top_text_line;
3875 #ifdef _WINDOWS
3876 mswin_beginupdate();
3877 #endif
3878 /*---- Actually display the text on the screen ------*/
3879 for(i = 0; i < st->screen.length; i++){
3880 ClearLine(i + st->screen.start_line);
3881 if((offset + i) < st->num_lines)
3882 PutLine0n8b(i + st->screen.start_line, 0, st->text_lines[offset + i],
3883 st->line_lengths[offset + i], st->parms->text.handles);
3887 fflush(stdout);
3888 #ifdef _WINDOWS
3889 mswin_endupdate();
3890 #endif
3894 /*----------------------------------------------------------------------
3895 Free memory used as scrolling buffers for text on disk. Also mark
3896 text_lines as available
3897 ----*/
3898 void
3899 zero_scroll_text(void)
3901 SCRLCTRL_S *st = scroll_state(SS_CUR);
3902 register int i;
3904 for(i = 0; i < st->lines_allocated; i++)
3905 if(st->parms->text.src == FileStar && st->text_lines[i])
3906 fs_give((void **)&st->text_lines[i]);
3907 else
3908 st->text_lines[i] = NULL;
3910 if(st->parms->text.src == FileStar && st->findex != NULL){
3911 fclose(st->findex);
3912 st->findex = NULL;
3913 if(st->fname){
3914 our_unlink(st->fname);
3915 fs_give((void **)&st->fname);
3919 if(st->text_lines)
3920 fs_give((void **)&st->text_lines);
3922 if(st->line_lengths)
3923 fs_give((void **) &st->line_lengths);
3927 /*----------------------------------------------------------------------
3929 Always format at least 20 chars wide. Wrapping lines would be crazy for
3930 screen widths of 1-20 characters
3931 ----*/
3932 void
3933 format_scroll_text(void)
3935 int i;
3936 char *p, **pp;
3937 SCRLCTRL_S *st = scroll_state(SS_CUR);
3938 register short *ll;
3939 register char **tl, **tl_end;
3941 if(!st || (st->screen.width == (i = ps_global->ttyo->screen_cols)
3942 && st->screen.length == PGSIZE(st)))
3943 return;
3945 st->screen.width = MAX(20, i);
3946 st->screen.length = PGSIZE(st);
3948 if(st->lines_allocated == 0) {
3949 st->lines_allocated = TYPICAL_BIG_MESSAGE_LINES;
3950 st->text_lines = (char **)fs_get(st->lines_allocated *sizeof(char *));
3951 memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
3952 st->line_lengths = (short *)fs_get(st->lines_allocated *sizeof(short));
3955 tl = st->text_lines;
3956 ll = st->line_lengths;
3957 tl_end = &st->text_lines[st->lines_allocated];
3959 if(st->parms->text.src == CharStarStar) {
3960 /*---- original text is already list of lines -----*/
3961 /* The text could be wrapped nicely for narrow screens; for now
3962 it will get truncated as it is displayed */
3963 for(pp = (char **)st->parms->text.text; *pp != NULL;) {
3964 *tl++ = *pp++;
3965 *ll++ = st->screen.width;
3966 if(tl >= tl_end) {
3967 i = tl - st->text_lines;
3968 st->lines_allocated *= 2;
3969 fs_resize((void **)&st->text_lines,
3970 st->lines_allocated * sizeof(char *));
3971 fs_resize((void **)&st->line_lengths,
3972 st->lines_allocated*sizeof(short));
3973 tl = &st->text_lines[i];
3974 ll = &st->line_lengths[i];
3975 tl_end = &st->text_lines[st->lines_allocated];
3979 st->num_lines = tl - st->text_lines;
3981 else if (st->parms->text.src == CharStar) {
3982 /*------ Format the plain text ------*/
3983 for(p = (char *)st->parms->text.text; *p; ) {
3984 *tl = p;
3986 for(; *p && !(*p == RETURN || *p == LINE_FEED); p++)
3989 *ll = p - *tl;
3990 ll++; tl++;
3991 if(tl >= tl_end) {
3992 i = tl - st->text_lines;
3993 st->lines_allocated *= 2;
3994 fs_resize((void **)&st->text_lines,
3995 st->lines_allocated * sizeof(char *));
3996 fs_resize((void **)&st->line_lengths,
3997 st->lines_allocated*sizeof(short));
3998 tl = &st->text_lines[i];
3999 ll = &st->line_lengths[i];
4000 tl_end = &st->text_lines[st->lines_allocated];
4003 if(*p == '\r' && *(p+1) == '\n')
4004 p += 2;
4005 else if(*p == '\n' || *p == '\r')
4006 p++;
4009 st->num_lines = tl - st->text_lines;
4011 else {
4012 /*------ Display text is in a file --------*/
4015 * This is pretty much only useful under DOS where we can't fit
4016 * all of big messages in core at once. This scheme makes
4017 * some simplifying assumptions:
4018 * 1. Lines are on disk just the way we'll display them. That
4019 * is, line breaks and such are left to the function that
4020 * writes the disk file to catch and fix.
4021 * 2. We get away with this mainly because the DOS display isn't
4022 * going to be resized out from under us.
4024 * The idea is to use the already alloc'd array of char * as a
4025 * buffer for sections of what's on disk. We'll set up the first
4026 * few lines here, and read new ones in as needed in
4027 * scroll_scroll_text().
4029 * but first, make sure there are enough buffer lines allocated
4030 * to serve as a place to hold lines from the file.
4032 * Actually, this is also used under windows so the display will
4033 * be resized out from under us. So I changed the following
4034 * to always
4035 * 1. free old text_lines, which may have been allocated
4036 * for a narrow screen.
4037 * 2. insure we have enough text_lines
4038 * 3. reallocate all text_lines that are needed.
4039 * (tom unger 10/26/94)
4042 /* free old text lines, which may be too short. */
4043 for(i = 0; i < st->lines_allocated; i++)
4044 if(st->text_lines[i]) /* clear alloc'd lines */
4045 fs_give((void **)&st->text_lines[i]);
4047 /* Insure we have enough text lines. */
4048 if(st->lines_allocated < (2 * PGSIZE(st)) + 1){
4049 st->lines_allocated = (2 * PGSIZE(st)) + 1; /* resize */
4051 fs_resize((void **)&st->text_lines,
4052 st->lines_allocated * sizeof(char *));
4053 memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
4054 fs_resize((void **)&st->line_lengths,
4055 st->lines_allocated*sizeof(short));
4058 /* reallocate all text lines that are needed. */
4059 for(i = 0; i <= PGSIZE(st); i++)
4060 if(st->text_lines[i] == NULL)
4061 st->text_lines[i] = (char *)fs_get((st->screen.width + 1)
4062 * sizeof(char));
4064 tl = &st->text_lines[i];
4066 st->num_lines = make_file_index();
4068 ScrollFile(st->top_text_line); /* then load them up */
4072 * Efficiency hack. If there are handles, fill in their
4073 * line number field for later...
4075 if(st->parms->text.handles){
4076 long line;
4077 int i, col, n, key;
4078 HANDLE_S *h;
4080 for(line = 0; line < st->num_lines; line++)
4081 for(i = 0, col = 0; i < st->line_lengths[line];)
4082 switch(st->text_lines[line][i]){
4083 case TAG_EMBED:
4084 i++;
4085 switch((i < st->line_lengths[line]) ? st->text_lines[line][i]
4086 : 0){
4087 case TAG_HANDLE:
4088 for(key = 0, n = st->text_lines[line][++i]; n > 0; n--)
4089 key = (key * 10) + (st->text_lines[line][++i] - '0');
4091 i++;
4092 for(h = st->parms->text.handles; h; h = h->next)
4093 if(h->key == key){
4094 scroll_handle_set_loc(&h->loc, line, col);
4095 break;
4098 if(!h) /* anything behind us? */
4099 for(h = st->parms->text.handles->prev; h; h = h->prev)
4100 if(h->key == key){
4101 scroll_handle_set_loc(&h->loc, line, col);
4102 break;
4105 break;
4107 case TAG_FGCOLOR :
4108 case TAG_BGCOLOR :
4109 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
4110 break;
4112 case TAG_INVON:
4113 case TAG_INVOFF:
4114 case TAG_BOLDON:
4115 case TAG_BOLDOFF:
4116 case TAG_ULINEON:
4117 case TAG_ULINEOFF:
4118 i++;
4119 break;
4121 default: /* literal embed char */
4122 break;
4125 break;
4127 case TAB:
4128 i++;
4129 while(((++col) & 0x07) != 0) /* add tab's spaces */
4132 break;
4134 default:
4135 col += width_at_this_position((unsigned char*) &st->text_lines[line][i],
4136 st->line_lengths[line] - i);
4137 i++; /* character count */
4138 break;
4142 #ifdef _WINDOWS
4143 scroll_setrange (st->screen.length, st->num_lines);
4144 #endif
4146 *tl = NULL;
4151 * ScrollFile - scroll text into the st struct file making sure 'line'
4152 * of the file is the one first in the text_lines buffer.
4154 * NOTE: talk about massive potential for tuning...
4155 * Goes without saying this is still under constuction
4157 void
4158 ScrollFile(long int line)
4160 SCRLCTRL_S *st = scroll_state(SS_CUR);
4161 SCRLFILE_S sf;
4162 register int i;
4164 if(line <= 0){ /* reset and load first couple of pages */
4165 fseek((FILE *) st->parms->text.text, 0L, 0);
4166 line = 0L;
4169 if(!st->text_lines)
4170 return;
4172 for(i = 0; i < PGSIZE(st); i++){
4173 /*** do stuff to get the file pointer into the right place ***/
4175 * BOGUS: this is painfully crude right now, but I just want to get
4176 * it going.
4178 * possibly in the near furture, an array of indexes into the
4179 * file that are the offset for the beginning of each line will
4180 * speed things up. Of course, this
4181 * will have limits, so maybe a disk file that is an array
4182 * of indexes is the answer.
4184 if(fseek(st->findex, (size_t)(line++) * sizeof(SCRLFILE_S), 0) < 0
4185 || fread(&sf, sizeof(SCRLFILE_S), (size_t)1, st->findex) != 1
4186 || fseek((FILE *) st->parms->text.text, sf.offset, 0) < 0
4187 || !st->text_lines[i]
4188 || (sf.len && !fgets(st->text_lines[i], sf.len + 1,
4189 (FILE *) st->parms->text.text)))
4190 break;
4192 st->line_lengths[i] = sf.len;
4195 for(; i < PGSIZE(st); i++)
4196 if(st->text_lines[i]){ /* blank out any unused lines */
4197 *st->text_lines[i] = '\0';
4198 st->line_lengths[i] = 0;
4204 * make_file_index - do a single pass over the file containing the text
4205 * to display, recording line lengths and offsets.
4206 * NOTE: This is never really to be used on a real OS with virtual
4207 * memory. This is the whole reason st->findex exists. Don't
4208 * want to waste precious memory on a stupid array that could
4209 * be very large.
4211 long
4212 make_file_index(void)
4214 SCRLCTRL_S *st = scroll_state(SS_CUR);
4215 SCRLFILE_S sf;
4216 long l = 0L;
4217 int state = 0;
4219 if(!st->findex){
4220 if(!st->fname)
4221 st->fname = temp_nam(NULL, "pi");
4223 if(!st->fname || (st->findex = our_fopen(st->fname,"w+b")) == NULL){
4224 if(st->fname){
4225 our_unlink(st->fname);
4226 fs_give((void **)&st->fname);
4229 return(0);
4232 else
4233 fseek(st->findex, 0L, 0);
4235 fseek((FILE *)st->parms->text.text, 0L, 0);
4237 while(1){
4238 sf.len = st->screen.width + 1;
4239 if(scroll_file_line((FILE *) st->parms->text.text,
4240 tmp_20k_buf, &sf, &state)){
4241 fwrite((void *) &sf, sizeof(SCRLFILE_S), (size_t)1, st->findex);
4242 l++;
4244 else
4245 break;
4248 fseek((FILE *)st->parms->text.text, 0L, 0);
4250 return(l);
4254 /*----------------------------------------------------------------------
4255 Get the next line to scroll from the given file
4257 ----*/
4258 char *
4259 scroll_file_line(FILE *fp, char *buf, SCRLFILE_S *sfp, int *wrapt)
4261 register char *s = NULL;
4263 while(1){
4264 if(!s){
4265 sfp->offset = ftell(fp);
4266 if(!(s = fgets(buf, sfp->len, fp)))
4267 return(NULL); /* can't grab a line? */
4270 if(!*s){
4271 *wrapt = 1; /* remember; that we wrapped */
4272 break;
4274 else if(*s == NEWLINE[0] && (!NEWLINE[1] || *(s+1) == NEWLINE[1])){
4275 int empty = (*wrapt && s == buf);
4277 *wrapt = 0; /* turn off wrapped state */
4278 if(empty)
4279 s = NULL; /* get a new line */
4280 else
4281 break; /* done! */
4283 else
4284 s++;
4287 sfp->len = s - buf;
4288 return(buf);
4292 /*----------------------------------------------------------------------
4293 Scroll the text on the screen
4295 Args: new_top_line -- The line to be displayed on top of the screen
4296 redraw -- Flag to force a redraw even if nothing changed
4298 Returns: resulting top line
4299 Note: the returned line number may be less than new_top_line if
4300 reformatting caused the total line count to change.
4302 ----*/
4303 long
4304 scroll_scroll_text(long int new_top_line, HANDLE_S *handle, int redraw)
4306 SCRLCTRL_S *st = scroll_state(SS_CUR);
4307 int num_display_lines, l, top;
4308 POSLIST_S *lp, *lp2;
4310 /* When this is true, we're still on the same page of the display. */
4311 if(st->top_text_line == new_top_line && !redraw){
4312 /* handle changed, so hilite the new handle and unhilite the old */
4313 if(handle && handle != st->parms->text.handles){
4314 top = st->screen.start_line - new_top_line;
4315 /* hilite the new one */
4316 if(!scroll_handle_obscured(handle))
4317 for(lp = handle->loc; lp; lp = lp->next)
4318 if((l = lp->where.row) >= st->top_text_line
4319 && l < st->top_text_line + st->screen.length){
4320 if(st->parms->text.src == FileStar)
4321 l -= new_top_line;
4323 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4324 st->line_lengths[l], handle);
4327 /* unhilite the old one */
4328 if(!scroll_handle_obscured(st->parms->text.handles))
4329 for(lp = st->parms->text.handles->loc; lp; lp = lp->next)
4330 if((l = lp->where.row) >= st->top_text_line
4331 && l < st->top_text_line + st->screen.length){
4332 for(lp2 = handle->loc; lp2; lp2 = lp2->next)
4333 if(l == lp2->where.row)
4334 break;
4336 if(!lp2){
4337 if(st->parms->text.src == FileStar)
4338 l -= new_top_line;
4340 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4341 st->line_lengths[l], handle);
4345 st->parms->text.handles = handle; /* update current */
4348 return(new_top_line);
4351 num_display_lines = PGSIZE(st);
4353 format_scroll_text();
4355 if(st->top_text_line >= st->num_lines) /* don't pop line count */
4356 new_top_line = st->top_text_line = MAX(st->num_lines - 1, 0);
4358 if(st->parms->text.src == FileStar)
4359 ScrollFile(new_top_line); /* set up new st->text_lines */
4361 #ifdef _WINDOWS
4362 scroll_setrange (st->screen.length, st->num_lines);
4363 scroll_setpos (new_top_line);
4364 #endif
4366 /* ---
4367 Check out the scrolling situation. If we want to scroll, but BeginScroll
4368 says we can't then repaint, + 10 is so we repaint most of the time.
4369 ----*/
4370 if(redraw ||
4371 (st->top_text_line - new_top_line + 10 >= num_display_lines ||
4372 new_top_line - st->top_text_line + 10 >= num_display_lines) ||
4373 BeginScroll(st->screen.start_line,
4374 st->screen.start_line + num_display_lines - 1) != 0) {
4375 /* Too much text to scroll, or can't scroll -- just repaint */
4377 if(handle)
4378 st->parms->text.handles = handle;
4380 st->top_text_line = new_top_line;
4381 redraw_scroll_text();
4383 else{
4385 * We're going to scroll the screen, but first we have to make sure
4386 * the old hilited handles are unhilited if they are going to remain
4387 * on the screen.
4389 top = st->screen.start_line - st->top_text_line;
4390 if(handle && handle != st->parms->text.handles
4391 && st->parms->text.handles
4392 && !scroll_handle_obscured(st->parms->text.handles))
4393 for(lp = st->parms->text.handles->loc; lp; lp = lp->next)
4394 if((l = lp->where.row) >= MAX(st->top_text_line,new_top_line)
4395 && l < MIN(st->top_text_line,new_top_line) + st->screen.length){
4396 if(st->parms->text.src == FileStar)
4397 l -= new_top_line;
4399 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4400 st->line_lengths[l], handle);
4403 if(new_top_line > st->top_text_line){
4404 /*------ scroll down ------*/
4405 while(new_top_line > st->top_text_line) {
4406 ScrollRegion(1);
4408 l = (st->parms->text.src == FileStar)
4409 ? num_display_lines - (new_top_line - st->top_text_line)
4410 : st->top_text_line + num_display_lines;
4412 if(l < st->num_lines){
4413 PutLine0n8b(st->screen.start_line + num_display_lines - 1,
4414 0, st->text_lines[l], st->line_lengths[l],
4415 handle ? handle : st->parms->text.handles);
4417 * We clear to the end of line in the right background
4418 * color. If the line was exactly the width of the screen
4419 * then PutLine0n8b will have left _col and _row moved to
4420 * the start of the next row. We don't need or want to clear
4421 * that next row.
4423 if(pico_usingcolor()
4424 && (st->line_lengths[l] < ps_global->ttyo->screen_cols
4425 || visible_linelen(l) < ps_global->ttyo->screen_cols))
4426 CleartoEOLN();
4429 st->top_text_line++;
4432 else{
4433 /*------ scroll up -----*/
4434 while(new_top_line < st->top_text_line) {
4435 ScrollRegion(-1);
4437 st->top_text_line--;
4438 l = (st->parms->text.src == FileStar)
4439 ? st->top_text_line - new_top_line
4440 : st->top_text_line;
4441 PutLine0n8b(st->screen.start_line, 0, st->text_lines[l],
4442 st->line_lengths[l],
4443 handle ? handle : st->parms->text.handles);
4445 * We clear to the end of line in the right background
4446 * color. If the line was exactly the width of the screen
4447 * then PutLine0n8b will have left _col and _row moved to
4448 * the start of the next row. We don't need or want to clear
4449 * that next row.
4451 if(pico_usingcolor()
4452 && (st->line_lengths[l] < ps_global->ttyo->screen_cols
4453 || visible_linelen(l) < ps_global->ttyo->screen_cols))
4454 CleartoEOLN();
4458 EndScroll();
4460 if(handle && handle != st->parms->text.handles){
4461 POSLIST_S *lp;
4463 for(lp = handle->loc; lp; lp = lp->next)
4464 if(lp->where.row >= st->top_text_line
4465 && lp->where.row < st->top_text_line + st->screen.length){
4466 PutLine0n8b(st->screen.start_line
4467 + (lp->where.row - st->top_text_line),
4468 0, st->text_lines[lp->where.row],
4469 st->line_lengths[lp->where.row],
4470 handle);
4474 st->parms->text.handles = handle;
4477 fflush(stdout);
4480 return(new_top_line);
4484 /*---------------------------------------------------------------------
4485 Edit individual char in text so that the entire text doesn't need
4486 to be completely reformatted.
4488 Returns 0 if there were no errors, 1 if we would like the entire
4489 text to be reformatted.
4490 ----*/
4492 ng_scroll_edit(CONTEXT_S *context, int index)
4494 SCRLCTRL_S *st = scroll_state(SS_CUR);
4495 char *ngp, tmp[MAILTMPLEN+10];
4496 int len;
4497 FOLDER_S *f;
4499 if (!(f = folder_entry(index, FOLDERS(context))))
4500 return 1;
4501 if(f->subscribed)
4502 return 0; /* nothing in scroll needs to be changed */
4503 tmp[0] = TAG_HANDLE;
4504 snprintf(tmp+2, sizeof(tmp)-2, "%d", st->parms->text.handles->key);
4505 tmp[sizeof(tmp)-1] = '\0';
4506 tmp[1] = len = strlen(tmp+2);
4507 snprintf(tmp+len+2, sizeof(tmp)-(len+2), "%s ", f->selected ? "[ ]" : "[X]");
4508 tmp[sizeof(tmp)-1] = '\0';
4509 snprintf(tmp+len+6, sizeof(tmp)-(len+6), "%.*s", MAILTMPLEN, f->name);
4510 tmp[sizeof(tmp)-1] = '\0';
4512 ngp = *(st->text_lines);
4514 ngp = strstr(ngp, tmp);
4516 if(!ngp) return 1;
4517 ngp += 3+len;
4519 /* assumption that text is of form "[ ] xxx.xxx" */
4521 if(ngp){
4522 if(*ngp == 'X'){
4523 *ngp = ' ';
4524 return 0;
4526 else if (*ngp == ' '){
4527 *ngp = 'X';
4528 return 0;
4531 return 1;
4535 /*---------------------------------------------------------------------
4536 Similar to ng_scroll_edit, but this is the more general case of
4537 selecting a folder, as opposed to selecting a newsgroup for
4538 subscription while in listmode.
4540 Returns 0 if there were no errors, 1 if we would like the entire
4541 text to be reformatted.
4542 ----*/
4544 folder_select_update(CONTEXT_S *context, int index)
4546 SCRLCTRL_S *st = scroll_state(SS_CUR);
4547 FOLDER_S *f;
4548 char *ngp, tmp[MAILTMPLEN+10];
4549 int len, total, fnum, num_sel = 0;
4551 if (!(f = folder_entry(index, FOLDERS(context))))
4552 return 1;
4553 ngp = *(st->text_lines);
4555 total = folder_total(FOLDERS(context));
4557 for (fnum = 0; num_sel < 2 && fnum < total; fnum++)
4558 if(folder_entry(fnum, FOLDERS(context))->selected)
4559 num_sel++;
4560 if(!num_sel || (f->selected && num_sel == 1))
4561 return 1; /* need to reformat the whole thing */
4563 tmp[0] = TAG_HANDLE;
4564 snprintf(tmp+2, sizeof(tmp)-2, "%d", st->parms->text.handles->key);
4565 tmp[sizeof(tmp)-1] = '\0';
4566 tmp[1] = len = strlen(tmp+2);
4568 ngp = strstr(ngp, tmp);
4569 if(!ngp) return 1;
4571 if(F_ON(F_SELECTED_SHOWN_BOLD, ps_global)){
4572 ngp += 2 + len;
4573 while(*ngp && ngp[0] != TAG_EMBED
4574 && ngp[1] != (f->selected ? TAG_BOLDOFF : TAG_BOLDON)
4575 && *ngp != *(f->name))
4576 ngp++;
4578 if (!(*ngp) || (*ngp == *(f->name)))
4579 return 1;
4580 else {
4581 ngp++;
4582 *ngp = (f->selected ? TAG_BOLDON : TAG_BOLDOFF);
4583 return 0;
4586 else{
4587 while(*ngp != ' ' && *ngp != *(f->name) && *ngp)
4588 ngp++;
4589 if(!(*ngp) || (*ngp == *(f->name)))
4590 return 1;
4591 else {
4592 ngp++;
4593 *ngp = f->selected ? 'X' : ' ';
4594 return 0;
4600 /*---------------------------------------------------------------------
4601 We gotta go through all of the formatted text and add "[ ] " in the right
4602 place. If we don't do this, we must completely reformat the whole text,
4603 which could take a very long time.
4605 Return 1 if we encountered some sort of error and we want to reformat the
4606 whole text, return 0 if everything went as planned.
4608 ASSUMPTION: for this to work, we assume that there are only total
4609 number of handles, numbered 1 through total.
4610 ----*/
4612 scroll_add_listmode(CONTEXT_S *context, int total)
4614 SCRLCTRL_S *st = scroll_state(SS_CUR);
4615 long i;
4616 char *ngp, *ngname, handle_str[MAILTMPLEN];
4617 HANDLE_S *h;
4620 ngp = *(st->text_lines);
4621 h = st->parms->text.handles;
4623 while(h && h->key != 1 && h->prev)
4624 h = h->prev;
4625 if (!h) return 1;
4626 handle_str[0] = TAG_EMBED;
4627 handle_str[1] = TAG_HANDLE;
4628 for(i = 1; i <= total && h; i++, h = h->next){
4629 snprintf(handle_str+3, sizeof(handle_str)-3, "%d", h->key);
4630 handle_str[sizeof(handle_str)-1] = '\0';
4631 handle_str[2] = strlen(handle_str+3);
4632 ngp = strstr(ngp, handle_str);
4633 if(!ngp){
4634 ngp = *(st->text_lines);
4635 if (!ngp)
4636 return 1;
4638 ngname = ngp + strlen(handle_str);
4639 while (strncmp(ngp, " ", 4) && !(*ngp == '\n')
4640 && !(ngp == *(st->text_lines)))
4641 ngp--;
4642 if (strncmp(ngp, " ", 4))
4643 return 1;
4644 while(ngp+4 != ngname && *ngp){
4645 ngp[0] = ngp[4];
4646 ngp++;
4649 if(folder_entry(h->h.f.index, FOLDERS(context))->subscribed){
4650 ngp[0] = 'S';
4651 ngp[1] = 'U';
4652 ngp[2] = 'B';
4654 else{
4655 ngp[0] = '[';
4656 ngp[1] = ' ';
4657 ngp[2] = ']';
4659 ngp[3] = ' ';
4662 return 0;
4667 /*----------------------------------------------------------------------
4668 Search the set scrolling text
4670 Args: start_line -- line to start searching on
4671 start_index -- column to start searching at in first line
4672 word -- string to search for
4673 cursor_pos -- position of cursor is returned to caller here
4674 (Actually, this isn't really the position of the
4675 cursor because we don't know where we are on the
4676 screen. So row is set to the line number and col
4677 is set to the right column.)
4678 offset_in_line -- Offset where match was found.
4680 Returns: the line the word was found on, or -2 if it wasn't found, or
4681 -3 if the only match is at column start_index - 1.
4683 ----*/
4685 search_scroll_text(long int start_line, int start_index, char *word,
4686 Pos *cursor_pos, int *offset_in_line)
4688 SCRLCTRL_S *st = scroll_state(SS_CUR);
4689 char *wh;
4690 long l, offset, dlines;
4691 #define SROW(N) ((N) - offset)
4692 #define SLINE(N) st->text_lines[SROW(N)]
4693 #define SLEN(N) st->line_lengths[SROW(N)]
4695 dlines = PGSIZE(st);
4696 offset = (st->parms->text.src == FileStar) ? st->top_text_line : 0;
4698 if(start_line < st->num_lines){
4699 /* search first line starting at position start_index in */
4700 if((wh = search_scroll_line(SLINE(start_line) + start_index,
4701 word,
4702 SLEN(start_line) - start_index,
4703 st->parms->text.handles != NULL)) != NULL){
4704 cursor_pos->row = start_line;
4705 cursor_pos->col = scroll_handle_column(SROW(start_line),
4706 *offset_in_line = wh - SLINE(start_line));
4707 return(start_line);
4710 if(st->parms->text.src == FileStar)
4711 offset++;
4713 for(l = start_line + 1; l < st->num_lines; l++) {
4714 if(st->parms->text.src == FileStar && l > offset + dlines)
4715 ScrollFile(offset += dlines);
4717 if((wh = search_scroll_line(SLINE(l), word, SLEN(l),
4718 st->parms->text.handles != NULL)) != NULL){
4719 cursor_pos->row = l;
4720 cursor_pos->col = scroll_handle_column(SROW(l),
4721 *offset_in_line = wh - SLINE(l));
4722 return(l);
4726 else
4727 start_line = st->num_lines;
4729 if(st->parms->text.src == FileStar) /* wrap offset */
4730 ScrollFile(offset = 0);
4732 for(l = 0; l < start_line; l++) {
4733 if(st->parms->text.src == FileStar && l > offset + dlines)
4734 ScrollFile(offset += dlines);
4736 if((wh = search_scroll_line(SLINE(l), word, SLEN(l),
4737 st->parms->text.handles != NULL)) != NULL){
4738 cursor_pos->row = l;
4739 cursor_pos->col = scroll_handle_column(SROW(l),
4740 *offset_in_line = wh - SLINE(l));
4741 return(l);
4745 /* search in current line */
4746 if(start_line < st->num_lines
4747 && (wh = search_scroll_line(SLINE(start_line), word,
4748 start_index + strlen(word) - 2,
4749 st->parms->text.handles != NULL)) != NULL){
4750 cursor_pos->row = start_line;
4751 cursor_pos->col = scroll_handle_column(SROW(start_line),
4752 *offset_in_line = wh - SLINE(start_line));
4754 return(start_line);
4757 /* see if the only match is a repeat */
4758 if(start_index > 0 && start_line < st->num_lines
4759 && (wh = search_scroll_line(
4760 SLINE(start_line) + start_index - 1,
4761 word, strlen(word),
4762 st->parms->text.handles != NULL)) != NULL){
4763 cursor_pos->row = start_line;
4764 cursor_pos->col = scroll_handle_column(SROW(start_line),
4765 *offset_in_line = wh - SLINE(start_line));
4766 return(-3);
4769 return(-2);
4773 /*----------------------------------------------------------------------
4774 Search one line of scroll text for given string
4776 Args: haystack -- The string to search in, the larger string
4777 needle -- The string to search for, the smaller string
4778 n -- The max number of chars in haystack to search
4780 Search for first occurrence of needle in the haystack, and return a pointer
4781 into the string haystack when it is found. The search is case independent.
4782 ----*/
4783 char *
4784 search_scroll_line(char *haystack, char *needle, int n, int handles)
4786 char *return_ptr = NULL, *found_it = NULL;
4787 char *haystack_copy, *p, *free_this = NULL, *end;
4788 char buf[1000];
4789 int state = 0, i = 0;
4791 if(n > 0 && haystack){
4792 if(n < sizeof(buf))
4793 haystack_copy = buf;
4794 else
4795 haystack_copy = free_this = (char *) fs_get((n+1) * sizeof(char));
4797 strncpy(haystack_copy, haystack, n);
4798 haystack_copy[n] = '\0';
4801 * We don't want to match text inside embedded tags.
4802 * Replace embedded octets with nulls and convert
4803 * uppercase ascii to lowercase. We should also do
4804 * some sort of canonicalization of UTF-8 but that
4805 * sounds daunting.
4807 for(i = n, p = haystack_copy; i-- > 0 && *p; p++){
4808 if(handles)
4809 switch(state){
4810 case 0 :
4811 if(*p == TAG_EMBED){
4812 *p = '\0';
4813 state = -1;
4814 continue;
4816 else{
4817 /* lower case just ascii chars */
4818 if(!(*p & 0x80) && isupper(*p))
4819 *p = tolower(*p);
4822 break;
4824 case -1 :
4825 state = (*p == TAG_HANDLE)
4826 ? -2
4827 : (*p == TAG_FGCOLOR || *p == TAG_BGCOLOR) ? RGBLEN : 0;
4828 *p = '\0';
4829 continue;
4831 case -2 :
4832 state = *p; /* length of handle's key */
4833 *p = '\0';
4834 continue;
4836 default :
4837 state--;
4838 *p = '\0';
4839 continue;
4844 * The haystack_copy string now looks like
4846 * "chars\0\0\0\0\0\0chars...\0\0\0chars... \0"
4848 * with that final \0 at haystack_copy[n].
4849 * Search each piece one at a time.
4852 end = haystack_copy + n;
4853 p = haystack_copy;
4855 while(p < end && !return_ptr){
4857 /* skip nulls */
4858 while(*p == '\0' && p < end)
4859 p++;
4861 if(*p != '\0')
4862 found_it = srchstr(p, needle);
4864 if(found_it){
4865 /* found it, make result relative to haystack */
4866 return_ptr = haystack + (found_it - haystack_copy);
4869 /* skip to next null */
4870 while(*p != '\0' && p < end)
4871 p++;
4874 if(free_this)
4875 fs_give((void **) &free_this);
4878 return(return_ptr);
4883 * Returns the number of columns taken up by the visible part of the line.
4884 * That is, account for handles and color changes and so forth.
4887 visible_linelen(int line)
4889 SCRLCTRL_S *st = scroll_state(SS_CUR);
4890 int i, n, len = 0;
4892 if(line < 0 || line >= st->num_lines)
4893 return(len);
4895 for(i = 0, len = 0; i < st->line_lengths[line];)
4896 switch(st->text_lines[line][i]){
4898 case TAG_EMBED:
4899 i++;
4900 switch((i < st->line_lengths[line]) ? st->text_lines[line][i] : 0){
4901 case TAG_HANDLE:
4902 i++;
4903 /* skip the length byte plus <length> more bytes */
4904 if(i < st->line_lengths[line]){
4905 n = st->text_lines[line][i];
4906 i++;
4909 if(i < st->line_lengths[line] && n > 0)
4910 i += n;
4912 break;
4914 case TAG_FGCOLOR :
4915 case TAG_BGCOLOR :
4916 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
4917 break;
4919 case TAG_INVON:
4920 case TAG_INVOFF:
4921 case TAG_BOLDON:
4922 case TAG_BOLDOFF:
4923 case TAG_ULINEON:
4924 case TAG_ULINEOFF:
4925 i++;
4926 break;
4928 case TAG_EMBED: /* escaped embed character */
4929 i++;
4930 len++;
4931 break;
4933 default: /* the embed char was literal */
4934 i++;
4935 len += 2;
4936 break;
4939 break;
4941 case TAB:
4942 i++;
4943 while(((++len) & 0x07) != 0) /* add tab's spaces */
4946 break;
4948 default:
4949 i++;
4950 len++;
4951 break;
4954 return(len);
4958 /*----------------------------------------------------------------------
4959 Display the contents of the given file (likely output from some command)
4961 Args: filename -- name of file containing output
4962 title -- title to be used for screen displaying output
4963 alt_msg -- if no output, Q this message instead of the default
4964 mode -- non-zero to display short files in status line
4965 Returns: none
4966 ----*/
4967 void
4968 display_output_file(char *filename, char *title, char *alt_msg, int mode)
4970 STORE_S *in_file = NULL, *out_store = NULL;
4972 if((in_file = so_get(FileStar, filename, READ_ACCESS|READ_FROM_LOCALE))){
4973 if(mode == DOF_BRIEF){
4974 int msg_q = 0, i = 0;
4975 char buf[512], *msg_p[4];
4976 #define MAX_SINGLE_MSG_LEN 60
4978 buf[0] = '\0';
4979 msg_p[0] = buf;
4982 * Might need to do something about CRLFs for Windows.
4984 while(so_fgets(in_file, msg_p[msg_q], sizeof(buf) - (msg_p[msg_q] - buf))
4985 && msg_q < 3
4986 && (i = strlen(msg_p[msg_q])) < MAX_SINGLE_MSG_LEN){
4987 msg_p[msg_q+1] = msg_p[msg_q]+strlen(msg_p[msg_q]);
4988 if (*(msg_p[++msg_q] - 1) == '\n')
4989 *(msg_p[msg_q] - 1) = '\0';
4992 if(msg_q < 3 && i < MAX_SINGLE_MSG_LEN){
4993 if(*msg_p[0])
4994 for(i = 0; i < msg_q; i++)
4995 q_status_message2(SM_ORDER, 3, 4,
4996 "%s Result: %s", title, msg_p[i]);
4997 else
4998 q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
4999 alt_msg
5000 ? alt_msg
5001 : " command completed with no output");
5003 so_give(&in_file);
5004 in_file = NULL;
5007 else if(mode == DOF_EMPTY){
5008 unsigned char c;
5010 if(so_readc(&c, in_file) < 1){
5011 q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
5012 alt_msg
5013 ? alt_msg
5014 : " command completed with no output");
5015 so_give(&in_file);
5016 in_file = NULL;
5021 * We need to translate the file contents from the user's locale
5022 * charset to UTF-8 for use in scrolltool. We get that translation
5023 * from the READ_FROM_LOCALE in the in_file storage object.
5024 * It would be nice to skip this step but scrolltool doesn't use
5025 * the storage object routines to read from the file, so would
5026 * skip the translation step.
5028 if(in_file){
5029 char *errstr;
5030 gf_io_t gc, pc;
5032 if(!(out_store = so_get(CharStar, NULL, EDIT_ACCESS))){
5033 so_give(&in_file);
5034 our_unlink(filename);
5035 q_status_message(SM_ORDER | SM_DING, 3, 3,
5036 _("Error allocating space."));
5037 return;
5040 so_seek(in_file, 0L, 0);
5042 gf_filter_init();
5044 gf_link_filter(gf_wrap,
5045 gf_wrap_filter_opt(ps_global->ttyo->screen_cols - 4,
5046 ps_global->ttyo->screen_cols,
5047 NULL, 0, GFW_NONE));
5049 gf_set_so_readc(&gc, in_file);
5050 gf_set_so_writec(&pc, out_store);
5052 if((errstr = gf_pipe(gc, pc)) != NULL){
5053 so_give(&in_file);
5054 so_give(&out_store);
5055 our_unlink(filename);
5056 q_status_message(SM_ORDER | SM_DING, 3, 3,
5057 _("Error allocating space."));
5058 return;
5061 gf_clear_so_writec(out_store);
5062 gf_clear_so_readc(in_file);
5063 so_give(&in_file);
5066 if(out_store){
5067 SCROLL_S sargs;
5068 char title_buf[64];
5070 snprintf(title_buf, sizeof(title_buf), "HELP FOR %s VIEW", title);
5071 title_buf[sizeof(title_buf)-1] = '\0';
5073 memset(&sargs, 0, sizeof(SCROLL_S));
5074 sargs.text.text = so_text(out_store);
5075 sargs.text.src = CharStar;
5076 sargs.text.desc = "output";
5077 sargs.bar.title = title;
5078 sargs.bar.style = TextPercent;
5079 sargs.help.text = h_simple_text_view;
5080 sargs.help.title = title_buf;
5081 scrolltool(&sargs);
5082 ps_global->mangled_screen = 1;
5083 so_give(&out_store);
5086 our_unlink(filename);
5088 else
5089 dprint((2, "Error reopening %s to get results: %s\n",
5090 filename ? filename : "?", error_description(errno)));
5094 /*--------------------------------------------------------------------
5095 Call the function that will perform the double click operation
5097 Returns: the current top line
5098 --------*/
5099 long
5100 doubleclick_handle(SCROLL_S *sparms, HANDLE_S *next_handle, int *cmd, int *done)
5102 if(sparms->mouse.clickclick){
5103 if((*sparms->mouse.clickclick)(sparms))
5104 *done = 1;
5106 else
5107 switch(scroll_handle_launch(next_handle, TRUE)){
5108 case 1 :
5109 *cmd = MC_EXIT; /* propagate */
5110 *done = 1;
5111 break;
5113 case -1 :
5114 cmd_cancelled("View");
5115 break;
5117 default :
5118 break;
5121 return(scroll_state(SS_CUR)->top_text_line);
5126 #ifdef _WINDOWS
5128 * Just a little something to simplify assignments
5130 #define VIEWPOPUP(p, c, s) { \
5131 (p)->type = tQueue; \
5132 (p)->data.val = c; \
5133 (p)->label.style = lNormal; \
5134 (p)->label.string = s; \
5142 format_message_popup(sparms, in_handle)
5143 SCROLL_S *sparms;
5144 int in_handle;
5146 MPopup fmp_menu[32];
5147 HANDLE_S *h = NULL;
5148 int i = -1, n;
5149 long rawno;
5150 MESSAGECACHE *mc;
5152 /* Reason to offer per message ops? */
5153 if(mn_get_total(ps_global->msgmap) > 0L){
5154 if(in_handle){
5155 SCRLCTRL_S *st = scroll_state(SS_CUR);
5157 switch((h = get_handle(st->parms->text.handles, in_handle))->type){
5158 case Attach :
5159 fmp_menu[++i].type = tIndex;
5160 fmp_menu[i].label.string = "View Attachment";
5161 fmp_menu[i].label.style = lNormal;
5162 fmp_menu[i].data.val = 'X'; /* for local use */
5164 if(h->h.attach
5165 && dispatch_attachment(h->h.attach) != MCD_NONE
5166 && !(h->h.attach->can_display & MCD_EXTERNAL)
5167 && h->h.attach->body
5168 && (h->h.attach->body->type == TYPETEXT
5169 || (h->h.attach->body->type == TYPEMESSAGE
5170 && h->h.attach->body->subtype
5171 && !strucmp(h->h.attach->body->subtype,"rfc822")))){
5172 fmp_menu[++i].type = tIndex;
5173 fmp_menu[i].label.string = "View Attachment in New Window";
5174 fmp_menu[i].label.style = lNormal;
5175 fmp_menu[i].data.val = 'Y'; /* for local use */
5178 fmp_menu[++i].type = tIndex;
5179 fmp_menu[i].label.style = lNormal;
5180 fmp_menu[i].data.val = 'Z'; /* for local use */
5181 msgno_exceptions(ps_global->mail_stream,
5182 mn_m2raw(ps_global->msgmap,
5183 mn_get_cur(ps_global->msgmap)),
5184 h->h.attach->number, &n, FALSE);
5185 fmp_menu[i].label.string = (n & MSG_EX_DELETE)
5186 ? "Undelete Attachment"
5187 : "Delete Attachment";
5188 break;
5190 case URL :
5191 default :
5192 fmp_menu[++i].type = tIndex;
5193 fmp_menu[i].label.string = "View Link";
5194 fmp_menu[i].label.style = lNormal;
5195 fmp_menu[i].data.val = 'X'; /* for local use */
5197 fmp_menu[++i].type = tIndex;
5198 fmp_menu[i].label.string = "Copy Link";
5199 fmp_menu[i].label.style = lNormal;
5200 fmp_menu[i].data.val = 'W'; /* for local use */
5201 break;
5204 fmp_menu[++i].type = tSeparator;
5207 /* Delete or Undelete? That is the question. */
5208 fmp_menu[++i].type = tQueue;
5209 fmp_menu[i].label.style = lNormal;
5210 mc = ((rawno = mn_m2raw(ps_global->msgmap,
5211 mn_get_cur(ps_global->msgmap))) > 0L
5212 && ps_global->mail_stream
5213 && rawno <= ps_global->mail_stream->nmsgs)
5214 ? mail_elt(ps_global->mail_stream, rawno) : NULL;
5215 if(mc && mc->deleted){
5216 fmp_menu[i].data.val = 'U';
5217 fmp_menu[i].label.string = in_handle
5218 ? "&Undelete Message" : "&Undelete";
5220 else{
5221 fmp_menu[i].data.val = 'D';
5222 fmp_menu[i].label.string = in_handle
5223 ? "&Delete Message" : "&Delete";
5226 if(F_ON(F_ENABLE_FLAG, ps_global)){
5227 fmp_menu[++i].type = tSubMenu;
5228 fmp_menu[i].label.string = "Flag";
5229 fmp_menu[i].data.submenu = flag_submenu(mc);
5232 i++;
5233 VIEWPOPUP(&fmp_menu[i], 'S', in_handle ? "&Save Message" : "&Save");
5235 i++;
5236 VIEWPOPUP(&fmp_menu[i], 'E', in_handle ? "&Export Message" : "&Export");
5238 i++;
5239 VIEWPOPUP(&fmp_menu[i], '%', in_handle ? "Print Message" : "Print");
5241 i++;
5242 VIEWPOPUP(&fmp_menu[i], 'R',
5243 in_handle ? "&Reply to Message" : "&Reply");
5245 i++;
5246 VIEWPOPUP(&fmp_menu[i], 'F',
5247 in_handle ? "&Forward Message" : "&Forward");
5249 i++;
5250 VIEWPOPUP(&fmp_menu[i], 'B',
5251 in_handle ? "&Bounce Message" : "&Bounce");
5253 i++;
5254 VIEWPOPUP(&fmp_menu[i], 'T', "&Take Addresses");
5256 fmp_menu[++i].type = tSeparator;
5258 if(mn_get_cur(ps_global->msgmap) < mn_get_total(ps_global->msgmap)){
5259 i++;
5260 VIEWPOPUP(&fmp_menu[i], 'N', "View &Next Message");
5263 if(mn_get_cur(ps_global->msgmap) > 0){
5264 i++;
5265 VIEWPOPUP(&fmp_menu[i], 'P', "View &Prev Message");
5268 if(mn_get_cur(ps_global->msgmap) < mn_get_total(ps_global->msgmap)
5269 || mn_get_cur(ps_global->msgmap) > 0)
5270 fmp_menu[++i].type = tSeparator;
5272 /* Offer the attachment screen? */
5273 for(n = 0; ps_global->atmts && ps_global->atmts[n].description; n++)
5276 if(n > 1){
5277 i++;
5278 VIEWPOPUP(&fmp_menu[i], 'V', "&View Attachment Index");
5282 i++;
5283 VIEWPOPUP(&fmp_menu[i], 'I', "Message &Index");
5285 i++;
5286 VIEWPOPUP(&fmp_menu[i], 'M', "&Main Menu");
5288 fmp_menu[++i].type = tTail;
5290 if((i = mswin_popup(fmp_menu)) >= 0 && in_handle)
5291 switch(fmp_menu[i].data.val){
5292 case 'W' : /* Copy URL to clipboard */
5293 mswin_addclipboard(h->h.url.path);
5294 break;
5296 case 'X' :
5297 return(1); /* return like the user double-clicked */
5299 break;
5301 case 'Y' : /* popup the thing in another window */
5302 display_att_window(h->h.attach);
5303 break;
5305 case 'Z' :
5306 if(h && h->type == Attach){
5307 msgno_exceptions(ps_global->mail_stream,
5308 mn_m2raw(ps_global->msgmap,
5309 mn_get_cur(ps_global->msgmap)),
5310 h->h.attach->number, &n, FALSE);
5311 n ^= MSG_EX_DELETE;
5312 msgno_exceptions(ps_global->mail_stream,
5313 mn_m2raw(ps_global->msgmap,
5314 mn_get_cur(ps_global->msgmap)),
5315 h->h.attach->number, &n, TRUE);
5316 q_status_message2(SM_ORDER, 0, 3, "Attachment %s %s!",
5317 h->h.attach->number,
5318 (n & MSG_EX_DELETE) ? "deleted" : "undeleted");
5320 return(2);
5323 break;
5325 default :
5326 break;
5329 return(0);
5338 simple_text_popup(sparms, in_handle)
5339 SCROLL_S *sparms;
5340 int in_handle;
5342 MPopup simple_menu[12];
5343 int n = 0;
5345 VIEWPOPUP(&simple_menu[n], '%', "Print");
5346 n++;
5348 VIEWPOPUP(&simple_menu[n], 'S', "Save");
5349 n++;
5351 VIEWPOPUP(&simple_menu[n], 'F', "Forward in Email");
5352 n++;
5354 simple_menu[n++].type = tSeparator;
5356 VIEWPOPUP(&simple_menu[n], 'E', "Exit Viewer");
5357 n++;
5359 simple_menu[n].type = tTail;
5361 (void) mswin_popup(simple_menu);
5362 return(0);
5367 /*----------------------------------------------------------------------
5368 Return characters in scroll tool buffer serially
5370 Args: n -- index of char to return
5372 Returns: returns the character at index 'n', or -1 on error or
5373 end of buffer.
5375 ----*/
5377 mswin_readscrollbuf(n)
5378 int n;
5380 SCRLCTRL_S *st = scroll_state(SS_CUR);
5381 int c;
5382 static char **orig = NULL, **l, *p;
5383 static int lastn;
5385 if(!st)
5386 return(-1);
5389 * All of these are mind-numbingly slow at the moment...
5391 switch(st->parms->text.src){
5392 case CharStar :
5393 return((n >= strlen((char *)st->parms->text.text))
5394 ? -1 : ((char *)st->parms->text.text)[n]);
5396 case CharStarStar :
5397 /* BUG? is this test rigorous enough? */
5398 if(orig != (char **)st->parms->text.text || n < lastn){
5399 lastn = n;
5400 if(orig = l = (char **)st->parms->text.text) /* reset l and p */
5401 p = *l;
5403 else{ /* use cached l and p */
5404 c = n; /* and adjust n */
5405 n -= lastn;
5406 lastn = c;
5409 while(l){ /* look for 'n' on each line */
5410 for(; n && *p; n--, p++)
5413 if(n--) /* 'n' found ? */
5414 p = *++l;
5415 else
5416 break;
5419 return((l && *l) ? *p ? *p : '\n' : -1);
5421 case FileStar :
5422 return((fseek((FILE *)st->parms->text.text, (long) n, 0) < 0
5423 || (c = fgetc((FILE *)st->parms->text.text)) == EOF) ? -1 : c);
5425 default:
5426 return(-1);
5432 /*----------------------------------------------------------------------
5433 MSWin scroll callback. Called during scroll message processing.
5437 Args: cmd - what type of scroll operation.
5438 scroll_pos - paramter for operation.
5439 used as position for SCROLL_TO operation.
5441 Returns: TRUE - did the scroll operation.
5442 FALSE - was not able to do the scroll operation.
5443 ----*/
5445 pcpine_do_scroll (cmd, scroll_pos)
5446 int cmd;
5447 long scroll_pos;
5449 SCRLCTRL_S *st = scroll_state(SS_CUR);
5450 HANDLE_S *next_handle;
5451 int paint = FALSE;
5452 int num_display_lines;
5453 int scroll_lines;
5454 char message[64];
5455 long maxscroll;
5458 message[0] = '\0';
5459 maxscroll = st->num_lines;
5460 switch (cmd) {
5461 case MSWIN_KEY_SCROLLUPLINE:
5462 if(st->top_text_line > 0) {
5463 st->top_text_line -= (int) scroll_pos;
5464 paint = TRUE;
5465 if (st->top_text_line <= 0){
5466 snprintf(message, sizeof(message), "START of %.*s",
5467 32, STYLE_NAME(st->parms));
5468 message[sizeof(message)-1] = '\0';
5469 st->top_text_line = 0;
5472 break;
5474 case MSWIN_KEY_SCROLLDOWNLINE:
5475 if(st->top_text_line < maxscroll) {
5476 st->top_text_line += (int) scroll_pos;
5477 paint = TRUE;
5478 if (st->top_text_line >= maxscroll){
5479 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5480 message[sizeof(message)-1] = '\0';
5481 st->top_text_line = maxscroll;
5484 break;
5486 case MSWIN_KEY_SCROLLUPPAGE:
5487 if(st->top_text_line > 0) {
5488 num_display_lines = SCROLL_LINES(ps_global);
5489 scroll_lines = MIN(MAX(num_display_lines -
5490 ps_global->viewer_overlap, 1), num_display_lines);
5491 if (st->top_text_line > scroll_lines)
5492 st->top_text_line -= scroll_lines;
5493 else {
5494 st->top_text_line = 0;
5495 snprintf(message, sizeof(message), "START of %.*s", 32, STYLE_NAME(st->parms));
5496 message[sizeof(message)-1] = '\0';
5498 paint = TRUE;
5500 break;
5502 case MSWIN_KEY_SCROLLDOWNPAGE:
5503 num_display_lines = SCROLL_LINES(ps_global);
5504 if(st->top_text_line < maxscroll) {
5505 scroll_lines = MIN(MAX(num_display_lines -
5506 ps_global->viewer_overlap, 1), num_display_lines);
5507 st->top_text_line += scroll_lines;
5508 if (st->top_text_line >= maxscroll) {
5509 st->top_text_line = maxscroll;
5510 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5511 message[sizeof(message)-1] = '\0';
5513 paint = TRUE;
5515 break;
5517 case MSWIN_KEY_SCROLLTO:
5518 if (st->top_text_line != scroll_pos) {
5519 st->top_text_line = scroll_pos;
5520 if (st->top_text_line == 0)
5521 snprintf(message, sizeof(message), "START of %.*s", 32, STYLE_NAME(st->parms));
5522 else if(st->top_text_line >= maxscroll)
5523 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5525 message[sizeof(message)-1] = '\0';
5526 paint = TRUE;
5528 break;
5531 /* Need to frame some handles? */
5532 if(st->parms->text.handles
5533 && (next_handle = scroll_handle_in_frame(st->top_text_line)))
5534 st->parms->text.handles = next_handle;
5536 if (paint) {
5537 mswin_beginupdate();
5538 update_scroll_titlebar(st->top_text_line, 0);
5539 (void) scroll_scroll_text(st->top_text_line,
5540 st->parms->text.handles, 1);
5541 if (message[0])
5542 q_status_message(SM_INFO, 0, 1, message);
5544 /* Display is always called so that the "START(END) of message"
5545 * message gets removed when no longer at the start(end). */
5546 display_message (KEY_PGDN);
5547 mswin_endupdate();
5550 return (TRUE);
5554 char *
5555 pcpine_help_scroll(title)
5556 char *title;
5558 SCRLCTRL_S *st = scroll_state(SS_CUR);
5560 if(title)
5561 strncpy(title, (st->parms->help.title)
5562 ? st->parms->help.title : "Alpine Help", 256);
5564 return(pcpine_help(st->parms->help.text));
5569 pcpine_view_cursor(col, row)
5570 int col;
5571 long row;
5573 SCRLCTRL_S *st = scroll_state(SS_CUR);
5574 int key;
5575 long line;
5577 return((row >= HEADER_ROWS(ps_global)
5578 && row < HEADER_ROWS(ps_global) + SCROLL_LINES(ps_global)
5579 && (line = (row - 2) + st->top_text_line) < st->num_lines
5580 && (key = dot_on_handle(line, col))
5581 && scroll_handle_selectable(get_handle(st->parms->text.handles,key)))
5582 ? MSWIN_CURSOR_HAND
5583 : MSWIN_CURSOR_ARROW);
5585 #endif /* _WINDOWS */