/************************************************************************
					ＸＷｉｎｄｏｗツールボックス

				ファイル・ユーティリティ・モジュール

			 		  Programmed by Y.Nishida

												[ Jan.20, 1994 ]
 ************************************************************************/

#include "ToolBox.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>

extern alphasort();

#define LOAD_DIALOG		1
#define SAVE_DIALOG 	2

#define DLOG_WIDTH		380
#define	DLOG_HEIGHT		220

static ControlPtr dirControl;			/* ディレクトリ表示エリア横のスクロール・バー		*/
static ControlPtr dirNameArea;			/* ディレクトリ名表示用サブウィンドウ				*/
static ControlPtr dirViewArea;			/* ディレクトリ内容表示用サブウィンドウ				*/
static ControlPtr openButton;			/* オープン・ボタン（デフォルト・ボタン）			*/
static ControlPtr saveButton;			/* セーブ・ボタン（デフォルト・ボタン）				*/
static ControlPtr newButton;			/* ニュー・ボタン（デフォルト・ボタン）				*/
static TxEditPtr  fileNameArea;			/* セーブ・ファイル名表示用サブウィンドウ			*/

static int dialogType;					/* ロード・ダイアログかサーブ・ダイアログかを示す	*/
static int dirEntries;					/* ディレクトリ内のエントリ数						*/
static struct dirent **dirNameList;		/* ディレクトリ・リストへのポインタ					*/
static char *dirFlagList;				/* サブディレクトリ・フラッグリストへのポインタ		*/

static char directory[1024] = "";		/* ディレクトリ名									*/
static char filename[256] = "";			/* セーブ・ファイル名								*/
static char pathname[1024];				/* パス名											*/
static char *fnameptr;					/* パス名中のファイル名へのポインタ					*/
static FuncPtr filterFunc;				/* フィルター関数へのポインタ						*/

/***********************************************************************
	ディレクトリ・リストに入れるファイルを選択する.
 ***********************************************************************/

static int SelectDirEntry(entry)
struct dirent *entry;
{
	struct stat status;

	strcpy(fnameptr,entry->d_name);
	stat(pathname,&status);

	if (!strcmp(entry->d_name,"." )) return(0);
	if (!strcmp(entry->d_name,"..")) return(0);
	if (filterFunc != NULL) return((*filterFunc)(entry,&status));
	return(1);
}

/***********************************************************************
	ディレクトリ・フラッグリストを作成する.
 ***********************************************************************/

static void CreateFlagList()
{
	struct stat status;
	int index;

	strcpy(pathname,directory);
	strcat(pathname,"/");
	fnameptr = pathname + strlen(pathname);

	for (index = 0; index< dirEntries; index++) {
		strcpy(fnameptr,dirNameList[index]->d_name);
		stat(pathname,&status);
		dirFlagList[index] = (status.st_mode & S_IFDIR) != 0;
	}
}

/***********************************************************************
	ディレクトリ・リストを読みだす.
 ***********************************************************************/

static void CreateDirList()
{
	strcpy(pathname,directory);
	strcat(pathname,"/");

	fnameptr	= pathname + strlen(pathname);
	dirEntries  = scandir(directory,&dirNameList,(_Selectfunc *)SelectDirEntry,(_Sortfunc *)alphasort);
	dirFlagList = (char *)calloc(dirEntries,sizeof(char));
	CreateFlagList();

	dirViewArea->minval =  0;
	dirViewArea->maxval = (dirEntries>10) ? 10 : dirEntries;
	dirViewArea->value  = -1;
	DrawControl(dirViewArea);
	if (dialogType == LOAD_DIALOG) EnableControl(openButton,0);

	dirControl->maxval = (dirEntries>10) ? dirEntries-10 : 0;
	SetCtrlValue(dirControl,0);
}

/***********************************************************************
	ディレクトリ・リストを削除する.
 ***********************************************************************/

static void DisposeDirList()
{
	int count;

	for (count=0; count<dirEntries; count++) {
		free(dirNameList[count]);
	}
	free(dirNameList);
	free(dirFlagList);
}

/************************************************************************
	ディレクトリ名を表示する.
 ************************************************************************/

static void DrawDirName(wcore)
WCorePtr wcore;
{
	int posx,width,length;
	char *string = directory + strlen(directory);

	while (*--string != '/');
	if (*(string+1) != '\0') string++;

	length = strlen(string);
	width  = XTextWidth(systemFont,string,length);
	posx   = (wcore->width  - width) / 2;

	XClearWindow(display,wcore->windowID);
	XDrawImageString(display,wcore->windowID,systemGPort,posx,13,string,length);
}

/************************************************************************
	ディレクトリ・リストを表示する.
 ************************************************************************/

static void DrawDirectory(wcore)
WCorePtr wcore;
{
	int posy,index;
	ControlPtr ctrl = (ControlPtr)wcore;

	XClearWindow(display,wcore->windowID);

	for (posy=13,index=ctrl->minval; index<ctrl->maxval; posy+=16,index++) {
		char *name = dirNameList[index]->d_name;
/*		int  len   = dirNameList[index]->d_namlen;	*/
		int  len   = strlen(name);
		int pict   = dirFlagList[index] + 23;

		XCopyPlane(display,systemPicture[pict],wcore->windowID,systemGPort,0,0,16,16,2,posy-13,1);
		XDrawImageString(display,wcore->windowID,systemGPort,20,posy,name,len);

		if (index == ctrl->value) {
			XFillRectangle(display,wcore->windowID,sysEorGPort,0,posy-13,wcore->width,16);
		}
	}
}

/************************************************************************
	選択したディレクトリのエントリをチェックする.
 ************************************************************************/

static int CheckDirEntry()
{
	int index  = dirViewArea->value;
	int result = (dirFlagList[index] == 0);

	if (!result) {
		strcat(directory,"/");
		strcat(directory,dirNameList[index]->d_name);

		DisposeDirList();
		CreateDirList();
		DrawDirectory((WCorePtr)dirViewArea);
		DrawDirName((WCorePtr)dirNameArea);
	}
	else if (dialogType == SAVE_DIALOG) {
		strcpy(filename,dirNameList[index]->d_name);
		SetTxEditString(fileNameArea,filename);
		EnableControl(saveButton,strlen(filename)>0);
		EnableControl( newButton,strlen(filename)>0);
		result = 0;
	}
	return(result);
}

/************************************************************************
	ディレクトリ表示開始位置を設定する.
 ************************************************************************/

static void SetDirEntry(ctrl)
ControlPtr ctrl;
{
	dirViewArea->minval = ctrl->value;
	dirViewArea->maxval = ctrl->value+10;
	DrawDirectory((WCorePtr)dirViewArea);
}

/************************************************************************
	ディレクトリ表示を下にスクロールさせる.
 ************************************************************************/

static void ScrlDownDirEntry(ctrl)
ControlPtr ctrl;
{
	if (ctrl->value > ctrl->minval) {
		ctrl->value			-= 1;
		dirViewArea->minval -= 1;
		dirViewArea->maxval -= 1;
		DrawDirectory((WCorePtr)dirViewArea);
	}
}

/************************************************************************
	ディレクトリ表示を上にスクロールさせる.
 ************************************************************************/

static void ScrlUpDirEntry(ctrl)
ControlPtr ctrl;
{
	if (ctrl->value < ctrl->maxval) {
		ctrl->value			+= 1;
		dirViewArea->minval += 1;
		dirViewArea->maxval += 1;
		DrawDirectory((WCorePtr)dirViewArea);
	}
}

/************************************************************************
	ディレクトリ表示を１ページ下げる.
 ************************************************************************/

static void PageDownDirEntry(ctrl)
ControlPtr ctrl;
{
	if (ctrl->value > ctrl->minval+10) {
		ctrl->value			-= 10;
		dirViewArea->minval -= 10;
		dirViewArea->maxval -= 10;
		DrawDirectory((WCorePtr)dirViewArea);
	}
	else if (ctrl->value != ctrl->minval) {
		ctrl->value			= ctrl->minval;
		dirViewArea->minval =  0;
		dirViewArea->maxval = 10;
		DrawDirectory((WCorePtr)dirViewArea);
	}
}

/************************************************************************
	ディレクトリ表示を１ページ上げる.
 ************************************************************************/

static void PageUpDirEntry(ctrl)
ControlPtr ctrl;
{
	if (ctrl->value < ctrl->maxval-10) {
		ctrl->value			+= 10;
		dirViewArea->minval += 10;
		dirViewArea->maxval += 10;
		DrawDirectory((WCorePtr)dirViewArea);
	}
	else if (ctrl->value != ctrl->maxval) {
		ctrl->value			= ctrl->maxval;
		dirViewArea->minval = dirEntries-10;
		dirViewArea->maxval = dirEntries;
		DrawDirectory((WCorePtr)dirViewArea);
	}
}

/************************************************************************
	ディレクトリ表示用コントロールへのイベント処理.
 ************************************************************************/

static void DirControlEvent(ctrl,code)
ControlPtr ctrl;
int code;
{
	switch (code) {
		case 0: SetDirEntry(ctrl);			break;
		case 1: ScrlDownDirEntry(ctrl);		break;
		case 2:	ScrlUpDirEntry(ctrl);		break;
		case 3: PageDownDirEntry(ctrl);		break;
		case 4: PageUpDirEntry(ctrl);		break;

	}
}

/************************************************************************
	ファイル名の選択処理.
 ************************************************************************/

static SelectFileName(event,wcore)
XEventPtr event;
WCorePtr wcore;
{
	static int oldTick = 0;

	int fname,posy;
	ControlPtr ctrl = (ControlPtr)wcore;
	int newTick		= event->xbutton.time;

	fname = event->xbutton.y / 16 + ctrl->minval;
	if (fname == ctrl->value) {
		if ((newTick - oldTick) < 400) {
			WindowPtr parent = GetWindowPtr(wcore->parentID);

			if (parent != NULL) parent->flags = CheckDirEntry();
		}
	}
	else if (ctrl->minval <= fname && fname < ctrl->maxval) {
		if (ctrl->minval <= ctrl->value && ctrl->value < ctrl->maxval) {
			posy = (ctrl->value - ctrl->minval) * 16;
			XFillRectangle(display,wcore->windowID,sysEorGPort,0,posy,wcore->width,16);
		}
		ctrl->value = fname;
		posy		= (ctrl->value - ctrl->minval) * 16;
		XFillRectangle(display,wcore->windowID,sysEorGPort,0,posy,wcore->width,16);
		if (dialogType == LOAD_DIALOG) EnableControl(openButton,1);
	}
	oldTick	= newTick;
}

/************************************************************************
	ディレクトリ名表示エリアへのイベント処理.
 ************************************************************************/

static void DirNameEvent(event,wcore)
XEventPtr event;
WCorePtr wcore;
{
	if (event->type == Expose && event->xexpose.count == 0) {
		DrawDirName(wcore);
	}
}

/************************************************************************
	ディレクトリ表示エリアへのイベント処理.
 ************************************************************************/

static void DirectoryEvent(event,wcore)
XEventPtr event;
WCorePtr wcore;
{
	switch (event->type) {
		case 		Expose: DrawDirectory(wcore);			break;
		case ButtonRelease: SelectFileName(event,wcore);	break;
	}
}

/************************************************************************
	カレントのディレクトリをホーム・ディレクトリに変更する.
 ************************************************************************/

static void ChangeToHomeDir(ctrl,code)
ControlPtr ctrl;
int code;
{
	char *home = getenv("HOME");

	if (home != NULL && strcmp(home,directory)) {
		strcpy(directory,home);
		DisposeDirList();
		CreateDirList();
		DrawDirectory((WCorePtr)dirViewArea);
		DrawDirName((WCorePtr)dirNameArea);
	}
}

/************************************************************************
	カレントのディレクトリを親ディレクトリに変更する.
 ************************************************************************/

static void ChangeToParentDir(ctrl,code)
ControlPtr ctrl;
int code;
{
	int index = strlen(directory) - 1;

	while (directory[index] != '/') index--;
	if (index == 0) index += 1;
	directory[index] = '\0';

	DisposeDirList();
	CreateDirList();
	DrawDirectory((WCorePtr)dirViewArea);
	DrawDirName((WCorePtr)dirNameArea);
}

/************************************************************************
	オープン・ボタンを押したときの処理.
 ************************************************************************/

static void PressOpenButton(wcore,type)
WCorePtr wcore;
int type;
{
	WindowPtr parent = GetWindowPtr(wcore->parentID);

	if (parent != NULL && dirViewArea->value >= 0) {
		parent->flags = CheckDirEntry();
	}
}

/************************************************************************
	キャンセル・ボタンを押したときの処理.
 ************************************************************************/

static void PressCancelButton(wcore,type)
WCorePtr wcore;
int type;
{
	WindowPtr parent = GetWindowPtr(wcore->parentID);

	if (parent != NULL) parent->flags = -1;
}

/************************************************************************
	ファイル・ロード用ダイアログの描画を行なう.
 ************************************************************************/

static void DrawFLoadDialog(event,wcore)
XEventPtr event;
WCorePtr wcore;
{
	if (event->type == Expose && event->xexpose.count == 0) {
		WindowPtr window = (WindowPtr)wcore;

		XClearWindow(display,wcore->windowID);
		XDrawRectangle(display,wcore->windowID,systemGPort,wcore->width-100,125,80,1);
		XDrawRectangle(display,wcore->windowID,systemGPort, 3, 3,wcore->width-8,wcore->height-8);

		XSetLineAttributes(display,systemGPort,3,LineSolid,CapRound,JoinRound);
		XDrawRectangle(display,wcore->windowID,systemGPort,wcore->width-104,136,89,29);
		XSetLineAttributes(display,systemGPort,0,LineSolid,CapButt ,JoinMiter);
	}
}

/************************************************************************
	ファイル・ロード用ダイアログへのイベント処理を行なう.
 ************************************************************************/

static void FLoadDialogEvent(event,wcore)
XEventPtr event;
WCorePtr wcore;
{
	switch (event->type) {
		case   Expose: DrawFLoadDialog(event,wcore);	break;
		case KeyPress: InputDialog(event,wcore);		break;
	}
}

/************************************************************************
	ファイル・ロード用ダイアログを作成する.
 ************************************************************************/

static WindowPtr CreateFLoadDialog(px,py)
int px,py;
{
	ControlPtr viewArea;
	WindowPtr dialog = NewApplWindow(px,py,DLOG_WIDTH,DLOG_HEIGHT,NULL,0,1,FLoadDialogEvent);
	WCorePtr wcore   = (WCorePtr)dialog;

	openButton = CreatePushButton(dialog,DLOG_WIDTH-100,140,80,20,"Open"  ,0,PressOpenButton);

	CreatePushButton(dialog,DLOG_WIDTH-100, 60,80,20,"Home"  ,0,ChangeToHomeDir  );
	CreatePushButton(dialog,DLOG_WIDTH-100, 90,80,20,"Parent",0,ChangeToParentDir);
	CreatePushButton(dialog,DLOG_WIDTH-100,175,80,20,"Cancel",0,PressCancelButton);

	dirControl   = CreateScrollBar(dialog,250,40,15,160,0,16, 4,0,DirControlEvent);
	dirViewArea  = CreateSubWindow(dialog, 10,40,240,160,1,0,DirectoryEvent);
	dirNameArea  = CreateSubWindow(dialog, 40,15,180, 16,1,0,DirNameEvent  );

	XSelectInput(display,dirViewArea->window.windowID,ExposureMask | ButtonPressMask | ButtonReleaseMask);
	XSelectInput(display,dirNameArea->window.windowID,ExposureMask);
	return(dialog);
}

/************************************************************************
	入力ファイル選択ユーティリティ
 ************************************************************************/

extern int SelectInputFile(px,py,path,func)
int px,py;
String path;
FuncPtr func;
{
	int result;
	XEvent event;
	WCorePtr wcore;
	WindowPtr dialog;

	if (!strcmp(directory,"")) {
		getwd(directory);
	}
	*path	   = '\0';
	filterFunc = func;
	dialogType = LOAD_DIALOG;
	dialog	   = CreateFLoadDialog(px,py);
	CreateDirList();

	if ((result = ModalDialog(dialog)) == 1) {
		strcpy(path,directory);
		strcat(path,"/");
		strcat(path,dirNameList[dirViewArea->value]->d_name);
	}
	DisposeDirList();
	DisposeApplWindow(dialog);
	return(result == 1);
}




/************************************************************************
	ニュー・ディレクトリ・ボタンを押したときの処理.
 ************************************************************************/

static void PressNewButton(wcore,type)
WCorePtr wcore;
int type;
{
	char temp[1024];

	GetTxEditString(fileNameArea,filename);
	strcpy(temp,directory);
	strcat(temp,"/");
	strcat(temp,filename);

	if (mkdir(temp,0777) == 0) {
		strcpy(directory,temp);
		DisposeDirList();
		CreateDirList();
		DrawDirectory((WCorePtr)dirViewArea);
		DrawDirName((WCorePtr)dirNameArea);
	}
}

/************************************************************************
	セーブ・ボタンを押したときの処理.
 ************************************************************************/

static void PressSaveButton(wcore,type)
WCorePtr wcore;
int type;
{
	WindowPtr parent = GetWindowPtr(wcore->parentID);

	if (parent != NULL) parent->flags = 1;
}

/************************************************************************
	ファイル・セーブ用ダイアログの描画を行なう.
 ************************************************************************/

static void DrawFSaveDialog(event,wcore)
XEventPtr event;
WCorePtr wcore;
{
	if (event->type == Expose && event->xexpose.count == 0) {
		WindowPtr window = (WindowPtr)wcore;

		XClearWindow(display,wcore->windowID);
		XDrawRectangle(display,wcore->windowID,systemGPort,wcore->width-100,155,80,1);
		XDrawRectangle(display,wcore->windowID,systemGPort, 3, 3,wcore->width-8,wcore->height-8);

		XDrawImageString(display,wcore->windowID,systemGPort,12,220,"Enter file name:",16);

		XSetLineAttributes(display,systemGPort,3,LineSolid,CapRound,JoinRound);
		XDrawRectangle(display,wcore->windowID,systemGPort,wcore->width-104,166,89,29);
		XSetLineAttributes(display,systemGPort,0,LineSolid,CapButt ,JoinMiter);
	}
}

/************************************************************************
	セーブ用ダイアログへのキー入力処理を行なう.
 ************************************************************************/

static void InputSaveDialog(event,wcore)
XEventPtr event;
WCorePtr wcore;
{
	InputDialog(event,wcore);
	if (fileNameArea->length == 0) {
		if (fileNameArea->refCon == 1) {
			EnableControl(saveButton,0);
			EnableControl( newButton,0);
			fileNameArea->refCon = 0;
		}
	}
	else if (fileNameArea->refCon == 0) {
		EnableControl(saveButton,1);
		EnableControl( newButton,1);
		fileNameArea->refCon = 1;
	}
}

/************************************************************************
	ファイル・セーブ用ダイアログへのイベント処理を行なう.
 ************************************************************************/

static void FSaveDialogEvent(event,wcore)
XEventPtr event;
WCorePtr wcore;
{
	switch (event->type) {
		case   Expose: DrawFSaveDialog(event,wcore);	break;
		case KeyPress: InputSaveDialog(event,wcore);	break;
	}
}

/************************************************************************
	ファイル・セーブ用ダイアログを作成する.
 ************************************************************************/

static WindowPtr CreateFSaveDialog(px,py)
int px,py;
{
	ControlPtr viewArea;
	WindowPtr dialog = NewApplWindow(px,py,DLOG_WIDTH,DLOG_HEIGHT+40,NULL,0,1,FSaveDialogEvent);
	WCorePtr wcore   = (WCorePtr)dialog;
	int active	     = strlen(filename) > 0;

	saveButton = CreatePushButton(dialog,DLOG_WIDTH-100,170,80,20,"Save"  ,0,PressSaveButton);
	newButton  = CreatePushButton(dialog,DLOG_WIDTH-100,120,80,20,"New"   ,0,PressNewButton );
	EnableControl(saveButton,active);
	EnableControl( newButton,active);

	CreatePushButton(dialog,DLOG_WIDTH-100, 60,80,20,"Home"  ,0,ChangeToHomeDir  );
	CreatePushButton(dialog,DLOG_WIDTH-100, 90,80,20,"Parent",0,ChangeToParentDir);
	CreatePushButton(dialog,DLOG_WIDTH-100,205,80,20,"Cancel",0,PressCancelButton);

	dirControl   = CreateScrollBar(dialog,250,40,15,160,0,16, 4,0,DirControlEvent);
	dirViewArea  = CreateSubWindow(dialog, 10,40,240,160,1,0,DirectoryEvent);
	dirNameArea  = CreateSubWindow(dialog, 40,15,180, 16,1,0,DirNameEvent);
	fileNameArea = CreateTextEdit(dialog, 10,225,256,22,filename,active,NULL);

	XSelectInput(display,dirViewArea->window.windowID,ExposureMask | ButtonPressMask | ButtonReleaseMask);
	XSelectInput(display,dirNameArea->window.windowID,ExposureMask);

	return(dialog);
}

/************************************************************************
	出力ファイル選択ユーティリティ
 ************************************************************************/

extern int SelectOutputFile(px,py,path,func)
int px,py;
String path;
FuncPtr func;
{
	int result;
	XEvent event;
	WCorePtr wcore;
	WindowPtr dialog;

	if (!strcmp(directory,"")) {
		getwd(directory);
	}
	*path	   = '\0';
	filterFunc = func;
	dialogType = SAVE_DIALOG;
	dialog	   = CreateFSaveDialog(px,py,path);
	CreateDirList();

	if ((result = ModalDialog(dialog)) == 1) {
		GetTxEditString(fileNameArea,filename);
		strcpy(path,directory);
		strcat(path,"/");
		strcat(path,filename);
	}
	DisposeDirList();
	DisposeApplWindow(dialog);
	return(result == 1);
}




/************************************************************************
	パス名内のファイル名へのポインタを得る.
 ************************************************************************/

static char *GetFileName(pathname)
char *pathname;
{
	char *filename = pathname + strlen(pathname);

	while (*--filename != '/');
	return(filename+1);
}


/************************************************************************
	パス名をファイル選択ユーティリティに設定する
 ************************************************************************/

extern void SetPathName(path)
String path;
{
	char *fname;

	strcpy(directory,path);
	fname = GetFileName(directory);

	*(fname-1) = '\0';
	strcpy(filename,fname);
}

