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

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 <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <values.h>
#include <limits.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>


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

#include "xmalloc.h"
#include "window.h"
#include "status.h"
#include "tty.h"
#include "panel.h"
#include "fnmatch.h"
#include "config.h"
#include "misc.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  signals_status;
extern int  AnsiColorSequences;


#ifndef HAVE_CONST
#define const
#endif	/* HAVE_CONST */


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


#define FILE_DISPLAY_MODES	5

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


#define FILE_SORT_METHODS	9

char *FileSortMethod[FILE_SORT_METHODS] = 
{
    "Name",
    "Extension",
    "Size",
    "Date",
    "Mode",
    "OwnerId",
    "GroupId",
    "OwnerName",
    "GroupName",
};


#define PANEL_FIELDS	17

static char *PanelFields[PANEL_FIELDS] =
{
    "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	/* not HAVE_LINUX */
static int PanelColors[PANEL_FIELDS] = 
{
    WHITE, BLACK, WHITE, ON, WHITE, OFF, WHITE, BLACK, 
    WHITE, BLACK, OFF, BLACK, OFF, BLACK, OFF, BLACK, OFF
};
#endif	/* not HAVE_LINUX */

#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 StartupFileSortMethod;
static int CurrentSortMethod;
static int LeadingDotMatch = OFF;
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);


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)
{
    char *data;
    int index, i;
    static int configured;
    
    panel *this = (panel *)xmalloc(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->entries	  = 0;
    this->selected_files  = 0;
    this->last_index      = -1;
    this->display_mode    = this->sort_method = 0;
    this->found_selection = this->end = 0;
    this->current_entry   = 0;
    this->first_on_screen = 0;
    this->on_screen       = INT_MAX / 2;
    this->temp		  = xmalloc(this->columns);
    this->dir		  = NULL;

#ifdef HAVE_LINUX
    this->msdosfs   = 0;
#endif	/* HAVE_LINUX */

    UserHeartAttack = _UserHeartAttack;
    this->dir_entry = NULL;

    if (path)
    {
	this->path = xmalloc(strlen(path) + 1);
	strcpy(this->path, path);
	clear_path(this->path);
    }
    else
	this->path = xcalloc(256, 1);

    if (this->path[0] == 0)
    {
	this->path[0] = '.';
	this->path[1] = 0;
    }

    this->pathlen = strlen(this->path);

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

    if (configured)
    {
        this->display_mode = StartupFileDisplayMode;
        this->sort_method  = StartupFileSortMethod;
        return this;
    }


    use_section("[UIT-Setup]");

    StartupFileDisplayMode = get_int_var("StartupFileDisplayMode",
    					 FileDisplayMode,FILE_DISPLAY_MODES,0);
    this->display_mode = StartupFileDisplayMode;

    StartupFileSortMethod = get_int_var("StartupFileSortMethod",
    					FileSortMethod,FILE_SORT_METHODS,0);
    this->sort_method = StartupFileSortMethod;

    InfoDisplay     = get_flag_var("InfoDisplay",     ON);
    FrameDisplay    = get_flag_var("InfoDisplay",     ON);
    LeadingDotMatch = get_flag_var("LeadingDotMatch", ON);
	

    use_section(AnsiColorSequences ? cSection : bwSection);

    get_colorset_var(PanelColors, PanelFields, PANEL_FIELDS);

    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->dir_entry);
    free(this->temp);

    window_end(this->win);
    
    free(this);
}


static int get_fos(panel *this)
{
    return max(0, this->current_entry - (this->lines - 2) + 1);
}


static int get_centered_fos(panel *this)
{
    int lines = (this->lines - 2);
    int tmp = this->current_entry - (lines >> 1);

    if (tmp + lines >= this->entries)
	return max(0, this->entries - lines);
    else
	return max(0, tmp);
}


static 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;
    char *pfirst, *psecond;
    dir_entry_struct *first  = (dir_entry_struct *)_first;
    dir_entry_struct *second = (dir_entry_struct *)_second;
    int first_is_dir   = first->type  == DIR_ENTRY;
    int second_is_dir  = second->type == DIR_ENTRY;    

    if (first_is_dir != second_is_dir)
	return first_is_dir ? -1 : 1;

    switch (CurrentSortMethod)
    {
	case SORT_BY_NAME:

	    pfirst = psecond = NULL;
	    return strcmp(first->name, second->name);

	case SORT_BY_EXTENSION:

	    pfirst  = strrchr(first->name,  '.');
	    psecond = strrchr(second->name, '.');

	    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);
	    break;

	case SORT_BY_SIZE:

	    if (first->size == second->size)
		return strcmp(first->name, second->name);
	    return first->size - second->size;

	case SORT_BY_DATE:

	    if (first->mtime == second->mtime)
		return strcmp(first->name, second->name);
	    return first->mtime - second->mtime;

	case SORT_BY_MODE:

	    if (first->mode == second->mode)
		return strcmp(first->name, second->name);
	    return first->mode - second->mode;

	case SORT_BY_OWNER_ID:

	    if (first->uid == second->uid)
		return strcmp(first->name, second->name);
	    return first->uid - second->uid;

	case SORT_BY_GROUP_ID:

	    if (first->gid == second->gid)
		return strcmp(first->name, second->name);
	    return first->gid - second->gid;

	case SORT_BY_OWNER_NAME:

	    if (first->uid == second->uid)
		return strcmp(first->name, second->name);
	    return strcmp(first->owner, second->owner);

	case SORT_BY_GROUP_NAME:

	    if (first->gid == second->gid)
		return strcmp(first->name, second->name);
	    return strcmp(first->group, second->group);

	default:
	    fatal("bad sort method");
    }
}


void panel_nooptimizations(panel *this)
{
    this->on_screen = INT_MAX / 2;
}


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;

    this->first_on_screen = this->current_entry = 0;

    msg = xmalloc(80 + this->pathlen + 1);
    sprintf(msg, "Can't get permission in directory %s ! (press any key).",
	    this->path);
    status(msg, 1, 1, 1, MSG_ERROR);
    free(msg);

    if (strcmp(this->path, "/") == 0)
        fatal("can't get permission in root directory");

    strcpy(this->path, "/");
    this->pathlen = 1;
    chdir(this->path);
    panel_action(this, act_REFRESH, NULL, NULL, 1);
}

void abort_mem(void)
{
    fatal("bad heap");
}


int panel_getdirinfo(panel *this, char *directory, int verify)
{
#ifdef HAVE_STATFS
    struct statfs fstat;
#endif	/* HAVE_STATFS */

    DIR *tmpdir;
    struct stat s;
    struct dirent *d;
    char *old_path;
    struct tm *time;
    struct group *grp;
    struct passwd *pwd;
    int dot_found = 0, dotdot_found = 0;
    dir_entry_struct *old_dir_entry = NULL, tmp;
    int i, j, old_uid = -1, old_gid = -1, namelen;
    int old_entries = 0, backdir_index = -1, sz, hour;


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

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

    if (this->dir) closedir(this->dir);
    this->dir = tmpdir;
    old_path = xmalloc(this->pathlen + 1);
    strcpy(old_path, this->path);

    if (directory[0] == '/')
    {
	this->path = xmalloc(strlen(directory) + 1);
	strcpy(this->path, directory);
    }
    else
    {
      retry:
	errno = 0;
	if (getcwd(this->path, this->pathlen) == NULL)
	{
	    if (errno == ERANGE)
	    {
		this->path = xrealloc(this->path, this->pathlen + 1 + 64);
		this->pathlen += 64;
		goto retry; 
	    }

	    errno = 0;

	    this->pathlen = strlen(this->path);

	    if (directory[0] == '.' && directory[1] == '.')
	    {
		char *ptr = strrchr(this->path, '/');

		if (ptr == NULL)
		    fatal("bad path");

		*ptr = 0;
	    }
	    else
	    {
		this->path = xrealloc(this->path, this->pathlen + 1 + 1 +
						  strlen(directory));
		strcat(this->path, "/");
		strcat(this->path, directory);
	    }
	}
    }

    clear_path(this->path);
    this->pathlen = strlen(this->path);

#ifdef HAVE_LINUX
    statfs(".", &fstat);
    this->msdosfs = fstat.f_type == 0x4d44;
    					/* I can't get this number without */
 					/* including linux/msdos_fs.h :-(  */
#endif	/* HAVE_LINUX */

    if (verify = (verify && this->selected_files &&
		  strcmp(old_path, this->path) == 0))
    {
	old_dir_entry	= this->dir_entry;
	old_entries	= this->entries;
        this->dir_entry	= NULL;
    }
    else
	if (this->dir_entry)
	{
	    for (i = 0; i < this->entries; i++)
		if (this->dir_entry[i].name)
		    free(this->dir_entry[i].name);

	    free(this->dir_entry);
	    this->dir_entry = NULL;
	}

    free(old_path);

    this->dir_entry = (dir_entry_struct *)xmalloc(sizeof(dir_entry_struct));

    for (this->selected_files = this->maxname = this->entries = 0;
	 d = readdir(this->dir);
	 this->entries++)
    {
	if (d->d_name[0] == '.' && !d->d_name[1])	/* ignore "." */
	{
	    dot_found = 1;
	    this->entries--;
	    continue;
	}

        if (d->d_name[0] == '.' && d->d_name[1] == '.' && d->d_name[2] == 0)
	{
            dotdot_found = 1;
            if (this->path[1])
                backdir_index = this->entries;
            else				/* ignore ".." if root dir */
	    {
		this->entries--;
		continue;
	    }
	}

	this->dir_entry=(dir_entry_struct *)xrealloc(this->dir_entry,
						     (this->entries + 1) *
						     sizeof(dir_entry_struct));

        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;
                }

            if (j == old_entries)
		this->dir_entry[this->entries].selected = 0;
        }
        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
		    /*
		     * starting with the 0.99.13 Linux kernel all MSDOS
		     * files have are "executables", so, when working with
		     * msdos file systems, we have to ignore those bits ...
		     * At least when displaying them in the panel. :-(
		     */
		    this->dir_entry[this->entries].executable =
			((s.st_mode & 0111) && !this->msdosfs) ? 1 : 0;
#else	/* not HAVE_LINUX */
		    this->dir_entry[this->entries].executable =
			(s.st_mode & 0111) ? 1 : 0;
#endif	/* not HAVE_LINUX */
                }
                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 = my_readlink(d->d_name);
            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);
	}

	this->dir_entry[this->entries].mtime = s.st_mtime;
	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');

        this->dir_entry[this->entries].name =
	      xmalloc((namelen = strlen(d->d_name)) + 1);

        strcpy(this->dir_entry[this->entries].name, d->d_name);
        this->maxname = max(this->maxname, namelen);
    }

    if (!dot_found)			/* check for bad directories */
    {
	status("'.' directory missing ! (press any key)", 1, 1, 1, MSG_ERROR);
	return 0;
    }

    if (!dotdot_found)			/* check for bad directories */
    {
	status("'..' directory missing ! (press any key)", 1, 1, 1, MSG_ERROR);
	return 0;
    }

    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);
    }

    CurrentSortMethod = this->sort_method;

    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_struct), sortfn);
    }
    else
	qsort(this->dir_entry, this->entries, sizeof(dir_entry_struct), 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);
    tty_cursor(OFF);

    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(this->temp, ' ', this->columns);
    limit = min(this->lines - 2, this->on_screen);

    for (; i < limit; i++)
    {
	window_cursormove_notify(this->win, i - this->first_on_screen + 1, 1);
	window_write(this->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;
    tty_status status;

    tty_save(&status);
    tty_cursor(OFF);

    memset(this->temp, ' ', this->columns);
    panel_trunc_fname(this->path, t = this->temp, len = this->columns-4-11-1);

    for (i = 0; i < len; i++)
	if (!is_print(t[i])) t[i] = '?';

    tty_bright(PanelPathBrightness);
    tty_foreground(PanelPath);
    tty_background(PanelFrame);

    window_cursormove_notify(this->win, 0, 2);
    window_write(this->temp, len);

    tty_restore(&status);
}


void panel_update_size(panel *this)
{
    char sz[16];
    tty_status status;

#ifdef HAVE_STATFS
    struct statfs fsbuf;
#endif	/* not HAVE_STATFS */

    tty_save(&status);
    tty_cursor(OFF);

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

    tty_bright(PanelDeviceFreeSpaceBrightness);
    tty_foreground(PanelDeviceFreeSpace);
#else	/* not HAVE_STATFS */
    memset(sz, ' ', 11);

    tty_bright(OFF);
    tty_foreground(PanelFrame);
#endif	/* not HAVE_STATFS */

    tty_background(PanelFrame);

    window_cursormove_notify(this->win, 0, this->columns - 2 - 11);
    window_write(sz, 11);

    tty_restore(&status);
}


void panel_update_info(panel *this)
{
    int i, total_size = 0, index, mode, len, maxname;
    char str[256], temp_rights[16], temp_name[16], *t;
    
    if (this->selected_files)
    {
	for (i = 0; i < this->entries; i++)
	    if (this->dir_entry[i].selected)
		total_size += this->dir_entry[i].size;

        sprintf(str, "%d bytes in %d file(s)",total_size,this->selected_files);
        tty_bright(PanelFilesInfoBrightness);
        tty_foreground(PanelFilesInfo);
    }
    else
    {
	if (InfoDisplay == OFF)
	{
	    *str = 0;
	    goto display_info;
	}

        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(this->temp, str, len = strlen(str));
    memset(this->temp + len, ' ', this->columns - 2 - len);
    tty_background(PanelFrame);
    window_cursormove_notify(this->win, this->lines - 1, 2);
    window_write(this->temp, this->columns - 4);
}


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

    memset(this->temp, ' ', this->columns);
    reserved = (this->display_mode == 4) ? 4 : 20;
    len = min(strlen(this->dir_entry[entry].name),
	      (unsigned)this->columns - reserved);

    memcpy(t = &this->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:	this->temp[len + 1] = '/';
            			break;
            			
            case FILE_ENTRY:	if (this->dir_entry[entry].executable)
            			    this->temp[len + 1] = '*';
            			break;

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

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

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

    switch (this->display_mode)
    {
        case DISPLAY_OWNER_GROUP:

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

	case DISPLAY_DATE_TIME:

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

	case DISPLAY_SIZE:

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

	case DISPLAY_MODE:

	    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(this->temp + this->columns - 2 - 11, temp_rights, 10);
	    break;

	case DISPLAY_FULL_NAME:

	    break;

    	default:

	    fatal("invalid mode");
    }

    if (this->dir_entry[entry].selected)
	this->temp[this->columns - 3] = '*';

    if (entry == this->current_entry)
	this->temp[0] = this->focus?ACTIVE_PANEL_MARKER:INACTIVE_PANEL_MARKER;

    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_bright(bright);
    tty_foreground(foreground);
    tty_background(background);

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


void panel_update_frame(panel *this)
{
    int i;
    tty_status status;

    tty_save(&status);
    tty_cursor(OFF);

    tty_bright(OFF);
    tty_foreground(PanelFrame);
    tty_background(PanelFrame);

    if (FrameDisplay == ON)
    {
	for (i = 1; i < this->lines - 1; i++)
	{
	    window_cursormove_notify(this->win, i, 0);
	    window_putch(' ');
	}

	for (i = 1; i < this->lines - 1; i++)
	{
	    window_cursormove_notify(this->win, i, this->columns - 1);
	    window_putch(' ');
	}
    }

    window_cursormove_notify(this->win, 0, 0);
    window_write("  ", 2);
    window_cursormove_notify(this->win, 0, this->columns - 11 - 1 - 2);
    window_putch(' ');
    window_cursormove_notify(this->win, 0, this->columns - 2);
    window_write("  ", 2);

    window_cursormove_notify(this->win, this->lines - 1, 0);
    window_write("  ", 2);
    window_cursormove_notify(this->win, this->lines - 1, this->columns - 2);
    window_write("  ", 2);

    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, this->pathlen)] = 0;
    return temppath;
}


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


#define COPY_BUFFER_SIZE	(128*1024)

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

char *copyerr[8] =
{
    "",
    "",
    "",
    "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 !",
};


int panel_copy(panel *this, char *src, char *dest, int mode)
{
    char *buf;
    char *dest_file;
    char msg[80 + 1];		/* big enough in this version */
    int shandle, dhandle, i, len, err, memsize, bytes_to_transfer;
    
    dest_file = strrchr(dest, '/');

    if (dest_file == NULL)
	return D_CREATERR;

    dest_file++;

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

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

        switch (status(msg, 1, 1, 1, MSG_ERROR))
        {
            case key_ENTER:
            case 'O':
            case 'o':

           	break;

            case 'a':
            case 'A':

		if (this->selected_files)
		{
		    this->chkdest = OFF;
		    break;
		}

            case 's':
            case 'S':

		if (this->selected_files) return SD_SKIP;

            default:

		return SD_CANCEL;
	}
    }

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

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

#ifdef HAVE_LINUX
    if (this->msdosfs)
	mode &= ~0111;	/* ignoring the executable bits when copying from a
			   msdos file system */
#endif	/* HAVE_LINUX */

    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 = INT_MAX;
        memsize = COPY_BUFFER_SIZE;
    }

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

    buf = xmalloc(memsize);

    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,
    FT_SKIP,
    T_CREATERR,
    F_DELETERR,
    F_STATERR,
    T_STATERR,
};


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


int panel_move(panel *this, char *from, char *to)
{
    int err;
    char *to_file;
    char msg[80 + 1];		/* big enough in this version */
    struct stat from_statbuf;
    struct stat to_statbuf;


    to_file = strrchr(to, '/');

    if (to_file == NULL)
	return T_STATERR;

    to_file++;

    if (this->chkdest && !access(to, 0))
    {
        if (this->selected_files)
            sprintf(msg, "(MOVE) Destination file %s exists.\
 (Overwrite/Skip/All/Cancel) ?", cutname(to_file, 0, 0));
        else
            sprintf(msg, "(MOVE) Destination file %s exists.\
 (Overwrite/Cancel) ?", cutname(to_file, 0, 0));

        switch (status(msg, 1, 1, 1, MSG_ERROR))
        {
            case key_ENTER:
            case 'O':
            case 'o':

            	break;

            case 'a':
            case 'A':

		if (this->selected_files)
		{
		    this->chkdest = OFF;
		    break;
		}

            case 's':
            case 'S':

		if (this->selected_files) return FT_SKIP;

            default:

		return FT_CANCEL;
	}
    }

    sprintf(msg, "(MOVE) Moving  %s to %s",
            cutname(from, 0, 1), cutname(to_file, 1, 1));
    status(msg, 0, 0, 0, MSG_WARNING);

    if (stat(from, &from_statbuf) == -1)
	return F_STATERR;

    if (stat(to, &to_statbuf) == -1)
    {
	/* This is very very ugly ... :-< */
	char c = *(to_file - 1);
	*(to_file - 1) = 0;
	err = (*to) ? stat(to, &to_statbuf) : 0;
	*(to_file - 1) = c;
	if (err == -1)
	    return T_STATERR;
    }

    if (to_statbuf.st_dev != from_statbuf.st_dev)
    {
	/*
	 * from & to resides on different file systems so we have to
	 * copy / remove.
	 */
	err = panel_copy(this, from, to, from_statbuf.st_mode);

	if (err == SD_OK)     goto remove_from;
	if (err == SD_CANCEL) return FT_CANCEL;
	if (err == SD_SKIP)   return FT_SKIP;
	sprintf(msg, "(COPY) Error copying file %s: %s",
	        cutname(from, 0, 1), copyerr[err]);
	status(msg, 1, 1, 1, MSG_ERROR);
	return FT_OK;
    }

    unlink(to);

    if (link(from, to) == -1)
        return T_CREATERR;

  remove_from:

    if (unlink(from) == -1)
        return F_DELETERR;

    return FT_OK;
}


int panel_check_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, len = strlen(str);
    char *temp = xmalloc(len + 1);


    strncpy(temp, str, len = min(len, this->maxname));
    temp[len] = 0;

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

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

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

    free(temp);
    return i;
}


int panel_act_ENTER(panel *this, panel *link, char *screen)
{
    int i, len, back, done = 0;
    char *old_path, *cmd, *ptr;

    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;

	    old_path = xmalloc((len = this->pathlen) + 1);
	    strcpy(old_path, 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)
	    {
		ptr = strrchr(old_path, '/');
		
		if (ptr == NULL)
		    panel_recover(this);
		
		ptr++;

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

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

		this->current_entry = i;
		this->first_on_screen = get_centered_fos(this);
	    }
	    else
	        this->current_entry = this->first_on_screen = 0;

	    free(old_path);

	    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)
	    {
		len = 32 + strlen(this->dir_entry[this->current_entry].name)+1;

		uitclock(OFF);
		tty_set_mode(TTY_CANONIC);
		tty_putscreen(screen);
		cmd = xmalloc(len);
		sprintf(cmd, "exec ./\"%s\"",
			this->dir_entry[this->current_entry].name);
		restore_signals();
		signals_dfl();
		system(cmd);
		free(cmd);
		signals(signals_status);
		ignore_signals();
		write(1, "\n\n", 2);
		tty_set_mode(TTY_NONCANONIC);
		tty_touch();
		panel_nooptimizations(this);
		panel_nooptimizations(link);
		edit_puts(this->edt,this->dir_entry[this->current_entry].name);
		done = -1;
	    }

	    break;
	}

    return done;
}


void panel_act_COPY(panel *this, panel *link)
{
    char *file, *temp;
    char msg[132 + 1];
    char *input = NULL;
    int len, err, entry;


    this->chkdest = ON;

    if (!this->selected_files)
    {
	signals(OFF);

	if (this->dir_entry[this->current_entry].type == DIR_ENTRY)
	    return;

	sprintf(msg, "(COPY) Copying file %s to: ",
		cutname(this->dir_entry[this->current_entry].name, 0, 0));

	len  = 1 + strlen(this->dir_entry[this->current_entry].name) + 1;
	file = xmalloc(strlen(link->path) + len);

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

	if (!edit_gets(this->edt, msg, &input, file))
	    return;

	file = xrealloc(file, this->pathlen + len);

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

	if (strcmp(input, file) == 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);
	    free(file);
	    free(input);
	    return;
	}

	free(file);

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

	free(input);

	if (err != SD_OK)
	{
	    if (err != SD_CANCEL)
	    {
		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);
	    }

	    return;
	}

	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);
	    return;
	}

	if (status("(COPY) Copying file(s) ...  (ENTER to continue, TAB to cancel)",
		   1, 0, 1, MSG_WARNING) != key_ENTER)
	    return;

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

	while ((entry = panel_getnext(this)) != -1)
	{
	    temp = xrealloc(temp, len+strlen(this->dir_entry[entry].name)+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;
		if (err == SD_SKIP) continue;

		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);
	}

	free(temp);
	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);
    }
}


void panel_act_DELETE(panel *this, panel *link)
{
    char *temp;
    int len, i, entry;
    char msg[132 + 1];


    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)
	    return;

	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)
	    return;

	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);
	    return;
	}
	else
	{
	    len  = this->pathlen + 1 +
		   strlen(this->dir_entry[this->current_entry].name) + 1;
	    temp = xmalloc(len);

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

	    if (strcmp(temp, link->path) == 0)
	    {
	        link->path = xrealloc(link->path, this->pathlen + 1);
	        link->pathlen = this->pathlen;
	        strcpy(link->path, this->path);
	        panel_action(link, act_REFRESH, this, NULL, 1);
	    }

	    free(temp);
	}

	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)
	    return;

	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 = get_centered_fos(this);
	panel_update(this);
	panel_update_info(this);
    }

    if (strcmp(this->path, link->path) == 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 = get_centered_fos(link);
	    panel_update(link);
	    panel_update_info(link);
	}
    }
}


void panel_act_MKDIR(panel *this, panel *link)
{
    char *input = NULL;

    if (!edit_gets(this->edt, "(MKDIR) Enter directory name: ",
		   &input, NULL))
	return;

    if (!input[0] || !panel_check_name(input))
    {
	free(input);
	return;
    }

    if (mkdir(input, S_IFDIR | S_IRWXU | S_IRWXG) == -1)
    {
	status(no_perm, 1, 1, 1, MSG_ERROR); 
	free(input);
	return;
    }

    if (!panel_getdirinfo(this, this->path, ON))
        panel_recover(this);
    else
    {
	this->current_entry = panel_getindex(this, input);
	this->first_on_screen = get_centered_fos(this);
	panel_update(this);
	panel_update_info(this);
	panel_update_size(this);
    }

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

    panel_update_size(link);
    free(input);
}


void panel_act_MOVE(panel *this, panel *link)
{
    char *file, *temp;
    char msg[132 + 1];
    char *input = NULL;
    int i, entry, err, len, rename_dir = 0;


    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));

	len  = 1 + strlen(this->dir_entry[this->current_entry].name) + 1;
	file = xmalloc(strlen(link->path) + len);

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

	if (!edit_gets(this->edt, msg, &input, file))
	    return;

	file = xrealloc(file, this->pathlen + len);

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

	if (strcmp(input, file) == 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);
	    free(file);
	    free(input);
	    return;
	}

	free(file);

	err = panel_move(this,this->dir_entry[this->current_entry].name,input);

	if (err != FT_OK)
	{
	    if (err == FT_CANCEL)
	    {
		free(input);
		return;
	    }
	    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)
		return;

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

	    if (rename(this->dir_entry[this->current_entry].name, input) == -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);
		free(input);
		return;
	    }

	    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);
		return;
	    }

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

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

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

	    while ((entry = panel_getnext(this)) != -1)
	    {
		temp = xrealloc(temp,
				len + strlen(this->dir_entry[entry].name) + 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_move(this,this->dir_entry[entry].name,temp);

		if (err != FT_OK)
		{
		    if (err == FT_CANCEL) break;
		    if (err == FT_SKIP) continue;

		    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;
	    }

	    free(temp);

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

	    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 = get_centered_fos(link);
	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, input) :
					   min(this->current_entry,
					       this->entries - 1);

	this->first_on_screen = get_centered_fos(this);
	panel_update(this);
	panel_update_info(this);
	panel_update_size(this);
    }

    free(input);
}


void panel_act_CHDIR(panel *this, panel *link, char *new_dir)
{
    this->first_on_screen = this->current_entry = 0;
    this->path = xrealloc(this->path, (this->pathlen = strlen(new_dir)) + 1);
    strcpy(this->path, new_dir);
    panel_action(this, act_REFRESH, NULL, NULL, 1);
    if (strcmp(this->path, link->path) == 0)
	panel_action(link, act_REFRESH, NULL, NULL, 1);
}


void panel_act_REFRESH(panel *this, panel *link, void *aux_info)
{
    int len, verify;
    char *old_entry;

    if (this->dir_entry && this->dir_entry[this->current_entry].name)
    {
	len = strlen(this->dir_entry[this->current_entry].name) + 1;
	old_entry = xmalloc(len);
	strcpy(old_entry, this->dir_entry[this->current_entry].name);
    }
    else
	old_entry = "", len = 0;

    verify = aux_info == (void *)-1;

    if (!panel_getdirinfo(this, this->path, verify))
        panel_recover(this);
    else
        if (verify)
        {
	    this->current_entry = min(panel_getindex(this, old_entry),
				      this->entries - 1);
	    this->first_on_screen = get_centered_fos(this);
        }
        else
            this->current_entry = this->first_on_screen = 0;

    if (len) free(old_entry);
    panel_update_frame(this);
    panel_update_path(this);
    panel_update_info(this);
    panel_update_size(this);
    panel_update(this);
}


int panel_action(panel *this, int action, panel *link,
		 void *aux_info, int repeat_count)
{
    int i, done = 0;
    int need_update, need_update_all, old_current_entry;


    switch (action)
    {
        case act_ENTER:

	    done = panel_act_ENTER(this, link, (char *)aux_info);
	    break;

        case act_COPY:

	    panel_act_COPY(this, link);
	    break;

        case act_DELETE:

	    panel_act_DELETE(this, 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_SELECT_ALL:

	    this->selected_files = 0;

	    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_UNSELECT_ALL:

	    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:

	    panel_act_MKDIR(this, link);
	    break;

        case act_MOVE:

	    panel_act_MOVE(this, link);
	    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 = get_fos(this);
	        }
	        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 = get_fos(this);
	    panel_update(this);
	    break;

	case act_CHDIR:

	    panel_act_CHDIR(this, link, (char *)aux_info);
	    break;

	case act_DISPLAY_NEXT_MODE:

	    this->display_mode = (this->display_mode + 1) % FILE_DISPLAY_MODES;
	    goto all_display_modes;

	case act_DISPLAY_OWNER_GROUP:
	case act_DISPLAY_DATE_TIME:
	case act_DISPLAY_SIZE:
	case act_DISPLAY_MODE:
	case act_DISPLAY_FULL_NAME:

	    this->display_mode = action - act_DISPLAY_OWNER_GROUP;

	  all_display_modes:

	    panel_update(this);
	    break;

	case act_SORT_NEXT_METHOD:

	    this->sort_method = (this->sort_method + 1) % FILE_SORT_METHODS;
	    goto all_sort_methodes;

	case act_SORT_BY_NAME:
	case act_SORT_BY_EXTENSION:
	case act_SORT_BY_SIZE:
	case act_SORT_BY_DATE:
	case act_SORT_BY_MODE:
	case act_SORT_BY_OWNER_ID:
	case act_SORT_BY_GROUP_ID:
	case act_SORT_BY_OWNER_NAME:
	case act_SORT_BY_GROUP_NAME:

	    this->sort_method = action - act_SORT_BY_NAME;

	  all_sort_methodes:

	    CurrentSortMethod = this->sort_method;

	    if (this->path[0] == 0) /* root dir */
		qsort(this->dir_entry, this->entries,
		      sizeof(dir_entry_struct), sortfn);
	    else
		qsort(this->dir_entry + 1, this->entries - 1,
		      sizeof(dir_entry_struct), sortfn);

	    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);
	    window_end(this->win);
	    this->win = window_init(this->begin_x, this->begin_y,
                                    this->lines, this->columns);
	    window_end(link->win);
	    link->win = window_init(link->begin_x, link->begin_y,
                                    link->lines, link->columns);
	    break;

	case act_PATTERN_SELECT:
	case act_PATTERN_UNSELECT:
	
	    for (i = 0; i < this->entries; i++)
		if (this->dir_entry[i].type != DIR_ENTRY)
		{
		    int fnm_flags = FNM_PATHNAME;
		    if (LeadingDotMatch == OFF)
		        fnm_flags |= FNM_PERIOD;

		    if (fnmatch(aux_info,this->dir_entry[i].name,fnm_flags) !=
		    	FNM_NOMATCH)
		    {
		        switch (action)
		        {
			    case act_PATTERN_SELECT:

				if (!this->dir_entry[i].selected)
				{
				    this->dir_entry[i].selected = ON;
				    this->selected_files++;
				}
				break;

			    case act_PATTERN_UNSELECT:

				if (this->dir_entry[i].selected)
				{
				    this->dir_entry[i].selected = OFF;
				    this->selected_files--;
				}
				break;
			}
		    }
		}

	    panel_update(this);
	    done = 1;
	    break;

	case act_REFRESH:

	    panel_act_REFRESH(this, link, aux_info);
	    break;

        default:

	    fatal("no action");
	    break;
    }

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

    return done;
}
