/*****************************************************************************
 *					                                    
 *	astrash - AfterStep Trashcan, Version 0.9
 *	(C) 1997 Carsten Weinholz <weinholz@hni.uni-paderborn.de>
 *
 *	Idea and some code borrowed from Trash - the OffiX waste basket
 *	Copyright (C) 1996  César Crusius
 *					                                    
 *  	This program is free software; you can redistribute it and/or modify
 *  	it under the terms of the GNU General Public License as published by
 *  	the Free Software Foundation; either version 2 of the License, or
 *  	(at your option) any later version.
 *
 *  	This program is distributed in the hope that it will be useful,
 *  	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  	GNU General Public License for more details.
 *
 *  	You should have received a copy of the GNU General Public License
 *  	along with this program; if not, write to the Free Software
 *  	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *					                                    
 ****************************************************************************/
#include <sys/param.h>
#include <sys/stat.h>
#include <limits.h>
#include <OffiX/DragAndDrop.h>
#include <OffiX/DragAndDropTypes.h>
#include <X11/StringDefs.h>
#include <X11/IntrinsicP.h>
#include <X11/Shell.h>
#include <X11/ShellP.h>
#include <X11/xpm.h>
#include <X11/Xaw/Viewport.h>
#include <X11/Xaw/Dialog.h>
#include <X11/Xaw/DialogP.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/List.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Toggle.h>
#include <X11/Xaw/Text.h>
#include <X11/extensions/shape.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>



/* Version ******************************************************************/
#define VERSION "0.9"

/* Definitions **************************************************************/
#define APPNAME		"AsTrash"
#define max(A,B) 	((A>B)?(A):(B))
#define LISTNAME 	"index.db" 
#define TRASHDIR 	".trash"
#define PIDFILE  	"trash.pid"
#define DEFAULTTIME	"6"
#define LEFTBUTTON  	1
#define LISTWIDTH   	400
#define LISTHEIGHT  	100
#define INFOWIDTH   	163
#define FILLWIDTH   	32

/* XPM struct and icons *****************************************************/
typedef struct _XpmIcon {
	Pixmap pixmap;
	Pixmap mask;
	XpmAttributes attributes;       
	struct XpmIcon *next;
} XpmIcon;

XpmIcon *Plate, *TrashEmpty, *TrashFull;

#include "plate.xpm"
#include "trash.xpm"
#include "trash_full.xpm"

/* List struct **************************************************************/
typedef struct _List {
	struct _List *next;
	int    index;
	time_t time;
	char   entry;	/* muss letztes Element sein ! */
} List;

/* Functions ****************************************************************/
void Help(void);
void CreateGC(void);
void CreateWidgets(int, char **);
void CreatePopups(void);
void CreateXPMIcons(void);
void CheckArgs(int, char**);
void StartLoop(void);
void DeleteHandler(Widget, XtPointer, XEvent *, Boolean *);
void ExposeHandler(Widget, XtPointer, XEvent *, Boolean *);
void ButtonHandler(Widget, XtPointer, XEvent *, Boolean *);
void DropHandler(Widget, XtPointer, XEvent *, Boolean *);
void PopdownHandler(Widget, XtPointer, XEvent *, Boolean *);
void ExitHandler();
void SetWMIcon(XpmIcon *);
void XPMError(int);
void RedrawToplevelIcon(XpmIcon *, Boolean);
int  InitTrashDir(char *);
int  ListSize(char *);
void ReadList(char *);
void WriteList(char *);
void DeleteList(char *);
void UpdateIcons(void);
void ItemclickCallback(Widget, XEvent *, String *, Cardinal *);
void DummyCallback(Widget, XEvent *, String *, Cardinal *);
int CheckTimeCallback(Widget, XEvent *, String *, Cardinal *);
String *CreateTrashList();
void UpdateTrashCallback(Widget, XtPointer, XtPointer);
void ClearTrashCallback(Widget, XtPointer, XtPointer);
void DeleteItemCallback(Widget, XtPointer, XtPointer);
void UndeleteItemCallback(Widget, XtPointer, XtPointer);
void CloseCallback(Widget, XtPointer, XtPointer);
void ClearAfterCallback(Widget, XtPointer, XtPointer);
int  ProcessOneFile(char *);
int  TrashMove(char *,char *);
void AddListEntry(int, char *);
void RemoveListEntry(List *);
void UpdateList ();
int TrashDelete(char *);
int Update(int);
void ClearTrash();
void exitOnSignal();
void updateSignal();
void writePId (char *);
int readPId (char *);

/* Global stuff *************************************************************/
Widget Toplevel;
XtAppContext AsTrashApp;

/* Local stuff **************************************************************/
static char HelpMessage[] = {
	"astrash - Version %s\n"
	"usage:  astrash "
	"[-geometry spec]"
	"[-shape]"
	"[-clearonexit]"
	"[-clearafter num]"
	"[-aserror]"
	"\n" 
};
static String fallback_resources[] = {
#include "astrash.h"
NULL
};
static Display *dpy;
static Window root;
static Atom WMDeleteWindow;
static Widget TrashPopup;
static GC NormalGC;
static unsigned int ToplevelWidth, ToplevelHeight;
static Boolean shape;
static int GeoWidth, GeoHeight;
static List *TrashList = NULL;
static int ListEntries;
static DialogWidget CurrentItem, KeepTime;
static Widget TrashListW, ClearAfterW, ClearOnExitW;
static int clearonexitFlag, clearaftertime;
static int aserror;

/****************************************************************************/
void main
	(int argc, char *argv[])
{
	CheckArgs (argc, argv);
	
	if (!InitTrashDir(TRASHDIR))
		exit(1);

	if (readPId (PIDFILE))
		exit(1);

	atexit (ExitHandler);		
	signal (SIGTERM, exitOnSignal);
	signal (SIGUSR1, updateSignal);
	
	CreateWidgets(argc, argv);

	writePId (PIDFILE);
	StartLoop();	
}

/****************************************************************************/
void CheckArgs
	(int argc, char *argv[])
{
	int i;

	GeoWidth = 0;
	GeoHeight= 0;

	shape = 0;

	clearonexitFlag = 0;
	clearaftertime  = -1;
	aserror         = 0;
				
	for (i = 1; i < argc; i++) {

		if (argv[i][0] != '-')
			continue;
			
		if (!strncmp (argv[i], "-geometry", 9)) {

			char *sizep;
			 
			if ((argv[i+1][0] == '+') || (argv[i+1][0] == '-')) { 
			
				i++;
				continue;
			}
				
			if (argv[i+1][0] == 'x')
				sizep = &(argv[i+1][1]);
			else
				sizep = &(argv[i+1][0]);
			   
			GeoWidth = atoi(sizep);
			
			if (!(sizep = index (sizep, 'x')))
				continue;
	
			GeoHeight = atoi(sizep+1);		
		}
		else if (!strncmp(argv[i], "-shape", 6)) {
		
			shape = 1;
		}
		else if (!strncmp(argv[i], "-clearonexit", 12)) {

			clearonexitFlag = 1;
		}
		else if (!strncmp(argv[i], "-clearafter", 11)) {
		
			clearaftertime = atoi (argv[i+1]);
		}
		else if (!strncmp(argv[i], "-aserror", 8)) {
		
			aserror = 1;
		}  
		else {
		
			Help ();
		} 
	}
}

/****************************************************************************/
void Help
	()
{       	
	/* Print help message */
	fprintf(stderr, HelpMessage, VERSION);	
	exit(1);
}

/****************************************************************************/
void CreateWidgets
	(int argc, char *argv[])
{
	Toplevel=XtVaAppInitialize(&AsTrashApp, APPNAME, NULL, 0,
				   &argc, argv,
				   fallback_resources,
				   NULL);
				   
	dpy = XtDisplay(Toplevel);
	root = RootWindow(dpy, DefaultScreen(dpy));
	
	CreateGC();
	CreatePopups();
	CreateXPMIcons();

	
	if (shape) {
		
		ToplevelWidth = max(TrashEmpty->attributes.width,GeoWidth);
		ToplevelHeight= max(TrashEmpty->attributes.height,GeoHeight);
	}
	else {

		ToplevelWidth = Plate->attributes.width;
		ToplevelHeight= Plate->attributes.height;
	}

	XtResizeWidget(Toplevel, ToplevelWidth, ToplevelHeight, 0);
	
	DndInitialize(Toplevel);

	if (!aserror) { 
	
		DndRegisterOtherDrop(DropHandler);
		DndRegisterIconDrop(DropHandler);
	}
	
	DndRegisterDropWidget(Toplevel,DropHandler,NULL);
	DndRegisterDropWidget(TrashPopup,DropHandler,NULL);
	
	XtRealizeWidget(Toplevel);

	
	WMDeleteWindow=XInternAtom(dpy, "WM_DELETE_WINDOW", False);
	XSetWMProtocols(dpy, XtWindow(Toplevel), &WMDeleteWindow, 1);
	
	XtAddEventHandler(Toplevel,NoEventMask,True,DeleteHandler,NULL);
	XtAddEventHandler(Toplevel,ExposureMask,False,ExposeHandler, NULL);
	XtAddEventHandler(Toplevel,ButtonPressMask,False,ButtonHandler,NULL);

	UpdateIcons();
}

/****************************************************************************/
void CreatePopups
	()
{
	static XtActionsRec Itemclick[]=
		{{"ItemSelected", ItemclickCallback},};
	static XtActionsRec NoSpace[]=
		{{"Dummy",DummyCallback},};
	static XtActionsRec CheckTimeEntry[]=
		{{"CheckTime", CheckTimeCallback},};
	
	XtTranslations trans;
	Widget Box;
	Widget HBox0;
	Widget Viewport;
	Widget HBox1;
	Widget UpdateButton, ClearButton, DelButton, UndelButton,
		CloseButton;
	char buf[10];
	Arg  args[10];
	int  h, bw;
	
	ReadList(LISTNAME);
	
	TrashPopup = XtVaCreatePopupShell ("TrashPopup",
			shellWidgetClass, Toplevel,
			XtNtitle,	  APPNAME,
			NULL); 
	
   	Box = XtVaCreateManagedWidget("Box", 
   			formWidgetClass, TrashPopup,
			XtNleft,		XawChainLeft,
			XtNright,		XawChainRight,
			XtNtop,			XawChainTop,
			XtNbottom,		XawChainBottom,
			NULL);
  
  	HBox0 = XtVaCreateManagedWidget("HBox0",
  			formWidgetClass, Box,
			XtNborderWidth,		0,
			XtNdefaultDistance,	1,
			XtNleft,		XawChainLeft,
			XtNright,		XawChainLeft,
			XtNtop,			XawChainTop,
			XtNbottom,		XawChainTop,	
  			NULL);
		
	ClearAfterW = XtVaCreateManagedWidget("ClearAfter",
			toggleWidgetClass, 	HBox0,
			XtNlabel,		"Clear item after (h)",
			XtNleft,		XawChainRight,
			XtNright,		XawChainRight,
			XtNstate,		(clearaftertime >= 0),	
			XtNborderWidth,         1,
			NULL);

	XtAddCallback(ClearAfterW, XtNcallback, ClearAfterCallback, NULL);
	
	if (clearaftertime >= 0)
		sprintf (buf, "%d", clearaftertime);
	else
		sprintf (buf, "%s", DEFAULTTIME);
		 
	KeepTime = (DialogWidget)XtVaCreateManagedWidget("KeepTime",
			dialogWidgetClass, 	HBox0,
			XtNvalue,		buf,
			XtNborderWidth, 	1,
			XtNlabel,		"",
			XtNfromHoriz,		ClearAfterW,
			XtNdefaultDistance,	0,
			XtNleft,		XawChainRight,
			XtNright,		XawChainRight,
			XtNtop,			XawChainTop,
			XtNbottom,		XawChainTop,
			NULL);	
	
  	XtAppAddActions(
  		XtWidgetToApplicationContext(KeepTime->dialog.valueW),
	  	CheckTimeEntry, 1);	
		
	trans=XtParseTranslationTable(":<Key>Return:CheckTime()");
   	XtOverrideTranslations(KeepTime->dialog.valueW, trans);

			
	XtSetArg(args[0], XtNheight, (XtArgVal) &h);
	XtGetValues (ClearAfterW, args, 2);
	
	XtSetArg(args[0], XtNborderWidth, (XtArgVal) &bw);	
	XtGetValues (KeepTime->dialog.valueW, args, 1);
	
   	XtSetArg(args[0], XtNfromHoriz,(XtArgVal) NULL);
   	XtSetArg(args[1], XtNfromVert, (XtArgVal) NULL);
   	XtSetArg(args[2], XtNwidth,    (XtArgVal) h); 
	XtSetArg(args[3], XtNheight,   (XtArgVal) h-2*bw);
   	XtSetValues(KeepTime->dialog.valueW, args, 4); 
   	 
   	XtUnmanageChild(KeepTime->dialog.labelW);
					
	ClearOnExitW = XtVaCreateManagedWidget("Confirm",
			toggleWidgetClass, HBox0,
			XtNlabel,		"Clear list on exit",
			XtNfromHoriz,		KeepTime,
			XtNhorizDistance,	8,
			XtNleft,		XawChainRight,
			XtNright,		XawChainRight,	
			XtNstate,		clearonexitFlag,
			XtNborderWidth,         1,
			NULL);
		 						 	
   	Viewport = XtVaCreateManagedWidget("VP",
   			viewportWidgetClass, Box, 			
   			XtNallowVert,		True,
   			XtNforceBars,		True,
			XtNwidth,		LISTWIDTH,
			XtNheight,   		LISTHEIGHT,
			XtNfromVert,		HBox0,		
			XtNleft,		XawChainLeft,
			XtNright,		XawChainRight,
			XtNtop,			XawChainTop,
			XtNbottom,		XawChainBottom,
   			NULL);


	   
        TrashListW = XtVaCreateManagedWidget("TrashList", 
        		listWidgetClass, Viewport, 
        		XtNverticalList,	True,	
        		XtNlist,		CreateTrashList(),
        		XtNdefaultColumns, 	1,
        		XtNforceColumns,   	True,
        		XtNborderWidth,     	0,
        		NULL);

	XtAppAddActions(XtWidgetToApplicationContext(TrashListW),
			Itemclick, 1);
	trans=XtParseTranslationTable("<Btn1Down>,<Btn1Up>:"
					"Set()Notify()ItemSelected()");
   	XtOverrideTranslations(TrashListW,trans);

   	CurrentItem = (DialogWidget)XtVaCreateManagedWidget("CurrentItem",
			dialogWidgetClass, Box,
			XtNvalue,		"",
			XtNborderWidth, 	0,
			XtNlabel,		"",
			XtNfromVert,		Viewport,
			XtNleft,		XawChainLeft,
			XtNright,		XawChainRight,
			XtNtop,			XawChainBottom,
			XtNbottom,		XawChainBottom,
			XtNvertDistance,	0,		
			XtNdefaultDistance,	0,
			NULL);
	
   	XtSetArg(args[0], XtNfromHoriz,(XtArgVal) NULL);
   	XtSetArg(args[0], XtNfromHoriz,(XtArgVal) NULL);
   	XtSetArg(args[1], XtNfromVert, (XtArgVal) NULL);
   	XtSetArg(args[2], XtNhorizDistance, (XtArgVal) 23);
   	XtSetArg(args[3], XtNwidth, (XtArgVal) LISTWIDTH - 23);
   	XtSetValues(CurrentItem->dialog.valueW, args, 4);
   	 
   	XtUnmanageChild(CurrentItem->dialog.labelW);

  	XtAppAddActions(
  		XtWidgetToApplicationContext(CurrentItem->dialog.valueW),
	  	NoSpace, 1);
	  	
   	trans=XtParseTranslationTable(":<Key>space:Dummy()");
   	XtOverrideTranslations(CurrentItem->dialog.valueW, trans);

  	HBox1 = XtVaCreateManagedWidget("HBox1",
  			formWidgetClass, Box,
			XtNfromVert, 		CurrentItem,
			XtNdefaultDistance,	1,
			XtNborderWidth,		0, 
			XtNleft,		XawChainLeft,
			XtNright,		XawChainRight,
			XtNtop,			XawChainBottom,
			XtNbottom,		XawChainBottom,
  			NULL);  		
	
	UpdateButton=XtVaCreateManagedWidget("Update",
			commandWidgetClass, HBox1,
			XtNlabel, 		"Update list",
			XtNborderWidth, 	1,
			NULL);	

	XtAddCallback(UpdateButton, XtNcallback, UpdateTrashCallback, NULL);
	
	ClearButton=XtVaCreateManagedWidget("DeleteAll",
			commandWidgetClass, HBox1,
			XtNlabel, 		"Clear list", 
			XtNfromHoriz,		UpdateButton,
			XtNborderWidth, 	1,
			NULL);
			
  	XtAddCallback(ClearButton, XtNcallback, ClearTrashCallback, NULL);
	
	UndelButton=XtVaCreateManagedWidget("Undelete",
			commandWidgetClass, HBox1,
			XtNlabel, 		"Undelete item",
			XtNfromHoriz,		ClearButton,
			XtNhorizDistance,	8,
			XtNborderWidth, 	1,
			NULL);	

	XtAddCallback(UndelButton, XtNcallback, UndeleteItemCallback, NULL);
	
	DelButton=XtVaCreateManagedWidget("Delete",
			commandWidgetClass, HBox1,
			XtNlabel, 		"Delete item", 
			XtNfromHoriz,		UndelButton,
			XtNborderWidth, 	1,
			NULL);
			
	XtAddCallback(DelButton, XtNcallback, DeleteItemCallback, NULL);

	XtAddEventHandler(TrashPopup,NoEventMask,True,PopdownHandler,NULL);

	CloseButton=XtVaCreateManagedWidget("Close",
			commandWidgetClass, HBox1,
			XtNlabel, 		"   Close   ", 
			XtNfromHoriz,		DelButton,
			XtNhorizDistance,	8,
			XtNborderWidth, 	1,
			NULL);
			
	XtAddCallback(CloseButton, XtNcallback, CloseCallback, NULL);
}

/****************************************************************************/
void CreateGC
	()
{
	XGCValues gcv;
	unsigned long gcm;
		
	 /* Create a GC for drawing */
  	gcm = GCForeground|GCBackground|GCGraphicsExposures,GCFillStyle;
  	gcv.foreground = BlackPixel(dpy, DefaultScreen (dpy));
  	gcv.background = WhitePixel(dpy, DefaultScreen (dpy)); 
  	gcv.fill_style = FillSolid;
  	gcv.graphics_exposures = FALSE;
  	
  	NormalGC = XCreateGC(dpy, root, gcm, &gcv);
}

/****************************************************************************/
void StartLoop
	(void)
{
	XtAppContext AppContext;
	XEvent ev;
	Atom DndProtocol;
	
     	AppContext = XtWidgetToApplicationContext(Toplevel);
      	DndProtocol=XInternAtom(XtDisplay(Toplevel),"DndProtocol",False);
      	do {
      		
	 	XtAppNextEvent(AppContext,&ev);
	 	
	 	if (ev.type == ClientMessage &&
	 	    ev.xclient.message_type==DndProtocol) {
	 	 
	 	 	DropHandler (Toplevel, NULL, &ev, NULL); 
	 	}
	 	else	
	 		XtDispatchEvent(&ev);
      	}
      	while (1);
}

/****************************************************************************/
void exitOnSignal
	()
{
	exit(0);
}

/****************************************************************************/
void updateSignal
	()
{
	ReadList(LISTNAME);
	UpdateList();
	UpdateIcons();
	
	XFlush (XtDisplay (Toplevel));
	signal (SIGUSR1, updateSignal);
}

/****************************************************************************/
void DeleteHandler
	(Widget widget, XtPointer p, XEvent *event, Boolean *f)
{     
	if ((event->type != ClientMessage) ||
	    (event->xclient.format != 32)  ||
	    (event->xclient.data.l[0] != WMDeleteWindow))
	    return;

	exit(0);
}

/****************************************************************************/
void ExitHandler
	(void)
{
	if (ClearOnExit())
		ClearTrash();
}

/****************************************************************************/
void 
ExposeHandler
	(Widget widget, XtPointer p, XEvent *event, Boolean *f)
{
	UpdateIcons();
}

/****************************************************************************/
void 
ButtonHandler
	(Widget widget, XtPointer p, XEvent *event, Boolean *f)
{ 
	XtAppContext AppContext;
	int keeptime;
	XEvent ev;
	
	if (event->xbutton.button == LEFTBUTTON) {

		ReadList(LISTNAME);
		UpdateList();
			
		XtPopup(TrashPopup, XtGrabExclusive);
		XRaiseWindow (dpy, XtWindow(TrashPopup));
	
		DndRegisterDropWidget(TrashListW,DropHandler,NULL);
		
		WMDeleteWindow=XInternAtom(dpy,"WM_DELETE_WINDOW",False);
		XSetWMProtocols(dpy,XtWindow(TrashPopup),&WMDeleteWindow,1);

      		/* Events im Popup selbst verwalten */
      		AppContext = XtWidgetToApplicationContext(TrashPopup);
      		while(((ShellWidget)TrashPopup)->shell.popped_up) {
      		
	 		XtAppNextEvent(AppContext,&ev);
	 		XtDispatchEvent(&ev);
      		}
	
		if (UpdateAfter (&keeptime)) {
	
			Update (keeptime);
			UpdateIcons();
		}
	}    
}

/****************************************************************************/
void 
DropHandler
	(Widget widget, XtPointer p, XEvent *event, Boolean *f)
{
	int Type, keeptime;
	unsigned char *Data;
	unsigned long DataSize;

	if((Type = DndDataType(event)) == DndNotDnd) 
		return;

	XtVaSetValues(TrashListW, XtNresizable, False, NULL);	

	DndGetData(&Data,&DataSize);
	
	ReadList (LISTNAME);
	if (UpdateAfter (&keeptime))
		Update (keeptime);	
		
	if(Type==DndFile || Type==DndExe || Type==DndDir) {

		ProcessOneFile((char*)Data);
	  		
	  	WriteList (LISTNAME);
	  	UpdateList();		
		UpdateIcons();
		return;
	}
	else if(Type==DndFiles) {
	
		while(*Data)
		{	
			ProcessOneFile((char*)Data);
			Data+=strlen((char*)Data)+1;
		}

		WriteList (LISTNAME);
		UpdateList();
		UpdateIcons();
	}
}

/****************************************************************************/
void PopdownHandler
	(Widget widget, XtPointer p, XEvent *event, Boolean *f)
{
	if ((event->type != ClientMessage) ||
	    (event->xclient.format != 32)  ||
	    (event->xclient.data.l[0] != WMDeleteWindow))
	    return;

	XtPopdown (widget);
}

/****************************************************************************/
void ItemclickCallback
	(Widget widget, XEvent *event, String *params, Cardinal *n_params)
{
	XawListReturnStruct *item = XawListShowCurrent(widget);
	Arg args[1];
		
	XtSetArg(args[0], XtNvalue, item->string);
        XtSetValues((Widget)CurrentItem, args, 1);
}

/****************************************************************************/
int UpdateAfter 
	(int *keeptime)
{
	Arg args[1];
	int state;
	
	XtSetArg(args[0], XtNstate, (XtPointer)&state);
	XtGetValues (ClearAfterW, args, 1);
	
	if (state & 1) 
	  *keeptime = atoi(XawDialogGetValueString((Widget)KeepTime));	
		
	return (state & 1);
}

/****************************************************************************/
int ClearOnExit
	()
{
	Arg args[1];
	int state;
	
	XtSetArg(args[0], XtNstate, (XtPointer)&state);
	XtGetValues (ClearOnExitW, args, 1);
	
	return (state & 1);
}	

/****************************************************************************/
void UpdateTrashCallback  
	(Widget widget, XtPointer callData, XtPointer clientData)
{
	int keeptime;

	if (UpdateAfter (&keeptime)) { 
	
		Update (keeptime);
	
		UpdateList ();
		UpdateIcons ();			
	}
	else
		fprintf (stderr, "\a");
}

/****************************************************************************/
void ClearTrashCallback
	(Widget widget, XtPointer callData, XtPointer clientData)
{
	ClearTrash();
}

/****************************************************************************/
void DeleteItemCallback
	(Widget widget, XtPointer callData, XtPointer clientData)
{
	XawListReturnStruct *item = XawListShowCurrent(TrashListW);
	char *path = XawDialogGetValueString((Widget)CurrentItem);
	char oldPath[NAME_MAX];
	List *entry;
	int i;

	if (!item || !(path && strlen(path))) {
	
		fprintf (stderr, "\a");
		return;
	}

	for ((i = item->list_index), entry = TrashList; 
		i--; entry = entry->next); 	

	sprintf (oldPath, "%ld", entry->index);			

	if (!TrashDelete (oldPath))
		fprintf (stderr, "\a");
	else {
	
		RemoveListEntry (entry);
		WriteList (LISTNAME);
		UpdateList ();
		UpdateIcons();
	}		
}

/****************************************************************************/
void UndeleteItemCallback
	(Widget widget, XtPointer callData, XtPointer clientData)
{
	XawListReturnStruct *item = XawListShowCurrent(TrashListW);
	char *path = XawDialogGetValueString((Widget)CurrentItem);
	char oldPath[NAME_MAX];
	List *entry;
	int i;

	if (!item || !(path && strlen(path))) {
	
		fprintf (stderr, "\a");
		return;
	}
	
	for ((i = item->list_index), entry = TrashList; 
		i--; entry = entry->next); 	

	sprintf (oldPath, "%ld", entry->index);			

	if (!TrashMove (oldPath, path))
		fprintf (stderr, "\a");
	else {
	
		RemoveListEntry (entry);
		WriteList (LISTNAME);
		UpdateList ();
		UpdateIcons();
	}
}

/****************************************************************************/
void CloseCallback
	(Widget widget, XtPointer callData, XtPointer clientData)
{
	XtPopdown (TrashPopup);
}

/****************************************************************************/
void DummyCallback
	(Widget widget, XEvent *event, String *params, Cardinal *n_params)
{
	fprintf (stderr, "\a");
}

/****************************************************************************/
void ClearAfterCallback
	(Widget widget, XtPointer callData, XtPointer clientData)
{
	Arg args[1];
	int state;

/*	- funktioniert nicht !?	
	XtSetArg(args[0], XtNstate, (XtPointer)&state);
	XtGetValues (ClearAfter, args, 1);
	
	if (!(state & 1))
		XtSetArg(args[0], XtNsensitive, False);
	else
		XtSetArg(args[0], XtNsensitive, True);
	
	XtSetValues((Widget)KeepTime, args, 1);
*/
}

/****************************************************************************/
int CheckTimeCallback
	(Widget widget, XEvent *event, String *params, Cardinal *n_params)
{
	char newVal[0xff], *start, *end;
	Arg args[1];
	int v;
	
	v = strtol (start = XawDialogGetValueString((Widget)KeepTime), 
				&end, 10);
	
	if (end == start || end != (start + strlen(start))) { 
		
		fprintf (stderr, "\a");

		XtSetArg(args[0], XtNstate, 0);
		XtSetValues(ClearAfterW, args, 1);

		XtSetArg(args[0], XtNvalue, DEFAULTTIME);
		XtSetValues((Widget)KeepTime, args, 1);
				
		return 0;
	}		
	else {
		
		sprintf (newVal, "%d", v);
		XtSetArg(args[0], XtNvalue, newVal);
		XtSetValues((Widget)KeepTime, args, 1);

		XtSetArg(args[0], XtNstate, 1);
		XtSetValues(ClearAfterW, args, 1);

		return 1;
	}	
		
}

/****************************************************************************/
void UpdateIcons
	()
{
	XpmIcon *newIcon;
	
	if (ListSize (LISTNAME) > 0)
		newIcon = TrashFull;
	else
		newIcon = TrashEmpty;
		
	RedrawToplevelIcon (newIcon, shape);
	SetWMIcon(newIcon);
}

/****************************************************************************/
void UpdateList
	()
{
	Arg args[1];

	XtSetArg(args[0], XtNvalue, "");
        XtSetValues((Widget)CurrentItem, args, 1);
        	
	XawListChange (TrashListW, CreateTrashList(), ListEntries, 
		LISTWIDTH, False);
}

/****************************************************************************/
void CreateXPMIcons
	(void)
{	
	Plate 	   = (XpmIcon *)calloc (1, sizeof (XpmIcon));
	TrashEmpty = (XpmIcon *)calloc (1, sizeof (XpmIcon));
	TrashFull  = (XpmIcon *)calloc (1, sizeof (XpmIcon));
	     		
	XPMError(XpmCreatePixmapFromData(dpy, root, 
					 plate, 
					 &Plate->pixmap, 
					 &Plate->mask, 
					 &Plate->attributes));
					 		
	XPMError(XpmCreatePixmapFromData(dpy, root, 
				         trash, 
				         &TrashEmpty->pixmap, 
				         &TrashEmpty->mask, 
				         &TrashEmpty->attributes));	

	XPMError(XpmCreatePixmapFromData(dpy, root, 
				         trash_full, 
				         &TrashFull->pixmap, 
				         &TrashFull->mask, 
				         &TrashFull->attributes));
}

/****************************************************************************/
void XPMError
	(int Code)
{
	switch(Code)
	{
	 case 0:
		break;
	 case 1:
	 case -4:
		fprintf(stderr, APPNAME ": not enough free color cells\n");
		break;
	 case -1:
	 case -2:
		fprintf(stderr, APPNAME ": could not load xpm\n");
		break;
	 case -3:
		fprintf(stderr, APPNAME ": not enough memory free\n");
		break;
	 default:
		fprintf(stderr, APPNAME ": unknown xpm-error\n");
		break;
	}
	
	if(Code != 0)
		exit(1);
}
		
/****************************************************************************/
void SetWMIcon
	(XpmIcon *icon)
{
	XWMHints *hints;
	
	hints=XAllocWMHints();

	hints->flags=IconPixmapHint | IconMaskHint;
	hints->icon_pixmap=icon->pixmap;
	hints->icon_mask=icon->mask;

	XSetWMHints(dpy, XtWindow(Toplevel), hints);
	XFree(hints);	
}

/****************************************************************************/
void RedrawToplevelIcon 
	(XpmIcon *icon, Boolean shape)
{
	Pixmap result;
	int xo = (ToplevelWidth-icon->attributes.width)/2;
	int yo = (ToplevelHeight-icon->attributes.height)/2;

	if (!shape) { 
	
		Pixmap result = XCreatePixmap (dpy, XtWindow(Toplevel), 
				  ToplevelWidth, ToplevelHeight,
				  DefaultDepth (dpy, DefaultScreen(dpy)));
	
		XSetClipMask(dpy, NormalGC, None);	
		XCopyArea (dpy, Plate->pixmap, result, NormalGC, 0, 0, 
			ToplevelWidth, ToplevelHeight, 0, 0);
	
		XSetClipMask(dpy, NormalGC, icon->mask);
		XSetClipOrigin(dpy, NormalGC, xo, yo);

		XCopyArea (dpy, icon->pixmap, result, NormalGC, 0, 0, 
			icon->attributes.width, icon->attributes.height,
			xo,yo); 	
		XSetWindowBackgroundPixmap(dpy,	XtWindow(Toplevel), result);
		XFreePixmap (dpy, result);
	
		XClearWindow(dpy,XtWindow(Toplevel));
	}
	else {

		XShapeCombineMask (dpy, XtWindow(Toplevel), ShapeBounding, 
				xo, yo, icon->mask, ShapeSet);		
		XCopyArea (dpy, icon->pixmap, XtWindow (Toplevel), 
			NormalGC, 0, 0, 
			icon->attributes.width, icon->attributes.height, 
			xo, yo);		
	}
}

/****************************************************************************/
int InitTrashDir
	(char *TrashDir)
{
	struct stat JunkStat;
	char DestDir[MAXPATHLEN];
	int ErrStatus;

	if(getenv("HOME") == NULL) {
		
		fprintf(stderr,APPNAME ": Could not get HOME environment.\n");
		return 0;
	}
	
	sprintf(DestDir,"%s/%s",getenv("HOME"),TrashDir);
	
	ErrStatus=lstat(DestDir,&JunkStat);
	if(ErrStatus) {
	
		fprintf(stderr,APPNAME 
			": Creating directory %s...\n",DestDir);

		ErrStatus=mkdir(DestDir, 0700);
		if(ErrStatus) {
		
			fprintf(stderr,APPNAME ": Could not create!\n");
			return 0;
		}
	}

	ErrStatus=chdir(DestDir);
	if(ErrStatus) {
	
		fprintf(stderr,"Trash: could not chdir to trash dir!\n");
		return 0;
	}
	

	return 1;
}

/****************************************************************************/
int ListSize
	(char *filename)
{
	FILE *f;
	int lines = -1, i, ch;
	
	if ((f = fopen (filename,"r"))) {
	
		lines = 0;
		while ((ch = getc (f)) != EOF)
			if (ch == '\n')
				lines++;
				
		fclose (f);
	}

	return lines;
}

/****************************************************************************/
void DeleteList
	(char *filename)
{
	List *next;
	
	while (TrashList) {
		
		next = TrashList->next;
		free (TrashList);
		TrashList = next;
	}
	
	ListEntries = 0;		

	if (filename)
		fclose (fopen (filename, "w"));		
}

/****************************************************************************/
void ReadList 
	(char *filename)
{
	FILE *f;
	List *next;
	char buf[PATH_MAX], *p;
	struct stat JunkStat;

	DeleteList (NULL);

	if ((f = fopen (filename, "r"))) {

		while (!feof(f)) {

			if (fgets (buf, PATH_MAX, f) &&
			    (next = malloc (sizeof(List) + PATH_MAX))) {
			
				buf[strlen(buf)-1] = '\0';
				next->index = strtol(buf, &p, 10); 
				strncpy (&(next->entry), p, PATH_MAX);

				next->next  = TrashList;
				TrashList   = next;
			
				ListEntries++;
				
				sprintf (buf, "%d", next->index);
				lstat (buf, &JunkStat);				
				next->time  = JunkStat.st_ctime;
			}
		}
		
		fclose (f);
	}				
}

/****************************************************************************/
void WriteListEntry (List *entry, FILE *f)
{
	static char buf[PATH_MAX];
	
	if (entry) { 
	
		WriteListEntry (entry->next, f);
		
		sprintf (buf, "%d%s\n", entry->index, &(entry->entry));	
		fputs (buf, f);
	}
}

/****************************************************************************/
void WriteList
	(char *filename)
{
	FILE *f;
	List *entry, *next, *prev;
		
	if ((f = fopen (filename, "w"))) {

		WriteListEntry (TrashList, f);
		fclose (f);
	}
}

/****************************************************************************/
void writePId
	(char *filename)
{
	FILE *f;
	char buf[10];
	
	if ((f = fopen (filename, "w"))) {
	
		sprintf (buf,"%d", getpid());
		fputs (buf, f);
		fclose (f);
	}
}

/****************************************************************************/
int readPId
	(char *filename)
{
	FILE *f;
	char buf[10];
	int pid;
	
	if ((f = fopen (filename, "r"))) {
	
		fgets (buf, 10, f);
		fclose (f);
		
		pid = atoi (buf);
		if (!kill (pid, SIGUSR1))
			return pid;
	}
	
	return 0;	
}	
						
/****************************************************************************/
void AddListEntry 
	(int index, char *entry)
{
	char buf[PATH_MAX];
	List *new;
	FILE *f;
		
	if ((new = malloc (sizeof(List) + PATH_MAX))) {

		new->index = index;
		strncpy (&(new->entry), entry, PATH_MAX);

		new->next = TrashList;
		TrashList = new;

		time (&new->time); 		
		ListEntries++;
	}		
}
 
/****************************************************************************/
void RemoveListEntry
	(List *entry)
{
	if (TrashList == entry)
		TrashList = entry->next;
	else {
	
		List *prev;
		for (prev = TrashList; 
			prev->next != entry; prev = prev->next);
			
		prev->next = entry->next;
	}
	
	ListEntries--;
	free (entry);		
}

/****************************************************************************/
String *CreateTrashList
	()
{
	static String *list = NULL;
	List *entry = TrashList;
	int i;
	
	if (list)
		free(list);
		
	list = (String *)malloc (sizeof(String) * (ListEntries+1));
	
	for (i = 0, entry = TrashList; i < ListEntries; 
		i++, entry = entry->next) {
		
		list[i] = &(entry->entry);	
	}
	list[i] = NULL;

	return list;
}

/****************************************************************************/
int ProcessOneFile
	(char *Path)
{
	char newPath[0xff];
	int newIndex;
	
	newIndex = FreeIndex();
	sprintf (newPath, "%ld", newIndex);
	
	if (TrashMove (Path, newPath)) {

		AddListEntry (newIndex, Path);
		UpdateList();
		return 1;
	}
	
	return 0;
}

/***************************************************************************/
int FreeIndex
	()
{
	int i, index;
	List *entry;
		
	do {
		index = random();
		for (i = 0, entry = TrashList; i < ListEntries; 
		     i++, entry = entry->next)
			if (atoi(&(entry->entry)) == index)
				break;
	} while (i < ListEntries);
	
	return index;			
}

/***************************************************************************/
int TrashMove
	(char *OldPath,char *NewPath)
{
	char CmdBuf[2*MAXPATHLEN+13];
	struct stat JunkStat;
	int ErrStatus;

	if(lstat(OldPath,&JunkStat)) return 1;
	if(!lstat(NewPath,&JunkStat)) return 0;
	
	sprintf(CmdBuf,"cp -r '%s' '%s'",OldPath,NewPath);
	if ((ErrStatus=system(CmdBuf))==0)
		if((ErrStatus=!TrashDelete(OldPath))!=0)
			TrashDelete(NewPath);

	return (ErrStatus==0);
}

/***************************************************************************/
int TrashDelete
	(char *OldPath)
{
	struct stat JunkStat;
	char Command[MAXPATHLEN];
	int ErrStatus;
	
	if(lstat(OldPath,&JunkStat)) return 1;
	
	sprintf(Command,"rm -rf %s",OldPath);
	ErrStatus=system(Command);
	
	return (!ErrStatus);
}

/***************************************************************************/
int Update
	(int keeptime)
{
	List *entry, *next;
	char oldPath[NAME_MAX];
	int diff;
	time_t now;
	
	time (&now);	

	for (entry = TrashList; entry; entry = next) {

		next = entry->next;

		diff = difftime (now, entry->time)/3600.0;		
		if (diff >= keeptime) {

			sprintf (oldPath, "%d", entry->index);					TrashDelete (oldPath);		
			
			RemoveListEntry (entry);
		}
	}	
	
	WriteList (LISTNAME);
}

/****************************************************************************/
void ClearTrash
	()
{
	List *entry;
	char oldPath[NAME_MAX];
		
	for (entry = TrashList; entry; entry = entry->next) {
	
		sprintf (oldPath, "%d", entry->index);
		TrashDelete (oldPath);
	}
	
	DeleteList (LISTNAME);		
	UpdateList ();		
	UpdateIcons();
}
