implementation module EdFileMenu;

/*	The commands of the File menu */

import StdClass,StdString, StdInt, StdBool, StdTuple, StdArray;
import deltaEventIO, deltaWindow, deltaDialog, deltaFileSelect, deltaIOState;

import	EdProgramState, EdWindows, EdFiles, EdDialogs, EdProject, EdPath, EdTextWindow,
		EdDrawWindow, EdMenuItems, EdSupport, EdDialogs, EdLists, EdParse, EdText,
		EdProjectUtils;

import deltaPrintText;

from EdCleanSystem import QuitCleanCompiler;

OKID		:== 1;
CancelID	:== 2;

//	The device function for the New command
New	:: !ProgState !IO -> ProgIO;
New prog io
	# (save,newname,prog,io)
		=  EdSelectOutputFile "Save As:" "Untitled" prog io;
	| not save
		= (prog, io);
	#! newlines
		=	prog.editor.defaults.edit.eo.EditOptions.newlines; // phew!
	# (saved,io)
		=	accFiles (SaveFile newname newlines EmptyText) io;
	| not saved
		= AlertDialog	[ "The file could not be saved because","of a file I/O error." ] prog io;
	// otherwise
		= OpenEdWindow newname EmptyTSel 1 (NoMixedNewlines, newlines) EmptyText prog io;

//	The device function for the Open command

Open :: !ProgState !IO -> ProgIO;
Open ps io0
	| not open
		= (ps2, io1);
		= OpenFile pathname ps2 io1;
	{}{
		(open,pathname,ps2,io1) = EdSelectInputFile ps io0;
	};

OpenFile :: !String !ProgState !IO -> ProgIO;
OpenFile pathname prog=:{editor=ed=:{editwindows}} io
	| is_help
		= HelpFileAlert prog io;
	| exists
		= SetThisWindow id EmptyTSel prog io;
	| not good
		= (AlertDialog ["The file \"" +++  name +++ "\"" , "could not be opened."] prog io`);
		= OpenEdWindow pathname EmptyTSel nrlines newlines text prog io`;
	{}{
		((good,text,nrlines,newlines),io`)= accFiles (ReadFile pathname) io;
		(exists,id)					= IsExistingPathname pathname editwindows;
		name						= RemovePath pathname;
		is_help						= name == HelpFile;
	};



/*	Auxiliary function for the New and Open commands */

OpenEdWindow :: !Pathname !PartTSel !NrLines (MixedNewlines, !NewlineConvention) !Text !ProgState !IO -> ProgIO;
OpenEdWindow pathname tsel nrlines (mixed, newlines) text prog io
 	# (prog, io)
 		=	CheckMixedNewlines pathname mixed prog io
	# ((prog, io),pos,eo,co)
		=	GetEdit_and_CompilerOptions pathname prog io;
 	= OpenEditWindow pathname pos {EditOptions | eo & newlines=newlines} co tsel nrlines text prog io;

// The device function for the Open Definition/OpenImplementation command

OpenDefOrImp :: !Bool !Bool !ProgState !IO -> ProgIO;
OpenDefOrImp def moduleNameSelected pstate=:{editor=editor=:{editwindows}} io
	| moduleNameSelected
		=	OpenModule pathname EmptyTSel {pstate & editor=editor`} io;
	| otherwise
		=	OpenModuleDialog (if def ".dcl" ".icl") {pstate & editor=editor`} io;
	where {
	editor`				= {editor & editwindows = editwindows`};
	pathname | def		= MakeDefPathname clipString;
						= MakeImpPathname clipString;
	(front`,clip)		= CopyText front selection;
	clipString			= Singleton clip;
	editwindows`		= SetFrontWindow front` editwindows;
	(_,front)			= GetFrontWindow editwindows;
	selection			= front.wtext.WinText.selection.tsel;
	};

POTitleID	:== 10;
	PSCTitleID	:== 10;
	DOpenTitleID		:== 110;
	DOpenEditID			:== 111;
	DOpenFileCancelCID	:== 110;
	DOpenFileOKCID		:== 111;

OpenModuleDialog :: String ProgState IO -> ProgIO;
OpenModuleDialog string pstate io
	=  OpenModalDialog dialog pstate io;
	where
	{
		dialog
			= CommandDialog POTitleID "Open Module" [] DOpenFileOKCID [title, edit, cancel, ok];
		title
			= StaticText DOpenTitleID Left "Open:";
		edit
			= EditText DOpenEditID (RightTo DOpenTitleID) (MM 80.0) 1 string;
		cancel
			= DialogButton DOpenFileCancelCID	Left					"Cancel"	Able	Cancel;
		ok
			= DialogButton DOpenFileOKCID (RightTo DOpenFileCancelCID)	"OK"		Able	OKOpenModule;
	}

OKOpenModule :: !DialogInfo !ProgState !IO -> ProgIO;
OKOpenModule dialog pstate io
	=	OpenModule pathname EmptyTSel pstate (ActivateCurrentWindow (CloseActiveDialog io));
	where
	{
		pathname
			=	GetEditText DOpenEditID dialog;
	}

// The device function for the OpenOther command

OpenOther :: !ProgState !IO -> ProgIO;
OpenOther prog=:{editor={editwindows}} io
	= OpenModule path` EmptyTSel prog io;
	where {
	path				= RemovePath front.wstate.pathname;
	path` | def			= MakeImpPathname path;
						= MakeDefPathname path;
	def					= IsDefPathname path;
	(_,front)			= GetFrontWindow editwindows;
	};

/*	Auxiliary function for the Open Definition and Open Implementation commands */										
	
OpenModule :: !Pathname !PartTSel !ProgState !IO -> ProgIO;
OpenModule pathname tsel prog io
	= read_and_open fullpathname prog` io`;
	where {
	((prog`,fullpathname),io`)	= accFiles (GetFullPathname pathname prog) io;
	
	read_and_open :: !Pathname !ProgState !IO -> ProgIO;
	read_and_open fullpathname prog=:{editor=ed=:{editwindows}} io
		| notondisk	= (prog,ioa);
		| exists	= SetThisWindow id tsel prog io
		| good		= OpenEdWindow fullpathname tsel nrlines newlines text prog io`;
					= AlertDialog alertstrings prog io`;
		where {
		notondisk					= fullpathname == EmptyPathname;
		(exists,id)					= IsExistingPathname fullpathname editwindows;
		((good,text,nrlines,newlines),io`)= accFiles (ReadFile fullpathname) io;
		ioa							= Alert io;
		alertstrings				= [	"The file could not be opened because",
	            			           				"of a file I/O error."]
		};
	};

//	The device function for the Close command

Close :: !ProgState !IO -> ProgIO;
Close prog=:{editor={editwindows}} io
	| dialog_present	= (prog, CloseDialog DFindIdentID io`);
						= CloseWindow "closing" wdid prog io`;
	where {
	(dialog_present,_,io`)	= GetDialogInfo DFindIdentID io;
	(wdid,_)				= GetFrontWindow editwindows;
	};

//	The device function for the Save command
	
Save :: !ProgState !IO -> ProgIO;
Save prog=:{editor=ed=:{editwindows}} io
	| new_window	= SaveAs prog io;
	| good			= Saved_UpdateMenuItems path prog` io`;
					= AlertDialog [ "The file could not be saved because",
	                                "of a file I/O error."] prog` io`;
	where {
	(_,front)		= GetFrontWindow editwindows;
	(front`,_,text)	= GetText {front & wstate = {front.wstate & saved = True}};
	path			= front`.wstate.pathname;
	prog`			= {prog & editor={ed & editwindows=SetFrontWindow front` editwindows}};
	(good,io`)		= accFiles (SaveFile path front`.wformat.WinFormat.newlines text) io;
	new_window		= RemovePath (RemoveSuffix path) == "Untitled";
	};

//
//	The device function for the Save As command
//

SaveAs :: !ProgState !IO -> ProgIO;
SaveAs prog=:{editor = {editwindows}} io
	# (save,path,prog=:{editor=ed=:{project}},io)
	                        =  EdSelectOutputFile "Save As:" (RemovePath oldpath) prog io;
	  (front1,_,text)			=  GetText front;
	  front`					=  {front1 & wstate={front1.wstate & saved=True,pathname=path}};
	  project`				=  UpdateAfterSavedAs oldpath path project;
	  editwindows`			=  SetFrontWindow front` editwindows;
	  editwindows1			=  SetFrontWindow front1 editwindows;
	| not save
		=	(prog, io);
	| RemovePath path == HelpFile
		=	HelpFileAlert prog io;
	| fst (IsExistingPathname path editwindows) && (oldpath <> path)
		=	AlertDialog ["The file could not be saved because",
                               "there is still another window open on it."] prog io;
	# (good, io)
		=  accFiles (SaveFile path front.wformat.WinFormat.newlines text) io;
	| not good
		= AlertDialog ["The file could not be saved because", "of a file I/O error."]
									{prog & editor={ed & editwindows=editwindows1}} io;
	// otherwise
			= Saved_UpdateMenuItems oldpath {prog & editor={ed & editwindows=editwindows`,project=project`}} io;
	where {
	  (_,front)			 	=  GetFrontWindow editwindows;
	  oldpath					=  front.wstate.pathname;
	};


//	The device function for the Print command

Print :: !ProgState !IO -> ProgIO;
Print prog=:{editor=ed=:{editwindows}} io
	| user_confirmed
  		= (prog2,io3);
		= (prog1,io1)
	where {
	(_,front)	= GetFrontWindow editwindows;
	(user_confirmed,prog1,io1) = evtlConfirm front.wstate.wdtype prog io;
	({ tabw,winfont }) = front.wformat;
	({ WindowFont | fontname,fontsize}) = winfont;
    (_,_,text)	= GetText front;
	(defaultPS, io2) = defaultPrintSetup io1;
	(_,{PState| s=prog2,io=io3})
                = printText2 front.wstate.pathname "page " True RightJustify 
                 	(fontname,[],fontsize) tabw.ttabw (toCharStream text) defaultPS { s=prog1,io=io2 };
	};

// If the Error, Type or Clipboard window is active, the user is asked for confirmation before printing
evtlConfirm :: !WdType !ProgState !IO -> (!Bool,!ProgState,!IO);
evtlConfirm EditWd s io
	= (True,s,io);
evtlConfirm wdtype s io
	# wdtype_string 
		= case wdtype of
		  {
			ErrorWd 	-> "Error";
			TypeWd		-> "Type";
			ClpbrdWd	-> "Clipboard";
			_			->	"";
		  };
	  noticeDef = Notice ["Do you really want to print","the "+++wdtype_string+++" window ?"]
						 (NoticeButton 1 "no") [(NoticeButton 2 "yes")];
	  (button_id,s,io) = OpenNotice noticeDef s io;
	= (button_id==2,s,io);



// The printText functions in deltaPrintText expect an object of class CharStreams
// IDECharStream is an instance of this class. The next character to read is always
// part of the first string in "tailText", indexed by "chrCount". The actual
// position can be saved in "savedPos" 

:: *IDECharStream = { tailText::!Block, chrCount::!Int, savedPos::!(!Block,!Int)  };



// for printing, the whole text of a window will be converted into one block
toCharStream :: Text -> IDECharStream;
toCharStream text
	= { tailText=FlattenList text, chrCount=0, savedPos=(Nil,0) };

instance CharStreams IDECharStream
where
  {
	getChar	:: !*IDECharStream -> (!Bool,!Char,!*IDECharStream);
	getChar cs=:{tailText=Nil}		// there are no lines
		 = (False,'\f',cs);
	getChar cs=:{tailText=Nil:!restOfText}	// the first line is empty
		 = getChar { cs & tailText=restOfText };
	getChar cs=:{tailText=(str:!restOfLine):!restOfText, chrCount} // the first string is empty
		| size str==chrCount
			= getChar { cs & tailText=restOfLine :! restOfText, chrCount=0 };
		= (True, str.[chrCount],{ cs & chrCount=inc chrCount });
	savePos :: !*IDECharStream -> *IDECharStream;
	savePos	cs=:{tailText,chrCount}
		= { cs & savedPos=(tailText,chrCount) };
	restorePos	:: !*IDECharStream -> *IDECharStream;
	restorePos	cs=:{savedPos=(savedTailText,savedChrCount)}
		= { cs & tailText=savedTailText, chrCount=savedChrCount };
	eos	:: !*IDECharStream -> (!Bool,!*IDECharStream);
	eos cs=:{tailText=Nil}		// this is the only case, where getChar False
		= (True,cs);
	eos cs
		= (False,cs);
  };

//
//	The device function for the Revert command
//

Revert :: !ProgState !IO -> ProgIO;
Revert prog=:{editor=ed=:{editwindows}} io
	# (butid,prog,io)
		=	OpenNotice notice prog io;
	| butid == CancelID
		=	(prog,io);
	# ((good,text,nrlines,(_,newlines)), io)
		=	accFiles (ReadFile path) io;
	| not good
		=	AlertDialog [ "The file \"" +++  name +++ "\"",
						  			     "could not be opened." ] prog io;
	# 	  (prog,io)
		=	Saved_UpdateMenuItems path {prog & editor=editor`} io;
		with {
			editor`
				=	{ed & editwindows = SetFrontWindow front` editwindows};
			front`
				=	{front & wstate={front.wstate & saved=True}, wformat.WinFormat.newlines=newlines};
		}
	=	DrawUpdateWindow frontid (SetText nrlines text) prog io;
where {
	notice            =  Notice [line1,line2] (NoticeButton OKID "Revert")
											[NoticeButton CancelID "Cancel"];
	line1             =  "Revert to the last saved version of the document?";
	line2             =  "Changes made since the last save will be lost.";
	(frontid,front)   =  GetFrontWindow editwindows;
	path              =  front.wstate.pathname;
	name			  =  RemovePath path;
	};

//
//	The device function for the Quit command
//

Quit :: !ProgState !IO -> ProgIO;
Quit prog io
	| present	= (prog`,io`);
				= DoQuit (-1) prog` (CloseWindows [HelpWdID] io`);
	where {
	present						= PR_ProjectSet prog`.editor.Editor.project;
	(prog`,io`)	= CloseWindow "quitting" ProjectWdID prog io;
	};

/* Close the current project, if any, first so it isn't affected by closing other windows */

DoQuit :: !EditWdId !ProgState !IO -> ProgIO;
DoQuit id prog=:{editor} io
	| not windows	= ({prog & editor=editora}, QuitIO (QuitCleanCompiler ioa));
	| id == wdid	= (prog, io1);
					= DoQuit wdid progb iob;
	where {
	(windows,wdid,io1)	= GetActiveWindow io;
	(editora,ioa)	= accFiles (SaveDefaultSettings editor) io1;
	(progb,iob)			= CloseWindow "quitting" wdid prog io1;
	};


/*	Functions for updating the project after a window has been opened, closed, saved(as), or
	reverted.
*/
GetEdit_and_CompilerOptions :: !Pathname !ProgState !IO -> (!ProgIO, !WindowPos_and_Size, !EditOptions, !CompilerOptions);
GetEdit_and_CompilerOptions	pathname
							prog=:{editor=ed=:{defaults={edit={pos_size,eo},defaultCompilerOptions},project}} io
	| not deforimp	=  ((prog, io),pos_size,eo,defaultCompilerOptions);
					=  ((prog`, io`),pos,peo,projectCompilerOptions);
	where {
	(abcpath, io1)			= accFiles (MakeABCSystemPathname pathname) io;
	((version,abcOptions), io`)
		=	accFiles (getABCVersionAndOptions abcpath) io1;
		where
		{
			getABCVersionAndOptions path files
				=	((version,abcOptions), files1)
				where {
					(files1, _, _, version,abcOptions)
						=	GetABCCompiledInfo path files;
				};
		};
	prog2	= prog;
	prog`						= setproject prog2;
	project`					= PR_UpdateModule mn update project;
	(exists,modinfo)			= PR_GetModuleInfo mn project;
	pos	| isdefpath && exists	= modinfo.defeo.pos_size;
		| isimppath && exists	= modinfo.impeo.pos_size;
								= pos_size;
	peo | isdefpath && exists	= modinfo.defeo.eo;
		| isimppath	&& exists	= modinfo.impeo.eo;
								= eo;
	projectCompilerOptions
		| isimppath
			| exists
				=	modinfo.compilerOptions;
			| abc
				=	UpdateCompilerOptionsWithABCOptions defaultCompilerOptions abcOptions;
			// otherwise
				=	defaultCompilerOptions;
		// otherwise
			=	defaultCompilerOptions;
	deforimp					= isdefpath || isimppath;
	isdefpath					= IsDefPathname pathname;
	isimppath					= IsImpPathname pathname;
	mn							= GetModuleName pathname;
	abc							= version<>(-1);
	
	setproject	:: !ProgState -> ProgState;
	setproject prog=:{editor}
		= {prog & editor={Editor | editor & project=project`}};
		
	update :: !ModInfo -> ModInfo;
	update minfo=:{defopen,impopen}
		= {minfo & defopen=defopen || isdefpath, impopen=impopen || isimppath, compilerOptions=projectCompilerOptions};
	};

UpdateAfterSavedAs :: !String !String !Project -> Project;
UpdateAfterSavedAs oldpath newpath project
	| oldisnew	= project;
				= PR_UpdateModule newmn (update newdef True)
						(PR_UpdateModule oldmn (update olddef False) project);
	where {
	oldisnew	= oldpath == newpath;
	olddef		= IsDefPathname oldpath;
	oldmn		= GetModuleName oldpath;
	newdef		= IsDefPathname newpath;
	newmn		= GetModuleName newpath;
	
	update :: !Bool !Bool !ModInfo -> ModInfo;
	update def open modinfo=:{defopen,impopen}
		| def	= {modinfo & defopen=open};
				= {modinfo & impopen=open};
	};
