/*  MikMod example player
	(c) 1998, 1999 Miodrag Vallat and others - see file AUTHORS for
	complete list.

	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 2 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 to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
	02111-1307, USA.
*/

/*==============================================================================

  $Id: display.c,v 1.39 1999/07/11 17:49:33 miod Exp $

  Display routines for the different panels and the playlist menu

==============================================================================*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#if !defined(__OS2__)&&!defined(__EMX__)
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifndef GWINSZ_IN_SYS_IOCTL
#include <termios.h>
#endif
#endif

#include "display.h"
#include <mikmod.h>
#include "player.h"
#include "mutilities.h"
#include "mwindow.h"
#include "mmenu.h"
#include "mdialog.h"
#include "mconfedit.h"

/*========== Display layout */

/* minimum width of one column */
#define MINWIDTH (20)
/* minimum width of second column */
#define MINVISIBLE (10)
/* half width */
int halfwidth;
/* format used for message/banner lines : like "%-80.80s" */
char fmt_fullwidth[20];
/* format used for sample/instrument lines - like "%3i %-36.36s"
   (the number being halfwidth-4) */
char fmt_halfwidth[20];
/* start of information panels */
#define PANEL_Y 7

typedef struct {
	MMENU *menu;
	int *actLine;
} MENU_DATA;

/*========== Variables */

extern BOOL quiet,semiquiet;
extern MODULE *mf;

static int cur_display=DISPLAY_SAMPLE,old_display=DISPLAY_SAMPLE;

/* first line of displayed information in the panels */
static int first_help=0,first_sample=0,first_inst=0,first_comment=0,first_list=0;

/* computes printf templates when screen size changes, so that two-column
   display fills the screen */
static void setup_printf(void)
{
	int maxx,winy;

	win_get_size_root(&maxx,&winy);
	if (maxx>MAXWIDTH) maxx=MAXWIDTH;

	halfwidth=maxx>>1;
	if (halfwidth<MINWIDTH) halfwidth=MINWIDTH;
#ifdef HAVE_SNPRINTF
	snprintf(fmt_fullwidth,20,"%%-%d.%ds",maxx,maxx);
	snprintf(fmt_halfwidth,20,"%%3i %%-%d.%ds",halfwidth-4,halfwidth-4);
#else
	sprintf(fmt_fullwidth,"%%-%d.%ds",maxx,maxx);
	sprintf(fmt_halfwidth,"%%3i %%-%d.%ds",halfwidth-4,halfwidth-4);
#endif
}

static BOOL remove_msg=0;
static time_t start_time;
static char old_message[121];

/* displays a warning message on the top right corner of the display */
void display_message(char *str)
{
	int len=strlen(str);

	if (quiet) return;
	if (len>120) len=120;
	strncpy(old_message,str,len);
	old_message[len]='\0';

	win_attrset(A_REVERSE);
	win_print_root(strlen(mikversion)+1,0,str);
	win_attrset(A_NORMAL);

	remove_msg=1;
	start_time=time(NULL);
}

/* changes the warning message */
static void update_message(void)
{
	if ((remove_msg)&&(old_message[0])) {
		win_attrset(A_REVERSE);
		win_print_root(strlen(mikversion)+1,0,old_message);
		win_attrset(A_NORMAL);
	}
}

/* removes the warning message */
static void remove_message(void)
{
	if (remove_msg) {
		time_t end_time=time(NULL);
		if (end_time-start_time>=6) {
			win_clrtoeol_root(strlen(mikversion)+1,0);
			remove_msg=0;
		}
	}
}

/* display a banner/message from line skip, at position origin
   returns updated skip value if it is out of bounds and would prevent the
   message from being seen. */
static int display_banner(char* banner,BOOL root,int origin,int skip,BOOL wrap)
{
	char *buf=banner,str[MAXWIDTH+1];
	int i,n,t,winx,winy;

	if (root)
		win_get_size_root(&winx,&winy);
	else
		win_get_size(&winx,&winy);
	if (winx<5) return skip;

	/* count message lines */
	for (t=0;*buf;t++) {
		n=0;
		while ((((n<winx)&&(n<MAXWIDTH))||(!wrap))&&
			   (*buf!='\r')&&(*buf!='\n')&&(*buf))
			buf++,n++;
		if ((*buf=='\r')||(*buf=='\n')) buf++;
	}

	/* update skip value */
	if (skip<0) skip=0;
	if (skip+winy-origin>t) skip=t-winy+origin;
	if (skip<0) skip=0;
	if (t-skip+origin>winy) t=winy-origin+skip;

	/* skip first lines */
	buf=banner;
	for (i=0;i<skip && i<t;i++) {
		n=0;
		while ((((n<winx)&&(n<MAXWIDTH))||(!wrap))&&
		      ((*buf!='\r')&&(*buf!='\n')&&(*buf)))
			buf++,n++;
		if ((*buf=='\r')||(*buf=='\n')) buf++;
	}

	/* display lines */
	for (i=skip;i<t;i++) {
		for (n=0;(((n<winx)&&(n<MAXWIDTH))||(!wrap))&&
		        (*buf!='\r')&&(*buf!='\n')&&(*buf);buf++) {
			if (*buf<' ') str[n]=' ';
			else str[n]=*buf;
			if (n<MAXWIDTH) n++;
		}
		if ((*buf=='\r')||(*buf=='\n')) buf++;
		if (n) {
			str[n]='\0';
#ifdef HAVE_SNPRINTF
			snprintf(storage,STORAGELEN,fmt_fullwidth,str);
#else
			sprintf(storage,fmt_fullwidth,str);
#endif
			if (root) win_print_root(0,i-skip+origin,storage);
		 	else win_print(0,i-skip+origin,storage);
		} else if (root)
			win_clrtoeol_root(0,i-skip+origin);
		else
			win_clrtoeol(0,i-skip+origin);
	}

	return skip;
}

/* first line : MikMod version */
static void display_version(void)
{
	if (quiet) return;
	win_print_root(0,0,mikversion);
}

/* displays the "paused" banner */
void display_pausebanner(void)
{
	if (quiet) return;
	display_banner(pausebanner,1,1,0,0);
}	

/* display the "extracting" banner */
void display_extractbanner(void)
{
	if (quiet) return;
	display_banner(extractbanner,1,1,0,0);
	win_refresh();
}	

/* display the "loading" banner */
void display_loadbanner(void)
{
	if (quiet) return;
	display_banner(loadbanner,1,1,0,0);
	win_refresh();
}	

/* second line : driver settings */
static void display_driver(void)
{
	char reverb[13];
	
	if (quiet) return;
	if (md_reverb)
#ifdef HAVE_SNPRINTF
		snprintf(reverb,12,"reverb: %2d",md_reverb);
#else
		sprintf(reverb,"reverb: %2d",md_reverb);
#endif
	else
		strcpy(reverb,"no reverb");
			
#ifdef HAVE_SNPRINTF
	snprintf
#else
	sprintf
#endif
	       (storage,
#ifdef HAVE_SNPRINTF
	                STORAGELEN,
#endif		 
	    "%s: %d bit %s %s, %u Hz, %s\n",
		md_driver->Name,(md_mode&DMODE_16BITS)?16:8,
		(md_mode&DMODE_INTERP)?
			(md_mode&DMODE_SURROUND?"interp. surround":"interpolated"):
			(md_mode&DMODE_SURROUND?"surround":"normal"),
		(md_mode&DMODE_STEREO)?"stereo":"mono",md_mixfreq,reverb);

	win_print_root(0,1,storage);
}

/* third line : filename */
static void display_file(void)
{
	PLAYENTRY *entry;

	if (quiet) return;
	if ((entry=PL_GetCurrent(&playlist))) {
		CHAR *archive=entry->archive,*file;

		if (archive && !config.fullpaths) {
			archive=strrchr(entry->archive,PATH_SEP);
			if (archive)
				archive++;
			else
				archive=entry->archive;
		}

		file=strrchr(entry->file,PATH_SEP);
		if (file && !config.fullpaths)
			file++;
		else
			file=entry->file;

		if ((archive)&&(strlen(file)<MAXWIDTH-13)) {
			if (strlen(archive)+strlen(file)>MAXWIDTH-10) {
				archive+=strlen(archive)-(MAXWIDTH-13-strlen(file));
#ifdef HAVE_SNPRINTF
				snprintf(storage,STORAGELEN,"File: %s (...%s)\n",file,archive);
#else
				sprintf(storage,"File: %s (...%s)\n",file,archive);
#endif
			} else
#ifdef HAVE_SNPRINTF
				snprintf(storage,STORAGELEN,"File: %s (%s)\n",file,archive);
#else
				sprintf(storage,"File: %s (%s)\n",file,archive);
#endif
		} else
#ifdef HAVE_SNPRINTF
			snprintf(storage,STORAGELEN,"File: %.70s\n",file);
#else
			sprintf(storage,"File: %.70s\n",file);
#endif
		win_print_root(0,2,storage);
	}
}

/* fourth and fifth lines : module name and format */
static void display_name(void)
{
	if ((quiet)||(!mf)) return;

#ifdef HAVE_SNPRINTF
	snprintf(storage,STORAGELEN,"Name: %.70s\n",mf->songname);
#else
	sprintf(storage,"Name: %.70s\n",mf->songname);
#endif
	win_print_root(0,3,storage);
#ifdef HAVE_SNPRINTF
	snprintf
#else
	sprintf
#endif
	       (storage,
#ifdef HAVE_SNPRINTF
	                STORAGELEN,
#endif
		"Type: %s, Periods: %s, %s\n",
		mf->modtype,
		(mf->flags&UF_XMPERIODS)?"XM type":"mod type",
		(mf->flags&UF_LINEAR)?"linear":"log");
	win_print_root(0,4,storage);
}

/* sixth line : player status */
void display_status(void)
{
	if (quiet) return;

	if (win_check_resize()) {
		setup_printf();
		win_panel_repaint();
	}
	remove_message();
	if ((!mf)||Player_Paused()) return;

	if (mf->sngpos<mf->numpos) {
		PLAYENTRY *cur=PL_GetCurrent(&playlist);
		char time[7]="";
		char channels[17]="";

		if (cur && cur->time>0)
#ifdef HAVE_SNPRINTF
			snprintf(time,7,"/%2d:%02d",
			        (int)((cur->time/60)%60),(int)(cur->time%60));
#else
			sprintf(time,"/%2d:%02d",
			        (int)((cur->time/60)%60),(int)(cur->time%60));
#endif
#if LIBMIKMOD_VERSION >= 0x030107
		if (mf->flags&UF_NNA) {
#ifdef HAVE_SNPRINTF
			snprintf(channels,17,"%2d/%d+%d->%d",
			     mf->realchn,mf->numchn,mf->totalchn-mf->realchn,mf->totalchn);
#else
			sprintf(channels,"%2d/%d+%d->%d",
			     mf->realchn,mf->numchn,mf->totalchn-mf->realchn,mf->totalchn);
#endif
		} else
#endif
		       {
#ifdef HAVE_SNPRINTF
			snprintf(channels,17,"%2d/%d      ",mf->realchn,mf->numchn);
#else
			sprintf(channels,"%2d/%d      ",mf->realchn,mf->numchn);
#endif
		}

#ifdef HAVE_SNPRINTF
		snprintf
#else
		sprintf
#endif
		       (storage,
#ifdef HAVE_SNPRINTF
		                STORAGELEN,
#endif
			"pat:%03d/%03d pos:%02.2X spd:%2d/%3d "
			"vol:%3d%%/%3d%% time:%2d:%02d%s chn:%s  \n",
			mf->sngpos,mf->numpos-1,mf->patpos,mf->sngspd,mf->bpm,
			(mf->volume*100+64)>>7,(md_volume*100+64)>>7,
		    (int)(((mf->sngtime>>10)/60)%60),(int)((mf->sngtime>>10)%60),
			time,channels);

		win_print_root(0,5,storage);

	}
}

/* help panel */
static void display_help(int diff)
{
static char helptext[]=
"\n"
#if !defined(__OS2__)&&!defined(__EMX__)
"Keys help             (depending on your terminal and your curses library, \n"
"=========                      some of these keys might not be recognized) \n"
#else
"Keys help\n"
"=========\n"
#endif
"\n"
"H/F1      show help panel               ()        decrease/increase tempo  \n"
"S/F2      show samples panel            {}        decrease/increase bpm    \n"
"I/F3      show instrument panel         :/;       toggle interpolation     \n"
"M/F4      show message panel            U         toggle surround sound    \n"
"L/F5      show list panel               1..0      volume 10%..100%         \n"
"C/F6      show config panel             <>        decrease/increase volume \n"
"ENTER     in list panel, activate menu  P         switch to previous module\n"
"Left/-    previous pattern              N         switch to next module    \n"
"Right/+   next pattern                  R         restart module           \n"
"Up/Down   scroll panel                  Space     toggle pause             \n"
"PgUp/PgDn scroll panel (faster)         ^L        refresh screen           \n"
"Home/End  start/end of panel            Q         exit MikMod              \n";

	first_help+=diff;
	first_help=display_banner(helptext,0,0,first_help,0);
}

static void convert_string(char *str)
{
	for (;str&&*str;str++)
		if (*str<' ') *str=' ';
}

/* sample panel */
static void display_sample(int diff)
{
	int count,semicount,t,x,winx;

	first_sample+=diff;
	win_get_size(&winx,&semicount);
	if (winx<MINWIDTH+MINVISIBLE) count=semicount;
	else count=semicount*2;

	win_clear();

	if (first_sample>=mf->numsmp-count) first_sample=mf->numsmp-count;
	if (first_sample<0) first_sample=0;

	if ((mf->numsmp>semicount)&&(mf->numsmp<count)) {
		semicount=(mf->numsmp+1)>>1;
		if (winx<MINWIDTH+MINVISIBLE) count=semicount;
		else count=semicount*2;
	}

	for (t=first_sample;t<mf->numsmp&&t<(count+first_sample);t++) {
		x=((t-first_sample)<semicount)?0:halfwidth;
		if (x<winx) {
#ifdef HAVE_SNPRINTF
			snprintf
#else
			sprintf
#endif
			       (storage,
#ifdef HAVE_SNPRINTF
			                STORAGELEN,
#endif
			fmt_halfwidth,t,
			mf->samples[t].samplename?mf->samples[t].samplename:"");
			convert_string(storage);
			win_print(x,(t-first_sample)%semicount,storage);
		}
	}
}

/* instrument panel */
static void display_inst(int diff)
{
	int count,semicount,t,x,winx;

	first_inst+=diff;
	win_get_size(&winx,&semicount);
	if (winx<MINWIDTH+MINVISIBLE) count=semicount;
	else count=semicount*2;

	win_clear();

	if (first_inst>=mf->numins-count) first_inst=mf->numins-count;
	if (first_inst<0) first_inst=0;

	if (mf->numins>semicount&&mf->numins<count) {
		semicount=(mf->numins+1)>>1;
		if (winx<MINWIDTH+MINVISIBLE) count=semicount;
		else count=semicount*2;
	}

	for (t=first_inst;t<mf->numins&&t<(count+first_inst);t++) {
		x=((t-first_inst)<semicount)?0:halfwidth;
		if (x<winx) {
#ifdef HAVE_SNPRINTF
			snprintf
#else
			sprintf
#endif
			       (storage,
#ifdef HAVE_SNPRINTF
			                STORAGELEN,
#endif
			fmt_halfwidth,t,
			mf->instruments[t].insname ? mf->instruments[t].insname:"");
			convert_string(storage);
			win_print(x,(t-first_inst)%semicount,storage);
		}
	}
}

/* comment panel */
static void display_comment(int diff)
{
	first_comment+=diff;
	win_clear();
	first_comment=display_banner(mf->comment,0,0,first_comment,1);
}

/* remove an entry from the playlist */
static void remove_entry(int entry)
{
	int cur=PL_GetCurrentPos(&playlist);
	if (cur==entry) {
		if (cur==PL_GetLength(&playlist)-1)
			Player_SetNextMod(0);
		else
			Player_SetNextMod(entry);
	}
	PL_DelEntry(&playlist,entry);
}

/* remove an entry from the playlist and delete the associated module */
static void cb_delete_entry(int button,void *input,void *entry)
{
	if (!button) {
		PLAYENTRY *cur=PL_GetEntry(&playlist,(long)entry);
		if (cur->archive) {
			if (unlink(cur->archive)==-1)
				dlg_error_show("Error deleting file!\n(%s)");
		} else {
			if (unlink(cur->file)==-1)
				dlg_error_show("Error deleting file!\n(%s)");
		}
		remove_entry((long)entry);
	}
}

/* split a filename into the name and the last extension */
static void split_name(char *file,char **name,char **ext)
{
	*name=strrchr(file,PATH_SEP);
	if (!*name) *name=file;
	*ext=strrchr(*name,'.');
	if (!*ext) *ext=&(*name[strlen(*name)]);
}

static BOOL sort_rev=0;
static enum {SORT_NAME,SORT_EXT,SORT_PATH,SORT_TIME} sort_mode=SORT_NAME;

static int cb_cmp_sort(PLAYENTRY *small,PLAYENTRY *big)
{
	char ch_s=' ',ch_b=' ',*ext_s,*ext_b,*name_s,*name_b;
	int ret=0;

	switch (sort_mode) {
		case SORT_NAME:
			split_name(small->file,&name_s,&ext_s);
			split_name(big->file,&name_b,&ext_b);
			ch_s=*ext_s;ch_b=*ext_b;
			*ext_s='\0';*ext_b='\0';
			ret=strcasecmp(name_s,name_b);
			*ext_s=ch_s;*ext_b=ch_b;
			break;
		case SORT_EXT:
			split_name(small->file,&name_s,&ext_s);
			split_name(big->file,&name_b,&ext_b);
			ret=strcasecmp(ext_s,ext_b);
			break;
		case SORT_PATH:
			ext_s=small->archive;
			if (!ext_s) ext_s=small->file;
			name_s=strrchr(ext_s,PATH_SEP);
			if (name_s) {
				ch_s=*name_s;*name_s='\0';
			}
			ext_b=big->archive;
			if (!ext_b) ext_b=big->file;
			name_b=strrchr(ext_b,PATH_SEP);
			if (name_b) {
				ch_b=*name_b;*name_b='\0';
			}
			ret=strcasecmp(ext_s,ext_b);
			if (name_s) *name_s=ch_s;
			if (name_b) *name_b=ch_b;
			break;
		case SORT_TIME:
			ret=(small->time==big->time?0:
				   (small->time<big->time?-1:1));
			break;
	}
	return (sort_rev)?-ret:ret;
}

/* overwrites an existdng playlist */
static void cb_overwrite(int button,void *input,void *file)
{
	if (!button) {
		if (PL_Save(&playlist,file))
			CF_set_string(&config.pl_name,file,PATH_MAX);
		else
			dlg_error_show("Error saving playlist!\n(%s)");
	}
	if (file) free(file);
}

/* saves a playlist */
static void cb_save_as(int button,void *input,void *data)
{
	if (!button) {
		if (file_exist(input)) {
			char *f_copy=strdup(input);
			char *msg=str_sprintf("File \"%s\" exists.\n"
			                      "Really overwrite the file?",f_copy);
			dlg_message_open(msg,"&Yes|&No",1,cb_overwrite,f_copy);
			free(msg);
		} else {
			if (PL_Save(&playlist,input))
				CF_set_string(&config.pl_name,input,PATH_MAX);
			else
				dlg_error_show("Error saving playlist!\n(%s)");
		}
	}
}

/* loads or merges a playlist */
static void cb_load_playlist(int button,void *input,void *data)
{
	if (!button) {
		int actLine=(long)data; /* <0: Load list/module */
		if (actLine<0)
			PL_ClearList(&playlist);
		else
			PL_StartInsert(&playlist,actLine);
		MA_FindFiles(&playlist,input);
		PL_StopInsert(&playlist);
		if (actLine<0) {
			PL_InitCurrent(&playlist);
			Player_SetNextMod(-1);
		} else {
			/* remove duplicates entries */
			int current,old_cur=PL_GetCurrentPos(&playlist);
			PL_DelDouble(&playlist);
			current=PL_GetCurrentPos(&playlist);
			if (current!=old_cur)
				Player_SetNextMod(current);
		}
	}
}

/* playlist menu handler */
static void cb_handle_menu(MMENU *menu)
{
	MENU_DATA *data=menu->data;
	int actLine=*data->actLine;
	PLAYENTRY *cur;
	char *name,*msg;

	/* main menu */
	if (!menu->id) {
		switch (menu->cur) {
			/* play highlighted module */
			case 0:
				if (actLine>=0) Player_SetNextMod(actLine);
				break;
			/* remove highlighted module */
			case 1:
				if (actLine>=0) remove_entry(actLine);
				break;
			/* delete highlighted module */
			case 2:
				cur=PL_GetEntry(&playlist,actLine);
				if (!cur) break;

				if (cur->archive) {
					name=strrchr(cur->file,PATH_SEP);
					if (name) name++;
						 else name=cur->file;

					if (strlen(cur->archive)>60)
						msg=str_sprintf2("File \"%s\" is in an archive!\n"
						                 "Really delete whole archive\n"
						                 "  \"...%s\"?",name,
						             &(cur->archive[strlen(cur->archive)-57]));
					else
						msg=str_sprintf2("File \"%s\" is in an archive!\n"
						                 "Really delete whole archive\n"
						                 "  \"%s\"?",name,cur->archive);
					dlg_message_open(msg,"&Yes|&No",1,
									  cb_delete_entry,(void*)(long)actLine);
				} else {
					if (strlen(cur->file)>50)
						msg=str_sprintf("Delete file \"...%s\"?",
										&(cur->file[strlen(cur->file)-47]));
					else
						msg=str_sprintf("Delete file \"%s\"?",cur->file);
					dlg_message_open(msg,"&Yes|&No",1,
									  cb_delete_entry,(void*)(long)actLine);
				}
				free(msg);
				break;
			/* shuffle list */
			case 5:
				PL_Randomize(&playlist,1);
				break;
			/* cancel */
			case 7:
				break;
			default:
				return;
		}
	/* file menu */
	} else if (menu->id == 1) {
		switch (menu->cur) {
			/* load */
			case 0:
				dlg_input_str("Load playlist/module:",config.pl_name,PATH_MAX,
				              cb_load_playlist,(void*)-1);
				break;
			/* insert */
			case 1:
				dlg_input_str("Insert playlist/module:",config.pl_name,PATH_MAX,
				              cb_load_playlist,(void*)(long)actLine);
				break;
			/* save */
			case 2:
				if (!PL_Save(&playlist,config.pl_name))
					dlg_error_show("Error saving playlist!\n(%s)");
				break;
			/* save as */
			case 3:
				dlg_input_str("Save playlist as:",config.pl_name,PATH_MAX,
				              cb_save_as,NULL);
				break;
			default:
				return;
		}
	/* sort menu */
	} else {
		/* reverse flag */
		sort_rev=(long)menu->entries[5].data;
		switch (menu->cur) {
			/* by name */
			case 0:
				sort_mode=SORT_NAME;
				PL_Sort(&playlist,cb_cmp_sort);
				break;
			/* by extension */
			case 1:
				sort_mode=SORT_EXT;
				PL_Sort(&playlist,cb_cmp_sort);
				break;
			/* by path */
			case 2:
				sort_mode=SORT_PATH;
				PL_Sort(&playlist,cb_cmp_sort);
				break;
			/* by time */
			case 3:
				sort_mode=SORT_TIME;
				PL_Sort(&playlist,cb_cmp_sort);
				break;
			default:
				return;
		}
	}
	menu_close(data->menu);
	return;
}

static void display_playentry(PLAYENTRY *pos,PLAYENTRY *cur,int nr,int y,int x,BOOL reverse,int width)
{
	char *name,sort;
	char time[8]="",tmpfmt[30];
	int timelen=0;

	if (pos->time>0) {
#ifdef HAVE_SNPRINTF
		snprintf(time,7," %2d:%02d",
				(int)((pos->time/60)%60),(int)(pos->time%60));
#else
		sprintf(time," %2d:%02d",
				(int)((pos->time/60)%60),(int)(pos->time%60));
#endif
		timelen=strlen(time);
	}
	name=strrchr(pos->file,PATH_SEP);
	if (name && !config.fullpaths)
		name++;
	else
		name=pos->file;

	if (pos==cur) sort='>';
	else if (pos->played) sort='*';
	else sort=' ';

	if (pos->archive) {
		if (strlen(name)>width-13-timelen) {
			name=name+strlen(name)-(width-16-timelen);
			if (timelen) {
				sprintf(tmpfmt,"%%4i %%c...%%-%ds%%s(pack)",width-22);
#ifdef HAVE_SNPRINTF
				snprintf(storage,STORAGELEN,tmpfmt,nr,sort,name,time);
#else
				sprintf(storage,tmpfmt,nr,sort,name,time);
#endif
			} else {
				sprintf(tmpfmt,"%%4i %%c...%%-%ds(pack)",width-16);
#ifdef HAVE_SNPRINTF
				snprintf(storage,STORAGELEN,tmpfmt,nr,sort,name);
#else
				sprintf(storage,tmpfmt,nr,sort,name);
#endif
			}
		} else
			if (timelen) {
				sprintf(tmpfmt,"%%4i %%c%%-%ds%%s(pack)",width-19);
#ifdef HAVE_SNPRINTF
				snprintf(storage,STORAGELEN,tmpfmt,nr,sort,name,time);
#else
				sprintf(storage,tmpfmt,nr,sort,name,time);
#endif
			} else {
				sprintf(tmpfmt,"%%4i %%c%%-%ds(pack)",width-13);
#ifdef HAVE_SNPRINTF
				snprintf(storage,STORAGELEN,tmpfmt,nr,sort,name);
#else
				sprintf(storage,tmpfmt,nr,sort,name);
#endif
			}
	} else if (strlen(name)>width-7-timelen) {
		name=name+strlen(name)-(width-10-timelen);
		if (timelen) {
			sprintf(tmpfmt,"%%4i %%c...%%-%ds%%s",width-16);
#ifdef HAVE_SNPRINTF
			snprintf(storage,STORAGELEN,tmpfmt,nr,sort,name,time);
#else
			sprintf(storage,tmpfmt,nr,sort,name,time);
#endif
		} else {
			sprintf(tmpfmt,"%%4i %%c...%%-%ds",width-10);
#ifdef HAVE_SNPRINTF
			snprintf(storage,STORAGELEN,tmpfmt,nr,sort,name);
#else
			sprintf(storage,tmpfmt,nr,sort,name);
#endif
		}
	} else
		if (timelen) {
			sprintf(tmpfmt,"%%4i %%c%%-%ds%%s",width-13);
#ifdef HAVE_SNPRINTF
			snprintf(storage,STORAGELEN,tmpfmt,nr,sort,name,time);
#else
			sprintf(storage,tmpfmt,nr,sort,name,time);
#endif
		} else {
			sprintf(tmpfmt,"%%4i %%c%%-%ds",width-7);
#ifdef HAVE_SNPRINTF
			snprintf(storage,STORAGELEN,tmpfmt,nr,sort,name);
#else
			sprintf(storage,tmpfmt,nr,sort,name);
#endif
		}

	if (reverse) win_attrset(A_REVERSE);
	win_print(x,y,storage);
	win_attrset(A_NORMAL);
}

/* playlist panel */
static void display_list(int diff,COMMAND com)
{
	static char *no_data ="\nPlaylist is empty!\n";
	static MENU_DATA menu_data;
	static int actLine=-1;

	static MENTRY file_entries[]={
		{"&Load...",   0,"Load new playlist/module"},
		{"&Insert...", 0,"Insert new playlist/module in current playlist"},
		{"&Save",      0,NULL},
		{"Save &as...",0,"Save playlist in a specified file"},
	};
	static MMENU file_menu={0,0,4,1,file_entries,cb_handle_menu,NULL,&menu_data,1};

	static MENTRY sort_entries[]={
		{"by &name",     0,"Sort list by name of modules"},
		{"by &extension",0,"Sort list by extension of modules"},
		{"by &path",     0,"Sort list by path of modules/archives"},
		{"by &time",     0,"Sort list by playing time of modules"},
		{"----------",   0,NULL},
		{"[%c] &reverse",0,"Smaller to bigger or reverse sort"}};
	static MMENU sort_menu={0,0,6,1,sort_entries,cb_handle_menu,NULL,&menu_data,2};

	static MENTRY entries[]={
		{"&Play",         0,"Play selected entry"},
		{"&Remove",       0,"Remove selected entry from list"},
		{"&Delete...",    0,"Remove selected entry from list and delete it on disk"},
		{"-----------",   0,NULL},
		{"&File        >",&file_menu,"Load/Save playlist/modules"},
		{"&Shuffle",      0,"Shuffle the list"},
		{"S&ort        >",&sort_menu,"Sort the list"},
		{"&Back",         0,"Leave menu"}};
	static MMENU menu={0,0,8,1,entries,cb_handle_menu,NULL,&menu_data,0};

	int count,semicount,playcount,t,winx,x,width;
	PLAYENTRY *cur;

	playcount=PL_GetLength(&playlist);
	if (actLine>=playcount) actLine=playcount-1;

	if (com==MENU_ACTIVATE) {
		menu_data.menu=&menu;
		menu_data.actLine=&actLine;
		set_help(&file_entries[2],"Save list in '%s'",config.pl_name);
		menu_open(&menu,5,5);
		return;
	}

	win_clear();

	if (playcount) {
		win_get_size(&winx,&semicount);
		if (winx<40+MINVISIBLE) {
			count=semicount;
			width=winx;
		} else {
			count=semicount*2;
			width=winx>>1;
		}
		cur=PL_GetCurrent(&playlist);

		if (actLine<0) {
			actLine=PL_GetCurrentPos(&playlist);
			first_list=actLine-semicount/2;
			if (first_list<0) first_list=0;
		}
		actLine+=diff;

		if (actLine<0) actLine=0;
		else if (actLine>=playcount) actLine=playcount-1;

		if (actLine<first_list) first_list=actLine;
		else if (actLine>=first_list+count) first_list=actLine-count+1;

		for (t=first_list;t<playcount&&t<(count+first_list);t++) {
			x=(t-first_list)<semicount?0:width;
			if (x<winx)
				display_playentry(PL_GetEntry(&playlist,t),cur,t,
								  (t-first_list)%semicount,
								  x,actLine==t,width);
		}
	} else {
		first_list+=diff;
		first_list=display_banner(no_data,0,0,first_list,1);
	}
}

/* open config-editor panel */
static void display_config(int diff)
{
	static BOOL open=0;

	if (!open) {
		config_open();
		open=1;
	}
}

/* seventh line to bottom of screen: information panel */
static BOOL display_information(int diff,COMMAND com)
{
	char paneltitle[STORAGELEN],otherpanels[STORAGELEN];
	BOOL change=0;

	if (quiet || semiquiet) return 1;

	/* sanity check */
	if (!mf && ((cur_display==DISPLAY_INST) ||
				(cur_display==DISPLAY_SAMPLE) ||
				(cur_display==DISPLAY_COMMENT))) {
		cur_display=DISPLAY_LIST;
		change=1;
	}
	while (1) {
		if (((cur_display==DISPLAY_INST)&&(!(mf->flags&UF_INST)))||
			((cur_display==DISPLAY_COMMENT)&&(!mf->comment))) {
			cur_display=(cur_display==old_display)?DISPLAY_SAMPLE:old_display;
			change=1;
		} else
			break;
	}
	if (change) {
		win_change_panel(cur_display);
		return 0;
	}
	/* set panel title */
	switch(cur_display) {
		case DISPLAY_HELP:
			strcpy(paneltitle,"[ Help ]");
			break;
		case DISPLAY_INST:
#ifdef HAVE_SNPRINTF
			snprintf(paneltitle,STORAGELEN,"[ %d Instruments ]",mf->numins);
#else
			sprintf(paneltitle,"[ %d Instruments ]",mf->numins);
#endif
			break;
		case DISPLAY_SAMPLE:
#ifdef HAVE_SNPRINTF
			snprintf(paneltitle,STORAGELEN,"[ %d Samples ]",mf->numsmp);
#else
			sprintf(paneltitle,"[ %d Samples ]",mf->numsmp);
#endif
			break;
		case DISPLAY_COMMENT:
			strcpy(paneltitle,"[ Message ]");
			break;
		case DISPLAY_LIST:
#ifdef HAVE_SNPRINTF
			snprintf(paneltitle,STORAGELEN,"[ %d Modules ]",PL_GetLength(&playlist));
#else
			sprintf(paneltitle,"[ %d Modules ]",PL_GetLength(&playlist));
#endif
			break;
		case DISPLAY_CONFIG:
			strcpy(paneltitle,"[ Config ]");
			break;
	}
	
	/* set available panel information */
	strcpy(otherpanels," --");

	if (cur_display!=DISPLAY_HELP)
		strcat(otherpanels," H:[help]");
	if ((cur_display!=DISPLAY_INST) && mf && (mf->flags&UF_INST))
		strcat(otherpanels," I:[instruments]");
	if (cur_display!=DISPLAY_SAMPLE && mf)
		strcat(otherpanels," S:[samples]");
	if ((cur_display!=DISPLAY_COMMENT) && mf && (mf->comment))
		strcat(otherpanels," M:[message]");
	if (cur_display!=DISPLAY_LIST)
		strcat(otherpanels," L:[playlist]");
	if (cur_display!=DISPLAY_CONFIG)
		strcat(otherpanels," C:[config]");
	strcat (otherpanels,"\n");

	win_attrset(A_REVERSE);
	win_print_root(0,6,paneltitle);
	win_attrset(A_NORMAL);
	win_print_root(strlen(paneltitle),6,otherpanels);

	/* display panel contents */
	switch(cur_display) {
		case DISPLAY_HELP:
			display_help(diff);
			break;
		case DISPLAY_INST:
			display_inst(diff);
			break;
		case DISPLAY_SAMPLE:
			display_sample(diff);
			break;
		case DISPLAY_COMMENT:
			display_comment(diff);
			break;
		case DISPLAY_LIST:
			display_list(diff,com);
			break;
		case DISPLAY_CONFIG:
			display_config(diff);
			break;
	}
	return 1;
}

/* displays the top of the screen */
void display_header(void)
{
	if (quiet) return;

	display_version();
	update_message();
	if (Player_Paused())
		display_pausebanner();
	else {
		display_driver();
		display_file();
		display_name();
		display_status();
	}
}

static BOOL display_head_repaint(MWINDOW *win)
{
	display_header();
	return 1;
}

static BOOL display_inf_repaint(MWINDOW *win)
{
	if ((long)win->data!=cur_display)
		old_display=cur_display;
	cur_display=(long)win->data;
	return display_information(0,COM_NONE);
}

void display_start(void)
{
	if (quiet) return;

	first_inst=first_sample=first_comment=0;
	win_panel_repaint();
}

/* handle interface-specific keys */
static BOOL display_handle_key(MWINDOW *win,int ch)
{
	switch (ch) {
		case KEY_DOWN:
			display_information(1,COM_NONE);
			break;
		case KEY_UP:
			display_information(-1,COM_NONE);
			break;
		case KEY_RIGHT:
			if (cur_display!=DISPLAY_LIST)
				return 0;
			/* fall through */
		case KEY_NPAGE:
			display_information(win->height,COM_NONE);
			break;
		case KEY_LEFT:
			if (cur_display!=DISPLAY_LIST)
				return 0;
			/* fall through */
		case KEY_PPAGE:
			display_information(-win->height,COM_NONE);
			break;
		case KEY_HOME:
			display_information(-32000,COM_NONE);
			break;
#ifdef KEY_END
		case KEY_END:
			display_information(32000,COM_NONE);
			break;
#endif
		case KEY_ENTER:
		case '\r':
			if (cur_display==DISPLAY_LIST)
				display_information(0,MENU_ACTIVATE);
			else
				return 0;
			break;
		default:
			return 0;
	}
	return 1;
}

/* setup interface */
void display_init(void)
{
	int i;
	win_panel_set_repaint(DISPLAY_ROOT,display_head_repaint);

	for (i=1;i<=DISPLAY_CONFIG;i++) {
		win_panel_open(i,0,PANEL_Y,999,999,0,NULL);
		win_panel_set_repaint(i,display_inf_repaint);
		win_panel_set_handle_key(i,display_handle_key);
		win_panel_set_resize(i,1,NULL);
		win_panel_set_data(i,(void*)(long)i);
	}
	win_change_panel(cur_display);
	setup_printf();
}

/* ex:set ts=4: */
