implementation module EdWindows;

import StdClass,StdInt, StdBool, StdChar, StdString, StdMisc, StdTuple;
import deltaFileSelect, deltaIOState;
import deltaWindow, deltaDialog, deltaMenu;

import	EdProgramState, EdDialogs, EdMouse, EdFiles, EdPath, EdLists, EdMenuItems, EdDrawWindow,
		EdKeyboard, EdTextWindow, EdText;
import EdCleanSystem;

YesID				:== 1;
NoID				:== 2;
CancelID			:== 3;

HorBar htmb			:== ScrollBar (Thumb htmb) (Scroll HorScroll);
VerBar vtmb hght	:== ScrollBar (Thumb vtmb) (Scroll hght);

/* RWS
import Debug;

CheckWdId which windowID programState ioState
	# (activeWindow, activeWindowId, ioState)
		=	GetActiveWindow ioState;
	| not activeWindow || activeWindowId <> windowID
		=	(programState, ioState);
	| fst (GetFrontWindow programState.editor.editwindows) <> windowID
		=	(programState, ioState) ->> which +++ " CheckWdId -> not the front window\n";
	| otherwise
		=	(programState, ioState);
CheckWdId0 ::  String WindowId (ProgState -> .(IO -> ProgIO)) ProgState IO -> ProgIO;
CheckWdId0 which windowID f programState ioState
	# (programState, ioState)
		=	CheckWdId which windowID programState ioState
	=	f programState ioState;

CheckWdId1 ::  String WindowId (a ProgState -> .(IO -> ProgIO))  a ProgState IO -> ProgIO;
CheckWdId1 which windowID f arg programState ioState
	# (programState, ioState)
		=	CheckWdId which windowID programState ioState
	=	f arg programState ioState;
*/



CheckWdId0 _ _ f :== f;
CheckWdId1 _ _ f :== f;

/*	Create a new edit-window */

CreateEditWindow :: !EditWdId !Int !WindowPos_and_Size !EditWdId !EditWindow !String !ProgState !IO -> ProgIO;
CreateEditWindow	oldid index windowPositionAndSize
					wdid editwd=:{wtext={nrlines,curline,selection,cursorpos},wformat={metrics,winfont}}
					title prog io
	= Opened_UpdateMenuItems oldid index wdid title prog io`;
	where {
	io`			= DrawInActiveWindow [SetFont winfont.font, SetPenMode CopyMode] io1;
	io1			= OpenWindows [window] io;
	window  	= ScrollWindow wdid (posx,posy) title (HorBar htmb) (VerBar vtmb metrics.height)
				               ((PictureLeft,PictureTop),(PictureRight,bottom))
				               WdMinSize (sizex,sizey) (UpdateWindow wdid)
				               [	Activate (CheckWdId0 "Activate" wdid (ActivWindow wdid)),
									Deactivate (CheckWdId0 "Deactivate" wdid DeactivWindow),
									GoAway (CheckWdId0 "GoAway" wdid (CloseWindow "closing" wdid)),
									Keyboard Able (CheckWdId1 "Keyboard" wdid TypeUndo),
									Mouse Able (CheckWdId1 "Mouse" wdid Track),
									Cursor (SelectCursor wdid) ];
	bottom		= PictureBottom nrlines metrics.height;
	(htmb,vtmb)	= ScrollThumbs curline cursorpos selection sizex sizey metrics.height;

	({posx,posy,sizex,sizey})	= validatePositionAndSize windowPositionAndSize;

	validatePositionAndSize :: WindowPos_and_Size -> WindowPos_and_Size;
	validatePositionAndSize positionAndSize=:{posx, posy, sizex, sizey}
		=	{positionAndSize & posx = Between 0 (maxXPos-minXSize) posx, sizex = max sizex minXSize,
								posy = Between 0 (maxYPos-minYSize) posy, sizey = max sizey minYSize};
		where
		{
			(maxXPos, maxYPos)
				=	MaxScrollWindowSize;
			(minXSize, minYSize)
				=	WdMinSize;
		}
	};
	
/*	Determine the bottom of the picture domain */

PictureBottom :: !Int !Int -> Int;
PictureBottom nrlines hght
	| MinBottom > bot
		=  MinBottom;
	    =  bot;
	where {
		bot = PictureTop +  nrlines * hght ;
	};

/* Determine the thumb values */

ScrollThumbs :: !CurLine !CursorPos !Selection !Int !Int !Int -> (!Int, !Int);
ScrollThumbs cline curpos selection hwidth vwidth height
	= (htmb,vtmb);
	where {
	htmb | actpos.ActPasPos.x < PictureLeft + hwidth
			= PictureLeft;
			= actpos.ActPasPos.x - hwidth / 2;
	vtmb | actpos.ActPasPos.y <= PictureTop + vwidth - height
			= PictureTop;
			= actpos.ActPasPos.y - vwidth / 2;
	actpos	= EW_GetActivePos cline curpos selection;
	};

/* Select cursor */

SelectCursor :: !EditWdId -> CursorShape;
SelectCursor ClpbrdWdID		= ArrowCursor;
SelectCursor ProjectWdID	= ArrowCursor;
SelectCursor wdid			= IBeamCursor;


// The activate function for the windows

ActivWindow	:: !EditWdId !ProgState !IO -> ProgIO;
ActivWindow wdid prog=:{editor=ed=:{editwindows}} io
	= Window_UpdateMenuItems oldid wdid prog` io;
	where {
		prog`		= {prog & editor={ed & editwindows=SetWindowInFront wdid editwindows}};
		(oldid,_)	= GetFrontWindow editwindows;
	};

// The deactivate function for the windows

DeactivWindow :: !ProgState !IO -> ProgIO;
DeactivWindow prog io
//	=  (prog, io);
	=  (prog, DisableMenuItems [ICutID, ICopyID, IPasteID] io);

// Opens a new edit window

OpenEditWindow :: !Pathname !WindowPos_and_Size !EditOptions !CompilerOptions !PartTSel !NrLines !Text !ProgState !IO -> ProgIO;
OpenEditWindow path pos eo co tsel nrlines text prog=:{editor=ed=:{editwindows,editwdids,execwdid}} io
	= CreateEditWindow oldid index pos wdid wd name prog` io;
	where {
	prog`		= {prog & editor={ed & editwindows=editwindows`,editwdids=editwdids`,execwdid=execwdid`}};
	(editwindows`,editwdids`,wd,oldid,index,wdid)
				= AddEditWindow eo co tsel nrlines text path editwindows editwdids;
	execwdid`	= NewExecWdId execwdid editwindows`;
	name		= RemovePath path;
	};

//
//	Opening / updating of the info windows (errors and types).
//											
											
UpdateInfoWindow :: !EditWdId !Text !ProgState !IO -> ProgIO;
UpdateInfoWindow wdid newtext prog=:{editor=ed=:{editwindows}} io
	| newnrlines == 0	= (prog, io);
	| winopen			= DrawTextUpdate wdid ScrollHalfWin update prog` io;
						= OpenInfoWindow wdid newnrlines newtext` prog io;
	where {
	winopen			= WindowPresent wdid editwindows;
	oldwd			= GetWindow wdid editwindows;
	prog`			= {prog & editor={ed & editwindows=SetWindow wdid newwd editwindows}};
	newtext`		= Text_AppendNewLine newtext;	// Make sure new error window text ends
	newnrlines		= Text_NrLines newtext`;		// with an empty line.
	(newwd,update)	= AppendText oldwd newtext;
	};
	
/*	Auxilary function used by 'UpdateInfoWindow' to open an info window if not already open.
*/

OpenInfoWindow :: !EditWdId !NrLines !Text !ProgState !IO -> ProgIO;
OpenInfoWindow wdid nrlines text prog=:{editor=ed=:{defaults,editwindows,editwdids,execwdid}} io
	= DrawUpdateWindow wdid id prog` io`;
	where {
	(prog`,io`)			= CreateEditWindow oldid index options.pos_size wdid wd wdtitle prog1 io;
	prog1				= {prog & editor={ed & editwindows=editwindows`,editwdids=editwdids`,execwdid=execwdid`}};
	(editwindows`,editwdids`,wd,oldid,index,_)
						= AddInfoWindow wdtype options.eo nrlines text editwindows editwdids;
	execwdid`			= NewExecWdId execwdid editwindows`;
	options | is_error	= defaults.Defaults.errors;
						= defaults.Defaults.types;
	wdtype | is_error	= ErrorWd;
						= TypeWd;
	wdtitle | is_error	= ErrorWdTitle;
						= TypeWdTitle;
	is_error			= wdid == ErrorWdID;
	id x = x;
	};	

// Update the project window.

import ProjectWindowDialog;
UpdateProjectWindowAndSaveProjectFile	:: !Project !ProgState !IO -> ProgIO;
UpdateProjectWindowAndSaveProjectFile project prog=:{editor=ed=:{editwindows}} io
	= Window_UpdateMenuItems frontid frontid prog` io`;
	where {
	(prog`,io`)	= SaveProjectIfNecessary {prog & editor={Editor | ed & project=project}} io;
	(frontid,_)	= GetFrontWindow editwindows;
	};

//
// Open the project window.
//
	
OpenProjectWindow :: !Pathname !Project !ProgState !IO -> ProgIO;
OpenProjectWindow path project prog=:{editor=ed=:{defaults={Defaults | dproject={eo}},editwindows,editwdids,execwdid}} io
	= Opened_UpdateMenuItems oldid index ProjectWdID title prog` io;
	where {
	prog`		= {prog & editor={ed & editwindows=editwindows`,editwdids=editwdids`,execwdid=execwdid`,project=project}};
	(editwindows1,editwdids`,_,oldid,index,_)
				= AddProjectWindow eo 1 EmptyText path editwindows editwdids;
	editwindows`= SetWindowInFront oldid editwindows1;
	execwdid`	= NewExecWdId execwdid editwindows`;
	title		= RemovePath path +++ "...";
	};

//
// The open function for the clipboard window
//
	
OpenClpbrdWindow :: !ProgState !IO -> ProgIO;
OpenClpbrdWindow prog=:{editor=ed=:{clipboard,defaults={clip},editwindows,editwdids,execwdid}} io
	= CreateEditWindow oldid index clip.pos_size ClpbrdWdID clpbrdwd ClpbrdWdTitle prog` io`
	where {
	io`				= ChangeMenuItemFunctions [(IShowCID, CloseClpbrdWindow)] io;
	prog`			= {prog & editor={ed & editwindows=editwindows`,editwdids=editwdids`,execwdid=execwdid`}};
	(editwindows`,editwdids`,clpbrdwd,oldid,index,_)
					= AddClipboardWindow clip.eo nrlines text editwindows editwdids;
	execwdid`		= NewExecWdId execwdid editwindows`;
	(text,nrlines)	= Text_ClipboardToText clipboard;
	};

CloseClpbrdWindow prog io
	= CloseTheWindow ClpbrdWdID ClpbrdWd prog
		(ChangeMenuItemFunctions [(IShowCID, OpenClpbrdWindow)] io);
		
//
// The update function for the clipboard
//

UpdateClipboardAndItsWindow :: !Clipboard !ProgState !IO -> ProgIO;
UpdateClipboardAndItsWindow clip prog=:{editor=ed=:{editwindows}} io
	# prog	= {prog & editor={Editor | ed & clipboard=clip}};
	| WindowPresent ClpbrdWdID editwindows
		= DrawUpdateWindow ClpbrdWdID (SetText nrlines text) prog io;
		{
			(text,nrlines)		= Text_ClipboardToText clip;
		}
	// otherwise
		= (prog,io);

UpdateGlobalClipboardAndClipboardWindow :: !Clipboard !ProgState !IO -> ProgIO;
UpdateGlobalClipboardAndClipboardWindow clip prog=:{editor=ed=:{editwindows}} io
	# io	= SetClipboardText (strict_string_list_to_string clip) io;
	=	UpdateClipboardAndItsWindow clip prog io;
	where {
		strict_string_list_to_string Nil = "";
		strict_string_list_to_string (s:!Nil) = s;
		strict_string_list_to_string (s:!sl) = s+++strict_string_list_to_string sl;
	};

//
// The close function for all windows
//

CloseWindow	:: !String !EditWdId !ProgState !IO -> ProgIO;
CloseWindow mes wdid prog io
	| wdid == ClpbrdWdID	= CloseClpbrdWindow prog io;
	| wdid == ProjectWdID	= CloseProjectWindow mes prog io; 
	| wdid == ErrorWdID		= CloseTheWindow ErrorWdID ErrorWd prog io;
	| wdid == TypeWdID		= CloseTheWindow TypeWdID TypeWd prog io;
							= CloseEditWindow True mes wdid prog io;

/* The Actual close function */

CloseTheWindow :: !EditWdId !WdType !ProgState !IO -> ProgIO;
CloseTheWindow wdid wdtype prog=:{editor=ed=:{defaults,execwdid,editwindows,editwdids}} io
	| not wdidpresent	= (prog, io)
						= Closed_UpdateMenuItems front_closed wdid prog` io`;
	where {
	io`							= CloseWindows [wdid] io1;
	(pos,io1)					= GetWindowPos_and_Size wdid io;
	prog`						= {prog & editor={ed & defaults=defaults`,editwindows=editwindows`,execwdid=execwdid`,editwdids=editwdids`}};
	defaults`					= SetDefaultWindowOptions wdtype defaults pos window.wformat.winfont;
	window						= GetWindow wdid editwindows;
	execwdid`					= NewExecWdId execwdid editwindows`;
	editwindows` | present		= SetWindowInFront curfrontid editwindows2;
								= editwindows2;
	(editwindows2,editwdids`)	= RemoveFrontWindow (SetWindowInFront wdid editwindows) editwdids;
	(curfrontid,_)				= GetFrontWindow editwindows;
	wdidpresent					= WindowPresent wdid editwindows;
	present						= WindowPresent curfrontid editwindows2;
	front_closed				= not present;
	};

/* The Close function for the project window */
	
CloseProjectWindow :: !String !ProgState !IO -> ProgIO;
CloseProjectWindow mes prog=:{editor={project,editwindows}} io
	= CloseTheWindow ProjectWdID ProjectWd prog` io`;
	where {
	prog`			= {prog2 & editor={Editor | prog2.editor & project=PR_InitProject}};
	(prog2,io`)		= SaveProjectIfNecessary prog1 io1;
	(prog1,io1)		= CloseProjectModules modnames prog io;
	modnames		= PR_GetModulenames True IclMod project;
	};
	
CloseProjectModules :: !(List Pathname) !ProgState !IO -> ProgIO;
CloseProjectModules Nil prog io
	= (prog,io);
CloseProjectModules (path:!rest) prog io
	= CloseProjectModules rest prog` io`;
	where {
	(prog`,io`)	= CloseProjectModule path prog io;
	};

CloseProjectModule :: !Pathname !ProgState !IO -> ProgIO;
CloseProjectModule path prog=:{editor={editwindows}} io
	= (prog`,io`);
	where {
	(_,dcl_id)		= IsExistingPathname defpath editwindows;
	(_,icl_id)		= IsExistingPathname path editwindows;
	(prog1,io1)		= CloseEditWindow False "closing" dcl_id prog io;
	(prog`,io`)		= CloseEditWindow False "closing" icl_id prog1 io1;
	defpath			= MakeDefPathname path;
	};
		
/* The Close function for the edit windows */

CloseEditWindow	:: !Bool !String !EditWdId !ProgState !IO -> ProgIO;
CloseEditWindow close mes wdid prog=:{editor={editwindows}} io
	| wdid	== NoWdID	= (prog,io);
	| wd.wstate.saved	= CloseTheEditWindow close wdid wd wd.wstate.pathname prog io;
						= SaveBeforeCloseNotice close wdid wd wd.wstate.pathname mes prog io;
	where {
	wd	= GetWindow wdid editwindows;
	};
	
/* The Save Before Close dialog if edit/project window has not been saved yet. */

SaveBeforeCloseNotice :: !Bool !EditWdId !EditWindow !Pathname  !String !ProgState !IO -> ProgIO;
SaveBeforeCloseNotice close wdid wd path mes prog io
	| butid == YesID	= SvBfClSave close wdid wd path prog` io`;
	| butid == NoID		= SvBfClDontSave close wdid wd path prog` io`;
						= (prog`, io`);
	where {
	(butid,prog`,io`)  =  OpenNotice (Notice [line1,line2] yes [no,cancel]) prog io;
	line1              =  "Save changes to \"" +++  name +++ "\"" ;
	line2              =  "before " +++  mes +++ "?" ;
	yes				   =  NoticeButton YesID "Yes";
	no	               =  NoticeButton NoID "No";
	cancel             =  NoticeButton CancelID "Cancel";
	name               =  RemovePath path;
	};

SvBfClSave :: !Bool !EditWdId !EditWindow !Pathname  !ProgState !IO -> ProgIO;
SvBfClSave close wdid wd path prog io
	| is_untitled			= DoSaveAs close wdid wd path prog io;
	| good					= CloseTheEditWindow close wdid wd path prog io`;
							= AlertDialog [	"The file "+++RemovePath path+++" could not be saved because",
	                				"of a file I/O error."] prog io`; 
	where {
	(good,io`)	= accFiles (SaveFile path wd.wformat.WinFormat.newlines text) io;
	(_,_,text)		= GetText wd;
	is_untitled		= GetModuleName path  == "Untitled";
	};

DoSaveAs :: !Bool !EditWdId !EditWindow !Pathname !ProgState !IO -> ProgIO;
DoSaveAs close wdid wd oldpath prog io
	# (save,path,prog,io)
		=	EdSelectOutputFile "Save As: " (RemovePath oldpath) prog io;
	| not save
		=	(prog,io);
	| RemovePath path == HelpFile
			=	HelpFileAlert prog io;
	# (exists,_)
			=	IsExistingPathname path prog.editor.editwindows;
	| exists && oldpath <> path
			=	AlertDialog [	"The file "+++RemovePath path+++" could not be saved because",
										"there is still another window open on it."] prog io;
	# (good,io)
		=	accFiles (SaveFile path wd.wformat.WinFormat.newlines text) io;
		with {
			(_,_,text)
				= GetText wd;
		};
	| not good
		=	AlertDialog [	"The file "+++RemovePath path+++" could not be saved because",
											"of a file I/O error."] prog io;
	// otherwise
		=	CloseTheEditWindow close wdid wd path prog io;

SvBfClDontSave :: !Bool !EditWdId !EditWindow !Pathname !ProgState !IO -> ProgIO;
SvBfClDontSave close wdid wd path editor io
	= CloseTheEditWindow close wdid wd path editor io;

CloseTheEditWindow :: !Bool !EditWdId !EditWindow !Pathname !ProgState !IO -> ProgIO;
CloseTheEditWindow close wdid wd path prog=:{editor=ed=:{Editor | project}} io
	= CloseTheWindow wdid EditWd prog` io`;
	where {
	prog`		= {prog & editor={Editor | ed & project=project`}};
	(pos,io`)	= GetWindowPos_and_Size wdid io;
	options		= {eo = EW_GetEditOptions wd, pos_size = pos};
	project`	= UpdateAfterClose close options path project;
	};

/*	Aux. function: updates the project after an edit window has been closed */
	
UpdateAfterClose :: !Bool !EditWdOptions !Pathname  !Project -> Project;
UpdateAfterClose close eo path project
	= PR_UpdateModule mn update project;
	where {
	isdefpath	= IsDefPathname path;
	mn			= GetModuleName path;
	
	update :: !ModInfo -> ModInfo;
	update modinfo=:{defopen,impopen}
		| isdefpath	= { modinfo &	defopen = if close False defopen,
									defeo	= eo };
					= { modinfo &	impopen	= if close False impopen,
									impeo	= eo };
	};
					
//
// SetThisWindow is called when one of the windows in the Windows menu is selected
//

SetThisWindow :: !EditWdId !PartTSel !ProgState !IO -> ProgIO;
SetThisWindow wdid tsel=:{l1,c1,l2,c2} prog=:{editor=ed=:{editwindows}} io
	| wdid == ProjectWdID	= OpenProjectWindowDialog prog io`;
	| l1==l2 && c1==c2		= (prog, io`);
							= DrawSelect True wdid wu prog` io`;
	where {
	io`			= ActivateWindow wdid io;
	prog`		= {prog & editor={ed & editwindows=SetWindow wdid wd` editwindows}};
	wd			= GetWindow wdid editwindows;
	(wd`,wu)	= SelectText wd tsel;
	};
	
/* determines the pos and size of the indicated window */

GetWindowPos_and_Size :: !EditWdId !IO -> (!WindowPos_and_Size,!IO);
GetWindowPos_and_Size wdid io
	| wdid == NoWdID	= (DefWindowPos_and_Size, io);
						= ({posx = posx, posy = posy, sizex = sizex, sizey = sizey}, io`); 
	where {
	(pos,io1)	= WindowGetPos wdid io;
	(frame,io`)	= WindowGetFrame wdid io1;
	(topl,botr)	= frame;
	(left,top)	= topl;
	(right,bot)	= botr;
	(posx,posy)	= pos;
	sizex		= right - left;
	sizey		= bot - top;
	};

/* Save the project if not already saved */

SaveProjectIfNecessary :: !ProgState !IO -> ProgIO;
SaveProjectIfNecessary prog=:{editor=ed=:{editwindows,project,startupinfo={startupdir}}} io
	| saved
		= (prog,io`);
	| ok
		= (prog1,io`);
		= AlertDialog ["The file "+++RemovePath prjpath+++" could not be saved because",
								"of a file I/O error." ] prog1 io`;
	where {
		prog1			= {prog & editor={Editor | ed & project=project`}};
		(ok, io`)	= ok_io`;
		ok_io` | saved= (True, io);
						= accFiles (SaveProjectFile prjpath project startupdir) io;
		projectwd		= GetWindow ProjectWdID editwindows;
		project`		= PR_SetSaved project;
		saved			= PR_Saved project;
		prjpath			= MakeProjectPathname projectwd.wstate.pathname;
	};

SetDefaultWindowOptions :: !WdType !Defaults !WindowPos_and_Size !WindowFont -> Defaults;
SetDefaultWindowOptions wdtype	defaults=:{edit,types,errors,clip,dproject} pos winFont
	= case wdtype of
		{	ErrorWd		-> {Defaults | defaults & errors=setPosAndFont pos winFont defaults.errors};
			TypeWd		-> {Defaults | defaults & types=setPosAndFont pos winFont defaults.types};
			EditWd		-> {Defaults | defaults & edit=setPosAndFont pos winFont defaults.edit};
			ProjectWd	-> {Defaults | defaults & dproject=setPosAndFont pos winFont defaults.dproject};
			ClpbrdWd	-> {Defaults | defaults & clip=setPosAndFont pos winFont defaults.clip};
		};
		where
		{
			setPosAndFont pos_size {WindowFont | fontname, fontsize} options
				=	{options & pos_size = pos_size, eo.EditOptions.fontname = fontname, eo.EditOptions.fontsize = fontsize};
		}
