/***************************************************************************
    file        : main.c
    project     : cDirCmp (simple directory compare utility)
    begin       : July 9, 2003
    copyright   : (C) 2003 by Paul Schuurmans
    email       : paul.schuurmans@hccnet.nl
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include <sys/stat.h>
#include <ctype.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>

#include <curses.h>
#include <form.h>

#define APPNAME		"cDirCmp v0.3"

#define MODE_OPTIONS	0
#define MODE_GETSRC		1
#define MODE_GETDST		2
#define MODE_REPORT		3
#define MODE_HELP		4

#define NUMHELPLINES	34
#define MAXFILES		10000
#define MAXPATH			260
#define MAXDIRS			1000
#define MAXLEVELS		16

#define BTN_GETSRC		0
#define TXT_SRC			1
#define BTN_GETDST		2
#define TXT_DST			3
#define CHK_SUBDIRS		4
#define CHK_INC_ALL		5
#define CHK_KEEP_ATTR	6
#define CHK_BACKUP		7
#define NUMFIELDS		8

// Format codes for StrCvt
#define RWHITE		1  	// Remove white space.
#define RLWHITE    	2	// Remove leading white space.
#define RTWHITE    	4	// Remove trailing white space.
#define REDUCE	   	8	// Reduce white space to 1 blank.
#define NOQUOTE   	16	// Quoted substrings not altered.
#define TOUP	  	32	// Convert to upper case.
#define TOLOW	  	64	// Convert to lower case.
#define RCONTROL 	128	// Remove all control characters.
#define TOCASE	 	256	// Toggle case

#define FN_DIR		0x01
#define FN_FILE		0x02
#define FN_EXT		0x04

// Keycodes
#define KEY_BS			8
#define KEY_TAB			9
#define KEY_S_TAB		353
#define KEY_RETURN		10
#define KEY_BKSPC		127
#define KEY_DEL			330
#define KEY_INS			331
#define KEY_PGDN		338
#define KEY_PGUP		339

#define KEY_ATERM_HOME	362
#define KEY_ETERM_HOME	299
#define KEY_XTERM_HOME	293
#define KEY_ATERM_END	385
#define KEY_ETERM_END	300
#define KEY_XTERM_END	296
#define KEY_ETERM_F1	342
#define KEY_ETERM_F2	343
#define KEY_ETERM_F3	344
#define KEY_ETERM_F4	345

void CheckDirNames (void);
void ClearArray (void);
int Copy (char *name);
WINDOW *CreateWindow (int height, int width, int starty, int startx, int do_box);
int FileExists (char *path);
int Msg (char *txt, ...);
int ReadDirectory (char *path);
int ReadLine (FILE *rfile, char *line, int maxlinesize);
int Report (void);
int ReportDirDiffs (char *srcdir, int level);
void SetCheckBox (void);
void SetMode (int mode);
void SetStatusBar (char *txt, ...);
void SetTitleBar (char *txt, ...);
void SetValidList (int set);
void ShowInfo (int ndx);
void SortList (void);
void StartCopy (void);
int StrChrIndex (char check, char *psearch);
char *StrCvt (char *psource, int conv);
void SwitchDirs (void);
void UpdateDirList (int cmd);
void UpdateFileList (int cmd);
void UpdateHelp (int cmd);
int WinGetCh (WINDOW *win);

typedef struct {
	char Name[MAXPATH];
	struct stat St;
	struct stat StD;
	bool Selected;
	int DiffCode;
} TFileInfo;

char CurDir[MAXPATH], SrcDir[MAXPATH], DstDir[MAXPATH];
int Mode, LastMode, SortMode, Quit;
WINDOW *TitleBar, *OptionsWin, *MainWin, *StatusBar;
FIELD *Fields[NUMFIELDS+1];	// 1 added for NULL at end
FORM *Form;
TFileInfo *Files[MAXFILES];
char *Dirs[MAXLEVELS][MAXDIRS];
int FwRows, FwCurItm, FwTopItm;
int HelpRows, HelpTopLine, HelpLastTop;
int FileCnt, SelCnt, DirCnt[MAXLEVELS], ValidList;
char *ModeStrs[5] = {
	"H=Help  Space=SetOption  F6=SwitchSrcDst  F7=Report  F12=Exit",
	"Space=SetSource  Enter=ChangeDir  .=UseCurDir  F12=Cancel",
	"Space=SetDestination  Enter=ChangeDir  .=UseCurDir  F12=Cancel",
	"H=Help  Space=Select  A=All  N=None  S=Sort  F8=Copy  F12=Cancel",
	"Space=ExitHelp"
};
char *HelpTxt[NUMHELPLINES] = {
	"cDirCmp is a simple utility that compares two directories, displays the ",
	"differences, and allows you to select items to copy.  This is the ncurses ",
	"version of gDirCmp. This program basically uses two windows: the Options ",
	"window and this multi-purpose window that serves as either a Help, Directory, ",
	"or Report window.",
	"",
	"The Options Window",
	"------------------",
	"Start by selecting the source and destination directories, and any other ",
	"options you wish to set.  Selections are made using the Space key.",
	"",
	"After setting the desired options, press the F7 key to show a report of the ",
	"differences found.  Note that the report may take some time, depending on ",
	"how many files and/or directories need to be checked.", 
	"",
	"The Report Window",
	"-----------------",
	"This window shows a list of differences between the source and destination ",
	"directories and allows you to select items to copy.  Selections are made ",
	"using the Space key.  The list may be sorted in one of two ways: by Filename ",
	"or by Difference Code.  The 'S' key toggles between the two.",
	"",
	"The meanings of the Difference Codes are as follows:",
	"  1  File Not In Dst (file in Src does not exist in Dst)",
	"  2  Older In Dst (Src file is newer than Dst file)",
	"  3  Newer In Dst (Dst file is newer than Src file)",
	"  4  Smaller In Dst (Dst file is smaller than Src file)",
	"  5  Larger In Dst (Src file is smaller than Dst file)",
	"  6  Dir Not In Dst (directory in Src does not exist in Dst)",
	"",
	"After selecting the desired items in the Report window, you can use the F8 ",
	"key to copy the selected files and/or directories from Src to Dst.  Or, you ",
	"can use the F12 key to return to the Options window.  Note that returning ",
	"to the Options window invalidates the contents of the Report window."
};
char *ConfigStrs[NUMFIELDS] = {
	"cb_srcdir",
	"txt_srcdir",
	"cb_dstdir",
	"txt_dstdir",
	"cb_subdirs",
	"cb_inc_all",
	"cb_keep_attr",
	"cb_backup"
};
char *StStrs[7] = {
	" ",
	"File Not In Dst",
	"Older In Dst",
	"Newer In Dst",
	"Smaller In Dst",
	"Larger In Dst",
	"Dir Not In Dst"
};

int main (int argc, char *argv[])
{	
	FILE *fn = NULL;
	int w, h, ch, i, rows, cols, ndx;
	char configfile[MAXPATH], txt[1024] = "", line[1024] = "";
	printf("%s (C)2003 JointProjects Software\n", APPNAME);
	getcwd(CurDir, MAXPATH);
	if(argc > 1)  strcpy(SrcDir, argv[1]);
	if(argc > 2)  strcpy(DstDir, argv[2]);
	CheckDirNames();
	initscr();
	cbreak();
	noecho();
	keypad(stdscr, TRUE);
	getmaxyx(stdscr, h, w);
	if((h < 24) || (w < 80)) { endwin(); printf("TermSize=%dx%d (need 80x24 min.)\n", w, h); return(1); }
	// Initialize Windows ////////////////////////////////////////////////////
	TitleBar = CreateWindow(1, w, 0, 0, 0);
	SetTitleBar("%s - Simple Directory Compare and File Copy Utility", APPNAME);
	OptionsWin = CreateWindow(6, w, 1, 0, 1);
	keypad(OptionsWin, TRUE);
	wrefresh(OptionsWin);
	MainWin =  CreateWindow(h - 8, w, 7, 0, 1);
	keypad(MainWin, TRUE);
	StatusBar = CreateWindow(1, w, h - 1, 0, 0);
	FwRows = HelpRows = h - 10;
	HelpLastTop = NUMHELPLINES - HelpRows;
	mvwprintw(OptionsWin, 0, 1, " Options ");
	//mvwprintw(MainWin, 0, 1, " Help ");
	wrefresh(MainWin);
	/*+123456789+123456789+123456789+123456789+123456789+123456789+123456789+123456"
	 "[+] Src: ____________________________________________________________________"
	 "[+] Dst: ____________________________________________________________________"
	 "[ ] Check All Subdirectories           [ ] Include Hidden Files In Report"
	 "[ ] Preserve File Attributes           [ ] Backup Existing Files In Dst*/
	Fields[0] = new_field(1, 1, 0, 1, 0, 0);
	Fields[1] = new_field(1, 68, 0, 9, 0, 0);
	Fields[2] = new_field(1, 1, 1, 1, 0, 0);
	Fields[3] = new_field(1, 68, 1, 9, 0, 0);
	Fields[4] = new_field(1, 1, 2, 1, 0, 0);
	Fields[5] = new_field(1, 1, 2, 40, 0, 0);
	Fields[6] = new_field(1, 1, 3, 1, 0, 0);
	Fields[7] = new_field(1, 1, 3, 40, 0, 0);
	Fields[8] = NULL;
	set_field_buffer(Fields[TXT_SRC], 0, "  ");
	set_field_buffer(Fields[TXT_DST], 0, "  ");
	field_opts_off(Fields[TXT_SRC], O_ACTIVE);
	field_opts_off(Fields[TXT_DST], O_ACTIVE);
	for(i = 0; i < NUMFIELDS; i++) {
		field_opts_off(Fields[i], O_AUTOSKIP);
		set_field_fore(Fields[i], A_BOLD);
		set_field_pad(Fields[i], 0);
	}
	Form = new_form(Fields);
	scale_form(Form, &rows, &cols);
	set_form_win(Form, OptionsWin);
	set_form_sub(Form, derwin(OptionsWin, rows, cols, 1, 2));
	post_form(Form);
	ShowInfo(-1);
	// Read configuration file ///////////////////////////////////////////////
	sprintf(configfile, "%s/.cdircmp.cfg", getenv("HOME"));
	if((fn = fopen(configfile, "rb")) != NULL) {
		for(i = 0; i < NUMFIELDS; i++) {
			txt[0] = 0;
			if((ReadLine(fn, line, 1000)) != -1 && (ndx = StrChrIndex('=', line)) != -1) {
				strcpy(txt, line + ndx + 1);
				if(i == TXT_SRC)  strcpy(SrcDir, txt);
				else if(i == TXT_DST)  strcpy(DstDir, txt);
			}
			set_field_buffer(Fields[i], 0, txt);
		}
		fclose(fn);
		CheckDirNames();
	}
	else {
		set_field_buffer(Fields[BTN_GETSRC], 0, "+");
		set_field_buffer(Fields[TXT_SRC], 0, SrcDir);
		set_field_buffer(Fields[BTN_GETDST], 0, "+");
		set_field_buffer(Fields[TXT_DST], 0, DstDir);
	}
	set_current_field(Form, Fields[0]);
	//UpdateHelp(0);
	wrefresh(OptionsWin);
	LastMode = MODE_OPTIONS;
	SetMode(MODE_OPTIONS);
	// Main loop /////////////////////////////////////////////////////////////
	while(!Quit) {
		if(Mode)  ch = WinGetCh(MainWin);
		else  ch = WinGetCh(OptionsWin);
		if(Mode == MODE_OPTIONS) {
			switch(ch) {
			  case KEY_DOWN: case KEY_RIGHT: case 9:  form_driver(Form, REQ_NEXT_FIELD); break;
			  case KEY_UP: case KEY_LEFT: case 353:  form_driver(Form, REQ_PREV_FIELD); break;
			  case ' ': case KEY_RETURN:  SetCheckBox(); break;
			  case KEY_F(6):  SwitchDirs(); break;
			  case KEY_F(7):  if(Report()) SetMode(MODE_REPORT); break;
			  case 'h': case 'H': LastMode = MODE_OPTIONS; SetMode(MODE_HELP); break;
			  case KEY_F(12):  SetStatusBar(""); Quit++; break;
			  default:  break;
			}
		}
		else if(Mode == MODE_GETSRC || Mode == MODE_GETDST) {
			switch(ch) {
			  case KEY_DOWN: case KEY_UP: case KEY_PGDN: case KEY_PGUP: case KEY_HOME: case KEY_ATERM_HOME:  
			  case KEY_ETERM_HOME: case KEY_XTERM_HOME: case KEY_END: case KEY_ATERM_END: case KEY_ETERM_END: 
			  case KEY_XTERM_END: case KEY_RETURN: case ' ': case '.': case KEY_F(12):  UpdateDirList(ch); break;
			  default:  break;
			}
		}
		else if(Mode == MODE_REPORT) {
			switch(ch) {
			  case KEY_DOWN: case KEY_UP: case KEY_PGDN: case KEY_PGUP: case KEY_HOME: case KEY_END: 
			  case KEY_ATERM_HOME: case KEY_ETERM_HOME: case KEY_XTERM_HOME: case KEY_ATERM_END: 
			  case KEY_ETERM_END: case KEY_XTERM_END:  UpdateFileList(ch); break;
			  case KEY_RETURN: case ' ':  UpdateFileList(ch); break;
			  case 'a': case 'A':  for(i = 0; i < FileCnt; i++) Files[i]->Selected = 1; UpdateFileList(0); break;
			  case 'n': case 'N':  for(i = 0; i < FileCnt; i++) Files[i]->Selected = 0; UpdateFileList(0); break;
			  case 'h': case 'H':  LastMode = MODE_REPORT; SetMode(MODE_HELP); break;
			  case 's': case 'S':  SortMode = !SortMode; SortList(); UpdateFileList(0); break;
			  case KEY_F(8):  StartCopy(); break;
			  case KEY_F(12):  SetValidList(0); ShowInfo(-1); SetMode(MODE_OPTIONS); break;
			  default:  break;
			}
		}
		else if(Mode == MODE_HELP) {
			switch(ch) {
			  case KEY_DOWN: case KEY_UP: case KEY_PGDN: case KEY_PGUP:  UpdateHelp(ch); break;
			  case ' ':  SetMode(LastMode); break;
			  default:  break;
			}
		}
	}
	// Save settings, cleanup, and exit //////////////////////////////////////
	if((fn = fopen(configfile, "w+")) != NULL) {
		for(i = 0; i < NUMFIELDS; i++) {
			if(i == TXT_SRC)  strcpy(txt, SrcDir);
			else if(i == TXT_DST)  strcpy(txt, DstDir);
			else  strcpy(txt, field_buffer(Fields[i], 0));
			fprintf(fn, "%s=%s\n", ConfigStrs[i], StrCvt(txt, RTWHITE));
		}
		fclose(fn);
	}
	ClearArray();
	unpost_form(Form);
	for(i = 0; i < NUMFIELDS; i++)  free_field(Fields[i]);
	delwin(TitleBar);
	delwin(MainWin);
	delwin(OptionsWin);
	delwin(StatusBar);
	endwin();
	return(0);
}

void CheckDirNames ()
{
	struct stat st;
	int res;
	res = stat(SrcDir, &st);
	if(res < 0 || !S_ISDIR(st.st_mode))  strcpy(SrcDir, CurDir);
	res = stat(DstDir, &st);
	if(res < 0 || !S_ISDIR(st.st_mode))  strcpy(DstDir, CurDir);
	set_field_buffer(Fields[TXT_SRC], 0, SrcDir);
	set_field_buffer(Fields[TXT_DST], 0, DstDir);
}

void ClearArray ()
{
	int i, j;
	FwCurItm = FwTopItm = 0;
	for(i = 0; i < FileCnt; i++) {
		free(Files[i]);
		Files[i] = NULL;
	}
	FileCnt = 0;
	for(i = 0; i < MAXLEVELS; i++) {
		for(j = 0; j < DirCnt[i]; j++) {
			free(Dirs[i][j]);
			Dirs[i][j] = NULL;
		}
		DirCnt[i] = 0;
	}
}

int Copy (char *name)
{
	char cmd[2048] = "", args[16] = "";
	if(*(field_buffer(Fields[CHK_KEEP_ATTR], 0)) == '*')  strcpy(args, "-a");
	else  strcpy(args, "-dR");
	if(*(field_buffer(Fields[CHK_BACKUP], 0)) == '*')  strcat(args, "b");
 	SetStatusBar("Copying %s...", name);
	sprintf(cmd, "cp %s \"%s/%s\" \"%s/%s\"", args, SrcDir, name, DstDir, name);
	if(system(cmd) == -1)  return(0);
	return(1);
}

WINDOW *CreateWindow (int height, int width, int starty, int startx, int do_box)
{	
	WINDOW *win;
	win = newwin(height, width, starty, startx);
	if(do_box)  box(win, 0, 0);
	wrefresh(win);
	return(win);
}

int FileExists (char *path)
{
	return(access(path, 0) == 0);
}

int Msg (char *txt, ...)
{
	char msg[1024] = "";
	int ch;
	va_list args;
	va_start(args, txt);
	vsprintf(msg, txt, args);
	va_end(args);
	wclear(StatusBar);
	mvwprintw(StatusBar, 0, 1, msg);
	wrefresh(StatusBar);
	ch = wgetch(StatusBar);
	SetMode(Mode);
	if(ch == 'y' || ch == 'Y')  return(1);
	return(0);
}

int ReadDirectory (char *dname)
{
	DIR *dp;
	struct dirent *entry;
	struct stat st;
	TFileInfo *fi;
	char dirname[1024], filename[1024];
	int cnt = 0;
	strcpy(dirname, dname);
	if((dp = opendir(dirname)) == NULL) {
		SetStatusBar("Cannot Open Directory: %s", dirname);
		getcwd(dirname, MAXPATH);
		dp = opendir(dirname);
	}
	FwCurItm = FwTopItm = 0;
	SetStatusBar("Checking %s...", dirname);
	ClearArray();
	while(((entry = readdir(dp)) != NULL) && (FileCnt < MAXFILES)) {
		sprintf(filename, "%s/%s", dirname, entry->d_name);
		if(stat(filename, &st) != 0)  continue;
		if(S_ISDIR(st.st_mode) && strcmp(".", entry->d_name)) {
			if((fi = calloc(1, sizeof(TFileInfo)))) {
				strcpy(fi->Name, entry->d_name);
				stat(filename, &fi->St);
				Files[FileCnt++] = fi;
				cnt++;
			}
			else { SetStatusBar("Allocation Error! Files[%d]", FileCnt); }
		}
	}
	closedir(dp);
	SortList();
	strcpy(CurDir, dirname);
	UpdateDirList(0);
	return(cnt);
}

int ReadLine (FILE *rfile, char *line, int maxlinesize)
{
	register int ch, c = 0;
	do {
		while((ch = getc(rfile)) == 13);
		if((ch == 26) || (ch == -1)) return(-1);
		if(ch == 10) break;
		line[c] = ch;
		c++;
	} while(c < maxlinesize);
	line[c] = 0;
	return(c);
}

int Report ()
{
	int chksubs = 0, i, j;
	if(!SrcDir[0] || !DstDir[0]) { Msg("Src and/or Dst not yet set"); SetMode(Mode); return(0); }
	if(strcmp(SrcDir, DstDir) == 0) { Msg("Src and Dst are the same!"); return(0); }
	ClearArray();
	ReportDirDiffs(0, 0);
	if(*(field_buffer(Fields[CHK_SUBDIRS], 0)) == '*')  chksubs = 1;
	for(i = 1; chksubs && i < MAXLEVELS && DirCnt[i]; i++) {
		for(j = 0; j < DirCnt[i]; j++)  ReportDirDiffs(Dirs[i][j], i);
	}
	if(FileCnt) {
		ValidList++;
		SetMode(MODE_REPORT);
		FwCurItm = FwTopItm = 0;
		SortList();
		ShowInfo(0);
		UpdateFileList(0);
	}
	else {
		Msg("No differences found.");
		SetMode(MODE_OPTIONS);
	}
	return(FileCnt);
}

int ReportDirDiffs (char *srcdir, int level)
{
	DIR *dp;
	struct dirent *entry;
	struct stat srcbuf, dstbuf;
	//struct tm *sts, *dts;
	char srcname[MAXPATH], dstname[MAXPATH], srcdirname[1024], dstdirname[1024];
	char temp[1024];
	time_t srctime, dsttime;
	off_t srcsize = 0, dstsize = 0;
	int addit = 0, showHidden = 0;
	if(level) {
		sprintf(srcdirname, "%s/%s", SrcDir, srcdir);
		sprintf(dstdirname, "%s/%s", DstDir, srcdir);
	}
	else {
		strcpy(srcdirname, SrcDir);
		strcpy(dstdirname, DstDir);
	}
	if((dp = opendir(srcdirname)) == NULL) { SetStatusBar("opendir failed: %s", srcdirname); return(0); }
	SetStatusBar("Checking %s...", srcdirname);
	if(*(field_buffer(Fields[CHK_INC_ALL], 0)) == '*')  showHidden = 1;
	while((entry = readdir(dp)) != NULL) {
		addit = 0;
		if(entry->d_name[0] == '.' && !showHidden)  continue;
		sprintf(srcname, "%s/%s", srcdirname, entry->d_name);
		sprintf(dstname, "%s/%s", dstdirname, entry->d_name);
		if(stat(srcname, &srcbuf) != 0)  continue;
		srcsize = srcbuf.st_size;
		srctime = srcbuf.st_mtime;
		if(FileExists(dstname)) {
			if(stat(dstname, &dstbuf) != 0)  continue;
			if(S_ISREG(dstbuf.st_mode)) {
				dstsize = dstbuf.st_size;
				dsttime = dstbuf.st_mtime;
				if(srcsize != dstsize)  addit = (dstsize < srcsize) ? 4 : 5;
				if(srctime != dsttime)  addit = (dsttime < srctime) ? 2 : 3;
			}
			else if(S_ISDIR(srcbuf.st_mode) && S_ISDIR(dstbuf.st_mode)) {
				if(!strcmp(".", entry->d_name) || !strcmp("..", entry->d_name))  continue;
				if(level)  sprintf(temp, "%s/%s", srcdir, entry->d_name);
				else  strcpy(temp, entry->d_name);
				if((level + 1) < (MAXLEVELS - 1)) {
					if((Dirs[level+1][DirCnt[level+1]] = calloc(1, strlen(temp) + 1))) {
						strcpy(Dirs[level+1][DirCnt[level+1]], temp);
						DirCnt[level+1]++;
					}
					else { SetStatusBar("Allocation Error! Dirs[%d][%d]", level+1, DirCnt[level+1]); return(0); }
				}
				else { SetStatusBar("Maximum Levels Reached!"); return(0); }
			}
		}
		else {
			if(S_ISREG(srcbuf.st_mode))  addit = 1;
			else if(S_ISDIR(srcbuf.st_mode) && strcmp(".", entry->d_name) && strcmp("..", entry->d_name))  addit = 6;
		}
		if(addit) {
			if((Files[FileCnt] = calloc(1, sizeof(TFileInfo)))) {
				stat(srcname, &Files[FileCnt]->St);
				if(level)  sprintf(Files[FileCnt]->Name, "%s/%s", srcdir, entry->d_name);
				else strcpy(Files[FileCnt]->Name, entry->d_name);
				//sts = localtime(&srctime);
				//sprintf(Files[FileCnt]->Sts, "%04d.%02d.%02d %02d.%02d %10ld", sts->tm_year+1900, sts->tm_mon+1, sts->tm_mday, sts->tm_hour, sts->tm_min, srcsize);
				if((addit > 1) && (addit < 6)) {
					stat(dstname, &Files[FileCnt]->StD);
					//dts = localtime(&dsttime);
					//sprintf(Files[FileCnt]->Dts, "%04d.%02d.%02d %02d.%02d %10ld", dts->tm_year+1900, dts->tm_mon+1, dts->tm_mday, dts->tm_hour, dts->tm_min, dstsize);
				}
				Files[FileCnt]->DiffCode = addit;
				FileCnt++;
			}
			else { SetStatusBar("Allocation Error! Files[%d]", FileCnt); return(0); }
		}
	}
	closedir(dp);
	return(1);
}

void SetCheckBox ()
{
	int ndx;
	char *fldbuf;
	ndx = field_index(current_field(Form));
	switch(ndx) {
	  case BTN_GETSRC:
		SetMode(MODE_GETSRC);
		//strcpy(dirname, field_buffer(Fields[TXT_SRC], 0));
		//FileCnt = ReadDirectory(StrCvt(dirname, RTWHITE));
		FileCnt = ReadDirectory(SrcDir);
		break;
	  case BTN_GETDST:
		SetMode(MODE_GETDST);
		//strcpy(dirname, field_buffer(Fields[TXT_DST], 0));
		//FileCnt = ReadDirectory(StrCvt(dirname, RTWHITE));
		FileCnt = ReadDirectory(DstDir);
		break;
	  case CHK_SUBDIRS: case CHK_INC_ALL: case CHK_KEEP_ATTR: case CHK_BACKUP:
		fldbuf = field_buffer(Fields[ndx], 0);
		if(*fldbuf == ' ')  form_driver(Form, '*');
		else  form_driver(Form, ' ');
		form_driver(Form, REQ_NEXT_FIELD);
		form_driver(Form, REQ_PREV_FIELD);
		set_current_field(Form, Fields[ndx]);
		break;
	  default:
		break;
	}
}

void SetMode (int mode)
{
	Mode = mode;
	SetStatusBar(ModeStrs[Mode]);
	if(mode == MODE_OPTIONS) {
		ShowInfo(-1);
		wrefresh(OptionsWin);
		form_driver(Form, REQ_NEXT_FIELD);
		form_driver(Form, REQ_PREV_FIELD);
	}
	else {
		if(mode == MODE_HELP)  UpdateHelp(0);
		else if(mode == MODE_REPORT)  UpdateFileList(0);
		else  wmove(MainWin, (FwCurItm - FwTopItm) + 1, 1);
		wrefresh(MainWin);
	}
}

void SetStatusBar (char *txt, ...)
{
	char msg[1024] = "";
	va_list args;
	va_start(args, txt);
	vsprintf(msg, txt, args);
	va_end(args);
	wclear(StatusBar);
	mvwprintw(StatusBar, 0, 1, msg);
	wrefresh(StatusBar);
}

void SetTitleBar (char *txt, ...)
{
	char msg[1024] = "";
	va_list args;
	va_start(args, txt);
	vsprintf(msg, txt, args);
	va_end(args);
	wclear(TitleBar);
	mvwprintw(TitleBar, 0, 1, msg);
	wrefresh(TitleBar);
}

void SetValidList (int set)
{
	if(set)  ValidList++;
	else {
		ValidList = 0;
		if(FileCnt) {
			wclear(MainWin);
			box(MainWin, 0, 0);
			wrefresh(MainWin);
		}
	}
}

void ShowInfo (int ndx)
{
	int i;
	struct tm *ts;
	char txt[80], temp[40] = "";
	char *strs[4] = {
		"[+] Src:",
		"[+] Dst:",
		"[ ] Check All Subdirectories           [ ] Include Hidden Files In Report",
		"[ ] Preserve File Attributes           [ ] Make Backups Of Existing Files"
	};
	wclear(OptionsWin);
	box(OptionsWin, 0, 0);
	if(ndx < 0) {
		mvwprintw(OptionsWin, 0, 1, " Options ");
		for(i = 0; i < 4; i++)  mvwprintw(OptionsWin, i + 1, 2, "%s", strs[i]);
		for(i = 0; i < NUMFIELDS; i++)  set_field_buffer(Fields[i], 0, field_buffer(Fields[i], 0));
	}
	else {
		mvwprintw(OptionsWin, 0, 2, " Information ");
		mvwprintw(OptionsWin, 1, 2, Files[ndx]->Name);
		mvwprintw(OptionsWin, 2, 2, "%s", StStrs[Files[ndx]->DiffCode]);
		temp[0] = (Files[ndx]->St.st_mode & S_IRUSR) ? 'r' : '-';
		temp[1] = (Files[ndx]->St.st_mode & S_IWUSR) ? 'w' : '-';
		temp[2] = (Files[ndx]->St.st_mode & S_IXUSR) ? 'x' : '-';
		temp[3] = (Files[ndx]->St.st_mode & S_IRGRP) ? 'r' : '-';
		temp[4] = (Files[ndx]->St.st_mode & S_IWGRP) ? 'w' : '-';
		temp[5] = (Files[ndx]->St.st_mode & S_IXGRP) ? 'x' : '-';
		temp[6] = (Files[ndx]->St.st_mode & S_IROTH) ? 'r' : '-';
		temp[7] = (Files[ndx]->St.st_mode & S_IWOTH) ? 'w' : '-';
		temp[8] = (Files[ndx]->St.st_mode & S_IXOTH) ? 'x' : '-';
		temp[9] = 0;
		ts = localtime(&Files[ndx]->St.st_mtime);
		sprintf(txt, "%10ld  %04d.%02d.%02d %02d.%02d  %s %4d %4d", Files[ndx]->St.st_size, ts->tm_year+1900, 
		  ts->tm_mon+1, ts->tm_mday, ts->tm_hour, ts->tm_min, temp, Files[ndx]->St.st_uid, Files[ndx]->St.st_gid);
		mvwprintw(OptionsWin, 3, 2, "Src: %s", txt);
		if((Files[ndx]->DiffCode > 1) && (Files[ndx]->DiffCode < 6)) {
			temp[0] = (Files[ndx]->StD.st_mode & S_IRUSR) ? 'r' : '-';
			temp[1] = (Files[ndx]->StD.st_mode & S_IWUSR) ? 'w' : '-';
			temp[2] = (Files[ndx]->StD.st_mode & S_IXUSR) ? 'x' : '-';
			temp[3] = (Files[ndx]->StD.st_mode & S_IRGRP) ? 'r' : '-';
			temp[4] = (Files[ndx]->StD.st_mode & S_IWGRP) ? 'w' : '-';
			temp[5] = (Files[ndx]->StD.st_mode & S_IXGRP) ? 'x' : '-';
			temp[6] = (Files[ndx]->StD.st_mode & S_IROTH) ? 'r' : '-';
			temp[7] = (Files[ndx]->StD.st_mode & S_IWOTH) ? 'w' : '-';
			temp[8] = (Files[ndx]->StD.st_mode & S_IXOTH) ? 'x' : '-';
			temp[9] = 0;
			ts = localtime(&Files[ndx]->StD.st_mtime);
			sprintf(txt, "%10ld  %04d.%02d.%02d %02d.%02d  %s %4d %4d", Files[ndx]->StD.st_size, ts->tm_year+1900, 
			  ts->tm_mon+1, ts->tm_mday, ts->tm_hour, ts->tm_min, temp, Files[ndx]->StD.st_uid, Files[ndx]->StD.st_gid);
		}
		else  txt[0] = 0;
		mvwprintw(OptionsWin, 4, 2, "Dst: %s", txt);
	}
	wrefresh(OptionsWin);
}

void SortList ()
{
	int i, j;
	char astr[1024], bstr[1024];
	TFileInfo *tmp;
	for(i = 0; i < FileCnt - 1; i++) {
		for(j = i + 1; j < FileCnt; j++) {
			if(Mode == MODE_REPORT && SortMode) {
				sprintf(astr, "%d%d%s", Files[i]->DiffCode, (S_ISDIR(Files[i]->St.st_mode)) ? 0 : 1, Files[i]->Name);
				sprintf(bstr, "%d%d%s", Files[j]->DiffCode, (S_ISDIR(Files[j]->St.st_mode)) ? 0 : 1, Files[j]->Name);
			}
			else {
				sprintf(astr, "%d%s", (S_ISDIR(Files[i]->St.st_mode)) ? 0 : 1, Files[i]->Name);
				sprintf(bstr, "%d%s", (S_ISDIR(Files[j]->St.st_mode)) ? 0 : 1, Files[j]->Name);
			}
			if(strcmp(astr, bstr) > 0) {
				tmp = Files[i];
				Files[i] = Files[j];
				Files[j] = tmp;
			}
		}
	}
}

void StartCopy ()
{
	char fname[MAXPATH];
	int i, selcnt = 0;
	for(i = 0; i < FileCnt; i++)  if(Files[i]->Selected)  selcnt++;
	if(!selcnt) { Msg("Nothing selected."); return; }
	ShowInfo(-1);
	if(!Msg("Copy %d item%sfrom Src to Dst (y/N)? ", selcnt, (selcnt == 1) ? " " : "s "))  return;
	for(i = 0; i < FileCnt; i++) {
		if(!Files[i]->Selected)  continue;
		strcpy(fname, Files[i]->Name);
		Copy(fname);
	}
	SetValidList(0);
	SetMode(MODE_OPTIONS);
}

int StrChrIndex (char check, char *psearch)
{
	char *p;
	if((p = strchr(psearch, check)) == NULL)  return (-1);
	return((int) (p - psearch));
}

char *StrCvt (char *psource, int conv)
{
	char *pfrom = psource;	       //Next character to get fron source
	char *pto = psource;  // Next position to fill in target
	char c;
	char quote_char = '\0';
	int rlwhite = conv & RLWHITE;
	int rwhite = conv & RWHITE;
	int reduce = (!rwhite) && (conv & REDUCE);
	int ckquotes = conv & NOQUOTE;
	int to_up = conv & TOUP;
	int to_low = conv & TOLOW;
	int rcontrol = conv & RCONTROL;
	int to_case = conv & TOCASE;
	int in_white = FALSE;   	// Not in a white field yet.
	int hit_nonwhite = FALSE;   	// No nonwhite chars encountered.
	int quote_on = FALSE;   	// Not in a quote yet.
	while((c = *pfrom++) != '\0') {
		if(quote_on) {
			*pto++ = c;
			if(c == quote_char)  quote_on = FALSE;
		}
		else if (ckquotes && ((c == '"') || (c == '\''))) {
			*pto++ = c;
			in_white = FALSE;
			hit_nonwhite = TRUE;
			quote_on = TRUE;
			quote_char = c;
		}
		else if(isspace(c) && isascii(c)) {
			if(rwhite)  ;
			else if(rlwhite && !hit_nonwhite)  ;
			else if(reduce) {
				if(in_white)  ;
				else {
					*pto++ = ' ';
					in_white = TRUE;
				}
			}
			else if(rcontrol && iscntrl(c))  ;
			else  *pto++ = c;
		}
		else if(iscntrl(c) && isascii(c)) {
			in_white = FALSE;
			hit_nonwhite = TRUE;
			if (rcontrol)  ;
			else  *pto++ = c;
		}
		else {
			in_white = FALSE;
			hit_nonwhite = TRUE;
			if(isascii(c)) {
				if(to_up)  c = toupper(c);
				if(to_low)  c = tolower(c);
				if(to_case)  c = islower(c) ? toupper(c) : tolower(c);
			}
			*pto++ = c;
		}
	}
	*pto = '\0';
	if(conv & RTWHITE) {
		for(c = *--pto; isspace(c) && isascii(c) && (pto >= psource); c = *--pto)  *pto = '\0';
	}
	return(psource);
}

void SwitchDirs ()
{
	char txt[1024];
	strcpy(txt, SrcDir);
	strcpy(SrcDir, DstDir);
	strcpy(DstDir, txt);
	set_field_buffer(Fields[TXT_SRC], 0, SrcDir);
	set_field_buffer(Fields[TXT_DST], 0, DstDir);
	ValidList = 0;
}

void UpdateDirList (int cmd)
{
	int i, len, update = 0, end = 0;
	char txt[1024];
	TFileInfo *fi;
	switch(cmd) {
	  case KEY_PGDN:
		for(i = 0; i < FwRows && FwCurItm < FileCnt - 1; i++) {
			if(FwCurItm < FileCnt - 1)  FwCurItm++;
			if((FwCurItm - FwTopItm + 1) > FwRows) {
				FwTopItm++;
				update++;
			}
		}
		break;
	  case KEY_PGUP:
		for(i = FwRows; i && FwCurItm; i--) {
			if(FwCurItm)  FwCurItm--;
			if(FwCurItm < FwTopItm) {
				FwTopItm--; 
				update++;
			}
		}
		break;
	  case KEY_DOWN:
		if(FwCurItm < FileCnt - 1)  FwCurItm++;
		if((FwCurItm - FwTopItm + 1) > FwRows) {
			FwTopItm++;
			update++;
		}
		else  wmove(MainWin, (FwCurItm - FwTopItm) + 1, 1);
		break;
	  case KEY_UP:
		if(FwCurItm)  FwCurItm--;
		if(FwCurItm < FwTopItm) {
			FwTopItm--; 
			update++;
		}
		else  wmove(MainWin, (FwCurItm - FwTopItm) + 1, 1);
		break;
	  case KEY_HOME: case KEY_ATERM_HOME: case KEY_ETERM_HOME: case KEY_XTERM_HOME:
		FwCurItm = FwTopItm = 0;
		update++;
		break;
	  case KEY_END: case KEY_ATERM_END: case KEY_ETERM_END: case KEY_XTERM_END:
		FwCurItm = FileCnt - 1;
		if((FwCurItm - FwTopItm + 1) > FwRows) {
			FwTopItm = (FwCurItm - FwRows) + 1;
			update++;
		}
		else  wmove(MainWin, (FwCurItm - FwTopItm) + 1, 1);
		break;
	  case ' ':
		fi = Files[FwCurItm];
		if(strcmp(fi->Name, "..") != 0) {
			if(strlen(CurDir) == 1 && CurDir[0] == '/')  CurDir[0] = 0;
			if(Mode == MODE_GETSRC) {
				sprintf(SrcDir, "%s/%s", CurDir, fi->Name);
				set_field_buffer(Fields[TXT_SRC], 0, SrcDir);
			}
			else if(Mode == MODE_GETDST) {
				sprintf(DstDir, "%s/%s", CurDir, fi->Name);
				set_field_buffer(Fields[TXT_DST], 0, DstDir);
			}
			end++;
		}
		break;
	  case KEY_RETURN:
		fi = Files[FwCurItm];
		if(!strcmp(fi->Name, "..")) {
			strcpy(txt, CurDir);
			len = strlen(txt);
			if(len) {
				for(i = len - 1; i && (txt[i] != '/'); i--)  txt[i] = 0;
				txt[i] = 0;
			}
		}
		else {
			if(strlen(CurDir) == 1 && CurDir[0] == '/')  sprintf(txt, "/%s", Files[FwCurItm]->Name);
			else  sprintf(txt, "%s/%s", CurDir, Files[FwCurItm]->Name);
		}
		if(!strlen(txt))  strcpy(txt, "/");
		FileCnt = ReadDirectory(txt);
		update++;
		break;
	  case '.':
		if(Mode == MODE_GETSRC) {
			strcpy(SrcDir, CurDir);
			set_field_buffer(Fields[TXT_SRC], 0, SrcDir);
		}
		else if(Mode == MODE_GETDST) {
			strcpy(DstDir, CurDir);
			set_field_buffer(Fields[TXT_DST], 0, DstDir);
		}
		end++;
		break;
	  case KEY_F(12):
		end++;
		break;
	  default:  break;
	}
	if(end) {
		wclear(MainWin);
		box(MainWin, 0, 0);
		wrefresh(MainWin);
		SetMode(MODE_OPTIONS);
	}
	if(update || !cmd) {
		SetStatusBar(ModeStrs[Mode]);
		wclear(MainWin);
		box(MainWin, 0, 0);
		mvwprintw(MainWin, 0, 1, " %s ", CurDir);
		for(i = 0; i < FwRows && i < FileCnt; i++) {
			fi = Files[i+FwTopItm];
			strcpy(txt, (fi->Selected) ? ">" : " ");
			strcat(txt, fi->Name);
			mvwprintw(MainWin, i+1, 1, txt);
		}
		wmove(MainWin, (FwCurItm - FwTopItm) + 1, 1);
		wrefresh(MainWin);
	}
}

void UpdateFileList (int cmd)
{
	int i, update = 0, selchanged = 0;
	char txt[1024];
	TFileInfo *fi;
	switch(cmd) {
	  case KEY_DOWN:
		if(FwCurItm < FileCnt - 1) {
			FwCurItm++;
			if((FwCurItm - FwTopItm + 1) > FwRows) {
				FwTopItm++;
				update++;
			}
			else  wmove(MainWin, (FwCurItm - FwTopItm) + 1, 1);
			selchanged++;
		}
		break;
	  case KEY_PGDN:
		if(FwCurItm < FileCnt - 1) {
			for(i = 0; i < FwRows; i++) {
				if(FwCurItm < FileCnt - 1)  FwCurItm++;
				if((FwCurItm - FwTopItm + 1) > FwRows) {
					FwTopItm++;
					update++;
				}
				else  wmove(MainWin, (FwCurItm - FwTopItm) + 1, 1);
			}
			selchanged++;
		}
		break;
	  case KEY_UP:
		if(FwCurItm) {
			FwCurItm--;
			if(FwCurItm < FwTopItm) {
				FwTopItm--; 
				update++;
			}
			else  wmove(MainWin, (FwCurItm - FwTopItm) + 1, 1);
			selchanged++;
		}
		break;
	  case KEY_PGUP:
		if(FwCurItm) {
			for(i = FwRows; i; i--) {
				if(FwCurItm)  FwCurItm--;
				if(FwCurItm < FwTopItm) {
					FwTopItm--;
					update++;
				}
				else  wmove(MainWin, (FwCurItm - FwTopItm) + 1, 1);
			}
			selchanged++;
		}
		break;
	  case KEY_HOME: case KEY_ATERM_HOME: case KEY_ETERM_HOME: case KEY_XTERM_HOME:
		FwCurItm = FwTopItm = 0;
		update++;
		selchanged++;
		break;
	  case KEY_END: case KEY_ATERM_END: case KEY_ETERM_END: case KEY_XTERM_END:
		FwCurItm = FileCnt - 1;
		if((FwCurItm - FwTopItm + 1) > FwRows) {
			FwTopItm = (FwCurItm - FwRows) + 1;
			update++;
		}
		else  wmove(MainWin, (FwCurItm - FwTopItm) + 1, 1);
		selchanged++;
		break;
	  case ' ':
		fi = Files[FwCurItm];
		if(fi->Selected) { fi->Selected = 0; SelCnt--; }
		else { fi->Selected = 1; SelCnt++; }
		if(S_ISDIR(fi->St.st_mode))  sprintf(txt, "%c %d {%s}", (fi->Selected) ? '>' : ' ', fi->DiffCode, fi->Name);
		else  sprintf(txt, "%c %d %s", (fi->Selected) ? '>' : ' ', fi->DiffCode, fi->Name);
		mvwprintw(MainWin, (FwCurItm - FwTopItm) + 1, 1, txt);
		wmove(MainWin, (FwCurItm - FwTopItm) + 1, 1);
		break;
	  default:  break;
	}
	if(selchanged || !cmd)  ShowInfo(FwCurItm);
	if(update || !cmd) {
		wclear(MainWin);
		box(MainWin, 0, 0);
		mvwprintw(MainWin, 0, 1, " %d New Or Different ", FileCnt);
		for(i = 0; i < FwRows && i < FileCnt; i++) {
			fi = Files[i+FwTopItm];
			if(S_ISDIR(fi->St.st_mode))  sprintf(txt, "%c %d {%s}", (fi->Selected) ? '>' : ' ', fi->DiffCode, fi->Name);
			else  sprintf(txt, "%c %d %s", (fi->Selected) ? '>' : ' ', fi->DiffCode, fi->Name);
			mvwprintw(MainWin, i+1, 1, txt);
		}
		wmove(MainWin, (FwCurItm - FwTopItm) + 1, 1);
		wrefresh(MainWin);
	}
}

void UpdateHelp (int cmd)
{
	int i, update = 0;
	switch(cmd) {
	  case KEY_PGDN:
		if(HelpTopLine != HelpLastTop) {
			for(i = 0; i < HelpRows && HelpTopLine < HelpLastTop; i++)  HelpTopLine++;
			update++;
		}
		break;
	  case KEY_PGUP:
		if(HelpTopLine) {
			for(i = HelpRows; i && HelpTopLine; i--)  HelpTopLine--;
			update++;
		}
		break;
	  case KEY_DOWN:
		if(HelpTopLine < HelpLastTop) {
			HelpTopLine++;
			update++;
		}
		break;
	  case KEY_UP:
		if(HelpTopLine) {
			HelpTopLine--;
			update++;
		}
		break;
	  case KEY_HOME: case KEY_ATERM_HOME: case KEY_ETERM_HOME: case KEY_XTERM_HOME:
		if(HelpTopLine) {
			HelpTopLine = 0;
			update++;
		}
		break;
	  case KEY_END: case KEY_ATERM_END: case KEY_ETERM_END: case KEY_XTERM_END:
		if(HelpTopLine != HelpLastTop) {
			HelpTopLine = HelpLastTop;
			update++;
		}
		break;
	  default:  break;
	}
	if(update || !cmd) {
		wclear(MainWin);
		box(MainWin, 0, 0);
		mvwprintw(MainWin, 0, 1, " Help ");
		for(i = 0; i < HelpRows; i++)  mvwprintw(MainWin, i+1, 1, HelpTxt[HelpTopLine + i]);
		wmove(MainWin, 1, 1);
		wrefresh(MainWin);
	}
}

int WinGetCh (WINDOW *win)
{
	int ch, ndx = 0, wch[5] = { 0, 0, 0, 0, 0 }, done = 0;
	while(!done) {
		ch = wgetch(win);
		wch[ndx] = ch;
		switch(ndx) {
		  case 0:
			if(ch == 27)  ndx++;
			else  done++;
			break;
		  case 1:
			if(ch == 91 || ch == 79)  ndx++;
			else  done++;
			break;
		  case 2:
			if(wch[1] == 91 || wch[1] == 79) {
				if(wch[1] == 79) {
					ch = wch[0] + wch[1] + wch[2] + 156;
					done++;
				}
				else  ndx++;
			}
			else {
				ch = wch[0] + wch[1] + wch[2];
				done++;
			}
			break;
		  case 3:
			if(wch[1] == 91 && ch != 126)  ndx++;
			else {
				ch = wch[0] + wch[1] + wch[2] + wch[3];
				done++;
			}
			break;
		  case 4:
			ch = wch[0] + wch[1] + wch[2] + wch[3] + wch[4];
			done++;
			break;
		}
	}
	return(ch);
}


syntax highlighted by Code2HTML, v. 0.9.1