/*
    Panels management file
    Copyright (c) Tudor Hulubei & Andrei Pitis, May 1994

This file is part of UIT (UNIX Interactive Tools)

UIT 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, or (at your option) any later version.

UIT 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
UIT; see the file COPYING.  If not, write to the Free Software Foundation,
675 Mass Ave, Cambridge, MA 02139, USA.  */


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <malloc.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <values.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>

#ifdef HAVE_LINUX
#include <sys/vfs.h>
#endif

#include "tty.h"
#include "window.h"
#include "status.h"
#include "panel.h"


/* This is really ugly ... */

#ifndef S_IFMT
#define S_IFMT  00170000
#endif

#ifndef S_IFSOCK
#define S_IFSOCK 0140000
#endif

#ifndef S_IFLNK
#define S_IFLNK	 0120000
#endif

#ifndef S_IFREG
#define S_IFREG  0100000
#endif

#ifndef S_IFBLK
#define S_IFBLK  0060000
#endif

#ifndef S_IFDIR
#define S_IFDIR  0040000
#endif

#ifndef S_IFCHR
#define S_IFCHR  0020000
#endif

#ifndef S_IFIFO
#define S_IFIFO  0010000
#endif

#ifndef S_ISUID
#define S_ISUID  0004000
#endif

#ifndef S_ISGID
#define S_ISGID  0002000
#endif

#ifndef S_ISVTX
#define S_ISVTX  0001000
#endif


#ifndef S_ISLNK
#define S_ISLNK(m)	(((m) & S_IFMT) == S_IFLNK)
#endif

#ifndef S_ISREG
#define S_ISREG(m)	(((m) & S_IFMT) == S_IFREG)
#endif

#ifndef S_ISDIR
#define S_ISDIR(m)	(((m) & S_IFMT) == S_IFDIR)
#endif

#ifndef S_ISCHR
#define S_ISCHR(m)	(((m) & S_IFMT) == S_IFCHR)
#endif

#ifndef S_ISBLK
#define S_ISBLK(m)	(((m) & S_IFMT) == S_IFBLK)
#endif

#ifndef S_ISFIFO
#define S_ISFIFO(m)	(((m) & S_IFMT) == S_IFIFO)
#endif

#ifndef S_ISSOCK
#define S_ISSOCK(m)	(((m) & S_IFMT) == S_IFSOCK)
#endif

/* Finally ... */


extern int  SignalsStatus;
extern int  LinuxConsole;
extern int  ColorMonitor;
extern char cSection[];
extern char bwSection[];


#ifndef HAVE_CONST
#define const
#endif


char temp[PATH_MAX];
char no_perm[]  = "***** Permission denied ! *****";
char bad_name[] = "***** Invalid name ! *****";
char rights[16] = "-rwxrwxrwx";


#define FILE_DISPLAY_MODES	5

char FileDisplayMode[FILE_DISPLAY_MODES][15] = 
{
    "OwnerGroup",
    "DateTime",
    "Size",
    "Mode",
    "FullName"
};


#define PANEL_FIELDS	17

static char PanelFields[PANEL_FIELDS][40] = 
{
    "PanelFrame",
    "PanelBackground",
    "PanelSelectedFile",
    "PanelSelectedFileBrightness",
    "PanelNotSelectedFile",
    "PanelNotSelectedFileBrightness",
    "PanelCurrentSelectedFile",
    "PanelCurrentNotSelectedFile",
    "PanelCurrentFile",
    "PanelPath",
    "PanelPathBrightness",
    "PanelDeviceFreeSpace",
    "PanelDeviceFreeSpaceBrightness",
    "PanelFileInfo",
    "PanelFileInfoBrightness",
    "PanelFilesInfo",
    "PanelFilesInfoBrightness",
};

#ifdef HAVE_LINUX
static int PanelColors[PANEL_FIELDS] = 
{
    WHITE, BLUE, YELLOW, ON, WHITE, ON, YELLOW, WHITE, 
    CYAN, RED, OFF, RED, OFF, RED, OFF, BLACK, OFF
};
#else
static int PanelColors[PANEL_FIELDS] = 
{
    WHITE, BLACK, WHITE, ON, WHITE, OFF, WHITE, BLACK, 
    WHITE, BLACK, OFF, BLACK, OFF, BLACK, OFF, BLACK, OFF
};
#endif

#define PanelFrame 			PanelColors[0]
#define PanelBackground 		PanelColors[1]
#define PanelSelectedFile 		PanelColors[2]
#define PanelSelectedFileBrightness	PanelColors[3]
#define PanelNotSelectedFile		PanelColors[4]
#define PanelNotSelectedFileBrightness	PanelColors[5]
#define PanelCurrentSelectedFile 	PanelColors[6]
#define PanelCurrentNotSelectedFile 	PanelColors[7]
#define PanelCurrentFile 		PanelColors[8]
#define PanelPath 			PanelColors[9]
#define PanelPathBrightness 		PanelColors[10]
#define PanelDeviceFreeSpace        	PanelColors[11]
#define PanelDeviceFreeSpaceBrightness	PanelColors[12]
#define PanelFileInfo 			PanelColors[13]
#define PanelFileInfoBrightness 	PanelColors[14]
#define PanelFilesInfo			PanelColors[15]
#define PanelFilesInfoBrightness	PanelColors[16]

static int *UserHeartAttack;
static int StartupFileDisplayMode;
static int InfoDisplay  = OFF;
int FrameDisplay = OFF;		/* Sorry, I really nead it this way ... :-( */

int  tell(int);
void uitclock(int);
void signals(int);
void fatal(char *);
void ignore_signals(void);
void restore_signals(void);

#define max(a, b) ((a) >= (b) ? (a) : (b))
#define min(a, b) ((a) <= (b) ? (a) : (b))

static void xchg(int *a, int *b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}


panel *panel_init(int _lines, int _columns,
		  int _begin_x, int _begin_y,
		  char *_path, edit *_edt,
		  int *_UserHeartAttack,
		  configuration *config)
{
    char *data, *section;
    int sectionptr, index, i;
    static int configured;
    
    panel *this = (panel *)malloc(sizeof(panel));
    this->lines   = _lines;
    this->columns = _columns;
    this->begin_x = _begin_x;
    this->begin_y = _begin_y;
    this->focus   = OFF;
    this->edt     = _edt;
    this->info_dirty = ON;
    this->entries = this->selected_files = 0;
    this->last_index = -1;
    this->mode = this->found_selection = this->end = 0;
    this->current_entry = 0;
    this->first_on_screen = max(0, this->current_entry - (this->lines-2) + 1);
    this->on_screen = MAX_ENTRIES;
    UserHeartAttack = _UserHeartAttack;
    memset(this->dir_entry, 0, sizeof(this->dir_entry));
    strcpy(this->path, _path);

    this->win = window_init(this->begin_x, this->begin_y,
			    this->lines, this->columns);

    if (configured)
    {
        this->mode = StartupFileDisplayMode;
        return this;
    }
    if (configuration_getstatus(config) == STATUS_OK)
    {
	sectionptr = configuration_getsectionptr(config, "[UIT-Setup]");
        if (sectionptr != -1)
        {
	    configuration_getfielddata(config, sectionptr,
				       "StartupFileDisplayMode",
				       &data, 1, DO_SEEK);
	    if (data)
	    {
                for (i = 0; i < FILE_DISPLAY_MODES; i++)
                    if (strcmp(data, FileDisplayMode[i]) == 0)
                        break;
                if (i == FILE_DISPLAY_MODES)
                    fprintf(stderr, "Invald StartupFileDisplayMode (%s).\n",
			    data);
                else
                    this->mode = StartupFileDisplayMode = i;
            }
	    configuration_getfielddata(config, sectionptr, "InfoDisplay",
				       &data, 1, DO_SEEK);
	    InfoDisplay = data && strcmp(data, "ON") == 0;
	    configuration_getfielddata(config, sectionptr, "FrameDisplay",
				       &data, 1, DO_SEEK);
	    FrameDisplay = data && strcmp(data, "ON") == 0;
	}
	
	section = (LinuxConsole && ColorMonitor) ? cSection : bwSection;

	sectionptr = configuration_getsectionptr(config, section);
        if (sectionptr != -1)
            for (i = 0; i < PANEL_FIELDS; i++)
            {
                configuration_getfielddata(config, sectionptr, PanelFields[i],
					   &data, 1, DO_SEEK);
                if (!data || (index = tty_getcolorindex(data)) == -1)
                    fprintf(stderr, "Invalid %s (%s).\n", PanelFields[i],data);
	        else
                    PanelColors[i] = index;
            }
    }
    configured = 1;
    return this;
}


void panel_end(panel *this)
{
    int i;

    if (this->dir) closedir(this->dir);

    for (i = 0; i < this->entries; i++)
        if (this->dir_entry[i].name)
	    free(this->dir_entry[i].name);

    free(this->win);
}


char *cutname(char *name, int which, int how)
{
    static char tname[2][16];
    
    if (how)
    {
        memset(tname[which], ' ', 14);
        tname[which][14] = 0;
        return memcpy(tname[which], name, min(strlen(name), 14));
   }
   else
	return strncpy(tname[which], name, 14);
}


static int sortfn(const void *_first, const void *_second)
{
    int retval;
    _dir_entry *first  = (_dir_entry *)_first;
    _dir_entry *second = (_dir_entry *)_second;
    int first_is_dir   = first->type  == DIR_ENTRY;
    int second_is_dir  = second->type == DIR_ENTRY;    
    char *pfirst       = strrchr(first->name , '.');
    char *psecond      = strrchr(second->name, '.');

    if (first_is_dir == second_is_dir)
        if (pfirst && psecond)
	    return (retval = strcmp(++pfirst, ++psecond)) ?
                    retval : strcmp(first->name, second->name);
        else
            return (pfirst || psecond) ?
                   (pfirst ? -1 : 1) : strcmp(first->name, second->name);
    else return first_is_dir ? -1 : 1;
}


void  panel_nooptimizations(panel *this)
{
    this->on_screen = MAX_ENTRIES;
}


char *panel_getcurrentfilename(panel *this)
{
    return this->dir_entry[this->current_entry].name;
}


int panel_getcurrentfileuid(panel *this)
{
    return this->dir_entry[this->current_entry].uid;
}


int panel_getcurrentfilegid(panel *this)
{
    return this->dir_entry[this->current_entry].gid;
}


int panel_getcurrentfilemode(panel *this)
{
    return this->dir_entry[this->current_entry].mode;
}


int panel_getcurrentfiletype(panel *this)
{
    return this->dir_entry[this->current_entry].type;
}


void panel_recover(panel *this)
{
    char msg[PATH_MAX];
    
    this->first_on_screen = this->current_entry = 0;
    sprintf(msg, "Can't get permission in directory %s ! (press any key).",
	    this->path);
    status(msg, 1, 1, 1, MSG_ERROR);
    if (strcmp(this->path, "/") == 0)
        fatal("can't get permission in root directory");
    strcpy(this->path, "/");
    chdir(this->path);
    panel_action(this, act_REFRESH, NULL, NULL, 1);
}


int panel_getdirinfo(panel *this, char *directory, int verify)
{
    DIR *tmpdir;
    struct dirent *d;
    struct stat s;
#ifdef HAVE_STATFS
    struct statfs fstat;
#endif
    struct passwd *pwd;
    struct group *grp;
    struct tm *time;
    int i, j, old_uid = -1, old_gid = -1, msdosfs = 0, namelen;
    int old_entries = 0, backdir_index = -1, sz, hour;
    _dir_entry *old_dir_entry = NULL, tmp;
    char old_path[PATH_MAX], temp[PATH_MAX];


    if (!(tmpdir = opendir(directory)))
        return 0;

    if (chdir(directory) == -1)
    {
        closedir(tmpdir);
        return 0;
    }

    if (this->dir) closedir(this->dir);
    this->dir = tmpdir;
    strcpy(old_path, this->path);
    getcwd(this->path, PATH_MAX);

#ifdef HAVE_STATFS
    statfs(".", &fstat);
    msdosfs = fstat.f_type == 0x4d44;	/* I can't get this number without */
 					/* including linux/msdos_fs.h    */
#endif
    
    if (verify = (verify && this->selected_files &&
		  strcmp(old_path, this->path) == 0))
    {
        if (!(old_dir_entry = (_dir_entry *)malloc(sizeof(this->dir_entry))))
            fatal("not enough memory");
        memcpy(old_dir_entry, this->dir_entry,
	       (old_entries = this->entries) * sizeof(_dir_entry));
        memset(this->dir_entry, 0, sizeof(this->dir_entry));
    }

    this->selected_files = 0;
    this->maxname = 0;
    for (this->entries = 0;
	 (d = readdir(this->dir)) && this->entries < MAX_ENTRIES;
	 this->entries++)
    {
       if (d->d_name[0] == '.' && !d->d_name[1])
            { this->entries--; continue; }		/* ignore "." */
        if (d->d_name[0] == '.' && d->d_name[1] == '.' && d->d_name[2] == 0)
            if (this->path[1])
                backdir_index = this->entries;
            else { this->entries--; continue; }	/* ignore ".." if root dir */
        s.st_ino = 0;
        stat(d->d_name, &s);
        this->dir_entry[this->entries].mode = s.st_mode;
        this->dir_entry[this->entries].uid  = s.st_uid;
        this->dir_entry[this->entries].gid  = s.st_gid;

        if (verify)
        {
            for (j = 0; j < old_entries; j++)
                if (strcmp(d->d_name, old_dir_entry[j].name) == 0)
                {
                    this->selected_files +=
			(this->dir_entry[this->entries].selected =
			 old_dir_entry[j].selected);
                    break;
                }
        }
        else
            this->dir_entry[this->entries].selected = 0;

        if (s.st_ino)
        {
            if (S_ISDIR(s.st_mode))
                this->dir_entry[this->entries].type = DIR_ENTRY;
            else
            {
                if (S_ISREG(s.st_mode))
                {
		    this->dir_entry[this->entries].type = FILE_ENTRY;
#ifdef HAVE_LINUX
		    /*
		     * with the new Linux kernel (0.99.13) all MSDOS
		     * files have are "executables", so, when working with
		     * msdos file systems, we have to ignore those bits.
		     */
		    this->dir_entry[this->entries].executable =
			((s.st_mode & 0111) && !msdosfs) ? 1 : 0;
#else
		    this->dir_entry[this->entries].executable =
			(s.st_mode & 0111) ? 1 : 0;
#endif
                }
                else
                {
                    if (S_ISFIFO(s.st_mode))
			this->dir_entry[this->entries].type = FIFO_ENTRY;
		    else
                        if (S_ISSOCK(s.st_mode))
			    this->dir_entry[this->entries].type = SOCKET_ENTRY;
			else
			    this->dir_entry[this->entries].type = FILE_ENTRY;

                    this->dir_entry[this->entries].executable = OFF;
                }
            }
            this->dir_entry[this->entries].size = s.st_size;
        }
        else
        {
            this->dir_entry[this->entries].type = SYMLINK_ENTRY;
            sz = readlink(d->d_name, temp, PATH_MAX);
            this->dir_entry[this->entries].size = (sz == -1) ? 0 : sz;
        }
        
        if (s.st_uid == old_uid)
            memcpy(this->dir_entry[this->entries].owner,
		   this->dir_entry[this->entries - 1].owner, 7);
        else
        {
            pwd = getpwuid(old_uid = s.st_uid);
            if (pwd)
                sprintf(this->dir_entry[this->entries].owner, "%-7s",
			pwd->pw_name);
	    else
                sprintf(this->dir_entry[this->entries].owner, "%-7d",s.st_uid);
	}

        if (s.st_gid == old_gid)
            memcpy(this->dir_entry[this->entries].group,
		   this->dir_entry[this->entries - 1].group, 7);
        else
        {
            grp = getgrgid(old_gid = s.st_gid);
            if (grp)
                sprintf(this->dir_entry[this->entries].group, "%-7s",
			grp->gr_name);
	    else
                sprintf(this->dir_entry[this->entries].group, "%-7d",s.st_gid);
	}

	time = localtime(&s.st_mtime);
	if ((hour = time->tm_hour % 12) == 0) hour = 12;
	sprintf(this->dir_entry[this->entries].date,"%2d-%02d-%02d %2d:%02d%c",
	        time->tm_mon + 1, time->tm_mday, time->tm_year,
		hour, time->tm_min, (time->tm_hour < 12) ? 'a' : 'p');

        if (this->dir_entry[this->entries].name)
            free(this->dir_entry[this->entries].name);
        if (!(this->dir_entry[this->entries].name =
	      (char *)malloc((namelen = strlen(d->d_name)) + 1)))
            fatal("not enough memory");
        strcpy(this->dir_entry[this->entries].name, d->d_name);
        this->maxname = max(this->maxname, namelen);
    }

    if (this->entries == MAX_ENTRIES && readdir(this->dir))
    {
        sprintf(temp, "Too many directory entries!\
 Only %d will be displayed. (press any key)", MAX_ENTRIES);
        status(temp, 1, 1, 1, MSG_ERROR);
    }

    if (verify)    
    {
        for (i = 0; i < old_entries; i++)
            if (old_dir_entry[i].name)
                free(old_dir_entry[i].name);
        free(old_dir_entry);
    }

    if (backdir_index != -1)
    {
        tmp = this->dir_entry[0];
        this->dir_entry[0] = this->dir_entry[backdir_index];
        this->dir_entry[backdir_index] = tmp;
        qsort(this->dir_entry + 1, this->entries - 1,
	      sizeof(_dir_entry), sortfn);
    }
    else
        qsort(this->dir_entry, this->entries, sizeof(_dir_entry), sortfn);

    return 1;
}


int panel_getnext(panel *this)
{
    int i, retval;

    if (this->end) { this->end = 0; return -1; }
    
    for (i = this->last_index + 1; i < this->entries; i++)
        if (this->dir_entry[i].selected)
        {
            this->found_selection = 1;
            return this->last_index = i;
        }     
    this->end = !this->found_selection;
    retval = this->found_selection ? -1 : this->current_entry;
    this->last_index = -1;
    this->found_selection = 0;
    return (retval == this->current_entry && 
            this->dir_entry[this->current_entry].type == DIR_ENTRY) ?
	    -1 : retval;
}


void panel_update(panel *this)
{
    int i, limit;
    tty_status status;

    tty_save(&status);

    for (i = this->first_on_screen; 
         i < this->entries && (i - this->first_on_screen < this->lines - 2);
         i++)
        panel_update_entry(this, i);

    tty_background(PanelBackground);
    memset(temp, ' ', this->columns);
    limit = min(this->lines - 2, this->on_screen);
    for (; i < limit; i++)
    {
        window_cursormove(this->win, i - this->first_on_screen + 1, 1);
        window_write(temp, this->columns - 2);
    }
    this->on_screen = this->entries;
    tty_restore(&status);
}


void panel_trunc_fname(char *fname, char *dest, int len)
{
    int flen;

    if ((flen = strlen(fname)) > len)
    {
        dest[0] = dest[1] = dest[2] = '.';
        memcpy(dest + 3, fname + flen - len + 3, len - 3);
    }
    else
        memcpy(dest, fname, flen);
}


void panel_update_path(panel *this)
{
    char *t;
    int i, len;

    memset(temp, ' ', this->columns);
    panel_trunc_fname(this->path, t = temp, len = this->columns - 4 - 11);
    for (i = 0; i < len; i++)
	if (!is_print(t[i])) t[i] = '?';

    tty_bright(PanelPathBrightness);
    tty_foreground(PanelPath);
    tty_background(PanelFrame);
    window_cursormove(this->win, 0, 2);
    window_write(temp, len);
}


void panel_update_size(panel *this)
{
#ifdef HAVE_STATFS
    struct statfs fsbuf;
    char sz[16];

    statfs(this->path, &fsbuf);
    sprintf(sz, "%10d", fsbuf.f_bfree * fsbuf.f_bsize);

    tty_bright(PanelDeviceFreeSpaceBrightness);
    tty_foreground(PanelDeviceFreeSpace);
    tty_background(PanelFrame);
    window_cursormove(this->win, 0, this->columns - 2 - 10);
    window_write(sz, strlen(sz));
#endif
}


void panel_update_info(panel *this)
{
    char str[256], temp_rights[16], temp_name[16], *t;
    int i, total_size = 0, total_files = 0, index, mode, len, maxname;
    
    if (this->selected_files)
    {
        while((index = panel_getnext(this)) != -1)
        {
            total_files ++;
            total_size += this->dir_entry[index].size;
        }
        sprintf(str, "%d bytes in %d file(s)", total_size, total_files);
        tty_bright(PanelFilesInfoBrightness);
        tty_foreground(PanelFilesInfo);
        this->info_dirty = ON;
    }
    else
    {
	if (InfoDisplay == OFF)
	    if (this->info_dirty)
	    {
		*str = 0;
		this->info_dirty = 0;
	        goto display_info;
	    }
	    else return;

        strcpy(temp_rights, rights);
        mode = this->dir_entry[this->current_entry].mode;

	if (S_ISLNK(mode))  temp_rights[0] = 'l';
	if (S_ISDIR(mode))  temp_rights[0] = 'd';
	if (S_ISCHR(mode))  temp_rights[0] = 'c';
	if (S_ISBLK(mode))  temp_rights[0] = 'b';
	if (S_ISFIFO(mode)) temp_rights[0] = 'p';
	if (S_ISSOCK(mode)) temp_rights[0] = 's';

        for (i = 0; i < 9; mode >>= 1, i++)
            if (!(mode & 1))
                temp_rights[9 - i] = '-';

	mode = this->dir_entry[this->current_entry].mode;
	if (mode & S_ISUID)
	    temp_rights[3] = (temp_rights[3] == 'x') ? 's' : 'S';
	if (mode & S_ISGID)
	    temp_rights[6] = (temp_rights[6] == 'x') ? 's' : 'S';
	if (mode & S_ISVTX)
	    temp_rights[9] = (temp_rights[9] == 'x') ? 't' : 'T';

	maxname = this->columns - 26;
	len = min(strlen(this->dir_entry[this->current_entry].name), maxname);

	memcpy(t = str, this->dir_entry[this->current_entry].name, len);
	for (i = 0; i < len; i++)
	    if (!is_print(t[i])) t[i] = '?';
	memset(str + len, ' ', maxname - len);

        if (this->dir_entry[this->current_entry].type == DIR_ENTRY)
	    sprintf(str + maxname, " %10s %10s",
		    (strcmp(this->dir_entry[this->current_entry].name, "..") ==
		     0) ?
		    "UP--DIR" : "SUB-DIR", temp_rights);
	else
	    sprintf(str + maxname, " %10d %10s",
		    this->dir_entry[this->current_entry].size, temp_rights);

      display_info:
        tty_bright(PanelFileInfoBrightness);
        tty_foreground(PanelFileInfo);
    }    
    memcpy(temp, str, len = strlen(str));
    memset(temp + len, ' ', this->columns - 2 - len);
    tty_background(PanelFrame);
    window_cursormove(this->win, this->lines - 1, 2);
    window_write(temp, this->columns - 4);
}


void panel_update_entry(panel *this, int entry)
{
    int len, i, mode, reserved;
    char buf[256], temp_rights[16], *t;
    int foreground, background, bright;

    memset(temp, ' ', this->columns);
    reserved = (this->mode == 4) ? 4 : 20;
    len = min(strlen(this->dir_entry[entry].name),
	      (unsigned)this->columns - reserved);
    memcpy(t = &temp[1], this->dir_entry[entry].name, len);
    for (i = 0; i < len; i++)
	if (!is_print(t[i])) t[i] = '?';
    if (len == (unsigned)this->columns - reserved) len--;
    
    if (entry || this->path[1] == 0)
        switch (this->dir_entry[entry].type)
        {
            case DIR_ENTRY:	temp[len + 1] = '/';
            			break;
            			
            case FILE_ENTRY:	if (this->dir_entry[entry].executable)
            			    temp[len + 1] = '*';
            			break;

            case SYMLINK_ENTRY: temp[len + 1] = '@';
                                break;

            case FIFO_ENTRY:    temp[len + 1] = '|';
                                break;

            case SOCKET_ENTRY:  temp[len + 1] = '=';
                                break;
        }

    switch (this->mode)
    {
        case 0:

            memcpy(temp + this->columns - 2 - 16,
		   this->dir_entry[entry].owner, 7);
	    memcpy(temp + this->columns - 2 -  8,
		   this->dir_entry[entry].group, 7);
	    break;

	case 1:

	    memcpy(temp + this->columns - 2 - 16,
		   this->dir_entry[entry].date, 15);
	    break;

	case 2:

	    sprintf(buf, "%10d", this->dir_entry[entry].size);
	    memcpy(temp + this->columns - 2 - 11, buf, 10);
	    break;

	case 3:

	    strcpy(temp_rights, rights);

	    mode = this->dir_entry[entry].mode;

	    if (S_ISLNK(mode))  temp_rights[0] = 'l';
	    if (S_ISDIR(mode))  temp_rights[0] = 'd';
	    if (S_ISCHR(mode))  temp_rights[0] = 'c';
	    if (S_ISBLK(mode))  temp_rights[0] = 'b';
	    if (S_ISFIFO(mode)) temp_rights[0] = 'p';
	    if (S_ISSOCK(mode)) temp_rights[0] = 's';

	    for (i = 0; i < 9; mode >>= 1, i++)
	        if (!(mode & 1))
		    temp_rights[9 - i] = '-';

	    mode = this->dir_entry[entry].mode;

	    if (mode & S_ISUID)
	        temp_rights[3] = (temp_rights[3] == 'x') ? 's' : 'S';
	    if (mode & S_ISGID)
                temp_rights[6] = (temp_rights[6] == 'x') ? 's' : 'S';
	    if (mode & S_ISVTX)
                temp_rights[9] = (temp_rights[9] == 'x') ? 't' : 'T';

	    memcpy(temp + this->columns - 2 - 11, temp_rights, 10);
	    break;

	case 4:

	    break;

    	default:

	    fatal("invalid mode");
    }

    if (entry == this->current_entry && this->focus == ON)
    {
	foreground = this->dir_entry[entry].selected ?
	             PanelCurrentSelectedFile : PanelCurrentNotSelectedFile;
	background = PanelCurrentFile;
    }
    else
    {
	foreground = this->dir_entry[entry].selected ?
	             PanelSelectedFile : PanelNotSelectedFile;
	background = PanelBackground;
    }

    bright = this->dir_entry[entry].selected ?
	     PanelSelectedFileBrightness : PanelNotSelectedFileBrightness;

    tty_foreground(foreground);
    tty_background(background);
    tty_bright(bright);

    window_cursormove(this->win, entry - this->first_on_screen + 1, 1);
    window_write(temp, this->columns - 2);
}


void panel_update_frame(panel *this)
{
    int i;
    tty_status status;
    char buf[256];

    tty_save(&status);
   
    tty_foreground(PanelFrame);
    tty_bright(OFF);
    tty_reverse(ON);
    
    if (FrameDisplay == ON)
    {
	for (i = 0; i < this->lines; i++)
	{
	    window_cursormove(this->win, i, 0);
	    tty_putch(' ');
	}

	for (i = 0; i < this->lines; i++)
	{
	    window_cursormove(this->win, i, this->columns - 1);
	    tty_putch(' ');
	}
    }

    memset(buf, ' ', 256);
    window_cursormove(this->win, 0, 0);
    tty_write(buf, this->columns);

    window_cursormove(this->win, this->lines - 1, 0);
    tty_write(buf, this->columns);

    tty_restore(&status);
}


void panel_setfocus(panel *this, int status, panel *link)
{
    this->focus = status;
    panel_update_entry(this, this->current_entry);
    if (this->focus)
        if (chdir(this->path) == -1)
            panel_recover(this);
}


char *panel_getpath(panel *this, char *temppath, unsigned len)
{
    panel_trunc_fname(this->path, temppath, len);
    temppath[min(len, strlen(this->path))] = 0;
    return temppath;
}


int filelength(int handle)
{
    int temp, length;
    
    temp = tell(handle);
    lseek(handle, 0, SEEK_END);
    length = tell(handle);
    lseek(handle, temp, SEEK_SET);
    return length;
}


#define COPY_BUFFER_SIZE	(128*1024)

enum 
{ 
    SD_OK, 
    SD_CANCEL,
    S_OPENERR, 
    S_READERR, 
    D_CREATERR, 
    D_WRITEERR, 
    SD_NOSPACE,
    SD_NOMEM,
};

char copyerr[8][50] =
{
    " ",
    " ",
    "can't open source file !",
    "can't read from source file !",
    "can't create destination file !",
    "can't write to destination file !",
    "not enough free disk space !",
    "not enough memory !",
};


int panel_copy(panel *this, char *src, char *dest, int mode)
{
    char *buf;
    char msg[PATH_MAX];
    int shandle, dhandle, i, len, err, key, memsize, bytes_to_transfer;
    
    for (buf = dest + strlen(dest); *buf != '/'; buf--);
    buf++;

    if (this->chkdest && !access(dest, 0))
    {

        if (this->selected_files)
            sprintf(msg, "(COPY) Destination file %s exists.\
 (Overwrite/Skip/All/Cancel) ?", cutname(buf, 0, 0));
        else
            sprintf(msg, "(COPY) Destination file %s exists.\
 (Overwrite/Skip/Cancel) ?", cutname(buf, 0, 0));

        switch (key = status(msg, 1, 1, 1, MSG_ERROR))
        {
            case KEY_ENTER:
            case 'O':
            case 'o':

           	break;

            case 'a':
            case 'A':

		this->chkdest = OFF;
		break;

            case 's':
            case 'S':

		return SD_OK;

            default:

		return SD_CANCEL;
	}
    }

    sprintf(msg, "(COPY) Copying %s to %s",
            cutname(src, 0, 1), cutname(buf, 1, 1));
    status(msg, 0, 0, 0, MSG_WARNING);

    if ((shandle = open(src, O_RDONLY)) == -1)
        return S_OPENERR;

    if ((dhandle = creat(dest, mode)) == -1)
    {
        close(shandle);
        return D_CREATERR;
    }

    memsize = min(len = filelength(shandle), COPY_BUFFER_SIZE);
    if (S_ISBLK(mode) || S_ISCHR(mode))
    {
        len = MAXINT;
        memsize = COPY_BUFFER_SIZE;
    }

    if (len == 0)
    {
	close(shandle);
	close(dhandle);
	return SD_OK;
    }

    if ((buf = (char *)malloc(memsize)) == NULL)
    {
        close(shandle);
        close(dhandle);
        remove(dest);
        return SD_NOMEM;
    }

    for (i = 0; i < len; i += COPY_BUFFER_SIZE)
    {
	bytes_to_transfer = min(len - i, memsize);
	err = read(shandle, buf, bytes_to_transfer);
        if (err != bytes_to_transfer)
            if (err >= 0)
            {
                if (err)
                    bytes_to_transfer = err;
                else
                {
		    close(shandle);
		    close(dhandle);
		    free(buf);
		    return SD_OK;
		}
            }
            else
	    {
		close(shandle);
		close(dhandle);
		free(buf);
		remove(dest);
		return S_READERR;
	    }

        err = write(dhandle, buf, bytes_to_transfer);
        if (err != bytes_to_transfer)
            if (err >= 0)
            {
 		close(shandle);
		close(dhandle);
		free(buf);
		remove(dest);
		return SD_NOSPACE;
            }
	    else
	    {
		close(shandle);
		close(dhandle);
		free(buf);
		remove(dest);
		return (errno == ENOSPC) ? SD_NOSPACE : D_WRITEERR;
            }
    }

    close(shandle);
    close(dhandle);
    free(buf);
    return SD_OK;
}


enum 
{ 
    FT_OK, 
    FT_CANCEL,
    T_CREATERR, 
    F_DELETERR,
};


char moveerr[7][50] =
{
    " ",
    " ",
    "can't create destination file !",
    "can't delete source file !",
};


int panel_move(panel *this, char *from, char *to)
{
    int key;
    char *buf;
    char msg[PATH_MAX];

    for (buf = to + strlen(to); *buf != '/'; buf--);
    buf++;

    if (this->chkdest && !access(to, 0))
    {

        if (this->selected_files)
            sprintf(msg, "(MOVE) Destination file %s exists.\
 (Overwrite/Skip/All/Cancel) ?", cutname(buf, 0, 0));
        else
            sprintf(msg, "(MOVE) Destination file %s exists.\
 (Overwrite/Skip/Cancel) ?", cutname(buf, 0, 0));

        switch (key = status(msg, 1, 1, 1, MSG_ERROR))
        {
            case KEY_ENTER:
            case 'O':
            case 'o':

            	break;

            case 'a':
            case 'A':

		this->chkdest = OFF;
		break;

            case 's':
            case 'S':

		return FT_OK;

            default:
		
		return FT_CANCEL;
	}
    }
    
    sprintf(msg, "(MOVE) Moving %s to %s",
            cutname(from, 0, 1), cutname(buf, 1, 1));
    status(msg, 0, 0, 0, MSG_WARNING);

    unlink(to);
    if (link(from, to) == -1)
        return T_CREATERR;
    if (unlink(from) == -1)
        return F_DELETERR;
    return FT_OK;
}


int panel_verify_name(char *file_name)
{
    char *ptr;
    
    for (ptr = file_name; *ptr; ptr++)
        if (*ptr == '/')
        {
       	    status(bad_name, 1, 1, 1, MSG_ERROR);
            return 0;
        }
    return 1;
}


int panel_getindex(panel *this, char *str)
{
    int i;

    str[this->maxname] = 0;

    for (i = 0; i < this->entries && strcmp(str,this->dir_entry[i].name); i++);

    if (i == this->entries)
    {
        for (i = 0;
	     i < this->entries && strcasecmp(str, this->dir_entry[i].name);
	     i++);

        if (i == this->entries)
            return 0;
    }
    return i;
}


int panel_action(panel *this, int action, panel *link,
		 void *aux_info, int repeat_count)
{
    int need_update, need_update_all, old_current_entry;
    int done = 0, i, err = 0, entry, len, back;
    int rename_dir = 0, verify = aux_info == (void *)-1;
    char oldentry[256], msg[PATH_MAX], text[PATH_MAX];
    char oldpath[PATH_MAX], *ptr;

    switch (action)
    {
        case act_ENTER:

	    switch (this->dir_entry[this->current_entry].type)
	    {
		case DIR_ENTRY:

		    if ((strcmp(this->dir_entry[this->current_entry].name,
				"..") == 0) && (strcmp(this->path, "/") == 0)) 
		        break;
		    back = strcmp(this->dir_entry[this->current_entry].name,
				  "..") ? 0 : 1;
		    strcpy(oldpath, this->path);

		    if (!panel_getdirinfo(this,
			        this->dir_entry[this->current_entry].name, ON))
		    {
			if (back)
			    panel_recover(this);
			else
			    status(no_perm, 1, 1, 1, MSG_ERROR);
			break; 
		    }
		    if (back)
		    {
			for (ptr = oldpath + strlen(oldpath) - 1;
			     *ptr != '/';
			     ptr--);
			ptr++;
			for (i = 0;
			     strcmp(this->dir_entry[i].name, ptr) &&
			     i < this->entries;
			     i++);
			this->current_entry = i;
			this->first_on_screen = max(0,
						    this->current_entry -
						    (this->lines - 2) + 1);
		    }
		    else
		        this->current_entry = this->first_on_screen = 0;

		    panel_update_path(this);
		    panel_update(this);
		    panel_update_size(this);
		    if (strcmp(this->path, link->path) == 0)
		        panel_action(link, act_REFRESH, this, (void *)-1, 1);
		    panel_update_size(link);
		    done = 1;
		    break;

		case FILE_ENTRY:

		    if (this->dir_entry[this->current_entry].executable)
		    {
			uitclock(OFF);
			tty_end();
			tty_putscreen((char *)aux_info);
			sprintf(text, "\"%s\"",
				this->dir_entry[this->current_entry].name);
			restore_signals();
			signals_dfl();
			system(text);
			signals(SignalsStatus);
			ignore_signals();
			write(1, "\n\n", 2);
			tty_init();
			panel_nooptimizations(this);
			panel_nooptimizations(link);
			edit_puts(this->edt,
				  this->dir_entry[this->current_entry].name);
			done = -1;
		    }
		    break;
		}
	    break;

        case act_COPY:

	    this->chkdest = ON;
	    if (!this->selected_files)
	    {
		signals(OFF);
		if (this->dir_entry[this->current_entry].type == DIR_ENTRY)
		    break;
		sprintf(msg, "(COPY) Copying file %s to : ",
		       cutname(this->dir_entry[this->current_entry].name,0,0));
		strcpy(oldpath, link->path);
		strcat(oldpath, "/");
		strcat(oldpath, this->dir_entry[this->current_entry].name);
		if (!edit_gets(this->edt, msg, text, oldpath)) break;

		strcpy(oldpath, this->path);
		strcat(oldpath, "/");
		strcat(oldpath, this->dir_entry[this->current_entry].name);

		if (strcmp(text, oldpath) == 0)
		{
		    sprintf(msg,
		       "(COPY) Can't copy file %s to itself! (press any key)",
		       cutname(this->dir_entry[this->current_entry].name,0,0));
		    status(msg, 1, 1, 1, MSG_ERROR);
		    break;
		}

		err=panel_copy(this,this->dir_entry[this->current_entry].name,
			       text,this->dir_entry[this->current_entry].mode);

		if (err != SD_OK)
		{
		    if (err == SD_CANCEL) break;
		    sprintf(msg, "(COPY) Error copying file %s : %s",
			    cutname(this->dir_entry[this->current_entry].name,
				    0, 1),
			    copyerr[err]);
		    status(msg, 1, 1, 1, MSG_ERROR);
		    break;
		}

		status(NULL, 0, 0, 1, MSG_OK);
		panel_update_size(this);
		panel_update_size(link);
	    }
	    else
	    {
		signals(OFF);
		if (strcmp(this->path, link->path) == 0)
		{
		    status("(COPY) Can't do it! (press any key)",
			   1, 1, 1, MSG_ERROR);
		    break;
		}
		if (status("(COPY) Copying file(s) ...  \
(ENTER to continue, TAB to cancel)",
			   1, 0, 1, MSG_WARNING) != KEY_ENTER)
		break;

		strcpy(temp, link->path);
		strcat(temp, "/");
		len = strlen(temp);
		signals(ON);

		while ((entry = panel_getnext(this)) != -1)
		{
		    strcpy(temp + len, this->dir_entry[entry].name);
		    if (*UserHeartAttack)
		    {
			*UserHeartAttack = 0;
			status("Operation aborted. (press any key)",
			       1, 1, 1, MSG_ERROR);
			break;
		    }

		    err = panel_copy(this, this->dir_entry[entry].name,
				     temp, this->dir_entry[entry].mode);

		    if (err != SD_OK)
		    {
			if (err == SD_CANCEL) break;

			sprintf(msg, "(COPY) Error copying file %s : %s",
				cutname(this->dir_entry[entry].name, 0, 1),
				copyerr[err]);

			if (status(msg, 1, 1, 0, MSG_ERROR) != KEY_ENTER)
			    break;
		    }
		    else
			this->dir_entry[entry].selected = 0;

		    panel_update_size(this);
		    panel_update_size(link);
		}
		status(NULL, 0, 0, 1, MSG_OK);
	    }

	    signals(OFF);

	    if (!panel_getdirinfo(link, link->path, ON))
		panel_recover(link);
	    else
	    {
		panel_update(link);
		panel_update_info(link);
	    }

	    if (!panel_getdirinfo(this, this->path, ON))
		panel_recover(this);
	    else
	    {
		panel_update(this);
		panel_update_info(this);
	    }
	    break;

        case act_DELETE:

	    if (!this->selected_files &&
		this->dir_entry[this->current_entry].type == DIR_ENTRY)
	    {
		signals(OFF);
		if (strcmp(this->dir_entry[this->current_entry].name, "..")==0)
 		    break;

		sprintf(msg, "(RMDIR) Deleting directory %s ?\
(ENTER to continue, TAB to cancel)",
                        cutname(this->dir_entry[this->current_entry].name,
                                0, 0));

		if (status(msg, 1, 0, 1, MSG_ERROR) != KEY_ENTER) break;

		strcpy(temp, this->path);
		strcat(temp, "/");
		strcat(temp, this->dir_entry[this->current_entry].name);

		if (rmdir(this->dir_entry[this->current_entry].name)  == -1 &&
                    unlink(this->dir_entry[this->current_entry].name) == -1)
		{
		    sprintf(msg, "(RMDIR) Can't remove directory %s !",
                            cutname(this->dir_entry[this->current_entry].name,
                                    0, 0));
		    status(msg, 1, 1, 0, MSG_ERROR);
		    status(NULL, 0, 0, 1, MSG_OK);
		    break;
		}
		else
		    if (strcmp(temp, link->path) == 0)
		    {
		        strcpy(link->path, this->path);
		        panel_action(link, act_REFRESH, this, NULL, 1);
		    }

		panel_update_size(this);
		panel_update_size(link);
	    }
	    else
	    {
		signals(OFF);

		if (status("(DEL) Deleting file(s) ...  \
(ENTER to continue, TAB to cancel)",
                           1, 0, 1, MSG_ERROR) != KEY_ENTER)
		    break;

		for (i = 0; i < this->entries; i++)
		    if (this->dir_entry[i].selected) break;

		signals(ON);

		while ((entry = panel_getnext(this)) != -1)
		{
		    sprintf(msg, "(DEL) Deleting %s",
                            cutname(this->dir_entry[entry].name, 0, 1));
		    status(msg, 0, 0, 0, MSG_ERROR); 

		    if (*UserHeartAttack)
		    {
			*UserHeartAttack = 0;
			status("Operation aborted. Not too late I hope ...  \
(press any key)", 1, 1, 1, MSG_ERROR);
			break;
		    }

		    if (unlink(this->dir_entry[entry].name) == -1)
		    {
			sprintf(msg, "(DEL) Can't delete file %s !  \
(press any key)",
                                cutname(this->dir_entry[entry].name, 0, 1));

			if (status(msg, 1, 1, 1, MSG_ERROR) != KEY_ENTER)
			    break;
		    }
		    else
		        this->dir_entry[entry].selected = 0;
		}

		if (i != this->entries)
		    this->current_entry = i;

		signals(OFF);
		panel_update_size(this);
		panel_update_size(link);
		status(NULL, 0, 0, 1, MSG_OK);
	    }

	    signals(OFF);

	    if (!panel_getdirinfo(this, this->path, ON))
	        panel_recover(this);
	    else
	    {
		this->current_entry = min(this->current_entry,this->entries-1);
		this->first_on_screen = max(0, this->current_entry -
                                               (this->lines - 2) + 1);
		panel_update(this);
		panel_update_info(this);
	    }

	    if (strcmp(this->path, panel_getpath(link, temp, PATH_MAX)) == 0)
	    {
		if (!panel_getdirinfo(link, link->path, ON))
		    panel_recover(link);
		else
		{
		    link->current_entry = min(link->current_entry,
					      link->entries - 1);
		    link->first_on_screen = max(0, link->current_entry -
                                                   (link->lines - 2) + 1);
		    panel_update(link);
		    panel_update_info(link);
		}
	    }
	    break;

        case act_SELECT:

	    if (this->dir_entry[this->current_entry].type != DIR_ENTRY)
	    {
		this->dir_entry[this->current_entry].selected++;
		this->selected_files +=
		    this->dir_entry[this->current_entry].selected ? 1 : -1;
		panel_update_entry(this, this->current_entry);
	    }

	    panel_action(this, act_DOWN, link, NULL, repeat_count);
	    break;

        case act_SELECTALL:

	    for (i = 0; i < this->entries; i++)
	        if (this->dir_entry[i].type != DIR_ENTRY)
		{
		    this->dir_entry[i].selected = ON;
		    this->selected_files++;
		}

	    panel_update(this);
	    done = 1;
	    break;

        case act_UNSELECTALL:

	    for (i = 0; i < this->entries; i++)
	        this->dir_entry[i].selected = OFF;

	    this->selected_files = 0;
	    panel_update(this);
	    done = 1;
	    break;

        case act_TOGGLE:

	    for (i = 0; i < this->entries; i++)
	        if (this->dir_entry[i].type != DIR_ENTRY)
		    this->dir_entry[i].selected = !this->dir_entry[i].selected;

	    this->selected_files = this->entries - this->selected_files;
	    panel_update(this);
	    done = 1;
	    break;

        case act_MKDIR:

	    if (!edit_gets(this->edt, "(MKDIR) Enter directory name : ",
                           temp, NULL))
	        break;

	    if (!temp[0] || !panel_verify_name(temp)) break;

	    if (mkdir(temp, S_IFDIR | S_IRWXU | S_IRWXG) == -1)
	    {
		status(no_perm, 1, 1, 1, MSG_ERROR); 
		break; 
	    }

	    if (!panel_getdirinfo(this, this->path, ON))
	        panel_recover(this);
	    else
	    {
		this->current_entry = panel_getindex(this, temp);
		this->first_on_screen = max(0, this->current_entry -
                                               (this->lines - 2) + 1);
		panel_update(this);
		panel_update_info(this);
		panel_update_size(this);
	    }

	    if (strcmp(this->path, panel_getpath(link, temp, PATH_MAX)) == 0)
	    {
		if (!panel_getdirinfo(link, link->path, ON))
		    panel_recover(link);
		else
		{
		    panel_update(link);
		    panel_update_info(link);
		}
	    }

	    panel_update_size(link);
	    break;

        case act_MOVE:

            this->chkdest = ON;

	    if (!this->selected_files &&
                (this->dir_entry[this->current_entry].type != DIR_ENTRY))
	    {
		signals(OFF);
		sprintf(msg, "(MOVE) Moving file %s to : ",
                       cutname(this->dir_entry[this->current_entry].name,0,0));

		strcpy(oldpath, link->path);
		strcat(oldpath, "/");
		strcat(oldpath, this->dir_entry[this->current_entry].name);
		if (!edit_gets(this->edt, msg, text, oldpath)) break;
		
		strcpy(oldpath, this->path);
		strcat(oldpath, "/");
		strcat(oldpath, this->dir_entry[this->current_entry].name);
		
		if (strcmp(text, oldpath) == 0)
		{
		    sprintf(msg, "(MOVE) Can't move file %s to itself! \
(press any key)",
                            cutname(this->dir_entry[this->current_entry].name,
                                    0, 0));
		    status(msg, 1, 1, 1, MSG_ERROR);
		    break;
		}

		if ((err=panel_move(this,
                                    this->dir_entry[this->current_entry].name,
                                    text)) != FT_OK)
		{
		    if (err == FT_CANCEL) break;
		    sprintf(msg, "(MOVE) Error moving file %s : %s",
                            cutname(this->dir_entry[this->current_entry].name,
                                    0, 1), moveerr[err]);
		    status(msg, 1, 1, 1, MSG_ERROR);
		}

		status(NULL, 0, 0, 1, MSG_OK);
		panel_update_size(this);
		panel_update_size(link);
	    }
	    else
	    {
		if (!this->selected_files &&
                    this->dir_entry[this->current_entry].type == DIR_ENTRY)
		{
		    signals(OFF);

		    if (strcmp(this->dir_entry[this->current_entry].name, "..")
                        == 0) break;

		    if (!edit_gets(this->edt, "(RENAME) New directory name : ",
                                   text, NULL) || !text[0])
		        break;

		    if (rename(this->dir_entry[this->current_entry].name, text)
                        == -1)
		    {
			sprintf(msg, "(RENAME) Error renaming directory : %s !\
  (press any key)", cutname(this->dir_entry[this->current_entry].name, 0, 0));
			status(msg, 1, 1, 1, MSG_ERROR);
			break;
		    }

		    rename_dir = 1;
		    status(NULL, 0, 0, 1, MSG_OK);
		}
		else
		{
		    signals(OFF);
		    if (strcmp(this->path, link->path) == 0)
		    {
			status("(MOVE) Can't do it! (press any key)",
                               1, 1, 1, MSG_ERROR);
			break;
		    }

		    if (status("(MOVE) Moving file(s) ...  \
(ENTER to continue, TAB to cancel)", 1, 0, 1, MSG_WARNING) != KEY_ENTER)
			break;

		    strcpy(temp, link->path);
		    strcat(temp, "/");
		    len = strlen(temp);
		    signals(ON);

		    while ((entry = panel_getnext(this)) != -1)
		    {
			strcpy(temp + len, this->dir_entry[entry].name);
			if (*UserHeartAttack)
			{
			    *UserHeartAttack = 0;
			    status("Operation aborted. (press any key)",
                                   1, 1, 1, MSG_ERROR);
			    break;
			}

			if ((err = panel_move(this,this->dir_entry[entry].name,
                                              temp)) != FT_OK)
			{
			    if (err == FT_CANCEL) break;

			    sprintf(msg, "(MOVE) Error moving file %s : %s",
                                    cutname(this->dir_entry[entry].name, 0, 1),
                                    moveerr[err]);

			    if (status(msg, 1, 1, 0, MSG_ERROR) != KEY_ENTER)
			    break;
			}
			else
			    this->dir_entry[entry].selected = 0;
		    }
		    signals(OFF);
		    status(NULL, 0, 0, 1, MSG_OK);
		}
	    }
	    signals(OFF);

	    if (!panel_getdirinfo(link, link->path, ON))
	        panel_recover(link);
	    else
	    {
		link->current_entry = min(link->current_entry,
                                          link->entries - 1);
		link->first_on_screen = max(0, link->current_entry -
                                               (link->lines - 2) + 1);
		panel_update(link);
		panel_update_info(link);
		panel_update_size(link);
	    }

	    if (!panel_getdirinfo(this, this->path, ON))
	        panel_recover(this);
	    else
	    {
		this->current_entry = rename_dir ? panel_getindex(this, text) :
                                                   min(this->current_entry,
                                                       this->entries - 1);

		this->first_on_screen = max(0, this->current_entry -
                                               (this->lines - 2) + 1);
		panel_update(this);
		panel_update_info(this);
		panel_update_size(this);
	    }
	    break;

        case act_UP:

	    need_update_all = need_update = 0;
	    while (repeat_count--)
	    {
		if (this->current_entry == 0) break;
		if (this->current_entry == this->first_on_screen)
		{
		    this->current_entry--;
		    this->first_on_screen--;
		    need_update_all = 1;
		}
		else
		{
		    this->current_entry--;
		    if (!need_update)
		        panel_update_entry(this, this->current_entry + 1);
		    need_update = 1;
		}
	    }
	    if (need_update_all) panel_update(this);
	    else
	        if (need_update)
	            panel_update_entry(this, this->current_entry);
	        else
	            done = -1;
	    break;
	    
        case act_DOWN:

	    need_update_all = need_update = 0;
	    while (repeat_count--)
	    {
		if (this->current_entry < this->entries - 1)
		this->current_entry++;
		else break;
		if (this->current_entry - this->first_on_screen >=
                    this->lines - 2) 
		{
		    this->first_on_screen++;
		    need_update_all = 1;
		    continue;
		}
		if (!need_update)
		    panel_update_entry(this, this->current_entry - 1);
		need_update = 1;
	    }
	    if (need_update_all) panel_update(this);
	    else
	        if (need_update)
	    	    panel_update_entry(this, this->current_entry);
	        else
	            done = -1;
	    break;

	case act_PGUP:

	    if (this->current_entry == 0)
	    {
		done = -1;
		break;
	    }

	    old_current_entry = this->current_entry;
	    
	    if (this->current_entry < this->lines - 2)
	        this->current_entry = this->first_on_screen = 0;
	    else
	    {
		this->current_entry -= this->lines - 2;
		this->first_on_screen = max(0, this->first_on_screen -
                                               (this->lines - 2));
	    }

	    if (this->entries > this->lines - 2)
	        panel_update(this);
	    else
	    {
		panel_update_entry(this, old_current_entry);
		panel_update_entry(this, this->current_entry);
	    }

	    break;

        case act_PGDOWN:

	    if (this->current_entry == this->entries - 1)
	    {
		done = -1;
		break;
	    }

	    old_current_entry = this->current_entry;
	    
	    if (this->entries - 1 - this->first_on_screen < this->lines - 2)
	        this->current_entry = this->entries - 1;
	    else
	        if (this->entries - 1 - this->current_entry < this->lines - 2)
	        {
		    this->current_entry = this->entries - 1;
		    this->first_on_screen = this->entries - 1 -
                                            (this->lines - 2) + 1;
	        }
	        else
	        {
		    this->current_entry += this->lines - 2;
		    this->first_on_screen =
                        min(this->first_on_screen + this->lines - 2,
                        (this->entries - 1) - (this->lines - 2) + 1);
	         }

	    if (this->entries > this->lines - 2)
	        panel_update(this);
	    else
	    {
		panel_update_entry(this, old_current_entry);
		panel_update_entry(this, this->current_entry);
	    }

	    break;

        case act_HOME:

	    if (this->current_entry == 0) break;
	    this->current_entry = this->first_on_screen = 0;
	    panel_update(this);
	    break;

	case act_END:

	    if (this->current_entry == this->entries - 1) break;
	    this->current_entry = this->entries - 1;
	    this->first_on_screen = max(0, (this->entries - 1) -
                                           (this->lines - 2) + 1);
	    panel_update(this);
	    break;

	case act_CHDIR:

	    this->first_on_screen = this->current_entry = 0;
	    strcpy(this->path, (char *)aux_info);
	    panel_action(this, act_REFRESH, NULL, NULL, 1);
	    break;

	case act_MODE:

	    this->mode = (this->mode + 1) % FILE_DISPLAY_MODES;
	    panel_update(this);
	    break;

	case act_SWITCH:

	    xchg(&this->lines,   &link->lines);
	    xchg(&this->columns, &link->columns);
	    xchg(&this->begin_x, &link->begin_x);
	    xchg(&this->begin_y, &link->begin_y);
	    free(this->win);
	    this->win = window_init(this->begin_x, this->begin_y,
                                    this->lines, this->columns);
	    free(link->win);
	    link->win = window_init(link->begin_x, link->begin_y,
                                    link->lines, link->columns);
	    break;

	case act_REFRESH:

	    if (this->dir_entry[this->current_entry].name)
	        strcpy(oldentry, this->dir_entry[this->current_entry].name);

	    if (!panel_getdirinfo(this, this->path, verify))
	        panel_recover(this);
	    else
	        if (verify)
	        {
		    this->current_entry = min(panel_getindex(this, oldentry),
                                              this->entries - 1);
		    this->first_on_screen = max(0, this->current_entry -
                                                   (this->lines - 2) + 1);
	        }
	        else
	            this->current_entry = this->first_on_screen = 0;

	    panel_update_frame(this);
	    panel_update_path(this);
	    panel_update_info(this);
	    panel_update_size(this);
	    panel_update(this);
	    break;

        default:

	    fatal("no action");
	    break;
    }

    if (done != -1)
        panel_update_info(this);

    return done;
}
