This is art.c in view mode; [Download] [Up]
/* -*- mode: C; mode: fold; -*- */
/* Copyright (c) 1996 John E. Davis (davis@space.mit.edu)
* All rights reserved.
*/
#include "config.h"
#include "features.h"
/*{{{ system include files */
#include <stdio.h>
#include <string.h>
#include <time.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif
#include <ctype.h>
#include <slang.h>
#include "jdmacros.h"
#ifndef isalpha
# define isalpha(x) \
((((x) <= 'Z') && ((x) >= 'A')) \
|| (((x) <= 'z') && ((x) >= 'a')))
#endif
#ifndef isspace
# define isspace(x) (((x) == ' ') || ((x) == '\t'))
#endif
/*}}}*/
/*{{{ slrn include files */
#include "slrn.h"
#include "group.h"
#include "server.h"
#include "art.h"
#include "misc.h"
#include "post.h"
/* #include "clientlib.h" */
#include "startup.h"
#include "hash.h"
#include "score.h"
#include "uudecode.h"
#include "menu.h"
#if SLRN_HAS_MIME
# include "mime.h"
#endif
#if SLRN_HAS_GROUPLENS
# include "grplens.h"
#endif
/*}}}*/
/*{{{ extern Global variables */
SLKeyMap_List_Type *Slrn_Article_Keymap;
char *Slrn_X_Browser;
char *Slrn_NonX_Browser;
char *Slrn_Quote_String = ">";
char *Slrn_Save_Directory;
int Slrn_Startup_With_Article = 0;
int Slrn_Show_Author = 2; /* If non-zero, show author in header */
int Slrn_Query_Next_Article = 1;
int Slrn_Query_Next_Group = 1;
int Slrn_Auto_CC_To_Poster = 0;
int Slrn_Score_After_XOver;
int Slrn_Use_Tmpdir = 0;
int Slrn_Threads_Visible = 0;
int Slrn_Use_Header_Numbers = 1;
#if SLRN_HAS_SORT_BY_SCORE
int Slrn_Display_Score;
#endif
int Slrn_Reads_Per_Update = 50;
int Slrn_Sig_Is_End_Of_Article = 0;
#if SLRN_HAS_SPOILERS
int Slrn_Spoiler_Char = 42;
#endif
char *Slrn_Current_Group_Name;
Slrn_Header_Type *Slrn_First_Header;
Slrn_Header_Type *Slrn_Current_Header;
/* range of articles on server for current group */
int Slrn_Server_Min, Slrn_Server_Max;
/* Sorting mode:
* 0 No sorting
* 1 sort by threads
* 2 sort by subject
* 3 sort by subject and threads
* 4 sort by score
* 5 sort by score and threads
* 6 sort by score then by subject
* 7 thread, then sort by score then subject
*/
#define SORT_BY_THREADS 1
#define SORT_BY_SUBJECT 2
#ifdef SLRN_HAS_SORT_BY_SCORE
# define SORT_BY_SCORE 4
#endif
int Slrn_Sorting_Mode = (SORT_BY_THREADS|SORT_BY_SUBJECT);
#define ALL_THREAD_FLAGS (FAKE_PARENT | FAKE_CHILDREN | FAKE_HEADER_HIGH_SCORE)
Slrn_Article_Line_Type *Slrn_Article_Lines;
/*}}}*/
/*{{{ static global variables */
static int Header_Number; /* line number of header window */
static int Num_Headers;
static unsigned int Number_Killed;
static unsigned int Number_High_Scored;
static unsigned int Number_Low_Scored;
static int User_Aborted_Group_Read;
#if SLRN_HAS_SPOILERS
static Slrn_Header_Type *Spoilers_Visible;
#endif
#if SLRN_HAS_GROUPLENS
static int Num_GroupLens_Rated = -1;
#endif
static Slrn_Header_Type *Mark_Header; /* header with mark set */
static Slrn_Group_Type *Current_Group; /* group being processed */
static Slrn_Header_Type *Headers;
static int Last_Cursor_Row; /* row where --> cursor last was */
static Slrn_Header_Type *Top_Header; /* header at top of window */
static Slrn_Header_Type *Header_Showing; /* header whose article is selected */
static Slrn_Header_Type *Last_Read_Header;
static int Article_Visible; /* non-zero if article window is visible */
static int Article_Window_Height;
static int Header_Window_Height;
static int Article_Line_Number; /* line number of article */
static int Article_Total_Lines; /* number of article lines */
static char Output_Filename[256];
static Slrn_Mode_Type *Last_Current_Mode;
static int Headers_Threaded;
/* If +1, threads are all collapsed. If zero, none are. If -1, some may
* be and some may not. In other words, if -1, this variable should not
* be trusted.
*/
static int Threads_Collapsed = 0;
#define HEADER_TABLE_SIZE 1250
static Slrn_Header_Type *Header_Table[HEADER_TABLE_SIZE];
static int Headers_Hidden_Mode = 1;
static int Quotes_Hidden = 0;
static char *Super_Cite_Regexp = "^[^A-Za-z0-9]*\"\\([a-zA-Z/]+\\)\" == .+";
static int Group_Modified;
static int Do_Rot13;
static int Perform_Scoring;
static int Largest_Header_Number;
static int HScroll;
static SLKeymap_Function_Type *Art_Functions_Ptr;
static Slrn_Article_Line_Type *Top_Line, *Current_Line;
static Slrn_Header_Type *At_End_Of_Article;
/* If this variable is NULL, then we are not at the end of an article. If it
* points at the current article, the we are at the end of that article.
* If it points anywhere else, ignore it.
*/
/*}}}*/
/*{{{ static function declarations */
static void slrn_art_hangup (int);
static void sort_threads (void);
static void hide_quotes (void);
static void wrap_article (void);
static void uncollapse_this_thread (Slrn_Header_Type *);
/* This is the function that is called to update the screen for this mode. */
static void art_update_screen (void);
static void art_next_unread (void);
static void (*Slrn_Group_Hangup_Hook) (int);
static void thread_headers (void);
static void sort_by_threads (void);
static void sort_by_sorting_mode (void);
static int select_article (int);
static Slrn_Article_Line_Type *unwrap_line (Slrn_Article_Line_Type *);
static void quick_help (void);
static void for_this_tree (Slrn_Header_Type *, void (*)(Slrn_Header_Type *));
static void skip_to_next_group (void);
#if SLRN_HAS_SPOILERS
static void show_spoilers (void);
#endif
/*}}}*/
/*{{{ utility functions */
static int is_blank_line (unsigned char *b)
{
b = (unsigned char *) slrn_skip_whitespace ((char *) b);
return (*b == 0);
}
static void digit_arg (void)
{
slrn_digit_arg (Slrn_Article_Keymap);
}
/*}}}*/
/*{{{ SIGWINCH and window resizing functions */
static void art_winch (void)
{
static int rows;
if ((rows != SLtt_Screen_Rows)
|| (Article_Window_Height == 0)
|| (Article_Window_Height >= SLtt_Screen_Rows - 4))
{
rows = SLtt_Screen_Rows;
Article_Window_Height = (3 * rows) / 4;
if (rows <= 28)
Article_Window_Height = rows - 8;
}
if (Article_Visible)
{
Header_Window_Height = rows - Article_Window_Height - 4;
}
else
{
Header_Window_Height = rows - 3;
}
if (Header_Window_Height < 1) Header_Window_Height = 1;
if (Article_Window_Height < 1) Article_Window_Height = 1;
Slrn_Full_Screen_Update = 1;
}
static void art_winch_sig (void)
{
Article_Window_Height = 0;
art_winch ();
}
static void shrink_window (void)
{
if (Article_Visible == 0) return;
Article_Window_Height++;
art_winch ();
}
static void enlarge_window (void)
{
if (Article_Visible == 0) return;
Article_Window_Height--;
art_winch ();
}
/*}}}*/
/*{{{ header hash functions */
static void delete_hash_table (void)
{
SLMEMSET ((char *) Header_Table, 0, sizeof (Header_Table));
}
static void make_hash_table (void)
{
Slrn_Header_Type *h;
delete_hash_table ();
h = Slrn_First_Header;
while (h != NULL)
{
h->hash_next = Header_Table[h->hash % HEADER_TABLE_SIZE];
Header_Table[h->hash % HEADER_TABLE_SIZE] = h;
h = h->real_next;
}
}
/*}}}*/
/*{{{ article line specific functions */
static void free_article (void)
{
Slrn_Article_Line_Type *l, *next;
l = Slrn_Article_Lines;
while (l != NULL)
{
SLFREE (l->buf);
next = l->next;
SLFREE (l);
l = next;
}
Top_Line = Current_Line = Slrn_Article_Lines = NULL;
Header_Showing = NULL;
Header_Window_Height = SLtt_Screen_Rows - 3;
Article_Visible = 0;
}
static void find_article_line_number (void)
{
Slrn_Article_Line_Type *l = Slrn_Article_Lines;
int n = 0;
while (l != NULL)
{
if ((l->flags & HIDDEN_LINE) == 0)
{
n++;
if (l == Current_Line) break;
}
l = l->next;
}
if (l == NULL)
{
l = Slrn_Article_Lines;
n = 0;
while (l != NULL)
{
if ((l->flags & HIDDEN_LINE) == 0)
{
Current_Line = l;
n = 1;
break;
}
l = l->next;
}
}
Article_Line_Number = n;
}
static void count_article_lines (void)
{
Slrn_Article_Line_Type *l = Slrn_Article_Lines;
int n = 0;
while (l != NULL)
{
if ((l->flags & HIDDEN_LINE) == 0)
{
n++;
}
l = l->next;
}
Article_Total_Lines = n;
}
static void hide_art_headers (void)
{
Slrn_Article_Line_Type *l = Slrn_Article_Lines;
char ch, ch1, ch2;
while ((l != NULL) && ((ch = *l->buf) != 0))
{
int hide_header;
ch |= 0x20;
ch1 = l->buf[1];
if (ch1) ch2 = l->buf[2]; else ch2 = 0;
ch1 |= 0x20;
ch2 |= 0x20;
l->flags = HEADER_LINE;
hide_header = 0;
switch (ch)
{
case 'r': /* references, etc... */
if (slrn_case_strncmp ((unsigned char *)l->buf,
(unsigned char *) "Reply-To:", 9))
hide_header = 1;
break;
case 'n': /* nntp */
if (ch1 == 'e') break;
/* drop */
case 'i': /* In-Reply-To */
case 'x': /* user defined */
case 'a': /* approved */
case 'p': /* path */
case 'l': /* lines */
case 'm': /* msgid, mime */
case 'c': /* content */
case 't': /* To */
hide_header = 1;
break;
case 'd': /* Distribution */
if (ch1 == 'a') break; /* Date ok */
hide_header = 1;
break;
case 's': /* sender etc... */
if ((ch1 == 'u') && (ch2 == 'b')) break;
hide_header = 1;
}
do
{
l->flags |= HEADER_LINE;
if (Headers_Hidden_Mode && hide_header)
{
l->flags |= HIDDEN_LINE;
}
else l->flags &= ~HIDDEN_LINE;
l = l->next;
}
while ((l != NULL) && ((*l->buf == ' ') || (*l->buf == '\t')));
}
Slrn_Full_Screen_Update = 1;
find_article_line_number ();
count_article_lines ();
}
static void skip_quoted_text (void)
{
Slrn_Article_Line_Type *l = Current_Line;
int linenum = -1;
/* look for a quoted line */
while (l != NULL)
{
if ((l->flags & HIDDEN_LINE) == 0)
{
Current_Line = l;
linenum++;
if (l->flags & QUOTE_LINE) break;
}
l = l->next;
}
if (linenum != -1) Article_Line_Number += linenum;
/* Now we are either at the end of the buffer or on a quote line. Skip
* past other quote lines.
*/
if (l == NULL) return;
l = l->next;
while (l != NULL)
{
if (l->flags & HIDDEN_LINE)
{
l = l->next;
continue;
}
Article_Line_Number++;
Current_Line = l;
if ((l->flags & QUOTE_LINE) == 0)
{
/* Check to see if it is blank */
if (is_blank_line ((unsigned char *) l->buf) == 0) break;
}
l = l->next;
}
}
static void skip_digest_forward (void)
{
Slrn_Article_Line_Type *l;
int num_passes;
/* We are looking for:
* <blank line> (actually, most digests do not have this-- even the FAQ that suggests it!!)
* ------------------------------
* <blank line>
* Subject: something
*
* In fact, most digests do not conform to this. So, I will look for:
* <blank line>
* Subject: something
*
* Actually, most faqs, etc... do not support this. So, look for any line
* beginning with a number on second pass. Sigh.
*/
num_passes = 0;
while (num_passes < 2)
{
l = Current_Line;
if (l != NULL) l = l->next;
while (l != NULL)
{
char ch;
char *buf;
if ((l->flags & HIDDEN_LINE) || (l->flags & HEADER_LINE))
{
l = l->next;
continue;
}
buf = l->buf;
if (num_passes == 0)
{
if ((strncmp ("Subject:", buf, 8))
|| (((ch = buf[8]) != ' ') && (ch != '\t')))
{
l = l->next;
continue;
}
}
else
{
ch = *buf;
if ((ch > '9') || (ch < '0'))
{
l = l->next;
continue;
}
}
Current_Line = l;
find_article_line_number ();
return;
}
num_passes++;
}
slrn_error ("No next digest.");
}
static int try_supercite (void)
{
Slrn_Article_Line_Type *l = Slrn_Article_Lines, *last, *lsave;
static unsigned char compiled_pattern_buf[256];
static SLRegexp_Type re;
unsigned char *b;
int count;
char name[32];
unsigned int len;
int ret;
re.pat = (unsigned char *) Super_Cite_Regexp;
re.buf = compiled_pattern_buf;
re.case_sensitive = 1;
re.buf_len = sizeof (compiled_pattern_buf);
/* skip header --- I should look for Xnewsreader: gnus */
while ((l != NULL) && (*l->buf != 0)) l = l->next;
if ((*compiled_pattern_buf == 0) && SLang_regexp_compile (&re))
return -1;
/* look at the first 15 lines on first attempt.
* After that, scan the whole buffer looking for more citations */
count = 15;
lsave = l;
ret = -1;
while (1)
{
while (count && (l != NULL))
{
if ((l->flags & QUOTE_LINE) == 0)
{
if (NULL != slrn_regexp_match (&re, l->buf))
{
l->flags |= QUOTE_LINE;
break;
}
}
l = l->next;
count--;
}
if ((l == NULL) || (count == 0)) return ret;
/* Now find out what is used for citing. */
b = (unsigned char *) l->buf + re.beg_matches[1];
len = re.end_matches[1];
if (len > sizeof (name) - 2) return ret;
ret = 0;
strncpy (name, (char *) b, len); name[len] = 0;
/* strcat (name, ">");
len++; */
while (l != NULL)
{
unsigned char ch;
b = (unsigned char *) l->buf;
last = l;
l = l->next;
if (last->flags & QUOTE_LINE) continue;
b = (unsigned char *) slrn_skip_whitespace ((char *) b);
if (!strncmp ((char *) b, name, len)
&& (((ch = b[len] | 0x20) < 'a')
|| (ch > 'z')))
{
last->flags |= QUOTE_LINE;
while (l != NULL)
{
b = (unsigned char *) slrn_skip_whitespace (l->buf);
if (strncmp ((char *) b, name, len)
|| (((ch = b[len] | 0x20) >= 'a')
&& (ch <= 'z')))
break;
l->flags |= QUOTE_LINE;
l = l->next;
}
}
}
count = -1;
l = lsave;
}
}
#if SLRN_HAS_SPOILERS
static void mark_spoilers (void)
{
Slrn_Article_Line_Type *l = Slrn_Article_Lines;
int spoiler = 0;
/* skip header */
while ((l != NULL) && (l->flags & HEADER_LINE))
l = l->next;
while (l != NULL)
{
if ((l->buf[0] == 12) && (l->buf[1] == 0))
{
spoiler = 1;
}
else if (spoiler)
{
l->flags |= SPOILER_LINE;
}
l = l->next;
}
}
#endif
static void mark_quotes (void)
{
Slrn_Article_Line_Type *l, *last;
unsigned char *b;
SLRegexp_Type **r;
if (0 == try_supercite ())
{
/* return; */
}
if (Slrn_Ignore_Quote_Regexp[0] == NULL) return;
/* skip header */
l = Slrn_Article_Lines;
while ((l != NULL) && (*l->buf != 0)) l = l->next;
while (l != NULL)
{
unsigned int min_len;
b = (unsigned char *) l->buf;
min_len = strlen ((char *) b);
last = l;
l = l->next; /* if first time through, this skips
* blank line at at last header.
*/
r = Slrn_Ignore_Quote_Regexp;
while (*r != NULL)
{
SLRegexp_Type *re;
re = *r;
if ((re->min_length <= min_len) &&
(NULL != SLang_regexp_match (b, min_len, re)))
{
last->flags |= QUOTE_LINE;
while (l != NULL)
{
b = (unsigned char *) l->buf;
min_len = strlen ((char *) b);
if ((re->min_length <= min_len)
&& (NULL == SLang_regexp_match (b, min_len, re)))
{
/* Here it might be a good idea to add:
* l = l->prev;
*/
break;
}
l->flags |= QUOTE_LINE;
l = l->next;
}
break;
}
r++;
}
}
}
static void mark_signature (void)
{
Slrn_Article_Line_Type *l = Slrn_Article_Lines;
int nmax = 10;
if (l == NULL) return;
/* go to end of article */
while (l->next != NULL) l = l->next;
/* skip back untill "-- " seen. Assume that it is not more than
* 10 lines long.
*/
while ((l != NULL) && nmax--
&& ((*l->buf != '-')
|| strcmp (l->buf, "-- ")))
l = l->prev;
if (nmax == -1) return;
while (l != NULL)
{
l->flags |= SIGNATURE_LINE;
l->flags &= ~(
QUOTE_LINE /* if in a signature, not a quote */
#if SLRN_HAS_SPOILERS
| SPOILER_LINE /* not a spoiler */
#endif
);
l = l->next;
}
}
static Slrn_Article_Line_Type *Extract_Header_Header_Line;
/* This does not take continued lines into account. This is a bug. */
static char *extract_header (char *hdr, unsigned int len)
{
Slrn_Article_Line_Type *l = Slrn_Article_Lines;
while ((l != NULL)
&& (*l->buf != 0))
{
if (0 == slrn_case_strncmp ((unsigned char *) hdr,
(unsigned char *) l->buf, len))
{
if ((l->next != NULL) && (l->next->flags & WRAPPED_LINE))
unwrap_line (l->next);
Extract_Header_Header_Line = l;
return l->buf + len;
}
l = l->next;
}
Extract_Header_Header_Line = NULL;
return NULL;
}
/*{{{ wrap article functions */
/* The input line is assumed to be the first wrapped portion of a line. For
* example, if a series of lines denoted as A/B is wrapped: A0/A1/A2/B0/B1,
* then to unwrap A, A1 is passed and B0 is returned.
*/
static Slrn_Article_Line_Type *unwrap_line (Slrn_Article_Line_Type *l)
{
char *b;
Slrn_Article_Line_Type *next, *ll;
ll = l->prev;
b = ll->buf;
do
{
b += strlen (b);
strcpy (b, l->buf + 1); /* skip the space at beginning of
* the wrapped line. */
next = l->next;
SLFREE (l->buf);
SLFREE (l);
if (l == Current_Line) Current_Line = ll;
l = next;
}
while ((l != NULL) && (l->flags & WRAPPED_LINE));
ll->next = l;
if (l != NULL) l->prev = ll;
return l;
}
int Slrn_Wrap_Mode = 3;
static void wrap_article (void)
{
unsigned int len;
unsigned char *buf, ch;
Slrn_Article_Line_Type *l = Slrn_Article_Lines;
int was_wrapped = 0;
unsigned int wrap_mode = Slrn_Wrap_Mode;
if (Slrn_Prefix_Arg_Ptr != NULL) wrap_mode = 0x7F;
Slrn_Prefix_Arg_Ptr = NULL;
if (Header_Showing == NULL) return;
/* First scan through to see if it needs unwrapped. */
while (l != NULL)
{
if (l->flags & WRAPPED_LINE)
{
l = unwrap_line (l);
was_wrapped = 1;
}
else l = l->next;
}
l = Slrn_Article_Lines;
if (was_wrapped == 0) while (l != NULL)
{
if (l->flags & HEADER_LINE)
{
if ((wrap_mode & 1) == 0)
{
l = l->next;
continue;
}
}
else if (l->flags & QUOTE_LINE)
{
if ((wrap_mode & 2) == 0)
{
l = l->next;
continue;
}
}
len = 0;
buf = (unsigned char *) l->buf;
ch = *buf;
while (ch != 0)
{
if (ch == '\t') len++;
else if (((int) (ch & 0x7F) < ' ')
|| (ch == 127))
{
len += 2;
if (ch & 0x80) len++;
}
else len++;
if (len > SLtt_Screen_Cols)
{
Slrn_Article_Line_Type *new_l;
/* Start wrapped lines with a space */
buf--;
ch = *buf;
*buf = ' ';
if ((NULL == (new_l = (Slrn_Article_Line_Type *)SLMALLOC(sizeof (Slrn_Article_Line_Type))))
|| (NULL == (new_l->buf = SLmake_string((char *)buf))))
{
if (new_l != NULL) SLFREE (new_l);
slrn_error ("Malloc error.");
return;
}
*buf++ = ch;
*buf = 0;
/* We are not expanding tabs so, get rid of them */
buf = (unsigned char *) l->buf;
while ((ch = *buf) != 0)
{
if (ch == '\t') *buf = ' ';
buf++;
}
new_l->next = l->next;
new_l->prev = l;
l->next = new_l;
if (new_l->next != NULL) new_l->next->prev = new_l;
new_l->flags = l->flags | WRAPPED_LINE;
l = new_l;
buf = (unsigned char *) new_l->buf;
len = 0;
}
else buf++;
ch = *buf;
}
l = l->next;
}
Slrn_Full_Screen_Update = 1;
find_article_line_number ();
count_article_lines ();
}
/*}}}*/
Slrn_Article_Line_Type *slrn_search_article (char *string,
char **ptrp,
int is_regexp,
int set_current_flag)
{
SLsearch_Type st;
Slrn_Article_Line_Type *l;
char *ptr;
int ret;
SLRegexp_Type *re;
if (-1 == (ret = select_article (1)))
return NULL;
if (is_regexp)
{
re = slrn_compile_regexp_pattern (string);
if (re == NULL)
return NULL;
}
else SLsearch_init (string, 1, 0, &st);
if (ret == 1)
{
art_update_screen ();
slrn_smg_refresh ();
l = Top_Line->next;
}
else
l = Slrn_Article_Lines;
while (l != NULL)
{
if ((l->flags & HIDDEN_LINE) == 0)
{
if (is_regexp)
ptr = (char *) slrn_regexp_match (re, l->buf);
else
ptr = (char *) SLsearch ((unsigned char *) l->buf,
(unsigned char *) l->buf + strlen (l->buf),
&st);
if (ptr != NULL)
{
if (ptrp != NULL) *ptrp = ptr;
if (set_current_flag)
{
Current_Line = l;
find_article_line_number ();
}
break;
}
}
l = l->next;
}
return l;
}
#define URL_SIG_STR "://"
#define BROWSER_BUFLEN 4096
static char *find_url (char *buf)
{
Slrn_Article_Line_Type *l;
char *ptr, *tmp, ch, *s;
unsigned int len;
if (NULL == (l = slrn_search_article (URL_SIG_STR, &ptr, 0, 1)))
return NULL;
s = l->buf;
if (ptr > s) ptr--;
while ((ptr >= s)
&& isalpha (*ptr))
ptr--;
ptr++;
tmp = ptr;
while ((ch = *ptr) != 0)
{
if (!isalpha(ch)
&& ((ch == ' ')
|| (ch == '\t')
|| (ch == '\n')
|| (ch == '\"')
|| (ch == '}')
|| (ch == '{')
|| (ch == ')')
|| (ch == ',')
|| (ch == ';')
|| (ch == '>')))
break;
ptr++;
}
len = (unsigned int) (ptr - tmp);
if (len >= BROWSER_BUFLEN) len = BROWSER_BUFLEN - 1;
strncpy (buf, tmp, len);
buf[len] = 0;
return buf;
}
static void browse_url (void)
{
char command [BROWSER_BUFLEN];
char url[BROWSER_BUFLEN];
char *browser, *has_percent;
int reinit;
if ((NULL == getenv ("DISPLAY")) || (NULL == Slrn_X_Browser))
browser = Slrn_NonX_Browser;
else browser = Slrn_X_Browser;
if (browser == NULL)
{
slrn_error ("No Web Browser has been defined.");
return;
}
/* Perform a simple-minded syntax check. */
has_percent = slrn_strchr (browser, '%');
if (has_percent != NULL)
{
if ((has_percent[1] != 's')
|| ((has_percent != browser) && (*(has_percent - 1) == '\\')))
has_percent = NULL;
}
if (NULL == find_url (url))
{
slrn_error ("No URLs found.");
return;
}
if (slrn_read_input ("Browse (^G aborts): ", url, 0) <= 0)
{
slrn_error ("Aborted.");
return;
}
if (has_percent != NULL)
sprintf (command, browser, url);
else
sprintf (command, "%s %s", browser, url);
reinit = (browser != Slrn_X_Browser);
slrn_posix_system (command, reinit);
/* if (reinit) slrn_redraw (); */
}
static void article_search (void)
{
static char search_str[256];
Slrn_Article_Line_Type *l;
if (slrn_read_input ("Search: ", search_str, 0) <= 0) return;
l = slrn_search_article (search_str, NULL, 0, 1);
if (l == NULL) slrn_error ("Not found.");
}
/*}}}*/
/*{{{ current article movement functions */
static int art_lineup_n (int n)
{
Slrn_Article_Line_Type *l;
int m;
if (select_article (1) <= 0) return 0;
l = Current_Line;
m = -1;
while ((n >= 0) && (l != NULL))
{
if ((l->flags & HIDDEN_LINE) == 0)
{
n--;
m++;
Current_Line = l;
}
l = l->prev;
}
/* This should ALWAYS be the case. */
if (m != -1)
{
Article_Line_Number -= m;
}
return m;
}
static void art_pageup (void)
{
art_lineup_n (Article_Window_Height - 1);
}
static void art_lineup (void)
{
art_lineup_n (1);
}
static void art_bob (void)
{
while (1000 == art_lineup_n (1000));
}
static int art_linedn_n (int n)
{
Slrn_Article_Line_Type *l;
int m;
if (select_article (1) <= 0)
return 0;
l = Current_Line;
m = -1;
while ((n >= 0) && (l->next != NULL))
{
if ((l->flags & HIDDEN_LINE) == 0)
{
n--;
m++;
Current_Line = l;
}
l = l->next;
}
if (m != -1)
{
Article_Line_Number += m;
}
return m;
}
static void art_linedn (void)
{
art_linedn_n (1);
}
static void art_eob (void)
{
while (art_linedn_n (10000) > 0)
;
(void) art_lineup_n (Article_Window_Height / 2);
}
/*}}}*/
/*{{{ Tag functions */
typedef struct
{
Slrn_Header_Type **headers;
unsigned int max_len;
unsigned int len;
}
Num_Tag_Type;
static Num_Tag_Type Num_Tag_List;
static void free_tag_list (void)
{
if (Num_Tag_List.headers != NULL)
{
SLFREE (Num_Tag_List.headers);
Num_Tag_List.headers = NULL;
Num_Tag_List.len = Num_Tag_List.max_len = 0;
}
}
void slrn_goto_num_tagged_header (int *nump)
{
unsigned int num;
num = (unsigned int) *nump;
num--;
if (num >= Num_Tag_List.len)
return;
if (Num_Tag_List.headers == NULL)
return;
slrn_goto_header (Num_Tag_List.headers[num], 0);
Slrn_Full_Screen_Update = 1;
}
static void num_tag_header (void)
{
char *memory_error = "Out of memory.";
unsigned int len;
uncollapse_this_thread (Slrn_Current_Header);
if (Num_Tag_List.headers == NULL)
{
Slrn_Header_Type **headers;
unsigned int max_len = 20;
if (NULL == (headers = (Slrn_Header_Type **) SLMALLOC (max_len * sizeof (Slrn_Header_Type))))
{
slrn_error (memory_error);
return;
}
Num_Tag_List.max_len = max_len;
Num_Tag_List.headers = headers;
Num_Tag_List.len = 0;
}
if (Num_Tag_List.max_len == Num_Tag_List.len)
{
Slrn_Header_Type **headers = Num_Tag_List.headers;
unsigned int max_len = Num_Tag_List.max_len + 20;
if (NULL == (headers = (Slrn_Header_Type **) SLREALLOC (headers,
max_len * sizeof (Slrn_Header_Type))))
{
slrn_error (memory_error);
return;
}
Num_Tag_List.max_len = max_len;
Num_Tag_List.headers = headers;
}
Slrn_Full_Screen_Update = 1;
if ((Slrn_Current_Header->flags & HEADER_NTAGGED) == 0)
{
Num_Tag_List.headers[Num_Tag_List.len] = Slrn_Current_Header;
Num_Tag_List.len += 1;
Slrn_Current_Header->tag_number = Num_Tag_List.len;
Slrn_Current_Header->flags |= HEADER_NTAGGED;
(void) slrn_header_down_n (1, 0);
return;
}
/* It is already tagged. So, what do we do. The most sensible thing to
* do is to simply give this header the last number and renumber the others
* that follow it. If it is the last one, untag it.
*/
if (Slrn_Current_Header->tag_number == Num_Tag_List.len)
{
Num_Tag_List.len -= 1;
Slrn_Current_Header->tag_number = 0;
Slrn_Current_Header->flags &= ~HEADER_NTAGGED;
return;
}
for (len = Slrn_Current_Header->tag_number + 1; len <= Num_Tag_List.len; len++)
{
Slrn_Header_Type *h = Num_Tag_List.headers[len - 1];
Num_Tag_List.headers[len - 2] = h;
h->tag_number -= 1;
}
Num_Tag_List.headers[len - 2] = Slrn_Current_Header;
Slrn_Current_Header->tag_number = len - 1;
}
static void num_untag_headers (void)
{
unsigned int len;
for (len = 1; len <= Num_Tag_List.len; len++)
{
Slrn_Header_Type *h = Num_Tag_List.headers[len - 1];
h->flags &= ~HEADER_NTAGGED;
h->tag_number = 0;
}
Num_Tag_List.len = 0;
Slrn_Full_Screen_Update = 1;
}
static void toggle_one_header_tag (Slrn_Header_Type *h)
{
if (h == NULL) return;
if (h->flags & HEADER_TAGGED)
{
h->flags &= ~HEADER_TAGGED;
}
else h->flags |= HEADER_TAGGED;
}
static void toggle_header_tag (void)
{
if (Slrn_Prefix_Arg_Ptr != NULL)
{
Slrn_Header_Type *h;
Slrn_Prefix_Arg_Ptr = NULL;
h = Headers;
while (h != NULL)
{
h->flags &= ~HEADER_TAGGED;
h = h->next;
}
Slrn_Full_Screen_Update = 1;
return;
}
if ((Slrn_Current_Header->parent != NULL)/* in middle of thread */
|| (Slrn_Current_Header->child == NULL)/* At top with no child */
/* or at top with child showing */
|| (0 == (Slrn_Current_Header->child->flags & HEADER_HIDDEN)))
{
toggle_one_header_tag (Slrn_Current_Header);
}
else
{
for_this_tree (Slrn_Current_Header, toggle_one_header_tag);
}
(void) slrn_header_down_n (1, 0);
Slrn_Full_Screen_Update = 1;
}
int slrn_prev_tagged_header (void)
{
Slrn_Header_Type *h = Slrn_Current_Header;
if (h == NULL) return 0;
while (h->prev != NULL)
{
h = h->prev;
if (h->flags & HEADER_TAGGED)
{
slrn_goto_header (h, 0);
return 1;
}
}
return 0;
}
int slrn_next_tagged_header (void)
{
Slrn_Header_Type *h = Slrn_Current_Header;
if (h == NULL) return 0;
while (h->next != NULL)
{
h = h->next;
if (h->flags & HEADER_TAGGED)
{
slrn_goto_header (h, 0);
return 1;
}
}
return 0;
}
/*}}}*/
/*{{{ Header specific functions */
static Slrn_Header_Type *find_header_from_serverid (int id)
{
Slrn_Header_Type *h;
h = Slrn_First_Header;
while (h != NULL)
{
if (h->number > id) return NULL;
if (h->number == id) break;
h = h->real_next;
}
return h;
}
static void kill_cross_references (Slrn_Header_Type *h)
{
char *b;
char group[256], *g;
long num;
if (h->xref == NULL)
{
if ((Header_Showing != h)
|| (NULL == (b = extract_header ("Xref:", 5))))
{
return;
}
}
else b = h->xref;
/* The format appears to be:
* Xref: machine group:num group:num...
*/
/* skip machine name */
while (*b > ' ') b++;
while (*b != 0)
{
while (*b == ' ') b++;
if (*b == 0) break;
/* now we are looking at the groupname */
g = group;
while (*b && (*b != ':')) *g++ = *b++;
if (*b++ == 0) break;
*g = 0;
num = atoi (b);
while ((*b <= '9') && (*b >= '0')) b++;
if ((num != h->number)
|| strcmp (group, Slrn_Current_Group_Name))
slrn_mark_article_as_read (group, num);
}
}
static void find_header_line_number (void)
{
Slrn_Header_Type *h = Headers;
Header_Number = 1;
while (h != Slrn_Current_Header)
{
if ((h->flags & HEADER_HIDDEN) == 0)
Header_Number++;
h = h->next;
}
Slrn_Full_Screen_Update = 1;
}
static void for_all_headers (void (*func)(Slrn_Header_Type *), int all)
{
Slrn_Header_Type *h, *end;
if (func == NULL) return;
if (all) end = NULL; else end = Slrn_Current_Header;
h = Headers;
while (h != end)
{
(*func)(h);
h = h->next;
}
}
int slrn_goto_header (Slrn_Header_Type *header, int read_flag)
{
Slrn_Header_Type *h = Slrn_First_Header;
while ((h != NULL) && (h != header))
h = h->real_next;
if (h == NULL) return -1;
if (h->flags & HEADER_HIDDEN) uncollapse_this_thread (h);
Slrn_Current_Header = h;
find_header_line_number ();
if (read_flag) select_article (1);
return 0;
}
/*{{{ parse_from */
static char *parse_from (char *from)
{
static char buf[256], *b;
char ch, ch1;
/* Here I am assume the following:
* 1. parenthesis and double quotes are comments.
* 2. If a name is in <> it is given precedence
*/
if (from == NULL) return NULL;
from = slrn_skip_whitespace (from);
b = buf;
/* strip comments */
while ((ch = *from++) != 0)
{
if (ch == '"')
{
ch1 = ch;
while (((ch = *from++) != 0) && (ch != ch1))
;
if (ch == 0) return NULL;
}
else if (ch == '(')
{
int nump = 1;
ch1 = ')';
while ((ch = *from++) != 0)
{
if (ch == '(') nump++;
else if (ch == ch1)
{
nump--;
if (nump == 0) break;
}
}
if (ch == 0) return NULL;
}
else if (ch == ')') return NULL;
else if (ch != ' ') *b++ = ch;
}
*b = 0;
from = b = buf;
while ((ch = *from++) != 0)
{
if (ch == '<')
{
while (((ch = *from++) != 0) && (ch != '>'))
{
*b++ = ch;
}
if (ch == 0) return NULL;
*b = 0;
break;
}
}
return buf;
}
/*}}}*/
/*{{{ fixup_realname */
static void fixup_realname (Slrn_Header_Type *h)
{
char *p, *pmin, *pmax;
if ((h->realname == NULL) || (h->realname_len == 0)) return;
pmin = h->realname;
p = pmin + (h->realname_len - 1);
while ((p >= pmin) && ((*p == ' ') || (*p == '\t'))) p--;
p++;
h->realname_len = (int) (p - pmin);
/* Check if realname is enclosed in "" and if so strip them off.
* Leave "" alone though.
*/
if ((p > pmin + 2) &&
(*pmin == '"') && (*(p - 1) == '"'))
{
h->realname++;
h->realname_len -= 2;
}
/* Look for a character in the range [A-Za-z]. If none, found use from.
*/
p = h->realname;
pmax = p + h->realname_len;
while (p < pmax)
{
char ch = *p;
if (isalpha (ch)) return;
p++;
}
h->realname = parse_from (h->from);
if (h->realname == NULL)
h->realname = h->from;
h->realname_len = strlen (h->realname);
}
/*}}}*/
/*{{{ get_header_real_name */
static void get_header_real_name (Slrn_Header_Type *h)
{
register char *f, *f0, *f1;
register char ch;
char *from = h->from;
int n;
f = from;
while (((ch = *f) != 0) && (ch != '<') && (ch != '(')) f++;
if (ch == '(')
{
/* Look for a '<'. There is some probability that this is of the
* form: John Doe (800) 555-1212 <doe@nowhere.com>
*/
f0 = f;
f1 = f + strlen (f);
while ((f1 > f) && (*f1 != ')') && (*f1 != '<')) f1--;
if (*f1 == '<')
{
ch = '<';
f = f1;
}
}
if (ch != '(')
{
if (ch == '<')
{
f1 = slrn_skip_whitespace (from);
if (*f1 == '<')
{
f = f1 + 1;
from = f;
while (((ch = *f) != 0) && (ch != '>')) f++;
}
}
h->realname = from;
h->realname_len = (int) (f - from);
fixup_realname (h);
return;
}
f1 = f;
f0 = f + 1;
f0 = slrn_skip_whitespace (f0);
if (*f0 == ')')
{
h->realname = from;
h->realname_len = (int) (f1 - from);
fixup_realname (h);
return;
}
f = f0;
n = 0;
while ((ch = *f) != 0)
{
if (ch == '(') n++;
else if (ch == ')')
{
if (n == 0) break;
n--;
}
f++;
}
h->realname = f0;
h->realname_len = (int) (f - f0);
fixup_realname (h);
}
/*}}}*/
static void extract_real_names (void)
{
Slrn_Header_Type *h = Headers;
while (h != NULL)
{
get_header_real_name (h);
h = h->next;
}
}
static Slrn_Header_Type *find_header_from_msgid (char *r0, char *r1)
{
unsigned long hash;
Slrn_Header_Type *h;
unsigned int len;
len = (unsigned int) (r1 - r0);
hash = slrn_compute_hash ((unsigned char *) r0, (unsigned char *) r1);
h = Header_Table[hash % HEADER_TABLE_SIZE];
while (h != NULL)
{
if (!slrn_case_strncmp ((unsigned char *) h->msgid,
(unsigned char *) r0,
len))
break;
h = h->hash_next;
}
return h;
}
Slrn_Header_Type *slrn_find_header_with_msgid (char *msgid)
{
return find_header_from_msgid (msgid, msgid + strlen (msgid));
}
static void goto_article (void)
{
Slrn_Header_Type *h;
int want_n;
if (-1 == slrn_read_integer ("Goto article: ", NULL, &want_n))
return;
h = Headers;
while (h != NULL)
{
if (h->number == want_n)
{
Slrn_Full_Screen_Update = 1;
if (h->flags & HEADER_HIDDEN) uncollapse_this_thread (h);
Slrn_Current_Header = h;
find_header_line_number ();
return;
}
h = h->next;
}
slrn_error ("Article not found.");
}
/*}}}*/
/*{{{ read_article */
static int read_article (Slrn_Header_Type *h, int kill_refs, int do_mime)
{
Slrn_Article_Line_Type *l;
char buf[NNTP_BUFFER_SIZE], *b, *b1, ch;
unsigned int len;
int n = h->number;
Slrn_Header_Type *last_header_showing;
int num_lines_update;
last_header_showing = Header_Showing;
if (Header_Showing == h)
{
#if SLRN_HAS_MIME
if (Slrn_Use_Mime == 0)
return 0;
if ((do_mime && Slrn_Mime_Was_Parsed)
|| ((do_mime == 0) && (Slrn_Mime_Was_Modified == 0)))
#endif
return 0; /* The cached copy is good */
}
if (h->tag_number) slrn_message ("#%2d/%-2d: Retrieving... %s",
h->tag_number, Num_Tag_List.len,
h->subject);
else slrn_message ("Reading...");
slrn_smg_refresh ();
slrn_set_suspension (1);
if (Slrn_Server_Obj->sv_select_article (n, h->msgid) < 0)
{
slrn_set_suspension (0);
slrn_error ("Article %d unavailable.", n);
if (kill_refs && ((h->flags & HEADER_READ) == 0))
{
kill_cross_references (h);
h->flags |= HEADER_READ;
}
return -1;
}
At_End_Of_Article = NULL;
if (n != -1) Group_Modified = 1;
Do_Rot13 = 0;
HScroll = 0;
Slrn_Full_Screen_Update = 1;
free_article ();
Article_Total_Lines = 0;
if ((num_lines_update = Slrn_Reads_Per_Update) < 5)
{
if (h->lines < 200)
num_lines_update = 20;
else
num_lines_update = 50;
}
while (Slrn_Server_Obj->sv_read_line(buf, sizeof(buf)) != NULL)
{
if (SLang_Error == USER_BREAK)
{
slrn_error ("Article transfer aborted.");
Slrn_Server_Obj->sv_close ();
if (Slrn_Article_Lines == NULL)
{
slrn_set_suspension (0);
return -1;
}
break;
}
Article_Total_Lines++;
if ((1 == (Article_Total_Lines % num_lines_update))
/* Just so the ratio does not confuse the reader because of the
* header lines...
*/
&& (Article_Total_Lines < h->lines))
{
if (h->tag_number)
slrn_message ("#%2d/%-2d: Read %4d/%-4d lines (%s)",
h->tag_number, Num_Tag_List.len,
Article_Total_Lines, h->lines, h->subject);
else
slrn_message ("Read %d/%d lines so far...",
Article_Total_Lines, h->lines);
slrn_smg_refresh ();
}
len = strlen (buf);
if ((NULL == (l = (Slrn_Article_Line_Type *) SLMALLOC (sizeof(Slrn_Article_Line_Type))))
|| (NULL == (l->buf = (char *) SLMALLOC (len + 1))))
{
slrn_exit_error ("Memory allocation error.");
}
/* here I am going to remove _^H combinations. Later, it will be a
* good idea to perform the implied underlining
*/
b1 = l->buf;
b = buf;
if ((*b == '.') && (*(b + 1) == '.')) b++;
while (0 != (ch = *b++))
{
if ((ch == '_') && (*b == '\b'))
{
b++;
}
else *b1++ = ch;
}
*b1 = 0;
l->next = l->prev = NULL;
l->flags = 0;
if (Slrn_Article_Lines == NULL)
{
Slrn_Article_Lines = l;
}
else
{
l->next = Current_Line->next;
l->prev = Current_Line;
Current_Line->next = l;
if (l->next != NULL) l->next->prev = l;
}
Current_Line = l;
}
Current_Line = Slrn_Article_Lines; Article_Line_Number = 1;
Slrn_Message_Present = 0;
if (kill_refs && ((h->flags & HEADER_READ) == 0))
{
kill_cross_references (h);
h->flags |= HEADER_READ;
}
/* Do this now so that header lines are marked. Mime processing
* assumes this.
*/
if (h == Slrn_Current_Header) hide_art_headers ();
#if SLRN_HAS_MIME
if (Slrn_Use_Mime)
{
/* Note: the mime routines assume that the article flags are valid.
* That is, a header line is given by line->flags & HEADER_LINE
*/
slrn_mime_article_init ();
if (do_mime) slrn_mime_process_article ();
}
#endif
if (h == Slrn_Current_Header)
{
mark_quotes ();
#if SLRN_HAS_SPOILERS
if (Slrn_Spoiler_Char) mark_spoilers ();
#endif
mark_signature ();
hide_quotes ();
}
if (last_header_showing != h)
{
Last_Read_Header = last_header_showing;
}
Header_Showing = h;
if (h == Slrn_Current_Header)
{
if (Slrn_Wrap_Mode & 0x4) wrap_article ();
if (Last_Read_Header == NULL)
Last_Read_Header = h;
}
slrn_set_suspension (0);
return 0;
}
/*}}}*/
/*{{{ reply, reply_cmd, forward, followup */
/*{{{ reply */
static void reply (void)
{
char *msgid, *from, *subject, *f;
Slrn_Article_Line_Type *l;
FILE *fp;
char file[256];
uncollapse_this_thread (Slrn_Current_Header);
if (read_article (Slrn_Current_Header, 1, 1) < 0) return;
/* Check for FQDN. If it appear bogus, warn user */
if (NULL == ( from = extract_header ("Reply-To: ", 10)))
{
from = extract_header ("From: ", 6);
}
from = parse_from (from);
if ((from == NULL)
|| (NULL == (f = slrn_strchr (from, '@')))
|| (f == from)
|| (0 == slrn_is_fqdn (f + 1)))
{
if (0 == slrn_get_yesno (1, "%s appears invalid. Continue anyway",
((from == NULL) ? "Email address" : from)))
return;
}
if (Slrn_Use_Tmpdir)
{
fp = slrn_open_tmpfile (file, "w");
}
else fp = slrn_open_home_file (SLRN_LETTER_FILENAME, "w", file, 0);
if (NULL == fp)
{
slrn_error ("Unable to open %s for writing.", file);
return;
}
/* parse header */
msgid = extract_header ("Message-ID: ", 12);
subject = extract_header ("Subject: ", 9);
fprintf (fp, "To: %s\nSubject: %s\nIn-Reply-To: %s\n",
from == NULL ? "" : from,
subject == NULL ? "" : subject,
msgid);
#ifdef __os2__
fprintf (fp, "From: %s@%s (%s)\n",
Slrn_User_Info.username, Slrn_User_Info.host,
Slrn_User_Info.realname);
#endif
if (0 != *Slrn_User_Info.replyto)
fprintf (fp, "Reply-To: %s\n", Slrn_User_Info.replyto);
#if 0
newsgroups = extract_header ("Newsgroups: ", 12);
if (newsgroups != NULL)
fprintf (fp, "Newsgroups: %s\n", newsgroups);
#endif
fputs ("\n", fp);
fprintf (fp, "In article %s, you wrote:\n", msgid == NULL ? "" : msgid);
l = Slrn_Article_Lines;
if (Slrn_Prefix_Arg_Ptr == NULL)
{
while ((l != NULL) && (*l->buf != 0)) l = l->next;
if (l != NULL) l = l->next;
}
while (l != NULL)
{
fprintf (fp, "%s%s\n", Slrn_Quote_String, l->buf);
l = l->next;
}
slrn_add_signature (fp);
slrn_fclose (fp);
slrn_mail_file (file, 1, 5, from, subject);
if (Slrn_Use_Tmpdir) (void) slrn_delete_file (file);
}
/*}}}*/
/*{{{ reply_cmd */
static void reply_cmd (void)
{
uncollapse_this_thread (Slrn_Current_Header);
if (read_article (Slrn_Current_Header, 1, 1) < 0) return;
if (Slrn_User_Wants_Confirmation
&& (slrn_get_yesno (1, "Are you sure you want to reply") <= 0))
return;
reply ();
}
/*}}}*/
/*{{{ forward */
static void forward (void)
{
char *subject;
Slrn_Article_Line_Type *l;
FILE *fp;
char file[256];
char to[256];
int n;
uncollapse_this_thread (Slrn_Current_Header);
if (read_article (Slrn_Current_Header, 1, 1) < 0) return;
*to = 0;
if (slrn_read_input ("Forward to (^G aborts): ", to, 1) <= 0)
{
slrn_error ("Aborted. An email address is required.");
return;
}
if (Slrn_Use_Tmpdir)
{
fp = slrn_open_tmpfile (file, "w");
}
else fp = slrn_open_home_file (SLRN_LETTER_FILENAME, "w", file, 0);
if (fp == NULL)
{
slrn_error ("Unable to open %s for writing.", file);
return;
}
subject = extract_header ("Subject: ", 9);
fprintf (fp, "To: %s\nSubject: %s\n",
to, subject == NULL ? "" : subject);
#ifdef __os2__
fprintf (fp, "From: %s@%s (%s)\n",
Slrn_User_Info.username, Slrn_User_Info.host,
Slrn_User_Info.realname);
#endif
if (0 != *Slrn_User_Info.replyto)
fprintf (fp, "Reply-To: %s (%s)\n",
Slrn_User_Info.replyto, Slrn_User_Info.realname);
putc ('\n', fp);
l = Slrn_Article_Lines;
while (l != NULL)
{
fprintf (fp, "%s\n", l->buf);
l = l->next;
}
slrn_fclose (fp);
n = slrn_get_yesno (1, "Edit the message before sending");
if (n >= 0)
slrn_mail_file (file, n, 3, to, subject);
if (Slrn_Use_Tmpdir) slrn_delete_file (file);
}
/*}}}*/
/*{{{ insert_followup_format */
static void insert_followup_format (char *f, FILE *fp)
{
char ch, *s, *smax;
while ((ch = *f++) != 0)
{
if (ch != '%')
{
putc (ch, fp);
continue;
}
s = smax = NULL;
ch = *f++;
if (ch == 0) break;
switch (ch)
{
case 's':
s = Slrn_Current_Header->subject;
break;
case 'm':
s = Slrn_Current_Header->msgid;
break;
case 'r':
s = Slrn_Current_Header->realname;
smax = s + Slrn_Current_Header->realname_len;
break;
case 'f':
s = parse_from (Slrn_Current_Header->from);
break;
case 'n':
s = extract_header ("Newsgroups: ", 12);
break;
case 'd':
s = Slrn_Current_Header->date;
break;
case '%':
default:
putc (ch, fp);
}
if (s == NULL) continue;
if (smax == NULL) fputs (s, fp);
else fwrite (s, 1, (unsigned int) (smax - s), fp);
}
}
/*}}}*/
/*{{{ followup */
/* If prefix arg is 1, insert all headers. If it is 2, insert all headers
* but do not quote text nor attach signature. 2 is good for re-posting.
*/
static void followup (void)
{
char *msgid, *from, *newsgroups, *subject, *xref, *quote_str;
Slrn_Article_Line_Type *l;
FILE *fp;
char file[256];
unsigned int n;
int prefix_arg;
int perform_cc;
if (Slrn_Post_Obj->po_can_post == 0)
{
slrn_error ("Posting not allowed by server.");
return;
}
uncollapse_this_thread (Slrn_Current_Header);
if (read_article (Slrn_Current_Header, 1, 1) < 0) return;
if (Slrn_User_Wants_Confirmation
&& (slrn_get_yesno (1, "Are you sure you want to followup") <= 0))
return;
if (NULL != (newsgroups = extract_header ("Followup-To: ", 13)))
{
newsgroups = slrn_skip_whitespace (newsgroups);
if (!slrn_case_strncmp ((unsigned char *) newsgroups,
(unsigned char *) "poster", 6))
{
if (slrn_get_yesno (1, "Do you want to reply to POSTER as poster prefers"))
{
reply ();
return;
}
newsgroups = NULL;
}
}
if (Slrn_Post_Obj->po_can_post == 0)
{
slrn_error ("Posting not allowed.");
return;
}
if (Slrn_Prefix_Arg_Ptr != NULL)
{
prefix_arg = *Slrn_Prefix_Arg_Ptr;
Slrn_Prefix_Arg_Ptr = NULL;
}
else prefix_arg = -1;
if (newsgroups == NULL)
{
if (NULL == (newsgroups = extract_header ("Newsgroups: ", 12)))
newsgroups = "";
}
if (NULL == (from = extract_header ("Reply-To: ", 10)))
{
from = extract_header ("From: ", 6);
}
from = parse_from (from);
if ((from == NULL) || (prefix_arg == 2))
perform_cc = 0;
else if (-1 == (perform_cc = Slrn_Auto_CC_To_Poster))
perform_cc = slrn_get_yesno (1, "Cc message to poster");
if (perform_cc)
{
char *ff;
if ((NULL == (ff = slrn_strchr (from, '@')))
|| (ff == from)
|| (0 == slrn_is_fqdn (ff + 1))
|| (strlen (ff + 1) < 5))
{
if (0 == slrn_get_yesno (1, "%s appears invalid. CC anyway", from))
perform_cc = 0;
}
}
msgid = extract_header ("Message-ID: ", 12);
if (NULL != (subject = extract_header ("Subject: ", 9)))
{
subject = slrn_skip_whitespace (subject);
if (((*subject | 0x20) == 'r')
&& ((*(subject + 1) | 0x20) == 'e')
&& (*(subject + 2) == ':'))
{
subject = slrn_skip_whitespace (subject + 3);
}
}
else subject = "";
if (Slrn_Use_Tmpdir)
{
fp = slrn_open_tmpfile (file, "w");
}
else fp = slrn_open_home_file (SLRN_FOLLOWUP_FILENAME, "w", file, 0);
if (fp == NULL)
{
slrn_error ("Unable to open %s for writing.", file);
return;
}
fprintf (fp, "Newsgroups: %s\nSubject: Re: %s\n",
newsgroups, subject); n = 3;
xref = extract_header("References: ", 12);
if (msgid != NULL)
{
fputs ("References: ", fp);
while (Extract_Header_Header_Line != NULL)
{
char ch;
fprintf (fp, "%s ", xref);
Extract_Header_Header_Line = Extract_Header_Header_Line->next;
if (Extract_Header_Header_Line == NULL) break;
xref = Extract_Header_Header_Line->buf;
ch = *xref++;
if ((ch != ' ') && (ch != '\t')) break;
}
fprintf (fp, "%s\n", msgid);
n++;
}
if (Slrn_User_Info.org != NULL)
{
fprintf (fp, "Organization: %s\n", Slrn_User_Info.org);
n++;
}
if (perform_cc)
{
fprintf (fp, "Cc: %s\n", from);
n++;
}
if (0 != *Slrn_User_Info.replyto)
{
fprintf (fp, "Reply-To: %s\n", Slrn_User_Info.replyto);
n++;
}
fprintf (fp, "Followup-To: \n");
n++;
n += slrn_add_custom_headers (fp);
fputs ("\n", fp);
if (prefix_arg != 2)
{
insert_followup_format (Slrn_User_Info.followup_string, fp);
fputs ("\n", fp);
}
n += 1; /* by having + 1, the cursor will be
* placed on the first line of message.
*/
/* skip header */
l = Slrn_Article_Lines;
if (prefix_arg == -1)
{
while ((l != NULL) && (*l->buf != 0)) l = l->next;
if (l != NULL) l = l->next;
}
if (prefix_arg == 2) quote_str = ""; else quote_str = Slrn_Quote_String;
while (l != NULL)
{
if ((l->next != NULL) && (l->next->flags & WRAPPED_LINE))
unwrap_line (l->next);
fprintf (fp, "%s%s\n", quote_str, l->buf);
l = l->next;
}
if (prefix_arg != 2) slrn_add_signature (fp);
slrn_fclose (fp);
if (slrn_edit_file (file, n) >= 0)
{
if (0 == slrn_post_file (file, from))
{
/* Success. */
if (Slrn_Last_Message_Id != NULL)
{
/* Later I want to actually get the article from the server
* and display it. Of course I can only do it if I know
* the message-id. If Slrn_Last_Message_Id is non-null,
* then slrn generated the message-id and we can do it.
*/
}
}
}
if (Slrn_Use_Tmpdir) (void) slrn_delete_file (file);
}
/*}}}*/
/*}}}*/
/*{{{ header movement functions */
int slrn_header_down_n (int n, int err)
{
Slrn_Header_Type *h;
int i;
h = Slrn_Current_Header;
if (h == NULL) return 0;
i = 0;
while (i < n)
{
h = h->next;
while ((h != NULL) && (h->flags & HEADER_HIDDEN))
{
h = h->next;
}
if (h == NULL)
{
if (err) slrn_error ("End of buffer.");
break;
}
Header_Number++;
Slrn_Current_Header = h;
i++;
}
return i;
}
static void header_down (void)
{
slrn_header_down_n (1, 1);
}
int slrn_header_up_n (int n, int err)
{
Slrn_Header_Type *h;
int i;
h = Slrn_Current_Header;
if (h == NULL) return 0;
i = 0;
while (i < n)
{
h = h->prev;
while ((h != NULL) && (h->flags & HEADER_HIDDEN))
{
h = h->prev;
}
if (h == NULL)
{
if (err) slrn_error ("Top of buffer.");
break;
}
i++;
Header_Number--;
Slrn_Current_Header = h;
}
return i;
}
static void header_up (void)
{
slrn_header_up_n (1, 1);
}
static void header_pageup (void)
{
Slrn_Header_Type *g;
int n;
if (Top_Header != NULL)
{
Slrn_Full_Screen_Update = 1;
/* Move to top of window, then go up one page */
if (Header_Window_Height <= 2)
{
header_up ();
return;
}
n = 0;
g = Slrn_Current_Header;
while ((g != Top_Header) && (g != NULL))
{
g = g->prev;
if ((g != NULL) && (g->flags & HEADER_HIDDEN) == 0)
n++;
}
if (g != NULL)
{
Slrn_Current_Header = g;
Header_Number -= n;
n = Header_Number; /* save this */
/* Now compute new top header */
slrn_header_up_n (Header_Window_Height - 2, 0);
Top_Header = Slrn_Current_Header;
Slrn_Current_Header = g; Header_Number = n;
return;
}
}
slrn_header_up_n (Header_Window_Height - 1, 1);
}
static void header_pagedn (void)
{
Slrn_Header_Type *h, *g;
int n;
/* find bottom header and bring it to the top */
if (Top_Header != NULL)
{
Slrn_Full_Screen_Update = 1;
/* This fails if the window is only one line */
n = Header_Window_Height - 1;
if (n < 2)
{
slrn_header_down_n (n + 1, 0);
return;
}
h = Top_Header;
while ((h->next != NULL) && n)
{
h = h->next;
if (0 == (h->flags & HEADER_HIDDEN)) n--;
}
if (n)
{
while (h->flags & HEADER_HIDDEN)
{
h = h->prev;
}
}
else Top_Header = h;
/* Now move the current to this header */
g = Slrn_Current_Header;
n = 0;
while ((g != h) && (g != NULL))
{
g = g->next;
if (0 == (g->flags & HEADER_HIDDEN)) n++;
}
if (g != NULL)
{
Slrn_Current_Header = g;
Header_Number += n;
}
return;
}
slrn_header_down_n (Article_Window_Height, 0);
}
static void art_header_bob (void)
{
Slrn_Current_Header = Headers;
Header_Number = 1;
}
static void art_header_eob (void)
{
while (1000 == slrn_header_down_n (1000, 0));
}
static int prev_unread (void)
{
Slrn_Header_Type *h;
int dn = 1;
h = Slrn_Current_Header -> prev;
while (h != NULL)
{
if (0 == (h->flags & HEADER_READ)) break;
if ((h->flags & HEADER_HIDDEN) == 0)
{
dn++;
}
h = h->prev;
}
if (h == NULL)
{
slrn_message ("No previous unread articles.");
return 0;
}
Slrn_Current_Header = h;
if (h->flags & HEADER_HIDDEN)
{
uncollapse_this_thread (h);
find_header_line_number ();
}
else Header_Number -= dn;
return 1;
}
static void art_pagedn (void)
{
if (Slrn_Current_Header == NULL) return;
#if SLRN_HAS_SPOILERS
if (Spoilers_Visible == Slrn_Current_Header)
{
show_spoilers ();
return;
}
#endif
if (Article_Visible && (At_End_Of_Article == Slrn_Current_Header))
{
unsigned char ch = ' ';
char *msg = NULL;
At_End_Of_Article = NULL;
if (Slrn_Current_Header->next == NULL)
{
if (Slrn_Query_Next_Group)
{
msg = "At end of article, press SPACE for next group.";
}
}
else if (Slrn_Query_Next_Article)
msg = "At end of article, press SPACE for next unread article.";
if (msg != NULL)
{
slrn_message (msg);
slrn_smg_refresh ();
ch = SLang_getkey ();
}
if (ch == ' ')
{
At_End_Of_Article = NULL;
if (Slrn_Current_Header->next != NULL) art_next_unread ();
else skip_to_next_group ();
}
else SLang_ungetkey (ch);
return;
}
At_End_Of_Article = NULL;
art_linedn_n (Article_Window_Height - 1);
}
static void goto_last_read (void)
{
if (Last_Read_Header == NULL) return;
slrn_goto_header (Last_Read_Header, 1);
}
static void art_prev_unread (void)
{
if (prev_unread () && Article_Visible) art_pagedn ();
}
static int next_unread (void)
{
Slrn_Header_Type *h;
int dn = 0;
h = Slrn_Current_Header->next;
while (h != NULL)
{
if ((h->flags & HEADER_HIDDEN) == 0)
{
/* if (0 == (h->flags & HEADER_READ)) break; */
dn++;
}
if (0 == (h->flags & HEADER_READ)) break;
h = h->next;
}
if (h == NULL)
{
slrn_message ("No following unread articles.");
return 0;
}
Slrn_Current_Header = h;
if (h->flags & HEADER_HIDDEN)
{
uncollapse_this_thread (h);
find_header_line_number ();
}
else Header_Number += dn;
return 1;
}
static void art_next_unread (void)
{
char ch;
unsigned char ch1;
if (next_unread ())
{
if (Article_Visible) art_pagedn ();
return;
}
if (Slrn_Query_Next_Group == 0)
{
skip_to_next_group ();
return;
}
ch1 = SLang_Last_Key_Char;
if (ch1 < 32) ch1 = 'n';
slrn_message ("No following unread articles. Press '%c' for other groups.", ch1);
slrn_smg_refresh ();
ch = SLang_getkey ();
if ((unsigned char)ch == ch1)
{
skip_to_next_group ();
}
else SLang_ungetkey (ch);
}
static void next_high_score (void)
{
Slrn_Header_Type *l;
l = Slrn_Current_Header->next;
while (l != NULL)
{
if (l->flags & HEADER_HIGH_SCORE)
{
break;
}
l = l->next;
}
if (l == NULL)
{
slrn_error ("No more high scoring articles.");
return;
}
if (l->flags & HEADER_HIDDEN) uncollapse_this_thread (l);
Slrn_Current_Header = l;
find_header_line_number ();
if (Article_Visible)
{
art_pagedn ();
}
}
/*{{{ next_header_same_subject */
static Slrn_Header_Type *Same_Subject_Start_Header;
static void next_header_same_subject (void)
{
SLsearch_Type st;
Slrn_Header_Type *l;
char *prompt;
static char same_subject[256];
if ((Same_Subject_Start_Header == NULL)
|| (Slrn_Prefix_Arg_Ptr != NULL))
{
Slrn_Prefix_Arg_Ptr = NULL;
prompt = "Subject: ";
if (slrn_read_input (prompt, same_subject, 0) <= 0) return;
Same_Subject_Start_Header = Slrn_Current_Header;
}
SLsearch_init (same_subject, 1, 0, &st);
l = Slrn_Current_Header->next;
while (l != NULL)
{
if (
#if 0
/* Do we want to do this?? */
((l->flags & HEADER_READ) == 0) &&
#endif
(l->subject != NULL)
&& (NULL != SLsearch ((unsigned char *) l->subject,
(unsigned char *) l->subject + strlen (l->subject),
&st)))
break;
l = l->next;
}
if (l == NULL)
{
slrn_error ("No more articles on that subject.");
l = Same_Subject_Start_Header;
Same_Subject_Start_Header = NULL;
}
if (l->flags & HEADER_HIDDEN) uncollapse_this_thread (l);
Slrn_Current_Header = l;
find_header_line_number ();
if ((Same_Subject_Start_Header != NULL)
&& (Article_Visible))
{
art_pagedn ();
}
}
/*}}}*/
/*{{{ goto_header_number */
static void goto_header_number (void)
{
int diff, i, ich;
i = 0;
ich = SLang_Last_Key_Char;
do
{
i = i * 10 + (ich - '0');
if (10 * i > Largest_Header_Number)
{
ich = '\r';
break;
}
slrn_message ("Goto Header: %d", i);
slrn_smg_refresh ();
}
while ((ich = SLang_getkey ()), (ich <= '9') && (ich >= '0'));
if (SLKeyBoard_Quit) return;
if (ich != '\r')
SLang_ungetkey (ich);
diff = i - Last_Cursor_Row;
if (diff > 0) slrn_header_down_n (diff, 0); else slrn_header_up_n (-diff, 0);
Slrn_Message_Present = 0;
#if SLRN_HAS_SLANG
SLang_run_hooks ("header_number_hook", NULL, NULL);
#endif
Slrn_Full_Screen_Update = 1;
}
/*}}}*/
/*}}}*/
/*{{{ article save/decode functions */
static int save_article_as_unix_mail (Slrn_Header_Type *h, FILE *fp)
{
char *from;
time_t now;
Slrn_Article_Line_Type *l;
if (read_article (h, 1, 0) < 0) return -1;
from = extract_header ("From: ", 6);
if (from != NULL) from = parse_from (from);
if ((from == NULL) || (*from == 0)) from = "nobody@nowhere";
time (&now);
fprintf (fp, "From %s %s", from, ctime(&now));
l = Slrn_Article_Lines;
while (l != NULL)
{
if ((*l->buf == 'F')
&& !strncmp ("From", l->buf, 4)
&& ((unsigned char)(l->buf[4]) <= ' '))
{
putc ('>', fp);
}
fputs (l->buf, fp);
putc ('\n', fp);
l = l->next;
}
fputs ("\n\n", fp);
return 0;
}
static char *save_article_to_file (char *defdir, char *input_string)
{
char file[256];
char name[256];
int save_tagged = 0;
int save_thread = 0;
int save_simple;
FILE *fp;
if (Num_Tag_List.len)
{
save_tagged = slrn_get_yesno_cancel ("Save tagged articles");
if (save_tagged < 0) return NULL;
}
else if ((Slrn_Current_Header->child != NULL)
&& (Slrn_Current_Header->child->flags & HEADER_HIDDEN))
{
save_thread = slrn_get_yesno_cancel ("Save this thread");
if (save_thread == -1) return NULL;
}
save_simple = !(save_tagged || save_thread);
if (*Output_Filename == 0)
{
unsigned int defdir_len;
if (defdir == NULL) defdir = "News";
defdir_len = strlen (defdir);
sprintf (name, "%s/%s", defdir, Slrn_Current_Group_Name);
#ifndef VMS
#ifndef __os2__
/* Lowercase first letter and see if it exists. */
name[defdir_len + 1] = LOWER_CASE(name[defdir_len + 1]);
#endif
#endif
slrn_make_home_filename (name, file);
#ifndef VMS
#ifndef __os2__
if (1 != slrn_file_exists (file))
{
/* Lowercase version does not exist so user uppercase form. */
name[defdir_len + 1] = UPPER_CASE(name[defdir_len + 1]);
slrn_make_home_filename (name, file);
}
#endif
#endif
}
else strcpy (file, Output_Filename);
if (slrn_read_input (input_string, file, 1) <= 0)
{
slrn_error ("Aborted.");
return NULL;
}
if (NULL == (fp = fopen (file, "a")))
{
slrn_error ("Unable to open %s", file);
return NULL;
}
strcpy (Output_Filename, file);
if (save_simple) save_article_as_unix_mail (Slrn_Current_Header, fp);
else if (save_tagged)
{
unsigned int i;
unsigned int num_saved = 0;
for (i = 0; i < Num_Tag_List.len; i++)
{
if (-1 == save_article_as_unix_mail (Num_Tag_List.headers[i], fp))
{
slrn_smg_refresh ();
SLang_Error = 0;
(void) SLang_input_pending (5); /* half second delay */
}
else num_saved++;
}
if (num_saved == 0) return NULL;
}
else
{
Slrn_Header_Type *h = Slrn_Current_Header;
unsigned int num_saved = 0;
do
{
if (-1 == save_article_as_unix_mail (h, fp))
{
slrn_smg_refresh ();
SLang_Error = 0;
(void) SLang_input_pending (5); /* half second delay */
}
else num_saved++;
h = h->next;
}
while ((h != NULL) && (h->parent != NULL));
if (num_saved == 0) return NULL;
}
slrn_fclose (fp);
if (SLang_Error) return NULL;
return Output_Filename;
}
static void save_article (void)
{
(void) save_article_to_file (Slrn_Save_Directory, "Save to file (^G aborts): ");
}
#if SLRN_HAS_DECODE
static void decode_article (void)
{
char *uu_dir;
char *file;
if (NULL == (uu_dir = Slrn_Decode_Directory))
{
uu_dir = Slrn_Save_Directory;
}
else *Output_Filename = 0; /* force it to use this directory */
file = save_article_to_file(uu_dir, "Filename (^G aborts): ");
if (file == NULL) return;
if (1 == slrn_get_yesno (1, "decode %s", file))
{
slrn_set_suspension (1);
(void) slrn_uudecode_file (file, NULL, 0);
slrn_set_suspension (0);
if (SLang_Error == 0)
{
if (1 == slrn_get_yesno (1, "Delete %s", file))
{
if (-1 == slrn_delete_file (file))
slrn_error ("Unable to delete %s", file);
}
}
}
/* Since we have a decode directory, do not bother saving this */
if (NULL != Slrn_Decode_Directory)
*Output_Filename = 0;
}
#endif /* SLRN_HAS_DECODE */
/*}}}*/
/*{{{ pipe_article */
int slrn_pipe_article_to_cmd (char *cmd)
{
int n = -1;
#if SLRN_HAS_PIPING
FILE *fp;
Slrn_Article_Line_Type *l;
if (-1 == select_article (1))
return -1;
if (NULL == (fp = slrn_popen (cmd, "w")))
{
slrn_error ("Unable to open pipe to %s", cmd);
return -1;
}
l = Slrn_Article_Lines;
n = 0;
while (l != NULL)
{
fputs (l->buf, fp);
putc ('\n', fp);
n++;
l = l->next;
}
fputs ("\n\n", fp);
slrn_pclose (fp);
#else
slrn_error ("Piping not implemented on this system.");
#endif
return n;
}
static void pipe_article (void)
{
#if SLRN_HAS_PIPING
static char cmd[256];
int n;
*cmd = 0;
if (slrn_read_input ("Pipe to command: ", cmd, 1) <= 0)
{
slrn_error ("Aborted. Command name is required.");
return;
}
if (-1 != (n = slrn_pipe_article_to_cmd (cmd)))
slrn_message ("Piped %d lines to %s.", n, cmd);
#else
slrn_error ("Piping not implemented on this system.");
#endif
}
/*}}}*/
/*{{{ sorting header functions */
static int subject_cmp (register unsigned char *sa, register unsigned char *sb)
{
register unsigned char ch;
/* skip past re: */
sa = (unsigned char *) slrn_skip_whitespace ((char *) sa);
ch = *sa;
if (((ch | 0x20) == 'r') && ((*(sa + 1) | 0x20) == 'e')
&& (*(sa + 2) == ':'))
{
sa = (unsigned char *) slrn_skip_whitespace ((char *) sa + 3);
}
sb = (unsigned char *) slrn_skip_whitespace ((char *) sb);
ch = *sb;
if (((ch | 0x20) == 'r') && ((*(sb + 1) | 0x20) == 'e')
&& (*(sb + 2) == ':'))
{
sb = (unsigned char *) slrn_skip_whitespace ((char *) sb + 3);
}
return slrn_case_strcmp ((unsigned char *) sa, (unsigned char *) sb);
}
#if 1
static int header_subj_cmp (Slrn_Header_Type **ap, Slrn_Header_Type **bp)
{
int cmp;
Slrn_Header_Type *a = *ap, *b = *bp;
int ahigh, bhigh;
ahigh = (0 != (a->flags & (HEADER_HIGH_SCORE | FAKE_HEADER_HIGH_SCORE)));
bhigh = (0 != (b->flags & (HEADER_HIGH_SCORE | FAKE_HEADER_HIGH_SCORE)));
if (ahigh == bhigh)
{
cmp = subject_cmp ((unsigned char *) (a->subject),
(unsigned char *) (b->subject));
if (!cmp) return a->number - b->number;
}
else cmp = bhigh - ahigh;
return cmp;
}
#else
static int header_subj_cmp (Slrn_Header_Type **a, Slrn_Header_Type **b)
{
int cmp = subject_cmp ((unsigned char *) (*a)->subject,
(unsigned char *) (*b)->subject);
if (!cmp) return (*a)->number - (*b)->number;
return cmp;
}
#endif
typedef int (*Header_Cmp_Func_Type)(Slrn_Header_Type **, Slrn_Header_Type **);
static void sort_by_function (Header_Cmp_Func_Type cmp_func)
{
Slrn_Header_Type **header_list, *h;
unsigned int i, nheaders;
void (*qsort_fun) (char *, unsigned int,
unsigned int, int (*)(Slrn_Header_Type **, Slrn_Header_Type **));
/* This is a silly hack to make up for braindead compilers and the lack of
* uniformity in prototypes for qsort.
*/
qsort_fun = (void (*)(char *, unsigned int,
unsigned int, int (*)(Slrn_Header_Type **, Slrn_Header_Type **)))
qsort;
/* Count the number we need to sort. */
nheaders = 0;
h = Slrn_First_Header;
while (h != NULL)
{
if (h->parent == NULL) nheaders++;
h = h->real_next;
}
if (nheaders < 2) return;
if (NULL == (header_list = (Slrn_Header_Type **) SLCALLOC (sizeof (Slrn_Header_Type *), nheaders + 1)))
{
slrn_error ("sort_headers(): memory allocation failure.");
return;
}
h = Slrn_First_Header;
nheaders = 0;
while (h != NULL)
{
if (h->parent == NULL)
header_list[nheaders++] = h;
h = h->real_next;
}
header_list[nheaders] = NULL;
(*qsort_fun) ((char *) header_list, nheaders, sizeof (Slrn_Header_Type *), cmp_func);
/* What to do now depends upon the current threading state. */
if (Headers_Threaded == 0)
{
header_list[0]->next = header_list[1];
header_list[0]->prev = NULL;
for (i = 1; i < nheaders; i++)
{
h = header_list[i];
h->next = header_list[i + 1];
h->prev = header_list[i - 1];
}
}
else
{
slrn_collapse_threads (0);
/* The headers are threaded so we simply have sorted parents. Arrange
* those.
*/
h = NULL;
for (i = 0; i <= nheaders; i++)
{
Slrn_Header_Type *h1 = header_list[i];
if (h != NULL)
{
h->sister = h1;
while (h->child != NULL)
{
h = h->child;
while (h->sister != NULL) h = h->sister;
}
h->next = h1;
}
if (h1 != NULL) h1->prev = h;
h = h1;
}
}
Headers = header_list[0];
Num_Headers = nheaders;
find_header_line_number ();
Slrn_Full_Screen_Update = 1;
SLFREE (header_list);
if (Slrn_Threads_Visible)
{
slrn_uncollapse_threads (1);
}
}
static void sort_by_subject (void)
{
sort_by_function (header_subj_cmp);
}
#if SLRN_HAS_SORT_BY_SCORE
static int header_score_cmp (Slrn_Header_Type **a, Slrn_Header_Type **b)
{
/* sort by *descending* score */
int cmp = (*b)->thread_score - (*a)->thread_score;
if (cmp == 0)
{
if (Slrn_Sorting_Mode & SORT_BY_SUBJECT)
return header_subj_cmp (a, b);
else
return (*a)->number - (*b)->number;
}
return cmp;
}
static void sort_by_score (void)
{
sort_by_function (header_score_cmp);
}
#endif
static void sort_by_server_number (void)
{
Slrn_Header_Type *h;
/* This is easy since the real_next, prev are already ordered. */
h = Slrn_First_Header;
Num_Headers = 0;
while (h != NULL)
{
Slrn_Header_Type *next = h->real_next;
Num_Headers++;
h->next = h->real_next;
h->prev = h->real_prev;
h->num_children = 0;
h->flags &= ~(HEADER_HIDDEN | ALL_THREAD_FLAGS);
h->sister = h->parent = h->child = NULL;
h = next;
}
/* Now find out where to put the Headers pointer */
while (Headers->prev != NULL) Headers = Headers->prev;
Headers_Threaded = 0;
find_header_line_number ();
Slrn_Full_Screen_Update = 1;
}
static void sort_by_threads (void)
{
thread_headers ();
sort_threads ();
if (Slrn_Threads_Visible)
{
slrn_uncollapse_threads (1);
}
Slrn_Full_Screen_Update = 1;
Headers_Threaded = 1;
}
#if 0
static void toggle_sort (void)
{
#ifdef SLRN_HAS_SORT_BY_SCORE
char rsp = slrn_get_response ("tTsSvVNnCc", "Sorting method: T-hreads, S-ubject, score V-alue, N-one, C-ancel");
#else
char rsp = slrn_get_response ("tTsSNnCc", "Sorting method: T-hreads, S-ubject, N-one, C-ancel");
#endif
Slrn_Sorting_Mode &= (SORT_BY_THREADS|SORT_BY_SUBJECT);
switch (rsp | 0x20)
{
case 'n':
Slrn_Sorting_Mode = 0;
sort_by_server_number ();
break;
case 't':
Slrn_Sorting_Mode = SORT_BY_THREADS;
sort_by_threads ();
break;
case 's':
Slrn_Sorting_Mode |= SORT_BY_SUBJECT;
sort_by_subject ();
break;
#ifdef SLRN_HAS_SORT_BY_SCORE
case 'v':
Slrn_Sorting_Mode |= SORT_BY_SCORE;
sort_by_score ();
break;
#endif
default:
return;
}
}
#else
static void toggle_sort (void)
{
int rsp;
rsp = slrn_sbox_sorting_method ();
if (rsp != -1)
{
Slrn_Sorting_Mode = rsp;
sort_by_sorting_mode ();
}
}
#endif
static void sort_by_sorting_mode (void)
{
if ((Slrn_Sorting_Mode & SORT_BY_THREADS) == 0)
sort_by_server_number ();
else
sort_by_threads ();
if (Slrn_Sorting_Mode & SORT_BY_SUBJECT)
sort_by_subject ();
#if SLRN_HAS_SORT_BY_SCORE
if (Slrn_Sorting_Mode & SORT_BY_SCORE)
sort_by_score ();
#endif
}
/*}}}*/
/*{{{ Thread related functions */
void slrn_collapse_threads (int sync_now)
{
Slrn_Header_Type *h = Slrn_First_Header;
if ((h == NULL)
|| (Threads_Collapsed == 1))
return;
Num_Headers = 0;
while (h != NULL)
{
if (h->parent != NULL) h->flags |= HEADER_HIDDEN;
else
{
h->flags &= ~HEADER_HIDDEN;
Num_Headers++;
}
h = h->real_next;
}
if (Slrn_Current_Header->flags & HEADER_HIDDEN)
{
slrn_header_up_n (1, 0);
}
if (sync_now) find_header_line_number ();
Slrn_Full_Screen_Update = 1;
Threads_Collapsed = 1;
}
void slrn_uncollapse_threads (int sync_now)
{
Slrn_Header_Type *h = Slrn_First_Header;
if ((h == NULL)
|| (0 == Threads_Collapsed))
return;
Num_Headers = 0;
while (h != NULL)
{
h->flags &= ~HEADER_HIDDEN;
h = h->real_next;
Num_Headers++;
}
Slrn_Full_Screen_Update = 1;
Threads_Collapsed = 0;
if (sync_now) find_header_line_number ();
}
static void uncollapse_header (Slrn_Header_Type *h)
{
h->flags &= ~HEADER_HIDDEN;
}
static void collapse_header (Slrn_Header_Type *h)
{
h->flags |= HEADER_HIDDEN;
}
static void for_this_tree (Slrn_Header_Type *h, void (*f)(Slrn_Header_Type *))
{
Slrn_Header_Type *child = h->child;
while (child != NULL)
{
for_this_tree (child, f);
child = child->sister;
}
(*f) (h);
Slrn_Full_Screen_Update = 1;
}
static void for_this_family (Slrn_Header_Type *h, void (*f)(Slrn_Header_Type *))
{
while (h != NULL)
{
for_this_tree (h, f);
h = h->sister;
}
}
/* Up to calling routine to adjust header line number if necessary. */
static void uncollapse_this_thread (Slrn_Header_Type *h)
{
Slrn_Header_Type *child;
if (Threads_Collapsed == 0) return;
while (h->parent != NULL) h = h->parent;
if ((child = h->child) == NULL) return;
if (0 == (child->flags & HEADER_HIDDEN)) return;
for_this_family (child, uncollapse_header);
Num_Headers += h->num_children;
Threads_Collapsed = -1; /* uncertain */
}
/* Up to calling routine to adjust header line number and make sure to move
* current header out of thread
*/
static void collapse_this_thread (Slrn_Header_Type *h)
{
Slrn_Header_Type *child;
if (Threads_Collapsed == 1) return;
while (h->parent != NULL) h = h->parent;
if ((child = h->child) == NULL) return;
if (child->flags & HEADER_HIDDEN) return;
for_this_family (child, collapse_header);
Num_Headers -= h->num_children;
Threads_Collapsed = -1; /* uncertain */
}
static void toggle_collapse_threads (void)
{
if (Slrn_Prefix_Arg_Ptr != NULL)
{
if (Threads_Collapsed == 1)
{
slrn_uncollapse_threads (0);
}
else slrn_collapse_threads (0);
Slrn_Prefix_Arg_Ptr = NULL;
}
else
{
Slrn_Header_Type *child;
/* Determine whether or not the thread is already collapsed. Easy
* check */
if ((Slrn_Current_Header->parent != NULL)
|| (((child = Slrn_Current_Header->child) != NULL)
&& ((child->flags & HEADER_HIDDEN) == 0)))
collapse_this_thread (Slrn_Current_Header);
else
uncollapse_this_thread (Slrn_Current_Header);
if (Slrn_Current_Header->flags & HEADER_HIDDEN) slrn_header_up_n (1, 0);
}
find_header_line_number ();
}
static Slrn_Header_Type *sort_thread_node (Slrn_Header_Type *h, char *tree)
{
Slrn_Header_Type *last = NULL;
static int level;
char vline_char;
if (h == NULL) return NULL;
#ifdef __os2__
vline_char = SLSMG_VLINE_CHAR;
#else
vline_char = (SLtt_Has_Alt_Charset ? SLSMG_VLINE_CHAR : ' ');
#endif
while (1)
{
last = h;
if (h->child != NULL)
{
Slrn_Header_Type *child = h->child;
int tree_level;
int save_level = level;
h->next = child;
child->prev = h;
if (level == 0)
{
if (h->flags & FAKE_CHILDREN)
{
if ((child->flags & FAKE_PARENT) == 0)
{
level = 1;
}
}
}
else if (h->flags & FAKE_PARENT)
{
if (h->sister != NULL) tree[0] = vline_char;
else tree[0] = ' ';
tree[1] = ' ';
level = 1;
}
tree_level = 2 * level - 2;
if (level && (tree_level < sizeof (h->tree) - 2))
{
if (h->sister != NULL)
{
if (((h->sister->flags & FAKE_PARENT) == 0)
|| (h->flags & FAKE_PARENT))
{
tree[tree_level] = vline_char;
}
else tree[tree_level] = ' ';
}
else
{
if ((h->parent == NULL) && (h->flags & FAKE_CHILDREN))
{
tree[tree_level] = vline_char;
}
else tree[tree_level] = ' ';
}
tree[tree_level + 1] = ' ';
tree[tree_level + 2] = 0;
}
level++;
last = sort_thread_node (h->child, tree);
level--;
if (level &&
((tree_level < sizeof (h->tree) - 2)))
tree[tree_level] = 0;
level = save_level;
}
if (h->flags & FAKE_PARENT) *tree = 0;
if (*tree)
{
strncpy ((char *) h->tree, tree, sizeof (h->tree) - 1);
h->tree[sizeof (h->tree) - 1] = 0;
}
h = h->sister;
last->next = h;
if (h == NULL) break;
h->prev = last;
}
return last;
}
static unsigned int compute_num_children (Slrn_Header_Type *h)
{
unsigned int n = 0, dn;
h = h->child;
while (h != NULL)
{
n++;
if (h->child == NULL) dn = 0;
else
{
dn = compute_num_children (h);
n += dn;
}
h->num_children = dn;
h = h->sister;
}
return n;
}
static void sort_threads (void)
{
Slrn_Header_Type *h;
char tree[MAX_TREE_SIZE];
h = Slrn_First_Header;
Headers = NULL;
if (h == NULL) return;
while (h != NULL)
{
if ((h->parent == NULL) && (Headers == NULL))
Headers = h;
h->prev = h->next = NULL;
h->flags &= ~HEADER_HIDDEN;
h = h->real_next;
}
Threads_Collapsed = 0;
if (Headers == NULL)
slrn_exit_error ("Internal Error.");
*tree = 0;
sort_thread_node (Headers, tree);
while (Headers->prev != NULL) Headers = Headers->prev;
slrn_collapse_threads (0);
h = Headers;
while (h != NULL)
{
if (h->child == NULL) h->num_children = 0;
else
{
Slrn_Header_Type *next;
h->num_children = compute_num_children (h);
next = h->next;
while ((next != NULL) && (next->parent != NULL))
{
#if SLRN_HAS_SORT_BY_SCORE
if (next->flags & HEADER_HIGH_SCORE)
h->flags |= FAKE_HEADER_HIGH_SCORE;
if (next->score > h->thread_score)
h->thread_score = next->score;
#else
if (next->flags & HEADER_HIGH_SCORE)
{
h->flags |= FAKE_HEADER_HIGH_SCORE;
break;
}
#endif
next = next->next;
}
}
h = h->sister;
}
find_header_line_number ();
}
static void link_same_subjects (void)
{
Slrn_Header_Type **header_list, *h;
unsigned int i, nparents;
void (*qsort_fun) (char *, unsigned int, unsigned int, int (*)(Slrn_Header_Type **, Slrn_Header_Type **));
/* This is a silly hack to make up for braindead compilers and the lack of
* uniformity in prototypes for qsort.
*/
qsort_fun = (void (*)(char *,
unsigned int, unsigned int,
int (*)(Slrn_Header_Type **, Slrn_Header_Type **)))
qsort;
h = Slrn_First_Header;
nparents = 0;
while (h != NULL)
{
if (h->parent == NULL)
nparents++;
h = h->real_next;
}
if (nparents < 2) return;
if (NULL == (header_list = (Slrn_Header_Type **) SLCALLOC (sizeof (Slrn_Header_Type *), nparents)))
{
slrn_error ("link_same_subjects: memory allocation failure.");
return;
}
h = Slrn_First_Header;
i = 0;
while (i < nparents)
{
if (h->parent == NULL) header_list[i++] = h;
h = h->real_next;
}
(*qsort_fun) ((char *) header_list,
nparents, sizeof (Slrn_Header_Type *), header_subj_cmp);
h = header_list[0];
for (i = 1; i < nparents; i++)
{
Slrn_Header_Type *h1 = header_list[i];
if (0 == subject_cmp ((unsigned char *) h->subject,
(unsigned char *) h1->subject))
{
if (h->child == NULL)
{
h->child = h1;
}
else
{
Slrn_Header_Type *child = h->child;
while (child->sister != NULL) child = child->sister;
child->sister = h1;
}
h1->parent = h;
h->flags |= FAKE_CHILDREN;
h1->flags |= FAKE_PARENT;
if (h1->flags & FAKE_CHILDREN)
{
/* Well, we have to link them up to the new parent. That
* is, h1 will become their sister. So, extract the
* adopted children of h1 and make them the sister,
*/
Slrn_Header_Type *child = h1->child, *last_child;
last_child = child;
/* child CANNOT be NULL here!! (the parent claims to have
children) */
child = child->sister;
while (child != NULL)
{
if (child->flags & FAKE_PARENT)
break;
last_child = child;
child = child->sister;
}
if (last_child->flags & FAKE_PARENT)
{
child = last_child;
h1->child = NULL;
}
else last_child->sister = NULL;
last_child = child;
while (child != NULL)
{
child->parent = h;
/* No need to set fake parent flags since fake children
* are all group together. That is, once you loop
* through the sisters and find one, you have found them
* all.
*/
child = child->sister;
}
/* Now h1 will become the sister. */
child = h1;
while (child->sister != NULL) child = child->sister;
child->sister = last_child;
h1->flags &= ~FAKE_CHILDREN;
}
}
else h = h1;
}
SLFREE (header_list);
}
typedef struct
{
unsigned long ref_hash;
Slrn_Header_Type *h;
}
Relative_Type;
static void link_lost_relatives (void)
{
unsigned int n, i, j;
Slrn_Header_Type *h;
Relative_Type *relatives;
/* count the number of possible relatives */
n = 0;
h = Slrn_First_Header;
while (h != NULL)
{
if ((h->parent == NULL)
&& (h->refs != NULL)
&& (*h->refs != 0)) n++;
h = h->real_next;
}
if (n < 2) return;
if (NULL == (relatives = (Relative_Type *) SLMALLOC (sizeof (Relative_Type) * n)))
return;
n = 0;
h = Slrn_First_Header;
while (h != NULL)
{
if ((h->parent == NULL)
&& (h->refs != NULL)
&& (*h->refs != 0))
{
unsigned char *r, *ref_begin;
r = (unsigned char *) h->refs;
while (*r && (*r != '<')) r++;
if (*r == '<')
{
ref_begin = r;
while (*r && (*r != '>')) r++;
if (*r == '>') r++;
relatives[n].ref_hash = slrn_compute_hash (ref_begin, r);
relatives[n].h = h;
n++;
}
}
h = h->real_next;
}
for (i = 0; i < n; i++)
{
unsigned long ref_hash;
Relative_Type *ri = relatives + i;
Slrn_Header_Type *rih;
ref_hash = ri->ref_hash;
rih = ri->h;
for (j = i + 1; j < n; j++)
{
if (relatives[j].ref_hash == ref_hash)
{
Slrn_Header_Type *rjh = relatives[j].h;
if (rih->parent != NULL)
{
rih->sister = rjh;
rjh->parent = rih->parent;
}
else if (rih->child == NULL)
{
rih->child = rjh;
rjh->parent = rih;
rih->flags |= FAKE_CHILDREN;
}
else
{
Slrn_Header_Type *child = rih->child;
/* This is an important step. All adopted children
* get linked to the LAST child. This ordering
* assumption is used elsewhere.
*/
while (child->sister != NULL) child = child->sister;
child->sister = rjh;
rjh->parent = rih;
rih->flags |= FAKE_CHILDREN;
}
rjh->flags |= FAKE_PARENT;
break;
}
}
}
SLFREE (relatives);
}
static void thread_headers (void)
{
Slrn_Header_Type *h, *ref;
char *r0, *r1, *rmin;
make_hash_table ();
h = Slrn_First_Header;
while (h != NULL)
{
h->next = h->prev = h->child = h->parent = h->sister = NULL;
h->flags &= ~ALL_THREAD_FLAGS;
*h->tree = 0;
h = h->real_next;
}
/* SLMEMSET ((char *) Lost_Ancestors, 0, sizeof (Lost_Ancestors)); */
h = Slrn_First_Header;
while (h != NULL)
{
if (*h->refs == 0)
{
h = h->real_next;
continue;
}
rmin = h->refs;
r1 = rmin + strlen (rmin);
while (1)
{
while ((r1 > rmin) && (*r1 != '>')) r1--;
r0 = r1 - 1;
while ((r0 >= rmin) && (*r0 != '<')) r0--;
if ((r0 < rmin) || (r1 == rmin)) break;
ref = find_header_from_msgid (r0, r1 + 1);
if (ref != NULL)
{
Slrn_Header_Type *child, *rparent;
rparent = ref;
while (rparent->parent != NULL) rparent = rparent->parent;
if (rparent == h)
{
/* self referencing!!! */
slrn_error ("Article %d is part of reference loop!", h->number);
}
else
{
h->parent = ref;
child = ref->child;
if (child == NULL) ref->child = h;
else
{
while (child->sister != NULL) child = child->sister;
child->sister = h;
}
break;
}
}
r1 = r0;
}
h = h->real_next;
}
/* No perform a re-arrangement such that those with the no parents but
* share the same reference are placed side-by-side as sisters.
*/
link_lost_relatives ();
/* Now perform sort on subject to catch those that have fallen through the
* cracks, i.e., no references */
link_same_subjects ();
/* Now link others up as sisters */
h = Slrn_First_Header;
while ((h != NULL) && (h->parent != NULL))
{
h = h->real_next;
}
while (h != NULL)
{
Slrn_Header_Type *next;
next = h->real_next;
while ((next != NULL) && (next->parent != NULL))
next = next->real_next;
h->sister = next;
h = next;
}
}
/*}}}*/
/*{{{ select_article */
/* returns 0 if article selected, -1 if something went wrong or 1 if article
* already selected.
*/
static int select_article (int do_mime)
{
int ret = 1;
Slrn_Full_Screen_Update = 1;
uncollapse_this_thread (Slrn_Current_Header);
if (Slrn_Current_Header != Header_Showing)
{
if (read_article (Slrn_Current_Header, 1, 1) < 0) return -1;
#if SLRN_HAS_MIME
if (do_mime && Slrn_Use_Mime && Slrn_Mime_Needs_Metamail)
{
if (slrn_mime_call_metamail ())
return -1;
}
#endif
ret = 0;
}
Header_Window_Height = SLtt_Screen_Rows - Article_Window_Height - 4;
if (Header_Window_Height < 1)
art_winch_sig ();
Article_Visible = 1;
return ret;
}
/*}}}*/
/*{{{ mark_spot and exchange_mark */
static void mark_spot (void)
{
Mark_Header = Slrn_Current_Header;
slrn_message ("Mark set.");
}
static void exchange_mark (void)
{
if (Mark_Header == NULL)
{
slrn_error ("Mark not set.");
return;
}
if (-1 == slrn_goto_header (Mark_Header, 0)) return;
mark_spot ();
}
/*}}}*/
/*{{{ subject/author header searching commands */
static void header_generic_search (int dir, int type)
{
static char search_str[256];
SLsearch_Type st;
Slrn_Header_Type *l;
char prompt[80];
sprintf (prompt, "%s Search %s: ",
type == 's' ? "Subject" : "Author",
dir > 0 ? "Forward" : "Backward");
if (slrn_read_input (prompt, search_str, 0) <= 0)
{
return;
}
SLsearch_init (search_str, 1, 0, &st);
if (dir > 0) l = Slrn_Current_Header->next;
else l = Slrn_Current_Header->prev;
while (l != NULL)
{
if (type == 's')
{
if ((l->subject != NULL)
&& (NULL != SLsearch ((unsigned char *) l->subject,
(unsigned char *) l->subject + strlen (l->subject),
&st)))
break;
}
else if ((l->subject != NULL)
&& (NULL != SLsearch ((unsigned char *) l->from,
(unsigned char *) l->from + strlen (l->from),
&st)))
break;
if (dir > 0) l = l->next; else l = l->prev;
}
if (l == NULL)
{
slrn_error ("Not found.");
return;
}
if (l->flags & HEADER_HIDDEN) uncollapse_this_thread (l);
Slrn_Current_Header = l;
find_header_line_number ();
}
static void subject_search_forward (void)
{
header_generic_search (1, 's');
}
static void subject_search_backward (void)
{
header_generic_search (-1, 's');
}
static void author_search_forward (void)
{
header_generic_search (1, 'a');
}
static void author_search_backward (void)
{
header_generic_search (-1, 'a');
}
/*}}}*/
/*{{{ score header support */
/*{{{ kill list functions */
typedef struct Kill_List_Type
{
#define MAX_DKILLS 50
int nums[MAX_DKILLS];
unsigned int num_used;
struct Kill_List_Type *next;
}
Kill_List_Type;
static Kill_List_Type *Kill_List;
static Kill_List_Type *Missing_Article_List;
static Kill_List_Type *add_to_specified_kill_list (int num, Kill_List_Type *root)
{
if (num < 0) return root;
if ((root == NULL) || (root->num_used == MAX_DKILLS))
{
Kill_List_Type *k;
k = (Kill_List_Type *) SLMALLOC (sizeof (Kill_List_Type));
if (k == NULL) return root;
k->num_used = 0;
k->next = root;
root = k;
}
root->nums[root->num_used++] = num;
return root;
}
static void add_to_kill_list (int num)
{
Kill_List = add_to_specified_kill_list (num, Kill_List);
Number_Killed++;
}
static void add_to_missing_article_list (int num)
{
Missing_Article_List = add_to_specified_kill_list (num, Missing_Article_List);
}
static void free_specific_kill_list_and_update (Kill_List_Type *k)
{
while (k != NULL)
{
Kill_List_Type *next = k->next;
unsigned int i, imax = k->num_used;
int *nums = k->nums;
for (i = 0; i < imax; i++)
{
slrn_mark_article_as_read (NULL, nums[i]);
}
SLFREE (k);
k = next;
}
}
static void free_kill_lists_and_update (void)
{
free_specific_kill_list_and_update (Kill_List);
Kill_List = NULL;
Number_Killed = 0;
free_specific_kill_list_and_update (Missing_Article_List);
Missing_Article_List = NULL;
}
/*}}}*/
/*{{{ apply_score */
static Slrn_Header_Type *apply_score (Slrn_Header_Type *h)
{
int score;
if (h == NULL) return h;
if (Slrn_Apply_Score && Perform_Scoring)
score = slrn_score_header (h, Slrn_Current_Group_Name);
else score = 0;
if (score)
{
if (score > 0)
{
h->flags |= HEADER_HIGH_SCORE;
Number_High_Scored++;
}
else if (score < 0)
{
if (score == -9999)
{
int number = h->number;
SLFREE (h->subject);
SLFREE (h);
add_to_kill_list (number);
return NULL;
}
else h->flags |= (HEADER_READ | HEADER_LOW_SCORE);
Number_Low_Scored++;
/* The next line should be made configurable */
kill_cross_references (h);
}
}
h->thread_score = h->score = score;
return h;
}
/*}}}*/
/*{{{ score_headers */
static void score_headers (void)
{
Slrn_Header_Type *h = Slrn_First_Header;
int percent, last_percent, delta_percent;
int total;
if ((h == NULL) || (Slrn_Apply_Score == 0)) return;
slrn_set_suspension (1);
total = Num_Headers;
percent = Num_Headers = 0;
delta_percent = (30 * 100) / total + 1;
last_percent = -delta_percent;
while (h != NULL)
{
Slrn_Header_Type *prev, *next;
prev = h->real_prev;
next = h->real_next;
percent = (100 * Num_Headers) / total;
if (percent >= last_percent + delta_percent)
{
slrn_message ("Scoring articles: %2d%%, Killed: %u, High: %u, Low: %u",
percent, Number_Killed, Number_High_Scored, Number_Low_Scored);
slrn_smg_refresh ();
last_percent = percent;
}
h = apply_score (h);
if (h == NULL)
{
if (prev == NULL)
{
Slrn_First_Header = next;
}
else
{
prev->next = prev->real_next = next;
}
if (next != NULL)
{
next->prev = next->real_prev = prev;
}
}
else
{
Num_Headers++;
}
h = next;
}
Header_Number = 1;
Slrn_Current_Header = Headers = Slrn_First_Header;
slrn_set_suspension (0);
}
/*}}}*/
static void create_score (void)
{
(void) slrn_edit_score (Slrn_Current_Header, Slrn_Current_Group_Name);
}
/*}}}*/
/*{{{ get headers from server and process_xover_line */
/*{{{ process_xover_line */
/* The line consists of:
* id|subj|from|date|msgid|refs|bytes|line|misc stuff
* Here '|' is a TAB. The following code parses this.
*/
static Slrn_Header_Type *process_xover_line (char *buf)
{
Slrn_Header_Type *h;
unsigned int len;
char *subj, *bmax, *b;
/* Find the end of it */
b = buf;
/* skip past id */
while (*b && (*b != '\t')) b++;
*b++ = 0;
subj = b;
len = strlen (b);
/* Unfortunately, some servers are defective. So, we add enough
* space (13) to avoid problems. I found this out the hard way.
*/
if ((NULL == (h = (Slrn_Header_Type *) SLMALLOC (sizeof(Slrn_Header_Type))))
|| (NULL == (b = (char *) SLMALLOC (len + 13))))
{
slrn_exit_error ("Memory allocation error.");
}
SLMEMSET ((char *) h, 0, sizeof (Slrn_Header_Type));
SLMEMSET (b + len, 0, 13);
h->subject = b;
strcpy (b, subj);
h->number = atoi(buf);
/* now rest of headers */
bmax = b + len;
while ((b < bmax) && (*b != '\t')) b++;
*b++ = 0;
h->from = b;
while ((b < bmax) && (*b != '\t')) b++;
*b++ = 0;
h->date = b;
while ((b < bmax) && (*b != '\t')) b++;
*b++ = 0;
h->msgid = b;
while ((b < bmax) && (*b != '\t')) b++;
h->hash = slrn_compute_hash ((unsigned char *) h->msgid,
(unsigned char *) b);
*b++ = 0;
h->refs = b;
while ((b < bmax) && (*b != '\t')) b++;
*b++ = 0;
/* look for xrefs */
/* skip bytes */
while ((b < bmax) && (*b != '\t')) b++;
*b++ = 0; /* skipped bytes */
/* get number of lines */
h->lines = atoi (b);
while ((b < bmax) && *b && (*b != '\t')) b++;
/* Now at misc stuff */
h->xref = NULL;
if (*b)
{
*b++ = 0;
/* Optional fields consist of Header: stuff. Look for Xref: ... */
while (b < bmax)
{
char *xb = b;
/* skip to next field. */
while ((b < bmax) && (*b != '\t')) b++;
if (b < bmax) *b++ = 0;
if (0 == slrn_case_strncmp ((unsigned char *) xb,
(unsigned char *) "Xref: ", 6))
{
h->xref = xb + 6;
break;
}
}
}
if ((Slrn_Score_After_XOver == 0) && Perform_Scoring)
{
if (NULL == (h = apply_score (h)))
return h;
}
#if SLRN_HAS_MIME
if (Slrn_Use_Mime)
{
slrn_rfc1522_decode_string (h->subject);
slrn_rfc1522_decode_string (h->from);
}
#endif
#if SLRN_HAS_GROUPLENS
if (Slrn_Use_Group_Lens)
{
h->gl_rating = h->gl_pred = -1;
}
#endif
return h;
}
/*}}}*/
/*{{{ get_headers from server */
static int get_headers (int min, int max, int *totalp)
{
char buf[NNTP_BUFFER_SIZE];
Slrn_Header_Type *h;
/* int percent, last_percent, dpercent, */
int expected_num;
int scoring;
int total = *totalp;
int reads_per_update;
if ((total == 0) || (SLang_Error == USER_BREAK))
return -1;
scoring = Perform_Scoring && !Slrn_Score_After_XOver;
#if 0
if (Slrn_Delta_Percent <= 0)
{
if (scoring)
{
dpercent = (50 * 20) / total + 1;
}
else dpercent = (50 * 100) / total + 1;
}
else dpercent = Slrn_Delta_Percent;
last_percent = -dpercent;
#endif
if ((reads_per_update = Slrn_Reads_Per_Update) < 5)
reads_per_update = 50;
if (scoring) reads_per_update = reads_per_update / 3 + 1;
Num_Headers = Header_Number;
slrn_set_suspension (1);
Slrn_Server_Obj->sv_open_xover (min, max);
expected_num = min;
while (Slrn_Server_Obj->sv_read_xover(buf, sizeof(buf) - 10) != NULL)
{
int num = Header_Number + Number_Killed;
int this_num;
if (SLang_Error == USER_BREAK)
{
slrn_set_suspension (0);
return -1;
}
this_num = atoi(buf);
if (expected_num != this_num)
{
int bad_num;
total -= (this_num - expected_num);
for (bad_num = expected_num; bad_num < this_num; bad_num++)
add_to_missing_article_list (bad_num);
}
/* if (total <= 0) percent = 100;
* else percent = (num * 100) / total;
*/
expected_num = this_num + 1;
h = process_xover_line (buf);
/* if (percent >= last_percent + dpercent) */
num++;
if (1 == (num % reads_per_update))
{
if (!SLang_Error)
{
if (scoring)
{
/* slrn_message ("Headers Received and Scored: %2d%%, Number Killed: %u, Number Scoring High: %u",
* percent, Number_Killed, Number_High_Scored);
*/ slrn_message ("Headers Received and Scored: %3d/%-3d, Killed: %u, High: %u, Low: %u",
num, total,
Number_Killed, Number_High_Scored, Number_Low_Scored);
}
else
slrn_message ("Headers Received: %2d/%d", num, total);
/* slrn_message ("Headers Received: %d%%", percent); */
slrn_smg_refresh ();
}
/* last_percent = percent; */
}
if (h == NULL) continue;
if (Slrn_First_Header == NULL)
{
Slrn_First_Header = Headers = h;
}
else
{
h->real_next = Slrn_Current_Header->real_next;
h->real_prev = Slrn_Current_Header;
Slrn_Current_Header->real_next = h;
if (h->real_next != NULL)
{
h->real_next->real_prev = h;
}
}
Slrn_Current_Header = h;
Header_Number++;
}
Slrn_Server_Obj->sv_close_xover ();
if (expected_num != max + 1)
{
int bad_num;
total -= (max - expected_num) + 1;
for (bad_num = expected_num; bad_num <= max; bad_num++)
add_to_missing_article_list (bad_num);
}
slrn_set_suspension (0);
*totalp = total;
if (Num_Headers == Header_Number) return -1;
Num_Headers = Header_Number;
return 0;
}
/*}}}*/
/*}}}*/
/*{{{ get parent/children headers, etc... */
static void insert_header (Slrn_Header_Type *ref)
{
int n, id;
Slrn_Header_Type *h;
Slrn_Range_Type *r;
ref->hash_next = Header_Table[ref->hash % HEADER_TABLE_SIZE];
Header_Table[ref->hash % HEADER_TABLE_SIZE] = ref;
n = ref->number;
h = Slrn_First_Header;
while (h != NULL)
{
if (h->number >= n)
{
ref->real_next = h;
ref->real_prev = h->real_prev;
if (h->real_prev != NULL) h->real_prev->real_next = ref;
h->real_prev = ref;
if (h == Slrn_First_Header) Slrn_First_Header = ref;
if (h == Headers) Headers = ref;
break;
}
h = h->real_next;
}
if (h == NULL)
{
h = Slrn_First_Header;
while (h->real_next != NULL) h = h->real_next;
ref->real_next = NULL;
ref->real_prev = h;
h->real_next = ref;
}
/* This is ok since this header will not be marked as hidden. */
Num_Headers++;
if ((id = ref->number) <= 0) return;
/* Set the flags for this guy. */
r = Current_Group->range.next;
while (r != NULL)
{
if (r->min > id) break;
if (r->max >= id)
{
ref->flags = HEADER_READ;
return;
}
r = r->next;
}
ref->flags &= ~HEADER_READ;
}
static int get_header_by_message_id (char *msgid, int no_error_no_thread)
{
char buf[NNTP_BUFFER_SIZE];
Slrn_Header_Type *ref;
if ((msgid == NULL) || (*msgid == 0)) return -1;
ref = find_header_from_msgid (msgid, msgid + strlen (msgid));
if (ref != NULL)
{
Slrn_Current_Header = ref;
if (no_error_no_thread == 0)
find_header_line_number ();
Slrn_Full_Screen_Update = 1;
return 0;
}
slrn_message ("Finding parent from server...");
slrn_smg_refresh ();
slrn_set_suspension (1);
/* Try reading it from the server */
if (NULL == Slrn_Server_Obj->sv_head_from_msgid (msgid, buf, sizeof (buf)))
{
slrn_set_suspension (0);
if (no_error_no_thread == 0)
{
slrn_error ("Article %s not available.", msgid);
return -1;
}
return 1;
}
Slrn_Message_Present = 0;
ref = process_xover_line (buf);
#if 0
if (Slrn_Score_After_XOver)
ref = apply_score (ref);
#endif
slrn_set_suspension (0);
if (ref == NULL) return -1;
get_header_real_name (ref);
insert_header (ref);
Slrn_Current_Header = ref;
if (no_error_no_thread == 0)
{
sort_by_sorting_mode ();
}
return 0;
}
/* returns -1 if not implemented or the number of children returned from
* the server.
*/
static int find_children_headers (Slrn_Header_Type *parent)
{
char buf[NNTP_BUFFER_SIZE];
int id_array[1000];
int num_ids, i, id;
if (-1 == Slrn_Server_Obj->sv_xpat_cmd ("References",
Slrn_Server_Min, Slrn_Server_Max,
parent->msgid))
{
slrn_error ("Your server does not provide support for this feature.");
return -1;
}
num_ids = 0;
while (Slrn_Server_Obj->sv_read_line (buf, sizeof (buf) - 1) != NULL)
{
id = atoi (buf);
if (id <= 0) continue;
if (NULL != find_header_from_serverid (id)) continue;
id_array[num_ids] = id;
num_ids++;
}
for (i = 0; i < num_ids; i++)
{
id = id_array[i];
Slrn_Server_Obj->sv_open_xover (id, id);
/* This will loop once. */
while (NULL != Slrn_Server_Obj->sv_read_xover(buf, sizeof(buf) - 10))
{
Slrn_Header_Type *h;
h = process_xover_line (buf);
if (h == NULL) continue;
get_header_real_name (h);
insert_header (h);
}
Slrn_Server_Obj->sv_close_xover ();
}
return num_ids;
}
static void get_children_headers_1 (Slrn_Header_Type *h)
{
while (h != NULL)
{
(void) find_children_headers (h);
if (h->child != NULL)
{
get_children_headers_1 (h->child);
}
h = h->sister;
}
}
static void get_children_headers (void)
{
Slrn_Header_Type *h;
slrn_message ("Finding children from server...");
slrn_smg_refresh ();
Slrn_Message_Present = 0;
if (find_children_headers (Slrn_Current_Header) < 0) return;
sort_by_sorting_mode ();
h = Slrn_Current_Header->child;
if (h != NULL)
{
/* Now walk the tree getting children headers. For efficiency,
* only children currently threaded will be searched. Hopefully the
* above attempt got everything. If other newsreaders did not chop off
* headers, this would be unnecessary!
*/
get_children_headers_1 (h);
sort_by_sorting_mode ();
}
uncollapse_this_thread (Slrn_Current_Header);
find_header_line_number ();
}
static void get_parent_header (void)
{
char *r1, *r0, *rmin;
unsigned int len;
char buf[512];
int no_error_no_thread;
Slrn_Header_Type *last_header;
if (Slrn_Current_Header == NULL) return;
if (Slrn_Prefix_Arg_Ptr == NULL) no_error_no_thread = 0;
else no_error_no_thread = 1;
last_header = NULL;
r1 = rmin = NULL;
do
{
rmin = Slrn_Current_Header->refs;
if (rmin == NULL) break;
if (last_header != Slrn_Current_Header)
{
last_header = Slrn_Current_Header;
r1 = rmin + strlen (rmin);
}
while ((r1 > rmin) && (*r1 != '>')) r1--;
r0 = r1 - 1;
while ((r0 >= rmin) && (*r0 != '<')) r0--;
if ((r0 < rmin) || (r1 == rmin))
{
if (no_error_no_thread) break;
slrn_error ("Article has no parent reference.");
return;
}
len = (unsigned int) ((r1 + 1) - r0);
strncpy (buf, r0, len);
buf[len] = 0;
r1 = r0;
}
while ((get_header_by_message_id (buf, no_error_no_thread) >= 0)
&& no_error_no_thread);
if (no_error_no_thread)
{
sort_by_sorting_mode ();
get_children_headers ();
}
uncollapse_this_thread (Slrn_Current_Header);
}
static void locate_header_by_msgid (void)
{
char msgid[256];
*msgid = 0;
if (slrn_read_input ("Enter Message-Id:", msgid, 1) <= 0) return;
get_header_by_message_id (msgid, 0);
uncollapse_this_thread (Slrn_Current_Header);
}
/*}}}*/
/*{{{ article window display modes */
static void hide_article (void)
{
Slrn_Full_Screen_Update = 1;
if (Article_Visible == 0)
{
select_article (1);
return;
}
Article_Visible = 0;
Top_Line = NULL;
Header_Window_Height = SLtt_Screen_Rows - 3;
HScroll = 0;
}
static void art_left (void)
{
if (HScroll == 0) return;
HScroll -= (SLtt_Screen_Cols * 2) / 3;
if (HScroll < 0) HScroll = 0;
Slrn_Full_Screen_Update = 1;
}
static void art_right (void)
{
HScroll += (SLtt_Screen_Cols * 2) / 3;
Slrn_Full_Screen_Update = 1;
}
/*{{{ rot13 and spoilers */
static void toggle_rot13 (void)
{
Do_Rot13 = !Do_Rot13;
Slrn_Full_Screen_Update = 1;
}
#if SLRN_HAS_SPOILERS
static void show_spoilers (void)
{
Slrn_Article_Line_Type *l1 = NULL;
Slrn_Article_Line_Type *l = Slrn_Article_Lines;
do
{
/* first find the first spoiler-ed line */
while ((l != NULL) &&
(0 == (l->flags & SPOILER_LINE)))
{
l = l->next;
}
l1 = l;
/* now un-spoiler until we hit an un-spoiler-ed line */
while ((l != NULL) && (l->flags & SPOILER_LINE))
{
l->flags &= ~SPOILER_LINE;
l = l->next;
}
} /* Prefix arg means un-spoiler the whole article */
while ((Slrn_Prefix_Arg_Ptr != NULL) && (l != NULL));
Slrn_Prefix_Arg_Ptr = NULL;
if (l1 != NULL) Current_Line = l1;
Slrn_Full_Screen_Update = 1;
}
#endif
/*}}}*/
/*{{{ hide/toggle quotes */
static void hide_quotes (void)
{
Slrn_Article_Line_Type *l = Slrn_Article_Lines, *last = NULL;
while (l != NULL)
{
if (l->flags & QUOTE_LINE)
{
if (Quotes_Hidden && (last != NULL)) l->flags |= HIDDEN_LINE;
else l->flags &= ~HIDDEN_LINE;
last = l;
}
else last = NULL;
l = l->next;
}
find_article_line_number ();
count_article_lines ();
Slrn_Full_Screen_Update = 1;
}
/* This function needs generalized on an article by article basis */
static void toggle_quotes (void)
{
Quotes_Hidden = !Quotes_Hidden;
hide_quotes ();
}
/*}}}*/
static void toggle_headers (void)
{
Headers_Hidden_Mode = !Headers_Hidden_Mode;
hide_art_headers ();
if (Headers_Hidden_Mode == 0) art_bob ();
Slrn_Full_Screen_Update = 1;
}
/*}}}*/
/*{{{ header window display modes */
static void toggle_show_author (void)
{
Slrn_Show_Author++;
Slrn_Show_Author = (Slrn_Show_Author % 3);
Slrn_Full_Screen_Update = 1;
}
/*}}}*/
/*{{{ leave/suspend article mode and support functions */
/*{{{ update_ranges */
static void update_ranges (void)
{
int min, max;
int queued = 0;
Slrn_Range_Type *r, *r_save;
Slrn_Header_Type *h = Slrn_First_Header;
if (User_Aborted_Group_Read) return;
/* skip articles for which the numeric id was not available */
while ((h != NULL) && (h->number < 0)) h = h->real_next;
if (h == NULL) return;
/* we are creating new ranges so steal old. */
r_save = r = Current_Group->range.next;
Current_Group->range.next = NULL;
/* fill in range for articles prior to current group of headers. This
* is done in two parts. First, mark as read everything up to the
* first article on the server plus what we have read after that. Then,
* fill in the gap for articles on the server up to this group.
*/
max = Slrn_Server_Min - 1;
min = h->number; /* starting number of headers */
/* Part one. Skip past ranges that are below the server minimum number.
* They will be fused together.
*/
while ((r != NULL) && (r->min <= max))
{
if (r->max >= max)
{
max = r->max;
if (max >= min) max = min - 1;
r = r->next;
break;
}
r = r->next;
}
slrn_add_group_ranges (Current_Group, 1, max);
/* second part */
while ((r != NULL) && (r->min < min))
{
max = r->max;
if (max >= min) max = min - 1;
slrn_add_group_ranges (Current_Group, r->min, max);
r = r->next;
}
/* Now handle ranges in the current group. We no longer need the ranges
* so free them.
*/
if (r_save != NULL)
{
r = r_save;
while (r != NULL)
{
r_save = r->next;
SLFREE (r);
r = r_save;
}
}
queued = 0;
max = Slrn_Server_Max;
while (h != NULL)
{
max = h->number;
if (h->flags & HEADER_READ)
{
queued = 1;
}
else
{
if (queued || (min != max))
{
slrn_add_group_ranges (Current_Group, min, max - 1);
queued = 0;
}
min = max + 1; /* mark next number as start of
* a read range. We mark it now
* because h->next->number might not
* be max + 1.
*/
}
h = h->real_next;
}
if (queued == 0) min = max + 1;
slrn_add_group_ranges (Current_Group, min, Slrn_Server_Max);
}
/*}}}*/
/*{{{ art_quit */
static void art_quit (void)
{
Slrn_Header_Type *h = Headers, *next;
#if SLRN_HAS_GROUPLENS
if (Slrn_Use_Group_Lens) slrn_put_grouplens_scores ();
#endif
free_article ();
free_kill_lists_and_update ();
free_tag_list ();
slrn_close_score ();
if (h != NULL)
{
update_ranges ();
}
while (h != NULL)
{
next = h->next;
SLFREE (h->subject);
SLFREE (h);
h = next;
}
Headers = Slrn_Current_Header = NULL;
Header_Number = Num_Headers = 0;
Current_Group = NULL;
Last_Read_Header = NULL;
*Output_Filename = 0;
Same_Subject_Start_Header = NULL;
Slrn_Current_Group_Name = NULL;
Slrn_Current_Mode = Last_Current_Mode;
Slrn_Full_Screen_Update = 1;
}
/*}}}*/
static void skip_to_next_group (void)
{
art_quit ();
slrn_select_next_group ();
}
static void skip_to_prev_group (void)
{
art_quit ();
slrn_select_prev_group ();
}
static void fast_quit (void)
{
art_quit ();
slrn_group_quit ();
}
static void art_suspend_cmd (void)
{
int rows = SLtt_Screen_Rows;
slrn_suspend_cmd ();
if (rows != SLtt_Screen_Rows)
art_winch_sig ();
}
/*}}}*/
/*{{{ art_xpunge */
static void art_xpunge (void)
{
Slrn_Header_Type *save, *next, *h;
free_article ();
free_kill_lists_and_update ();
save = Headers;
if (Headers != NULL)
{
update_ranges ();
}
while (Headers != NULL)
{
if (0 == (Headers->flags & HEADER_READ))
break;
Headers = Headers->next;
}
if (Headers == NULL)
{
Headers = save;
art_quit ();
return;
}
if ((Num_Tag_List.len != 0)
&& (Num_Tag_List.headers != NULL))
{
unsigned int i, j;
Slrn_Header_Type *th;
j = 0;
for (i = 0; i < Num_Tag_List.len; i++)
{
th = Num_Tag_List.headers[i];
if (th->flags & HEADER_READ)
{
th->tag_number = 0;
th->flags &= ~HEADER_NTAGGED;
continue;
}
Num_Tag_List.headers [j] = th;
j++;
th->tag_number = j;
}
Num_Tag_List.len = j;
}
next = Slrn_Current_Header;
while (next != NULL)
{
if (0 == (next->flags & HEADER_READ))
break;
next = next->next;
}
if (next == NULL)
{
next = Slrn_Current_Header;
while (next != NULL)
{
if (0 == (next->flags & HEADER_READ))
break;
next = next->prev;
}
}
Slrn_Current_Header = next; /* cannot be NULL */
if (Top_Header->flags & HEADER_READ)
Top_Header = NULL;
h = Slrn_First_Header;
/* h cannot be NULL here*/
while (1)
{
next = h->real_next;
if (0 == (h->flags & HEADER_READ))
break;
SLFREE (h->subject);
SLFREE (h);
h = next;
}
Slrn_First_Header = h;
h->real_prev = NULL;
while (h != NULL)
{
Slrn_Header_Type *next_next;
next = h->real_next;
while (next != NULL)
{
next_next = next->real_next;
if (0 == (next->flags & HEADER_READ))
break;
SLFREE (next->subject);
SLFREE (next);
next = next_next;
}
h->real_next = next;
if (next != NULL)
next->real_prev = h;
h = next;
}
Last_Read_Header = NULL;
Num_Headers = 0;
h = Headers = Slrn_First_Header;
Headers->prev = NULL;
while (h != NULL)
{
Num_Headers++;
h->next = next = h->real_next;
if (next != NULL)
{
next->prev = h;
}
h = next;
}
sort_by_sorting_mode ();
Slrn_Full_Screen_Update = 1;
}
/*}}}*/
/*{{{ cancel_article */
static void cancel_article (void)
{
char *from, *msgid, *newsgroups, *dist;
char me[256];
if (-1 == select_article (0)) return;
art_update_screen ();
(void) slrn_smg_refresh ();
if (slrn_get_yesno (0, "Are you sure that you want to cancel this article") <= 0)
return;
slrn_message ("Cancelling..."); slrn_smg_refresh ();
/* TO cancel, we post a cancel message with a 'control' header. First, check to
* see if this is really the owner of the message.
*/
from = extract_header ("From: ", 6);
if (from != NULL) from = parse_from (from);
if (from == NULL) from = "";
sprintf (me, "%s@%s", Slrn_User_Info.username, Slrn_User_Info.host);
if (slrn_case_strcmp ((unsigned char *) from, (unsigned char *) me))
{
slrn_error ("Failed: Your name: '%s' is not '%s'", me, from);
return;
}
if (NULL == (newsgroups = extract_header ("Newsgroups: ", 12)))
newsgroups = "";
if (NULL == (msgid = extract_header ("Message-ID: ", 12)))
{
slrn_error ("No message id.");
return;
}
dist = extract_header("Distribution: ", 14);
if (Slrn_Post_Obj->po_start () < 0) return;
Slrn_Post_Obj->po_printf ("From: %s\r\nNewsgroups: %s\r\nSubject: cancel %s\r\nControl: cancel %s\r\n",
from, newsgroups, msgid, msgid);
if (dist != NULL)
{
Slrn_Post_Obj->po_printf ("Distribution: %s\r\n", dist);
}
Slrn_Post_Obj->po_printf("\r\nignore\r\nArticle canceled by slrn %s\r\n", Slrn_Version);
if (0 == Slrn_Post_Obj->po_end ())
{
slrn_message ("Done.");
}
}
/*}}}*/
/*{{{ header/thread (un)deletion/(un)catchup */
static void delete_header (Slrn_Header_Type *h)
{
if (h->flags & HEADER_TAGGED) return;
if (0 == (h->flags & HEADER_READ))
{
kill_cross_references (h);
h->flags |= HEADER_READ;
}
Group_Modified = 1;
}
static void undelete_header (Slrn_Header_Type *h)
{
if (h->flags & HEADER_TAGGED) return;
h->flags &= ~HEADER_READ;
Group_Modified = 1;
}
static void catch_up_all (void)
{
for_all_headers (delete_header, 1);
Slrn_Full_Screen_Update = 1;
Group_Modified = 1;
}
static void un_catch_up_all (void)
{
for_all_headers (undelete_header, 1);
Slrn_Full_Screen_Update = 1;
Group_Modified = 1;
}
static void catch_up_to_here (void)
{
for_all_headers (delete_header, 0);
Slrn_Full_Screen_Update = 1;
Group_Modified = 1;
}
static void un_catch_up_to_here (void)
{
for_all_headers (undelete_header, 0);
Slrn_Full_Screen_Update = 1;
Group_Modified = 1;
}
static void undelete_header_cmd (void)
{
if ((Slrn_Current_Header->parent != NULL)/* in middle of thread */
|| (Slrn_Current_Header->child == NULL)/* At top with no child */
/* or at top with child showing */
|| (0 == (Slrn_Current_Header->child->flags & HEADER_HIDDEN)))
{
undelete_header (Slrn_Current_Header);
}
else
{
for_this_tree (Slrn_Current_Header, undelete_header);
}
slrn_header_down_n (1, 0);
Slrn_Full_Screen_Update = 1;
}
static void delete_header_cmd (void)
{
if ((Slrn_Current_Header->parent != NULL)/* in middle of thread */
|| (Slrn_Current_Header->child == NULL)/* At top with no child */
/* or at top with child showing */
|| (0 == (Slrn_Current_Header->child->flags & HEADER_HIDDEN)))
{
delete_header (Slrn_Current_Header);
}
else
{
for_this_tree (Slrn_Current_Header, delete_header);
}
next_unread ();
Slrn_Full_Screen_Update = 1;
}
static void thread_delete_cmd (void)
{
for_this_tree (Slrn_Current_Header, delete_header);
delete_header_cmd ();
}
/*}}}*/
/*{{{ group_lens functions */
#if SLRN_HAS_GROUPLENS
static void grouplens_rate_article (void)
{
int ch;
if ((Slrn_Current_Header == NULL)
|| (Num_GroupLens_Rated == -1))
return;
slrn_message ("Rate article (1-5):");
slrn_smg_refresh ();
Slrn_Message_Present = 0;
ch = SLang_getkey ();
if ((ch < '1') || (ch > '5'))
{
slrn_error ("Rating must be in range 1 to 5.");
return;
}
slrn_group_lens_rate_article (Slrn_Current_Header, ch - '0',
(Article_Visible && (Header_Showing == Slrn_Current_Header)));
}
#endif
/*}}}*/
/*{{{ mouse commands */
/* actions for different regions:
* - top status line (help)
* - header status line
* - above header status line
* - below header status line
* - bottom status line
*/
static void art_mouse (void (*top_status)(void),
void (*header_status)(void),
void (*bot_status)(void),
void (*normal_region)(void)
)
{
int r, c;
slrn_get_mouse_rc (&r, &c);
/* take top status line into account */
if (r == 1)
{
if (Slrn_Use_Mouse)
(void) slrn_execute_menu (c, Art_Functions_Ptr);
else if (NULL != top_status) (*top_status) ();
return;
}
if (r >= SLtt_Screen_Rows)
return;
/* On header status line */
if (r - 2 == Header_Window_Height)
{
if (NULL != header_status) (*header_status) ();
return;
}
/* bottom status line */
if (r == SLtt_Screen_Rows - 1)
{
if (NULL != bot_status) (*bot_status) ();
return;
}
if (r - 2 > Header_Window_Height)
{
if (NULL != normal_region) (*normal_region) ();
return;
}
r -= (1 + Last_Cursor_Row);
if (r < 0)
{
r = -r;
if (r != slrn_header_up_n (r, 0)) return;
}
else if (r != slrn_header_down_n (r, 0)) return;
select_article (1);
/* if (NULL != normal_region) (*normal_region) (); */
}
static void art_mouse_left (void)
{
art_mouse (slrn_article_help, header_pagedn,
art_next_unread, art_pagedn);
}
static void art_mouse_middle (void)
{
art_mouse (toggle_show_author, hide_article,
toggle_quotes, hide_article);
#if 1
/* Make up for buggy rxvt which have problems with the middle key. */
if (NULL != getenv ("COLORTERM"))
{
if (SLang_input_pending (7))
{
while (SLang_input_pending (0)) SLang_getkey ();
}
}
#endif
}
static void art_mouse_right (void)
{
art_mouse (slrn_article_help, header_pageup,
art_prev_unread, art_pageup);
}
/*}}}*/
/*{{{ slrn_init_article_mode */
#define A_KEY(s, f) {s, (int (*)(void)) f}
static SLKeymap_Function_Type Art_Functions [] =
{
#if SLRN_HAS_GROUPLENS
A_KEY("grouplens_rate_article", grouplens_rate_article),
#endif
A_KEY("browse_url", browse_url),
A_KEY("browse_url", browse_url),
A_KEY("art_xpunge", art_xpunge),
A_KEY("wrap_article", wrap_article),
A_KEY("goto_last_read", goto_last_read),
#if SLRN_HAS_DECODE
A_KEY("decode", decode_article),
#endif
#if 0
#if SLRN_HAS_SPOILERS
A_KEY("show_spoilers", show_spoilers),
#endif
#endif
A_KEY("create_score", create_score),
A_KEY("toggle_collapse_threads", toggle_collapse_threads),
A_KEY("toggle_header_tag", toggle_header_tag),
A_KEY("tag_header", num_tag_header),
A_KEY("untag_headers", num_untag_headers),
A_KEY("repeat_last_key", slrn_repeat_last_key),
A_KEY("goto_beginning", art_bob),
A_KEY("goto_end", art_eob),
A_KEY("forward_digest", skip_digest_forward),
A_KEY("locate_article", locate_header_by_msgid),
A_KEY("delete_thread", thread_delete_cmd),
A_KEY("post", slrn_post_cmd),
A_KEY("get_children_headers", get_children_headers),
A_KEY("get_parent_header", get_parent_header),
A_KEY("skip_to_next_group", skip_to_next_group),
A_KEY("skip_to_prev_group", skip_to_prev_group),
A_KEY("fast_quit", fast_quit),
A_KEY("catchup_all", catch_up_all),
A_KEY("uncatchup_all", un_catch_up_all),
A_KEY("catchup", catch_up_to_here),
A_KEY("uncatchup", un_catch_up_to_here),
A_KEY("pipe_article", pipe_article),
A_KEY("toggle_rot13", toggle_rot13),
A_KEY("toggle_show_author", toggle_show_author),
A_KEY("toggle_sort", toggle_sort),
A_KEY("skip_quotes", skip_quoted_text),
A_KEY("art_header_bob", art_header_bob),
A_KEY("art_header_eob", art_header_eob),
A_KEY("goto_article", goto_article),
A_KEY("shrink_window", shrink_window),
A_KEY("enlarge_window", enlarge_window),
A_KEY("article_pagedn", art_pagedn),
A_KEY("article_pageup", art_pageup),
A_KEY("article_linedn", art_linedn),
A_KEY("article_lineup", art_lineup),
A_KEY("article_search", article_search),
A_KEY("author_search_backward", author_search_backward),
A_KEY("author_search_forward", author_search_forward),
A_KEY("cancel", cancel_article),
A_KEY("delete", delete_header_cmd),
A_KEY("down", header_down),
A_KEY("exchange_mark", exchange_mark),
A_KEY("followup", followup),
A_KEY("forward", forward),
A_KEY("help", slrn_article_help),
A_KEY("hide_article", hide_article),
A_KEY("left", art_left),
A_KEY("mark_spot", mark_spot),
A_KEY("next", art_next_unread),
A_KEY("prev", art_prev_unread),
A_KEY("quit", art_quit),
A_KEY("redraw", slrn_redraw),
A_KEY("reply", reply_cmd),
A_KEY("right", art_right),
A_KEY("save", save_article),
A_KEY("scroll_dn", art_pagedn),
A_KEY("scroll_up", art_pageup),
A_KEY("subject_search_backward", subject_search_backward),
A_KEY("subject_search_forward", subject_search_forward),
A_KEY("suspend", art_suspend_cmd),
A_KEY("toggle_headers", toggle_headers),
A_KEY("toggle_quotes", toggle_quotes),
A_KEY("undelete", undelete_header_cmd),
A_KEY("up", header_up),
A_KEY("pageup", header_pageup),
A_KEY("pagedn", header_pagedn),
A_KEY("next_same_subject", next_header_same_subject),
A_KEY("next_high_score", next_high_score),
A_KEY("next_high_score", next_high_score),
A_KEY("locate_header_by_msgid", locate_header_by_msgid),
A_KEY(NULL, NULL)
};
Slrn_Mode_Type Art_Mode_Cap =
{
NULL, /* keymap */
art_update_screen,
art_winch_sig,
slrn_art_hangup,
SLRN_ARTICLE_MODE
};
void slrn_init_article_mode (void)
{
char *err = "Unable to create Article keymap!";
char numbuf[2];
char ch;
if (NULL == (Slrn_Article_Keymap = SLang_create_keymap ("article", NULL)))
slrn_exit_error (err);
Art_Mode_Cap.keymap = Slrn_Article_Keymap;
Slrn_Article_Keymap->functions = Art_Functions;
Art_Functions_Ptr = Art_Functions;
numbuf[1] = 0;
for (ch = '0'; ch <= '9'; ch++)
{
numbuf[0] = ch;
SLkm_define_key (numbuf, (FVOID_STAR) goto_header_number, Slrn_Article_Keymap);
}
#if SLRN_HAS_GROUPLENS
numbuf[0] = '0';
/* Steal '0' for use as a prefix for rating. */
SLkm_define_key (numbuf, (FVOID_STAR) grouplens_rate_article, Slrn_Article_Keymap);
#endif
SLkm_define_key ("\033l", (FVOID_STAR) locate_header_by_msgid, Slrn_Article_Keymap);
SLkm_define_key ("\0331", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
SLkm_define_key ("\0332", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
SLkm_define_key ("\0333", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
SLkm_define_key ("\0334", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
SLkm_define_key ("\0335", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
SLkm_define_key ("\0336", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
SLkm_define_key ("\0337", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
SLkm_define_key ("\0338", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
SLkm_define_key ("\0339", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
SLkm_define_key ("\0330", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
SLkm_define_key ("*", (FVOID_STAR) toggle_header_tag, Slrn_Article_Keymap);
SLkm_define_key ("#", (FVOID_STAR) num_tag_header, Slrn_Article_Keymap);
SLkm_define_key ("\033#", (FVOID_STAR) num_untag_headers, Slrn_Article_Keymap);
#if SLRN_HAS_DECODE
SLkm_define_key (":", (FVOID_STAR) decode_article, Slrn_Article_Keymap);
#endif
SLkm_define_key (";", (FVOID_STAR) mark_spot, Slrn_Article_Keymap);
SLkm_define_key (",", (FVOID_STAR) exchange_mark, Slrn_Article_Keymap);
SLkm_define_key ("g", (FVOID_STAR) skip_digest_forward, Slrn_Article_Keymap);
SLkm_define_key ("s", (FVOID_STAR) subject_search_forward, Slrn_Article_Keymap);
SLkm_define_key ("S", (FVOID_STAR) subject_search_backward, Slrn_Article_Keymap);
SLkm_define_key ("a", (FVOID_STAR) author_search_forward, Slrn_Article_Keymap);
SLkm_define_key ("A", (FVOID_STAR) author_search_backward, Slrn_Article_Keymap);
SLkm_define_key ("/", (FVOID_STAR) article_search, Slrn_Article_Keymap);
SLkm_define_key ("\033^C", (FVOID_STAR) cancel_article, Slrn_Article_Keymap);
SLkm_define_key ("o", (FVOID_STAR) save_article, Slrn_Article_Keymap);
SLkm_define_key ("|", (FVOID_STAR) pipe_article, Slrn_Article_Keymap);
SLkm_define_key ("?", (FVOID_STAR) slrn_article_help, Slrn_Article_Keymap);
SLkm_define_key ("H", (FVOID_STAR) hide_article, Slrn_Article_Keymap);
SLkm_define_key ("L", (FVOID_STAR) goto_last_read, Slrn_Article_Keymap);
SLkm_define_key ("N", (FVOID_STAR) skip_to_next_group, Slrn_Article_Keymap);
SLkm_define_key ("n", (FVOID_STAR) art_next_unread, Slrn_Article_Keymap);
SLkm_define_key ("j", (FVOID_STAR) goto_article, Slrn_Article_Keymap);
SLkm_define_key ("p", (FVOID_STAR) art_prev_unread, Slrn_Article_Keymap);
SLkm_define_key ("P", (FVOID_STAR) slrn_post_cmd, Slrn_Article_Keymap);
SLkm_define_key ("d", (FVOID_STAR) delete_header_cmd, Slrn_Article_Keymap);
SLkm_define_key ("u", (FVOID_STAR) undelete_header_cmd, Slrn_Article_Keymap);
SLkm_define_key ("U", (FVOID_STAR) browse_url, Slrn_Article_Keymap);
SLkm_define_key ("x", (FVOID_STAR) art_xpunge, Slrn_Article_Keymap);
SLkm_define_key ("t", (FVOID_STAR) toggle_headers, Slrn_Article_Keymap);
SLkm_define_key ("T", (FVOID_STAR) toggle_quotes, Slrn_Article_Keymap);
SLkm_define_key ("\033a", (FVOID_STAR) toggle_show_author, Slrn_Article_Keymap);
SLkm_define_key ("\033d", (FVOID_STAR) thread_delete_cmd, Slrn_Article_Keymap);
SLkm_define_key ("\033p", (FVOID_STAR) get_parent_header, Slrn_Article_Keymap);
SLkm_define_key ("\033^P", (FVOID_STAR) get_children_headers, Slrn_Article_Keymap);
SLkm_define_key ("\033t", (FVOID_STAR) toggle_collapse_threads, Slrn_Article_Keymap);
SLkm_define_key ("\t", (FVOID_STAR) skip_quoted_text, Slrn_Article_Keymap);
SLkm_define_key (" ", (FVOID_STAR) art_pagedn, Slrn_Article_Keymap);
SLkm_define_key (".", (FVOID_STAR) slrn_repeat_last_key, Slrn_Article_Keymap);
SLkm_define_key ("\r", (FVOID_STAR) art_pagedn, Slrn_Article_Keymap);
#ifdef __os2__
SLkm_define_key ("^@S", (FVOID_STAR) art_pageup, Slrn_Article_Keymap);
SLkm_define_key ("\xE0S", (FVOID_STAR) art_pageup, Slrn_Article_Keymap);
#else
SLkm_define_key ("^?", (FVOID_STAR) art_pageup, Slrn_Article_Keymap);
#endif
SLkm_define_key ("b", (FVOID_STAR) art_pageup, Slrn_Article_Keymap);
SLkm_define_key ("q", (FVOID_STAR) art_quit, Slrn_Article_Keymap);
SLkm_define_key ("F", (FVOID_STAR) forward, Slrn_Article_Keymap);
SLkm_define_key ("f", (FVOID_STAR) followup, Slrn_Article_Keymap);
SLkm_define_key ("K", (FVOID_STAR) create_score, Slrn_Article_Keymap);
SLkm_define_key ("W", (FVOID_STAR) wrap_article, Slrn_Article_Keymap);
SLkm_define_key ("r", (FVOID_STAR) reply_cmd, Slrn_Article_Keymap);
SLkm_define_key ("=", (FVOID_STAR) next_header_same_subject, Slrn_Article_Keymap);
SLkm_define_key ("!", (FVOID_STAR) next_high_score, Slrn_Article_Keymap);
SLkm_define_key ("<", (FVOID_STAR) art_bob, Slrn_Article_Keymap);
SLkm_define_key (">", (FVOID_STAR) art_eob, Slrn_Article_Keymap);
SLkm_define_key ("^R", (FVOID_STAR) slrn_redraw, Slrn_Article_Keymap);
SLkm_define_key ("^L", (FVOID_STAR) slrn_redraw, Slrn_Article_Keymap);
SLkm_define_key ("^Z", (FVOID_STAR) art_suspend_cmd, Slrn_Article_Keymap);
SLkm_define_key ("^P", (FVOID_STAR) header_up, Slrn_Article_Keymap);
SLkm_define_key ("^M", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
#ifdef __os2__
SLkm_define_key ("\033^@H", (FVOID_STAR) art_lineup, Slrn_Article_Keymap);
SLkm_define_key ("\033\xE0H", (FVOID_STAR) art_lineup, Slrn_Article_Keymap);
SLkm_define_key ("\033^@P", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
SLkm_define_key ("\033\xE0P", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
SLkm_define_key ("^@H", (FVOID_STAR) header_up, Slrn_Article_Keymap);
SLkm_define_key ("\xE0H", (FVOID_STAR) header_up, Slrn_Article_Keymap);
SLkm_define_key ("^@P", (FVOID_STAR) header_down, Slrn_Article_Keymap);
SLkm_define_key ("\xE0P", (FVOID_STAR) header_down, Slrn_Article_Keymap);
SLkm_define_key ("^@M", (FVOID_STAR) art_right, Slrn_Article_Keymap);
SLkm_define_key ("\xE0M", (FVOID_STAR) art_right, Slrn_Article_Keymap);
SLkm_define_key ("^@K", (FVOID_STAR) art_left, Slrn_Article_Keymap);
SLkm_define_key ("\xE0K", (FVOID_STAR) art_left, Slrn_Article_Keymap);
#else
SLkm_define_key ("\033\033[A", (FVOID_STAR) art_lineup, Slrn_Article_Keymap);
SLkm_define_key ("\033\033OA", (FVOID_STAR) art_lineup, Slrn_Article_Keymap);
SLkm_define_key ("\033\033[B", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
SLkm_define_key ("\033\033OB", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
SLkm_define_key ("\033[A", (FVOID_STAR) header_up, Slrn_Article_Keymap);
SLkm_define_key ("\033OA", (FVOID_STAR) header_up, Slrn_Article_Keymap);
SLkm_define_key ("\033[B", (FVOID_STAR) header_down, Slrn_Article_Keymap);
SLkm_define_key ("\033OB", (FVOID_STAR) header_down, Slrn_Article_Keymap);
SLkm_define_key ("\033[C", (FVOID_STAR) art_right, Slrn_Article_Keymap);
SLkm_define_key ("\033OC", (FVOID_STAR) art_right, Slrn_Article_Keymap);
SLkm_define_key ("\033[D", (FVOID_STAR) art_left, Slrn_Article_Keymap);
SLkm_define_key ("\033OD", (FVOID_STAR) art_left, Slrn_Article_Keymap);
#endif
SLkm_define_key ("\033S", (FVOID_STAR) toggle_sort, Slrn_Article_Keymap);
SLkm_define_key ("^U", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
SLkm_define_key ("\033V", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
#ifdef __os2__
SLkm_define_key ("^@I", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
SLkm_define_key ("\xE0I", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
SLkm_define_key ("^@Q", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
SLkm_define_key ("\xE0Q", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
#else
SLkm_define_key ("\033[5~", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
SLkm_define_key ("\033[6~", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
#endif
SLkm_define_key ("^D", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
SLkm_define_key ("^V", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
SLkm_define_key ("\033>", (FVOID_STAR) art_header_eob, Slrn_Article_Keymap);
SLkm_define_key ("\033<", (FVOID_STAR) art_header_bob, Slrn_Article_Keymap);
SLkm_define_key ("c", (FVOID_STAR) catch_up_all, Slrn_Article_Keymap);
SLkm_define_key ("\033c", (FVOID_STAR) catch_up_all, Slrn_Article_Keymap);
SLkm_define_key ("\033u", (FVOID_STAR) un_catch_up_all, Slrn_Article_Keymap);
SLkm_define_key ("\033C", (FVOID_STAR) catch_up_to_here, Slrn_Article_Keymap);
SLkm_define_key ("C", (FVOID_STAR) catch_up_to_here, Slrn_Article_Keymap);
SLkm_define_key ("\033U", (FVOID_STAR) un_catch_up_to_here, Slrn_Article_Keymap);
SLkm_define_key ("\033R", (FVOID_STAR) toggle_rot13, Slrn_Article_Keymap);
#if 0
#if SLRN_HAS_SPOILERS
SLkm_define_key ("\033?", (FVOID_STAR) show_spoilers, Slrn_Article_Keymap);
#endif
#endif
SLkm_define_key ("^N", (FVOID_STAR) header_down, Slrn_Article_Keymap);
SLkm_define_key ("^", (FVOID_STAR) enlarge_window, Slrn_Article_Keymap);
SLkm_define_key ("^^", (FVOID_STAR) shrink_window, Slrn_Article_Keymap);
/* mouse (left/middle/right) */
SLkm_define_key ("\033[M\040", (FVOID_STAR) art_mouse_left, Slrn_Article_Keymap);
SLkm_define_key ("\033[M\041", (FVOID_STAR) art_mouse_middle, Slrn_Article_Keymap);
SLkm_define_key ("\033[M\042", (FVOID_STAR) art_mouse_right, Slrn_Article_Keymap);
if (SLang_Error) slrn_exit_error (err);
}
/*}}}*/
/*{{{ slrn_article_mode and support functions */
static void slrn_art_hangup (int sig)
{
if (Slrn_Current_Header != NULL)
undelete_header_cmd (); /* in case we are reading one */
art_quit ();
if (Slrn_Group_Hangup_Hook != NULL) (*Slrn_Group_Hangup_Hook) (sig);
slrn_quit (sig);
}
/*{{{ mark_ranges_read */
static void mark_ranges_read (Slrn_Range_Type *r)
{
Slrn_Header_Type *h = Slrn_First_Header;
int min, max;
while ((r != NULL) && (h != NULL))
{
min = r->min;
max = r->max;
while (h != NULL)
{
if (h->number < min)
{
h = h->real_next;
continue;
}
if (h->number > max)
{
break;
}
h->flags |= HEADER_READ;
h = h->real_next;
}
r = r->next;
}
}
/*}}}*/
/*{{{ slrn_select_article_mode */
/* If all > 0, get last 'all' headers from server independent of whether
* they have been read or not.
* If all < 0, and this is not the first time this group has been accessed,
* either during this session or during previous sessions, get
* that last 'all' UNREAD articles.
* Otherwise, fetch ALL UNREAD headers from the server.
*/
int slrn_select_article_mode (Slrn_Group_Type *g, int all, int score)
{
int min, max;
int smin, smax;
Slrn_Range_Type *r;
User_Aborted_Group_Read = 0;
Header_Number = 0;
Headers = Slrn_First_Header = NULL;
Threads_Collapsed = 0;
Same_Subject_Start_Header = NULL;
Current_Group = g;
r = &g->range;
Slrn_Current_Group_Name = g->name;
Slrn_Score_After_XOver = Slrn_Server_Obj->sv_has_xover;
Number_Killed = Number_High_Scored = Number_Low_Scored = 0;
if (score && (1 == slrn_open_score (Slrn_Current_Group_Name)))
Perform_Scoring = 1;
else Perform_Scoring = 0;
Slrn_Server_Min = r->min;
Slrn_Server_Max = r->max;
r = r->next;
/* Now r points to ranges already read. */
if (all > 0)
{
min = Slrn_Server_Max - all + 1;
if (min < Slrn_Server_Min) min = Slrn_Server_Min;
(void) get_headers (min, Slrn_Server_Max, &all);
mark_ranges_read (r);
}
else
{
if ((all < 0) && (r != NULL))
{
int unread;
/* This condition will occur when the user wants to read unread
* articles that occur in a gap, i.e., RRRUUUUURRRUUUUUUU and
* we need to dig back far enough below the last group of read
* ones until we have retrieved abs(all) articles.
*
* The problem with this is that some articles may not be
* available on the server which means that the number to
* go back will be under estimated.
*/
all = -all;
while (r->next != NULL) r = r->next;
/* Go back through previously read articles counting unread.
* If number unread becomes greater than the number that we
* intend to read, then we know where to start querying
* the server.
*/
unread = 0;
max = Slrn_Server_Max;
while (r->prev != NULL)
{
unread += max - r->max;
if (unread >= all) break;
max = r->min - 1;
r = r->prev;
}
if (unread >= all)
{
/* This may be problematic if some articles are missing on
* the server. If that is the case, smin will be to high
* and we will fall short of the goal.
*/
smin = r->max + (unread - all) + 1;
}
else smin = Slrn_Server_Min;
smax = Slrn_Server_Max;
r = r->next;
}
else
{
/* all == 0, or no previously read articles. */
smin = Slrn_Server_Min;
smax = Slrn_Server_Max;
if (r != NULL)
{
Slrn_Range_Type *r1;
/* Estimate how many are available to read */
all = smax - r->max;
#if 0 /* is this correct?? */
all++;
#endif
/* Now subtract the ones that we have already read. */
r1 = r->next;
while (r1 != NULL)
{
all -= (r1->max - r1->min) + 1;
r1 = r1->next;
}
/* This condition should never arise */
if (all == 0) all = smax - smin + 1;
}
else all = smax - smin + 1;
}
while (r != NULL)
{
if (r->min > smin)
{
min = smin;
max = r->min - 1;
if (get_headers (min, max, &all) == -1)
{
if (SLang_Error == USER_BREAK)
break;
Slrn_Groups_Dirty = 1;
r->min = min;
}
smin = r->max + 1;
}
else
{
smin = r->max + 1;
}
r = r->next;
}
if (smin <= smax)
{
if (-1 == get_headers (smin, smax, &all))
{
}
}
}
if (SLang_Error == USER_BREAK)
{
slrn_error ("Group transfer aborted.");
Slrn_Server_Obj->sv_close ();
/* This means that we cannot update ranges for this group because
* the user aborted and update_ranges assumes that all articles
* upto server max are present.
*/
User_Aborted_Group_Read = 1;
art_quit ();
}
else if (Slrn_Score_After_XOver
&& Perform_Scoring)
score_headers ();
if (Headers == NULL)
{
slrn_close_score ();
free_kill_lists_and_update ();
slrn_error ("No unread articles found.");
Slrn_Current_Group_Name = NULL;
if (SLang_Error == USER_BREAK) return -1;
else return -2;
}
make_hash_table ();
sort_by_sorting_mode ();
extract_real_names ();
Last_Current_Mode = Slrn_Current_Mode;
Slrn_Group_Hangup_Hook = Slrn_Current_Mode->hangup_fun;
Slrn_Current_Mode = &Art_Mode_Cap;
Slrn_Current_Header = Headers;
Header_Number = 1;
Top_Header = NULL;
Last_Cursor_Row = 0;
Group_Modified = 0;
Mark_Header = NULL;
Top_Line = Current_Line = Slrn_Article_Lines = NULL;
At_End_Of_Article = NULL;
Header_Showing = NULL;
Article_Visible = 0;
art_winch ();
#if SLRN_HAS_SLANG
(void) SLang_run_hooks ("article_mode_hook", NULL, NULL);
#endif
#if SLRN_HAS_GROUPLENS
slrn_set_suspension (1);
Num_GroupLens_Rated = slrn_get_grouplens_scores ();
slrn_set_suspension (0);
#endif
quick_help ();
if (Slrn_Startup_With_Article) art_pagedn ();
if (SLang_Error == 0)
{
if (Perform_Scoring
/* && (Number_Killed || Number_High_Scored) */
)
{
#if SLRN_HAS_GROUPLENS
if (Num_GroupLens_Rated != -1)
{
slrn_message ("Num Killed: %u, Num High: %u, Num Low: %u, Num GroupLens Rated: %d",
Number_Killed, Number_High_Scored, Number_Low_Scored,
Num_GroupLens_Rated);
}
else
#endif
slrn_message ("Num Killed: %u, Num High: %u, Num Low: %u",
Number_Killed, Number_High_Scored, Number_Low_Scored);
}
else slrn_clear_message ();
}
Number_Low_Scored = Number_Killed = Number_High_Scored = 0;
return 0;
}
/*}}}*/
/*}}}*/
/*{{{ screen update functions */
static void quick_help (void) /*{{{*/
{
char *msg;
if (Article_Visible == 0)
msg = "SPC:Select Ctrl-D:PgDn Ctrl-U:PgUp d:Mark-as-Read n:Next p:Prev q:Quit";
else
msg = "SPC:Pgdn B:PgUp u:Un-Mark-as-Read f:Followup n:Next p:Prev q:Quit";
if (0 == slrn_message (msg))
Slrn_Message_Present = 0;
}
/*}}}*/
static void write_rot13 (unsigned char *buf) /*{{{*/
{
static int init_rot13;
static char rot13buf[256];
unsigned char ch;
int i;
if (init_rot13 == 0)
{
init_rot13 = 1;
for (i = 0; i < 256; i++)
{
rot13buf[i] = i;
}
for (i = 'A'; i <= 'M'; i++)
{
rot13buf[i] = i + 13;
/* Now take care of lower case ones */
rot13buf[i + 32] = i + 32 + 13;
}
for (i = 'N'; i <= 'Z'; i++)
{
rot13buf[i] = i - 13;
/* Now take care of lower case ones */
rot13buf[i + 32] = i + 32 - 13;
}
}
while ((ch = *buf++) != 0)
{
ch = rot13buf[ch];
SLsmg_write_nchars ((char *) &ch, 1);
}
}
/*}}}*/
/*{{{ utility routines */
#if SLRN_HAS_SPOILERS
/* write out the line, replacing all printable chars with '*' */
static void write_spoiler (unsigned char *buf) /*{{{*/
{
unsigned char ch;
Spoilers_Visible = Slrn_Current_Header;
if (Slrn_Spoiler_Char == ' ')
return;
while ((ch = *buf++) != 0)
{
if (!isspace(ch)) ch = Slrn_Spoiler_Char;
SLsmg_write_nchars ((char *) &ch, 1);
}
}
/*}}}*/
#endif
static void draw_tree (Slrn_Header_Type *h) /*{{{*/
{
unsigned char *b;
#ifndef __os2__
if (SLtt_Has_Alt_Charset == 0)
{
SLsmg_write_string ((char *) h->tree);
SLsmg_write_string (" ");
return;
}
SLsmg_set_char_set (1);
#endif
slrn_set_color (TREE_COLOR);
if (*h->tree) SLsmg_write_string ((char *) h->tree);
if (h->flags & FAKE_CHILDREN)
{
static unsigned char buf[2] = {SLSMG_UTEE_CHAR,SLSMG_HLINE_CHAR};
b = buf;
SLsmg_forward (-1);
SLsmg_write_char (SLSMG_ULCORN_CHAR);
}
else if ((h->sister == NULL) ||
((h->sister->flags & FAKE_PARENT) && ((h->flags & FAKE_PARENT) == 0)))
{
static unsigned char buf[2] = {SLSMG_LLCORN_CHAR,SLSMG_HLINE_CHAR};
b = buf;
}
else
{
static unsigned char buf[2] = {SLSMG_LTEE_CHAR,SLSMG_HLINE_CHAR
};
b = buf;
}
SLsmg_write_nchars ((char *) b, 2);
#ifndef __os2__
SLsmg_set_char_set (0);
#endif
}
/*}}}*/
/*{{{ check_subject */
/*
* This checks if subjects should be printed (correctly I hope)
* hacked: articles in a tree shouldn't display their subject if the
* subject is already displayed (i.e. at top)
* To add: take more Re:'s into account (currently, one is allowed)
*/
int Slrn_Show_Thread_Subject = 0;
static int check_subject (Slrn_Header_Type *h)
{
char *psubj, *subj;
subj = h->subject;
psubj = h->prev->subject; /* used to be: h->parent->subject */
if ((subj == NULL) || (psubj == NULL)) return 1;
if (((*subj | 0x20) == 'r')
&& ((*(subj + 1) | 0x20) == 'e')
&& (*(subj + 2) == ':'))
{
subj += 3;
while (*subj == ' ') subj++;
}
if (((*psubj | 0x20) == 'r')
&& ((*(psubj + 1) | 0x20) == 'e')
&& (*(psubj + 2) == ':'))
{
psubj += 3;
while (*psubj == ' ') psubj++;
}
return slrn_case_strcmp ((unsigned char *)subj, (unsigned char *)psubj);
}
/*}}}*/
#if SLRN_HAS_GROUPLENS
static int Update_Column_Offset = 0;
#endif
static void write_header_flags (Slrn_Header_Type *h, int row)
{
unsigned int flags = h->flags;
#if 1
if (Slrn_Use_Header_Numbers)
{
slrn_set_color (HEADER_NUMBER_COLOR);
SLsmg_printf ("%2d", row);
if (row > Largest_Header_Number) Largest_Header_Number = row;
}
else SLsmg_write_string (" ");
#endif
if (flags & HEADER_NTAGGED)
{
slrn_set_color (HIGH_SCORE_COLOR);
SLsmg_printf ("%2d",
h->tag_number);
}
else
{
if ((flags & HEADER_HIGH_SCORE)
|| ((flags & FAKE_HEADER_HIGH_SCORE)
&& (h->child != NULL)
&& (h->child->flags & HEADER_HIDDEN)))
{
slrn_set_color (HIGH_SCORE_COLOR);
SLsmg_printf ("!%c",
((flags & HEADER_TAGGED) ? '*': ' '));
}
else
{
slrn_set_color (0);
SLsmg_printf (" %c",
((flags & HEADER_TAGGED) ? '*': ' '));
}
}
slrn_set_color (0);
if (Slrn_Sorting_Mode == 0)
{
SLsmg_printf ("%c %6d ",
((flags & HEADER_READ) ? 'D': '-'),
h->number);
}
#ifdef SLRN_HAS_SORT_BY_SCORE
else if (Slrn_Display_Score && (Slrn_Sorting_Mode & SORT_BY_SCORE))
{
SLsmg_printf ("%c %5d ",
((flags & HEADER_READ) ? 'D': '-'),
((h->child != NULL) && (h->child->flags & HEADER_HIDDEN))
? h->thread_score : h->score);
}
#endif
else SLsmg_printf ("%c%5d:",
((flags & HEADER_READ) ? 'D': '-'),
h->lines);
#if SLRN_HAS_GROUPLENS
# define UPDATE_COLUMN_OFFSET Update_Column_Offset
# define SLRN_GROUPLENS_DISPLAY_WIDTH 5
if (Num_GroupLens_Rated != -1)
{
char buf [SLRN_GROUPLENS_DISPLAY_WIDTH], *b, *bmax;
int pred = h->gl_pred;
b = buf;
bmax = b + SLRN_GROUPLENS_DISPLAY_WIDTH;
if (pred < 0)
{
while (b < bmax) *b++ = ' ';
buf [SLRN_GROUPLENS_DISPLAY_WIDTH / 2] = '?';
}
else
{
while ((pred > 0) && (b < bmax))
{
pred--;
*b++ = '*';
}
while (b < bmax) *b++ = ' ';
}
slrn_set_color (GROUPLENS_DISPLAY_COLOR);
SLsmg_write_nchars (buf, SLRN_GROUPLENS_DISPLAY_WIDTH);
slrn_set_color (0);
Update_Column_Offset = SLRN_GROUPLENS_DISPLAY_WIDTH;
}
else Update_Column_Offset = 0;
#else
# define UPDATE_COLUMN_OFFSET 0
#endif
}
/*}}}*/
#define NEW_SCROLL
static void art_update_screen (void)
{
Slrn_Header_Type *h;
Slrn_Article_Line_Type *l;
int height;
int row;
int c0;
int ctrl_l_found = 0;
At_End_Of_Article = NULL;
#if SLRN_HAS_SPOILERS
Spoilers_Visible = NULL;
#endif
if (Slrn_Full_Screen_Update) Largest_Header_Number = 0;
/* erase last cursor */
if (Last_Cursor_Row >= 0)
{
SLsmg_gotorc (Last_Cursor_Row, 0);
if (Slrn_Use_Header_Numbers)
{
slrn_set_color (HEADER_NUMBER_COLOR);
SLsmg_printf ("%2d", Last_Cursor_Row);
}
else SLsmg_write_string (" ");
}
height = Header_Window_Height;
if ((Top_Header != NULL)
&& (Top_Header->flags & HEADER_HIDDEN))
Top_Header = NULL;
h = Top_Header;
#ifdef NEW_SCROLL
if (Article_Visible)
{
/* do not consider top and bottom lines of the header window
* if article one is open
*/
if (height > 2) height--;
if (Top_Header == Slrn_Current_Header)
h = NULL;
}
#endif
/* Find out if the header is already visible */
row = 0;
if (h != NULL)
{
while ((h != Slrn_Current_Header) && (row < height))
{
h = h->next;
if (h == NULL) break;
if (0 == (h->flags & HEADER_HIDDEN)) row++;
}
if (row == height) h = NULL;
}
#ifdef NEW_SCROLL
height = Header_Window_Height;
#endif
/* The 1 is added because of the top status line */
if (h == Slrn_Current_Header) Last_Cursor_Row = row + 1;
if (h == NULL)
{
#ifdef NEW_SCROLL
h = Slrn_Current_Header;
if (height > 1)
{
if (h != NULL) h = h->prev;
while ((h != NULL) && (h->flags & HEADER_HIDDEN))
h = h->prev;
if (h == NULL) h = Slrn_Current_Header;
}
Top_Header = h;
#else
h = Top_Header = Slrn_Current_Header;
#endif
Slrn_Full_Screen_Update = 1;
row = 0;
}
else h = Top_Header;
SLsmg_gotorc (height + 1, 0);
slrn_set_color (STATUS_COLOR);
SLsmg_printf ("News Group: %s", Slrn_Current_Group_Name);
slrn_print_percent (height + 1, 60, Header_Number, Num_Headers, height - row);
slrn_set_color (0);
if (Slrn_Full_Screen_Update)
{
for (row = 1; row <= height; row++)
{
SLsmg_gotorc (row, 0);
SLsmg_erase_eol ();
while ((h != NULL) && (h->flags & HEADER_HIDDEN))
h = h->next;
if (h != NULL)
{
write_header_flags (h, row);
if (Slrn_Show_Author > 1)
{
SLsmg_write_char ('[');
slrn_set_color (AUTHOR_COLOR);
SLsmg_write_nchars (h->realname,
(h->realname_len > 15) ?
15 : h->realname_len);
SLsmg_set_color (0);
SLsmg_gotorc (row, 24 + UPDATE_COLUMN_OFFSET);
if ((h->next != NULL)
&& (h->next->flags & HEADER_HIDDEN))
{
SLsmg_write_string ("] ");
slrn_set_color (THREAD_NUM_COLOR);
SLsmg_printf ("%2d ",
1 + h->num_children);
}
else
{
SLsmg_write_string ("] ");
if ((h->parent != NULL) || (h->flags & FAKE_CHILDREN))
{
draw_tree (h);
}
}
slrn_set_color (SUBJECT_COLOR);
if ((Slrn_Show_Thread_Subject)
/* || (0 == h->num_children) */
|| (h->parent == NULL)
|| (row == 1)
|| check_subject (h))
SLsmg_write_string (h->subject);
else
SLsmg_write_string (">"); /* subthread */
}
else
{
if ((h->next != NULL)
&& (h->next->flags & HEADER_HIDDEN))
{
slrn_set_color (THREAD_NUM_COLOR);
SLsmg_printf (" %2d ", h->num_children + 1);
}
else
{
SLsmg_gotorc (row, 15 + UPDATE_COLUMN_OFFSET);
if ((h->parent != NULL) || (h->flags & FAKE_CHILDREN))
{
draw_tree (h);
}
}
slrn_set_color (SUBJECT_COLOR);
SLsmg_write_string (h->subject);
if (Slrn_Show_Author)
{
SLsmg_gotorc (row, 50 + UPDATE_COLUMN_OFFSET);
slrn_set_color (AUTHOR_COLOR);
SLsmg_printf (" %s", h->from);
SLsmg_erase_eol ();
}
}
if (h == Slrn_Current_Header) Last_Cursor_Row = row;
h = h->next;
slrn_set_color (0);
}
}
}
if (Article_Visible &&
(Slrn_Full_Screen_Update || (Top_Line != Current_Line)))
{
row = height + 2;
height = SLtt_Screen_Rows - 2;
SLsmg_gotorc (height, 0);
slrn_set_color (STATUS_COLOR);
if (HScroll) SLsmg_write_char ('<'); else SLsmg_write_char (' ');
SLsmg_printf (" %d : %s", Header_Showing->number, Header_Showing->subject);
slrn_print_percent (height, 60, Article_Line_Number, Article_Total_Lines, height - row);
slrn_set_color (0);
c0 = HScroll;
SLsmg_set_screen_start (NULL, &c0);
Top_Line = l = Current_Line;
while (row < height)
{
SLsmg_gotorc (row, 0);
SLsmg_erase_eol ();
if (l != NULL)
{
if ((l->flags & HIDDEN_LINE) == 0)
{
if (l->flags & HEADER_LINE)
{
slrn_set_color (HEADER_COLOR);
SLsmg_write_string (l->buf);
}
else
{
if (l->flags & QUOTE_LINE)
slrn_set_color (QUOTE_COLOR);
else if (l->flags & SIGNATURE_LINE)
slrn_set_color (SIGNATURE_COLOR);
else slrn_set_color (ARTICLE_COLOR);
#if SLRN_HAS_SPOILERS
if (l->flags & SPOILER_LINE)
write_spoiler ((unsigned char *) l->buf);
else
#endif
if (Do_Rot13) write_rot13 ((unsigned char *) l->buf);
else SLsmg_write_string (l->buf);
}
row++;
}
#if 0
/* Check for ^L */
if ((*l->buf == 12) && (l->buf[1] == 0))
{
if (Do_Rot13 == 0) Do_Rot13 = -1;
}
#endif
l = l->next;
}
else
{
#if SLRN_HAS_TILDE_FEATURE
slrn_set_color (SLRN_TILDE_COLOR);
SLsmg_write_char ('~');
#endif
row++;
}
slrn_set_color (0);
}
if (((l == NULL) || ((l->flags & SIGNATURE_LINE)
&& Slrn_Sig_Is_End_Of_Article))
&& (Slrn_Current_Header == Header_Showing)
&& (ctrl_l_found == 0))
At_End_Of_Article = Slrn_Current_Header;
SLsmg_set_screen_start (NULL, NULL);
}
if (Slrn_Use_Mouse) slrn_update_article_menu ();
else
update_top_status_line ();
if (Slrn_Message_Present == 0)
{
#if SLRN_HAS_SPOILERS
if (Spoilers_Visible != NULL)
slrn_message ("Spoilers visible!");
else
#endif
quick_help ();
}
SLsmg_gotorc (Last_Cursor_Row, 0);
write_header_flags (Slrn_Current_Header, Last_Cursor_Row);
SLsmg_gotorc (Last_Cursor_Row, 0);
slrn_set_color (CURSOR_COLOR);
SLsmg_write_string ("->");
slrn_set_color (0);
/* if (Slrn_Show_Author > 1) SLsmg_gotorc (Last_Cursor_Row, 7); */
Slrn_Full_Screen_Update = 0;
}
/*}}}*/
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.