* Update to packages/alpine.spec to account for the new location of man
[alpine.git] / alpine / mailview.c
blobe60c9558a39080174e104e311e3ac5bd5d1ca30a
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-2017 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 url_local_phone_home(char *);
178 int url_local_imap(char *);
179 int url_local_nntp(char *);
180 int url_local_news(char *);
181 int url_local_file(char *);
182 static int print_to_printer(SCROLL_S *);
183 int search_text(int, long, int, char **, Pos *, int *);
184 void update_scroll_titlebar(long, int);
185 SCRLCTRL_S *scroll_state(int);
186 void set_scroll_text(SCROLL_S *, long, SCRLCTRL_S *);
187 void redraw_scroll_text(void);
188 void zero_scroll_text(void);
189 void format_scroll_text(void);
190 void ScrollFile(long);
191 long make_file_index(void);
192 char *scroll_file_line(FILE *, char *, SCRLFILE_S *, int *);
193 long scroll_scroll_text(long, HANDLE_S *, int);
194 int search_scroll_text(long, int, char *, Pos *, int *);
195 char *search_scroll_line(char *, char *, int, int);
196 int visible_linelen(int);
197 long doubleclick_handle(SCROLL_S *, HANDLE_S *, int *, int *);
198 #ifdef _WINDOWS
199 int format_message_popup(SCROLL_S *, int);
200 int simple_text_popup(SCROLL_S *, int);
201 int mswin_readscrollbuf(int);
202 int pcpine_do_scroll(int, long);
203 char *pcpine_help_scroll(char *);
204 int pcpine_view_cursor(int, long);
205 #endif
209 /*----------------------------------------------------------------------
210 Format a buffer with the text of the current message for browser
212 Args: ps - pine state structure
214 Result: The scrolltool is called to display the message
216 Loop here viewing mail until the folder changed or a command takes
217 us to another screen. Inside the loop the message text is fetched and
218 formatted into a buffer allocated for it. These are passed to the
219 scrolltool(), that displays the message and executes commands. It
220 returns when it's time to display a different message, when we
221 change folders, when it's time for a different screen, or when
222 there are no more messages available.
223 ---*/
225 void
226 mail_view_screen(struct pine *ps)
228 char last_was_full_header = 0;
229 long last_message_viewed = -1L, raw_msgno, offset = 0L;
230 OtherMenu save_what = FirstMenu;
231 int we_cancel = 0, flags, cmd = 0;
232 int force_prefer = 0;
233 MESSAGECACHE *mc;
234 ENVELOPE *env;
235 BODY *body;
236 STORE_S *store;
237 HANDLE_S *handles = NULL;
238 SCROLL_S scrollargs;
239 SourceType src = CharStar;
241 dprint((1, "\n\n ----- MAIL VIEW -----\n"));
243 ps->prev_screen = mail_view_screen;
244 ps->force_prefer_plain = ps->force_no_prefer_plain = 0;
246 if(ps->ttyo->screen_rows - HEADER_ROWS(ps) - FOOTER_ROWS(ps) < 1){
247 q_status_message(SM_ORDER | SM_DING, 0, 3,
248 _("Screen too small to view message"));
249 ps->next_screen = mail_index_screen;
250 return;
253 /*----------------- Loop viewing messages ------------------*/
254 do {
256 ps->user_says_cancel = 0;
257 ps->some_quoting_was_suppressed = 0;
260 * Check total to make sure there's something to view. Check it
261 * inside the loop to make sure everything wasn't expunged while
262 * we were viewing. If so, make sure we don't just come back.
264 if(mn_get_total(ps->msgmap) <= 0L || !ps->mail_stream){
265 q_status_message(SM_ORDER, 0, 3, _("No messages to read!"));
266 ps->next_screen = mail_index_screen;
267 break;
270 we_cancel = busy_cue(NULL, NULL, 1);
272 if(mn_get_cur(ps->msgmap) <= 0L)
273 mn_set_cur(ps->msgmap,
274 THREADING() ? first_sorted_flagged(F_NONE,
275 ps->mail_stream,
276 0L, FSF_SKIP_CHID)
277 : 1L);
279 raw_msgno = mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap));
280 body = NULL;
281 if(raw_msgno == 0L
282 || !(env = pine_mail_fetchstructure(ps->mail_stream,raw_msgno,&body))
283 || !(raw_msgno > 0L && ps->mail_stream
284 && raw_msgno <= ps->mail_stream->nmsgs
285 && (mc = mail_elt(ps->mail_stream, raw_msgno)))){
286 q_status_message1(SM_ORDER, 3, 3,
287 "Error getting message %s data",
288 comatose(mn_get_cur(ps->msgmap)));
289 dprint((1, "!!!! ERROR fetching %s of msg %ld\n",
290 env ? "elt" : "env", mn_get_cur(ps->msgmap)));
292 ps->next_screen = mail_index_screen;
293 break;
295 else
296 ps->unseen_in_view = !mc->seen;
298 init_handles(&handles);
300 store = so_get(src, NULL, EDIT_ACCESS);
301 so_truncate(store, format_size_guess(body) + 2048);
303 view_writec_init(store, &handles, SCROLL_LINES_ABOVE(ps),
304 SCROLL_LINES_ABOVE(ps) +
305 ps->ttyo->screen_rows - (SCROLL_LINES_ABOVE(ps)
306 + SCROLL_LINES_BELOW(ps)));
308 flags = FM_DISPLAY;
309 if(last_message_viewed != mn_get_cur(ps->msgmap)
310 || last_was_full_header == 2 || cmd == MC_TOGGLE)
311 flags |= FM_NEW_MESS;
313 if(F_OFF(F_QUELL_FULL_HDR_RESET, ps_global)
314 && last_message_viewed != -1L
315 && last_message_viewed != mn_get_cur(ps->msgmap))
316 ps->full_header = 0;
318 if(offset) /* no pre-paint during resize */
319 view_writec_killbuf();
321 #ifdef SMIME
322 /* Attempt to handle S/MIME bodies */
323 if(ps->smime)
324 ps->smime->need_passphrase = 0;
326 if(F_OFF(F_DONT_DO_SMIME, ps_global) && fiddle_smime_message(body, raw_msgno))
327 flags |= FM_NEW_MESS; /* body was changed, force a reload */
328 #endif
330 #ifdef _WINDOWS
331 mswin_noscrollupdate(1);
332 #endif
333 ps->cur_uid_stream = ps->mail_stream;
334 ps->cur_uid = mail_uid(ps->mail_stream, raw_msgno);
335 (void) format_message(raw_msgno, env, body, &handles, flags | force_prefer,
336 view_writec);
337 #ifdef _WINDOWS
338 mswin_noscrollupdate(0);
339 #endif
341 view_writec_destroy();
343 last_message_viewed = mn_get_cur(ps->msgmap);
344 last_was_full_header = ps->full_header;
346 ps->next_screen = SCREEN_FUN_NULL;
348 memset(&scrollargs, 0, sizeof(SCROLL_S));
349 scrollargs.text.text = so_text(store);
350 scrollargs.text.src = src;
351 scrollargs.text.desc = "message";
354 * make first selectable handle the default
356 if(handles){
357 HANDLE_S *hp;
359 hp = handles;
360 while(!scroll_handle_selectable(hp) && hp != NULL)
361 hp = hp->next;
363 if((scrollargs.text.handles = hp) != NULL)
364 scrollargs.body_valid = (hp == handles);
365 else
366 free_handles(&handles);
368 else
369 scrollargs.body_valid = 1;
371 if(offset){ /* resize? preserve paging! */
372 scrollargs.start.on = Offset;
373 scrollargs.start.loc.offset = offset;
374 scrollargs.body_valid = 0;
375 offset = 0L;
378 scrollargs.use_indexline_color = 1;
380 /* TRANSLATORS: a screen title */
381 scrollargs.bar.title = _("MESSAGE TEXT");
382 scrollargs.end_scroll = view_end_scroll;
383 scrollargs.resize_exit = 1;
384 scrollargs.help.text = h_mail_view;
385 scrollargs.help.title = _("HELP FOR MESSAGE TEXT VIEW");
386 scrollargs.keys.menu = &view_keymenu;
387 scrollargs.keys.what = save_what;
388 setbitmap(scrollargs.keys.bitmap);
389 if(F_OFF(F_ENABLE_PIPE, ps_global))
390 clrbitn(VIEW_PIPE_KEY, scrollargs.keys.bitmap);
393 * turn off attachment viewing for raw msg txt, atts
394 * haven't been set up at this point
396 if(ps_global->full_header == 2
397 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
398 clrbitn(VIEW_ATT_KEY, scrollargs.keys.bitmap);
400 if(F_OFF(F_ENABLE_BOUNCE, ps_global))
401 clrbitn(BOUNCE_KEY, scrollargs.keys.bitmap);
403 if(F_OFF(F_ENABLE_FLAG, ps_global))
404 clrbitn(FLAG_KEY, scrollargs.keys.bitmap);
406 if(F_OFF(F_ENABLE_AGG_OPS, ps_global))
407 clrbitn(VIEW_SELECT_KEY, scrollargs.keys.bitmap);
409 if(F_OFF(F_ENABLE_FULL_HDR, ps_global))
410 clrbitn(VIEW_FULL_HEADERS_KEY, scrollargs.keys.bitmap);
412 #ifdef SMIME
413 if(!(ps->smime && ps->smime->need_passphrase))
414 clrbitn(DECRYPT_KEY, scrollargs.keys.bitmap);
416 if(F_ON(F_DONT_DO_SMIME, ps_global)){
417 clrbitn(DECRYPT_KEY, scrollargs.keys.bitmap);
418 clrbitn(SECURITY_KEY, scrollargs.keys.bitmap);
420 #endif
422 if(!handles){
424 * NOTE: the comment below only really makes sense if we
425 * decide to replace the "Attachment Index" with
426 * a model that lets you highlight the attachments
427 * in the header. In a way its consistent, but
428 * would mean more keymenu monkeybusiness since not
429 * all things would be available all the time. No wait.
430 * Then what would "Save" mean; the attachment, url or
431 * message you're currently viewing? Would Save
432 * of a message then only be possible from the message
433 * index? Clumsy. what about arrow-navigation. isn't
434 * that now inconsistent? Maybe next/prev url shouldn't
435 * be bound to the arrow/^N/^P navigation?
437 clrbitn(VIEW_VIEW_HANDLE, scrollargs.keys.bitmap);
438 clrbitn(VIEW_PREV_HANDLE, scrollargs.keys.bitmap);
439 clrbitn(VIEW_NEXT_HANDLE, scrollargs.keys.bitmap);
442 #ifdef _WINDOWS
443 scrollargs.mouse.popup = format_message_popup;
444 #endif
446 if(((cmd = scrolltool(&scrollargs)) == MC_RESIZE
447 || (cmd == MC_FULLHDR && ps_global->full_header == 1))
448 && scrollargs.start.on == Offset)
449 offset = scrollargs.start.loc.offset;
451 if(cmd == MC_TOGGLE && ps->force_prefer_plain == 0 && ps->force_no_prefer_plain == 0){
452 if(F_ON(F_PREFER_PLAIN_TEXT, ps_global))
453 ps->force_no_prefer_plain = 1;
454 else
455 ps->force_prefer_plain = 1;
457 else{
458 ps->force_prefer_plain = ps->force_no_prefer_plain = 0;
462 * We could use the values directly but this is the way it
463 * already worked with the flags, so leave it alone.
465 if(ps->force_prefer_plain == 0 && ps->force_no_prefer_plain == 0)
466 force_prefer = 0;
467 else if(ps->force_prefer_plain)
468 force_prefer = FM_FORCEPREFPLN;
469 else if(ps->force_no_prefer_plain)
470 force_prefer = FM_FORCENOPREFPLN;
472 save_what = scrollargs.keys.what;
473 ps_global->unseen_in_view = 0;
474 so_give(&store); /* free resources associated with store */
475 free_handles(&handles);
476 #ifdef _WINDOWS
477 mswin_destroyicons();
478 #endif
480 while(ps->next_screen == SCREEN_FUN_NULL);
482 if(we_cancel)
483 cancel_busy_cue(-1);
485 ps->force_prefer_plain = ps->force_no_prefer_plain = 0;
487 ps->cur_uid_stream = NULL;
488 ps->cur_uid = 0;
491 * Unless we're going into attachment screen,
492 * start over with full_header.
494 if(F_OFF(F_QUELL_FULL_HDR_RESET, ps_global)
495 && ps->next_screen != attachment_screen)
496 ps->full_header = 0;
502 * view_writec_init - function to create and init struct that manages
503 * writing to the display what we can as soon
504 * as we can.
506 void
507 view_writec_init(STORE_S *store, HANDLE_S **handlesp, int first_line, int last_line)
509 char tmp[MAILTMPLEN];
511 g_view_write = (struct view_write_s *)fs_get(sizeof(struct view_write_s));
512 memset(g_view_write, 0, sizeof(struct view_write_s));
513 g_view_write->store = store;
514 g_view_write->handles = handlesp;
515 g_view_write->screen_line = first_line;
516 g_view_write->last_screen_line = last_line;
518 if(!dfilter_trigger(NULL, tmp, sizeof(tmp))){
519 g_view_write->line = (char *) fs_get(LINEBUFSIZ*sizeof(char));
520 #ifdef _WINDOWS
521 mswin_beginupdate();
522 scroll_setrange(0L, 0L);
523 #endif
525 if(ps_global->VAR_DISPLAY_FILTERS)
526 ClearLines(first_line, last_line - 1);
532 void
533 view_writec_destroy(void)
535 if(g_view_write){
536 if(g_view_write->line && g_view_write->index)
537 view_writec('\n'); /* flush pending output! */
539 while(g_view_write->screen_line < g_view_write->last_screen_line)
540 ClearLine(g_view_write->screen_line++);
542 view_writec_killbuf();
544 fs_give((void **) &g_view_write);
547 #ifdef _WINDOWS
548 mswin_endupdate();
549 #endif
554 void
555 view_writec_killbuf(void)
557 if(g_view_write->line)
558 fs_give((void **) &g_view_write->line);
564 * view_screen_pc - write chars into the final buffer
567 view_writec(int c)
569 static int in_color = 0;
571 if(g_view_write->line){
573 * This only works if the 2nd and 3rd parts of the || don't happen.
574 * The only way it breaks is if we get a really, really long line
575 * because there are oodles of tags in it. In that case we will
576 * wrap incorrectly or split a tag across lines (losing color perhaps)
577 * but we won't crash.
579 if(c == '\n' ||
580 (!in_color &&
581 (char)c != TAG_EMBED &&
582 g_view_write->index >= (LINEBUFSIZ - 20 * (RGBLEN+2))) ||
583 (g_view_write->index >= LINEBUFSIZ - 1)){
584 int rv;
586 in_color = 0;
587 suspend_busy_cue();
588 ClearLine(g_view_write->screen_line);
589 if(c != '\n')
590 g_view_write->line[g_view_write->index++] = (char) c;
592 PutLine0n8b(g_view_write->screen_line++, 0,
593 g_view_write->line, g_view_write->index,
594 g_view_write->handles ? *g_view_write->handles : NULL);
596 resume_busy_cue(0);
597 rv = so_nputs(g_view_write->store,
598 g_view_write->line,
599 g_view_write->index);
600 g_view_write->index = 0;
602 if(g_view_write->screen_line >= g_view_write->last_screen_line){
603 fs_give((void **) &g_view_write->line);
604 fflush(stdout);
607 if(!rv)
608 return(0);
609 else if(c != '\n')
610 return(1);
612 else{
614 * Don't split embedded things over multiple lines. Colors are
615 * the longest tags so use their length.
617 if((char)c == TAG_EMBED)
618 in_color = RGBLEN+1;
619 else if(in_color)
620 in_color--;
622 g_view_write->line[g_view_write->index++] = (char) c;
623 return(1);
626 #ifdef _WINDOWS
627 else if(c == '\n' && g_view_write->lines++ > SCROLL_LINES(ps_global))
628 scroll_setrange(SCROLL_LINES(ps_global),
629 g_view_write->lines + SCROLL_LINES(ps_global));
630 #endif
632 return(so_writec(c, g_view_write->store));
636 view_end_scroll(SCROLL_S *sparms)
638 int done = 0, result, force;
640 if(F_ON(F_ENABLE_SPACE_AS_TAB, ps_global)){
642 if(F_ON(F_ENABLE_TAB_DELETES, ps_global)){
643 long save_msgno;
645 /* Let the TAB advance cur msgno for us */
646 save_msgno = mn_get_cur(ps_global->msgmap);
647 (void) cmd_delete(ps_global, ps_global->msgmap, MCMD_NONE, NULL);
648 mn_set_cur(ps_global->msgmap, save_msgno);
651 /* act like the user hit a TAB */
652 result = process_cmd(ps_global, ps_global->mail_stream,
653 ps_global->msgmap, MC_TAB, View, &force);
655 if(result == 1)
656 done = 1;
659 return(done);
664 * format_size_guess -- Run down the given body summing the text/plain
665 * pieces we're likely to display. It need only
666 * be a guess since this is intended for preallocating
667 * our display buffer...
669 long
670 format_size_guess(struct mail_bodystruct *body)
672 long size = 0L;
673 long extra = 0L;
674 char *free_me = NULL;
676 if(body){
677 if(body->type == TYPEMULTIPART){
678 PART *part;
680 for(part = body->nested.part; part; part = part->next)
681 size += format_size_guess(&part->body);
683 else if(body->type == TYPEMESSAGE
684 && body->subtype && !strucmp(body->subtype, "rfc822"))
685 size = format_size_guess(body->nested.msg->body);
686 else if((!body->type || body->type == TYPETEXT)
687 && (!body->subtype || !strucmp(body->subtype, "plain"))
688 && ((body->disposition.type
689 && !strucmp(body->disposition.type, "inline"))
690 || !(free_me = parameter_val(body->parameter, "name")))){
692 * Handles and colored quotes cause memory overhead. Figure about
693 * 100 bytes per level of quote per line and about 100 bytes per
694 * handle. Make a guess of 1 handle or colored quote per
695 * 10 lines of message. If we guess too low, we'll do a resize in
696 * so_cs_writec. Most of the overhead comes from the colors, so
697 * if we can see we don't do colors or don't have these features
698 * turned on, skip it.
700 if(pico_usingcolor() &&
701 ((ps_global->VAR_QUOTE1_FORE_COLOR &&
702 ps_global->VAR_QUOTE1_BACK_COLOR) ||
703 F_ON(F_VIEW_SEL_URL, ps_global) ||
704 F_ON(F_VIEW_SEL_URL_HOST, ps_global) ||
705 F_ON(F_SCAN_ADDR, ps_global)))
706 extra = MIN(100/10 * body->size.lines, MAX_FUDGE);
708 size = body->size.bytes + extra;
711 if(free_me)
712 fs_give((void **) &free_me);
715 return(size);
720 scroll_handle_prompt(HANDLE_S *handle, int force)
722 char prompt[256], tmp[MAILTMPLEN];
723 int rc, flags, local_h;
724 static ESCKEY_S launch_opts[] = {
725 /* TRANSLATORS: command names, editURL means user gets to edit a URL if they
726 want, editApp is edit application where they edit the application used to
727 view a URL */
728 {'y', 'y', "Y", N_("Yes")},
729 {'n', 'n', "N", N_("No")},
730 {-2, 0, NULL, NULL},
731 {-2, 0, NULL, NULL},
732 {0, 'u', "U", N_("editURL")},
733 {0, 'a', "A", N_("editApp")},
734 {-1, 0, NULL, NULL}};
736 if(handle->type == URL){
737 launch_opts[4].ch = 'u';
739 if((!(local_h = !struncmp(handle->h.url.path, "x-alpine-", 9))
740 || !(local_h = !struncmp(handle->h.url.path, "x-pine-help", 11)))
741 && (handle->h.url.tool
742 || ((local_h = url_local_handler(handle->h.url.path) != NULL)
743 && (handle->h.url.tool = url_external_handler(handle,1)))
744 || (!local_h
745 && (handle->h.url.tool = url_external_handler(handle,0))))){
746 #ifdef _WINDOWS
747 /* if NOT special DDE hack */
748 if(handle->h.url.tool[0] != '*')
749 #endif
750 if(ps_global->vars[V_BROWSER].is_fixed)
751 launch_opts[5].ch = -1;
752 else
753 launch_opts[5].ch = 'a';
755 else{
756 launch_opts[5].ch = -1;
757 if(!local_h){
758 if(ps_global->vars[V_BROWSER].is_fixed){
759 q_status_message(SM_ORDER, 3, 4,
760 _("URL-Viewer is disabled by sys-admin"));
761 return(0);
763 else{
764 /* TRANSLATORS: a question */
765 if(want_to(_("No URL-Viewer application defined. Define now"),
766 'y', 0, NO_HELP, WT_SEQ_SENSITIVE) == 'y'){
767 /* Prompt for the displayer? */
768 tmp[0] = '\0';
769 while(1){
770 flags = OE_APPEND_CURRENT |
771 OE_SEQ_SENSITIVE |
772 OE_KEEP_TRAILING_SPACE;
774 rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
775 sizeof(tmp),
776 _("Web Browser: "),
777 NULL, NO_HELP, &flags);
778 if(rc == 0){
779 if((flags & OE_USER_MODIFIED) && *tmp){
780 if(can_access(tmp, EXECUTE_ACCESS) == 0){
781 int n;
782 char **l;
785 * Save it for next time...
787 for(l = ps_global->VAR_BROWSER, n = 0;
788 l && *l;
789 l++)
790 n++; /* count */
792 l = (char **) fs_get((n+2)*sizeof(char *));
793 for(n = 0;
794 ps_global->VAR_BROWSER
795 && ps_global->VAR_BROWSER[n];
796 n++)
797 l[n] = cpystr(ps_global->VAR_BROWSER[n]);
799 l[n++] = cpystr(tmp);
800 l[n] = NULL;
802 set_variable_list(V_BROWSER, l, TRUE, Main);
803 free_list_array(&l);
805 handle->h.url.tool = cpystr(tmp);
806 break;
808 else{
809 q_status_message1(SM_ORDER | SM_DING, 2, 2,
810 _("Browser not found: %s"),
811 error_description(errno));
812 continue;
815 else
816 return(0);
818 else if(rc == 1 || rc == -1){
819 return(0);
821 else if(rc == 4){
822 if(ps_global->redrawer)
823 (*ps_global->redrawer)();
827 else
828 return(0);
833 else
834 launch_opts[4].ch = -1;
836 if(force
837 || (handle->type == URL
838 && (!struncmp(handle->h.url.path, "x-alpine-", 9)
839 || !struncmp(handle->h.url.path, "x-pine-help", 11))))
840 return(1);
842 while(1){
843 int sc = ps_global->ttyo->screen_cols;
846 * Customize the prompt for mailto, all the other schemes make
847 * sense if you just say View selected URL ...
849 if(handle->type == URL &&
850 !struncmp(handle->h.url.path, "mailto:", 7))
851 snprintf(prompt, sizeof(prompt), "Compose mail to \"%.*s%s\" ? ",
852 (int) MIN(MAX(0,sc - 25), sizeof(prompt)-50), handle->h.url.path+7,
853 (strlen(handle->h.url.path+7) > MAX(0,sc-25)) ? "..." : "");
854 else
855 snprintf(prompt, sizeof(prompt), "View selected %s %s%.*s%s ? ",
856 (handle->type == URL) ? "URL" : "Attachment",
857 (handle->type == URL) ? "\"" : "",
858 (int) MIN(MAX(0,sc-27), sizeof(prompt)-50),
859 (handle->type == URL) ? handle->h.url.path : "",
860 (handle->type == URL)
861 ? ((strlen(handle->h.url.path) > MAX(0,sc-27))
862 ? "...\"" : "\"") : "");
864 prompt[sizeof(prompt)-1] = '\0';
866 switch(radio_buttons(prompt, -FOOTER_ROWS(ps_global),
867 launch_opts, 'y', 'n', NO_HELP, RB_SEQ_SENSITIVE)){
868 case 'y' :
869 return(1);
871 case 'u' :
872 strncpy(tmp, handle->h.url.path, sizeof(tmp)-1);
873 tmp[sizeof(tmp)-1] = '\0';
874 while(1){
875 flags = OE_APPEND_CURRENT |
876 OE_SEQ_SENSITIVE |
877 OE_KEEP_TRAILING_SPACE;
879 rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
880 sizeof(tmp), _("Edit URL: "),
881 NULL, NO_HELP, &flags);
882 if(rc == 0){
883 if(flags & OE_USER_MODIFIED){
884 if(handle->h.url.path)
885 fs_give((void **) &handle->h.url.path);
887 handle->h.url.path = cpystr(tmp);
890 break;
892 else if(rc == 1 || rc == -1){
893 return(0);
895 else if(rc == 4){
896 if(ps_global->redrawer)
897 (*ps_global->redrawer)();
901 continue;
903 case 'a' :
904 if(handle->h.url.tool){
905 strncpy(tmp, handle->h.url.tool, sizeof(tmp)-1);
906 tmp[sizeof(tmp)-1] = '\0';
908 else
909 tmp[0] = '\0';
911 while(1){
912 flags = OE_APPEND_CURRENT |
913 OE_SEQ_SENSITIVE |
914 OE_KEEP_TRAILING_SPACE |
915 OE_DISALLOW_HELP;
917 rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
918 sizeof(tmp), _("Viewer Command: "),
919 NULL, NO_HELP, &flags);
920 if(rc == 0){
921 if(flags & OE_USER_MODIFIED){
922 if(handle->h.url.tool)
923 fs_give((void **) &handle->h.url.tool);
925 handle->h.url.tool = cpystr(tmp);
928 break;
930 else if(rc == 1 || rc == -1){
931 return(0);
933 else if(rc == 4){
934 if(ps_global->redrawer)
935 (*ps_global->redrawer)();
939 continue;
941 case 'n' :
942 default :
943 return(0);
951 scroll_handle_launch(HANDLE_S *handle, int force)
953 switch(handle->type){
954 case URL :
955 if(handle->h.url.path){
956 if(scroll_handle_prompt(handle, force)){
957 if(url_launch(handle)
958 || ps_global->next_screen != SCREEN_FUN_NULL)
959 return(1); /* done with this screen */
961 else
962 return(-1);
965 break;
967 case Attach :
968 if(scroll_handle_prompt(handle, force))
969 display_attachment(mn_m2raw(ps_global->msgmap,
970 mn_get_cur(ps_global->msgmap)),
971 handle->h.attach, DA_FROM_VIEW | DA_DIDPROMPT);
972 else
973 return(-1);
975 break;
977 case Folder :
978 break;
980 case Function :
981 (*handle->h.func.f)(handle->h.func.args.stream,
982 handle->h.func.args.msgmap,
983 handle->h.func.args.msgno);
984 break;
987 default :
988 alpine_panic("Unexpected HANDLE type");
991 return(0);
996 scroll_handle_obscured(HANDLE_S *handle)
998 SCRLCTRL_S *st = scroll_state(SS_CUR);
1000 return(handle_on_page(handle, st->top_text_line,
1001 st->top_text_line + st->screen.length));
1007 * scroll_handle_in_frame -- return handle pointer to visible handle.
1009 HANDLE_S *
1010 scroll_handle_in_frame(long int top_line)
1012 SCRLCTRL_S *st = scroll_state(SS_CUR);
1013 HANDLE_S *hp;
1015 switch(handle_on_page(hp = st->parms->text.handles, top_line,
1016 top_line + st->screen.length)){
1017 case -1 : /* handle above page */
1018 /* Find first handle from top of page */
1019 for(hp = st->parms->text.handles->next; hp; hp = hp->next)
1020 if(scroll_handle_selectable(hp))
1021 switch(handle_on_page(hp, top_line, top_line + st->screen.length)){
1022 case 0 : return(hp);
1023 case 1 : return(NULL);
1024 case -1 : default : break;
1027 break;
1029 case 1 : /* handle below page */
1030 /* Find first handle from top of page */
1031 for(hp = st->parms->text.handles->prev; hp; hp = hp->prev)
1032 if(scroll_handle_selectable(hp))
1033 switch(handle_on_page(hp, top_line, top_line + st->screen.length)){
1034 case 0 : return(hp);
1035 case -1 : return(NULL);
1036 case 1 : default : break;
1039 break;
1041 case 0 :
1042 default :
1043 break;
1046 return(hp);
1050 * scroll_handle_reframe -- adjust display params to display given handle
1052 long
1053 scroll_handle_reframe(int key, int center)
1055 long l, offset, dlines, start_line;
1056 SCRLCTRL_S *st = scroll_state(SS_CUR);
1058 dlines = PGSIZE(st);
1059 offset = (st->parms->text.src == FileStar) ? st->top_text_line : 0;
1060 start_line = st->top_text_line;
1062 if(key < 0)
1063 key = st->parms->text.handles->key;
1065 /* Searc down from the top line */
1066 for(l = start_line; l < st->num_lines; l++) {
1067 if(st->parms->text.src == FileStar && l > offset + dlines)
1068 ScrollFile(offset += dlines);
1070 if(handle_on_line(l - offset, key))
1071 break;
1074 if(l < st->num_lines){
1075 if(l >= dlines + start_line) /* bingo! */
1076 start_line = l - ((center ? (dlines / 2) : dlines) - 1);
1078 else{
1079 if(st->parms->text.src == FileStar) /* wrap offset */
1080 ScrollFile(offset = 0);
1082 for(l = 0; l < start_line; l++) {
1083 if(st->parms->text.src == FileStar && l > offset + dlines)
1084 ScrollFile(offset += dlines);
1086 if(handle_on_line(l - offset, key))
1087 break;
1090 if(l == start_line)
1091 alpine_panic("Internal Error: no handle found");
1092 else
1093 start_line = l;
1096 return(start_line);
1101 handle_on_line(long int line, int goal)
1103 int i, n, key;
1104 SCRLCTRL_S *st = scroll_state(SS_CUR);
1106 for(i = 0; i < st->line_lengths[line]; i++)
1107 if(st->text_lines[line][i] == TAG_EMBED
1108 && st->text_lines[line][++i] == TAG_HANDLE){
1109 for(key = 0, n = st->text_lines[line][++i]; n; n--)
1110 key = (key * 10) + (st->text_lines[line][++i] - '0');
1112 if(key == goal)
1113 return(1);
1116 return(0);
1121 handle_on_page(HANDLE_S *handle, long int first_line, long int last_line)
1123 POSLIST_S *l;
1124 int rv = 0;
1126 if(handle && (l = handle->loc)){
1127 for( ; l; l = l->next)
1128 if(l->where.row < first_line){
1129 if(!rv)
1130 rv = -1;
1132 else if(l->where.row >= last_line){
1133 if(!rv)
1134 rv = 1;
1136 else
1137 return(0); /* found! */
1140 return(rv);
1145 scroll_handle_selectable(HANDLE_S *handle)
1147 return(handle && (handle->type != URL
1148 || (handle->h.url.path && *handle->h.url.path)));
1152 HANDLE_S *
1153 scroll_handle_next_sel(HANDLE_S *handles)
1155 while(handles && !scroll_handle_selectable(handles = handles->next))
1158 return(handles);
1162 HANDLE_S *
1163 scroll_handle_prev_sel(HANDLE_S *handles)
1165 while(handles && !scroll_handle_selectable(handles = handles->prev))
1168 return(handles);
1172 HANDLE_S *
1173 scroll_handle_next(HANDLE_S *handles)
1175 HANDLE_S *next = NULL;
1177 if(scroll_handle_obscured(handles) <= 0){
1178 next = handles;
1179 while((next = scroll_handle_next_sel(next))
1180 && scroll_handle_obscured(next))
1184 return(next);
1189 HANDLE_S *
1190 scroll_handle_prev(HANDLE_S *handles)
1192 HANDLE_S *prev = NULL;
1194 if(scroll_handle_obscured(handles) >= 0){
1195 prev = handles;
1196 while((prev = scroll_handle_prev_sel(prev))
1197 && scroll_handle_obscured(prev))
1201 return(prev);
1205 HANDLE_S *
1206 scroll_handle_boundary(HANDLE_S *handle, HANDLE_S *(*f) (HANDLE_S *))
1208 HANDLE_S *hp, *whp = NULL;
1210 /* Multi-line handle? Punt! */
1211 if(handle && (!(hp = handle)->loc->next))
1212 while((hp = (*f)(hp))
1213 && hp->loc->where.row == handle->loc->where.row)
1214 whp = hp;
1216 return(whp);
1221 scroll_handle_column(int line, int offset)
1223 SCRLCTRL_S *st = scroll_state(SS_CUR);
1224 int i, n, col;
1225 int key, limit;
1227 limit = (offset > -1) ? offset : st->line_lengths[line];
1229 for(i = 0, col = 0; i < limit;){
1230 if(st && st->text_lines && st->text_lines[line])
1231 switch(st->text_lines[line][i]){
1232 case TAG_EMBED:
1233 i++;
1234 switch((i < limit) ? st->text_lines[line][i] : 0){
1235 case TAG_HANDLE:
1236 for(key = 0, n = st->text_lines[line][++i]; n > 0 && i < limit-1; n--)
1237 key = (key * 10) + (st->text_lines[line][++i] - '0');
1239 i++;
1240 break;
1242 case TAG_FGCOLOR :
1243 case TAG_BGCOLOR :
1244 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
1245 break;
1247 case TAG_INVON:
1248 case TAG_INVOFF:
1249 case TAG_BOLDON:
1250 case TAG_BOLDOFF:
1251 case TAG_ULINEON:
1252 case TAG_ULINEOFF:
1253 i++;
1254 break;
1256 default: /* literal embed char */
1257 break;
1260 break;
1262 case TAB:
1263 i++;
1264 while(((++col) & 0x07) != 0) /* add tab's spaces */
1267 break;
1269 default:
1270 col += width_at_this_position((unsigned char*) &st->text_lines[line][i],
1271 st->line_lengths[line] - i);
1272 i++;
1273 break;
1277 return(col);
1282 scroll_handle_index(int row, int column)
1284 SCRLCTRL_S *st = scroll_state(SS_CUR);
1285 int index = 0;
1287 for(index = 0; column > 0;)
1288 switch(st->text_lines[row][index++]){
1289 case TAG_EMBED :
1290 switch(st->text_lines[row][index++]){
1291 case TAG_HANDLE:
1292 index += st->text_lines[row][index] + 1;
1293 break;
1295 case TAG_FGCOLOR :
1296 case TAG_BGCOLOR :
1297 index += RGBLEN;
1298 break;
1300 default :
1301 break;
1304 break;
1306 case TAB : /* add tab's spaces */
1307 while(((--column) & 0x07) != 0)
1310 break;
1312 default :
1313 column--;
1314 break;
1317 return(index);
1321 void
1322 scroll_handle_set_loc(POSLIST_S **lpp, int line, int column)
1324 POSLIST_S *lp;
1326 lp = (POSLIST_S *) fs_get(sizeof(POSLIST_S));
1327 lp->where.row = line;
1328 lp->where.col = column;
1329 lp->next = NULL;
1330 for(; *lpp; lpp = &(*lpp)->next)
1333 *lpp = lp;
1338 dot_on_handle(long int line, int goal)
1340 int i = 0, n, key = 0, column = -1;
1341 SCRLCTRL_S *st = scroll_state(SS_CUR);
1344 if(i >= st->line_lengths[line])
1345 return(0);
1347 switch(st->text_lines[line][i++]){
1348 case TAG_EMBED :
1349 switch(st->text_lines[line][i++]){
1350 case TAG_HANDLE :
1351 for(key = 0, n = st->text_lines[line][i++]; n; n--)
1352 key = (key * 10) + (st->text_lines[line][i++] - '0');
1354 break;
1356 case TAG_FGCOLOR :
1357 case TAG_BGCOLOR :
1358 i += RGBLEN; /* advance past color setting */
1359 break;
1361 case TAG_BOLDOFF :
1362 key = 0;
1363 break;
1366 break;
1368 case TAB :
1369 while(((++column) & 0x07) != 0) /* add tab's spaces */
1372 break;
1374 default :
1375 column += width_at_this_position((unsigned char*) &st->text_lines[line][i-1],
1376 st->line_lengths[line] - (i-1));
1377 break;
1380 while(column < goal);
1382 return(key);
1387 * url_launch - Sniff the given url, see if we can do anything with
1388 * it. If not, hand off to user-defined app.
1392 url_launch(HANDLE_S *handle)
1394 int rv = 0;
1395 url_tool_t f;
1396 #define URL_MAX_LAUNCH (2 * MAILTMPLEN)
1398 if(handle->h.url.tool){
1399 char *toolp, *cmdp, *p, cmd[URL_MAX_LAUNCH + 4];
1400 int mode, copied = 0;
1401 PIPE_S *syspipe;
1403 toolp = handle->h.url.tool;
1405 /* This code used to quote a URL to prevent arbitrary command execution
1406 * through a URL. The plan was to quote the URL with single quotes,
1407 * and this used to work. BUT some shells do not care about quoting
1408 * and interpret some characters regardless of single quotes. The
1409 * simplest solution is to escape those characters, but then some
1410 * shells will see the escape characters and some others will not.
1411 * It's a mess. There are several ways to go around this mess,
1412 * including adding configuration options (one more!?), or forget
1413 * about shells. What we do is to forget about shells, and execute
1414 * the code as a PIPE_NOSHELL.
1417 cmdp = cmd;
1418 while(cmdp-cmd < URL_MAX_LAUNCH)
1419 if((!*toolp && !copied)
1420 || (*toolp == '_' && !strncmp(toolp + 1, "URL_", 4))){
1422 /* implicit _URL_ at end */
1423 if(!*toolp)
1424 *cmdp++ = ' ';
1426 if(cmdp[-1] == '\'') /* unquote old '_URL_' */
1427 cmdp--;
1429 copied = 1;
1430 for(p = handle->h.url.path;
1431 p && *p && cmdp-cmd < URL_MAX_LAUNCH; p++)
1432 *cmdp++ = *p;
1434 *cmdp = '\0';
1436 if(*toolp)
1437 toolp += 5; /* length of "_URL_" */
1438 if(*toolp == '\'')
1439 toolp++;
1441 else
1442 if(!(*cmdp++ = *toolp++))
1443 break;
1445 if(cmdp-cmd >= URL_MAX_LAUNCH)
1446 return(url_launch_too_long(rv));
1448 mode = PIPE_RESET | PIPE_USER | PIPE_RUNNOW | PIPE_NOSHELL ;
1449 if((syspipe = open_system_pipe(cmd, NULL, NULL, mode, 0, pipe_callback, pipe_report_error)) != NULL){
1450 close_system_pipe(&syspipe, NULL, pipe_callback);
1451 q_status_message(SM_ORDER, 0, 4, _("VIEWER command completed"));
1453 else
1454 q_status_message1(SM_ORDER, 3, 4,
1455 /* TRANSLATORS: Cannot start command : <command name> */
1456 _("Cannot start command : %s"), cmd);
1458 else if((f = url_local_handler(handle->h.url.path)) != NULL){
1459 if((*f)(handle->h.url.path) > 1)
1460 rv = 1; /* done! */
1462 else
1463 q_status_message1(SM_ORDER, 2, 2,
1464 _("\"URL-Viewer\" not defined: Can't open %s"),
1465 handle->h.url.path);
1467 return(rv);
1472 url_launch_too_long(int return_value)
1474 q_status_message(SM_ORDER | SM_DING, 3, 3,
1475 "Can't spawn. Command too long.");
1476 return(return_value);
1480 char *
1481 url_external_handler(HANDLE_S *handle, int specific)
1483 char **l, *test, *cmd, *p, *q, *ep;
1484 int i, specific_match;
1486 for(l = ps_global->VAR_BROWSER ; l && *l; l++){
1487 get_pair(*l, &test, &cmd, 0, 1);
1488 dprint((5, "TEST: \"%s\" CMD: \"%s\"\n",
1489 test ? test : "<NULL>", cmd ? cmd : "<NULL>"));
1490 removing_quotes(cmd);
1491 if(valid_filter_command(&cmd)){
1492 specific_match = 0;
1494 if((p = test) != NULL){
1495 while(*p && cmd)
1496 if(*p == '_'){
1497 if(!strncmp(p+1, "TEST(", 5)
1498 && (ep = strstr(p+6, ")_"))){
1499 *ep = '\0';
1501 if(exec_mailcap_test_cmd(p+6) == 0){
1502 p = ep + 2;
1504 else{
1505 dprint((5,"failed handler TEST\n"));
1506 fs_give((void **) &cmd);
1509 else if(!strncmp(p+1, "SCHEME(", 7)
1510 && (ep = strstr(p+8, ")_"))){
1511 *ep = '\0';
1513 p += 8;
1515 if((q = strchr(p, ',')) != NULL)
1516 *q++ = '\0';
1517 else
1518 q = ep;
1519 while(!((i = strlen(p))
1520 && ((p[i-1] == ':'
1521 && handle->h.url.path[i - 1] == ':')
1522 || (p[i-1] != ':'
1523 && handle->h.url.path[i] == ':'))
1524 && !struncmp(handle->h.url.path, p, i))
1525 && *(p = q));
1527 if(*p){
1528 specific_match = 1;
1529 p = ep + 2;
1531 else{
1532 dprint((5,"failed handler SCHEME\n"));
1533 fs_give((void **) &cmd);
1536 else{
1537 dprint((5, "UNKNOWN underscore test\n"));
1538 fs_give((void **) &cmd);
1541 else if(isspace((unsigned char) *p)){
1542 p++;
1544 else{
1545 dprint((1,"bogus handler test: \"%s\"",
1546 test ? test : "?"));
1547 fs_give((void **) &cmd);
1550 fs_give((void **) &test);
1553 if(cmd && (!specific || specific_match))
1554 return(cmd);
1557 if(test)
1558 fs_give((void **) &test);
1560 if(cmd)
1561 fs_give((void **) &cmd);
1564 cmd = NULL;
1566 if(!specific){
1567 cmd = url_os_specified_browser(handle->h.url.path);
1569 * Last chance, anything handling "text/html" in mailcap...
1571 if(!cmd && mailcap_can_display(TYPETEXT, "html", NULL, 0)){
1572 MCAP_CMD_S *mc_cmd;
1574 mc_cmd = mailcap_build_command(TYPETEXT, "html",
1575 NULL, "_URL_", NULL, 0);
1577 * right now URL viewing won't return anything requiring
1578 * special handling
1580 if(mc_cmd){
1581 cmd = mc_cmd->command;
1582 fs_give((void **)&mc_cmd);
1587 return(cmd);
1591 url_tool_t
1592 url_local_handler(char *s)
1594 int i;
1595 static struct url_t {
1596 char *url;
1597 short len;
1598 url_tool_t f;
1599 } handlers[] = {
1600 {"mailto:", 7, url_local_mailto}, /* see url_tool_t def's */
1601 {"imap://", 7, url_local_imap}, /* for explanations */
1602 {"nntp://", 7, url_local_nntp},
1603 {"file://", 7, url_local_file},
1604 #ifdef ENABLE_LDAP
1605 {"ldap://", 7, url_local_ldap},
1606 #endif
1607 {"news:", 5, url_local_news},
1608 {"x-alpine-phone-home:", 20, url_local_phone_home},
1609 {"x-alpine-gripe:", 15, gripe_gripe_to},
1610 {"x-alpine-help:", 14, url_local_helper},
1611 {"x-pine-help:", 12, url_local_helper},
1612 {"x-alpine-config:", 16, url_local_config},
1613 {"x-alpine-cert:", 14, url_local_certdetails},
1614 {"#", 1, url_local_fragment},
1615 {NULL, 0, NULL}
1618 for(i = 0; handlers[i].url ; i++)
1619 if(!struncmp(s, handlers[i].url, handlers[i].len))
1620 return(handlers[i].f);
1622 return(NULL);
1628 * mailto URL digester ala draft-hoffman-mailto-url-02.txt
1631 url_local_mailto(char *url)
1633 return(url_local_mailto_and_atts(url, NULL));
1637 url_local_mailto_and_atts(char *url, PATMT *attachlist)
1639 ENVELOPE *outgoing;
1640 BODY *body = NULL;
1641 REPLY_S fake_reply;
1642 char *sig, *urlp, *p, *hname, *hvalue;
1643 int rv = 0;
1644 long rflags;
1645 int was_a_body = 0, impl, template_len = 0;
1646 char *fcc = NULL;
1647 PAT_STATE dummy;
1648 REDRAFT_POS_S *redraft_pos = NULL;
1649 ACTION_S *role = NULL;
1651 outgoing = mail_newenvelope();
1652 outgoing->message_id = generate_message_id();
1653 body = mail_newbody();
1654 body->type = TYPETEXT;
1655 if((body->contents.text.data = (void *) so_get(PicoText,NULL,EDIT_ACCESS)) != NULL){
1657 * URL format is:
1659 * mailtoURL = "mailto:" [ to ] [ headers ]
1660 * to = #mailbox
1661 * headers = "?" header *( "&" header )
1662 * header = hname "=" hvalue
1663 * hname = *urlc
1664 * hvalue = *urlc
1666 * NOTE2: "from" and "bcc" are intentionally excluded from
1667 * the list of understood "header" fields
1669 if((p = strchr(urlp = cpystr(url+7), '?')) != NULL)
1670 *p++ = '\0'; /* headers? Tie off mailbox */
1672 /* grok mailbox as first "to", then roll thru specific headers */
1673 if(*urlp)
1674 rfc822_parse_adrlist(&outgoing->to,
1675 rfc1738_str(urlp),
1676 ps_global->maildomain);
1678 while(p){
1679 /* Find next "header" */
1680 if((p = strchr(hname = p, '&')) != NULL)
1681 *p++ = '\0'; /* tie off "header" */
1683 if((hvalue = strchr(hname, '=')) != NULL)
1684 *hvalue++ = '\0'; /* tie off hname */
1686 if(!hvalue || !strucmp(hname, "subject")){
1687 char *sub = rfc1738_str(hvalue ? hvalue : hname);
1689 if(outgoing->subject){
1690 int len = strlen(outgoing->subject);
1692 fs_resize((void **) &outgoing->subject,
1693 (len + strlen(sub) + 2) * sizeof(char));
1694 snprintf(outgoing->subject + len, strlen(sub)+2, " %s", sub);
1695 outgoing->subject[len + strlen(sub) + 2 - 1] = '\0';
1697 else
1698 outgoing->subject = cpystr(sub);
1700 else if(!strucmp(hname, "to")){
1701 url_mailto_addr(&outgoing->to, hvalue);
1703 else if(!strucmp(hname, "cc")){
1704 url_mailto_addr(&outgoing->cc, hvalue);
1706 else if(!strucmp(hname, "bcc")){
1707 q_status_message(SM_ORDER, 3, 4,
1708 "\"Bcc\" header in mailto url ignored");
1710 else if(!strucmp(hname, "from")){
1711 q_status_message(SM_ORDER, 3, 4,
1712 "\"From\" header in mailto url ignored");
1714 else if(!strucmp(hname, "body")){
1715 char *sub = rfc1738_str(hvalue ? hvalue : "");
1717 so_puts((STORE_S *)body->contents.text.data, sub);
1718 so_puts((STORE_S *)body->contents.text.data, NEWLINE);
1719 was_a_body++;
1723 fs_give((void **) &urlp);
1725 rflags = ROLE_COMPOSE;
1726 if(nonempty_patterns(rflags, &dummy)){
1727 role = set_role_from_msg(ps_global, rflags, -1L, NULL);
1728 if(confirm_role(rflags, &role))
1729 role = combine_inherited_role(role);
1730 else{ /* cancel */
1731 role = NULL;
1732 cmd_cancelled("Composition");
1733 goto outta_here;
1737 if(role)
1738 q_status_message1(SM_ORDER, 3, 4, "Composing using role \"%s\"",
1739 role->nick);
1741 if(!was_a_body && role && role->template){
1742 char *filtered;
1744 impl = 1; /* leave cursor in header if not explicit */
1745 filtered = detoken(role, NULL, 0, 0, 0, &redraft_pos, &impl);
1746 if(filtered){
1747 if(*filtered){
1748 so_puts((STORE_S *)body->contents.text.data, filtered);
1749 if(impl == 1)
1750 template_len = strlen(filtered);
1753 fs_give((void **)&filtered);
1756 else
1757 impl = 1;
1759 if(!was_a_body && (sig = detoken(role, NULL, 2, 0, 1, &redraft_pos,
1760 &impl))){
1761 if(impl == 2)
1762 redraft_pos->offset += template_len;
1764 if(*sig)
1765 so_puts((STORE_S *)body->contents.text.data, sig);
1767 fs_give((void **)&sig);
1770 memset((void *)&fake_reply, 0, sizeof(fake_reply));
1771 fake_reply.pseudo = 1;
1772 fake_reply.data.pico_flags = (outgoing->subject) ? P_BODY : P_HEADEND;
1775 if(!(role && role->fcc))
1776 fcc = get_fcc_based_on_to(outgoing->to);
1778 if(attachlist)
1779 create_message_body(&body, attachlist, 0);
1781 pine_send(outgoing, &body, "\"MAILTO\" COMPOSE",
1782 role, fcc, &fake_reply, redraft_pos, NULL, NULL, PS_STICKY_TO);
1783 rv++;
1784 ps_global->mangled_screen = 1;
1786 else
1787 q_status_message(SM_ORDER | SM_DING, 3, 4,
1788 _("Can't create space for composer"));
1790 outta_here:
1791 if(outgoing)
1792 mail_free_envelope(&outgoing);
1794 if(body)
1795 pine_free_body(&body);
1797 if(fcc)
1798 fs_give((void **)&fcc);
1800 free_redraft_pos(&redraft_pos);
1801 free_action(&role);
1803 return(rv);
1807 void
1808 url_mailto_addr(struct mail_address **a, char *a_raw)
1810 char *p = cpystr(rfc1738_str(a_raw));
1812 while(*a) /* append to address list */
1813 a = &(*a)->next;
1815 rfc822_parse_adrlist(a, p, ps_global->maildomain);
1816 fs_give((void **) &p);
1821 * imap URL digester ala RFC 2192
1824 url_local_imap(char *url)
1826 char *folder, *mailbox = NULL, *errstr = NULL, *search = NULL,
1827 newfolder[MAILTMPLEN];
1828 int rv;
1829 long i;
1830 imapuid_t uid = 0L, uid_val = 0L;
1831 CONTEXT_S *fake_context;
1832 MESSAGECACHE *mc;
1834 rv = url_imap_folder(url, &folder, &uid, &uid_val, &search, 0);
1835 switch(rv & URL_IMAP_MASK){
1836 case URL_IMAP_IMAILBOXLIST :
1837 /* BUG: deal with lsub tag */
1838 if((fake_context = new_context(folder, NULL)) != NULL){
1839 newfolder[0] = '\0';
1840 if(display_folder_list(&fake_context, newfolder,
1841 0, folders_for_goto))
1842 if(strlen(newfolder) + 1 < MAILTMPLEN)
1843 mailbox = newfolder;
1845 else
1846 errstr = "Problem building URL's folder list";
1848 fs_give((void **) &folder);
1849 free_context(&fake_context);
1851 if(mailbox){
1852 return(1);
1854 else if(errstr)
1855 q_status_message(SM_ORDER|SM_DING, 3, 3, errstr);
1856 else
1857 cmd_cancelled("URL Launch");
1859 break;
1861 case URL_IMAP_IMESSAGEPART :
1862 case URL_IMAP_IMESSAGELIST :
1863 if(ps_global && ps_global->ttyo){
1864 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
1865 ps_global->mangled_footer = 1;
1868 rv = do_broach_folder(folder, NULL, NULL, 0L);
1869 fs_give((void **) &folder);
1870 switch(rv){
1871 case -1 : /* utter failure */
1872 ps_global->next_screen = main_menu_screen;
1873 break;
1875 case 0 : /* same folder reopened */
1876 ps_global->next_screen = mail_index_screen;
1877 break;
1879 case 1 : /* requested folder open */
1880 ps_global->next_screen = mail_index_screen;
1882 if(uid_val && uid_val != ps_global->mail_stream->uid_validity){
1883 /* Complain! */
1884 q_status_message(SM_ORDER|SM_DING, 3, 3,
1885 "Warning! Referenced folder changed since URL recorded");
1888 if(uid){
1890 * Make specified message the currently selected..
1892 for(i = 1L; i <= mn_get_total(ps_global->msgmap); i++)
1893 if(mail_uid(ps_global->mail_stream, i) == uid){
1894 ps_global->next_screen = mail_view_screen;
1895 mn_set_cur(ps_global->msgmap, i);
1896 break;
1899 if(i > mn_get_total(ps_global->msgmap))
1900 q_status_message(SM_ORDER, 2, 3,
1901 "Couldn't find specified article number");
1903 else if(search){
1905 * Select the specified messages
1906 * and present a zoom'd index...
1908 /* BUG: not dealing with CHARSET yet */
1910 /* ANOTHER BUG: mail_criteria is a compatibility routine for IMAP2BIS
1911 * so it doesn't know about IMAP4 search criteria, like SENTSINCE.
1912 * It also doesn't handle literals. */
1914 pine_mail_search_full(ps_global->mail_stream, NULL,
1915 mail_criteria(search),
1916 SE_NOPREFETCH | SE_FREE);
1918 for(i = 1L; i <= mn_get_total(ps_global->msgmap); i++)
1919 if(ps_global->mail_stream
1920 && i <= ps_global->mail_stream->nmsgs
1921 && (mc = mail_elt(ps_global->mail_stream, i))
1922 && mc->searched)
1923 set_lflag(ps_global->mail_stream,
1924 ps_global->msgmap, i, MN_SLCT, 1);
1926 if((i = any_lflagged(ps_global->msgmap, MN_SLCT)) != 0){
1928 q_status_message2(SM_ORDER, 0, 3,
1929 "%s message%s selected",
1930 long2string(i), plural(i));
1931 /* Zoom the index! */
1932 zoom_index(ps_global, ps_global->mail_stream,
1933 ps_global->msgmap, MN_SLCT);
1938 return(1);
1940 default:
1941 case URL_IMAP_ERROR :
1942 break;
1945 return(0);
1950 url_local_nntp(char *url)
1952 char folder[2*MAILTMPLEN], *group;
1953 int group_len;
1954 long i, article_num;
1956 /* no hostport, no url, end of story */
1957 if((group = strchr(url + 7, '/')) != 0){
1958 group++;
1959 for(group_len = 0; group[group_len] && group[group_len] != '/';
1960 group_len++)
1961 if(!rfc1738_group(&group[group_len]))
1962 /* TRANSLATORS: these are errors in news group URLs */
1963 return(url_bogus(url, _("Invalid newsgroup specified")));
1965 if(group_len){
1966 snprintf(folder, sizeof(folder), "{%.*s/nntp}#news.%.*s",
1967 (int) MIN((group - 1) - (url + 7), MAILTMPLEN-20), url + 7,
1968 (int) MIN(group_len, MAILTMPLEN-20), group);
1969 folder[sizeof(folder)-1] = '\0';
1971 else
1972 return(url_bogus(url, _("No newsgroup specified")));
1974 else
1975 return(url_bogus(url, _("No server specified")));
1977 if(ps_global && ps_global->ttyo){
1978 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
1979 ps_global->mangled_footer = 1;
1982 switch(do_broach_folder(rfc1738_str(folder), NULL, NULL, 0L)){
1983 case -1 : /* utter failure */
1984 ps_global->next_screen = main_menu_screen;
1985 break;
1987 case 0 : /* same folder reopened */
1988 ps_global->next_screen = mail_index_screen;
1989 break;
1991 case 1 : /* requested folder open */
1992 ps_global->next_screen = mail_index_screen;
1994 /* grok article number --> c-client UID */
1995 if(group[group_len++] == '/'
1996 && (article_num = atol(&group[group_len]))){
1998 * Make the requested article our current message
2000 for(i = 1; i <= mn_get_nmsgs(ps_global->msgmap); i++)
2001 if(mail_uid(ps_global->mail_stream, i) == article_num){
2002 ps_global->next_screen = mail_view_screen;
2003 if((i = mn_raw2m(ps_global->msgmap, i)) != 0)
2004 mn_set_cur(ps_global->msgmap, i);
2005 break;
2008 if(i == 0 || i > mn_get_total(ps_global->msgmap))
2009 q_status_message(SM_ORDER, 2, 3,
2010 _("Couldn't find specified article number"));
2013 break;
2016 ps_global->redrawer = (void(*)(void))NULL;
2017 return(1);
2022 url_local_news(char *url)
2024 char folder[MAILTMPLEN], *p;
2025 CONTEXT_S *cntxt = NULL;
2028 * NOTE: NO SUPPORT for '*' or message-id
2030 if(*(url+5)){
2031 if(*(url+5) == '/' && *(url+6) == '/')
2032 return(url_local_nntp(url)); /* really meant "nntp://"? */
2034 if(ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0])
2036 * BUG: Only the first NNTP server is tried.
2038 snprintf(folder, sizeof(folder), "{%s/nntp}#news.", ps_global->VAR_NNTP_SERVER[0]);
2039 else
2040 strncpy(folder, "#news.", sizeof(folder));
2042 folder[sizeof(folder)-1] = '\0';
2044 for(p = strncpy(folder + strlen(folder), url + 5, sizeof(folder)-strlen(folder)-1);
2045 *p && rfc1738_group(p);
2046 p++)
2049 if(*p)
2050 return(url_bogus(url, "Invalid newsgroup specified"));
2052 else{ /* fish first group from newsrc */
2053 FOLDER_S *f;
2054 int alphaorder;
2056 folder[0] = '\0';
2058 /* Find first news context */
2059 for(cntxt = ps_global->context_list;
2060 cntxt && !(cntxt->use & CNTXT_NEWS);
2061 cntxt = cntxt->next)
2064 if(cntxt){
2065 if((alphaorder = F_OFF(F_READ_IN_NEWSRC_ORDER, ps_global)) != 0)
2066 (void) F_SET(F_READ_IN_NEWSRC_ORDER, ps_global, 1);
2068 build_folder_list(NULL, cntxt, NULL, NULL, BFL_LSUB);
2069 if((f = folder_entry(0, FOLDERS(cntxt))) != NULL){
2070 strncpy(folder, f->name, sizeof(folder));
2071 folder[sizeof(folder)-1] = '\0';
2074 free_folder_list(cntxt);
2076 if(alphaorder)
2077 (void) F_SET(F_READ_IN_NEWSRC_ORDER, ps_global, 0);
2080 if(folder[0] == '\0'){
2081 q_status_message(SM_ORDER | SM_DING, 3, 3,
2082 "No default newsgroup");
2083 return(0);
2087 if(ps_global && ps_global->ttyo){
2088 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2089 ps_global->mangled_footer = 1;
2092 if(do_broach_folder(rfc1738_str(folder), cntxt, NULL, 0L) < 0)
2093 ps_global->next_screen = main_menu_screen;
2094 else
2095 ps_global->next_screen = mail_index_screen;
2097 ps_global->redrawer = (void(*)(void))NULL;
2099 return(1);
2104 url_local_file(char *file_url)
2106 if(want_to(
2107 /* TRANSLATORS: this is a warning that the file URL can cause programs to run which may
2108 be a security problem. We are asking the user to confirm that they want to do this. */
2109 _("\"file\" URL may cause programs to be run on your system. Run anyway"),
2110 'n', 0, NO_HELP, WT_NORM) == 'y'){
2111 HANDLE_S handle;
2113 /* fake a handle */
2114 handle.h.url.path = file_url;
2115 if((handle.h.url.tool = url_external_handler(&handle, 1))
2116 || (handle.h.url.tool = url_external_handler(&handle, 0))){
2117 url_launch(&handle);
2118 return 1;
2120 else{
2121 q_status_message(SM_ORDER, 0, 4,
2122 _("No viewer for \"file\" URL. VIEWER command cancelled"));
2123 return 0;
2126 q_status_message(SM_ORDER, 0, 4, _("VIEWER command cancelled"));
2127 return 0;
2132 url_local_fragment(char *fragment)
2134 SCRLCTRL_S *st = scroll_state(SS_CUR);
2135 HANDLE_S *hp;
2138 * find a handle with the fragment's name
2140 for(hp = st->parms->text.handles; hp; hp = hp->next)
2141 if(hp->type == URL && hp->h.url.name
2142 && !strcmp(hp->h.url.name, fragment + 1))
2143 break;
2145 if(!hp)
2146 for(hp = st->parms->text.handles->prev; hp; hp = hp->prev)
2147 if(hp->type == URL && hp->h.url.name
2148 && !strcmp(hp->h.url.name, fragment + 1))
2149 break;
2152 * set the top line of the display to contain this line
2154 if(hp && hp->loc){
2155 st->top_text_line = hp->loc->where.row;
2156 ps_global->mangled_body = 1;
2158 else
2159 q_status_message1(SM_ORDER | SM_DING, 0, 3,
2160 "Can't find fragment: %s", fragment);
2162 return(1);
2167 url_local_phone_home(char *URL)
2169 phone_home(URL + strlen("x-alpine-phone-home:"));
2170 return(2);
2175 * Format editorial comment referencing screen offering
2176 * List-* header supplied commands
2179 rfc2369_editorial(long int msgno, HANDLE_S **handlesp, int flags, int width, gf_io_t pc)
2181 char *p, *hdrp, *hdrs[MLCMD_COUNT + 1],
2182 color[64], buf[2048];
2183 int i, n, rv = TRUE;
2184 HANDLE_S *h = NULL;
2186 if((flags & FM_DISPLAY)
2187 && (hdrp = pine_fetchheader_lines(ps_global->mail_stream, msgno,
2188 NULL, rfc2369_hdrs(hdrs)))){
2189 if(*hdrp){
2190 snprintf(buf, sizeof(buf), "Note: This message contains ");
2191 buf[sizeof(buf)-1] = '\0';
2192 p = buf + strlen(buf);
2194 if(handlesp){
2195 h = new_handle(handlesp);
2196 h->type = Function;
2197 h->h.func.f = rfc2369_display;
2198 h->h.func.args.stream = ps_global->mail_stream;
2199 h->h.func.args.msgmap = ps_global->msgmap;
2200 h->h.func.args.msgno = msgno;
2202 if(!(flags & FM_NOCOLOR)
2203 && handle_start_color(color, sizeof(color), &n, 0)){
2204 if((p-buf)+n < sizeof(buf))
2205 for(i = 0; i < n; i++)
2206 *p++ = color[i];
2208 else if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)){
2209 *p++ = TAG_EMBED;
2210 *p++ = TAG_BOLDON;
2213 if((p-buf)+2 < sizeof(buf)){
2214 *p++ = TAG_EMBED;
2215 *p++ = TAG_HANDLE;
2218 snprintf(p + 1, sizeof(buf)-(p+1-buf), "%d", h->key);
2219 buf[sizeof(buf)-1] = '\0';
2220 *p = strlen(p + 1);
2221 p += (*p + 1);
2224 sstrncpy(&p, "email list management information", sizeof(buf)-(p-buf));
2225 buf[sizeof(buf)-1] = '\0';
2227 if(h){
2228 /* in case it was the current selection */
2229 if((p-buf)+2 < sizeof(buf)){
2230 *p++ = TAG_EMBED;
2231 *p++ = TAG_INVOFF;
2234 if(handle_end_color(color, sizeof(color), &n)){
2235 if((p-buf)+n < sizeof(buf))
2236 for(i = 0; i < n; i++)
2237 *p++ = color[i];
2239 else{
2240 if((p-buf)+2 < sizeof(buf)){
2241 *p++ = TAG_EMBED;
2242 *p++ = TAG_BOLDOFF;
2246 if(p-buf < sizeof(buf))
2247 *p = '\0';
2250 buf[sizeof(buf)-1] = '\0';
2252 rv = (gf_puts(NEWLINE, pc)
2253 && format_editorial(buf, width, flags, handlesp, pc) == NULL
2254 && gf_puts(NEWLINE, pc));
2257 fs_give((void **) &hdrp);
2260 return(rv);
2265 /*----------------------------------------------------------------------
2266 routine for displaying text on the screen.
2268 Args: sparms -- structure of args controlling what happens outside
2269 just the business of managing text scrolling
2271 This displays in three different kinds of text. One is an array of
2272 lines passed in in text_array. The other is a simple long string of
2273 characters passed in in text.
2275 The style determines what some of the error messages will be, and
2276 what commands are available as different things are appropriate for
2277 help text than for message text etc.
2279 ---*/
2282 scrolltool(SCROLL_S *sparms)
2284 register long cur_top_line, num_display_lines;
2285 UCS ch;
2286 int result, done, cmd, found_on, found_on_index,
2287 first_view, force, scroll_lines, km_size,
2288 cursor_row, cursor_col, km_popped;
2289 char *utf8str;
2290 long jn;
2291 struct key_menu *km;
2292 HANDLE_S *next_handle;
2293 bitmap_t bitmap;
2294 OtherMenu what;
2295 Pos whereis_pos;
2297 num_display_lines = SCROLL_LINES(ps_global);
2298 km_popped = 0;
2299 ps_global->mangled_header = 1;
2300 ps_global->mangled_footer = 1;
2301 ps_global->mangled_body = !sparms->body_valid;
2304 what = sparms->keys.what; /* which key menu to display */
2305 cur_top_line = 0;
2306 done = 0;
2307 found_on = -1;
2308 found_on_index = -1;
2309 first_view = 1;
2310 if(sparms->quell_first_view)
2311 first_view = 0;
2313 force = 0;
2314 ch = 'x'; /* for first time through */
2315 whereis_pos.row = 0;
2316 whereis_pos.col = 0;
2317 next_handle = sparms->text.handles;
2319 set_scroll_text(sparms, cur_top_line, scroll_state(SS_NEW));
2320 format_scroll_text();
2322 if((km = sparms->keys.menu) != NULL){
2323 memcpy(bitmap, sparms->keys.bitmap, sizeof(bitmap_t));
2325 else{
2326 setbitmap(bitmap);
2327 km = &simple_text_keymenu;
2328 #ifdef _WINDOWS
2329 sparms->mouse.popup = simple_text_popup;
2330 #endif
2333 if(!sparms->bar.title)
2334 sparms->bar.title = "Text";
2336 if(sparms->bar.style == TitleBarNone){
2337 if(THREADING() && sp_viewing_a_thread(ps_global->mail_stream))
2338 sparms->bar.style = ThrdMsgPercent;
2339 else
2340 sparms->bar.style = MsgTextPercent;
2343 switch(sparms->start.on){
2344 case LastPage :
2345 cur_top_line = MAX(0, scroll_text_lines() - (num_display_lines-2));
2346 if(F_ON(F_SHOW_CURSOR, ps_global)){
2347 whereis_pos.row = scroll_text_lines() - cur_top_line;
2348 found_on = scroll_text_lines() - 1;
2351 break;
2353 case Fragment :
2354 if(sparms->start.loc.frag){
2355 (void) url_local_fragment(sparms->start.loc.frag);
2357 cur_top_line = scroll_state(SS_CUR)->top_text_line;
2359 if(F_ON(F_SHOW_CURSOR, ps_global)){
2360 whereis_pos.row = scroll_text_lines() - cur_top_line;
2361 found_on = scroll_text_lines() - 1;
2365 break;
2367 case Offset :
2368 if(sparms->start.loc.offset){
2369 for(cur_top_line = 0L;
2370 cur_top_line + 1 < scroll_text_lines()
2371 && (sparms->start.loc.offset
2372 -= scroll_handle_column(cur_top_line,
2373 -1)) >= 0;
2374 cur_top_line++)
2378 break;
2380 case Handle :
2381 if(scroll_handle_obscured(sparms->text.handles))
2382 cur_top_line = scroll_handle_reframe(-1, TRUE);
2384 break;
2386 default : /* no-op */
2387 break;
2390 /* prepare for calls below to tell us where to go */
2391 ps_global->next_screen = SCREEN_FUN_NULL;
2393 cancel_busy_cue(-1);
2395 while(!done) {
2396 ps_global->user_says_cancel = 0;
2397 if(km_popped){
2398 km_popped--;
2399 if(km_popped == 0){
2400 clearfooter(ps_global);
2401 ps_global->mangled_body = 1;
2405 if(ps_global->mangled_screen) {
2406 ps_global->mangled_header = 1;
2407 ps_global->mangled_footer = 1;
2408 ps_global->mangled_body = 1;
2411 if(!sparms->quell_newmail && streams_died())
2412 ps_global->mangled_header = 1;
2414 dprint((9, "@@@@ current:%ld\n",
2415 mn_get_cur(ps_global->msgmap)));
2418 /*==================== All Screen painting ====================*/
2419 /*-------------- The title bar ---------------*/
2420 update_scroll_titlebar(cur_top_line, ps_global->mangled_header);
2422 if(ps_global->mangled_screen){
2423 /* this is the only line not cleared by header, body or footer
2424 * repaint calls....
2426 ClearLine(1);
2427 ps_global->mangled_screen = 0;
2430 /*---- Scroll or update the body of the text on the screen -------*/
2431 cur_top_line = scroll_scroll_text(cur_top_line, next_handle,
2432 ps_global->mangled_body);
2433 ps_global->redrawer = redraw_scroll_text;
2434 ps_global->mangled_body = 0;
2436 /*--- Check to see if keymenu might change based on next_handle --*/
2437 if(sparms->text.handles != next_handle)
2438 ps_global->mangled_footer = 1;
2440 if(next_handle)
2441 sparms->text.handles = next_handle;
2443 /*------------- The key menu footer --------------------*/
2444 if(ps_global->mangled_footer || sparms->keys.each_cmd){
2445 if(km_popped){
2446 FOOTER_ROWS(ps_global) = 3;
2447 clearfooter(ps_global);
2450 if(F_ON(F_ARROW_NAV, ps_global)){
2451 menu_clear_binding(km, KEY_LEFT);
2452 if((cmd = menu_clear_binding(km, '<')) != MC_UNKNOWN){
2453 menu_add_binding(km, '<', cmd);
2454 menu_add_binding(km, KEY_LEFT, cmd);
2458 if(F_ON(F_ARROW_NAV, ps_global)){
2459 menu_clear_binding(km, KEY_RIGHT);
2460 if((cmd = menu_clear_binding(km, '>')) != MC_UNKNOWN){
2461 menu_add_binding(km, '>', cmd);
2462 menu_add_binding(km, KEY_RIGHT, cmd);
2466 if(sparms->keys.each_cmd){
2467 (*sparms->keys.each_cmd)(sparms,
2468 scroll_handle_obscured(sparms->text.handles));
2469 memcpy(bitmap, sparms->keys.bitmap, sizeof(bitmap_t));
2472 if(menu_binding_index(km, MC_JUMP) >= 0){
2473 for(cmd = 0; cmd < 10; cmd++)
2474 if(F_ON(F_ENABLE_JUMP, ps_global))
2475 (void) menu_add_binding(km, '0' + cmd, MC_JUMP);
2476 else
2477 (void) menu_clear_binding(km, '0' + cmd);
2480 draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
2481 1-FOOTER_ROWS(ps_global), 0, what);
2482 what = SameMenu;
2483 ps_global->mangled_footer = 0;
2484 if(km_popped){
2485 FOOTER_ROWS(ps_global) = 1;
2486 mark_keymenu_dirty();
2490 if((ps_global->first_time_user || ps_global->show_new_version)
2491 && first_view && sparms->text.handles
2492 && (sparms->text.handles->next || sparms->text.handles->prev)
2493 && !sparms->quell_help)
2494 q_status_message(SM_ORDER, 0, 3, HANDLE_INIT_MSG);
2496 /*============ Check for New Mail and CheckPoint ============*/
2497 if(!sparms->quell_newmail &&
2498 new_mail(force, NM_TIMING(ch), NM_STATUS_MSG) >= 0){
2499 update_scroll_titlebar(cur_top_line, 1);
2500 if(ps_global->mangled_footer)
2501 draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
2502 1-FOOTER_ROWS(ps_global), 0, what);
2504 ps_global->mangled_footer = 0;
2508 * If an expunge of the current message happened during the
2509 * new mail check we want to bail out of here. See mm_expunged.
2511 if(ps_global->next_screen != SCREEN_FUN_NULL){
2512 done = 1;
2513 continue;
2516 if(ps_global->noticed_change_in_unseen){
2517 ps_global->noticed_change_in_unseen = 0; /* redraw only once */
2518 cmd = MC_RESIZE; /* causes cursor to be saved in folder_lister */
2519 done = 1;
2520 continue;
2523 if(first_view && num_display_lines >= scroll_text_lines())
2524 q_status_message1(SM_INFO, 0, 1, "ALL of %s", STYLE_NAME(sparms));
2527 force = 0; /* may not need to next time around */
2528 first_view = 0; /* check_point a priority any more? */
2530 /*==================== Output the status message ==============*/
2531 if(!sparms->no_stat_msg){
2532 if(km_popped){
2533 FOOTER_ROWS(ps_global) = 3;
2534 mark_status_unknown();
2537 display_message(ch);
2538 if(km_popped){
2539 FOOTER_ROWS(ps_global) = 1;
2540 mark_status_unknown();
2544 if(F_ON(F_SHOW_CURSOR, ps_global)){
2545 #ifdef WINDOWS
2546 if(cur_top_line != scroll_state(SS_CUR)->top_text_line)
2547 whereis_pos.row = 0;
2548 #endif
2550 if(whereis_pos.row > 0){
2551 cursor_row = SCROLL_LINES_ABOVE(ps_global)
2552 + whereis_pos.row - 1;
2553 cursor_col = whereis_pos.col;
2555 else{
2556 POSLIST_S *lp = NULL;
2558 if(sparms->text.handles &&
2559 !scroll_handle_obscured(sparms->text.handles)){
2560 SCRLCTRL_S *st = scroll_state(SS_CUR);
2562 for(lp = sparms->text.handles->loc; lp; lp = lp->next)
2563 if(lp->where.row >= st->top_text_line
2564 && lp->where.row < st->top_text_line
2565 + st->screen.length){
2566 cursor_row = lp->where.row - cur_top_line
2567 + SCROLL_LINES_ABOVE(ps_global);
2568 cursor_col = lp->where.col;
2569 break;
2573 if(!lp){
2574 cursor_col = 0;
2575 /* first new line of text */
2576 cursor_row = SCROLL_LINES_ABOVE(ps_global) +
2577 ((cur_top_line == 0) ? 0 : ps_global->viewer_overlap);
2581 else{
2582 cursor_col = 0;
2583 cursor_row = ps_global->ttyo->screen_rows
2584 - SCROLL_LINES_BELOW(ps_global);
2587 MoveCursor(cursor_row, cursor_col);
2589 /*================ Get command and validate =====================*/
2590 #ifdef MOUSE
2591 #ifndef WIN32
2592 if(sparms->text.handles)
2593 #endif
2595 mouse_in_content(KEY_MOUSE, -1, -1, 0x5, 0);
2596 register_mfunc(mouse_in_content, HEADER_ROWS(ps_global), 0,
2597 ps_global->ttyo->screen_rows
2598 - (FOOTER_ROWS(ps_global) + 1),
2599 ps_global->ttyo->screen_cols);
2601 #endif
2602 #ifdef _WINDOWS
2603 mswin_allowcopy(mswin_readscrollbuf);
2604 mswin_setscrollcallback(pcpine_do_scroll);
2606 if(sparms->help.text != NO_HELP)
2607 mswin_sethelptextcallback(pcpine_help_scroll);
2609 if(sparms->text.handles
2610 && sparms->text.handles->type != Folder)
2611 mswin_mousetrackcallback(pcpine_view_cursor);
2613 if(ps_global->prev_screen == mail_view_screen)
2614 mswin_setviewinwindcallback(view_in_new_window);
2615 #endif
2616 ch = (sparms->quell_newmail || read_command_prep()) ? read_command(&utf8str) : NO_OP_COMMAND;
2617 #ifdef MOUSE
2618 #ifndef WIN32
2619 if(sparms->text.handles)
2620 #endif
2621 clear_mfunc(mouse_in_content);
2622 #endif
2623 #ifdef _WINDOWS
2624 mswin_allowcopy(NULL);
2625 mswin_setscrollcallback(NULL);
2626 mswin_sethelptextcallback(NULL);
2627 mswin_mousetrackcallback(NULL);
2628 mswin_setviewinwindcallback(NULL);
2629 cur_top_line = scroll_state(SS_CUR)->top_text_line;
2630 #endif
2632 cmd = menu_command(ch, km);
2634 if(km_popped)
2635 switch(cmd){
2636 case MC_NONE :
2637 case MC_OTHER :
2638 case MC_RESIZE:
2639 case MC_REPAINT :
2640 km_popped++;
2641 break;
2643 default:
2644 clearfooter(ps_global);
2645 break;
2649 /*============= Execute command =======================*/
2650 switch(cmd){
2652 /* ------ Help -------*/
2653 case MC_HELP :
2654 if(FOOTER_ROWS(ps_global) == 1 && km_popped == 0){
2655 km_popped = 2;
2656 ps_global->mangled_footer = 1;
2657 break;
2660 whereis_pos.row = 0;
2661 if(sparms->help.text == NO_HELP){
2662 q_status_message(SM_ORDER, 0, 5,
2663 _("No help text currently available"));
2664 break;
2667 km_size = FOOTER_ROWS(ps_global);
2669 helper(sparms->help.text, sparms->help.title, 0);
2671 if(ps_global->next_screen != main_menu_screen
2672 && km_size == FOOTER_ROWS(ps_global)) {
2673 /* Have to reset because helper uses scroll_text */
2674 num_display_lines = SCROLL_LINES(ps_global);
2675 ps_global->mangled_screen = 1;
2677 else
2678 done = 1;
2680 break;
2683 /*---------- Roll keymenu ------*/
2684 case MC_OTHER :
2685 if(F_OFF(F_USE_FK, ps_global))
2686 warn_other_cmds();
2688 what = NextMenu;
2689 ps_global->mangled_footer = 1;
2690 break;
2693 /* -------- Scroll back one page -----------*/
2694 case MC_PAGEUP :
2695 whereis_pos.row = 0;
2696 if(cur_top_line) {
2697 scroll_lines = MIN(MAX(num_display_lines -
2698 ps_global->viewer_overlap, 1), num_display_lines);
2699 cur_top_line -= scroll_lines;
2700 if(cur_top_line <= 0){
2701 cur_top_line = 0;
2702 q_status_message1(SM_INFO, 0, 1, "START of %s",
2703 STYLE_NAME(sparms));
2706 else{
2707 /* hilite last available handle */
2708 next_handle = NULL;
2709 if(sparms->text.handles){
2710 HANDLE_S *h = sparms->text.handles;
2712 while((h = scroll_handle_prev_sel(h))
2713 && !scroll_handle_obscured(h))
2714 next_handle = h;
2717 if(!next_handle)
2718 q_status_message1(SM_ORDER, 0, 1, _("Already at start of %s"),
2719 STYLE_NAME(sparms));
2724 break;
2727 /*---- Scroll down one page -------*/
2728 case MC_PAGEDN :
2729 if(cur_top_line + num_display_lines < scroll_text_lines()){
2730 whereis_pos.row = 0;
2731 scroll_lines = MIN(MAX(num_display_lines -
2732 ps_global->viewer_overlap, 1), num_display_lines);
2733 cur_top_line += scroll_lines;
2735 if(cur_top_line + num_display_lines >= scroll_text_lines())
2736 q_status_message1(SM_INFO, 0, 1, "END of %s",
2737 STYLE_NAME(sparms));
2739 else if(!sparms->end_scroll
2740 || !(done = (*sparms->end_scroll)(sparms))){
2741 q_status_message1(SM_ORDER, 0, 1, _("Already at end of %s"),
2742 STYLE_NAME(sparms));
2743 /* hilite last available handle */
2744 if(sparms->text.handles){
2745 HANDLE_S *h = sparms->text.handles;
2747 while((h = scroll_handle_next_sel(h)) != NULL)
2748 next_handle = h;
2752 break;
2754 /* scroll to the top page */
2755 case MC_HOMEKEY:
2756 if(cur_top_line){
2757 cur_top_line = 0;
2758 q_status_message1(SM_INFO, 0, 1, "START of %s",
2759 STYLE_NAME(sparms));
2762 next_handle = NULL;
2763 if(sparms->text.handles){
2764 HANDLE_S *h = sparms->text.handles;
2766 while((h = scroll_handle_prev_sel(h)) != NULL)
2767 next_handle = h;
2769 break;
2771 /* scroll to the bottom page */
2772 case MC_ENDKEY:
2773 if(cur_top_line + num_display_lines < scroll_text_lines()){
2774 cur_top_line = scroll_text_lines() - MIN(5, num_display_lines);
2775 q_status_message1(SM_INFO, 0, 1, "END of %s",
2776 STYLE_NAME(sparms));
2779 if(sparms->text.handles){
2780 HANDLE_S *h = sparms->text.handles;
2782 while((h = scroll_handle_next_sel(h)) != NULL)
2783 next_handle = h;
2785 break;
2787 /*------ Scroll down one line -----*/
2788 case MC_CHARDOWN :
2789 next_handle = NULL;
2790 if(sparms->text.handles){
2791 if(sparms->vert_handle){
2792 HANDLE_S *h, *h2;
2793 int i, j, k;
2795 h2 = sparms->text.handles;
2796 if(h2->type == Folder && h2->prev && h2->prev->is_dual_do_open)
2797 h2 = h2->prev;
2799 i = h2->loc->where.row + 1;
2800 j = h2->loc->where.col;
2801 for(h = NULL, k = h2->key;
2802 h2 && (!h
2803 || (h->loc->where.row == h2->loc->where.row));
2804 h2 = h2->next)
2805 /* must be different key */
2806 /* ... below current line */
2807 /* ... pref'bly to left */
2808 if(h2->key != k
2809 && h2->loc->where.row >= i){
2810 if(h2->loc->where.col > j){
2811 if(!h)
2812 h = h2;
2814 break;
2816 else
2817 h = h2;
2820 if(h){
2821 whereis_pos.row = 0;
2822 next_handle = h;
2823 if((result = scroll_handle_obscured(next_handle)) != 0){
2824 long new_top;
2826 if(scroll_handle_obscured(sparms->text.handles)
2827 && result > 0)
2828 next_handle = sparms->text.handles;
2830 ps_global->mangled_body++;
2831 new_top = scroll_handle_reframe(next_handle->key,0);
2832 if(new_top >= 0)
2833 cur_top_line = new_top;
2837 else if(!(ch == ctrl('N') || F_ON(F_FORCE_ARROWS, ps_global)))
2838 next_handle = scroll_handle_next(sparms->text.handles);
2841 if(!next_handle){
2842 if(cur_top_line + num_display_lines < scroll_text_lines()){
2843 whereis_pos.row = 0;
2844 cur_top_line++;
2845 if(cur_top_line + num_display_lines >= scroll_text_lines())
2846 q_status_message1(SM_INFO, 0, 1, "END of %s",
2847 STYLE_NAME(sparms));
2849 else
2850 q_status_message1(SM_ORDER, 0, 1, _("Already at end of %s"),
2851 STYLE_NAME(sparms));
2854 break;
2857 /* ------ Scroll back up one line -------*/
2858 case MC_CHARUP :
2859 next_handle = NULL;
2860 if(sparms->text.handles){
2861 if(sparms->vert_handle){
2862 HANDLE_S *h, *h2;
2863 int i, j, k;
2865 h2 = sparms->text.handles;
2866 if(h2->type == Folder && h2->prev && h2->prev->is_dual_do_open)
2867 h2 = h2->prev;
2869 i = h2->loc->where.row - 1;
2870 j = h2->loc->where.col;
2872 for(h = NULL, k = h2->key;
2873 h2 && (!h
2874 || (h->loc->where.row == h2->loc->where.row));
2875 h2 = h2->prev)
2876 /* must be new key, above current
2877 * line and pref'bly to right
2879 if(h2->key != k
2880 && h2->loc->where.row <= i){
2881 if(h2->loc->where.col < j){
2882 if(!h)
2883 h = h2;
2885 break;
2887 else
2888 h = h2;
2891 if(h){
2892 whereis_pos.row = 0;
2893 next_handle = h;
2894 if((result = scroll_handle_obscured(next_handle)) != 0){
2895 long new_top;
2897 if(scroll_handle_obscured(sparms->text.handles)
2898 && result < 0)
2899 next_handle = sparms->text.handles;
2901 ps_global->mangled_body++;
2902 new_top = scroll_handle_reframe(next_handle->key,0);
2903 if(new_top >= 0)
2904 cur_top_line = new_top;
2908 else if(!(ch == ctrl('P') || F_ON(F_FORCE_ARROWS, ps_global)))
2909 next_handle = scroll_handle_prev(sparms->text.handles);
2912 if(!next_handle){
2913 whereis_pos.row = 0;
2914 if(cur_top_line){
2915 cur_top_line--;
2916 if(cur_top_line == 0)
2917 q_status_message1(SM_INFO, 0, 1, "START of %s",
2918 STYLE_NAME(sparms));
2920 else
2921 q_status_message1(SM_ORDER, 0, 1,
2922 _("Already at start of %s"),
2923 STYLE_NAME(sparms));
2926 break;
2929 case MC_NEXT_HANDLE :
2930 if((next_handle = scroll_handle_next_sel(sparms->text.handles)) != NULL){
2931 whereis_pos.row = 0;
2932 if((result = scroll_handle_obscured(next_handle)) != 0){
2933 long new_top;
2935 if(scroll_handle_obscured(sparms->text.handles)
2936 && result > 0)
2937 next_handle = sparms->text.handles;
2939 ps_global->mangled_body++;
2940 new_top = scroll_handle_reframe(next_handle->key, 0);
2941 if(new_top >= 0)
2942 cur_top_line = new_top;
2945 else{
2946 if(scroll_handle_obscured(sparms->text.handles)){
2947 long new_top;
2949 ps_global->mangled_body++;
2950 if((new_top = scroll_handle_reframe(-1, 0)) >= 0){
2951 whereis_pos.row = 0;
2952 cur_top_line = new_top;
2956 q_status_message1(SM_ORDER, 0, 1,
2957 _("Already on last item in %s"),
2958 STYLE_NAME(sparms));
2961 break;
2964 case MC_PREV_HANDLE :
2965 if((next_handle = scroll_handle_prev_sel(sparms->text.handles)) != NULL){
2966 whereis_pos.row = 0;
2967 if((result = scroll_handle_obscured(next_handle)) != 0){
2968 long new_top;
2970 if(scroll_handle_obscured(sparms->text.handles)
2971 && result < 0)
2972 next_handle = sparms->text.handles;
2974 ps_global->mangled_body++;
2975 new_top = scroll_handle_reframe(next_handle->key, 0);
2976 if(new_top >= 0)
2977 cur_top_line = new_top;
2980 else{
2981 if(scroll_handle_obscured(sparms->text.handles)){
2982 long new_top;
2984 ps_global->mangled_body++;
2985 if((new_top = scroll_handle_reframe(-1, 0)) >= 0){
2986 whereis_pos.row = 0;
2987 cur_top_line = new_top;
2991 q_status_message1(SM_ORDER, 0, 1,
2992 _("Already on first item in %s"),
2993 STYLE_NAME(sparms));
2996 break;
2999 /*------ View the current handle ------*/
3000 case MC_VIEW_HANDLE :
3001 switch(scroll_handle_obscured(sparms->text.handles)){
3002 default :
3003 case 0 :
3004 switch(scroll_handle_launch(sparms->text.handles,
3005 sparms->text.handles->force_display)){
3006 case 1 :
3007 cmd = MC_EXIT; /* propagate */
3008 done = 1;
3009 break;
3011 case -1 :
3012 cmd_cancelled(NULL);
3013 break;
3015 default :
3016 break;
3019 cur_top_line = scroll_state(SS_CUR)->top_text_line;
3020 break;
3022 case 1 :
3023 q_status_message(SM_ORDER, 0, 2, HANDLE_BELOW_ERR);
3024 break;
3026 case -1 :
3027 q_status_message(SM_ORDER, 0, 2, HANDLE_ABOVE_ERR);
3028 break;
3031 break;
3033 /*---------- Search text (where is) ----------*/
3034 case MC_WHEREIS :
3035 ps_global->mangled_footer = 1;
3036 {long start_row;
3037 int start_index, key = 0;
3038 char *report = NULL;
3040 start_row = cur_top_line;
3041 start_index = 0;
3043 if(F_ON(F_SHOW_CURSOR,ps_global)){
3044 if(found_on < 0
3045 || found_on >= scroll_text_lines()
3046 || found_on < cur_top_line
3047 || found_on >= cur_top_line + num_display_lines){
3048 start_row = cur_top_line;
3049 start_index = 0;
3051 else{
3052 if(found_on_index < 0){
3053 start_row = found_on + 1;
3054 start_index = 0;
3056 else{
3057 start_row = found_on;
3058 start_index = found_on_index+1;
3062 else if(sparms->srch_handle){
3063 HANDLE_S *h;
3065 if((h = scroll_handle_next_sel(sparms->text.handles)) != NULL){
3067 * Translate the screen's column into the
3068 * line offset to start on...
3070 * This makes it so search_text never returns -3
3071 * so we don't know it is the same match. That's
3072 * because we start well after the current handle
3073 * (at the next handle) and that causes us to
3074 * think the one we just matched on is a different
3075 * one from before. Can't think of an easy way to
3076 * fix it, though, and it isn't a big deal. We still
3077 * match, we just don't say current line contains
3078 * the only match.
3080 start_row = h->loc->where.row;
3081 start_index = scroll_handle_index(start_row, h->loc->where.col);
3083 else{
3084 /* last handle, start over at top */
3085 start_row = cur_top_line;
3086 start_index = 0;
3089 else{
3090 start_row = (found_on < 0
3091 || found_on >= scroll_text_lines()
3092 || found_on < cur_top_line
3093 || found_on >= cur_top_line + num_display_lines)
3094 ? cur_top_line : found_on + 1,
3095 start_index = 0;
3098 found_on = search_text(-FOOTER_ROWS(ps_global), start_row,
3099 start_index, &report,
3100 &whereis_pos, &found_on_index);
3102 if(found_on == -4){ /* search to top of text */
3103 whereis_pos.row = 0;
3104 whereis_pos.col = 0;
3105 found_on = 0;
3106 if(sparms->text.handles && sparms->srch_handle)
3107 key = 1;
3109 else if(found_on == -5){ /* search to bottom of text */
3110 HANDLE_S *h;
3112 whereis_pos.row = MAX(scroll_text_lines() - 1, 0);
3113 whereis_pos.col = 0;
3114 found_on = whereis_pos.row;
3115 if((h = sparms->text.handles) && sparms->srch_handle)
3117 key = h->key;
3118 while((h = h->next) != NULL);
3120 else if(found_on == -3){
3121 whereis_pos.row = found_on = start_row;
3122 found_on_index = start_index - 1;
3123 q_status_message(SM_ORDER, 1, 3,
3124 _("Current line contains the only match"));
3127 if(found_on >= 0){
3128 result = found_on < cur_top_line;
3129 if(!key)
3130 key = (sparms->text.handles)
3131 ? dot_on_handle(found_on, whereis_pos.col) : 0;
3133 if(F_ON(F_FORCE_LOW_SPEED,ps_global)
3134 || ps_global->low_speed
3135 || F_ON(F_SHOW_CURSOR,ps_global)
3136 || key){
3137 if((found_on >= cur_top_line + num_display_lines ||
3138 found_on < cur_top_line) &&
3139 num_display_lines > ps_global->viewer_overlap){
3140 cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
3141 if(scroll_text_lines()-cur_top_line < 5)
3142 cur_top_line = MAX(0,
3143 scroll_text_lines()-MIN(5,num_display_lines));
3145 /* else leave cur_top_line alone */
3147 else{
3148 cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
3149 if(scroll_text_lines()-cur_top_line < 5)
3150 cur_top_line = MAX(0,
3151 scroll_text_lines()-MIN(5,num_display_lines));
3154 whereis_pos.row = whereis_pos.row - cur_top_line + 1;
3155 if(report)
3156 q_status_message(SM_ORDER, 0, 3, report);
3157 else
3158 q_status_message2(SM_ORDER, 0, 3,
3159 "%sFound on line %s on screen",
3160 result ? "Search wrapped to start. " : "",
3161 int2string(whereis_pos.row));
3163 if(key){
3164 if(sparms->text.handles->key < key)
3165 for(next_handle = sparms->text.handles->next;
3166 next_handle->key != key;
3167 next_handle = next_handle->next)
3169 else
3170 for(next_handle = sparms->text.handles;
3171 next_handle->key != key;
3172 next_handle = next_handle->prev)
3176 else if(found_on == -1)
3177 cmd_cancelled("Search");
3178 else
3179 q_status_message(SM_ORDER, 0, 3, _("Word not found"));
3182 break;
3185 /*-------------- jump command -------------*/
3186 /* NOTE: preempt the process_cmd() version because
3187 * we need to get at the number..
3189 case MC_JUMP :
3190 jn = jump_to(ps_global->msgmap, -FOOTER_ROWS(ps_global), ch,
3191 sparms, View);
3192 if(sparms && sparms->jump_is_debug)
3193 done = 1;
3194 else if(jn > 0 && jn != mn_get_cur(ps_global->msgmap)){
3196 if(mn_total_cur(ps_global->msgmap) > 1L)
3197 mn_reset_cur(ps_global->msgmap, jn);
3198 else
3199 mn_set_cur(ps_global->msgmap, jn);
3201 done = 1;
3203 else
3204 ps_global->mangled_footer = 1;
3206 break;
3209 #ifdef MOUSE
3210 /*-------------- Mouse Event -------------*/
3211 case MC_MOUSE:
3213 MOUSEPRESS mp;
3214 long line;
3215 int key;
3217 mouse_get_last (NULL, &mp);
3218 mp.row -= 2;
3220 /* The clicked line have anything special on it? */
3221 if((line = cur_top_line + mp.row) < scroll_text_lines()
3222 && (key = dot_on_handle(line, mp.col))){
3223 switch(mp.button){
3224 case M_BUTTON_RIGHT :
3225 #ifdef _WINDOWS
3226 if(sparms->mouse.popup){
3227 if(sparms->text.handles->key < key)
3228 for(next_handle = sparms->text.handles->next;
3229 next_handle->key != key;
3230 next_handle = next_handle->next)
3232 else
3233 for(next_handle = sparms->text.handles;
3234 next_handle->key != key;
3235 next_handle = next_handle->prev)
3238 if(sparms->mouse.popup){
3239 cur_top_line = scroll_scroll_text(cur_top_line,
3240 next_handle,
3241 ps_global->mangled_body);
3242 fflush(stdout);
3243 switch((*sparms->mouse.popup)(sparms, key)){
3244 case 1 :
3245 cur_top_line = doubleclick_handle(sparms, next_handle, &cmd, &done);
3246 break;
3248 case 2 :
3249 done++;
3250 break;
3255 #endif
3256 break;
3258 case M_BUTTON_LEFT :
3259 if(sparms->text.handles->key < key)
3260 for(next_handle = sparms->text.handles->next;
3261 next_handle->key != key;
3262 next_handle = next_handle->next)
3264 else
3265 for(next_handle = sparms->text.handles;
3266 next_handle->key != key;
3267 next_handle = next_handle->prev)
3270 if(mp.doubleclick) /* launch url */
3271 cur_top_line = doubleclick_handle(sparms, next_handle, &cmd, &done);
3272 else if(sparms->mouse.click)
3273 (*sparms->mouse.click)(sparms);
3275 break;
3277 case M_BUTTON_MIDDLE : /* NO-OP for now */
3278 break;
3280 default: /* just ignore */
3281 break;
3284 #ifdef _WINDOWS
3285 else if(mp.button == M_BUTTON_RIGHT){
3287 * Toss generic popup on to the screen
3289 if(sparms->mouse.popup)
3290 if((*sparms->mouse.popup)(sparms, 0) == 2){
3291 done++;
3294 #endif
3297 break;
3298 #endif /* MOUSE */
3301 /*-------------- Display Resize -------------*/
3302 case MC_RESIZE :
3303 if(sparms->resize_exit){
3304 long line;
3307 * Figure out char offset of the char in the top left
3308 * corner of the display. Pass it back to the
3309 * fetcher/formatter and have it pass the offset
3310 * back to us...
3312 sparms->start.on = Offset;
3313 for(sparms->start.loc.offset = line = 0L;
3314 line < cur_top_line;
3315 line++)
3316 sparms->start.loc.offset += scroll_handle_column(line, -1);
3318 done = 1;
3319 ClearLine(1);
3320 break;
3322 /* else no reformatting neccessary, fall thru to repaint */
3325 /*-------------- refresh -------------*/
3326 case MC_REPAINT :
3327 num_display_lines = SCROLL_LINES(ps_global);
3328 mark_status_dirty();
3329 mark_keymenu_dirty();
3330 mark_titlebar_dirty();
3331 ps_global->mangled_screen = 1;
3332 force = 1;
3333 break;
3336 /*------- no op timeout to check for new mail ------*/
3337 case MC_NONE :
3338 break;
3341 /*------- Forward displayed text ------*/
3342 case MC_FWDTEXT :
3343 forward_text(ps_global, sparms->text.text, sparms->text.src);
3344 break;
3347 /*----------- Save the displayed text ------------*/
3348 case MC_SAVETEXT :
3349 (void)simple_export(ps_global, sparms->text.text,
3350 sparms->text.src, "text", NULL);
3351 break;
3354 /*----------- Exit this screen ------------*/
3355 case MC_EXIT :
3356 done = 1;
3357 break;
3360 /*----------- Pop back to the Main Menu ------------*/
3361 case MC_MAIN :
3362 ps_global->next_screen = main_menu_screen;
3363 done = 1;
3364 break;
3367 /*----------- Print ------------*/
3368 case MC_PRINTTXT :
3369 print_to_printer(sparms);
3370 break;
3373 /* ------- First handle on Line ------ */
3374 case MC_GOTOBOL :
3375 if(sparms->text.handles){
3376 next_handle = scroll_handle_boundary(sparms->text.handles,
3377 scroll_handle_prev_sel);
3379 break;
3381 /* fall thru as bogus */
3383 /* ------- Last handle on Line ------ */
3384 case MC_GOTOEOL :
3385 if(sparms->text.handles){
3386 next_handle = scroll_handle_boundary(sparms->text.handles,
3387 scroll_handle_next_sel);
3389 break;
3391 /* fall thru as bogus */
3393 /*------- BOGUS INPUT ------*/
3394 case MC_CHARRIGHT :
3395 case MC_CHARLEFT :
3396 case MC_UNKNOWN :
3397 if(sparms->bogus_input)
3398 done = (*sparms->bogus_input)(ch);
3399 else
3400 bogus_command(ch, F_ON(F_USE_FK,ps_global) ? "F1" : "?");
3402 break;
3405 case MC_UTF8:
3406 bogus_utf8_command(utf8str, F_ON(F_USE_FK, ps_global) ? "F1" : "?");
3407 break;
3410 /*------- Standard commands ------*/
3411 default:
3412 whereis_pos.row = 0;
3413 if(sparms->proc.tool)
3414 result = (*sparms->proc.tool)(cmd, ps_global->msgmap, sparms);
3415 else
3416 result = process_cmd(ps_global, ps_global->mail_stream,
3417 ps_global->msgmap, cmd, View, &force);
3419 dprint((7, "PROCESS_CMD return: %d\n", result));
3421 if(ps_global->next_screen != SCREEN_FUN_NULL || result == 1){
3422 done = 1;
3423 if(cmd == MC_FULLHDR){
3424 if(ps_global->full_header == 1){
3425 long line;
3428 * Figure out char offset of the char in the top left
3429 * corner of the display. Pass it back to the
3430 * fetcher/formatter and have it pass the offset
3431 * back to us...
3433 sparms->start.on = Offset;
3434 for(sparms->start.loc.offset = line = 0L;
3435 line < cur_top_line;
3436 line++)
3437 sparms->start.loc.offset +=
3438 scroll_handle_column(line, -1);
3440 else
3441 sparms->start.on = 0;
3443 switch(km->which){
3444 case 0:
3445 sparms->keys.what = FirstMenu;
3446 break;
3447 case 1:
3448 sparms->keys.what = SecondMenu;
3449 break;
3450 case 2:
3451 sparms->keys.what = ThirdMenu;
3452 break;
3453 case 3:
3454 sparms->keys.what = FourthMenu;
3455 break;
3459 else if(!scroll_state(SS_CUR)){
3460 num_display_lines = SCROLL_LINES(ps_global);
3461 ps_global->mangled_screen = 1;
3464 break;
3466 } /* End of switch() */
3468 /* Need to frame some handles? */
3469 if(sparms->text.handles
3470 && ((!next_handle
3471 && handle_on_page(sparms->text.handles, cur_top_line,
3472 cur_top_line + num_display_lines))
3473 || (next_handle
3474 && handle_on_page(next_handle, cur_top_line,
3475 cur_top_line + num_display_lines))))
3476 next_handle = scroll_handle_in_frame(cur_top_line);
3478 } /* End of while() -- loop executing commands */
3480 ps_global->redrawer = NULL; /* next statement makes this invalid! */
3481 zero_scroll_text(); /* very important to zero out on return!!! */
3482 scroll_state(SS_FREE);
3483 if(sparms->bar.color)
3484 free_color_pair(&sparms->bar.color);
3486 #ifdef _WINDOWS
3487 scroll_setrange(0L, 0L);
3488 #endif
3489 return(cmd);
3493 /*----------------------------------------------------------------------
3494 Print text on paper
3496 Args: text -- The text to print out
3497 source -- What type of source text is
3498 message -- Message for open_printer()
3499 Handling of error conditions is very poor.
3501 ----*/
3502 static int
3503 print_to_printer(SCROLL_S *sparms)
3505 char message[64];
3507 snprintf(message, sizeof(message), "%s", STYLE_NAME(sparms));
3508 message[sizeof(message)-1] = '\0';
3510 if(open_printer(message) != 0)
3511 return(-1);
3513 switch(sparms->text.src){
3514 case CharStar :
3515 if(sparms->text.text != (char *)NULL)
3516 print_text((char *)sparms->text.text);
3518 break;
3520 case CharStarStar :
3521 if(sparms->text.text != (char **)NULL){
3522 register char **t;
3524 for(t = sparms->text.text; *t != NULL; t++){
3525 print_text(*t);
3526 print_text(NEWLINE);
3530 break;
3532 case FileStar :
3533 if(sparms->text.text != (FILE *)NULL) {
3534 size_t n;
3535 int i;
3537 fseek((FILE *)sparms->text.text, 0L, 0);
3538 n = SIZEOF_20KBUF - 1;
3539 while((i = fread((void *)tmp_20k_buf, sizeof(char),
3540 n, (FILE *)sparms->text.text)) != 0) {
3541 tmp_20k_buf[i] = '\0';
3542 print_text(tmp_20k_buf);
3546 default :
3547 break;
3550 close_printer();
3551 return(0);
3555 /*----------------------------------------------------------------------
3556 Search text being viewed (help or message)
3558 Args: q_line -- The screen line to prompt for search string on
3559 start_line -- Line number in text to begin search on
3560 start_index -- Where to begin search at in first line of text
3561 cursor_pos -- position of cursor is returned to caller here
3562 (Actually, this isn't really the position of the
3563 cursor because we don't know where we are on the
3564 screen. So row is set to the line number and col
3565 is set to the right column.)
3566 offset_in_line -- Offset where match was found.
3568 Result: returns line number string was found on
3569 -1 for cancel
3570 -2 if not found
3571 -3 if only match is at start_index - 1
3572 -4 if search to first line
3573 -5 if search to last line
3574 ---*/
3576 search_text(int q_line, long int start_line, int start_index, char **report,
3577 Pos *cursor_pos, int *offset_in_line)
3579 char prompt[MAX_SEARCH+50], nsearch_string[MAX_SEARCH+1], *p;
3580 HelpType help;
3581 int rc, flags;
3582 static HISTORY_S *history = NULL;
3583 char search_string[MAX_SEARCH+1];
3584 static ESCKEY_S word_search_key[] = { { 0, 0, "", "" },
3585 {ctrl('Y'), 10, "^Y", N_("First Line")},
3586 {ctrl('V'), 11, "^V", N_("Last Line")},
3587 {KEY_UP, 30, "", ""},
3588 {KEY_DOWN, 31, "", ""},
3589 {-1, 0, NULL, NULL}
3591 #define KU_ST (3) /* index of KEY_UP */
3593 init_hist(&history, HISTSIZE);
3596 * Put the last one used in the default search_string,
3597 * not in nsearch_string.
3599 search_string[0] = '\0';
3600 if((p = get_prev_hist(history, "", 0, NULL)) != NULL){
3601 strncpy(search_string, p, sizeof(search_string));
3602 search_string[sizeof(search_string)-1] = '\0';
3605 snprintf(prompt, sizeof(prompt), _("Word to search for [%s] : "), search_string);
3606 help = NO_HELP;
3607 nsearch_string[0] = '\0';
3609 while(1) {
3610 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE | OE_KEEP_TRAILING_SPACE;
3613 * 2 is really 1 because there will be one real entry and
3614 * one entry of "" because of the get_prev_hist above.
3616 if(items_in_hist(history) > 2){
3617 word_search_key[KU_ST].name = HISTORY_UP_KEYNAME;
3618 word_search_key[KU_ST].label = HISTORY_KEYLABEL;
3619 word_search_key[KU_ST+1].name = HISTORY_DOWN_KEYNAME;
3620 word_search_key[KU_ST+1].label = HISTORY_KEYLABEL;
3622 else{
3623 word_search_key[KU_ST].name = "";
3624 word_search_key[KU_ST].label = "";
3625 word_search_key[KU_ST+1].name = "";
3626 word_search_key[KU_ST+1].label = "";
3629 rc = optionally_enter(nsearch_string, q_line, 0, sizeof(nsearch_string),
3630 prompt, word_search_key, help, &flags);
3632 if(rc == 3) {
3633 help = help == NO_HELP ? h_oe_searchview : NO_HELP;
3634 continue;
3636 else if(rc == 10){
3637 if(report)
3638 *report = _("Searched to First Line.");
3640 return(-4);
3642 else if(rc == 11){
3643 if(report)
3644 *report = _("Searched to Last Line.");
3646 return(-5);
3648 else if(rc == 30){
3649 if((p = get_prev_hist(history, nsearch_string, 0, NULL)) != NULL){
3650 strncpy(nsearch_string, p, sizeof(nsearch_string));
3651 nsearch_string[sizeof(nsearch_string)-1] = '\0';
3653 else
3654 Writechar(BELL, 0);
3656 continue;
3658 else if(rc == 31){
3659 if((p = get_next_hist(history, nsearch_string, 0, NULL)) != NULL){
3660 strncpy(nsearch_string, p, sizeof(nsearch_string));
3661 nsearch_string[sizeof(nsearch_string)-1] = '\0';
3663 else
3664 Writechar(BELL, 0);
3666 continue;
3669 if(rc != 4){ /* 4 is redraw */
3670 save_hist(history, nsearch_string, 0, NULL);
3671 break;
3675 if(rc == 1 || (search_string[0] == '\0' && nsearch_string[0] == '\0'))
3676 return(-1);
3678 if(nsearch_string[0] != '\0'){
3679 strncpy(search_string, nsearch_string, sizeof(search_string)-1);
3680 search_string[sizeof(search_string)-1] = '\0';
3683 rc = search_scroll_text(start_line, start_index, search_string, cursor_pos,
3684 offset_in_line);
3685 return(rc);
3689 /*----------------------------------------------------------------------
3690 Update the scroll tool's titlebar
3692 Args: cur_top_line --
3693 redraw -- flag to force updating
3695 ----*/
3696 void
3697 update_scroll_titlebar(long int cur_top_line, int redraw)
3699 SCRLCTRL_S *st = scroll_state(SS_CUR);
3700 int num_display_lines = SCROLL_LINES(ps_global);
3701 long new_line = (cur_top_line + num_display_lines > st->num_lines)
3702 ? st->num_lines
3703 : cur_top_line + num_display_lines;
3704 long raw_msgno;
3705 COLOR_PAIR *returned_color = NULL;
3706 COLOR_PAIR *titlecolor = NULL;
3707 int colormatch;
3708 SEARCHSET *ss = NULL;
3710 if(st->parms->use_indexline_color
3711 && ps_global->titlebar_color_style != TBAR_COLOR_DEFAULT){
3712 raw_msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
3713 if(raw_msgno > 0L && ps_global->mail_stream
3714 && raw_msgno <= ps_global->mail_stream->nmsgs){
3715 ss = mail_newsearchset();
3716 ss->first = ss->last = (unsigned long) raw_msgno;
3719 if(ss){
3720 PAT_STATE *pstate = NULL;
3722 colormatch = get_index_line_color(ps_global->mail_stream,
3723 ss, &pstate, &returned_color);
3724 mail_free_searchset(&ss);
3727 * This is a bit tricky. If there is a colormatch but returned_color
3728 * is NULL, that means that the user explicitly wanted the
3729 * Normal color used in this index line, so that is what we
3730 * use. If no colormatch then we will use the TITLE color
3731 * instead of Normal.
3733 if(colormatch){
3734 if(returned_color)
3735 titlecolor = returned_color;
3736 else
3737 titlecolor = new_color_pair(ps_global->VAR_NORM_FORE_COLOR,
3738 ps_global->VAR_NORM_BACK_COLOR);
3741 if(titlecolor
3742 && ps_global->titlebar_color_style == TBAR_COLOR_REV_INDEXLINE){
3743 char cbuf[MAXCOLORLEN+1];
3745 strncpy(cbuf, titlecolor->fg, MAXCOLORLEN);
3746 strncpy(titlecolor->fg, titlecolor->bg, MAXCOLORLEN);
3747 strncpy(titlecolor->bg, cbuf, MAXCOLORLEN);
3751 /* Did the color change? */
3752 if((!titlecolor && st->parms->bar.color)
3754 (titlecolor && !st->parms->bar.color)
3756 (titlecolor && st->parms->bar.color
3757 && (strcmp(titlecolor->fg, st->parms->bar.color->fg)
3758 || strcmp(titlecolor->bg, st->parms->bar.color->bg)))){
3760 redraw++;
3761 if(st->parms->bar.color)
3762 free_color_pair(&st->parms->bar.color);
3764 st->parms->bar.color = titlecolor;
3765 titlecolor = NULL;
3768 if(titlecolor)
3769 free_color_pair(&titlecolor);
3773 if(redraw){
3774 set_titlebar(st->parms->bar.title, ps_global->mail_stream,
3775 ps_global->context_current, ps_global->cur_folder,
3776 ps_global->msgmap, 1, st->parms->bar.style,
3777 new_line, st->num_lines, st->parms->bar.color);
3778 ps_global->mangled_header = 0;
3780 else if(st->parms->bar.style == TextPercent)
3781 update_titlebar_lpercent(new_line);
3782 else
3783 update_titlebar_percent(new_line);
3787 /*----------------------------------------------------------------------
3788 manager of global (to this module, anyway) scroll state structures
3791 ----*/
3792 SCRLCTRL_S *
3793 scroll_state(int func)
3795 struct scrollstack {
3796 SCRLCTRL_S s;
3797 struct scrollstack *prev;
3798 } *s;
3799 static struct scrollstack *stack = NULL;
3801 switch(func){
3802 case SS_CUR: /* no op */
3803 break;
3804 case SS_NEW:
3805 s = (struct scrollstack *)fs_get(sizeof(struct scrollstack));
3806 memset((void *)s, 0, sizeof(struct scrollstack));
3807 s->prev = stack;
3808 stack = s;
3809 break;
3810 case SS_FREE:
3811 if(stack){
3812 s = stack->prev;
3813 fs_give((void **)&stack);
3814 stack = s;
3816 break;
3817 default: /* BUG: should complain */
3818 break;
3821 return(stack ? &stack->s : NULL);
3825 /*----------------------------------------------------------------------
3826 Save all the data for scrolling text and paint the screen
3829 ----*/
3830 void
3831 set_scroll_text(SCROLL_S *sparms, long int current_line, SCRLCTRL_S *st)
3833 /* save all the stuff for possible asynchronous redraws */
3834 st->parms = sparms;
3835 st->top_text_line = current_line;
3836 st->screen.start_line = SCROLL_LINES_ABOVE(ps_global);
3837 st->screen.other_lines = SCROLL_LINES_ABOVE(ps_global)
3838 + SCROLL_LINES_BELOW(ps_global);
3839 st->screen.width = -1; /* Force text formatting calculation */
3843 /*----------------------------------------------------------------------
3844 Redraw the text on the screen, possibly reformatting if necessary
3846 Args None
3848 ----*/
3849 void
3850 redraw_scroll_text(void)
3852 int i, offset;
3853 SCRLCTRL_S *st = scroll_state(SS_CUR);
3855 format_scroll_text();
3857 offset = (st->parms->text.src == FileStar) ? 0 : st->top_text_line;
3859 #ifdef _WINDOWS
3860 mswin_beginupdate();
3861 #endif
3862 /*---- Actually display the text on the screen ------*/
3863 for(i = 0; i < st->screen.length; i++){
3864 ClearLine(i + st->screen.start_line);
3865 if((offset + i) < st->num_lines)
3866 PutLine0n8b(i + st->screen.start_line, 0, st->text_lines[offset + i],
3867 st->line_lengths[offset + i], st->parms->text.handles);
3871 fflush(stdout);
3872 #ifdef _WINDOWS
3873 mswin_endupdate();
3874 #endif
3878 /*----------------------------------------------------------------------
3879 Free memory used as scrolling buffers for text on disk. Also mark
3880 text_lines as available
3881 ----*/
3882 void
3883 zero_scroll_text(void)
3885 SCRLCTRL_S *st = scroll_state(SS_CUR);
3886 register int i;
3888 for(i = 0; i < st->lines_allocated; i++)
3889 if(st->parms->text.src == FileStar && st->text_lines[i])
3890 fs_give((void **)&st->text_lines[i]);
3891 else
3892 st->text_lines[i] = NULL;
3894 if(st->parms->text.src == FileStar && st->findex != NULL){
3895 fclose(st->findex);
3896 st->findex = NULL;
3897 if(st->fname){
3898 our_unlink(st->fname);
3899 fs_give((void **)&st->fname);
3903 if(st->text_lines)
3904 fs_give((void **)&st->text_lines);
3906 if(st->line_lengths)
3907 fs_give((void **) &st->line_lengths);
3911 /*----------------------------------------------------------------------
3913 Always format at least 20 chars wide. Wrapping lines would be crazy for
3914 screen widths of 1-20 characters
3915 ----*/
3916 void
3917 format_scroll_text(void)
3919 int i;
3920 char *p, **pp;
3921 SCRLCTRL_S *st = scroll_state(SS_CUR);
3922 register short *ll;
3923 register char **tl, **tl_end;
3925 if(!st || (st->screen.width == (i = ps_global->ttyo->screen_cols)
3926 && st->screen.length == PGSIZE(st)))
3927 return;
3929 st->screen.width = MAX(20, i);
3930 st->screen.length = PGSIZE(st);
3932 if(st->lines_allocated == 0) {
3933 st->lines_allocated = TYPICAL_BIG_MESSAGE_LINES;
3934 st->text_lines = (char **)fs_get(st->lines_allocated *sizeof(char *));
3935 memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
3936 st->line_lengths = (short *)fs_get(st->lines_allocated *sizeof(short));
3939 tl = st->text_lines;
3940 ll = st->line_lengths;
3941 tl_end = &st->text_lines[st->lines_allocated];
3943 if(st->parms->text.src == CharStarStar) {
3944 /*---- original text is already list of lines -----*/
3945 /* The text could be wrapped nicely for narrow screens; for now
3946 it will get truncated as it is displayed */
3947 for(pp = (char **)st->parms->text.text; *pp != NULL;) {
3948 *tl++ = *pp++;
3949 *ll++ = st->screen.width;
3950 if(tl >= tl_end) {
3951 i = tl - st->text_lines;
3952 st->lines_allocated *= 2;
3953 fs_resize((void **)&st->text_lines,
3954 st->lines_allocated * sizeof(char *));
3955 fs_resize((void **)&st->line_lengths,
3956 st->lines_allocated*sizeof(short));
3957 tl = &st->text_lines[i];
3958 ll = &st->line_lengths[i];
3959 tl_end = &st->text_lines[st->lines_allocated];
3963 st->num_lines = tl - st->text_lines;
3965 else if (st->parms->text.src == CharStar) {
3966 /*------ Format the plain text ------*/
3967 for(p = (char *)st->parms->text.text; *p; ) {
3968 *tl = p;
3970 for(; *p && !(*p == RETURN || *p == LINE_FEED); p++)
3973 *ll = p - *tl;
3974 ll++; tl++;
3975 if(tl >= tl_end) {
3976 i = tl - st->text_lines;
3977 st->lines_allocated *= 2;
3978 fs_resize((void **)&st->text_lines,
3979 st->lines_allocated * sizeof(char *));
3980 fs_resize((void **)&st->line_lengths,
3981 st->lines_allocated*sizeof(short));
3982 tl = &st->text_lines[i];
3983 ll = &st->line_lengths[i];
3984 tl_end = &st->text_lines[st->lines_allocated];
3987 if(*p == '\r' && *(p+1) == '\n')
3988 p += 2;
3989 else if(*p == '\n' || *p == '\r')
3990 p++;
3993 st->num_lines = tl - st->text_lines;
3995 else {
3996 /*------ Display text is in a file --------*/
3999 * This is pretty much only useful under DOS where we can't fit
4000 * all of big messages in core at once. This scheme makes
4001 * some simplifying assumptions:
4002 * 1. Lines are on disk just the way we'll display them. That
4003 * is, line breaks and such are left to the function that
4004 * writes the disk file to catch and fix.
4005 * 2. We get away with this mainly because the DOS display isn't
4006 * going to be resized out from under us.
4008 * The idea is to use the already alloc'd array of char * as a
4009 * buffer for sections of what's on disk. We'll set up the first
4010 * few lines here, and read new ones in as needed in
4011 * scroll_scroll_text().
4013 * but first, make sure there are enough buffer lines allocated
4014 * to serve as a place to hold lines from the file.
4016 * Actually, this is also used under windows so the display will
4017 * be resized out from under us. So I changed the following
4018 * to always
4019 * 1. free old text_lines, which may have been allocated
4020 * for a narrow screen.
4021 * 2. insure we have enough text_lines
4022 * 3. reallocate all text_lines that are needed.
4023 * (tom unger 10/26/94)
4026 /* free old text lines, which may be too short. */
4027 for(i = 0; i < st->lines_allocated; i++)
4028 if(st->text_lines[i]) /* clear alloc'd lines */
4029 fs_give((void **)&st->text_lines[i]);
4031 /* Insure we have enough text lines. */
4032 if(st->lines_allocated < (2 * PGSIZE(st)) + 1){
4033 st->lines_allocated = (2 * PGSIZE(st)) + 1; /* resize */
4035 fs_resize((void **)&st->text_lines,
4036 st->lines_allocated * sizeof(char *));
4037 memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
4038 fs_resize((void **)&st->line_lengths,
4039 st->lines_allocated*sizeof(short));
4042 /* reallocate all text lines that are needed. */
4043 for(i = 0; i <= PGSIZE(st); i++)
4044 if(st->text_lines[i] == NULL)
4045 st->text_lines[i] = (char *)fs_get((st->screen.width + 1)
4046 * sizeof(char));
4048 tl = &st->text_lines[i];
4050 st->num_lines = make_file_index();
4052 ScrollFile(st->top_text_line); /* then load them up */
4056 * Efficiency hack. If there are handles, fill in their
4057 * line number field for later...
4059 if(st->parms->text.handles){
4060 long line;
4061 int i, col, n, key;
4062 HANDLE_S *h;
4064 for(line = 0; line < st->num_lines; line++)
4065 for(i = 0, col = 0; i < st->line_lengths[line];)
4066 switch(st->text_lines[line][i]){
4067 case TAG_EMBED:
4068 i++;
4069 switch((i < st->line_lengths[line]) ? st->text_lines[line][i]
4070 : 0){
4071 case TAG_HANDLE:
4072 for(key = 0, n = st->text_lines[line][++i]; n > 0; n--)
4073 key = (key * 10) + (st->text_lines[line][++i] - '0');
4075 i++;
4076 for(h = st->parms->text.handles; h; h = h->next)
4077 if(h->key == key){
4078 scroll_handle_set_loc(&h->loc, line, col);
4079 break;
4082 if(!h) /* anything behind us? */
4083 for(h = st->parms->text.handles->prev; h; h = h->prev)
4084 if(h->key == key){
4085 scroll_handle_set_loc(&h->loc, line, col);
4086 break;
4089 break;
4091 case TAG_FGCOLOR :
4092 case TAG_BGCOLOR :
4093 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
4094 break;
4096 case TAG_INVON:
4097 case TAG_INVOFF:
4098 case TAG_BOLDON:
4099 case TAG_BOLDOFF:
4100 case TAG_ULINEON:
4101 case TAG_ULINEOFF:
4102 i++;
4103 break;
4105 default: /* literal embed char */
4106 break;
4109 break;
4111 case TAB:
4112 i++;
4113 while(((++col) & 0x07) != 0) /* add tab's spaces */
4116 break;
4118 default:
4119 col += width_at_this_position((unsigned char*) &st->text_lines[line][i],
4120 st->line_lengths[line] - i);
4121 i++; /* character count */
4122 break;
4126 #ifdef _WINDOWS
4127 scroll_setrange (st->screen.length, st->num_lines);
4128 #endif
4130 *tl = NULL;
4135 * ScrollFile - scroll text into the st struct file making sure 'line'
4136 * of the file is the one first in the text_lines buffer.
4138 * NOTE: talk about massive potential for tuning...
4139 * Goes without saying this is still under constuction
4141 void
4142 ScrollFile(long int line)
4144 SCRLCTRL_S *st = scroll_state(SS_CUR);
4145 SCRLFILE_S sf;
4146 register int i;
4148 if(line <= 0){ /* reset and load first couple of pages */
4149 fseek((FILE *) st->parms->text.text, 0L, 0);
4150 line = 0L;
4153 if(!st->text_lines)
4154 return;
4156 for(i = 0; i < PGSIZE(st); i++){
4157 /*** do stuff to get the file pointer into the right place ***/
4159 * BOGUS: this is painfully crude right now, but I just want to get
4160 * it going.
4162 * possibly in the near furture, an array of indexes into the
4163 * file that are the offset for the beginning of each line will
4164 * speed things up. Of course, this
4165 * will have limits, so maybe a disk file that is an array
4166 * of indexes is the answer.
4168 if(fseek(st->findex, (size_t)(line++) * sizeof(SCRLFILE_S), 0) < 0
4169 || fread(&sf, sizeof(SCRLFILE_S), (size_t)1, st->findex) != 1
4170 || fseek((FILE *) st->parms->text.text, sf.offset, 0) < 0
4171 || !st->text_lines[i]
4172 || (sf.len && !fgets(st->text_lines[i], sf.len + 1,
4173 (FILE *) st->parms->text.text)))
4174 break;
4176 st->line_lengths[i] = sf.len;
4179 for(; i < PGSIZE(st); i++)
4180 if(st->text_lines[i]){ /* blank out any unused lines */
4181 *st->text_lines[i] = '\0';
4182 st->line_lengths[i] = 0;
4188 * make_file_index - do a single pass over the file containing the text
4189 * to display, recording line lengths and offsets.
4190 * NOTE: This is never really to be used on a real OS with virtual
4191 * memory. This is the whole reason st->findex exists. Don't
4192 * want to waste precious memory on a stupid array that could
4193 * be very large.
4195 long
4196 make_file_index(void)
4198 SCRLCTRL_S *st = scroll_state(SS_CUR);
4199 SCRLFILE_S sf;
4200 long l = 0L;
4201 int state = 0;
4203 if(!st->findex){
4204 if(!st->fname)
4205 st->fname = temp_nam(NULL, "pi");
4207 if(!st->fname || (st->findex = our_fopen(st->fname,"w+b")) == NULL){
4208 if(st->fname){
4209 our_unlink(st->fname);
4210 fs_give((void **)&st->fname);
4213 return(0);
4216 else
4217 fseek(st->findex, 0L, 0);
4219 fseek((FILE *)st->parms->text.text, 0L, 0);
4221 while(1){
4222 sf.len = st->screen.width + 1;
4223 if(scroll_file_line((FILE *) st->parms->text.text,
4224 tmp_20k_buf, &sf, &state)){
4225 fwrite((void *) &sf, sizeof(SCRLFILE_S), (size_t)1, st->findex);
4226 l++;
4228 else
4229 break;
4232 fseek((FILE *)st->parms->text.text, 0L, 0);
4234 return(l);
4238 /*----------------------------------------------------------------------
4239 Get the next line to scroll from the given file
4241 ----*/
4242 char *
4243 scroll_file_line(FILE *fp, char *buf, SCRLFILE_S *sfp, int *wrapt)
4245 register char *s = NULL;
4247 while(1){
4248 if(!s){
4249 sfp->offset = ftell(fp);
4250 if(!(s = fgets(buf, sfp->len, fp)))
4251 return(NULL); /* can't grab a line? */
4254 if(!*s){
4255 *wrapt = 1; /* remember; that we wrapped */
4256 break;
4258 else if(*s == NEWLINE[0] && (!NEWLINE[1] || *(s+1) == NEWLINE[1])){
4259 int empty = (*wrapt && s == buf);
4261 *wrapt = 0; /* turn off wrapped state */
4262 if(empty)
4263 s = NULL; /* get a new line */
4264 else
4265 break; /* done! */
4267 else
4268 s++;
4271 sfp->len = s - buf;
4272 return(buf);
4276 /*----------------------------------------------------------------------
4277 Scroll the text on the screen
4279 Args: new_top_line -- The line to be displayed on top of the screen
4280 redraw -- Flag to force a redraw even if nothing changed
4282 Returns: resulting top line
4283 Note: the returned line number may be less than new_top_line if
4284 reformatting caused the total line count to change.
4286 ----*/
4287 long
4288 scroll_scroll_text(long int new_top_line, HANDLE_S *handle, int redraw)
4290 SCRLCTRL_S *st = scroll_state(SS_CUR);
4291 int num_display_lines, l, top;
4292 POSLIST_S *lp, *lp2;
4294 /* When this is true, we're still on the same page of the display. */
4295 if(st->top_text_line == new_top_line && !redraw){
4296 /* handle changed, so hilite the new handle and unhilite the old */
4297 if(handle && handle != st->parms->text.handles){
4298 top = st->screen.start_line - new_top_line;
4299 /* hilite the new one */
4300 if(!scroll_handle_obscured(handle))
4301 for(lp = handle->loc; lp; lp = lp->next)
4302 if((l = lp->where.row) >= st->top_text_line
4303 && l < st->top_text_line + st->screen.length){
4304 if(st->parms->text.src == FileStar)
4305 l -= new_top_line;
4307 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4308 st->line_lengths[l], handle);
4311 /* unhilite the old one */
4312 if(!scroll_handle_obscured(st->parms->text.handles))
4313 for(lp = st->parms->text.handles->loc; lp; lp = lp->next)
4314 if((l = lp->where.row) >= st->top_text_line
4315 && l < st->top_text_line + st->screen.length){
4316 for(lp2 = handle->loc; lp2; lp2 = lp2->next)
4317 if(l == lp2->where.row)
4318 break;
4320 if(!lp2){
4321 if(st->parms->text.src == FileStar)
4322 l -= new_top_line;
4324 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4325 st->line_lengths[l], handle);
4329 st->parms->text.handles = handle; /* update current */
4332 return(new_top_line);
4335 num_display_lines = PGSIZE(st);
4337 format_scroll_text();
4339 if(st->top_text_line >= st->num_lines) /* don't pop line count */
4340 new_top_line = st->top_text_line = MAX(st->num_lines - 1, 0);
4342 if(st->parms->text.src == FileStar)
4343 ScrollFile(new_top_line); /* set up new st->text_lines */
4345 #ifdef _WINDOWS
4346 scroll_setrange (st->screen.length, st->num_lines);
4347 scroll_setpos (new_top_line);
4348 #endif
4350 /* ---
4351 Check out the scrolling situation. If we want to scroll, but BeginScroll
4352 says we can't then repaint, + 10 is so we repaint most of the time.
4353 ----*/
4354 if(redraw ||
4355 (st->top_text_line - new_top_line + 10 >= num_display_lines ||
4356 new_top_line - st->top_text_line + 10 >= num_display_lines) ||
4357 BeginScroll(st->screen.start_line,
4358 st->screen.start_line + num_display_lines - 1) != 0) {
4359 /* Too much text to scroll, or can't scroll -- just repaint */
4361 if(handle)
4362 st->parms->text.handles = handle;
4364 st->top_text_line = new_top_line;
4365 redraw_scroll_text();
4367 else{
4369 * We're going to scroll the screen, but first we have to make sure
4370 * the old hilited handles are unhilited if they are going to remain
4371 * on the screen.
4373 top = st->screen.start_line - st->top_text_line;
4374 if(handle && handle != st->parms->text.handles
4375 && st->parms->text.handles
4376 && !scroll_handle_obscured(st->parms->text.handles))
4377 for(lp = st->parms->text.handles->loc; lp; lp = lp->next)
4378 if((l = lp->where.row) >= MAX(st->top_text_line,new_top_line)
4379 && l < MIN(st->top_text_line,new_top_line) + st->screen.length){
4380 if(st->parms->text.src == FileStar)
4381 l -= new_top_line;
4383 PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
4384 st->line_lengths[l], handle);
4387 if(new_top_line > st->top_text_line){
4388 /*------ scroll down ------*/
4389 while(new_top_line > st->top_text_line) {
4390 ScrollRegion(1);
4392 l = (st->parms->text.src == FileStar)
4393 ? num_display_lines - (new_top_line - st->top_text_line)
4394 : st->top_text_line + num_display_lines;
4396 if(l < st->num_lines){
4397 PutLine0n8b(st->screen.start_line + num_display_lines - 1,
4398 0, st->text_lines[l], st->line_lengths[l],
4399 handle ? handle : st->parms->text.handles);
4401 * We clear to the end of line in the right background
4402 * color. If the line was exactly the width of the screen
4403 * then PutLine0n8b will have left _col and _row moved to
4404 * the start of the next row. We don't need or want to clear
4405 * that next row.
4407 if(pico_usingcolor()
4408 && (st->line_lengths[l] < ps_global->ttyo->screen_cols
4409 || visible_linelen(l) < ps_global->ttyo->screen_cols))
4410 CleartoEOLN();
4413 st->top_text_line++;
4416 else{
4417 /*------ scroll up -----*/
4418 while(new_top_line < st->top_text_line) {
4419 ScrollRegion(-1);
4421 st->top_text_line--;
4422 l = (st->parms->text.src == FileStar)
4423 ? st->top_text_line - new_top_line
4424 : st->top_text_line;
4425 PutLine0n8b(st->screen.start_line, 0, st->text_lines[l],
4426 st->line_lengths[l],
4427 handle ? handle : st->parms->text.handles);
4429 * We clear to the end of line in the right background
4430 * color. If the line was exactly the width of the screen
4431 * then PutLine0n8b will have left _col and _row moved to
4432 * the start of the next row. We don't need or want to clear
4433 * that next row.
4435 if(pico_usingcolor()
4436 && (st->line_lengths[l] < ps_global->ttyo->screen_cols
4437 || visible_linelen(l) < ps_global->ttyo->screen_cols))
4438 CleartoEOLN();
4442 EndScroll();
4444 if(handle && handle != st->parms->text.handles){
4445 POSLIST_S *lp;
4447 for(lp = handle->loc; lp; lp = lp->next)
4448 if(lp->where.row >= st->top_text_line
4449 && lp->where.row < st->top_text_line + st->screen.length){
4450 PutLine0n8b(st->screen.start_line
4451 + (lp->where.row - st->top_text_line),
4452 0, st->text_lines[lp->where.row],
4453 st->line_lengths[lp->where.row],
4454 handle);
4458 st->parms->text.handles = handle;
4461 fflush(stdout);
4464 return(new_top_line);
4468 /*---------------------------------------------------------------------
4469 Edit individual char in text so that the entire text doesn't need
4470 to be completely reformatted.
4472 Returns 0 if there were no errors, 1 if we would like the entire
4473 text to be reformatted.
4474 ----*/
4476 ng_scroll_edit(CONTEXT_S *context, int index)
4478 SCRLCTRL_S *st = scroll_state(SS_CUR);
4479 char *ngp, tmp[MAILTMPLEN+10];
4480 int len;
4481 FOLDER_S *f;
4483 if (!(f = folder_entry(index, FOLDERS(context))))
4484 return 1;
4485 if(f->subscribed)
4486 return 0; /* nothing in scroll needs to be changed */
4487 tmp[0] = TAG_HANDLE;
4488 snprintf(tmp+2, sizeof(tmp)-2, "%d", st->parms->text.handles->key);
4489 tmp[sizeof(tmp)-1] = '\0';
4490 tmp[1] = len = strlen(tmp+2);
4491 snprintf(tmp+len+2, sizeof(tmp)-(len+2), "%s ", f->selected ? "[ ]" : "[X]");
4492 tmp[sizeof(tmp)-1] = '\0';
4493 snprintf(tmp+len+6, sizeof(tmp)-(len+6), "%.*s", MAILTMPLEN, f->name);
4494 tmp[sizeof(tmp)-1] = '\0';
4496 ngp = *(st->text_lines);
4498 ngp = strstr(ngp, tmp);
4500 if(!ngp) return 1;
4501 ngp += 3+len;
4503 /* assumption that text is of form "[ ] xxx.xxx" */
4505 if(ngp){
4506 if(*ngp == 'X'){
4507 *ngp = ' ';
4508 return 0;
4510 else if (*ngp == ' '){
4511 *ngp = 'X';
4512 return 0;
4515 return 1;
4519 /*---------------------------------------------------------------------
4520 Similar to ng_scroll_edit, but this is the more general case of
4521 selecting a folder, as opposed to selecting a newsgroup for
4522 subscription while in listmode.
4524 Returns 0 if there were no errors, 1 if we would like the entire
4525 text to be reformatted.
4526 ----*/
4528 folder_select_update(CONTEXT_S *context, int index)
4530 SCRLCTRL_S *st = scroll_state(SS_CUR);
4531 FOLDER_S *f;
4532 char *ngp, tmp[MAILTMPLEN+10];
4533 int len, total, fnum, num_sel = 0;
4535 if (!(f = folder_entry(index, FOLDERS(context))))
4536 return 1;
4537 ngp = *(st->text_lines);
4539 total = folder_total(FOLDERS(context));
4541 for (fnum = 0; num_sel < 2 && fnum < total; fnum++)
4542 if(folder_entry(fnum, FOLDERS(context))->selected)
4543 num_sel++;
4544 if(!num_sel || (f->selected && num_sel == 1))
4545 return 1; /* need to reformat the whole thing */
4547 tmp[0] = TAG_HANDLE;
4548 snprintf(tmp+2, sizeof(tmp)-2, "%d", st->parms->text.handles->key);
4549 tmp[sizeof(tmp)-1] = '\0';
4550 tmp[1] = len = strlen(tmp+2);
4552 ngp = strstr(ngp, tmp);
4553 if(!ngp) return 1;
4555 if(F_ON(F_SELECTED_SHOWN_BOLD, ps_global)){
4556 ngp += 2 + len;
4557 while(*ngp && ngp[0] != TAG_EMBED
4558 && ngp[1] != (f->selected ? TAG_BOLDOFF : TAG_BOLDON)
4559 && *ngp != *(f->name))
4560 ngp++;
4562 if (!(*ngp) || (*ngp == *(f->name)))
4563 return 1;
4564 else {
4565 ngp++;
4566 *ngp = (f->selected ? TAG_BOLDON : TAG_BOLDOFF);
4567 return 0;
4570 else{
4571 while(*ngp != ' ' && *ngp != *(f->name) && *ngp)
4572 ngp++;
4573 if(!(*ngp) || (*ngp == *(f->name)))
4574 return 1;
4575 else {
4576 ngp++;
4577 *ngp = f->selected ? 'X' : ' ';
4578 return 0;
4584 /*---------------------------------------------------------------------
4585 We gotta go through all of the formatted text and add "[ ] " in the right
4586 place. If we don't do this, we must completely reformat the whole text,
4587 which could take a very long time.
4589 Return 1 if we encountered some sort of error and we want to reformat the
4590 whole text, return 0 if everything went as planned.
4592 ASSUMPTION: for this to work, we assume that there are only total
4593 number of handles, numbered 1 through total.
4594 ----*/
4596 scroll_add_listmode(CONTEXT_S *context, int total)
4598 SCRLCTRL_S *st = scroll_state(SS_CUR);
4599 long i;
4600 char *ngp, *ngname, handle_str[MAILTMPLEN];
4601 HANDLE_S *h;
4604 ngp = *(st->text_lines);
4605 h = st->parms->text.handles;
4607 while(h && h->key != 1 && h->prev)
4608 h = h->prev;
4609 if (!h) return 1;
4610 handle_str[0] = TAG_EMBED;
4611 handle_str[1] = TAG_HANDLE;
4612 for(i = 1; i <= total && h; i++, h = h->next){
4613 snprintf(handle_str+3, sizeof(handle_str)-3, "%d", h->key);
4614 handle_str[sizeof(handle_str)-1] = '\0';
4615 handle_str[2] = strlen(handle_str+3);
4616 ngp = strstr(ngp, handle_str);
4617 if(!ngp){
4618 ngp = *(st->text_lines);
4619 if (!ngp)
4620 return 1;
4622 ngname = ngp + strlen(handle_str);
4623 while (strncmp(ngp, " ", 4) && !(*ngp == '\n')
4624 && !(ngp == *(st->text_lines)))
4625 ngp--;
4626 if (strncmp(ngp, " ", 4))
4627 return 1;
4628 while(ngp+4 != ngname && *ngp){
4629 ngp[0] = ngp[4];
4630 ngp++;
4633 if(folder_entry(h->h.f.index, FOLDERS(context))->subscribed){
4634 ngp[0] = 'S';
4635 ngp[1] = 'U';
4636 ngp[2] = 'B';
4638 else{
4639 ngp[0] = '[';
4640 ngp[1] = ' ';
4641 ngp[2] = ']';
4643 ngp[3] = ' ';
4646 return 0;
4651 /*----------------------------------------------------------------------
4652 Search the set scrolling text
4654 Args: start_line -- line to start searching on
4655 start_index -- column to start searching at in first line
4656 word -- string to search for
4657 cursor_pos -- position of cursor is returned to caller here
4658 (Actually, this isn't really the position of the
4659 cursor because we don't know where we are on the
4660 screen. So row is set to the line number and col
4661 is set to the right column.)
4662 offset_in_line -- Offset where match was found.
4664 Returns: the line the word was found on, or -2 if it wasn't found, or
4665 -3 if the only match is at column start_index - 1.
4667 ----*/
4669 search_scroll_text(long int start_line, int start_index, char *word,
4670 Pos *cursor_pos, int *offset_in_line)
4672 SCRLCTRL_S *st = scroll_state(SS_CUR);
4673 char *wh;
4674 long l, offset, dlines;
4675 #define SROW(N) ((N) - offset)
4676 #define SLINE(N) st->text_lines[SROW(N)]
4677 #define SLEN(N) st->line_lengths[SROW(N)]
4679 dlines = PGSIZE(st);
4680 offset = (st->parms->text.src == FileStar) ? st->top_text_line : 0;
4682 if(start_line < st->num_lines){
4683 /* search first line starting at position start_index in */
4684 if((wh = search_scroll_line(SLINE(start_line) + start_index,
4685 word,
4686 SLEN(start_line) - start_index,
4687 st->parms->text.handles != NULL)) != NULL){
4688 cursor_pos->row = start_line;
4689 cursor_pos->col = scroll_handle_column(SROW(start_line),
4690 *offset_in_line = wh - SLINE(start_line));
4691 return(start_line);
4694 if(st->parms->text.src == FileStar)
4695 offset++;
4697 for(l = start_line + 1; l < st->num_lines; l++) {
4698 if(st->parms->text.src == FileStar && l > offset + dlines)
4699 ScrollFile(offset += dlines);
4701 if((wh = search_scroll_line(SLINE(l), word, SLEN(l),
4702 st->parms->text.handles != NULL)) != NULL){
4703 cursor_pos->row = l;
4704 cursor_pos->col = scroll_handle_column(SROW(l),
4705 *offset_in_line = wh - SLINE(l));
4706 return(l);
4710 else
4711 start_line = st->num_lines;
4713 if(st->parms->text.src == FileStar) /* wrap offset */
4714 ScrollFile(offset = 0);
4716 for(l = 0; l < start_line; l++) {
4717 if(st->parms->text.src == FileStar && l > offset + dlines)
4718 ScrollFile(offset += dlines);
4720 if((wh = search_scroll_line(SLINE(l), word, SLEN(l),
4721 st->parms->text.handles != NULL)) != NULL){
4722 cursor_pos->row = l;
4723 cursor_pos->col = scroll_handle_column(SROW(l),
4724 *offset_in_line = wh - SLINE(l));
4725 return(l);
4729 /* search in current line */
4730 if(start_line < st->num_lines
4731 && (wh = search_scroll_line(SLINE(start_line), word,
4732 start_index + strlen(word) - 2,
4733 st->parms->text.handles != NULL)) != NULL){
4734 cursor_pos->row = start_line;
4735 cursor_pos->col = scroll_handle_column(SROW(start_line),
4736 *offset_in_line = wh - SLINE(start_line));
4738 return(start_line);
4741 /* see if the only match is a repeat */
4742 if(start_index > 0 && start_line < st->num_lines
4743 && (wh = search_scroll_line(
4744 SLINE(start_line) + start_index - 1,
4745 word, strlen(word),
4746 st->parms->text.handles != NULL)) != NULL){
4747 cursor_pos->row = start_line;
4748 cursor_pos->col = scroll_handle_column(SROW(start_line),
4749 *offset_in_line = wh - SLINE(start_line));
4750 return(-3);
4753 return(-2);
4757 /*----------------------------------------------------------------------
4758 Search one line of scroll text for given string
4760 Args: haystack -- The string to search in, the larger string
4761 needle -- The string to search for, the smaller string
4762 n -- The max number of chars in haystack to search
4764 Search for first occurrence of needle in the haystack, and return a pointer
4765 into the string haystack when it is found. The search is case independent.
4766 ----*/
4767 char *
4768 search_scroll_line(char *haystack, char *needle, int n, int handles)
4770 char *return_ptr = NULL, *found_it = NULL;
4771 char *haystack_copy, *p, *free_this = NULL, *end;
4772 char buf[1000];
4773 int state = 0, i = 0;
4775 if(n > 0 && haystack){
4776 if(n < sizeof(buf))
4777 haystack_copy = buf;
4778 else
4779 haystack_copy = free_this = (char *) fs_get((n+1) * sizeof(char));
4781 strncpy(haystack_copy, haystack, n);
4782 haystack_copy[n] = '\0';
4785 * We don't want to match text inside embedded tags.
4786 * Replace embedded octets with nulls and convert
4787 * uppercase ascii to lowercase. We should also do
4788 * some sort of canonicalization of UTF-8 but that
4789 * sounds daunting.
4791 for(i = n, p = haystack_copy; i-- > 0 && *p; p++){
4792 if(handles)
4793 switch(state){
4794 case 0 :
4795 if(*p == TAG_EMBED){
4796 *p = '\0';
4797 state = -1;
4798 continue;
4800 else{
4801 /* lower case just ascii chars */
4802 if(!(*p & 0x80) && isupper(*p))
4803 *p = tolower(*p);
4806 break;
4808 case -1 :
4809 state = (*p == TAG_HANDLE)
4810 ? -2
4811 : (*p == TAG_FGCOLOR || *p == TAG_BGCOLOR) ? RGBLEN : 0;
4812 *p = '\0';
4813 continue;
4815 case -2 :
4816 state = *p; /* length of handle's key */
4817 *p = '\0';
4818 continue;
4820 default :
4821 state--;
4822 *p = '\0';
4823 continue;
4828 * The haystack_copy string now looks like
4830 * "chars\0\0\0\0\0\0chars...\0\0\0chars... \0"
4832 * with that final \0 at haystack_copy[n].
4833 * Search each piece one at a time.
4836 end = haystack_copy + n;
4837 p = haystack_copy;
4839 while(p < end && !return_ptr){
4841 /* skip nulls */
4842 while(*p == '\0' && p < end)
4843 p++;
4845 if(*p != '\0')
4846 found_it = srchstr(p, needle);
4848 if(found_it){
4849 /* found it, make result relative to haystack */
4850 return_ptr = haystack + (found_it - haystack_copy);
4853 /* skip to next null */
4854 while(*p != '\0' && p < end)
4855 p++;
4858 if(free_this)
4859 fs_give((void **) &free_this);
4862 return(return_ptr);
4867 * Returns the number of columns taken up by the visible part of the line.
4868 * That is, account for handles and color changes and so forth.
4871 visible_linelen(int line)
4873 SCRLCTRL_S *st = scroll_state(SS_CUR);
4874 int i, n, len = 0;
4876 if(line < 0 || line >= st->num_lines)
4877 return(len);
4879 for(i = 0, len = 0; i < st->line_lengths[line];)
4880 switch(st->text_lines[line][i]){
4882 case TAG_EMBED:
4883 i++;
4884 switch((i < st->line_lengths[line]) ? st->text_lines[line][i] : 0){
4885 case TAG_HANDLE:
4886 i++;
4887 /* skip the length byte plus <length> more bytes */
4888 if(i < st->line_lengths[line]){
4889 n = st->text_lines[line][i];
4890 i++;
4893 if(i < st->line_lengths[line] && n > 0)
4894 i += n;
4896 break;
4898 case TAG_FGCOLOR :
4899 case TAG_BGCOLOR :
4900 i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
4901 break;
4903 case TAG_INVON:
4904 case TAG_INVOFF:
4905 case TAG_BOLDON:
4906 case TAG_BOLDOFF:
4907 case TAG_ULINEON:
4908 case TAG_ULINEOFF:
4909 i++;
4910 break;
4912 case TAG_EMBED: /* escaped embed character */
4913 i++;
4914 len++;
4915 break;
4917 default: /* the embed char was literal */
4918 i++;
4919 len += 2;
4920 break;
4923 break;
4925 case TAB:
4926 i++;
4927 while(((++len) & 0x07) != 0) /* add tab's spaces */
4930 break;
4932 default:
4933 i++;
4934 len++;
4935 break;
4938 return(len);
4942 /*----------------------------------------------------------------------
4943 Display the contents of the given file (likely output from some command)
4945 Args: filename -- name of file containing output
4946 title -- title to be used for screen displaying output
4947 alt_msg -- if no output, Q this message instead of the default
4948 mode -- non-zero to display short files in status line
4949 Returns: none
4950 ----*/
4951 void
4952 display_output_file(char *filename, char *title, char *alt_msg, int mode)
4954 STORE_S *in_file = NULL, *out_store = NULL;
4956 if((in_file = so_get(FileStar, filename, READ_ACCESS|READ_FROM_LOCALE))){
4957 if(mode == DOF_BRIEF){
4958 int msg_q = 0, i = 0;
4959 char buf[512], *msg_p[4];
4960 #define MAX_SINGLE_MSG_LEN 60
4962 buf[0] = '\0';
4963 msg_p[0] = buf;
4966 * Might need to do something about CRLFs for Windows.
4968 while(so_fgets(in_file, msg_p[msg_q], sizeof(buf) - (msg_p[msg_q] - buf))
4969 && msg_q < 3
4970 && (i = strlen(msg_p[msg_q])) < MAX_SINGLE_MSG_LEN){
4971 msg_p[msg_q+1] = msg_p[msg_q]+strlen(msg_p[msg_q]);
4972 if (*(msg_p[++msg_q] - 1) == '\n')
4973 *(msg_p[msg_q] - 1) = '\0';
4976 if(msg_q < 3 && i < MAX_SINGLE_MSG_LEN){
4977 if(*msg_p[0])
4978 for(i = 0; i < msg_q; i++)
4979 q_status_message2(SM_ORDER, 3, 4,
4980 "%s Result: %s", title, msg_p[i]);
4981 else
4982 q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
4983 alt_msg
4984 ? alt_msg
4985 : " command completed with no output");
4987 so_give(&in_file);
4988 in_file = NULL;
4991 else if(mode == DOF_EMPTY){
4992 unsigned char c;
4994 if(so_readc(&c, in_file) < 1){
4995 q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
4996 alt_msg
4997 ? alt_msg
4998 : " command completed with no output");
4999 so_give(&in_file);
5000 in_file = NULL;
5005 * We need to translate the file contents from the user's locale
5006 * charset to UTF-8 for use in scrolltool. We get that translation
5007 * from the READ_FROM_LOCALE in the in_file storage object.
5008 * It would be nice to skip this step but scrolltool doesn't use
5009 * the storage object routines to read from the file, so would
5010 * skip the translation step.
5012 if(in_file){
5013 char *errstr;
5014 gf_io_t gc, pc;
5016 if(!(out_store = so_get(CharStar, NULL, EDIT_ACCESS))){
5017 so_give(&in_file);
5018 our_unlink(filename);
5019 q_status_message(SM_ORDER | SM_DING, 3, 3,
5020 _("Error allocating space."));
5021 return;
5024 so_seek(in_file, 0L, 0);
5026 gf_filter_init();
5028 gf_link_filter(gf_wrap,
5029 gf_wrap_filter_opt(ps_global->ttyo->screen_cols - 4,
5030 ps_global->ttyo->screen_cols,
5031 NULL, 0, GFW_NONE));
5033 gf_set_so_readc(&gc, in_file);
5034 gf_set_so_writec(&pc, out_store);
5036 if((errstr = gf_pipe(gc, pc)) != NULL){
5037 so_give(&in_file);
5038 so_give(&out_store);
5039 our_unlink(filename);
5040 q_status_message(SM_ORDER | SM_DING, 3, 3,
5041 _("Error allocating space."));
5042 return;
5045 gf_clear_so_writec(out_store);
5046 gf_clear_so_readc(in_file);
5047 so_give(&in_file);
5050 if(out_store){
5051 SCROLL_S sargs;
5052 char title_buf[64];
5054 snprintf(title_buf, sizeof(title_buf), "HELP FOR %s VIEW", title);
5055 title_buf[sizeof(title_buf)-1] = '\0';
5057 memset(&sargs, 0, sizeof(SCROLL_S));
5058 sargs.text.text = so_text(out_store);
5059 sargs.text.src = CharStar;
5060 sargs.text.desc = "output";
5061 sargs.bar.title = title;
5062 sargs.bar.style = TextPercent;
5063 sargs.help.text = h_simple_text_view;
5064 sargs.help.title = title_buf;
5065 scrolltool(&sargs);
5066 ps_global->mangled_screen = 1;
5067 so_give(&out_store);
5070 our_unlink(filename);
5072 else
5073 dprint((2, "Error reopening %s to get results: %s\n",
5074 filename ? filename : "?", error_description(errno)));
5078 /*--------------------------------------------------------------------
5079 Call the function that will perform the double click operation
5081 Returns: the current top line
5082 --------*/
5083 long
5084 doubleclick_handle(SCROLL_S *sparms, HANDLE_S *next_handle, int *cmd, int *done)
5086 if(sparms->mouse.clickclick){
5087 if((*sparms->mouse.clickclick)(sparms))
5088 *done = 1;
5090 else
5091 switch(scroll_handle_launch(next_handle, TRUE)){
5092 case 1 :
5093 *cmd = MC_EXIT; /* propagate */
5094 *done = 1;
5095 break;
5097 case -1 :
5098 cmd_cancelled("View");
5099 break;
5101 default :
5102 break;
5105 return(scroll_state(SS_CUR)->top_text_line);
5110 #ifdef _WINDOWS
5112 * Just a little something to simplify assignments
5114 #define VIEWPOPUP(p, c, s) { \
5115 (p)->type = tQueue; \
5116 (p)->data.val = c; \
5117 (p)->label.style = lNormal; \
5118 (p)->label.string = s; \
5126 format_message_popup(sparms, in_handle)
5127 SCROLL_S *sparms;
5128 int in_handle;
5130 MPopup fmp_menu[32];
5131 HANDLE_S *h = NULL;
5132 int i = -1, n;
5133 long rawno;
5134 MESSAGECACHE *mc;
5136 /* Reason to offer per message ops? */
5137 if(mn_get_total(ps_global->msgmap) > 0L){
5138 if(in_handle){
5139 SCRLCTRL_S *st = scroll_state(SS_CUR);
5141 switch((h = get_handle(st->parms->text.handles, in_handle))->type){
5142 case Attach :
5143 fmp_menu[++i].type = tIndex;
5144 fmp_menu[i].label.string = "View Attachment";
5145 fmp_menu[i].label.style = lNormal;
5146 fmp_menu[i].data.val = 'X'; /* for local use */
5148 if(h->h.attach
5149 && dispatch_attachment(h->h.attach) != MCD_NONE
5150 && !(h->h.attach->can_display & MCD_EXTERNAL)
5151 && h->h.attach->body
5152 && (h->h.attach->body->type == TYPETEXT
5153 || (h->h.attach->body->type == TYPEMESSAGE
5154 && h->h.attach->body->subtype
5155 && !strucmp(h->h.attach->body->subtype,"rfc822")))){
5156 fmp_menu[++i].type = tIndex;
5157 fmp_menu[i].label.string = "View Attachment in New Window";
5158 fmp_menu[i].label.style = lNormal;
5159 fmp_menu[i].data.val = 'Y'; /* for local use */
5162 fmp_menu[++i].type = tIndex;
5163 fmp_menu[i].label.style = lNormal;
5164 fmp_menu[i].data.val = 'Z'; /* for local use */
5165 msgno_exceptions(ps_global->mail_stream,
5166 mn_m2raw(ps_global->msgmap,
5167 mn_get_cur(ps_global->msgmap)),
5168 h->h.attach->number, &n, FALSE);
5169 fmp_menu[i].label.string = (n & MSG_EX_DELETE)
5170 ? "Undelete Attachment"
5171 : "Delete Attachment";
5172 break;
5174 case URL :
5175 default :
5176 fmp_menu[++i].type = tIndex;
5177 fmp_menu[i].label.string = "View Link";
5178 fmp_menu[i].label.style = lNormal;
5179 fmp_menu[i].data.val = 'X'; /* for local use */
5181 fmp_menu[++i].type = tIndex;
5182 fmp_menu[i].label.string = "Copy Link";
5183 fmp_menu[i].label.style = lNormal;
5184 fmp_menu[i].data.val = 'W'; /* for local use */
5185 break;
5188 fmp_menu[++i].type = tSeparator;
5191 /* Delete or Undelete? That is the question. */
5192 fmp_menu[++i].type = tQueue;
5193 fmp_menu[i].label.style = lNormal;
5194 mc = ((rawno = mn_m2raw(ps_global->msgmap,
5195 mn_get_cur(ps_global->msgmap))) > 0L
5196 && ps_global->mail_stream
5197 && rawno <= ps_global->mail_stream->nmsgs)
5198 ? mail_elt(ps_global->mail_stream, rawno) : NULL;
5199 if(mc && mc->deleted){
5200 fmp_menu[i].data.val = 'U';
5201 fmp_menu[i].label.string = in_handle
5202 ? "&Undelete Message" : "&Undelete";
5204 else{
5205 fmp_menu[i].data.val = 'D';
5206 fmp_menu[i].label.string = in_handle
5207 ? "&Delete Message" : "&Delete";
5210 if(F_ON(F_ENABLE_FLAG, ps_global)){
5211 fmp_menu[++i].type = tSubMenu;
5212 fmp_menu[i].label.string = "Flag";
5213 fmp_menu[i].data.submenu = flag_submenu(mc);
5216 i++;
5217 VIEWPOPUP(&fmp_menu[i], 'S', in_handle ? "&Save Message" : "&Save");
5219 i++;
5220 VIEWPOPUP(&fmp_menu[i], 'E', in_handle ? "&Export Message" : "&Export");
5222 i++;
5223 VIEWPOPUP(&fmp_menu[i], '%', in_handle ? "Print Message" : "Print");
5225 i++;
5226 VIEWPOPUP(&fmp_menu[i], 'R',
5227 in_handle ? "&Reply to Message" : "&Reply");
5229 i++;
5230 VIEWPOPUP(&fmp_menu[i], 'F',
5231 in_handle ? "&Forward Message" : "&Forward");
5233 i++;
5234 VIEWPOPUP(&fmp_menu[i], 'B',
5235 in_handle ? "&Bounce Message" : "&Bounce");
5237 i++;
5238 VIEWPOPUP(&fmp_menu[i], 'T', "&Take Addresses");
5240 fmp_menu[++i].type = tSeparator;
5242 if(mn_get_cur(ps_global->msgmap) < mn_get_total(ps_global->msgmap)){
5243 i++;
5244 VIEWPOPUP(&fmp_menu[i], 'N', "View &Next Message");
5247 if(mn_get_cur(ps_global->msgmap) > 0){
5248 i++;
5249 VIEWPOPUP(&fmp_menu[i], 'P', "View &Prev Message");
5252 if(mn_get_cur(ps_global->msgmap) < mn_get_total(ps_global->msgmap)
5253 || mn_get_cur(ps_global->msgmap) > 0)
5254 fmp_menu[++i].type = tSeparator;
5256 /* Offer the attachment screen? */
5257 for(n = 0; ps_global->atmts && ps_global->atmts[n].description; n++)
5260 if(n > 1){
5261 i++;
5262 VIEWPOPUP(&fmp_menu[i], 'V', "&View Attachment Index");
5266 i++;
5267 VIEWPOPUP(&fmp_menu[i], 'I', "Message &Index");
5269 i++;
5270 VIEWPOPUP(&fmp_menu[i], 'M', "&Main Menu");
5272 fmp_menu[++i].type = tTail;
5274 if((i = mswin_popup(fmp_menu)) >= 0 && in_handle)
5275 switch(fmp_menu[i].data.val){
5276 case 'W' : /* Copy URL to clipboard */
5277 mswin_addclipboard(h->h.url.path);
5278 break;
5280 case 'X' :
5281 return(1); /* return like the user double-clicked */
5283 break;
5285 case 'Y' : /* popup the thing in another window */
5286 display_att_window(h->h.attach);
5287 break;
5289 case 'Z' :
5290 if(h && h->type == Attach){
5291 msgno_exceptions(ps_global->mail_stream,
5292 mn_m2raw(ps_global->msgmap,
5293 mn_get_cur(ps_global->msgmap)),
5294 h->h.attach->number, &n, FALSE);
5295 n ^= MSG_EX_DELETE;
5296 msgno_exceptions(ps_global->mail_stream,
5297 mn_m2raw(ps_global->msgmap,
5298 mn_get_cur(ps_global->msgmap)),
5299 h->h.attach->number, &n, TRUE);
5300 q_status_message2(SM_ORDER, 0, 3, "Attachment %s %s!",
5301 h->h.attach->number,
5302 (n & MSG_EX_DELETE) ? "deleted" : "undeleted");
5304 return(2);
5307 break;
5309 default :
5310 break;
5313 return(0);
5322 simple_text_popup(sparms, in_handle)
5323 SCROLL_S *sparms;
5324 int in_handle;
5326 MPopup simple_menu[12];
5327 int n = 0;
5329 VIEWPOPUP(&simple_menu[n], '%', "Print");
5330 n++;
5332 VIEWPOPUP(&simple_menu[n], 'S', "Save");
5333 n++;
5335 VIEWPOPUP(&simple_menu[n], 'F', "Forward in Email");
5336 n++;
5338 simple_menu[n++].type = tSeparator;
5340 VIEWPOPUP(&simple_menu[n], 'E', "Exit Viewer");
5341 n++;
5343 simple_menu[n].type = tTail;
5345 (void) mswin_popup(simple_menu);
5346 return(0);
5351 /*----------------------------------------------------------------------
5352 Return characters in scroll tool buffer serially
5354 Args: n -- index of char to return
5356 Returns: returns the character at index 'n', or -1 on error or
5357 end of buffer.
5359 ----*/
5361 mswin_readscrollbuf(n)
5362 int n;
5364 SCRLCTRL_S *st = scroll_state(SS_CUR);
5365 int c;
5366 static char **orig = NULL, **l, *p;
5367 static int lastn;
5369 if(!st)
5370 return(-1);
5373 * All of these are mind-numbingly slow at the moment...
5375 switch(st->parms->text.src){
5376 case CharStar :
5377 return((n >= strlen((char *)st->parms->text.text))
5378 ? -1 : ((char *)st->parms->text.text)[n]);
5380 case CharStarStar :
5381 /* BUG? is this test rigorous enough? */
5382 if(orig != (char **)st->parms->text.text || n < lastn){
5383 lastn = n;
5384 if(orig = l = (char **)st->parms->text.text) /* reset l and p */
5385 p = *l;
5387 else{ /* use cached l and p */
5388 c = n; /* and adjust n */
5389 n -= lastn;
5390 lastn = c;
5393 while(l){ /* look for 'n' on each line */
5394 for(; n && *p; n--, p++)
5397 if(n--) /* 'n' found ? */
5398 p = *++l;
5399 else
5400 break;
5403 return((l && *l) ? *p ? *p : '\n' : -1);
5405 case FileStar :
5406 return((fseek((FILE *)st->parms->text.text, (long) n, 0) < 0
5407 || (c = fgetc((FILE *)st->parms->text.text)) == EOF) ? -1 : c);
5409 default:
5410 return(-1);
5416 /*----------------------------------------------------------------------
5417 MSWin scroll callback. Called during scroll message processing.
5421 Args: cmd - what type of scroll operation.
5422 scroll_pos - paramter for operation.
5423 used as position for SCROLL_TO operation.
5425 Returns: TRUE - did the scroll operation.
5426 FALSE - was not able to do the scroll operation.
5427 ----*/
5429 pcpine_do_scroll (cmd, scroll_pos)
5430 int cmd;
5431 long scroll_pos;
5433 SCRLCTRL_S *st = scroll_state(SS_CUR);
5434 HANDLE_S *next_handle;
5435 int paint = FALSE;
5436 int num_display_lines;
5437 int scroll_lines;
5438 char message[64];
5439 long maxscroll;
5442 message[0] = '\0';
5443 maxscroll = st->num_lines;
5444 switch (cmd) {
5445 case MSWIN_KEY_SCROLLUPLINE:
5446 if(st->top_text_line > 0) {
5447 st->top_text_line -= (int) scroll_pos;
5448 paint = TRUE;
5449 if (st->top_text_line <= 0){
5450 snprintf(message, sizeof(message), "START of %.*s",
5451 32, STYLE_NAME(st->parms));
5452 message[sizeof(message)-1] = '\0';
5453 st->top_text_line = 0;
5456 break;
5458 case MSWIN_KEY_SCROLLDOWNLINE:
5459 if(st->top_text_line < maxscroll) {
5460 st->top_text_line += (int) scroll_pos;
5461 paint = TRUE;
5462 if (st->top_text_line >= maxscroll){
5463 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5464 message[sizeof(message)-1] = '\0';
5465 st->top_text_line = maxscroll;
5468 break;
5470 case MSWIN_KEY_SCROLLUPPAGE:
5471 if(st->top_text_line > 0) {
5472 num_display_lines = SCROLL_LINES(ps_global);
5473 scroll_lines = MIN(MAX(num_display_lines -
5474 ps_global->viewer_overlap, 1), num_display_lines);
5475 if (st->top_text_line > scroll_lines)
5476 st->top_text_line -= scroll_lines;
5477 else {
5478 st->top_text_line = 0;
5479 snprintf(message, sizeof(message), "START of %.*s", 32, STYLE_NAME(st->parms));
5480 message[sizeof(message)-1] = '\0';
5482 paint = TRUE;
5484 break;
5486 case MSWIN_KEY_SCROLLDOWNPAGE:
5487 num_display_lines = SCROLL_LINES(ps_global);
5488 if(st->top_text_line < maxscroll) {
5489 scroll_lines = MIN(MAX(num_display_lines -
5490 ps_global->viewer_overlap, 1), num_display_lines);
5491 st->top_text_line += scroll_lines;
5492 if (st->top_text_line >= maxscroll) {
5493 st->top_text_line = maxscroll;
5494 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5495 message[sizeof(message)-1] = '\0';
5497 paint = TRUE;
5499 break;
5501 case MSWIN_KEY_SCROLLTO:
5502 if (st->top_text_line != scroll_pos) {
5503 st->top_text_line = scroll_pos;
5504 if (st->top_text_line == 0)
5505 snprintf(message, sizeof(message), "START of %.*s", 32, STYLE_NAME(st->parms));
5506 else if(st->top_text_line >= maxscroll)
5507 snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
5509 message[sizeof(message)-1] = '\0';
5510 paint = TRUE;
5512 break;
5515 /* Need to frame some handles? */
5516 if(st->parms->text.handles
5517 && (next_handle = scroll_handle_in_frame(st->top_text_line)))
5518 st->parms->text.handles = next_handle;
5520 if (paint) {
5521 mswin_beginupdate();
5522 update_scroll_titlebar(st->top_text_line, 0);
5523 (void) scroll_scroll_text(st->top_text_line,
5524 st->parms->text.handles, 1);
5525 if (message[0])
5526 q_status_message(SM_INFO, 0, 1, message);
5528 /* Display is always called so that the "START(END) of message"
5529 * message gets removed when no longer at the start(end). */
5530 display_message (KEY_PGDN);
5531 mswin_endupdate();
5534 return (TRUE);
5538 char *
5539 pcpine_help_scroll(title)
5540 char *title;
5542 SCRLCTRL_S *st = scroll_state(SS_CUR);
5544 if(title)
5545 strncpy(title, (st->parms->help.title)
5546 ? st->parms->help.title : "Alpine Help", 256);
5548 return(pcpine_help(st->parms->help.text));
5553 pcpine_view_cursor(col, row)
5554 int col;
5555 long row;
5557 SCRLCTRL_S *st = scroll_state(SS_CUR);
5558 int key;
5559 long line;
5561 return((row >= HEADER_ROWS(ps_global)
5562 && row < HEADER_ROWS(ps_global) + SCROLL_LINES(ps_global)
5563 && (line = (row - 2) + st->top_text_line) < st->num_lines
5564 && (key = dot_on_handle(line, col))
5565 && scroll_handle_selectable(get_handle(st->parms->text.handles,key)))
5566 ? MSWIN_CURSOR_HAND
5567 : MSWIN_CURSOR_ARROW);
5569 #endif /* _WINDOWS */