
/***************************************************************************
 *   Copyright (C) 1997 to 2004 by Jonathan Duddington                     *
 *   email: jonsd@users.sourceforge.net                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write see:                           *
 *               <http://www.gnu.org/licenses/>.                           *
 ***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "os.h"
#include "dbox.h"
#include "menu.h"
#include "wimp.h"
#include "font.h"
#include "werr.h"
#include "event.h"
#include "template.h"

#include "narc.h"
#include "hdrs.h"

extern char *path_choices;
extern OPTIONS options;
extern FOLDREC cty_fr;     /* for category data */
extern FOLDREC src_fr;

extern menu menu_main;
extern menu menu_prefs;
extern menu menu_list;


/* assembler routines */
extern int sorter_setup(void);
extern int standard_sorter(const void *a1, const void *a2);
extern int standard_sorter2(const void *a1, const void *a2);

extern char *get_cardfile2(FOLDREC *fr);
extern char *category_name(FOLDREC *fp, int x);
extern char *expand_source(int source, int explan);




extern int hide_level;    /* 2=hide all unread, 3=hide only unlocked unread */
extern int timezone_offset;
extern wimp_wind *text_window_template;


char *sort_card_file;

/* note: entry 8 also gives white-on-grey when reversed */
unsigned int colr_tab[] = {BLACK,RED2,ORANGE,YELLOW,GREEN,BLUE,PURPLE,GREY,GREY};
char *colour_names[] = {"Black","Red","Orange","Yellow","Green","Blue","Purple","Grey","",""};
char *menustr_colours = "Black,Red,Orange,Yellow,Green,Blue,Purple,Grey";





/* article status order for sort by status,
   include STATUS_BIT_OG for use by general_sorter
   Put "Writing" status first */
static char status_order[32] =
//   {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0,
{	2,4,6,8,10,12,14,16,1,3,5,7,9,11,13,0,  // 'replied' version immediately before std
	0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f
};

/* article status order for showing status of a higher level */
char status_priority[16] = {0,1,2,3,5,6,8,7, 0,1,4,3,5,6,8,7};
char priority_to_status[9] = {0,1,2,3,0xa,4,5,7,6};

menu menu_colours;
wimp_menustr *wmenu_colours;
wimp_menustr *wmenu_display;


int fontn[N_FONTS];
char *fontname[N_FONTS];
int font_spacing[N_FONTS];
int font_offset[N_FONTS];

unsigned int list_backg_rgb = 0;
unsigned int alist_backg_rgb = 0;
int standard_font;
int lists_line_height;
int lists_font_offset;
int status_sprite_offset;

int max_source_cat;  /* highest source number */
int scale_x, scale_y;   /* font scale factors */
char desktop_font_name[80];

char *status_sprites;


/***********************************************************************************/




/***********************************************************************************/


CAT *cat_data;   /* category data records */


/* sort control data */
/*********************/

int n_sort_levels;
char sort_field[N_SORT_LEVELS];
char sort_field2[N_SORT_LEVELS];
char sort_flags[N_SORT_LEVELS];   /* bit 0=descending */

static char std1_sort_field[N_SORT_LEVELS] = {CS_SOURCE,CS_TITLE,CS_DATE,0};
static char std1_sort_flags[N_SORT_LEVELS] = {0,0,0,0};


void paint_string(char *string, int x, int y, int width)
/******************************************************/
{
	int  n_chars;
	os_regset regs;

	regs.r[0] = standard_font;
	regs.r[1] = (int)string;
	regs.r[2] = 0x20180;   /* bits 7, 8, 17 */
	regs.r[3] = width * scale_x;
	regs.r[4] = lists_line_height * scale_y;
	regs.r[7] = strlen(string);
	os_swi(0x400a1+os_X,&regs);     /* Font_ScanString */

	n_chars = (char *)regs.r[1] - string;

	regs.r[1] = (int)string;
	regs.r[2] = 0x190;    /* bit 4, bit 7, bit 8 */
	regs.r[3] = x;
	regs.r[4] = y;
	regs.r[7] = n_chars;
	os_swi(0x40086+os_X,&regs);
}   /* end of paint_string */





void strcpy_printable(char *s1,char *s2, int n)
/*********************************************/
/* Copy, ensuring all characters are printable by font_paint */
{
	int  i;
	int  c;


	for(i=0; i<n; i++)
	{
		c = s1[i] = s2[i];
		if(c == 0)
			break;

		if(c < ' ')
		{
			if(c == '\r')
				s1[i] = ' ';  /* Incredimail encodes QP including CR at eol */
			else
				s1[i] = '#';
		}
	}
	s1[n-1] = 0;  /* ensure terminator */
}   /* end of strcpy_printable */



int font_find_variant(char *fontname, int charset, int width, int height)
/***********************************************************************/
{
	int  font = 0;
	os_regset regs;
	char buf[80];

	static char *encoding_name[] = {
		NULL, "Latin1", "Latin2", "Latin3", "Latin4", NULL, NULL, NULL,
		NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Latin9"
	};

	if(fontname[0] == 0)
		return(0);

	if((charset > 1) && (charset <= 15))
	{
		if(encoding_name[charset] != NULL)
		{
			sprintf(buf,"%s\\E%s",fontname,encoding_name[charset]);

			regs.r[0] = -1;
			regs.r[1] = (int)buf;
			regs.r[2] = width;
			regs.r[3] = height;
			regs.r[4] = 0;
			regs.r[5] = 0;
			if(os_swix(0x40081,&regs) == NULL)   /* Font_FindFont */
			{
				font = regs.r[0];
			}
		}
	}
	return(font);
}   /* end of font_find_variant */





#ifdef deleted
/* replaced by assember routine */

int standard_sorter1(const void *a1, const void *a2)
/*************************************************/
/* Sorter for newsgroups, more efficient than standard sorter */
{
	CARD *cptr1, *cptr2;
	int  i;

	cptr1 = (CARD *)&sort_card_file[(*((int *)a1) & IXLIST_MASK) << 2];
	cptr2 = (CARD *)&sort_card_file[(*((int *)a2) & IXLIST_MASK) << 2];

	/* sort on source */
	i = src_fr.cat_data[cptr1->source].position - src_fr.cat_data[cptr2->source].position;
	if(i != 0)
		return(i);

	/* sort on title */
	i = strcmp_lc(&cptr1->data[cptr1->d_title], &cptr2->data[cptr2->d_title]);
	if(i != 0)
		return(i);

	/* sort on date */
	return( (cptr1->date_box & DATE_MASK) - (cptr2->date_box & DATE_MASK));

}   /* end of standard_sorter */

#endif



int compare_domains(char *d1, char *d2)
/*************************************/
{
	char *p;
	char *b;
	int  i;

	char buf1[256];
	char buf2[256];

	b = buf1;
	p = d1 + strlen(d1);

	i = 0;
	while(--p >= d1)
	{
		if((*p == '.') || (*p == '@'))
		{
			memcpy(b,p+1,i);
			b += i;
			i = 0;
		}
		i++;
	}
	*b = 0;


	b = buf2;
	p = d2 + strlen(d2);

	i = 0;
	while(--p >= d2)
	{
		if((*p == '.') || (*p == '@'))
		{
			memcpy(b,p+1,i);
			b += i;
			i = 0;
		}
		i++;
	}
	*b = 0;

	return(strcmp_lc(buf1,buf2));
}   /* end of compare_domains */



int general_sorter(const void *a1, const void *a2)
/************************************************/
{
	CARD *cptr1, *cptr2;
	int  level;
	int  i;
	short *s1, *s2;
	int  x1, x2;
	CAT *cat1, *cat2;
	char *p1, *p2;

	cptr1 = (CARD *)&sort_card_file[(*((int *)a1) & IXLIST_MASK) << 2];
	cptr2 = (CARD *)&sort_card_file[(*((int *)a2) & IXLIST_MASK) << 2];

	for(level=0; level<n_sort_levels; level++)
	{
		switch(sort_field[level])
		{
		case CS_DATE:
			i = (cptr2->date_box & DATE_MASK) - (cptr1->date_box & DATE_MASK);
			if(i == 0)
			{
				/* datestamps are the same, differentiate by length, to give consistent
				   ordering */
				i = (cptr1->alength & ART_LENGTH_MASK) - (cptr2->alength & ART_LENGTH_MASK);
				if(i==0)
					i = cptr1->addr - cptr2->addr;

			}
			break;

		case CS_CAT1:
			s1 = (short *)(&cptr1->data[(cptr1->n_refs & N_REFS_MASK) * 4]);
			s2 = (short *)(&cptr2->data[(cptr2->n_refs & N_REFS_MASK) * 4]);

			if(((cptr1->n_cats & N_CATS_MASK) == 0) && ((cptr2->n_cats & N_CATS_MASK) == 0))
			{
				i = 0;
				break;
			}

			if((cptr1->n_cats & N_CATS_MASK) > 0)
				x1 = s1[0];
			else
			{
				x1 = 0;
			}

			if((cptr2->n_cats & N_CATS_MASK) > 0)
				x2 = s2[0];
			else
			{
				x2 = 0;
			}

			cat2 = &cat_data[x2];
			cat1 = &cat_data[x1];

			if(sort_field[level]==CS_CAT0)
			{
				/* look at parents */
				if(cat2->parent > 0)
					cat2 = &cat_data[cat2->parent];
				if(cat1->parent > 0)
					cat1 = &cat_data[cat1->parent];
			}
			i = cat2->position - cat1->position;  /* position in the hierarchy */
			if(i == 0)
				i = x2 - x1;   /* in case we have two categories with the same position */
			break;

		case CS_AUTHOR:
			i = strcmp_lc(&cptr2->data[cptr2->d_author], &cptr1->data[cptr1->d_author]);
			break;

		case CS_TITLE:
			i = strcmp_lc(&cptr2->data[cptr2->d_title], &cptr1->data[cptr1->d_title]);
			break;

		case CS_SOURCE:
			if(cptr2->source > max_source_cat)
				x2 = 0;
			else
				x2 = src_fr.cat_data[cptr2->source].position;

			if(cptr1->source > max_source_cat)
				x1 = 0;
			else
				x1 = src_fr.cat_data[cptr1->source].position;

			i = x2 - x1;
			break;

		case CS_LENGTH:
			x1 = (cptr1->alength & ART_LENGTH_MASK) - card_size(cptr1) - sizeof(ART_HEAD);
			x2 = (cptr2->alength & ART_LENGTH_MASK) - card_size(cptr2) - sizeof(ART_HEAD);
			i = x1 - x2;
			break;

		case CS_STATUS:
			/* NOTE: include STATUS_BIT_OG */
			i = status_order[cptr2->status & 0x1f] - status_order[cptr1->status & 0x1f];
			if(i==0)
				i = (cptr1->date_box & DATE_MASK) - (cptr2->date_box & DATE_MASK);
			break;

		case CS_MONTH:
			i = decode_month(cptr1->date_box & DATE_MASK) - decode_month(cptr2->date_box & DATE_MASK);
			break;

		case CS_YEAR:
			i = ((cptr1->date_box & DATE_MASK) >> 18) - ((cptr2->date_box & DATE_MASK) >> 18);
			break;

		case CS_ADDRCOLR:
			i = ((cptr1->alength & COLR_MASK) - (cptr2->alength & COLR_MASK));
			break;

		case CS_DOMAIN:
			p1 = strrchr(&cptr1->data[cptr1->d_author],'@');
			p2 = strrchr(&cptr2->data[cptr2->d_author],'@');
			if(p2==NULL)
			{
				if(p1==NULL)
					i = 0;
				else
					i = 1;
				break;
			}
			if(p1==NULL)
			{
				i = -1;
				break;
			}

			/* compare strings, last part first */
			i = compare_domains(p2,p1);
			break;

		case CS_SCORE:    /* Score */
			i = cptr1->score - cptr2->score;
			break;

		default:
			i = 0;
			break;
		}

		if(i != 0)
		{
			if(sort_flags[level] & 1)
				return(i);
			else
				return(-i);     /* difference found */
		}
	}

	i = 0;
	if(sort_field[n_sort_levels-1] != CS_DATE)
	{
		/* everything equal, so sort by date, if last sort level is not already "date" */
		i = (cptr2->date_box & DATE_MASK) - (cptr1->date_box & DATE_MASK);
		if(i == 0)
			i = cptr1->addr - cptr2->addr;
	}

	return(i);    /* no difference */
}   /* end of general_sorter */




void index_diff_levels(FOLDREC *fr)
/*********************************/
/* Set difference level information in an index list,
   Mark threads which contain unread artilces */
{
	int  ix;
	unsigned int *ixlist;
	int  n_ixlist;
	int  level;
	CARD *cptr1;
	CARD *cptr2;
	int  x1, x2;
	short *s1, *s2;
	int  duplicates=0;
	unsigned int *ixlist2;
	unsigned int *ixlist3;
	int  ix2;
	int  ix3;
	int  i;
	int  thread_level;
	int  thread_start;
	int  any_unread = 0;
	int  high_level;
	char *p1, *p2;

	ixlist = fr->ixlist;

	if(fr->n_entries_tot < fr->n_entries)
		fr->n_entries_tot = fr->n_entries;

	fr->n_entries = fr->n_entries_tot;
	n_ixlist = fr->n_entries_tot;

	/*   thread_level = fr->thread_level; */
	thread_level = fr->display_levels - 1;
	if(thread_level < 0)
		thread_level = 0;
	thread_start = 0;

	cptr1 = (CARD *)&sort_card_file[(ixlist[0] & IXLIST_MASK) << 2];

	ixlist[0] = ixlist[0] & IXLIST_MASK;   /* start with level 0 difference */

	for(ix=1; ix<n_ixlist; ix++)
	{
		cptr2 = (CARD *)&sort_card_file[(ixlist[ix] & IXLIST_MASK) << 2];
		if(cptr1==cptr2)
			duplicates++;

		/* any unread or locked articles ? */
		if((cptr1->status & STATUS_MASK) < hide_level)
			any_unread=1;

		for(level=0; level<fr->display_levels; level++)
		{
			if(sort_flags[level] & 2)
				continue;    /* don't fold at this level */

			switch(sort_field[level])
			{
			case CS_DATE:
				x1 = (cptr1->date_box + timezone_offset) & 0x3ffff;
				x2 = (cptr2->date_box + timezone_offset) & 0x3ffff;
				if((x1 / 576) == (x2 / 576))
					continue;
				break;

			case CS_CAT1:
			case CS_CAT0:
				s1 = (short *)(&cptr1->data[(cptr1->n_refs & N_REFS_MASK) * 4]);
				s2 = (short *)(&cptr2->data[(cptr2->n_refs & N_REFS_MASK) * 4]);

				if((cptr1->n_cats & N_CATS_MASK) > 0) x1 = s1[0];
				else x1 = 0;
				if((cptr2->n_cats & N_CATS_MASK) > 0) x2 = s2[0];
				else x2 = 0;

				if(sort_field[level]==CS_CAT0)
				{
					/* look at parents */
					if((i = cat_data[x1].parent) != 0)
						x1 = i;
					if((i = cat_data[x2].parent) != 0)
						x2 = i;
				}
				if(x1 == x2)
					continue;
				break;

			case CS_AUTHOR:
				if(strcmp_lc(&cptr2->data[cptr2->d_author], &cptr1->data[cptr1->d_author]) == 0)
					continue;
				break;

			case CS_TITLE:
				if(strcmp_lc(&cptr2->data[cptr2->d_title], &cptr1->data[cptr1->d_title]) == 0)
					continue;
				break;

			case CS_SOURCE:
				if(cptr2->source == cptr1->source)
					continue;
				break;

			case CS_LENGTH:
#ifdef deleted
				x1 = (cptr1->alength & ART_LENGTH_MASK) - card_size(cptr1);
				x2 = (cptr2->alength & ART_LENGTH_MASK) - card_size(cptr2);
				if(x1 == x2)
					continue;
#endif
				break;

			case CS_BOX:
				if((cptr2->date_box >> 26) == (cptr1->date_box >> 26))
					continue;
				break;

			case CS_STATUS:
				/* NOTE: include STATUS_BIT_OG */
				if(status_order[cptr1->status & 0x1f] == status_order[cptr2->status & 0x1f])
					continue;
				break;

			case CS_MONTH:
				if(decode_month(cptr2->date_box & DATE_MASK) == decode_month(cptr1->date_box & DATE_MASK))
					continue;
				break;

			case CS_YEAR:
				if(((cptr2->date_box & DATE_MASK) >> 18) == ((cptr1->date_box & DATE_MASK) >> 18))
					continue;
				break;

			case CS_ADDRCOLR:
				if((cptr2->alength & COLR_MASK) == (cptr1->alength & COLR_MASK))
					continue;
				break;

			case CS_DOMAIN:
				p2 = strrchr(&cptr2->data[cptr2->d_author],'@');
				p1 = strrchr(&cptr1->data[cptr1->d_author],'@');
				if((p1==NULL) && (p2 == NULL))
					continue;

				if((p1==NULL) || (p2==NULL))
					break;

				if(strcmp_lc(p2,p1) == 0)
					continue;
				break;

			case CS_SCORE:    /* Score */
				if(cptr1->score == cptr2->score)
					continue;
				break;

			default:
				break;
			}

			break;
		}

		if(level <= thread_level)
		{
			/* start of new thread, any unread threads ? */
			if(any_unread)
			{
				ixlist[thread_start] |= IXLIST_MARKED;
			}

			thread_start = ix;
			any_unread = 0;
		}
		ixlist[ix] = (ixlist[ix] & IXLIST_MASK) + (level << IXLEV);

		cptr1 = cptr2;
	}

	/* look for any unread or locked articles in this thread */
	if((n_ixlist > 0) && ((cptr1->status & STATUS_MASK) < hide_level))
		any_unread=1;

	if(any_unread)
		ixlist[thread_start] |= IXLIST_MARKED;


	if(duplicates)
	{
		/* remove duplicates from the list */
		ixlist2 = malloc((n_ixlist-duplicates)*sizeof(int));
		if(ixlist2 == NULL)
		{
			malloc_err(16);
			return;
		}

		ix2=1;
		ixlist2[0] = ixlist[0];
		for(ix=1; ix<n_ixlist; ix++)
		{
			if((ixlist[ix] & IXLIST_MASK) != (ixlist[ix-1] & IXLIST_MASK))
			{
				ixlist2[ix2++] = ixlist[ix];
			}
			else
			{
				duplicates--;
			}
		}
		free(fr->ixlist);
		fr->n_entries_max = n_ixlist-duplicates;
		fr->ixlist = ixlist2;
		fr->n_entries = ix2;
		fr->n_entries_tot = ix2;
	}

	if(fr->hide_read)
	{
		/* put threads with read articles at the end of the list,
		   and set fr->n_entries to end of unread section of the list */
		n_ixlist = fr->n_entries;
		ixlist2 = malloc(n_ixlist*sizeof(int));
		ixlist3 = malloc(n_ixlist*sizeof(int));
		ix2 = ix3 = 0;

		high_level = 0;
		for(ix=0; ix<n_ixlist; ix++)
		{
			if((ixlist[ix] >> IXLEV) <= thread_level)
			{
				any_unread = ixlist[ix] & IXLIST_MARKED;
			}

			if(any_unread)
			{
				if(high_level < thread_level)
				{
					ixlist2[ix2++] = (ixlist[ix] & ~IXLIST_LEVEL) | (high_level << IXLEV);
					high_level = thread_level;
				}
				else
				{
					ixlist2[ix2++] = ixlist[ix];
				}
			}
			else
			{
				/* remember the highest level in the articles being skipped, use
				   this for the start of the next displayed thread */
				ixlist3[ix3++] = ixlist[ix];
				if((level = (ixlist[ix] >> IXLEV)) < high_level)
					high_level = level;
			}
		}

		memcpy(ixlist,ixlist2,ix2 * sizeof(int));
		memcpy(&ixlist[ix2],ixlist3,ix3 * sizeof(int));
		fr->n_entries = ix2;

		free(ixlist2);
		free(ixlist3);
	}

	return;   /* no difference */
}   /* end of index_diff_levels */




int duplicate_sorter(const void *a1, const void *a2)
/**************************************************/
/* Sort by date, msgid, and title */
{
	CARD *cptr1, *cptr2;
	int  i;

	cptr1 = (CARD *)&sort_card_file[(*((int *)a1) & IXLIST_MASK) << 2];
	cptr2 = (CARD *)&sort_card_file[(*((int *)a2) & IXLIST_MASK) << 2];

	i = src_fr.cat_data[cptr1->source].position - src_fr.cat_data[cptr2->source].position;
	if(i != 0)
		return(i);

	i = (cptr1->date_box) - (cptr2->date_box);
	if(i != 0)
		return(i);

	i = (cptr1->msgid - cptr2->msgid);
	if(i != 0)
		return(i);

	i = strcmp_lc(&cptr2->data[cptr1->d_title], &cptr1->data[cptr2->d_title]);
	return(i);
}   /* end of duplicate_sorter */



void sort_for_duplicates(FOLDREC *fr)
/*******************************/
{
	sort_card_file = get_cardfile2(fr);
	qsortG(fr->ixlist,fr->n_entries,sizeof(int *),duplicate_sorter);
}   /* end of sort_for_duplicates */




void sort_index_list(FOLDREC *fr, int start_line)
/***********************************************/
/* Sort an index depending on the sort conditions in sort_field */
{
	int  level;
	unsigned int *ixlist;
	int  n_ixlist;
	DISPLAY_LEVEL *dlevel;

	if(fr->display->name[0] == 0)
		return;   /* display data not valid */

	sort_card_file = get_cardfile2(fr);

	ixlist = fr->ixlist;
	if(ixlist == NULL)
		return;

	n_ixlist = fr->n_entries_tot;

	/* set up sort control data */
	for(level=0; level<4; level++)
	{
		dlevel = &fr->display->level[level];
		if(dlevel->sort_field == 0)
			break;

		sort_field[level] = dlevel->sort_field;
		sort_field2[level] = dlevel->sort_field2;
		sort_flags[level] = dlevel->sort_reverse;
		if(dlevel->display_items[0]==0)
			sort_flags[level] |= 2;     /* don't break at this level */
	}
	n_sort_levels = level;

	fr->display_levels = n_sort_levels - 1;
	/*
	   if(sort_field[level-1] == CS_DATE)
	   {
	      fr->display_levels--;    don't break display on final date sort
	   }
	*/

	if(level == 0)
		return;    /* no sort data */


	max_source_cat = src_fr.cat_header.max_cat;

	if((n_sort_levels == 3) && (memcmp(sort_field,std1_sort_field,n_sort_levels*sizeof(char))==0) &&
			(memcmp(sort_flags,std1_sort_flags,n_sort_levels*sizeof(char))==0))
	{
		sorter_setup();
		qsortG(ixlist,n_ixlist,sizeof(int *),standard_sorter2);
	}
	else
		qsortG(ixlist,n_ixlist,sizeof(int *),general_sorter);

	/* set difference levels for each item in the index */
	index_diff_levels(fr);

	redraw_list_lines(fr,start_line);

	fr->threading_done = 0;
}   /* end of sort_index_list */



void index_diff_levels_expire(FOLDREC *fr)
/****************************************/
/* Used for sorting the main article list when expiring threads */
{
	sort_field[0] = CS_BOX;
	sort_field[1] = CS_SOURCE;
	sort_field[2] = CS_TITLE;
	sort_field[3] = CS_DATE;
	memset(sort_field2,0,sizeof(sort_field2));
	n_sort_levels = 4;

	/* set difference levels for each item in the index */
	index_diff_levels(fr);
}   /* end of index_diff_levels_expire */



void display_icon(int status, int x, int y, int control)
/******************************************************/
/* Display a sprite during a redraw
   Control: 1=status plus */
{
	int sprite_iconblk[12];

	/* restrict status icons for newsgroups and boxes */
	static char icon2[] = {0,1,2,3,7,7,7,7, 0,1,0xa,3,7,7,7,7};  /* for newsgroups */
	static char icon3[] = {1,1,3,3,7,7,7,7, 1,1,7,3,7,7,7,7};   /* for Boxes List */
	static char icon4[] = {1,1,3,3,4,5,6,6, 1,1,0xa,3,4,5,6,0x17};   /* for Boxes List, showing locked states */

	if(control == 0)
	{
		status = icon2[status & STATUS_MASK2];
	}
	else if(control == 2)
	{
		if(options.box_show_locked)
			status = icon4[status & STATUS_MASK2];
		else
			status = icon3[status & STATUS_MASK2];
	}
	else if(control == 3)
	{
		/* external Box, use special status symbol */
		status = 0x90;  /* black address book icon */
	}

	sprintf((char *)&sprite_iconblk[8],"stat_%.2x",status);

	sprite_iconblk[0] = x;
	sprite_iconblk[1] = y;
	sprite_iconblk[2] = x+40;
	sprite_iconblk[3] = y+36;
	sprite_iconblk[4] = 0x11a;  /* bits 1, 3, 4, 8 */
	sprite_iconblk[5] = (int)&sprite_iconblk[8];
	sprite_iconblk[6] = (int)status_sprites;
	sprite_iconblk[7] = 7;  /* length of sprite name */

	wimp_ploticon((wimp_icon *)&sprite_iconblk);

	/* add a locked/marked indication dot */

}   /* end of display_icon */


void display_icon2(char *name, int x, int y)
/******************************************/
/* Display a sprite during a redraw
   Control: 1=status plus */
{
	int sprite_iconblk[12];

	sprintf((char *)&sprite_iconblk[8],"%s",name);

	sprite_iconblk[0] = x;
	sprite_iconblk[1] = y;
	sprite_iconblk[2] = x+40;
	sprite_iconblk[3] = y+36;
	sprite_iconblk[4] = 0x11a;  /* bits 1, 3, 4, 8 */
	sprite_iconblk[5] = (int)&sprite_iconblk[8];
	sprite_iconblk[6] = (int)status_sprites;
	sprite_iconblk[7] = strlen(name);  /* length of sprite name */

	wimp_ploticon((wimp_icon *)&sprite_iconblk);

}   /* end of display_icon2 */





void display_tick(int x, int y, int invert)
/*****************************************/
{
	int sprite_iconblk[12];

	strcpy((char *)&sprite_iconblk[8],"\200");   /* tick sprite */
	sprite_iconblk[0] = x;
	sprite_iconblk[1] = y;
	sprite_iconblk[2] = x+40;
	sprite_iconblk[3] = y+36;
	if(invert)
		sprite_iconblk[4] = 0x20011a;
	else
		sprite_iconblk[4] = 0x11a;  /* bits 1, 3, 4, 8 */
	sprite_iconblk[5] = (int)&sprite_iconblk[8];
	sprite_iconblk[6] = 1;   /* wimp sprites */
	sprite_iconblk[7] = 1;  /* length of sprite name */

	wimp_ploticon((wimp_icon *)&sprite_iconblk);
}   /* end of display_tick */



static void set_text_colours(int reverse, int colr)
/*************************************************/
{
	int  colour;

	if(reverse)
	{
		os_swi4(0x4074f,0,0x55555500,0xffffff00,8);  /* ColourTrans_SetFonTColours */
		set_colour(WHITE,0,0);
	}
	else
	{
		colour = colr_tab[colr];
		os_swi4(0x4074f,0,alist_backg_rgb,colour,14);  /* ColourTrans_SetFonTColours */
		set_colour(colour,0,0);
	}
}   /* end of set_text_colours */





void display_line(FOLDREC *fr, int x, int y, CARD *cp, int level, int ix, wimp_redrawstr *r, int reverse, int colr, int colr2)
/*******************************************************************************************************/
{
	CARD *cp2;
	int offset;
	short *sp;
	int width;
	int width_chars;
	int n_chars;
	char *ds;
	DISPLAY_LEVEL *dlevel;
	int  i;
	int  count;
	int  min;
	char *p;
	int  cat;
	int  x2;
	int  y2;
	int  y_mid;
	int  status2;
	int  status_icon = -1;
	int  status_icon_control;
	int  status_offset;
	int  status_replied;
	int  status_marked;
	int  status_score;
	int  status_attachments=0;
	int  colour_flag = 0;
	int  dont_list;         /* don't add to file */
	int  right_justify;
	int  level2;
	int  lev;
	int  thread_offset = 0;
	char format_buf[8];
	char vbars[IXLIST_N_LEVELS];
	int  vcount_down[IXLIST_N_LEVELS];
	int  threading=0;
	int  thread_right_margin;
	static int indent_amount = 24;

	/* set bit 0 if fetched, 1 if locked, 2 if marked,  */
	static char marked_bits[16] = {
		0,0,1,0,4,2,0,0,
		0,0,0,0,4,2,0,0
	};

	FILE *f = NULL;

	os_regset regs;
	char buf[300];

	if(x == -1)
	{
		f = (FILE *)r;   /* write out to a file */
	}

	level2 = fr->ixlist[ix] >> IXLEV;
	if(level2 < level)
		level2 = level;

	if(level2 >= fr->display_levels)
		threading = fr->display->threading;

	if((threading) && (level >= fr->display_levels))
	{
		lev = (fr->ixlist[ix] >> IXLEV) - fr->display_levels;

		/* treat extra levels just as indents */
		if(lev > 0)
			thread_offset = lev * indent_amount;

		/* any vertical lines to link indentation levels ? */
		memset(vbars,0,sizeof(vbars));
		memset(vcount_down,0,sizeof(vcount_down));

		for(i=ix+1; i<fr->n_entries; i++)
		{
			lev = fr->ixlist[i] >> IXLEV;
			if(vcount_down[lev]==0)
				vcount_down[lev] = i;

			if(lev < 2)
				break;
		}

		for(lev=2; (lev<=level2); lev++)
		{
			if((vcount_down[lev+1] != 0) &&
					((vcount_down[lev]==0) || (vcount_down[lev+1] < vcount_down[lev])))
			{
				if(lev == level2)
					vbars[lev] = 2;
				else
					vbars[lev] = 1;
			}
			else if((lev+1) == level2)
			{
				vbars[lev] = 3;
			}
		}
	}


	dlevel = &fr->display->level[level];

	for(ds = dlevel->display_items; *ds != 0; ds += 3)
	{
		buf[0] = 0;
		right_justify = 0;

		offset = ds[1]*5;
		if(offset <= 0)
			offset = 0;

		width = ds[2]*5;
		if(width <= 0)
			width = 2000;
		else
			width -= offset;

		if(ds[3] != 0)
		{
			thread_right_margin = 0;
			if(ds[6] == 0)
				thread_right_margin = ds[4] - 8;
			else if(ds[9] == 0)
				thread_right_margin = ds[7] - 8;
			thread_right_margin *= 5;

			offset += thread_offset;  /* but not for the last item */

			if(thread_right_margin > 0)
			{
				if((offset+width) > thread_right_margin)
				{
					width = thread_right_margin - offset;
				}
			}
		}

		if((colour_flag > 0) && (f == NULL))
			set_text_colours(reverse,0);

		dont_list = 0;
		switch(ds[0])
		{
		case 1:    /* date */
			if((reverse==0) && (colr2 > 0))
			{
				set_text_colours(reverse,colr2);
				colour_flag = 1;
			}
			strcpy(buf,decode_date(cp->date_box,0));
			break;

		case 2:   /* 1st category (single part) */
		case 9:   /* 1st category (parent) */
			sp = (short *)(&cp->data[(cp->n_refs & N_REFS_MASK) * 4]);

			if(((cp->n_cats & N_CATS_MASK)==0) || ((cat = sp[0]) == 0))
				strcpy(buf,"(None)");
			else
			{
				if((ds[0] == CS_CAT0) && (cat_data[cat].parent != 0))
					cat = cat_data[cat].parent;
				strcpy(buf,category_name(&cty_fr,cat));
			}
			break;

		case 3:    /* author (full) */
		case 13:   /* author (name only) */
			if((reverse==0) && (colr > 0))
			{
				set_text_colours(reverse,colr);
				colour_flag = 1;
			}

			p = &cp->data[cp->d_author];
			while((*p <= ' ') && (*p != 0))
				p++;

			strcpy_printable(buf,p,sizeof(buf));

			if((ds[0] == 13) && isalnum(buf[0]))
			{
				p = buf;
				while((*p != '<') && (*p != 0))
					p++;
				*p = 0;
			}
			break;

		case 10:    /* domain */
			p = strrchr(&cp->data[cp->d_author],'@');
			if(p == NULL)
				buf[0] = 0;
			else
				strcpy_printable(buf,p,sizeof(buf));
			break;


		case 4:     /* title */
			strcpy_printable(buf,&cp->data[cp->d_title],sizeof(buf));
			colour_flag = 1;
			break;

		case 5:   /* source */
			strcpy(buf,expand_source(cp->source,2));
			break;


			p = category_name(&src_fr,cp->source);
			if(memcmp(p,"mail.",5)==0)
			{
				strcpy(buf,expand_source(cp->source,2));
			}
			else
			{
				strcpy(buf,p);
			}
			break;

		case 6:   /* article length */
			sprintf(buf,"%d",(cp->alength & ART_LENGTH_MASK) - card_size(cp) - sizeof(ART_HEAD));
			break;

		case 8:   /* month */
			strcpy(buf,decode_date(cp->date_box,0x10));
			break;

		case 18:  /* year */
			sprintf(buf,"Year %.4d",((cp->date_box & DATE_MASK) >> 18) + 1900);
			break;

		case 7:  /* status icon */
		case 15:  /* status plus */
			if(f != NULL)
				break;

			min = status_priority[STATUS_READ];
			i = ix;
			status_replied = 0;
			status_attachments = 0;
			status_score = 0;
			status_marked = 0;
			for(;;)
			{
				cp2 = cardfile_cptr(fr,i);
				if(status_priority[status2 = (cp2->status & STATUS_MASK2)] < min)
					min = status_priority[status2];

				if(status2 != STATUS_FETCHING)
					status_replied |= status2;

				status_marked |= marked_bits[status2];

				if(cp2->n_cats & STATUS_BIT_ATTACH)
					status_attachments = 1;

				if(cp2->score > status_score)
					status_score = cp2->score;

				i++;

				lev = fr->ixlist[i] >> IXLEV;
				if(lev > fr->display_levels)
					lev = fr->display_levels;
				if((i >= fr->n_entries) || (lev <= level))
					break;
			}
			status_icon = priority_to_status[min];

			/* promote New + Fetched in a thread to New/Fetched, so NEW doesn't
			   mask the FETCHED part of UNREAD+FETCHED */
			if((status_icon == STATUS_NEW) && (status_marked & 1))
				status_icon = STATUS_NEW-1;

			/* check for case of STATUS_FETCHING */
			if(status_replied & STATUS_BIT_REPLIED)
				status_icon |= 0x10;

			status_icon_control = 0;
			if(ds[0] == CS_STATUS)
				status_icon_control = 1;
			status_offset = offset;
			break;

		case 16:   /* number of entries unread */
			count = 0;
			i = ix;
			while((i < fr->n_entries) && ((i==ix) || ((fr->ixlist[i] >> IXLEV) > level)))
			{
				cp2 = cardfile_cptr(fr,i);
				if(((cp2->status & STATUS_MASK) <= STATUS_UNREAD) &&
						((cp2->status & STATUS_MASK2) != STATUS_FETCHING))
					count++;
				i++;
			}
			if(standard_font >= 0)
			{
				sprintf(buf,"%d",count);
				right_justify = 1;
			}
			else
			{
				sprintf(format_buf,"%%%dd",width/16);
				sprintf(buf,format_buf,count);
			}
			break;

		case 11:   /* REPLY indicator */
			dont_list=1;
			if(cp->status & STATUS_BIT_RE)
				strcpy(buf,"(R)");
			break;

		case 12:   /* number of entries */
			i = ix + 1;
			while((i < fr->n_entries) && ((fr->ixlist[i] >> IXLEV) > level))
			{
				i++;
			}
			if(standard_font >= 0)
			{
				sprintf(buf,"%d",i-ix);
				right_justify = 1;
			}
			else
			{
				sprintf(format_buf,"%%%dd",width/16);
				sprintf(buf,format_buf,i-ix);
			}
			break;

		case 14:  /* attachment */
			dont_list=1;
			if((cp->n_cats & STATUS_BIT_ATTACH) || (status_attachments))
				strcpy(buf,"+");
			break;

		case 17:  /* score */
			sprintf(buf,"%d",cp->score);
			break;
		}

		if(buf[0] == 0)
			continue;    /* nothing to print */

		if((f != NULL) && (dont_list==0))
		{
			/* write to file */
			for(i=0; i<(level*3); i++)
				fputc(' ',f);
			fprintf(f,"%s\t",buf);
		}
		else if(width > 0)
		{
			if(standard_font >= 0)
			{
				regs.r[0] = standard_font;
				regs.r[1] = (int)buf;
				regs.r[2] = 0x20180;   /* bits 7, 8, 17 */
				regs.r[3] = width * scale_x;
				regs.r[4] = lists_line_height * scale_y;
				regs.r[7] = strlen(buf);
				os_swi(0x400a1+os_X,&regs);     /* Font_ScanString */

				n_chars = (char *)regs.r[1] - buf;

				x2 = x + offset;
				if(right_justify)
				{
					x2 = x2 + width - 4  - (regs.r[3] / scale_x);
				}

				regs.r[1] = (int)buf;
				regs.r[2] = 0x190;    /* bit 4, bit 7, bit 8 */
				regs.r[3] = x2;
				regs.r[4] = y;
				regs.r[7] = n_chars;
				os_swi(0x40086+os_X,&regs);
			}
			else
			{
				width_chars = width / 16;
				buf[width_chars] = 0;
				bbc_move(x + offset, y-4);
				printf("%s",buf);
			}
		}
	}

	if(status_icon >= 0)
	{
		if(threading)
		{
			if((colour_flag > 0) && (f == NULL))
				set_text_colours(reverse,0);

			for(i=fr->display_levels; i<=level2; i++)
			{
				if((vbars[i] >= 1) && (vbars[i] <= 3))
				{
					y2 = y+lists_font_offset-lists_line_height;
					y_mid = y+status_sprite_offset+16;
					x2 = x+status_offset-thread_offset+(i-fr->display_levels)*indent_amount+16;
					x2 -= 4;

					if(i == (level2-1))
					{
						bbc_move(x2,y_mid);
						bbc_draw(x2+24,y_mid);
					}

					if(vbars[i]==2)
						bbc_move(x2,y_mid);
					else
						bbc_move(x2,y2+lists_line_height-2);

					if(vbars[i]==3)
						bbc_draw(x2,y_mid);
					else
						bbc_draw(x2,y2);

				}
			}
		}

		y2 = y - r->box.y1 + r->scy + status_sprite_offset;
		display_icon(status_icon,status_offset,y2,status_icon_control);

		if(status_score > options.filter_show_tick)
		{
			display_tick(status_offset-30,y2,reverse);
		}

		/* add locked or marked indicator to a thread */
		if((level==(fr->display_levels-1) && (status_icon & STATUS_MASK) <= STATUS_MARKED))
		{
			if(status_marked & 2)
			{
				display_icon2("locked",status_offset-14,y2+6);
			}
			if((status_marked & 4) && (status_icon & STATUS_MASK != STATUS_LOCKED))
			{
				display_icon2("marked",status_offset-14,y2-6);
			}
		}
	}

}   /* end of display_line */





void display_exit()
/*****************/
{
	int  font_opt;

	for(font_opt=0; font_opt<N_FONTS; font_opt++)
	{
		if(fontn[font_opt] > 0)
			os_swi1(0x40082+os_X,fontn[font_opt]);   /* Font_LoseFont */
	}
}   /* end of display_exit */



void init_display()
/*****************/
{

	menu_colours = menu_new("Colours",menustr_colours);
	wmenu_colours = (wimp_menustr *)menu_syshandle(menu_colours);

	text_window_template = template_syshandle("Text");

	cat_data = cty_fr.cat_data;

	init_sprites();
	init_display_edit();
}   /* end of init_display */


