* For a calendar entry with method PUBLISH, we show all entries in the calendar.
[alpine.git] / alpine / mailview.c
blobb9e9650d450904faa7e1ee650ab297763fb0afb1
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;
2146 * find a handle with the fragment's name
2148 for(hp = st->parms->text.handles; hp; hp = hp->next)
2149 if(hp->type == URL && hp->h.url.name
2150 && !strcmp(hp->h.url.name, fragment + 1))
2151 break;
2153 if(!hp)
2154 for(hp = st->parms->text.handles->prev; hp; hp = hp->prev)
2155 if(hp->type == URL && hp->h.url.name
2156 && !strcmp(hp->h.url.name, fragment + 1))
2157 break;
2160 * set the top line of the display to contain this line
2162 if(hp && hp->loc){
2163 st->top_text_line = hp->loc->where.row;
2164 ps_global->mangled_body = 1;
2166 else
2167 q_status_message1(SM_ORDER | SM_DING, 0, 3,
2168 "Can't find fragment: %s", fragment);
2170 return(1);
2174 ical_send_reply(char *url)
2176 // ical_compose_reply(url + strlen("x-alpine-ical:"));
2177 return 2;
2182 url_local_phone_home(char *URL)
2184 phone_home(URL + strlen("x-alpine-phone-home:"));
2185 return(2);
2190 * Format editorial comment referencing screen offering
2191 * List-* header supplied commands
2194 rfc2369_editorial(long int msgno, HANDLE_S **handlesp, int flags, int width, gf_io_t pc)
2196 char *p, *hdrp, *hdrs[MLCMD_COUNT + 1],
2197 color[64], buf[2048];
2198 int i, n, rv = TRUE;
2199 HANDLE_S *h = NULL;
2201 if((flags & FM_DISPLAY)
2202 && (hdrp = pine_fetchheader_lines(ps_global->mail_stream, msgno,
2203 NULL, rfc2369_hdrs(hdrs)))){
2204 if(*hdrp){
2205 snprintf(buf, sizeof(buf), "Note: This message contains ");
2206 buf[sizeof(buf)-1] = '\0';
2207 p = buf + strlen(buf);
2209 if(handlesp){
2210 h = new_handle(handlesp);
2211 h->type = Function;
2212 h->h.func.f = rfc2369_display;
2213 h->h.func.args.stream = ps_global->mail_stream;
2214 h->h.func.args.msgmap = ps_global->msgmap;
2215 h->h.func.args.msgno = msgno;
2217 if(!(flags & FM_NOCOLOR)
2218 && handle_start_color(color, sizeof(color), &n, 0)){
2219 if((p-buf)+n < sizeof(buf))
2220 for(i = 0; i < n; i++)
2221 *p++ = color[i];
2223 else if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)){
2224 *p++ = TAG_EMBED;
2225 *p++ = TAG_BOLDON;
2228 if((p-buf)+2 < sizeof(buf)){
2229 *p++ = TAG_EMBED;
2230 *p++ = TAG_HANDLE;
2233 snprintf(p + 1, sizeof(buf)-(p+1-buf), "%d", h->key);
2234 buf[sizeof(buf)-1] = '\0';
2235 *p = strlen(p + 1);
2236 p += (*p + 1);
2239 sstrncpy(&p, "email list management information", sizeof(buf)-(p-buf));
2240 buf[sizeof(buf)-1] = '\0';
2242 if(h){
2243 /* in case it was the current selection */
2244 if((p-buf)+2 < sizeof(buf)){
2245 *p++ = TAG_EMBED;
2246 *p++ = TAG_INVOFF;
2249 if(handle_end_color(color, sizeof(color), &n)){
2250 if((p-buf)+n < sizeof(buf))
2251 for(i = 0; i < n; i++)
2252 *p++ = color[i];
2254 else{
2255 if((p-buf)+2 < sizeof(buf)){
2256 *p++ = TAG_EMBED;
2257 *p++ = TAG_BOLDOFF;
2261 if(p-buf < sizeof(buf))
2262 *p = '\0';
2265 buf[sizeof(buf)-1] = '\0';
2267 rv = (gf_puts(NEWLINE, pc)
2268 && format_editorial(buf, width, flags, handlesp, pc) == NULL
2269 && gf_puts(NEWLINE, pc));
2272 fs_give((void **) &hdrp);
2275 return(rv);
2280 /*----------------------------------------------------------------------
2281 routine for displaying text on the screen.
2283 Args: sparms -- structure of args controlling what happens outside
2284 just the business of managing text scrolling
2286 This displays in three different kinds of text. One is an array of
2287 lines passed in in text_array. The other is a simple long string of
2288 characters passed in in text.
2290 The style determines what some of the error messages will be, and
2291 what commands are available as different things are appropriate for
2292 help text than for message text etc.
2294 ---*/
2297 scrolltool(SCROLL_S *sparms)
2299 register long cur_top_line, num_display_lines;
2300 UCS ch;
2301 int result, done, cmd, found_on, found_on_index,
2302 first_view, force, scroll_lines, km_size,
2303 cursor_row, cursor_col, km_popped;
2304 char *utf8str;
2305 long jn;
2306 struct key_menu *km;
2307 HANDLE_S *next_handle;
2308 bitmap_t bitmap;
2309 OtherMenu what;
2310 Pos whereis_pos;
2312 num_display_lines = SCROLL_LINES(ps_global);
2313 km_popped = 0;
2314 ps_global->mangled_header = 1;
2315 ps_global->mangled_footer = 1;
2316 ps_global->mangled_body = !sparms->body_valid;
2319 what = sparms->keys.what; /* which key menu to display */
2320 cur_top_line = 0;
2321 done = 0;
2322 found_on = -1;
2323 found_on_index = -1;
2324 first_view = 1;
2325 if(sparms->quell_first_view)
2326 first_view = 0;
2328 force = 0;
2329 ch = 'x'; /* for first time through */
2330 whereis_pos.row = 0;
2331 whereis_pos.col = 0;
2332 next_handle = sparms->text.handles;
2334 set_scroll_text(sparms, cur_top_line, scroll_state(SS_NEW));
2335 format_scroll_text();
2337 if((km = sparms->keys.menu) != NULL){
2338 memcpy(bitmap, sparms->keys.bitmap, sizeof(bitmap_t));
2340 else{
2341 setbitmap(bitmap);
2342 km = &simple_text_keymenu;
2343 #ifdef _WINDOWS
2344 sparms->mouse.popup = simple_text_popup;
2345 #endif
2348 if(!sparms->bar.title)
2349 sparms->bar.title = "Text";
2351 if(sparms->bar.style == TitleBarNone){
2352 if(THREADING() && sp_viewing_a_thread(ps_global->mail_stream))
2353 sparms->bar.style = ThrdMsgPercent;
2354 else
2355 sparms->bar.style = MsgTextPercent;
2358 switch(sparms->start.on){
2359 case LastPage :
2360 cur_top_line = MAX(0, scroll_text_lines() - (num_display_lines-2));
2361 if(F_ON(F_SHOW_CURSOR, ps_global)){
2362 whereis_pos.row = scroll_text_lines() - cur_top_line;
2363 found_on = scroll_text_lines() - 1;
2366 break;
2368 case Fragment :
2369 if(sparms->start.loc.frag){
2370 (void) url_local_fragment(sparms->start.loc.frag);
2372 cur_top_line = scroll_state(SS_CUR)->top_text_line;
2374 if(F_ON(F_SHOW_CURSOR, ps_global)){
2375 whereis_pos.row = scroll_text_lines() - cur_top_line;
2376 found_on = scroll_text_lines() - 1;
2380 break;
2382 case Offset :
2383 if(sparms->start.loc.offset){
2384 for(cur_top_line = 0L;
2385 cur_top_line + 1 < scroll_text_lines()
2386 && (sparms->start.loc.offset
2387 -= scroll_handle_column(cur_top_line,
2388 -1)) >= 0;
2389 cur_top_line++)
2393 break;
2395 case Handle :
2396 if(scroll_handle_obscured(sparms->text.handles))
2397 cur_top_line = scroll_handle_reframe(-1, TRUE);
2399 break;
2401 default : /* no-op */
2402 break;
2405 /* prepare for calls below to tell us where to go */
2406 ps_global->next_screen = SCREEN_FUN_NULL;
2408 cancel_busy_cue(-1);
2410 while(!done) {
2411 ps_global->user_says_cancel = 0;
2412 if(km_popped){
2413 km_popped--;
2414 if(km_popped == 0){
2415 clearfooter(ps_global);
2416 ps_global->mangled_body = 1;
2420 if(ps_global->mangled_screen) {
2421 ps_global->mangled_header = 1;
2422 ps_global->mangled_footer = 1;
2423 ps_global->mangled_body = 1;
2426 if(!sparms->quell_newmail && streams_died())
2427 ps_global->mangled_header = 1;
2429 dprint((9, "@@@@ current:%ld\n",
2430 mn_get_cur(ps_global->msgmap)));
2433 /*==================== All Screen painting ====================*/
2434 /*-------------- The title bar ---------------*/
2435 update_scroll_titlebar(cur_top_line, ps_global->mangled_header);
2437 if(ps_global->mangled_screen){
2438 /* this is the only line not cleared by header, body or footer
2439 * repaint calls....
2441 ClearLine(1);
2442 ps_global->mangled_screen = 0;
2445 /*---- Scroll or update the body of the text on the screen -------*/
2446 cur_top_line = scroll_scroll_text(cur_top_line, next_handle,
2447 ps_global->mangled_body);
2448 ps_global->redrawer = redraw_scroll_text;
2449 ps_global->mangled_body = 0;
2451 /*--- Check to see if keymenu might change based on next_handle --*/
2452 if(sparms->text.handles != next_handle)
2453 ps_global->mangled_footer = 1;
2455 if(next_handle)
2456 sparms->text.handles = next_handle;
2458 /*------------- The key menu footer --------------------*/
2459 if(ps_global->mangled_footer || sparms->keys.each_cmd){
2460 if(km_popped){
2461 FOOTER_ROWS(ps_global) = 3;
2462 clearfooter(ps_global);
2465 if(F_ON(F_ARROW_NAV, ps_global)){
2466 menu_clear_binding(km, KEY_LEFT);
2467 if((cmd = menu_clear_binding(km, '<')) != MC_UNKNOWN){
2468 menu_add_binding(km, '<', cmd);
2469 menu_add_binding(km, KEY_LEFT, cmd);
2473 if(F_ON(F_ARROW_NAV, ps_global)){
2474 menu_clear_binding(km, KEY_RIGHT);
2475 if((cmd = menu_clear_binding(km, '>')) != MC_UNKNOWN){
2476 menu_add_binding(km, '>', cmd);
2477 menu_add_binding(km, KEY_RIGHT, cmd);
2481 if(sparms->keys.each_cmd){
2482 (*sparms->keys.each_cmd)(sparms,
2483 scroll_handle_obscured(sparms->text.handles));
2484 memcpy(bitmap, sparms->keys.bitmap, sizeof(bitmap_t));
2487 if(menu_binding_index(km, MC_JUMP) >= 0){
2488 for(cmd = 0; cmd < 10; cmd++)
2489 if(F_ON(F_ENABLE_JUMP, ps_global))
2490 (void) menu_add_binding(km, '0' + cmd, MC_JUMP);
2491 else
2492 (void) menu_clear_binding(km, '0' + cmd);
2495 draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
2496 1-FOOTER_ROWS(ps_global), 0, what);
2497 what = SameMenu;
2498 ps_global->mangled_footer = 0;
2499 if(km_popped){
2500 FOOTER_ROWS(ps_global) = 1;
2501 mark_keymenu_dirty();
2505 if((ps_global->first_time_user || ps_global->show_new_version)
2506 && first_view && sparms->text.handles
2507 && (sparms->text.handles->next || sparms->text.handles->prev)
2508 && !sparms->quell_help)
2509 q_status_message(SM_ORDER, 0, 3, HANDLE_INIT_MSG);
2511 /*============ Check for New Mail and CheckPoint ============*/
2512 if(!sparms->quell_newmail &&
2513 new_mail(force, NM_TIMING(ch), NM_STATUS_MSG) >= 0){
2514 update_scroll_titlebar(cur_top_line, 1);
2515 if(ps_global->mangled_footer)
2516 draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
2517 1-FOOTER_ROWS(ps_global), 0, what);
2519 ps_global->mangled_footer = 0;
2523 * If an expunge of the current message happened during the
2524 * new mail check we want to bail out of here. See mm_expunged.
2526 if(ps_global->next_screen != SCREEN_FUN_NULL){
2527 done = 1;
2528 continue;
2531 if(ps_global->noticed_change_in_unseen){
2532 ps_global->noticed_change_in_unseen = 0; /* redraw only once */
2533 cmd = MC_RESIZE; /* causes cursor to be saved in folder_lister */
2534 done = 1;
2535 continue;
2538 if(first_view && num_display_lines >= scroll_text_lines())
2539 q_status_message1(SM_INFO, 0, 1, "ALL of %s", STYLE_NAME(sparms));
2542 force = 0; /* may not need to next time around */
2543 first_view = 0; /* check_point a priority any more? */
2545 /*==================== Output the status message ==============*/
2546 if(!sparms->no_stat_msg){
2547 if(km_popped){
2548 FOOTER_ROWS(ps_global) = 3;
2549 mark_status_unknown();
2552 display_message(ch);
2553 if(km_popped){
2554 FOOTER_ROWS(ps_global) = 1;
2555 mark_status_unknown();
2559 if(F_ON(F_SHOW_CURSOR, ps_global)){
2560 #ifdef WINDOWS
2561 if(cur_top_line != scroll_state(SS_CUR)->top_text_line)
2562 whereis_pos.row = 0;
2563 #endif
2565 if(whereis_pos.row > 0){
2566 cursor_row = SCROLL_LINES_ABOVE(ps_global)
2567 + whereis_pos.row - 1;
2568 cursor_col = whereis_pos.col;
2570 else{
2571 POSLIST_S *lp = NULL;
2573 if(sparms->text.handles &&
2574 !scroll_handle_obscured(sparms->text.handles)){
2575 SCRLCTRL_S *st = scroll_state(SS_CUR);
2577 for(lp = sparms->text.handles->loc; lp; lp = lp->next)
2578 if(lp->where.row >= st->top_text_line
2579 && lp->where.row < st->top_text_line
2580 + st->screen.length){
2581 cursor_row = lp->where.row - cur_top_line
2582 + SCROLL_LINES_ABOVE(ps_global);
2583 cursor_col = lp->where.col;
2584 break;
2588 if(!lp){
2589 cursor_col = 0;
2590 /* first new line of text */
2591 cursor_row = SCROLL_LINES_ABOVE(ps_global) +
2592 ((cur_top_line == 0) ? 0 : ps_global->viewer_overlap);
2596 else{
2597 cursor_col = 0;
2598 cursor_row = ps_global->ttyo->screen_rows
2599 - SCROLL_LINES_BELOW(ps_global);
2602 MoveCursor(cursor_row, cursor_col);
2604 /*================ Get command and validate =====================*/
2605 #ifdef MOUSE
2606 #ifndef WIN32
2607 if(sparms->text.handles)
2608 #endif
2610 mouse_in_content(KEY_MOUSE, -1, -1, 0x5, 0);
2611 register_mfunc(mouse_in_content, HEADER_ROWS(ps_global), 0,
2612 ps_global->ttyo->screen_rows
2613 - (FOOTER_ROWS(ps_global) + 1),
2614 ps_global->ttyo->screen_cols);
2616 #endif
2617 #ifdef _WINDOWS
2618 mswin_allowcopy(mswin_readscrollbuf);
2619 mswin_setscrollcallback(pcpine_do_scroll);
2621 if(sparms->help.text != NO_HELP)
2622 mswin_sethelptextcallback(pcpine_help_scroll);
2624 if(sparms->text.handles
2625 && sparms->text.handles->type != Folder)
2626 mswin_mousetrackcallback(pcpine_view_cursor);
2628 if(ps_global->prev_screen == mail_view_screen)
2629 mswin_setviewinwindcallback(view_in_new_window);
2630 #endif
2631 ch = (sparms->quell_newmail || read_command_prep()) ? read_command(&utf8str) : NO_OP_COMMAND;
2632 #ifdef MOUSE
2633 #ifndef WIN32
2634 if(sparms->text.handles)
2635 #endif
2636 clear_mfunc(mouse_in_content);
2637 #endif
2638 #ifdef _WINDOWS
2639 mswin_allowcopy(NULL);
2640 mswin_setscrollcallback(NULL);
2641 mswin_sethelptextcallback(NULL);
2642 mswin_mousetrackcallback(NULL);
2643 mswin_setviewinwindcallback(NULL);
2644 cur_top_line = scroll_state(SS_CUR)->top_text_line;
2645 #endif
2647 cmd = menu_command(ch, km);
2649 if(km_popped)
2650 switch(cmd){
2651 case MC_NONE :
2652 case MC_OTHER :
2653 case MC_RESIZE:
2654 case MC_REPAINT :
2655 km_popped++;
2656 break;
2658 default:
2659 clearfooter(ps_global);
2660 break;
2664 /*============= Execute command =======================*/
2665 switch(cmd){
2667 /* ------ Help -------*/
2668 case MC_HELP :
2669 if(FOOTER_ROWS(ps_global) == 1 && km_popped == 0){
2670 km_popped = 2;
2671 ps_global->mangled_footer = 1;
2672 break;
2675 whereis_pos.row = 0;
2676 if(sparms->help.text == NO_HELP){
2677 q_status_message(SM_ORDER, 0, 5,
2678 _("No help text currently available"));
2679 break;
2682 km_size = FOOTER_ROWS(ps_global);
2684 helper(sparms->help.text, sparms->help.title, 0);
2686 if(ps_global->next_screen != main_menu_screen
2687 && km_size == FOOTER_ROWS(ps_global)) {
2688 /* Have to reset because helper uses scroll_text */
2689 num_display_lines = SCROLL_LINES(ps_global);
2690 ps_global->mangled_screen = 1;
2692 else
2693 done = 1;
2695 break;
2698 /*---------- Roll keymenu ------*/
2699 case MC_OTHER :
2700 if(F_OFF(F_USE_FK, ps_global))
2701 warn_other_cmds();
2703 what = NextMenu;
2704 ps_global->mangled_footer = 1;
2705 break;
2708 /* -------- Scroll back one page -----------*/
2709 case MC_PAGEUP :
2710 whereis_pos.row = 0;
2711 if(cur_top_line) {
2712 scroll_lines = MIN(MAX(num_display_lines -
2713 ps_global->viewer_overlap, 1), num_display_lines);
2714 cur_top_line -= scroll_lines;
2715 if(cur_top_line <= 0){
2716 cur_top_line = 0;
2717 q_status_message1(SM_INFO, 0, 1, "START of %s",
2718 STYLE_NAME(sparms));
2721 else{
2722 /* hilite last available handle */
2723 next_handle = NULL;
2724 if(sparms->text.handles){
2725 HANDLE_S *h = sparms->text.handles;
2727 while((h = scroll_handle_prev_sel(h))
2728 && !scroll_handle_obscured(h))
2729 next_handle = h;
2732 if(!next_handle)
2733 q_status_message1(SM_ORDER, 0, 1, _("Already at start of %s"),
2734 STYLE_NAME(sparms));
2739 break;
2742 /*---- Scroll down one page -------*/
2743 case MC_PAGEDN :
2744 if(cur_top_line + num_display_lines < scroll_text_lines()){
2745 whereis_pos.row = 0;
2746 scroll_lines = MIN(MAX(num_display_lines -
2747 ps_global->viewer_overlap, 1), num_display_lines);
2748 cur_top_line += scroll_lines;
2750 if(cur_top_line + num_display_lines >= scroll_text_lines())
2751 q_status_message1(SM_INFO, 0, 1, "END of %s",
2752 STYLE_NAME(sparms));
2754 else if(!sparms->end_scroll
2755 || !(done = (*sparms->end_scroll)(sparms))){
2756 q_status_message1(SM_ORDER, 0, 1, _("Already at end of %s"),
2757 STYLE_NAME(sparms));
2758 /* hilite last available handle */
2759 if(sparms->text.handles){
2760 HANDLE_S *h = sparms->text.handles;
2762 while((h = scroll_handle_next_sel(h)) != NULL)
2763 next_handle = h;
2767 break;
2769 /* scroll to the top page */
2770 case MC_HOMEKEY:
2771 if(cur_top_line){
2772 cur_top_line = 0;
2773 q_status_message1(SM_INFO, 0, 1, "START of %s",
2774 STYLE_NAME(sparms));
2777 next_handle = NULL;
2778 if(sparms->text.handles){
2779 HANDLE_S *h = sparms->text.handles;
2781 while((h = scroll_handle_prev_sel(h)) != NULL)
2782 next_handle = h;
2784 break;
2786 /* scroll to the bottom page */
2787 case MC_ENDKEY:
2788 if(cur_top_line + num_display_lines < scroll_text_lines()){
2789 cur_top_line = scroll_text_lines() - MIN(5, num_display_lines);
2790 q_status_message1(SM_INFO, 0, 1, "END of %s",
2791 STYLE_NAME(sparms));
2794 if(sparms->text.handles){
2795 HANDLE_S *h = sparms->text.handles;
2797 while((h = scroll_handle_next_sel(h)) != NULL)
2798 next_handle = h;
2800 break;
2802 /*------ Scroll down one line -----*/
2803 case MC_CHARDOWN :
2804 next_handle = NULL;
2805 if(sparms->text.handles){
2806 if(sparms->vert_handle){
2807 HANDLE_S *h, *h2;
2808 int i, j, k;
2810 h2 = sparms->text.handles;
2811 if(h2->type == Folder && h2->prev && h2->prev->is_dual_do_open)
2812 h2 = h2->prev;
2814 i = h2->loc->where.row + 1;
2815 j = h2->loc->where.col;
2816 for(h = NULL, k = h2->key;
2817 h2 && (!h
2818 || (h->loc->where.row == h2->loc->where.row));
2819 h2 = h2->next)
2820 /* must be different key */
2821 /* ... below current line */
2822 /* ... pref'bly to left */
2823 if(h2->key != k
2824 && h2->loc->where.row >= i){
2825 if(h2->loc->where.col > j){
2826 if(!h)
2827 h = h2;
2829 break;
2831 else
2832 h = h2;
2835 if(h){
2836 whereis_pos.row = 0;
2837 next_handle = h;
2838 if((result = scroll_handle_obscured(next_handle)) != 0){
2839 long new_top;
2841 if(scroll_handle_obscured(sparms->text.handles)
2842 && result > 0)
2843 next_handle = sparms->text.handles;
2845 ps_global->mangled_body++;
2846 new_top = scroll_handle_reframe(next_handle->key,0);
2847 if(new_top >= 0)
2848 cur_top_line = new_top;
2852 else if(!(ch == ctrl('N') || F_ON(F_FORCE_ARROWS, ps_global)))
2853 next_handle = scroll_handle_next(sparms->text.handles);
2856 if(!next_handle){
2857 if(cur_top_line + num_display_lines < scroll_text_lines()){
2858 whereis_pos.row = 0;
2859 cur_top_line++;
2860 if(cur_top_line + num_display_lines >= scroll_text_lines())
2861 q_status_message1(SM_INFO, 0, 1, "END of %s",
2862 STYLE_NAME(sparms));
2864 else
2865 q_status_message1(SM_ORDER, 0, 1, _("Already at end of %s"),
2866 STYLE_NAME(sparms));
2869 break;
2872 /* ------ Scroll back up one line -------*/
2873 case MC_CHARUP :
2874 next_handle = NULL;
2875 if(sparms->text.handles){
2876 if(sparms->vert_handle){
2877 HANDLE_S *h, *h2;
2878 int i, j, k;
2880 h2 = sparms->text.handles;
2881 if(h2->type == Folder && h2->prev && h2->prev->is_dual_do_open)
2882 h2 = h2->prev;
2884 i = h2->loc->where.row - 1;
2885 j = h2->loc->where.col;
2887 for(h = NULL, k = h2->key;
2888 h2 && (!h
2889 || (h->loc->where.row == h2->loc->where.row));
2890 h2 = h2->prev)
2891 /* must be new key, above current
2892 * line and pref'bly to right
2894 if(h2->key != k
2895 && h2->loc->where.row <= i){
2896 if(h2->loc->where.col < j){
2897 if(!h)
2898 h = h2;
2900 break;
2902 else
2903 h = h2;
2906 if(h){
2907 whereis_pos.row = 0;
2908 next_handle = h;
2909 if((result = scroll_handle_obscured(next_handle)) != 0){
2910 long new_top;
2912 if(scroll_handle_obscured(sparms->text.handles)
2913 && result < 0)
2914 next_handle = sparms->text.handles;
2916 ps_global->mangled_body++;
2917 new_top = scroll_handle_reframe(next_handle->key,0);
2918 if(new_top >= 0)
2919 cur_top_line = new_top;
2923 else if(!(ch == ctrl('P') || F_ON(F_FORCE_ARROWS, ps_global)))
2924 next_handle = scroll_handle_prev(sparms->text.handles);
2927 if(!next_handle){
2928 whereis_pos.row = 0;
2929 if(cur_top_line){
2930 cur_top_line--;
2931 if(cur_top_line == 0)
2932 q_status_message1(SM_INFO, 0, 1, "START of %s",
2933 STYLE_NAME(sparms));
2935 else
2936 q_status_message1(SM_ORDER, 0, 1,
2937 _("Already at start of %s"),
2938 STYLE_NAME(sparms));
2941 break;
2944 case MC_NEXT_HANDLE :
2945 if((next_handle = scroll_handle_next_sel(sparms->text.handles)) != NULL){
2946 whereis_pos.row = 0;
2947 if((result = scroll_handle_obscured(next_handle)) != 0){
2948 long new_top;
2950 if(scroll_handle_obscured(sparms->text.handles)
2951 && result > 0)
2952 next_handle = sparms->text.handles;
2954 ps_global->mangled_body++;
2955 new_top = scroll_handle_reframe(next_handle->key, 0);
2956 if(new_top >= 0)
2957 cur_top_line = new_top;
2960 else{
2961 if(scroll_handle_obscured(sparms->text.handles)){
2962 long new_top;
2964 ps_global->mangled_body++;
2965 if((new_top = scroll_handle_reframe(-1, 0)) >= 0){
2966 whereis_pos.row = 0;
2967 cur_top_line = new_top;
2971 q_status_message1(SM_ORDER, 0, 1,
2972 _("Already on last item in %s"),
2973 STYLE_NAME(sparms));
2976 break;
2979 case MC_PREV_HANDLE :
2980 if((next_handle = scroll_handle_prev_sel(sparms->text.handles)) != NULL){
2981 whereis_pos.row = 0;
2982 if((result = scroll_handle_obscured(next_handle)) != 0){
2983 long new_top;
2985 if(scroll_handle_obscured(sparms->text.handles)
2986 && result < 0)
2987 next_handle = sparms->text.handles;
2989 ps_global->mangled_body++;
2990 new_top = scroll_handle_reframe(next_handle->key, 0);
2991 if(new_top >= 0)
2992 cur_top_line = new_top;
2995 else{
2996 if(scroll_handle_obscured(sparms->text.handles)){
2997 long new_top;
2999 ps_global->mangled_body++;
3000 if((new_top = scroll_handle_reframe(-1, 0)) >= 0){
3001 whereis_pos.row = 0;
3002 cur_top_line = new_top;
3006 q_status_message1(SM_ORDER, 0, 1,
3007 _("Already on first item in %s"),
3008 STYLE_NAME(sparms));
3011 break;
3014 /*------ View the current handle ------*/
3015 case MC_VIEW_HANDLE :
3016 switch(scroll_handle_obscured(sparms->text.handles)){
3017 default :
3018 case 0 :
3019 switch(scroll_handle_launch(sparms->text.handles,
3020 sparms->text.handles->force_display)){
3021 case 1 :
3022 cmd = MC_EXIT; /* propagate */
3023 done = 1;
3024 break;
3026 case -1 :
3027 cmd_cancelled(NULL);
3028 break;
3030 default :
3031 break;
3034 cur_top_line = scroll_state(SS_CUR)->top_text_line;
3035 break;
3037 case 1 :
3038 q_status_message(SM_ORDER, 0, 2, HANDLE_BELOW_ERR);
3039 break;
3041 case -1 :
3042 q_status_message(SM_ORDER, 0, 2, HANDLE_ABOVE_ERR);
3043 break;
3046 break;
3048 /*---------- Search text (where is) ----------*/
3049 case MC_WHEREIS :
3050 ps_global->mangled_footer = 1;
3051 {long start_row;
3052 int start_index, key = 0;
3053 char *report = NULL;
3055 start_row = cur_top_line;
3056 start_index = 0;
3058 if(F_ON(F_SHOW_CURSOR,ps_global)){
3059 if(found_on < 0
3060 || found_on >= scroll_text_lines()
3061 || found_on < cur_top_line
3062 || found_on >= cur_top_line + num_display_lines){
3063 start_row = cur_top_line;
3064 start_index = 0;
3066 else{
3067 if(found_on_index < 0){
3068 start_row = found_on + 1;
3069 start_index = 0;
3071 else{
3072 start_row = found_on;
3073 start_index = found_on_index+1;
3077 else if(sparms->srch_handle){
3078 HANDLE_S *h;
3080 if((h = scroll_handle_next_sel(sparms->text.handles)) != NULL){
3082 * Translate the screen's column into the
3083 * line offset to start on...
3085 * This makes it so search_text never returns -3
3086 * so we don't know it is the same match. That's
3087 * because we start well after the current handle
3088 * (at the next handle) and that causes us to
3089 * think the one we just matched on is a different
3090 * one from before. Can't think of an easy way to
3091 * fix it, though, and it isn't a big deal. We still
3092 * match, we just don't say current line contains
3093 * the only match.
3095 start_row = h->loc->where.row;
3096 start_index = scroll_handle_index(start_row, h->loc->where.col);
3098 else{
3099 /* last handle, start over at top */
3100 start_row = cur_top_line;
3101 start_index = 0;
3104 else{
3105 start_row = (found_on < 0
3106 || found_on >= scroll_text_lines()
3107 || found_on < cur_top_line
3108 || found_on >= cur_top_line + num_display_lines)
3109 ? cur_top_line : found_on + 1,
3110 start_index = 0;
3113 found_on = search_text(-FOOTER_ROWS(ps_global), start_row,
3114 start_index, &report,
3115 &whereis_pos, &found_on_index);
3117 if(found_on == -4){ /* search to top of text */
3118 whereis_pos.row = 0;
3119 whereis_pos.col = 0;
3120 found_on = 0;
3121 if(sparms->text.handles && sparms->srch_handle)
3122 key = 1;
3124 else if(found_on == -5){ /* search to bottom of text */
3125 HANDLE_S *h;
3127 whereis_pos.row = MAX(scroll_text_lines() - 1, 0);
3128 whereis_pos.col = 0;
3129 found_on = whereis_pos.row;
3130 if((h = sparms->text.handles) && sparms->srch_handle)
3132 key = h->key;
3133 while((h = h->next) != NULL);
3135 else if(found_on == -3){
3136 whereis_pos.row = found_on = start_row;
3137 found_on_index = start_index - 1;
3138 q_status_message(SM_ORDER, 1, 3,
3139 _("Current line contains the only match"));
3142 if(found_on >= 0){
3143 result = found_on < cur_top_line;
3144 if(!key)
3145 key = (sparms->text.handles)
3146 ? dot_on_handle(found_on, whereis_pos.col) : 0;
3148 if(F_ON(F_FORCE_LOW_SPEED,ps_global)
3149 || ps_global->low_speed
3150 || F_ON(F_SHOW_CURSOR,ps_global)
3151 || key){
3152 if((found_on >= cur_top_line + num_display_lines ||
3153 found_on < cur_top_line) &&
3154 num_display_lines > ps_global->viewer_overlap){
3155 cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
3156 if(scroll_text_lines()-cur_top_line < 5)
3157 cur_top_line = MAX(0,
3158 scroll_text_lines()-MIN(5,num_display_lines));
3160 /* else leave cur_top_line alone */
3162 else{
3163 cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
3164 if(scroll_text_lines()-cur_top_line < 5)
3165 cur_top_line = MAX(0,
3166 scroll_text_lines()-MIN(5,num_display_lines));
3169 whereis_pos.row = whereis_pos.row - cur_top_line + 1;
3170 if(report)
3171 q_status_message(SM_ORDER, 0, 3, report);
3172 else
3173 q_status_message2(SM_ORDER, 0, 3,
3174 "%sFound on line %s on screen",
3175 result ? "Search wrapped to start. " : "",
3176 int2string(whereis_pos.row));
3178 if(key){
3179 if(sparms->text.handles->key < key)
3180 for(next_handle = sparms->text.handles->next;
3181 next_handle->key != key;
3182 next_handle = next_handle->next)
3184 else
3185 for(next_handle = sparms->text.handles;
3186 next_handle->key != key;
3187 next_handle = next_handle->prev)
3191 else if(found_on == -1)
3192 cmd_cancelled("Search");
3193 else
3194 q_status_message(SM_ORDER, 0, 3, _("Word not found"));
3197 break;
3200 /*-------------- jump command -------------*/
3201 /* NOTE: preempt the process_cmd() version because
3202 * we need to get at the number..
3204 case MC_JUMP :
3205 jn = jump_to(ps_global->msgmap, -FOOTER_ROWS(ps_global), ch,
3206 sparms, View);
3207 if(sparms && sparms->jump_is_debug)
3208 done = 1;
3209 else if(jn > 0 && jn != mn_get_cur(ps_global->msgmap)){
3211 if(mn_total_cur(ps_global->msgmap) > 1L)
3212 mn_reset_cur(ps_global->msgmap, jn);
3213 else
3214 mn_set_cur(ps_global->msgmap, jn);
3216 done = 1;
3218 else
3219 ps_global->mangled_footer = 1;
3221 break;
3224 #ifdef MOUSE
3225 /*-------------- Mouse Event -------------*/
3226 case MC_MOUSE:
3228 MOUSEPRESS mp;
3229 long line;
3230 int key;
3232 mouse_get_last (NULL, &mp);
3233 mp.row -= 2;
3235 /* The clicked line have anything special on it? */
3236 if((line = cur_top_line + mp.row) < scroll_text_lines()
3237 && (key = dot_on_handle(line, mp.col))){
3238 switch(mp.button){
3239 case M_BUTTON_RIGHT :
3240 #ifdef _WINDOWS
3241 if(sparms->mouse.popup){
3242 if(sparms->text.handles->key < key)
3243 for(next_handle = sparms->text.handles->next;
3244 next_handle->key != key;
3245 next_handle = next_handle->next)
3247 else
3248 for(next_handle = sparms->text.handles;
3249 next_handle->key != key;
3250 next_handle = next_handle->prev)
3253 if(sparms->mouse.popup){
3254 cur_top_line = scroll_scroll_text(cur_top_line,
3255 next_handle,
3256 ps_global->mangled_body);
3257 fflush(stdout);
3258 switch((*sparms->mouse.popup)(sparms, key)){
3259 case 1 :
3260 cur_top_line = doubleclick_handle(sparms, next_handle, &cmd, &done);
3261 break;
3263 case 2 :
3264 done++;
3265 break;
3270 #endif
3271 break;
3273 case M_BUTTON_LEFT :
3274 if(sparms->text.handles->key < key)
3275 for(next_handle = sparms->text.handles->next;
3276 next_handle->key != key;
3277 next_handle = next_handle->next)
3279 else
3280 for(next_handle = sparms->text.handles;
3281 next_handle->key != key;
3282 next_handle = next_handle->prev)
3285 if(mp.doubleclick) /* launch url */
3286 cur_top_line = doubleclick_handle(sparms, next_handle, &cmd, &done);
3287 else if(sparms->mouse.click)
3288 (*sparms->mouse.click)(sparms);
3290 break;
3292 case M_BUTTON_MIDDLE : /* NO-OP for now */
3293 break;
3295 default: /* just ignore */
3296 break;
3299 #ifdef _WINDOWS
3300 else if(mp.button == M_BUTTON_RIGHT){
3302 * Toss generic popup on to the screen
3304 if(sparms->mouse.popup)
3305 if((*sparms->mouse.popup)(sparms, 0) == 2){
3306 done++;
3309 #endif
3312 break;
3313 #endif /* MOUSE */
3316 /*-------------- Display Resize -------------*/
3317 case MC_RESIZE :
3318 if(sparms->resize_exit){
3319 long line;
3322 * Figure out char offset of the char in the top left
3323 * corner of the display. Pass it back to the
3324 * fetcher/formatter and have it pass the offset
3325 * back to us...
3327 sparms->start.on = Offset;
3328 for(sparms->start.loc.offset = line = 0L;
3329 line < cur_top_line;
3330 line++)
3331 sparms->start.loc.offset += scroll_handle_column(line, -1);
3333 done = 1;
3334 ClearLine(1);
3335 break;
3337 /* else no reformatting neccessary, fall thru to repaint */
3340 /*-------------- refresh -------------*/
3341 case MC_REPAINT :
3342 num_display_lines = SCROLL_LINES(ps_global);
3343 mark_status_dirty();
3344 mark_keymenu_dirty();
3345 mark_titlebar_dirty();
3346 ps_global->mangled_screen = 1;
3347 force = 1;
3348 break;
3351 /*------- no op timeout to check for new mail ------*/
3352 case MC_NONE :
3353 break;
3356 /*------- Forward displayed text ------*/
3357 case MC_FWDTEXT :
3358 forward_text(ps_global, sparms->text.text, sparms->text.src);
3359 break;
3362 /*----------- Save the displayed text ------------*/
3363 case MC_SAVETEXT :
3364 (void)simple_export(ps_global, sparms->text.text,
3365 sparms->text.src, "text", NULL);
3366 break;
3369 /*----------- Exit this screen ------------*/
3370 case MC_EXIT :
3371 done = 1;
3372 break;
3375 /*----------- Pop back to the Main Menu ------------*/
3376 case MC_MAIN :
3377 ps_global->next_screen = main_menu_screen;
3378 done = 1;
3379 break;
3382 /*----------- Print ------------*/
3383 case MC_PRINTTXT :
3384 print_to_printer(sparms);
3385 break;
3388 /* ------- First handle on Line ------ */
3389 case MC_GOTOBOL :
3390 if(sparms->text.handles){
3391 next_handle = scroll_handle_boundary(sparms->text.handles,
3392 scroll_handle_prev_sel);
3394 break;
3396 /* fall thru as bogus */
3398 /* ------- Last handle on Line ------ */
3399 case MC_GOTOEOL :
3400 if(sparms->text.handles){
3401 next_handle = scroll_handle_boundary(sparms->text.handles,
3402 scroll_handle_next_sel);
3404 break;
3406 /* fall thru as bogus */
3408 /*------- BOGUS INPUT ------*/
3409 case MC_CHARRIGHT :
3410 case MC_CHARLEFT :
3411 case MC_UNKNOWN :
3412 if(sparms->bogus_input)
3413 done = (*sparms->bogus_input)(ch);
3414 else
3415 bogus_command(ch, F_ON(F_USE_FK,ps_global) ? "F1" : "?");
3417 break;
3420 case MC_UTF8:
3421 bogus_utf8_command(utf8str, F_ON(F_USE_FK, ps_global) ? "F1" : "?");
3422 break;
3425 /*------- Standard commands ------*/
3426 default:
3427 whereis_pos.row = 0;
3428 if(sparms->proc.tool)
3429 result = (*sparms->proc.tool)(cmd, ps_global->msgmap, sparms);
3430 else
3431 result = process_cmd(ps_global, ps_global->mail_stream,
3432 ps_global->msgmap, cmd, View, &force);
3434 dprint((7, "PROCESS_CMD return: %d\n", result));
3436 if(ps_global->next_screen != SCREEN_FUN_NULL || result == 1){
3437 done = 1;
3438 if(cmd == MC_FULLHDR){
3439 if(ps_global->full_header == 1){
3440 long line;
3443 * Figure out char offset of the char in the top left
3444 * corner of the display. Pass it back to the
3445 * fetcher/formatter and have it pass the offset
3446 * back to us...
3448 sparms->start.on = Offset;
3449 for(sparms->start.loc.offset = line = 0L;
3450 line < cur_top_line;
3451 line++)
3452 sparms->start.loc.offset +=
3453 scroll_handle_column(line, -1);
3455 else
3456 sparms->start.on = 0;
3458 switch(km->which){
3459 case 0:
3460 sparms->keys.what = FirstMenu;
3461 break;
3462 case 1:
3463 sparms->keys.what = SecondMenu;
3464 break;
3465 case 2:
3466 sparms->keys.what = ThirdMenu;
3467 break;
3468 case 3:
3469 sparms->keys.what = FourthMenu;
3470 break;
3474 else if(!scroll_state(SS_CUR)){
3475 num_display_lines = SCROLL_LINES(ps_global);
3476 ps_global->mangled_screen = 1;
3479 break;
3481 } /* End of switch() */
3483 /* Need to frame some handles? */
3484 if(sparms->text.handles
3485 && ((!next_handle
3486 && handle_on_page(sparms->text.handles, cur_top_line,
3487 cur_top_line + num_display_lines))
3488 || (next_handle
3489 && handle_on_page(next_handle, cur_top_line,
3490 cur_top_line + num_display_lines))))
3491 next_handle = scroll_handle_in_frame(cur_top_line);
3493 } /* End of while() -- loop executing commands */
3495 ps_global->redrawer = NULL; /* next statement makes this invalid! */
3496 zero_scroll_text(); /* very important to zero out on return!!! */
3497 scroll_state(SS_FREE);
3498 if(sparms->bar.color)
3499 free_color_pair(&sparms->bar.color);
3501 #ifdef _WINDOWS
3502 scroll_setrange(0L, 0L);
3503 #endif
3504 return(cmd);
3508 /*----------------------------------------------------------------------
3509 Print text on paper
3511 Args: text -- The text to print out
3512 source -- What type of source text is
3513 message -- Message for open_printer()
3514 Handling of error conditions is very poor.
3516 ----*/
3517 static int
3518 print_to_printer(SCROLL_S *sparms)
3520 char message[64];
3522 snprintf(message, sizeof(message), "%s", STYLE_NAME(sparms));
3523 message[sizeof(message)-1] = '\0';
3525 if(open_printer(message) != 0)
3526 return(-1);
3528 switch(sparms->text.src){
3529 case CharStar :
3530 if(sparms->text.text != (char *)NULL)
3531 print_text((char *)sparms->text.text);
3533 break;
3535 case CharStarStar :
3536 if(sparms->text.text != (char **)NULL){
3537 register char **t;
3539 for(t = sparms->text.text; *t != NULL; t++){
3540 print_text(*t);
3541 print_text(NEWLINE);
3545 break;
3547 case FileStar :
3548 if(sparms->text.text != (FILE *)NULL) {
3549 size_t n;
3550 int i;
3552 fseek((FILE *)sparms->text.text, 0L, 0);
3553 n = SIZEOF_20KBUF - 1;
3554 while((i = fread((void *)tmp_20k_buf, sizeof(char),
3555 n, (FILE *)sparms->text.text)) != 0) {
3556 tmp_20k_buf[i] = '\0';
3557 print_text(tmp_20k_buf);
3561 default :
3562 break;
3565 close_printer();
3566 return(0);
3570 /*----------------------------------------------------------------------
3571 Search text being viewed (help or message)
3573 Args: q_line -- The screen line to prompt for search string on
3574 start_line -- Line number in text to begin search on
3575 start_index -- Where to begin search at in first line of text
3576 cursor_pos -- position of cursor is returned to caller here
3577 (Actually, this isn't really the position of the
3578 cursor because we don't know where we are on the
3579 screen. So row is set to the line number and col
3580 is set to the right column.)
3581 offset_in_line -- Offset where match was found.
3583 Result: returns line number string was found on
3584 -1 for cancel
3585 -2 if not found
3586 -3 if only match is at start_index - 1
3587 -4 if search to first line
3588 -5 if search to last line
3589 ---*/
3591 search_text(int q_line, long int start_line, int start_index, char **report,
3592 Pos *cursor_pos, int *offset_in_line)
3594 char prompt[MAX_SEARCH+50], nsearch_string[MAX_SEARCH+1], *p;
3595 HelpType help;
3596 int rc, flags;
3597 static HISTORY_S *history = NULL;
3598 char search_string[MAX_SEARCH+1];
3599 static ESCKEY_S word_search_key[] = { { 0, 0, "", "" },
3600 {ctrl('Y'), 10, "^Y", N_("First Line")},
3601 {ctrl('V'), 11, "^V", N_("Last Line")},
3602 {KEY_UP, 30, "", ""},
3603 {KEY_DOWN, 31, "", ""},
3604 {-1, 0, NULL, NULL}
3606 #define KU_ST (3) /* index of KEY_UP */
3608 init_hist(&history, HISTSIZE);
3611 * Put the last one used in the default search_string,
3612 * not in nsearch_string.
3614 search_string[0] = '\0';
3615 if((p = get_prev_hist(history, "", 0, NULL)) != NULL){
3616 strncpy(search_string, p, sizeof(search_string));
3617 search_string[sizeof(search_string)-1] = '\0';
3620 snprintf(prompt, sizeof(prompt), _("Word to search for [%s] : "), search_string);
3621 help = NO_HELP;
3622 nsearch_string[0] = '\0';
3624 while(1) {
3625 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE | OE_KEEP_TRAILING_SPACE;
3628 * 2 is really 1 because there will be one real entry and
3629 * one entry of "" because of the get_prev_hist above.
3631 if(items_in_hist(history) > 2){
3632 word_search_key[KU_ST].name = HISTORY_UP_KEYNAME;
3633 word_search_key[KU_ST].label = HISTORY_KEYLABEL;
3634 word_search_key[KU_ST+1].name = HISTORY_DOWN_KEYNAME;
3635 word_search_key[KU_ST+1].label = HISTORY_KEYLABEL;
3637 else{
3638 word_search_key[KU_ST].name = "";
3639 word_search_key[KU_ST].label = "";
3640 word_search_key[KU_ST+1].name = "";
3641 word_search_key[KU_ST+1].label = "";
3644 rc = optionally_enter(nsearch_string, q_line, 0, sizeof(nsearch_string),
3645 prompt, word_search_key, help, &flags);
3647 if(rc == 3) {
3648 help = help == NO_HELP ? h_oe_searchview : NO_HELP;
3649 continue;
3651 else if(rc == 10){
3652 if(report)
3653 *report = _("Searched to First Line.");
3655 return(-4);
3657 else if(rc == 11){
3658 if(report)
3659 *report = _("Searched to Last Line.");
3661 return(-5);
3663 else if(rc == 30){
3664 if((p = get_prev_hist(history, nsearch_string, 0, NULL)) != NULL){
3665 strncpy(nsearch_string, p, sizeof(nsearch_string));
3666 nsearch_string[sizeof(nsearch_string)-1] = '\0';
3668 else
3669 Writechar(BELL, 0);
3671 continue;
3673 else if(rc == 31){
3674 if((p = get_next_hist(history, nsearch_string, 0, NULL)) != NULL){
3675 strncpy(nsearch_string, p, sizeof(nsearch_string));
3676 nsearch_string[sizeof(nsearch_string)-1] = '\0';
3678 else
3679 Writechar(BELL, 0);
3681 continue;
3684 if(rc != 4){ /* 4 is redraw */
3685 save_hist(history, nsearch_string, 0, NULL);
3686 break;
3690 if(rc == 1 || (search_string[0] == '\0' && nsearch_string[0] == '\0'))
3691 return(-1);
3693 if(nsearch_string[0] != '\0'){
3694 strncpy(search_string, nsearch_string, sizeof(search_string)-1);
3695 search_string[sizeof(search_string)-1] = '\0';
3698 rc = search_scroll_text(start_line, start_index, search_string, cursor_pos,
3699 offset_in_line);
3700 return(rc);
3704 /*----------------------------------------------------------------------
3705 Update the scroll tool's titlebar
3707 Args: cur_top_line --
3708 redraw -- flag to force updating
3710 ----*/
3711 void
3712 update_scroll_titlebar(long int cur_top_line, int redraw)
3714 SCRLCTRL_S *st = scroll_state(SS_CUR);
3715 int num_display_lines = SCROLL_LINES(ps_global);
3716 long new_line = (cur_top_line + num_display_lines > st->num_lines)
3717 ? st->num_lines
3718 : cur_top_line + num_display_lines;
3719 long raw_msgno;
3720 COLOR_PAIR *returned_color = NULL;
3721 COLOR_PAIR *titlecolor = NULL;
3722 int colormatch;
3723 SEARCHSET *ss = NULL;
3725 if(st->parms->use_indexline_color
3726 && ps_global->titlebar_color_style != TBAR_COLOR_DEFAULT){
3727 raw_msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
3728 if(raw_msgno > 0L && ps_global->mail_stream
3729 && raw_msgno <= ps_global->mail_stream->nmsgs){
3730 ss = mail_newsearchset();
3731 ss->first = ss->last = (unsigned long) raw_msgno;
3734 if(ss){
3735 PAT_STATE *pstate = NULL;
3737 colormatch = get_index_line_color(ps_global->mail_stream,
3738 ss, &pstate, &returned_color);
3739 mail_free_searchset(&ss);
3742 * This is a bit tricky. If there is a colormatch but returned_color
3743 * is NULL, that means that the user explicitly wanted the
3744 * Normal color used in this index line, so that is what we
3745 * use. If no colormatch then we will use the TITLE color
3746 * instead of Normal.
3748 if(colormatch){
3749 if(returned_color)
3750 titlecolor = returned_color;
3751 else
3752 titlecolor = new_color_pair(ps_global->VAR_NORM_FORE_COLOR,
3753 ps_global->VAR_NORM_BACK_COLOR);
3756 if(titlecolor
3757 && ps_global->titlebar_color_style == TBAR_COLOR_REV_INDEXLINE){
3758 char cbuf[MAXCOLORLEN+1];
3760 strncpy(cbuf, titlecolor->fg, MAXCOLORLEN);
3761 strncpy(titlecolor->fg, titlecolor->bg, MAXCOLORLEN);
3762 strncpy(titlecolor->bg, cbuf, MAXCOLORLEN);
3766 /* Did the color change? */
3767 if((!titlecolor && st->parms->bar.color)
3769 (titlecolor && !st->parms->bar.color)
3771 (titlecolor && st->parms->bar.color
3772 && (strcmp(titlecolor->fg, st->parms->bar.color->fg)
3773 || strcmp(titlecolor->bg, st->parms->bar.color->bg)))){
3775 redraw++;
3776 if(st->parms->bar.color)
3777 free_color_pair(&st->parms->bar.color);
3779 st->parms->bar.color = titlecolor;
3780 titlecolor = NULL;
3783 if(titlecolor)
3784 free_color_pair(&titlecolor);
3788 if(redraw){
3789 set_titlebar(st->parms->bar.title, ps_global->mail_stream,
3790 ps_global->context_current, ps_global->cur_folder,
3791 ps_global->msgmap, 1, st->parms->bar.style,
3792 new_line, st->num_lines, st->parms->bar.color);
3793 ps_global->mangled_header = 0;
3795 else if(st->parms->bar.style == TextPercent)
3796 update_titlebar_lpercent(new_line);
3797 else
3798 update_titlebar_percent(new_line);
3802 /*----------------------------------------------------------------------
3803 manager of global (to this module, anyway) scroll state structures
3806 ----*/
3807 SCRLCTRL_S *
3808 scroll_state(int func)
3810 struct scrollstack {
3811 SCRLCTRL_S s;
3812 struct scrollstack *prev;
3813 } *s;
3814 static struct scrollstack *stack = NULL;
3816 switch(func){
3817 case SS_CUR: /* no op */
3818 break;
3819 case SS_NEW:
3820 s = (struct scrollstack *)fs_get(sizeof(struct scrollstack));
3821 memset((void *)s, 0, sizeof(struct scrollstack));
3822 s->prev = stack;
3823 stack = s;
3824 break;
3825 case SS_FREE:
3826 if(stack){
3827 s = stack->prev;
3828 fs_give((void **)&stack);
3829 stack = s;
3831 break;
3832 default: /* BUG: should complain */
3833 break;
3836 return(stack ? &stack->s : NULL);
3840 /*----------------------------------------------------------------------
3841 Save all the data for scrolling text and paint the screen
3844 ----*/
3845 void
3846 set_scroll_text(SCROLL_S *sparms, long int current_line, SCRLCTRL_S *st)
3848 /* save all the stuff for possible asynchronous redraws */
3849 st->parms = sparms;
3850 st->top_text_line = current_line;
3851 st->screen.start_line = SCROLL_LINES_ABOVE(ps_global);
3852 st->screen.other_lines = SCROLL_LINES_ABOVE(ps_global)
3853 + SCROLL_LINES_BELOW(ps_global);
3854 st->screen.width = -1; /* Force text formatting calculation */
3858 /*----------------------------------------------------------------------
3859 Redraw the text on the screen, possibly reformatting if necessary
3861 Args None
3863 ----*/
3864 void
3865 redraw_scroll_text(void)
3867 int i, offset;
3868 SCRLCTRL_S *st = scroll_state(SS_CUR);
3870 format_scroll_text();
3872 offset = (st->parms->text.src == FileStar) ? 0 : st->top_text_line;
3874 #ifdef _WINDOWS
3875 mswin_beginupdate();
3876 #endif
3877 /*---- Actually display the text on the screen ------*/
3878 for(i = 0; i < st->screen.length; i++){
3879 ClearLine(i + st->screen.start_line);
3880 if((offset + i) < st->num_lines)
3881 PutLine0n8b(i + st->screen.start_line, 0, st->text_lines[offset + i],
3882 st->line_lengths[offset + i], st->parms->text.handles);
3886 fflush(stdout);
3887 #ifdef _WINDOWS
3888 mswin_endupdate();
3889 #endif
3893 /*----------------------------------------------------------------------
3894 Free memory used as scrolling buffers for text on disk. Also mark
3895 text_lines as available
3896 ----*/
3897 void
3898 zero_scroll_text(void)
3900 SCRLCTRL_S *st = scroll_state(SS_CUR);
3901 register int i;
3903 for(i = 0; i < st->lines_allocated; i++)
3904 if(st->parms->text.src == FileStar && st->text_lines[i])
3905 fs_give((void **)&st->text_lines[i]);
3906 else
3907 st->text_lines[i] = NULL;
3909 if(st->parms->text.src == FileStar && st->findex != NULL){
3910 fclose(st->findex);
3911 st->findex = NULL;
3912 if(st->fname){
3913 our_unlink(st->fname);
3914 fs_give((void **)&st->fname);
3918 if(st->text_lines)
3919 fs_give((void **)&st->text_lines);
3921 if(st->line_lengths)
3922 fs_give((void **) &st->line_lengths);
3926 /*----------------------------------------------------------------------
3928 Always format at least 20 chars wide. Wrapping lines would be crazy for
3929 screen widths of 1-20 characters
3930 ----*/
3931 void
3932 format_scroll_text(void)
3934 int i;
3935 char *p, **pp;
3936 SCRLCTRL_S *st = scroll_state(SS_CUR);
3937 register short *ll;
3938 register char **tl, **tl_end;
3940 if(!st || (st->screen.width == (i = ps_global->ttyo->screen_cols)
3941 && st->screen.length == PGSIZE(st)))
3942 return;
3944 st->screen.width = MAX(20, i);
3945 st->screen.length = PGSIZE(st);
3947 if(st->lines_allocated == 0) {
3948 st->lines_allocated = TYPICAL_BIG_MESSAGE_LINES;
3949 st->text_lines = (char **)fs_get(st->lines_allocated *sizeof(char *));
3950 memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
3951 st->line_lengths = (short *)fs_get(st->lines_allocated *sizeof(short));
3954 tl = st->text_lines;
3955 ll = st->line_lengths;
3956 tl_end = &st->text_lines[st->lines_allocated];
3958 if(st->parms->text.src == CharStarStar) {
3959 /*---- original text is already list of lines -----*/
3960 /* The text could be wrapped nicely for narrow screens; for now
3961 it will get truncated as it is displayed */
3962 for(pp = (char **)st->parms->text.text; *pp != NULL;) {
3963 *tl++ = *pp++;
3964 *ll++ = st->screen.width;
3965 if(tl >= tl_end) {
3966 i = tl - st->text_lines;
3967 st->lines_allocated *= 2;
3968 fs_resize((void **)&st->text_lines,
3969 st->lines_allocated * sizeof(char *));
3970 fs_resize((void **)&st->line_lengths,
3971 st->lines_allocated*sizeof(short));
3972 tl = &st->text_lines[i];
3973 ll = &st->line_lengths[i];
3974 tl_end = &st->text_lines[st->lines_allocated];
3978 st->num_lines = tl - st->text_lines;
3980 else if (st->parms->text.src == CharStar) {
3981 /*------ Format the plain text ------*/
3982 for(p = (char *)st->parms->text.text; *p; ) {
3983 *tl = p;
3985 for(; *p && !(*p == RETURN || *p == LINE_FEED); p++)
3988 *ll = p - *tl;
3989 ll++; tl++;
3990 if(tl >= tl_end) {
3991 i = tl - st->text_lines;
3992 st->lines_allocated *= 2;
3993 fs_resize((void **)&st->text_lines,
3994 st->lines_allocated * sizeof(char *));
3995 fs_resize((void **)&st->line_lengths,
3996 st->lines_allocated*sizeof(short));
3997 tl = &st->text_lines[i];
3998 ll = &st->line_lengths[i];
3999 tl_end = &st->text_lines[st->lines_allocated];
4002 if(*p == '\r' && *(p+1) == '\n')
4003 p += 2;
4004 else if(*p == '\n' || *p == '\r')
4005 p++;
4008 st->num_lines = tl - st->text_lines;
4010 else {
4011 /*------ Display text is in a file --------*/
4014 * This is pretty much only useful under DOS where we can't fit
4015 * all of big messages in core at once. This scheme makes
4016 * some simplifying assumptions:
4017 * 1. Lines are on disk just the way we'll display them. That
4018 * is, line breaks and such are left to the function that
4019 * writes the disk file to catch and fix.
4020 * 2. We get away with this mainly because the DOS display isn't
4021 * going to be resized out from under us.
4023 * The idea is to use the already alloc'd array of char * as a
4024 * buffer for sections of what's on disk. We'll set up the first
4025 * few lines here, and read new ones in as needed in
4026 * scroll_scroll_text().
4028 * but first, make sure there are enough buffer lines allocated
4029 * to serve as a place to hold lines from the file.
4031 * Actually, this is also used under windows so the display will
4032 * be resized out from under us. So I changed the following
4033 * to always
4034 * 1. free old text_lines, which may have been allocated
4035 * for a narrow screen.
4036 * 2. insure we have enough text_lines
4037 * 3. reallocate all text_lines that are needed.
4038 * (tom unger 10/26/94)
4041 /* free old text lines, which may be too short. */
4042 for(i = 0; i < st->lines_allocated; i++)
4043 if(st->text_lines[i]) /* clear alloc'd lines */
4044 fs_give((void **)&st->text_lines[i]);
4046 /* Insure we have enough text lines. */
4047 if(st->lines_allocated < (2 * PGSIZE(st)) + 1){
4048 st->lines_allocated = (2 * PGSIZE(st)) + 1; /* resize */
4050 fs_resize((void **)&st->text_lines,
4051 st->lines_allocated * sizeof(char *));
4052 memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
4053 fs_resize((void **)&st->line_lengths,
4054 st->lines_allocated*sizeof(short));
4057 /* reallocate all text lines that are needed. */
4058 for(i = 0; i <= PGSIZE(st); i++)
4059 if(st->text_lines[i] == NULL)
4060 st->text_lines[i] = (char *)fs_get((st->screen.width + 1)
4061 * sizeof(char));
4063 tl = &st->text_lines[i];
4065 st->num_lines = make_file_index();
4067 ScrollFile(st->top_text_line); /* then load them up */
4071 * Efficiency hack. If there are handles, fill in their
4072 * line number field for later...
4074 if(st->parms->text.handles){
4075 long line;
4076 int i, col, n, key;
4077 HANDLE_S *h;
4079 for(line = 0; line < st->num_lines; line++)
4080 for(i = 0, col = 0; i < st->line_lengths[line];)
4081 switch(st->text_lines[line][i]){
4082 case TAG_EMBED:
4083 i++;
4084 switch((i < st->line_lengths[line]) ? st->text_lines[line][i]
4085 : 0){
4086 case TAG_HANDLE:
4087 for(key = 0, n = st->text_lines[line][++i]; n > 0; n--)
4088 key = (key * 10) + (st->text_lines[line][++i] - '0');
4090 i++;
4091 for(h = st->parms->text.handles; h; h = h->next)
4092 if(h->key == key){
4093 scroll_handle_set_loc(&h->loc, line, col);
4094 break;
4097 if(!h) /* anything behind us? */
4098 for(h = st->parms->text.handles->prev; h; h = h->prev)
4099 if(h->key == key){
4100 scroll_handle_set_loc(&h->loc, line, col);
4101 break;
4104 break;
4106 case TAG_FGCOLOR :
4107 case TAG_BGCOLOR :
4108 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
4109 break;
4111 case TAG_INVON:
4112 case TAG_INVOFF:
4113 case TAG_BOLDON:
4114 case TAG_BOLDOFF:
4115 case TAG_ULINEON:
4116 case TAG_ULINEOFF:
4117 i++;
4118 break;
4120 default: /* literal embed char */
4121 break;
4124 break;
4126 case TAB:
4127 i++;
4128 while(((++col) & 0x07) != 0) /* add tab's spaces */
4131 break;
4133 default:
4134 col += width_at_this_position((unsigned char*) &st->text_lines[line][i],
4135 st->line_lengths[line] - i);
4136 i++; /* character count */
4137 break;
4141 #ifdef _WINDOWS
4142 scroll_setrange (st->screen.length, st->num_lines);
4143 #endif
4145 *tl = NULL;
4150 * ScrollFile - scroll text into the st struct file making sure 'line'
4151 * of the file is the one first in the text_lines buffer.
4153 * NOTE: talk about massive potential for tuning...
4154 * Goes without saying this is still under constuction
4156 void
4157 ScrollFile(long int line)
4159 SCRLCTRL_S *st = scroll_state(SS_CUR);
4160 SCRLFILE_S sf;
4161 register int i;
4163 if(line <= 0){ /* reset and load first couple of pages */
4164 fseek((FILE *) st->parms->text.text, 0L, 0);
4165 line = 0L;
4168 if(!st->text_lines)
4169 return;
4171 for(i = 0; i < PGSIZE(st); i++){
4172 /*** do stuff to get the file pointer into the right place ***/
4174 * BOGUS: this is painfully crude right now, but I just want to get
4175 * it going.
4177 * possibly in the near furture, an array of indexes into the
4178 * file that are the offset for the beginning of each line will
4179 * speed things up. Of course, this
4180 * will have limits, so maybe a disk file that is an array
4181 * of indexes is the answer.
4183 if(fseek(st->findex, (size_t)(line++) * sizeof(SCRLFILE_S), 0) < 0
4184 || fread(&sf, sizeof(SCRLFILE_S), (size_t)1, st->findex) != 1
4185 || fseek((FILE *) st->parms->text.text, sf.offset, 0) < 0
4186 || !st->text_lines[i]
4187 || (sf.len && !fgets(st->text_lines[i], sf.len + 1,
4188 (FILE *) st->parms->text.text)))
4189 break;
4191 st->line_lengths[i] = sf.len;
4194 for(; i < PGSIZE(st); i++)
4195 if(st->text_lines[i]){ /* blank out any unused lines */
4196 *st->text_lines[i] = '\0';
4197 st->line_lengths[i] = 0;
4203 * make_file_index - do a single pass over the file containing the text
4204 * to display, recording line lengths and offsets.
4205 * NOTE: This is never really to be used on a real OS with virtual
4206 * memory. This is the whole reason st->findex exists. Don't
4207 * want to waste precious memory on a stupid array that could
4208 * be very large.
4210 long
4211 make_file_index(void)
4213 SCRLCTRL_S *st = scroll_state(SS_CUR);
4214 SCRLFILE_S sf;
4215 long l = 0L;
4216 int state = 0;
4218 if(!st->findex){
4219 if(!st->fname)
4220 st->fname = temp_nam(NULL, "pi");
4222 if(!st->fname || (st->findex = our_fopen(st->fname,"w+b")) == NULL){
4223 if(st->fname){
4224 our_unlink(st->fname);
4225 fs_give((void **)&st->fname);
4228 return(0);
4231 else
4232 fseek(st->findex, 0L, 0);
4234 fseek((FILE *)st->parms->text.text, 0L, 0);
4236 while(1){
4237 sf.len = st->screen.width + 1;
4238 if(scroll_file_line((FILE *) st->parms->text.text,
4239 tmp_20k_buf, &sf, &state)){
4240 fwrite((void *) &sf, sizeof(SCRLFILE_S), (size_t)1, st->findex);
4241 l++;
4243 else
4244 break;
4247 fseek((FILE *)st->parms->text.text, 0L, 0);
4249 return(l);
4253 /*----------------------------------------------------------------------
4254 Get the next line to scroll from the given file
4256 ----*/
4257 char *
4258 scroll_file_line(FILE *fp, char *buf, SCRLFILE_S *sfp, int *wrapt)
4260 register char *s = NULL;
4262 while(1){
4263 if(!s){
4264 sfp->offset = ftell(fp);
4265 if(!(s = fgets(buf, sfp->len, fp)))
4266 return(NULL); /* can't grab a line? */
4269 if(!*s){
4270 *wrapt = 1; /* remember; that we wrapped */
4271 break;
4273 else if(*s == NEWLINE[0] && (!NEWLINE[1] || *(s+1) == NEWLINE[1])){
4274 int empty = (*wrapt && s == buf);
4276 *wrapt = 0; /* turn off wrapped state */
4277 if(empty)
4278 s = NULL; /* get a new line */
4279 else
4280 break; /* done! */
4282 else
4283 s++;
4286 sfp->len = s - buf;
4287 return(buf);
4291 /*----------------------------------------------------------------------
4292 Scroll the text on the screen
4294 Args: new_top_line -- The line to be displayed on top of the screen
4295 redraw -- Flag to force a redraw even if nothing changed
4297 Returns: resulting top line
4298 Note: the returned line number may be less than new_top_line if
4299 reformatting caused the total line count to change.
4301 ----*/
4302 long
4303 scroll_scroll_text(long int new_top_line, HANDLE_S *handle, int redraw)
4305 SCRLCTRL_S *st = scroll_state(SS_CUR);
4306 int num_display_lines, l, top;
4307 POSLIST_S *lp, *lp2;
4309 /* When this is true, we're still on the same page of the display. */
4310 if(st->top_text_line == new_top_line && !redraw){
4311 /* handle changed, so hilite the new handle and unhilite the old */
4312 if(handle && handle != st->parms->text.handles){
4313 top = st->screen.start_line - new_top_line;
4314 /* hilite the new one */
4315 if(!scroll_handle_obscured(handle))
4316 for(lp = handle->loc; lp; lp = lp->next)
4317 if((l = lp->where.row) >= st->top_text_line
4318 && l < st->top_text_line + st->screen.length){
4319 if(st->parms->text.src == FileStar)
4320 l -= new_top_line;
4322 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4323 st->line_lengths[l], handle);
4326 /* unhilite the old one */
4327 if(!scroll_handle_obscured(st->parms->text.handles))
4328 for(lp = st->parms->text.handles->loc; lp; lp = lp->next)
4329 if((l = lp->where.row) >= st->top_text_line
4330 && l < st->top_text_line + st->screen.length){
4331 for(lp2 = handle->loc; lp2; lp2 = lp2->next)
4332 if(l == lp2->where.row)
4333 break;
4335 if(!lp2){
4336 if(st->parms->text.src == FileStar)
4337 l -= new_top_line;
4339 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4340 st->line_lengths[l], handle);
4344 st->parms->text.handles = handle; /* update current */
4347 return(new_top_line);
4350 num_display_lines = PGSIZE(st);
4352 format_scroll_text();
4354 if(st->top_text_line >= st->num_lines) /* don't pop line count */
4355 new_top_line = st->top_text_line = MAX(st->num_lines - 1, 0);
4357 if(st->parms->text.src == FileStar)
4358 ScrollFile(new_top_line); /* set up new st->text_lines */
4360 #ifdef _WINDOWS
4361 scroll_setrange (st->screen.length, st->num_lines);
4362 scroll_setpos (new_top_line);
4363 #endif
4365 /* ---
4366 Check out the scrolling situation. If we want to scroll, but BeginScroll
4367 says we can't then repaint, + 10 is so we repaint most of the time.
4368 ----*/
4369 if(redraw ||
4370 (st->top_text_line - new_top_line + 10 >= num_display_lines ||
4371 new_top_line - st->top_text_line + 10 >= num_display_lines) ||
4372 BeginScroll(st->screen.start_line,
4373 st->screen.start_line + num_display_lines - 1) != 0) {
4374 /* Too much text to scroll, or can't scroll -- just repaint */
4376 if(handle)
4377 st->parms->text.handles = handle;
4379 st->top_text_line = new_top_line;
4380 redraw_scroll_text();
4382 else{
4384 * We're going to scroll the screen, but first we have to make sure
4385 * the old hilited handles are unhilited if they are going to remain
4386 * on the screen.
4388 top = st->screen.start_line - st->top_text_line;
4389 if(handle && handle != st->parms->text.handles
4390 && st->parms->text.handles
4391 && !scroll_handle_obscured(st->parms->text.handles))
4392 for(lp = st->parms->text.handles->loc; lp; lp = lp->next)
4393 if((l = lp->where.row) >= MAX(st->top_text_line,new_top_line)
4394 && l < MIN(st->top_text_line,new_top_line) + st->screen.length){
4395 if(st->parms->text.src == FileStar)
4396 l -= new_top_line;
4398 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4399 st->line_lengths[l], handle);
4402 if(new_top_line > st->top_text_line){
4403 /*------ scroll down ------*/
4404 while(new_top_line > st->top_text_line) {
4405 ScrollRegion(1);
4407 l = (st->parms->text.src == FileStar)
4408 ? num_display_lines - (new_top_line - st->top_text_line)
4409 : st->top_text_line + num_display_lines;
4411 if(l < st->num_lines){
4412 PutLine0n8b(st->screen.start_line + num_display_lines - 1,
4413 0, st->text_lines[l], st->line_lengths[l],
4414 handle ? handle : st->parms->text.handles);
4416 * We clear to the end of line in the right background
4417 * color. If the line was exactly the width of the screen
4418 * then PutLine0n8b will have left _col and _row moved to
4419 * the start of the next row. We don't need or want to clear
4420 * that next row.
4422 if(pico_usingcolor()
4423 && (st->line_lengths[l] < ps_global->ttyo->screen_cols
4424 || visible_linelen(l) < ps_global->ttyo->screen_cols))
4425 CleartoEOLN();
4428 st->top_text_line++;
4431 else{
4432 /*------ scroll up -----*/
4433 while(new_top_line < st->top_text_line) {
4434 ScrollRegion(-1);
4436 st->top_text_line--;
4437 l = (st->parms->text.src == FileStar)
4438 ? st->top_text_line - new_top_line
4439 : st->top_text_line;
4440 PutLine0n8b(st->screen.start_line, 0, st->text_lines[l],
4441 st->line_lengths[l],
4442 handle ? handle : st->parms->text.handles);
4444 * We clear to the end of line in the right background
4445 * color. If the line was exactly the width of the screen
4446 * then PutLine0n8b will have left _col and _row moved to
4447 * the start of the next row. We don't need or want to clear
4448 * that next row.
4450 if(pico_usingcolor()
4451 && (st->line_lengths[l] < ps_global->ttyo->screen_cols
4452 || visible_linelen(l) < ps_global->ttyo->screen_cols))
4453 CleartoEOLN();
4457 EndScroll();
4459 if(handle && handle != st->parms->text.handles){
4460 POSLIST_S *lp;
4462 for(lp = handle->loc; lp; lp = lp->next)
4463 if(lp->where.row >= st->top_text_line
4464 && lp->where.row < st->top_text_line + st->screen.length){
4465 PutLine0n8b(st->screen.start_line
4466 + (lp->where.row - st->top_text_line),
4467 0, st->text_lines[lp->where.row],
4468 st->line_lengths[lp->where.row],
4469 handle);
4473 st->parms->text.handles = handle;
4476 fflush(stdout);
4479 return(new_top_line);
4483 /*---------------------------------------------------------------------
4484 Edit individual char in text so that the entire text doesn't need
4485 to be completely reformatted.
4487 Returns 0 if there were no errors, 1 if we would like the entire
4488 text to be reformatted.
4489 ----*/
4491 ng_scroll_edit(CONTEXT_S *context, int index)
4493 SCRLCTRL_S *st = scroll_state(SS_CUR);
4494 char *ngp, tmp[MAILTMPLEN+10];
4495 int len;
4496 FOLDER_S *f;
4498 if (!(f = folder_entry(index, FOLDERS(context))))
4499 return 1;
4500 if(f->subscribed)
4501 return 0; /* nothing in scroll needs to be changed */
4502 tmp[0] = TAG_HANDLE;
4503 snprintf(tmp+2, sizeof(tmp)-2, "%d", st->parms->text.handles->key);
4504 tmp[sizeof(tmp)-1] = '\0';
4505 tmp[1] = len = strlen(tmp+2);
4506 snprintf(tmp+len+2, sizeof(tmp)-(len+2), "%s ", f->selected ? "[ ]" : "[X]");
4507 tmp[sizeof(tmp)-1] = '\0';
4508 snprintf(tmp+len+6, sizeof(tmp)-(len+6), "%.*s", MAILTMPLEN, f->name);
4509 tmp[sizeof(tmp)-1] = '\0';
4511 ngp = *(st->text_lines);
4513 ngp = strstr(ngp, tmp);
4515 if(!ngp) return 1;
4516 ngp += 3+len;
4518 /* assumption that text is of form "[ ] xxx.xxx" */
4520 if(ngp){
4521 if(*ngp == 'X'){
4522 *ngp = ' ';
4523 return 0;
4525 else if (*ngp == ' '){
4526 *ngp = 'X';
4527 return 0;
4530 return 1;
4534 /*---------------------------------------------------------------------
4535 Similar to ng_scroll_edit, but this is the more general case of
4536 selecting a folder, as opposed to selecting a newsgroup for
4537 subscription while in listmode.
4539 Returns 0 if there were no errors, 1 if we would like the entire
4540 text to be reformatted.
4541 ----*/
4543 folder_select_update(CONTEXT_S *context, int index)
4545 SCRLCTRL_S *st = scroll_state(SS_CUR);
4546 FOLDER_S *f;
4547 char *ngp, tmp[MAILTMPLEN+10];
4548 int len, total, fnum, num_sel = 0;
4550 if (!(f = folder_entry(index, FOLDERS(context))))
4551 return 1;
4552 ngp = *(st->text_lines);
4554 total = folder_total(FOLDERS(context));
4556 for (fnum = 0; num_sel < 2 && fnum < total; fnum++)
4557 if(folder_entry(fnum, FOLDERS(context))->selected)
4558 num_sel++;
4559 if(!num_sel || (f->selected && num_sel == 1))
4560 return 1; /* need to reformat the whole thing */
4562 tmp[0] = TAG_HANDLE;
4563 snprintf(tmp+2, sizeof(tmp)-2, "%d", st->parms->text.handles->key);
4564 tmp[sizeof(tmp)-1] = '\0';
4565 tmp[1] = len = strlen(tmp+2);
4567 ngp = strstr(ngp, tmp);
4568 if(!ngp) return 1;
4570 if(F_ON(F_SELECTED_SHOWN_BOLD, ps_global)){
4571 ngp += 2 + len;
4572 while(*ngp && ngp[0] != TAG_EMBED
4573 && ngp[1] != (f->selected ? TAG_BOLDOFF : TAG_BOLDON)
4574 && *ngp != *(f->name))
4575 ngp++;
4577 if (!(*ngp) || (*ngp == *(f->name)))
4578 return 1;
4579 else {
4580 ngp++;
4581 *ngp = (f->selected ? TAG_BOLDON : TAG_BOLDOFF);
4582 return 0;
4585 else{
4586 while(*ngp != ' ' && *ngp != *(f->name) && *ngp)
4587 ngp++;
4588 if(!(*ngp) || (*ngp == *(f->name)))
4589 return 1;
4590 else {
4591 ngp++;
4592 *ngp = f->selected ? 'X' : ' ';
4593 return 0;
4599 /*---------------------------------------------------------------------
4600 We gotta go through all of the formatted text and add "[ ] " in the right
4601 place. If we don't do this, we must completely reformat the whole text,
4602 which could take a very long time.
4604 Return 1 if we encountered some sort of error and we want to reformat the
4605 whole text, return 0 if everything went as planned.
4607 ASSUMPTION: for this to work, we assume that there are only total
4608 number of handles, numbered 1 through total.
4609 ----*/
4611 scroll_add_listmode(CONTEXT_S *context, int total)
4613 SCRLCTRL_S *st = scroll_state(SS_CUR);
4614 long i;
4615 char *ngp, *ngname, handle_str[MAILTMPLEN];
4616 HANDLE_S *h;
4619 ngp = *(st->text_lines);
4620 h = st->parms->text.handles;
4622 while(h && h->key != 1 && h->prev)
4623 h = h->prev;
4624 if (!h) return 1;
4625 handle_str[0] = TAG_EMBED;
4626 handle_str[1] = TAG_HANDLE;
4627 for(i = 1; i <= total && h; i++, h = h->next){
4628 snprintf(handle_str+3, sizeof(handle_str)-3, "%d", h->key);
4629 handle_str[sizeof(handle_str)-1] = '\0';
4630 handle_str[2] = strlen(handle_str+3);
4631 ngp = strstr(ngp, handle_str);
4632 if(!ngp){
4633 ngp = *(st->text_lines);
4634 if (!ngp)
4635 return 1;
4637 ngname = ngp + strlen(handle_str);
4638 while (strncmp(ngp, " ", 4) && !(*ngp == '\n')
4639 && !(ngp == *(st->text_lines)))
4640 ngp--;
4641 if (strncmp(ngp, " ", 4))
4642 return 1;
4643 while(ngp+4 != ngname && *ngp){
4644 ngp[0] = ngp[4];
4645 ngp++;
4648 if(folder_entry(h->h.f.index, FOLDERS(context))->subscribed){
4649 ngp[0] = 'S';
4650 ngp[1] = 'U';
4651 ngp[2] = 'B';
4653 else{
4654 ngp[0] = '[';
4655 ngp[1] = ' ';
4656 ngp[2] = ']';
4658 ngp[3] = ' ';
4661 return 0;
4666 /*----------------------------------------------------------------------
4667 Search the set scrolling text
4669 Args: start_line -- line to start searching on
4670 start_index -- column to start searching at in first line
4671 word -- string to search for
4672 cursor_pos -- position of cursor is returned to caller here
4673 (Actually, this isn't really the position of the
4674 cursor because we don't know where we are on the
4675 screen. So row is set to the line number and col
4676 is set to the right column.)
4677 offset_in_line -- Offset where match was found.
4679 Returns: the line the word was found on, or -2 if it wasn't found, or
4680 -3 if the only match is at column start_index - 1.
4682 ----*/
4684 search_scroll_text(long int start_line, int start_index, char *word,
4685 Pos *cursor_pos, int *offset_in_line)
4687 SCRLCTRL_S *st = scroll_state(SS_CUR);
4688 char *wh;
4689 long l, offset, dlines;
4690 #define SROW(N) ((N) - offset)
4691 #define SLINE(N) st->text_lines[SROW(N)]
4692 #define SLEN(N) st->line_lengths[SROW(N)]
4694 dlines = PGSIZE(st);
4695 offset = (st->parms->text.src == FileStar) ? st->top_text_line : 0;
4697 if(start_line < st->num_lines){
4698 /* search first line starting at position start_index in */
4699 if((wh = search_scroll_line(SLINE(start_line) + start_index,
4700 word,
4701 SLEN(start_line) - start_index,
4702 st->parms->text.handles != NULL)) != NULL){
4703 cursor_pos->row = start_line;
4704 cursor_pos->col = scroll_handle_column(SROW(start_line),
4705 *offset_in_line = wh - SLINE(start_line));
4706 return(start_line);
4709 if(st->parms->text.src == FileStar)
4710 offset++;
4712 for(l = start_line + 1; l < st->num_lines; l++) {
4713 if(st->parms->text.src == FileStar && l > offset + dlines)
4714 ScrollFile(offset += dlines);
4716 if((wh = search_scroll_line(SLINE(l), word, SLEN(l),
4717 st->parms->text.handles != NULL)) != NULL){
4718 cursor_pos->row = l;
4719 cursor_pos->col = scroll_handle_column(SROW(l),
4720 *offset_in_line = wh - SLINE(l));
4721 return(l);
4725 else
4726 start_line = st->num_lines;
4728 if(st->parms->text.src == FileStar) /* wrap offset */
4729 ScrollFile(offset = 0);
4731 for(l = 0; l < start_line; l++) {
4732 if(st->parms->text.src == FileStar && l > offset + dlines)
4733 ScrollFile(offset += dlines);
4735 if((wh = search_scroll_line(SLINE(l), word, SLEN(l),
4736 st->parms->text.handles != NULL)) != NULL){
4737 cursor_pos->row = l;
4738 cursor_pos->col = scroll_handle_column(SROW(l),
4739 *offset_in_line = wh - SLINE(l));
4740 return(l);
4744 /* search in current line */
4745 if(start_line < st->num_lines
4746 && (wh = search_scroll_line(SLINE(start_line), word,
4747 start_index + strlen(word) - 2,
4748 st->parms->text.handles != NULL)) != NULL){
4749 cursor_pos->row = start_line;
4750 cursor_pos->col = scroll_handle_column(SROW(start_line),
4751 *offset_in_line = wh - SLINE(start_line));
4753 return(start_line);
4756 /* see if the only match is a repeat */
4757 if(start_index > 0 && start_line < st->num_lines
4758 && (wh = search_scroll_line(
4759 SLINE(start_line) + start_index - 1,
4760 word, strlen(word),
4761 st->parms->text.handles != NULL)) != NULL){
4762 cursor_pos->row = start_line;
4763 cursor_pos->col = scroll_handle_column(SROW(start_line),
4764 *offset_in_line = wh - SLINE(start_line));
4765 return(-3);
4768 return(-2);
4772 /*----------------------------------------------------------------------
4773 Search one line of scroll text for given string
4775 Args: haystack -- The string to search in, the larger string
4776 needle -- The string to search for, the smaller string
4777 n -- The max number of chars in haystack to search
4779 Search for first occurrence of needle in the haystack, and return a pointer
4780 into the string haystack when it is found. The search is case independent.
4781 ----*/
4782 char *
4783 search_scroll_line(char *haystack, char *needle, int n, int handles)
4785 char *return_ptr = NULL, *found_it = NULL;
4786 char *haystack_copy, *p, *free_this = NULL, *end;
4787 char buf[1000];
4788 int state = 0, i = 0;
4790 if(n > 0 && haystack){
4791 if(n < sizeof(buf))
4792 haystack_copy = buf;
4793 else
4794 haystack_copy = free_this = (char *) fs_get((n+1) * sizeof(char));
4796 strncpy(haystack_copy, haystack, n);
4797 haystack_copy[n] = '\0';
4800 * We don't want to match text inside embedded tags.
4801 * Replace embedded octets with nulls and convert
4802 * uppercase ascii to lowercase. We should also do
4803 * some sort of canonicalization of UTF-8 but that
4804 * sounds daunting.
4806 for(i = n, p = haystack_copy; i-- > 0 && *p; p++){
4807 if(handles)
4808 switch(state){
4809 case 0 :
4810 if(*p == TAG_EMBED){
4811 *p = '\0';
4812 state = -1;
4813 continue;
4815 else{
4816 /* lower case just ascii chars */
4817 if(!(*p & 0x80) && isupper(*p))
4818 *p = tolower(*p);
4821 break;
4823 case -1 :
4824 state = (*p == TAG_HANDLE)
4825 ? -2
4826 : (*p == TAG_FGCOLOR || *p == TAG_BGCOLOR) ? RGBLEN : 0;
4827 *p = '\0';
4828 continue;
4830 case -2 :
4831 state = *p; /* length of handle's key */
4832 *p = '\0';
4833 continue;
4835 default :
4836 state--;
4837 *p = '\0';
4838 continue;
4843 * The haystack_copy string now looks like
4845 * "chars\0\0\0\0\0\0chars...\0\0\0chars... \0"
4847 * with that final \0 at haystack_copy[n].
4848 * Search each piece one at a time.
4851 end = haystack_copy + n;
4852 p = haystack_copy;
4854 while(p < end && !return_ptr){
4856 /* skip nulls */
4857 while(*p == '\0' && p < end)
4858 p++;
4860 if(*p != '\0')
4861 found_it = srchstr(p, needle);
4863 if(found_it){
4864 /* found it, make result relative to haystack */
4865 return_ptr = haystack + (found_it - haystack_copy);
4868 /* skip to next null */
4869 while(*p != '\0' && p < end)
4870 p++;
4873 if(free_this)
4874 fs_give((void **) &free_this);
4877 return(return_ptr);
4882 * Returns the number of columns taken up by the visible part of the line.
4883 * That is, account for handles and color changes and so forth.
4886 visible_linelen(int line)
4888 SCRLCTRL_S *st = scroll_state(SS_CUR);
4889 int i, n, len = 0;
4891 if(line < 0 || line >= st->num_lines)
4892 return(len);
4894 for(i = 0, len = 0; i < st->line_lengths[line];)
4895 switch(st->text_lines[line][i]){
4897 case TAG_EMBED:
4898 i++;
4899 switch((i < st->line_lengths[line]) ? st->text_lines[line][i] : 0){
4900 case TAG_HANDLE:
4901 i++;
4902 /* skip the length byte plus <length> more bytes */
4903 if(i < st->line_lengths[line]){
4904 n = st->text_lines[line][i];
4905 i++;
4908 if(i < st->line_lengths[line] && n > 0)
4909 i += n;
4911 break;
4913 case TAG_FGCOLOR :
4914 case TAG_BGCOLOR :
4915 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
4916 break;
4918 case TAG_INVON:
4919 case TAG_INVOFF:
4920 case TAG_BOLDON:
4921 case TAG_BOLDOFF:
4922 case TAG_ULINEON:
4923 case TAG_ULINEOFF:
4924 i++;
4925 break;
4927 case TAG_EMBED: /* escaped embed character */
4928 i++;
4929 len++;
4930 break;
4932 default: /* the embed char was literal */
4933 i++;
4934 len += 2;
4935 break;
4938 break;
4940 case TAB:
4941 i++;
4942 while(((++len) & 0x07) != 0) /* add tab's spaces */
4945 break;
4947 default:
4948 i++;
4949 len++;
4950 break;
4953 return(len);
4957 /*----------------------------------------------------------------------
4958 Display the contents of the given file (likely output from some command)
4960 Args: filename -- name of file containing output
4961 title -- title to be used for screen displaying output
4962 alt_msg -- if no output, Q this message instead of the default
4963 mode -- non-zero to display short files in status line
4964 Returns: none
4965 ----*/
4966 void
4967 display_output_file(char *filename, char *title, char *alt_msg, int mode)
4969 STORE_S *in_file = NULL, *out_store = NULL;
4971 if((in_file = so_get(FileStar, filename, READ_ACCESS|READ_FROM_LOCALE))){
4972 if(mode == DOF_BRIEF){
4973 int msg_q = 0, i = 0;
4974 char buf[512], *msg_p[4];
4975 #define MAX_SINGLE_MSG_LEN 60
4977 buf[0] = '\0';
4978 msg_p[0] = buf;
4981 * Might need to do something about CRLFs for Windows.
4983 while(so_fgets(in_file, msg_p[msg_q], sizeof(buf) - (msg_p[msg_q] - buf))
4984 && msg_q < 3
4985 && (i = strlen(msg_p[msg_q])) < MAX_SINGLE_MSG_LEN){
4986 msg_p[msg_q+1] = msg_p[msg_q]+strlen(msg_p[msg_q]);
4987 if (*(msg_p[++msg_q] - 1) == '\n')
4988 *(msg_p[msg_q] - 1) = '\0';
4991 if(msg_q < 3 && i < MAX_SINGLE_MSG_LEN){
4992 if(*msg_p[0])
4993 for(i = 0; i < msg_q; i++)
4994 q_status_message2(SM_ORDER, 3, 4,
4995 "%s Result: %s", title, msg_p[i]);
4996 else
4997 q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
4998 alt_msg
4999 ? alt_msg
5000 : " command completed with no output");
5002 so_give(&in_file);
5003 in_file = NULL;
5006 else if(mode == DOF_EMPTY){
5007 unsigned char c;
5009 if(so_readc(&c, in_file) < 1){
5010 q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
5011 alt_msg
5012 ? alt_msg
5013 : " command completed with no output");
5014 so_give(&in_file);
5015 in_file = NULL;
5020 * We need to translate the file contents from the user's locale
5021 * charset to UTF-8 for use in scrolltool. We get that translation
5022 * from the READ_FROM_LOCALE in the in_file storage object.
5023 * It would be nice to skip this step but scrolltool doesn't use
5024 * the storage object routines to read from the file, so would
5025 * skip the translation step.
5027 if(in_file){
5028 char *errstr;
5029 gf_io_t gc, pc;
5031 if(!(out_store = so_get(CharStar, NULL, EDIT_ACCESS))){
5032 so_give(&in_file);
5033 our_unlink(filename);
5034 q_status_message(SM_ORDER | SM_DING, 3, 3,
5035 _("Error allocating space."));
5036 return;
5039 so_seek(in_file, 0L, 0);
5041 gf_filter_init();
5043 gf_link_filter(gf_wrap,
5044 gf_wrap_filter_opt(ps_global->ttyo->screen_cols - 4,
5045 ps_global->ttyo->screen_cols,
5046 NULL, 0, GFW_NONE));
5048 gf_set_so_readc(&gc, in_file);
5049 gf_set_so_writec(&pc, out_store);
5051 if((errstr = gf_pipe(gc, pc)) != NULL){
5052 so_give(&in_file);
5053 so_give(&out_store);
5054 our_unlink(filename);
5055 q_status_message(SM_ORDER | SM_DING, 3, 3,
5056 _("Error allocating space."));
5057 return;
5060 gf_clear_so_writec(out_store);
5061 gf_clear_so_readc(in_file);
5062 so_give(&in_file);
5065 if(out_store){
5066 SCROLL_S sargs;
5067 char title_buf[64];
5069 snprintf(title_buf, sizeof(title_buf), "HELP FOR %s VIEW", title);
5070 title_buf[sizeof(title_buf)-1] = '\0';
5072 memset(&sargs, 0, sizeof(SCROLL_S));
5073 sargs.text.text = so_text(out_store);
5074 sargs.text.src = CharStar;
5075 sargs.text.desc = "output";
5076 sargs.bar.title = title;
5077 sargs.bar.style = TextPercent;
5078 sargs.help.text = h_simple_text_view;
5079 sargs.help.title = title_buf;
5080 scrolltool(&sargs);
5081 ps_global->mangled_screen = 1;
5082 so_give(&out_store);
5085 our_unlink(filename);
5087 else
5088 dprint((2, "Error reopening %s to get results: %s\n",
5089 filename ? filename : "?", error_description(errno)));
5093 /*--------------------------------------------------------------------
5094 Call the function that will perform the double click operation
5096 Returns: the current top line
5097 --------*/
5098 long
5099 doubleclick_handle(SCROLL_S *sparms, HANDLE_S *next_handle, int *cmd, int *done)
5101 if(sparms->mouse.clickclick){
5102 if((*sparms->mouse.clickclick)(sparms))
5103 *done = 1;
5105 else
5106 switch(scroll_handle_launch(next_handle, TRUE)){
5107 case 1 :
5108 *cmd = MC_EXIT; /* propagate */
5109 *done = 1;
5110 break;
5112 case -1 :
5113 cmd_cancelled("View");
5114 break;
5116 default :
5117 break;
5120 return(scroll_state(SS_CUR)->top_text_line);
5125 #ifdef _WINDOWS
5127 * Just a little something to simplify assignments
5129 #define VIEWPOPUP(p, c, s) { \
5130 (p)->type = tQueue; \
5131 (p)->data.val = c; \
5132 (p)->label.style = lNormal; \
5133 (p)->label.string = s; \
5141 format_message_popup(sparms, in_handle)
5142 SCROLL_S *sparms;
5143 int in_handle;
5145 MPopup fmp_menu[32];
5146 HANDLE_S *h = NULL;
5147 int i = -1, n;
5148 long rawno;
5149 MESSAGECACHE *mc;
5151 /* Reason to offer per message ops? */
5152 if(mn_get_total(ps_global->msgmap) > 0L){
5153 if(in_handle){
5154 SCRLCTRL_S *st = scroll_state(SS_CUR);
5156 switch((h = get_handle(st->parms->text.handles, in_handle))->type){
5157 case Attach :
5158 fmp_menu[++i].type = tIndex;
5159 fmp_menu[i].label.string = "View Attachment";
5160 fmp_menu[i].label.style = lNormal;
5161 fmp_menu[i].data.val = 'X'; /* for local use */
5163 if(h->h.attach
5164 && dispatch_attachment(h->h.attach) != MCD_NONE
5165 && !(h->h.attach->can_display & MCD_EXTERNAL)
5166 && h->h.attach->body
5167 && (h->h.attach->body->type == TYPETEXT
5168 || (h->h.attach->body->type == TYPEMESSAGE
5169 && h->h.attach->body->subtype
5170 && !strucmp(h->h.attach->body->subtype,"rfc822")))){
5171 fmp_menu[++i].type = tIndex;
5172 fmp_menu[i].label.string = "View Attachment in New Window";
5173 fmp_menu[i].label.style = lNormal;
5174 fmp_menu[i].data.val = 'Y'; /* for local use */
5177 fmp_menu[++i].type = tIndex;
5178 fmp_menu[i].label.style = lNormal;
5179 fmp_menu[i].data.val = 'Z'; /* for local use */
5180 msgno_exceptions(ps_global->mail_stream,
5181 mn_m2raw(ps_global->msgmap,
5182 mn_get_cur(ps_global->msgmap)),
5183 h->h.attach->number, &n, FALSE);
5184 fmp_menu[i].label.string = (n & MSG_EX_DELETE)
5185 ? "Undelete Attachment"
5186 : "Delete Attachment";
5187 break;
5189 case URL :
5190 default :
5191 fmp_menu[++i].type = tIndex;
5192 fmp_menu[i].label.string = "View Link";
5193 fmp_menu[i].label.style = lNormal;
5194 fmp_menu[i].data.val = 'X'; /* for local use */
5196 fmp_menu[++i].type = tIndex;
5197 fmp_menu[i].label.string = "Copy Link";
5198 fmp_menu[i].label.style = lNormal;
5199 fmp_menu[i].data.val = 'W'; /* for local use */
5200 break;
5203 fmp_menu[++i].type = tSeparator;
5206 /* Delete or Undelete? That is the question. */
5207 fmp_menu[++i].type = tQueue;
5208 fmp_menu[i].label.style = lNormal;
5209 mc = ((rawno = mn_m2raw(ps_global->msgmap,
5210 mn_get_cur(ps_global->msgmap))) > 0L
5211 && ps_global->mail_stream
5212 && rawno <= ps_global->mail_stream->nmsgs)
5213 ? mail_elt(ps_global->mail_stream, rawno) : NULL;
5214 if(mc && mc->deleted){
5215 fmp_menu[i].data.val = 'U';
5216 fmp_menu[i].label.string = in_handle
5217 ? "&Undelete Message" : "&Undelete";
5219 else{
5220 fmp_menu[i].data.val = 'D';
5221 fmp_menu[i].label.string = in_handle
5222 ? "&Delete Message" : "&Delete";
5225 if(F_ON(F_ENABLE_FLAG, ps_global)){
5226 fmp_menu[++i].type = tSubMenu;
5227 fmp_menu[i].label.string = "Flag";
5228 fmp_menu[i].data.submenu = flag_submenu(mc);
5231 i++;
5232 VIEWPOPUP(&fmp_menu[i], 'S', in_handle ? "&Save Message" : "&Save");
5234 i++;
5235 VIEWPOPUP(&fmp_menu[i], 'E', in_handle ? "&Export Message" : "&Export");
5237 i++;
5238 VIEWPOPUP(&fmp_menu[i], '%', in_handle ? "Print Message" : "Print");
5240 i++;
5241 VIEWPOPUP(&fmp_menu[i], 'R',
5242 in_handle ? "&Reply to Message" : "&Reply");
5244 i++;
5245 VIEWPOPUP(&fmp_menu[i], 'F',
5246 in_handle ? "&Forward Message" : "&Forward");
5248 i++;
5249 VIEWPOPUP(&fmp_menu[i], 'B',
5250 in_handle ? "&Bounce Message" : "&Bounce");
5252 i++;
5253 VIEWPOPUP(&fmp_menu[i], 'T', "&Take Addresses");
5255 fmp_menu[++i].type = tSeparator;
5257 if(mn_get_cur(ps_global->msgmap) < mn_get_total(ps_global->msgmap)){
5258 i++;
5259 VIEWPOPUP(&fmp_menu[i], 'N', "View &Next Message");
5262 if(mn_get_cur(ps_global->msgmap) > 0){
5263 i++;
5264 VIEWPOPUP(&fmp_menu[i], 'P', "View &Prev Message");
5267 if(mn_get_cur(ps_global->msgmap) < mn_get_total(ps_global->msgmap)
5268 || mn_get_cur(ps_global->msgmap) > 0)
5269 fmp_menu[++i].type = tSeparator;
5271 /* Offer the attachment screen? */
5272 for(n = 0; ps_global->atmts && ps_global->atmts[n].description; n++)
5275 if(n > 1){
5276 i++;
5277 VIEWPOPUP(&fmp_menu[i], 'V', "&View Attachment Index");
5281 i++;
5282 VIEWPOPUP(&fmp_menu[i], 'I', "Message &Index");
5284 i++;
5285 VIEWPOPUP(&fmp_menu[i], 'M', "&Main Menu");
5287 fmp_menu[++i].type = tTail;
5289 if((i = mswin_popup(fmp_menu)) >= 0 && in_handle)
5290 switch(fmp_menu[i].data.val){
5291 case 'W' : /* Copy URL to clipboard */
5292 mswin_addclipboard(h->h.url.path);
5293 break;
5295 case 'X' :
5296 return(1); /* return like the user double-clicked */
5298 break;
5300 case 'Y' : /* popup the thing in another window */
5301 display_att_window(h->h.attach);
5302 break;
5304 case 'Z' :
5305 if(h && h->type == Attach){
5306 msgno_exceptions(ps_global->mail_stream,
5307 mn_m2raw(ps_global->msgmap,
5308 mn_get_cur(ps_global->msgmap)),
5309 h->h.attach->number, &n, FALSE);
5310 n ^= MSG_EX_DELETE;
5311 msgno_exceptions(ps_global->mail_stream,
5312 mn_m2raw(ps_global->msgmap,
5313 mn_get_cur(ps_global->msgmap)),
5314 h->h.attach->number, &n, TRUE);
5315 q_status_message2(SM_ORDER, 0, 3, "Attachment %s %s!",
5316 h->h.attach->number,
5317 (n & MSG_EX_DELETE) ? "deleted" : "undeleted");
5319 return(2);
5322 break;
5324 default :
5325 break;
5328 return(0);
5337 simple_text_popup(sparms, in_handle)
5338 SCROLL_S *sparms;
5339 int in_handle;
5341 MPopup simple_menu[12];
5342 int n = 0;
5344 VIEWPOPUP(&simple_menu[n], '%', "Print");
5345 n++;
5347 VIEWPOPUP(&simple_menu[n], 'S', "Save");
5348 n++;
5350 VIEWPOPUP(&simple_menu[n], 'F', "Forward in Email");
5351 n++;
5353 simple_menu[n++].type = tSeparator;
5355 VIEWPOPUP(&simple_menu[n], 'E', "Exit Viewer");
5356 n++;
5358 simple_menu[n].type = tTail;
5360 (void) mswin_popup(simple_menu);
5361 return(0);
5366 /*----------------------------------------------------------------------
5367 Return characters in scroll tool buffer serially
5369 Args: n -- index of char to return
5371 Returns: returns the character at index 'n', or -1 on error or
5372 end of buffer.
5374 ----*/
5376 mswin_readscrollbuf(n)
5377 int n;
5379 SCRLCTRL_S *st = scroll_state(SS_CUR);
5380 int c;
5381 static char **orig = NULL, **l, *p;
5382 static int lastn;
5384 if(!st)
5385 return(-1);
5388 * All of these are mind-numbingly slow at the moment...
5390 switch(st->parms->text.src){
5391 case CharStar :
5392 return((n >= strlen((char *)st->parms->text.text))
5393 ? -1 : ((char *)st->parms->text.text)[n]);
5395 case CharStarStar :
5396 /* BUG? is this test rigorous enough? */
5397 if(orig != (char **)st->parms->text.text || n < lastn){
5398 lastn = n;
5399 if(orig = l = (char **)st->parms->text.text) /* reset l and p */
5400 p = *l;
5402 else{ /* use cached l and p */
5403 c = n; /* and adjust n */
5404 n -= lastn;
5405 lastn = c;
5408 while(l){ /* look for 'n' on each line */
5409 for(; n && *p; n--, p++)
5412 if(n--) /* 'n' found ? */
5413 p = *++l;
5414 else
5415 break;
5418 return((l && *l) ? *p ? *p : '\n' : -1);
5420 case FileStar :
5421 return((fseek((FILE *)st->parms->text.text, (long) n, 0) < 0
5422 || (c = fgetc((FILE *)st->parms->text.text)) == EOF) ? -1 : c);
5424 default:
5425 return(-1);
5431 /*----------------------------------------------------------------------
5432 MSWin scroll callback. Called during scroll message processing.
5436 Args: cmd - what type of scroll operation.
5437 scroll_pos - paramter for operation.
5438 used as position for SCROLL_TO operation.
5440 Returns: TRUE - did the scroll operation.
5441 FALSE - was not able to do the scroll operation.
5442 ----*/
5444 pcpine_do_scroll (cmd, scroll_pos)
5445 int cmd;
5446 long scroll_pos;
5448 SCRLCTRL_S *st = scroll_state(SS_CUR);
5449 HANDLE_S *next_handle;
5450 int paint = FALSE;
5451 int num_display_lines;
5452 int scroll_lines;
5453 char message[64];
5454 long maxscroll;
5457 message[0] = '\0';
5458 maxscroll = st->num_lines;
5459 switch (cmd) {
5460 case MSWIN_KEY_SCROLLUPLINE:
5461 if(st->top_text_line > 0) {
5462 st->top_text_line -= (int) scroll_pos;
5463 paint = TRUE;
5464 if (st->top_text_line <= 0){
5465 snprintf(message, sizeof(message), "START of %.*s",
5466 32, STYLE_NAME(st->parms));
5467 message[sizeof(message)-1] = '\0';
5468 st->top_text_line = 0;
5471 break;
5473 case MSWIN_KEY_SCROLLDOWNLINE:
5474 if(st->top_text_line < maxscroll) {
5475 st->top_text_line += (int) scroll_pos;
5476 paint = TRUE;
5477 if (st->top_text_line >= maxscroll){
5478 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5479 message[sizeof(message)-1] = '\0';
5480 st->top_text_line = maxscroll;
5483 break;
5485 case MSWIN_KEY_SCROLLUPPAGE:
5486 if(st->top_text_line > 0) {
5487 num_display_lines = SCROLL_LINES(ps_global);
5488 scroll_lines = MIN(MAX(num_display_lines -
5489 ps_global->viewer_overlap, 1), num_display_lines);
5490 if (st->top_text_line > scroll_lines)
5491 st->top_text_line -= scroll_lines;
5492 else {
5493 st->top_text_line = 0;
5494 snprintf(message, sizeof(message), "START of %.*s", 32, STYLE_NAME(st->parms));
5495 message[sizeof(message)-1] = '\0';
5497 paint = TRUE;
5499 break;
5501 case MSWIN_KEY_SCROLLDOWNPAGE:
5502 num_display_lines = SCROLL_LINES(ps_global);
5503 if(st->top_text_line < maxscroll) {
5504 scroll_lines = MIN(MAX(num_display_lines -
5505 ps_global->viewer_overlap, 1), num_display_lines);
5506 st->top_text_line += scroll_lines;
5507 if (st->top_text_line >= maxscroll) {
5508 st->top_text_line = maxscroll;
5509 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5510 message[sizeof(message)-1] = '\0';
5512 paint = TRUE;
5514 break;
5516 case MSWIN_KEY_SCROLLTO:
5517 if (st->top_text_line != scroll_pos) {
5518 st->top_text_line = scroll_pos;
5519 if (st->top_text_line == 0)
5520 snprintf(message, sizeof(message), "START of %.*s", 32, STYLE_NAME(st->parms));
5521 else if(st->top_text_line >= maxscroll)
5522 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5524 message[sizeof(message)-1] = '\0';
5525 paint = TRUE;
5527 break;
5530 /* Need to frame some handles? */
5531 if(st->parms->text.handles
5532 && (next_handle = scroll_handle_in_frame(st->top_text_line)))
5533 st->parms->text.handles = next_handle;
5535 if (paint) {
5536 mswin_beginupdate();
5537 update_scroll_titlebar(st->top_text_line, 0);
5538 (void) scroll_scroll_text(st->top_text_line,
5539 st->parms->text.handles, 1);
5540 if (message[0])
5541 q_status_message(SM_INFO, 0, 1, message);
5543 /* Display is always called so that the "START(END) of message"
5544 * message gets removed when no longer at the start(end). */
5545 display_message (KEY_PGDN);
5546 mswin_endupdate();
5549 return (TRUE);
5553 char *
5554 pcpine_help_scroll(title)
5555 char *title;
5557 SCRLCTRL_S *st = scroll_state(SS_CUR);
5559 if(title)
5560 strncpy(title, (st->parms->help.title)
5561 ? st->parms->help.title : "Alpine Help", 256);
5563 return(pcpine_help(st->parms->help.text));
5568 pcpine_view_cursor(col, row)
5569 int col;
5570 long row;
5572 SCRLCTRL_S *st = scroll_state(SS_CUR);
5573 int key;
5574 long line;
5576 return((row >= HEADER_ROWS(ps_global)
5577 && row < HEADER_ROWS(ps_global) + SCROLL_LINES(ps_global)
5578 && (line = (row - 2) + st->top_text_line) < st->num_lines
5579 && (key = dot_on_handle(line, col))
5580 && scroll_handle_selectable(get_handle(st->parms->text.handles,key)))
5581 ? MSWIN_CURSOR_HAND
5582 : MSWIN_CURSOR_ARROW);
5584 #endif /* _WINDOWS */