#include "Project.h"

#include "Settings.h"
#include "Dialogs.h"
#include "IdeFilePool.h"
#include "FileTools.h"
#include "Builder.h"
#include "LibraryPool.h"
#include "ProjectsCtrl.h"

#include "FishIDEEditor.h"
#include "Displays.h"

// constructor
Project::Project(String const &p)
{
	// builds the gui part
	_filesLibsSplitter.Vert(_libsList, _filesList);
	_hSplitter.Horz(_filesLibsSplitter, _editPane).SetPos(1500);
	_editPane.AddFrame(_filesTab);
	_vSplitter.Vert(_hSplitter, _bottomPane).SetPos(8000);
	Add(_vSplitter.SizePos());
	_bottomPane.Crosses(false);
	_bottomPane.AddCtrl(_outputPane, t_("Output"));
	_bottomPane.AddCtrl(_errorPane, t_("Errors"));
	_bottomPane.AddCtrl(_serialPane, t_("Serial port"));
	_bottomPane.AddCtrl(_findInFilesPane, t_("Find in files"));
	_bottomPane.SetCursor(0);

	// setup errors arrayctrl layout
	_errorPane.AddColumn(t_("File")).NoEdit();
	_errorPane.AddColumn(t_("Line")).NoEdit();
	_errorPane.AddColumn(t_("Message")).NoEdit();
	_errorPane.AddColumn("").NoEdit();
	_errorPane.ColumnWidths("10 2 22 0");
	_errorPane.ColumnAt(0).SetDisplay(Single<FoundFileDisplay>());

	_errorPane.WhenLeftClick = THISBACK(errorPaneCb);
	_errorPane.NoWantFocus();

	// setup find in files arrayctrl layout
	_findInFilesPane.AddColumn(t_("File")).NoEdit();
	_findInFilesPane.AddColumn(t_("Line")).NoEdit();
	_findInFilesPane.AddColumn(t_("Source line")).NoEdit();
	_findInFilesPane.AddColumn("").NoEdit();
	_findInFilesPane.ColumnWidths("10 2 22 0");

	_findInFilesPane.ColumnAt(0).SetDisplay(Single<FoundFileDisplay>());
	_findInFilesPane.ColumnAt(2).SetDisplay(Single<FoundDisplay>());

	_findInFilesPane.WhenLeftClick = THISBACK(findInFilesPaneCb);
	_findInFilesPane.NoWantFocus();

	_outputPane.SetReadOnly();

	_libsList.NoWantFocus();
	_filesList.NoWantFocus();
	
	_vSplitter.WhenSplitFinish			= THISBACK(splitterCb);
	_hSplitter.WhenSplitFinish			= THISBACK(splitterCb);
	_filesLibsSplitter.WhenSplitFinish	= THISBACK(splitterCb);
	
	_folder = GetFileFolder(p);
	_name = GetFileName(p);
	
	// connect library click handler
	_libsList.WhenLeftClick = THISBACK(libSelectCb);
	_libsList.WhenBar = THISBACK(libsContextCb);

	// connect file click handler
	_filesList.WhenLeftClick = THISBACK(fileSelectCb);
	_filesList.WhenBar = THISBACK(filesContextCb);

	// connect tab change handler
	_filesTab.WhenAction = THISBACK(SetLists);
	
	// allow close all tabs
	_filesTab.MinTabCount(0);
//	_filesTab.CancelClose = THISBACK(closeTabCb);
	_filesTab.WhenClose = THISBACK(closeTabCb);
	_filesTab.WhenCloseSome = THISBACK(closeTabsCb);
	
	_serialPane.WhenChange << THISBACK(SettingsChanged);
	
	// fetch theme
	ThemeChanged();
}
	
// destructor -- save project on exit
Project::~Project(void)
{
	// we shall not relay changes on project's destruction
	// gives memory access error if called during destructions
	WhenChange.Clear();
	
	Save();
}

// return project's full path
String Project::GetFullPath(void) const
{
	return AppendFileName(_folder, ForceExt(_name, ".fish"));
}

// return project's .INO path
String Project::GetINOPath(void) const
{
	return AppendFileName(_folder, ForceExt(_name, ".ino"));
}

// find a library by an include
Ptr<LibraryInfo> Project::FindLibrary(String const &include) const
{
	String architecture = Settings.GetArchitecture();
	return LibraryPool.FindByInclude(include, architecture);
}

// find used libraries
Vector<Ptr<LibraryInfo>> Project::FindLibraries(void) const
{
	Vector<Ptr<LibraryInfo>> res;

	Vector<String> includes = SourceTools::ScanIncludes(GetINOPath());
	if(includes.IsEmpty())
		return res;
	
	for(int i = 0; i < includes.GetCount(); i++)
	{
		Ptr<LibraryInfo> lib = FindLibrary(includes[i]);
		if(lib)
			res.Add(lib);
	}
	return res;
}

// add a library to project
bool Project::AddLibraryToProject(String const &libName)
{
	// get .ino path
	String inoFile = GetINOPath();
	
	// show its tab
	ShowTab(inoFile);
	
	// get library data
	LibraryInfo *libInfo = LibraryPool.FindByName(libName, Settings.GetArchitecture());
	if(!libInfo)
		return false;
	
	// get library includes
	Vector<String> const &libIncludes = libInfo->GetIncludes();

	// get editor object
	FileInfo *info = GetFileInfo(inoFile);

	// insert include files
	String s = info->Get();
	if(!SourceTools::InsertIncludes(s, libIncludes))
		return false;
	FishIDEEditor.Set(s);
	
	int idx = _libraries.Find(libName);
	if(idx >= 0)
		_libraries[idx] = UsedLib;
	FillLists();
	
	return true;
}

// remove a library to project
bool Project::RemoveLibraryFromProject(String const &libName)
{
	// get .ino path
	String inoFile = GetINOPath();
	
	// show its tab
	ShowTab(inoFile);
	
	// get library data
	LibraryInfo *libInfo = LibraryPool.FindByName(libName, Settings.GetArchitecture());
	if(!libInfo)
		return false;
	
	// get library includes
	Vector<String> const &libIncludes = libInfo->GetIncludes();

	// get editor object
	FileInfo *fileInfo = GetFileInfo(inoFile);
	
	String s = fileInfo->Get();
	if(!SourceTools::RemoveIncludes(s, libIncludes))
		return false;
	FishIDEEditor.Set(s);

	int idx = _libraries.Find(libName);
	if(idx >= 0)
		_libraries[idx] = NotUsedLib;
	FillLists();
	
	return true;
}

// remove control when closing tab
void Project::closeTabCb(Value tab)
{
	// find corresponding opened file
	int idx = -1;
	for(int i = 0; i < _openedFiles.GetCount(); i++)
		if(_openedFiles[i].GetPath() == tab)
		{
			idx = i;
			break;
		}
		
	if(idx >= 0)
	{
		// flush the editor, just in case
		FishIDEEditor.Flush();
		
		// remove selected file
		_openedFiles.Remove(idx);
	}
	
	// if no tabs are opened, just select a null editor
	// (tab is still not closed here, so we just check for 1 instead of 0)
	if(_filesTab.GetCount() <= 1)
		FishIDEEditor.Assign(NULL);
	
	return;
}

// remove controls when closing more tabs at once
void Project::closeTabsCb(ValueArray tabs)
{
	// get all files to be removed
	Vector<int>filesToClose;
	for(int iTab = 0; iTab < tabs.GetCount(); iTab++)
	{
		String path = tabs[iTab];
		for(int iFile = 0; iFile < _openedFiles.GetCount(); iFile++)
			if(_openedFiles[iFile].GetPath() == path)
				filesToClose << iFile;
	}
	if(!filesToClose.GetCount())
		return;

	// flush the editor, just in case
	FishIDEEditor.Flush();
	
	// sort tabs, to be able to remove from bottom
	Sort(filesToClose, [](int a, int b) { return a > b; });
	
	// remove closed files from opened ones
	for(int iFile = 0; iFile < filesToClose.GetCount(); iFile++)
		_openedFiles.Remove(filesToClose[iFile]);
	
	// if no tabs are opened, just select a null editor
	// (tab is still not closed here, so we just check for 1 instead of 0)
	if(_filesTab.GetCount() <= 1)
		FishIDEEditor.Assign(NULL);
}

// close a file from opened tab
bool Project::closeFile(String const &path)
{
	_filesTab.CloseKey(path);
	closeTabCb(path);
	FillLists();
	SetLists();
	return true;
}

// delete a file which may be opened in a tab
// (closes the tab first, and release all the editors)
bool Project::deleteFile(String const &path)
{
	// ask projects control to close ALL instances
	// in ALL projects
	WhenDeletingFile(path);
	
	FileDelete(path);
	return true;
}

// create a file, with default content depending on path
bool Project::createFile(String const &path, bool stub, String const &libName)
{
	String ext = GetFileExt(path);
	String s;
	if(stub)
	{
		String fileName = GetFileName(path);
		String fileTitle = GetFileTitle(path);
		if(ext == ".h" || ext == ".hpp" || ext == ".hxx")
		{
			// include file
			if(!libName.IsEmpty())
			{
				s <<
					"// Library : '" << libName << "' -- File : '" << fileName << "'\n"
					"// Created by FishIDE application \n"
					"#ifndef __" << ToUpper(libName) << "_" << ToUpper(fileTitle) << "_H\n"
					"#define __" << ToUpper(libName) << "_" << ToUpper(fileTitle) << "_H\n"
				;
			}
			else
			{
				s <<
					"// File : '" << fileName << "'\n"
					"// Created by FishIDE application \n"
					"#ifndef __" << ToUpper(fileTitle) << "_H\n"
					"#define __" << ToUpper(fileTitle) << "_H\n"
				;
			}
			s <<
				"\n"
				"#include <Arduino.h>\n"
				"\n"
				"class " << fileTitle << "\n"
				"{\n"
				"	private:\n"
				"\n"
				"	protected:\n"
				"\n"
				"	public:\n"
				"\n"
				"		// constructor\n"
				"		" << fileTitle << "();\n"
				"\n"
				"		// destructor\n"
				"		~" << fileTitle << "();\n"
				"\n"
				"};\n"
				"\n"
				"#endif\n"
			;
		}
		else if(ext == ".c" || ext == ".cpp" || ext == ".cxx")
		{
			// source file
			if(!libName.IsEmpty())
			{
				s <<
					"// Library : '" << libName << "' -- File : '" << fileName << "'\n"
					"// Created by FishIDE application \n"
				;
			}
			else
			{
				s <<
					"// File : '" << fileName << "'\n"
					"// Created by FishIDE application \n"
				;
			}
			s <<
				"#include \"" << fileTitle << ".h\"\n"
				"\n"
				"// constructor\n" <<
				fileTitle << "::" << fileTitle << "()\n"
				"{\n"
				"}\n"
				"\n"
				"// destructor\n" <<
				fileTitle << "::~" << fileTitle << "()\n"
				"{\n"
				"}\n"
			;
		}
		else if(ext == ".ino")
		{
			if(!libName.IsEmpty())
				s << "// Example for library '" << libName << "'\n";
			s <<
				"// Project : '" << fileTitle << "'\n"
				"// Created by FishIDE application \n"
			;
			if(!libName.IsEmpty())
				s << "#include \"" << libName << ".h\"\n";
			s <<
				"\n"
				"void setup()\n"
				"{\n"
				"	// put your initialization code here\n"
				"}\n"
				"\n"
				"void loop()\n"
				"{\n"
				"	// put your loop code here\n"
				"}\n"
			;
		}
		else
			s = "";
	}
	else
		s = "";
	return SaveFile(path, s);
}

// library add callback
void Project::libCreateCb(bool addToProject)
{
	CreateLibraryDialog dlg;
	if(dlg.Run() == IDOK)
	{
		String libName = ~dlg.nameEdit;
		String libFolder = AppendFileName(Settings.GetUserLibsRoot(), libName);
		if(!RealizeDirectory(libFolder))
		{
			Exclamation(t_("Can't create library folder"));
			return;
		}
		String author		= ~dlg.authorEdit;
		String maintainer	= ~dlg.maintainerEdit;
		String sentence		= ~dlg.sentenceEdit;
		String paragraph	= ~dlg.paragraphEdit;
		String category		= dlg.categoryDrop.Get();
		String url			= ~dlg.urlEdit;
		Vector<String>archs = Split(String(~dlg.architecturesEdit), ",");
		for(int i = 0; i < archs.GetCount(); i++)
			archs[i] = TrimBoth(archs[i]);
		String architectures = Join(archs, ",");
		AVersion version(~dlg.versionMajorEdit, ~dlg.versionMinorEdit, ~dlg.versionReleaseEdit);

		String srcFolder = AppendFileName(libFolder, "src");
		RealizeDirectory(srcFolder);
		String stubName = AppendFileName(srcFolder, libName);
		String hName = ForceExt(stubName, ".h");
		String cName = ForceExt(stubName, ".cpp");
		
		createFile(hName, Settings.GetIdeCreateLibraryStubs(), libName);
		createFile(cName, Settings.GetIdeCreateLibraryStubs(), libName);

		String propsName = AppendFileName(libFolder, "library.properties");
		SaveFile(propsName,
			"name="				<< libName		<< "\n"
			"version="			<< version		<< "\n"
			"author="			<< author		<< "\n"
			"maintainer="		<< maintainer	<< "\n"
			"sentence="			<< sentence		<< "\n"
			"paragraph="		<< paragraph	<< "\n"
			"category="			<< category		<< "\n"
			"url="				<< url			<< "\n"
			"architectures="	<< architectures<< "\n"
		);

		if(dlg.exampleOption)
		{
			String exaFolder = AppendFileName(AppendFileName(libFolder, "examples"), libName + "Test");
			RealizeDirectory(exaFolder);
			String exaName = AppendFileName(exaFolder, GetFileTitle(exaFolder));
			String inoName = ForceExt(exaName, ".ino");
			createFile(inoName, Settings.GetIdeCreateProjectStubs(), libName);
		}
		
		// re-parse libraries
		LibraryPool.Scan();
		
		// open the library
		if(_libraries.Find(libName) < 0)
			_libraries.Add(libName, NotUsedLib);
		
		// execute settings change callback
		// to signal that library pool has been changed
		SettingsChanged();
		
		// if requested, add library to sketch
		if(addToProject)
			AddLibraryToProject(libName);

		FillLists();
		int idx = _libsList.Find(libName);
		if(idx >= 0)
		{
			_libsList.SetCursor(idx);
			libSelectCb();
		}
		else
			SetLists();
	}
}

void Project::libOpenCb(bool addToProject)
{
	AddLibraryDialog dlg;
	if(dlg.Run() == IDOK)
	{
		String libName = dlg.GetLibrary();
		
		// check if library is already there.. in case do nothing
		if(_libraries.Find(libName) < 0)
			_libraries.Add(libName, NotUsedLib);
		
		// if requested, add library to sketch
		if(addToProject)
			AddLibraryToProject(libName);

		FillLists();
		int idx = _libsList.Find(libName);
		if(idx >= 0)
		{
			_libsList.SetCursor(idx);
			libSelectCb();
		}
		else
			SetLists();
	}
}

void Project::libAddCb(int libIdx)
{
	String libName = _libraries.GetKey(libIdx);
	AddLibraryToProject(libName);
}

void Project::libRemoveCb(int libIdx)
{
	String libName = _libraries.GetKey(libIdx);
	RemoveLibraryFromProject(libName);
}


void Project::libCloseCb(int libIdx)
{
	// before closing we shall remove from project
	String libName = _libraries.GetKey(libIdx);
	RemoveLibraryFromProject(libName);
	
	// remove it from opened list
	_libraries.Remove(libIdx);
	
	FillLists();
	SetLists();
}

void Project::libDuplicateCb(int libIdx)
{
	Exclamation("NOT IMPLEMENTED");
}


// add/remove files from library callbacks
void Project::libRemoveFileCb(int iLib, int iFile)
{
	String libName = _libraries.GetKey(iLib);
	String fileName = _filesList.Get(iFile);
	
	// get the library info
	LibraryInfo *info = LibraryPool.FindByName(libName, Settings.GetArchitecture());
	if(!info)
		return;
	
	// check if file belongs to library
	if(FindIndex(info->GetFiles(), fileName) < 0)
	{
		RLOG("OPS");
		return;
	}

	// ask if we really wanna delete the file
	if(!PromptYesNo(Format(t_("Delete file '%s' from library '%s' ?"), DeQtf(fileName), DeQtf(libName))))
		return;

	// create the full file path
	String filePath = AppendFileName(info->GetPath(), fileName);
	
	// delete the file, closing the tab if releasing it from all
	// opened editors, if needed
	deleteFile(filePath);
	
	// rescan the libraries
	LibraryPool.Scan();
	
	// signal changes
	SettingsChanged();
	
}

void Project::libAddFileCb(int iLib)
{
	String libName = _libraries.GetKey(iLib);
	LibraryInfo *lib = LibraryPool.FindByName(libName, Settings.GetArchitecture());
	if(!lib)
		return;
	String basePath = lib->GetPath();

	AddFilesDialog dlg(basePath, libName);
	if(dlg.Run())
	{
		// get paths
		Vector<String> const &paths = dlg.GetPaths();

		// create the files
		for(int i = 0; i < paths.GetCount(); i++)
		{
			String fullPath = AppendFileName(basePath, paths[i]);
			createFile(fullPath, Settings.GetIdeCreateLibraryStubs(), libName);
		}
		
		// rescan the libraries
		LibraryPool.Scan();
		
		// signal changes
		SettingsChanged();
		
		if(dlg.GetPaths().GetCount())
			ShowTab(AppendFileName(basePath, dlg.GetPaths()[0]));
	}
}
	
// add/remove files from project callbacks
void Project::projectRemoveFileCb(int iFile)
{
	String path = _files[iFile];
	
	// ask if we really wanna delete the file
	if(!PromptYesNo(Format(t_("Delete file '%s' from project '%s' ?"), DeQtf(path), DeQtf(GetTitle()))))
		return;

	// create the full file path
	String fullPath = AppendFileName(GetFolder(), path);
	
	// delete the file, closing the tab if releasing it from all
	// opened editors, if needed
	_files.Remove(iFile);
	deleteFile(fullPath);
	
	// signal changes
	SettingsChanged();
}

void Project::projectAddFileCb(void)
{
	String basePath = GetFolder();

	AddFilesDialog dlg(basePath, GetTitle());
	if(dlg.Run())
	{
		// get paths
		Vector<String> const &paths = dlg.GetPaths();

		// create the files
		for(int i = 0; i < paths.GetCount(); i++)
		{
			String path = paths[i];
			String fullPath = AppendFileName(basePath, path);
			createFile(fullPath, Settings.GetIdeCreateProjectStubs());
			if(FindIndex(_files, path) < 0)
				_files.Add(path);
		}
		
		// signal changes
		SettingsChanged();
		
		if(dlg.GetPaths().GetCount())
			ShowTab(AppendFileName(basePath, dlg.GetPaths()[0]));
	}
}
		
// add/remove/delete file from others file list
void Project::othersDeleteFileCb(int iFile)
{
	String path = _otherFiles[iFile];
	
	// ask if we really wanna delete the file
	if(!PromptYesNo(Format(t_("Delete file '%s' ?"), DeQtf(path))))
		return;

	// delete the file, closing the tab if releasing it from all
	// opened editors, if needed
	_otherFiles.Remove(iFile);
	deleteFile(path);
	
	// signal changes
	SettingsChanged();
}

void Project::othersRemoveFileCb(int iFile)
{
	String path = _otherFiles[iFile];
	_otherFiles.Remove(iFile);
	closeFile(path);

	SettingsChanged();
}

// compile a file
void Project::compileFileCb(int libIdx, int fileIdx)
{
	String path = "";
	if(libIdx == 0)
		// project files
		path = AppendFileName(_folder, _files[fileIdx]);
	else if(libIdx >= 1 && libIdx <= _libraries.GetCount())
	{
		Ptr<LibraryInfo> lib = LibraryPool.FindByName(_libraries.GetKey(libIdx - 1), Settings.GetArchitecture());
		path = lib->GetPath();
		Vector<String> const &files = lib->GetFiles();
		if(fileIdx < files.GetCount())
			path = AppendFileName(path, files[fileIdx]);
	}
	if(!path.IsEmpty())
		Builder.CompileFile(this, path);
}

void Project::othersAddFileCb(void)
{
	AddFilesDialog dlg;
	dlg.NoCheckExists();
	if(dlg.Run())
	{
		// get paths
		Vector<String> const &paths = dlg.GetPaths();

		// create the files if they don't exist
		for(int i = 0; i < paths.GetCount(); i++)
		{
			String path = paths[i];
			if(!FileExists(path))
				createFile(path, Settings.GetIdeCreateProjectStubs());
			if(FindIndex(_otherFiles, path) < 0)
				_otherFiles.Add(path);
		}
		
		// signal changes
		SettingsChanged();
		
		if(dlg.GetPaths().GetCount())
			ShowTab(dlg.GetPaths()[0]);
	}
}
		
// lib and files right click menus callbacks
void Project::libsContextCb(Bar &bar)
{
	bar.Add(t_("Create new library"), THISBACK1(libCreateCb, false));
	bar.Add(t_("Create new library and add to sketch"), THISBACK1(libCreateCb, true));
	bar.Separator();
	bar.Add(t_("Open existing library"), THISBACK1(libOpenCb, false));
	bar.Add(t_("Open existing library and add to sketch"), THISBACK1(libOpenCb, true));

	int libIdx = _libsList.GetCursor() - 1;
	if(libIdx < 0 || libIdx >= _libraries.GetCount())
		return;
	String name = _libraries.GetKey(libIdx);
	LibraryState state = _libraries[libIdx];
	Ptr<LibraryInfo> lib = LibraryPool.FindByName(_libraries.GetKey(libIdx), Settings.GetArchitecture());
	if(!lib)
		return;
	
	bar.Separator();
	if(state == UsedLib)
	{
		bar.Add(Format(t_("Remove '%s' from sketch"), name), THISBACK1(libRemoveCb, libIdx));
		bar.Add(Format(t_("Remove '%s' from sketch and close it"), name), THISBACK1(libCloseCb, libIdx));
	}
	else if(state == NotUsedLib)
	{
		bar.Add(Format(t_("Add '%s' to sketch"), name), THISBACK1(libAddCb, libIdx));
		bar.Separator();
		bar.Add(t_("Close library"), THISBACK1(libCloseCb, libIdx));
	}
	if(lib->GetKind() != LibraryInfo::USER)
	{
		bar.Separator();
		bar.Add(t_("Duplicate library in user folder"), THISBACK1(libDuplicateCb, libIdx));
	}
}

void Project::filesContextCb(Bar &bar)
{
	// get library index
	int libIdx = _libsList.GetCursor();
	if(libIdx < 0)
		return;
	
	// get file index
	int fileIdx = _filesList.GetCursor();
	
	// if on library, allow to add and remove files there
	// just inside base library path
	if(libIdx == 0)
	{
		if(fileIdx >= 0)
			bar.Add(t_("Delete file from project"), THISBACK1(projectRemoveFileCb, fileIdx));
		bar.Add(t_("Add file to project"), THISBACK(projectAddFileCb));
	}
	else if(libIdx >= 1 && libIdx <= _libraries.GetCount())
	{
		String name = _libraries.GetKey(libIdx - 1);
		Ptr<LibraryInfo> info = LibraryPool.FindByName(name, Settings.GetArchitecture());
		bool readOnly = false;
		if(info->GetKind() != LibraryInfo::USER && Settings.GetIdeSysLibsReadOnly())
			readOnly = true;
		if(!readOnly)
		{
			if(fileIdx >= 0)
				bar.Add(t_("Delete file from library"), THISBACK2(libRemoveFileCb, libIdx - 1, fileIdx));
			bar.Add(t_("Add file to library"), THISBACK1(libAddFileCb, libIdx - 1));
		}
	}
	else
	{
		if(fileIdx >= 0)
		{
			bool readOnly = false;
			String const &path = _otherFiles[fileIdx];
			if(Settings.GetIdeSysLibsReadOnly() && path.StartsWith(Settings.GetIdeRoot()))
				readOnly = true;
			if(!readOnly)
				bar.Add(t_("Delete file"), THISBACK1(othersDeleteFileCb, fileIdx));
			bar.Add(t_("Remove file"), THISBACK1(othersRemoveFileCb, fileIdx));
		}
		bar.Add(t_("Add/open file"), THISBACK(othersAddFileCb));
	}
	
	if(libIdx <= _libraries.GetCount() && fileIdx >= 0)
	{
		int typ = findarg(GetFileExt(_filesList.Get(fileIdx).name), ".ino", ".cpp", ".cxx", ".c");
		if(typ >= 0)
		{
			bar.Separator();
			bar.Add(t_("Compile file"), THISBACK2(compileFileCb, libIdx, fileIdx));
		}
	}
}
	
	
// clears project content
void Project::ClearProject(void)
{
	_libsList.Clear();
	_filesList.Clear();
	_filesTab.Clear();
	_files.Clear();
	_libraries.Clear();
	
	_openedFiles.Clear();
	_currentFile.Clear();
}
		
// create default project content
void Project::CreateDefaultContent(void)
{
	ClearProject();
	
	_files.Add(ForceExt(_name, ".ino"));
	String path = AppendFileName(_folder, _files.Top());
	createFile(path, true);
	FillLists();
	ShowTab(path);
}
		
// callback when an editor is updated
void Project::editorUpdateCb(void)
{
	// show title if file has been modified
	ShowTitle();
	
	KillTimeCallback(TIMEID_PERIODIC);
	SetTimeCallback(1000, THISBACK(SyncINOToLib), TIMEID_PERIODIC);
}
	
// get file extension image from path
Image Project::GetFileIcon(String const &path)
{
	String ext = ToUpper(GetFileExt(path));
	if(findarg(ext, ".C", ".CPP", ".CXX", ".INO", ".PDE") >= 0)
		return FishIDEImg::Source();
	if(findarg(ext, ".H", ".HPP", ".HXX") >= 0)
		return FishIDEImg::Header();
	return FishIDEImg::Text();
}
		
// fills lists
void Project::FillLibsList(void)
{
	String curItem;
	int cursor = _libsList.GetCursor();
	if(cursor >= 0)
		curItem = _libsList.Get(cursor);
	
	_libsList.Clear();
	_libsList.Add(t_("Main"));

	for(int i = 0; i < _libraries.GetCount(); i++)
	{
		String name = _libraries.GetKey(i);
		LibraryState state = _libraries[i];
		Display display = StdDisplay();
		AttrText text(name);
		if(state == NotUsedLib)
			text.Ink(LtGray());
		else if(state == DependentLib)
			text.Ink(LtCyan());
		_libsList.Add(name, text);
	}
	_libsList.Add(t_("<other files>"));

	if(!curItem.IsEmpty())
		cursor = _libsList.Find(curItem);
	if(cursor >= 0)
		_libsList.SetCursor(cursor);
	else
		_libsList.SetCursor(0);
}

void Project::FillFilesList(void)
{
	// store file cursor
	String item;
	int cursor = _filesList.GetCursor();
	int scrollPos = _filesList.GetSbPos();
	if(cursor >= 0)
		item = _filesList.Get(cursor);
	
	_filesList.Clear();

	int libCursor = _libsList.GetCursor();
	if(libCursor < 0)
		return;
	
	if(libCursor == 0)
	{
		for(int i = 0; i < _files.GetCount(); i++)
			_filesList.Add(_files[i], GetFileIcon(_files[i]));
	}
	else if(libCursor <= _libraries.GetCount())
	{
		// get library info for lib
		String name = _libraries.GetKey(libCursor - 1);
		Ptr<LibraryInfo> info = LibraryPool.FindByName(name, Settings.GetArchitecture());
		if(info)
		{
			Vector<String> const &libFiles = info->GetFiles();
			for(int i = 0; i < libFiles.GetCount(); i++)
				_filesList.Add(libFiles[i], GetFileIcon(libFiles[i]));
		}
	}
	else
	{
		for(int i = 0; i < _otherFiles.GetCount(); i++)
			_filesList.Add(_otherFiles[i], GetFileIcon(_otherFiles[i]));
	}
	
	// restore file cursor
	if(!item.IsEmpty())
	{
		cursor = _filesList.Find(item);
		if(cursor >= 0)
		{
			_filesList.SetCursor(cursor);
			_filesList.SetSbPos(scrollPos);
		}
	}
}

void Project::FillLists(void)
{
	FillLibsList();
	FillFilesList();
}


// library selection handler
void Project::libSelectCb(void)
{
	FillFilesList();
}
		
// file selection handler
void Project::fileSelectCb(void)
{
	int libCursor = _libsList.GetCursor();
	int filesCursor = _filesList.GetCursor();
	if(libCursor < 0 || filesCursor < 0)
		return;
	
	if(libCursor == 0)
	{
		// project files
		String path = AppendFileName(_folder, _files[filesCursor]);
		ShowTab(path);
	}
	else if(libCursor >= 1 && libCursor <= _libraries.GetCount())
	{
		Ptr<LibraryInfo> lib = LibraryPool.FindByName(_libraries.GetKey(libCursor - 1), Settings.GetArchitecture());
		String path = lib->GetPath();
		Vector<String> const &files = lib->GetFiles();
		if(filesCursor < files.GetCount())
		{
			path = AppendFileName(path, files[filesCursor]);
			ShowTab(path);
		}
	}
	else
	{
		String const &path = _otherFiles[filesCursor];
		ShowTab(path);
	}
}

// given a IdeFile ctrl shows the tab, creating it if needed
void Project::ShowTab(String path)
{
	// if path is empty, use current tab
	if(path.IsEmpty())
	{
		int tab = _filesTab.GetCursor();
		if(tab < 0 || !_filesTab.GetCount())
		{
			FishIDEEditor.Assign(NULL);
			ShowTitle();
			return;
		}
		path = _filesTab.GetKey(tab);
	}
	
	// get a new reference to editor object
	FileInfo *fileInfo = GetFileInfo(path);
	if(!fileInfo)
	{
		FishIDEEditor.Assign(NULL);
		ShowTitle();
		return;
	}
	
	// set file as read-only if its path is inside Fishide root
	// and if we selected the option in settings
	if(Settings.GetIdeSysLibsReadOnly() && path.StartsWith(Settings.GetIdeRoot()))
		fileInfo->SetReadOnly();
	
	// check if already in tab
	int tab = _filesTab.FindKey(path);
	
	// if not, just add it, otherwise select it
	if(tab < 0)
	{
		_filesTab.AddKey(path, GetFileName(path));
		tab = _filesTab.GetCount() - 1;
	}
	
	// set current tab
	_filesTab.SetCursor(tab);
	
	// grab the editor for current file
	auto &editor = FishIDEEditor;
	editor.Assign(fileInfo);
	
	// check if editor is already inside edit pane
	// otherwise add it
	if(_editPane.GetFirstChild() != &editor)
	{
		_editPane.Add(editor);
		editor.SizePos();
	}
	
	// show the editor, in case it was hidden
	editor.Show();
	
	// set focus to it
	editor.SetFocus();

	ShowTitle();
}

void Project::errorPaneCb()
{
	int cursor = _errorPane.GetCursor();
	if(cursor < 0)
		return;
	String path = _errorPane.Get(cursor, 0);
	if(!FileExists(path))
		return;
	
	// get file object
	FileInfo *fileInfo = GetFileInfo(path);
	if(!fileInfo)
		return;
	
	// show editor
	ShowTab(path);

	// get the editor and goto to error position
	auto &editor = FishIDEEditor;
	editor.Assign(fileInfo);
	
	int line = (int)_errorPane.Get(cursor, 1) - 1;
	int column = (int)_errorPane.Get(cursor, 3) - 1;
	int eCursor = editor.GetIndexLinePos(Point(column, line));
	editor.SetCursor(eCursor);
	editor.CenterCursor();
}

void Project::findInFilesPaneCb()
{
	int cursor = _findInFilesPane.GetCursor();
	if(cursor < 0)
		return;
	String path = _findInFilesPane.Get(cursor, 0);
	if(!FileExists(path))
		return;
	
	// get file object
	FileInfo *fileInfo = GetFileInfo(path);
	if(!fileInfo)
		return;
	
	// show editor
	ShowTab(path);

	// get the editor and goto to error position
	auto &editor = FishIDEEditor;
	editor.Assign(fileInfo);
	
	int line = (int)_findInFilesPane.Get(cursor, 1) - 1;
	int column = 0;
	int eCursor = editor.GetIndexLinePos(Point(column, line));
	editor.SetCursor(eCursor);
	editor.CenterCursor();
}

// show path and other info on titlebar
void Project::ShowTitle(void)
{
	// show editor path in IDE title
	if(GetTopWindow())
	{
		String title;
		
		// get current displayed editor
		// GetCursor returns 0 even if no tabs... so check it!
		int tab = _filesTab.GetCursor();
		if(tab >= 0 && _filesTab.GetCount())
		{
			String path = _filesTab.GetKey(tab);
			FileInfo *fileInfo = GetFileInfo(path);
			title = "FishIDE Version ";
			title += Settings.GetVersion();
			title += " - [" + path;
			if(fileInfo->IsModified())
				title << "*";
			title << "] { " << GetTitle() << " }";
		}
		GetTopWindow()->Title(title);
	}
}

// selecting current tab, sets corresponding library and file inside lists
void Project::SetLists()
{
	int tab = _filesTab.GetCursor();
	if(tab < 0)
		return;

	// get the file full path
	String path = _filesTab.GetKey(tab);
	
	// get corresponding editor object
	FileInfo *fileInfo = GetFileInfo(path);
	if(!fileInfo)
		return;
	
	// it's a project file ?
	int iFile, iLib;
	if( (iFile = FindProjectFile(path)) >= 0)
	{
		// yep, load project's file list and set cursor to file
		_libsList.SetCursor(0);
		FillFilesList();
		_filesList.SetCursor(iFile);
	}
	else if( (iFile = FindLibraryFile(path, iLib)) >= 0)
	{
		_libsList.SetCursor(iLib + 1);
		FillFilesList();
		_filesList.SetCursor(iFile);
	}
	else
	{
		// check if file is already in "others" list
		iFile = FindIndex(_otherFiles, path);
		if(iFile < 0)
		{
			_otherFiles.Add(path);
			iFile = _otherFiles.GetCount() - 1;
		}
		_libsList.SetCursor(_libsList.GetCount() - 1);
		FillFilesList();
		_filesList.SetCursor(iFile);
	}
	
	// show titlebar
	ShowTitle();
	
	// signal changes
	WhenChange();
}

// update other file list
// (used when changing platform, for example
void Project::UpdateOthers(void)
{
	int iLib;

	// remove files that now belongs to some part
	for(int i = _otherFiles.GetCount() - 1; i >= 0; i--)
	{
		String path = _otherFiles[i];
		if(FindProjectFile(path) >= 0 || FindLibraryFile(path, iLib) >= 0)
			_otherFiles.Remove(i);
	}
	
	// add missing other files
	for(int i = 0; i < _openedFiles.GetCount(); i++)
	{
		FileInfo &info = _openedFiles[i];
		String const &path = info.GetPath();
		if(
			FindProjectFile(path) < 0 &&
			FindLibraryFile(path, iLib) < 0 &&
			FindIndex(_otherFiles, path) < 0
		)
			_otherFiles.Add(path);
	}
}
		
// synchronize .ino to libs
void Project::SyncINOToLib()
{
	// get ino source
	String s;
	String inoPath = GetINOPath();
	int idx = -1;
	for(int i = 0; i < _openedFiles.GetCount(); i++)
		if(_openedFiles[i].GetPath() == inoPath)
		{
			idx = i;
			break;
		}
	if(idx >= 0)
		s = _openedFiles[idx].Get();
	else
		s = LoadFile(inoPath);

	// scan editor for include files
	Vector<String> includes = SourceTools::ScanStringIncludes(s);

	// get corresponding libraries
	Vector<Ptr<LibraryInfo>> infos = LibraryPool.FindByIncludes(includes, Settings.GetArchitecture());
	
	// merge both, marking as belonging to the project only the new ones
	for(int i = 0; i < _libraries.GetCount(); i++)
		_libraries[i] = NotUsedLib;

	for(int i = 0; i < infos.GetCount(); i++)
	{
		infos[i]->ScanFiles();
		int idx = _libraries.Find(infos[i]->GetName());
		if(idx < 0)
			_libraries.Add(infos[i]->GetName(), UsedLib);
		else
			_libraries[idx] = UsedLib;
	}
	
	FillLists();
}

// given the file path, get the FileInfo object for file, open it if needed
// and add to opened files list
FileInfo *Project::GetFileInfo(String const &path)
{
	FileInfo *fileInfo = NULL;
	
	// search first in opened files
	for(int i = 0; i < _openedFiles.GetCount(); i++)
		if(path == _openedFiles[i].GetPath())
		{
			fileInfo = &_openedFiles[i];
			break;
		}
		
	if(!fileInfo)
	{
		fileInfo = new FileInfo(path, this);
		if(!fileInfo)
			return NULL;
		_openedFiles.Add(fileInfo);
	}
		
	return fileInfo;
}


// synchronize project folder with internal structures
// to be called before streaming out and after streaming in
void Project::SynchProjectFolder(void)
{
	// get all files inside project folder
	Vector<String> projectFiles = FileTools::RecurseScan(_folder, "*.*");

	// append missing files to _files
	for(int iFile = 0; iFile < projectFiles.GetCount(); iFile++)
	{
		String file = projectFiles[iFile];
		String ext = GetFileExt(file);
		if(ext == ".fish")
			continue;
		if(FindIndex(_files, file) < 0)
			_files.Add(file);
	}
	
	// remove member of _files that are missing on
	for(int iFile = _files.GetCount() - 1; iFile >= 0; iFile--)
	{
		String file = _files[iFile];
		if(FindIndex(projectFiles, file) < 0)
			_files.Remove(iFile);
	}
}

// find a project file index by full path; return its index or -1 if none
int Project::FindProjectFile(String const &filePath) const
{
	// -1 if it's not a project file
	if(!filePath.StartsWith(_folder))
		return -1;

	// strip folder
	String path = filePath.Mid(_folder.GetCount());
	if(path.StartsWith("\\") || path.StartsWith("/"))
		path = path.Mid(1);

	for(int iFile = 0; iFile < _files.GetCount(); iFile++)
		if(path == _files[iFile])
		{
			return iFile;
		}
	return -1;
}

// find a library index given a file path; return its index, or -1 if none
// in libIndex the library index is returned
// if the library is not opened but the file is inside a library,
// open the corresponding library
int Project::FindLibraryFile(String const &filePath, int &libIndex) const
{
	for(libIndex = 0; libIndex < _libraries.GetCount(); libIndex++)
	{
		Ptr<LibraryInfo> lib = LibraryPool.FindByName(_libraries.GetKey(libIndex), Settings.GetArchitecture());
		if(!lib)
			continue;
		String libPath = UnixPath(lib->GetPath());
		if(!libPath.EndsWith("/"))
			libPath += '/';
		if(!filePath.StartsWith(libPath))
			continue;
		String fileSubPath = filePath.Mid(libPath.GetCount());
		Vector<String> const &libFiles = lib->GetFiles();
		int idx = FindIndex(libFiles, fileSubPath);
		if(idx >= 0)
			return idx;
	}
	
	// not found in an opened library
	// look if it's in a reachable one
	// @@ TO DO
	
	libIndex = -1;
	return -1;
}

// find an "others file" index by file path
int Project::FindOtherFile(String const &filePath)
{
	// first look into other files list
	int idx = FindIndex(_otherFiles, filePath);
	if(idx >= 0)
		return idx;
	
	// add it to other files and return it
	_otherFiles.Add(filePath);
	
	return _otherFiles.GetCount() - 1;
}

// make a path relative or absolute of important ide folders
String Project::RelativePath(String path) const
{
	String delim = NativePath("/");
	String root = Settings.GetIdeRoot();
	if(root.EndsWith(delim))
		root = root.Left(root.GetCount() - 1);
	int rootLen = root.GetCount();
	String skBook = Settings.GetSketchFolder();
	if(skBook.EndsWith(delim))
		skBook = skBook.Left(skBook.GetCount() - 1);
	int skBookLen = skBook.GetCount();
	String folder = _folder;
	if(folder.EndsWith(delim))
		folder = folder.Left(folder.GetCount() - 1);
	int folderLen = folder.GetCount();

	// try to make path location-independent as possible
	if(path.StartsWith(folder))
		path = AppendFileName("[PROJECT]", path.Mid(folderLen));
	else if(path.StartsWith(root))
		path = AppendFileName("[IDEFILES]", path.Mid(rootLen));
	else if(path.StartsWith(skBook))
		path = AppendFileName("[SKETCHBOOK]", path.Mid(skBookLen));
	return path;
}

String Project::AbsolutePath(String path) const
{
	String delim = NativePath("/");
	String root = Settings.GetIdeRoot();
	if(root.EndsWith(delim))
		root = root.Left(root.GetCount() - 1);
	String skBook = Settings.GetSketchFolder();
	if(skBook.EndsWith(delim))
		skBook = skBook.Left(skBook.GetCount() - 1);
	String folder = _folder;
	if(folder.EndsWith(delim))
		folder = folder.Left(folder.GetCount() - 1);

	if(path.StartsWith("[PROJECT]"))
		path = AppendFileName(folder, path.Mid(9));
	else if(path.StartsWith("[IDEFILES]"))
		path = AppendFileName(root, path.Mid(10));
	else if(path.StartsWith("[SKETCHBOOK]"))
		path = AppendFileName(skBook, path.Mid(12));
	return path;
}

namespace Upp
{
	template<> void Xmlize(XmlIO &xml, LineEdit::EditPos &pos)
	{
		xml
			("Cursor"		, pos.cursor)
			("ScrollBar"	, pos.sby)
		;
		if(xml.IsLoading())
		{
			if(IsNull(pos.cursor))
				pos.cursor = 0;
			if(IsNull(pos.sby))
				pos.sby = 0;
		}
	}
}

// xml support
void Project::Xmlize(XmlIO &xml)
{

	if(xml.IsLoading())
	{
		// stream project files
		xml("Files", _files);
		
		// convert to native path
		for(int i = 0; i < _files.GetCount(); i++)
			_files[i] = NativePath(_files[i]);
		
		// stream in libraries
		xml("Libraries", _libraries);
		
		// prescan library files
		Vector<Ptr<LibraryInfo>> infos = LibraryPool.FindByNames(_libraries.GetKeys(), Settings.GetArchitecture());
		_libraries.Clear();
		for(int i = 0; i < infos.GetCount(); i++)
		{
			infos[i]->ScanFiles();
			_libraries.Add(infos[i]->GetName());
		}
		
		// stream in other files
		Vector<String> others;
		xml("OtherFiles", others);
		_otherFiles.Clear();
		for(int i = 0; i < others.GetCount(); i++)
		{
			String s = AbsolutePath(others[i]);
			if(FileExists(s))
				_otherFiles.Add(s);
		}
		
		// check if internal state is consistent with files on disk.
		SynchProjectFolder();
		
		// load opened files and current file, if needed
		if(Settings.GetIdeRememberFiles())
		{
//			VectorMap<String, int> openedFiles;
			VectorMap<String, LineEdit::EditPos> openedFiles;
			xml("OpenedFiles", openedFiles);
			_openedFiles.Clear();
			for(int i = 0; i < openedFiles.GetCount(); i++)
			{
				String path = AbsolutePath(openedFiles.GetKey(i));
//				int cursor = openedFiles[i];
				LineEdit::EditPos pos = openedFiles[i];
				
				FileInfo *info = new FileInfo(path, this);
				if(*info)
				{
					_openedFiles.Add(info);
//					info->SetCursor(cursor);
					info->SetEditPos(pos);
				}
				else
					delete info;
			}
			xml("CurrentFile", _currentFile);
			_currentFile = AbsolutePath(_currentFile);
			
			_filesTab.Clear();
			for(int i = 0; i < _openedFiles.GetCount(); i++)
			{
				FileInfo const &info = _openedFiles[i];
				ShowTab(info.GetPath());
			}
			if(!_currentFile.IsEmpty() && FileExists(_currentFile))
				ShowTab(_currentFile);
			
			// if no file is opened, we probably opened a .ino file
			// so just show it
			if(!_filesTab.GetCount())
			{
				String inoPath = ForceExt(GetFullPath(), ".ino");
				if(FileExists(inoPath))
					ShowTab(inoPath);
			}
		}
		else
		{
			_openedFiles.Clear();
			_currentFile = "";
		}
		
	}
	// storing
	else
	{
		// check if internal state is consistent with files on disk.
		SynchProjectFolder();
	
		// stream project files
		xml("Files", _files);
		
		// stream out libraries
		xml("Libraries", _libraries);
		
		// stream out other files
		Vector<String> others;
		for(int i = 0; i < _otherFiles.GetCount(); i++)
		{
			String s = RelativePath(_otherFiles[i]);
			others.Add(s);
		}
		xml("OtherFiles", others);
		
		// store opened files and current file
//		VectorMap<String, int> openedFiles;
		VectorMap<String, LineEdit::EditPos> openedFiles;
		for(int i = 0; i < _openedFiles.GetCount(); i++)
		{
			FileInfo const &info = _openedFiles[i];
			String path = RelativePath(info.GetPath());

//			openedFiles.Add(path, info.GetCursor());
			openedFiles.Add(path, info.GetEditPos());
		}
		xml("OpenedFiles", openedFiles);
		
		int i = _filesTab.GetCursor();
		if(i >= 0 && i < _filesTab.GetCount())
			_currentFile = RelativePath(_filesTab.GetKey(i));
		else
			_currentFile = "";
		xml("CurrentFile", _currentFile);
	}
	xml("SerialMonitor", _serialPane);
}
		
// internal save
bool Project::Save0(String const &p, bool force)
{
	// flush the editor object before saving
	FishIDEEditor.Flush();
	
	String newFolder = GetFileFolder(p);
	String newName = GetFileName(p);

	// create dest path if needed
	RealizeDirectory(newFolder);
	
	// if we changed path, we shall copy all project files to new destination
	if(p != GetFullPath())
	{
		for(int iFile = 0; iFile < _files.GetCount(); iFile++)
		{
			String file = _files[iFile];
			String sourcePath = AppendFileName(_folder, file);
			String destPath = AppendFileName(newFolder, file);
			
			// if file is opened inside its codeeditor, just save it on a new path
			if(IdeFilePool.IsOpened(sourcePath))
				GetFileInfo(sourcePath)->SaveAs(destPath);
			// otherwise copy files
			else
				FileCopy(sourcePath, destPath);
		}
	}
	// otherwise, just save modified files
	else
	{
		IdeFilePool.SaveAll();
	}
	
	// replace path with new one
	_folder = newFolder;
	_name = newName;
	
	// @@ WE SHALL SAVE LIBS AND OTHER FILES HERE

	// save project metadata
	StoreAsXMLFile(*this, "FishIDE", p);
	
	// show title, to signal changes of
	
	// show titlebar
	ShowTitle();
	
	WhenChange();

	return true;
}
	
// opens a project
bool Project::Open(void)
{
	String path = GetFullPath();
	if(!FileExists(path))
		return false;

/*
	if(!Save())
		return false;
*/
	
	LoadFromXMLFile(*this, path);
	
	// synchronize ino file to loaded libs
	// and fill the lists
	SyncINOToLib();
	
	SetLists();
	
	WhenChange();
	return true;
}

// save a project
bool Project::Save(bool force)
{
/*
	if(!modified && !force)
		return true;
*/
	String path = GetFullPath();
	return Save0(path, force);
}

// save as -- copies all data in a new folder
bool Project::SaveAs(String const &path)
{
	String p = ForceExt(path, ".fish");
	return Save0(p);
}

// import an arduino sketch
bool Project::Import(String const &arduPath)
{
	if(!FileExists(arduPath))
		return false;
	
	if(!Save())
		return false;
	
	ClearProject();

	_folder = GetFileFolder(arduPath);
	_name = GetFileTitle(arduPath);
	
	SynchProjectFolder();
	
	// synchronize ino file to loaded libs
	// and fill the lists
	SyncINOToLib();
	
	FillLists();
	ShowTab(arduPath);

	return true;
}

// splitter changes callback
void Project::splitterCb(void)
{
	WhenSplitter(_hSplitter.GetPos(), _vSplitter.GetPos(), _filesLibsSplitter.GetPos());
}

// sets splitter positions -- ProjectsCtrl handles it
void Project::SetSplitters(int h, int v, int fl)
{
	_hSplitter.SetPos(h);
	_vSplitter.SetPos(v);
	_filesLibsSplitter.SetPos(fl);
}

// handle changes in settings
void Project::SettingsChanged(void)
{
	// re-synch ino
	SyncINOToLib();
	
	// update other file list
	UpdateOthers();
	
	// refil files lists
	FillLists();
	
	SetLists();
	
	// fix readonly flags, in case it has been changed
	bool sysRO = Settings.GetIdeSysLibsReadOnly();
	for(int iFile = 0; iFile < _openedFiles.GetCount(); iFile++)
	{
		FileInfo &info = _openedFiles[iFile];
		if(!sysRO)
			info.SetNoReadOnly();
		else
			info.SetReadOnly(info.GetPath().StartsWith(Settings.GetIdeRoot()));
	}
}

// propagate theme apply to all controls
void Project::ThemeChanged(Theme const *t)
{
	if(!t)
		t = &Themes.GetCurrent();

	Font f = t->GetSerialPortFontFont();
	Color co = t->GetSerialPortOutputTextColor();
	Color bo = t->GetSerialPortOutputBackgroundColor();
	Color ci = t->GetSerialPortInputTextColor();
	Color bi = t->GetSerialPortInputBackgroundColor();

	_serialPane.receiveEdit.SetFont(f);
	_serialPane.receiveEdit.SetColor(TextCtrl::INK_DISABLED, co);
	_serialPane.receiveEdit.SetColor(TextCtrl::INK_NORMAL, co);
	_serialPane.receiveEdit.SetColor(TextCtrl::PAPER_READONLY, bo);
	_serialPane.receiveEdit.SetColor(TextCtrl::PAPER_NORMAL, bo);

	_serialPane.sendEdit.SetFont(f);
	_serialPane.sendEdit.SetColor(TextCtrl::INK_DISABLED, ci);
	_serialPane.sendEdit.SetColor(TextCtrl::INK_NORMAL, ci);
	_serialPane.sendEdit.SetColor(TextCtrl::PAPER_READONLY, bi);
	_serialPane.sendEdit.SetColor(TextCtrl::PAPER_NORMAL, bi);
}

// clear build results
void Project::ClearBuildResults(void)
{
	_outputPane.Clear();
	_outputText.Clear();
	_errorPane.Clear();
}

// show build results in output pane -- internal function
void Project::ShowResults0(void)
{
	Theme const &t = Themes.GetCurrent();
	Theme::FontInfo const &fi = t.GetConsoleFontInfo();
	
	String s = Format("[!%s!", fi._name);
	if(fi._bold)
		s << "*";
	if(fi._italic)
		s << "/";
	s << Format("+%d ", fi._height);
	_outputPane.SetQTF(s + _outputText + "]", Zoom(1, 1));
	_outputPane.ScrollEnd();
	_bottomPane.SetCursor(0);
	Ctrl::ProcessEvents();
}

	
// show build results in output pane
void Project::ShowBuildResults(String const &output)
{
	Color c = Themes.GetCurrent().GetMessagesTextColor();
	
	_outputText << Format("[@(%d.%d.%d) ", c.GetR(), c.GetG(), c.GetB());
	_outputText << DeQtfLf(output) << "]";
	ShowResults0();
}

void Project::ShowBuildErrors(String const &output)
{
	Color c = Themes.GetCurrent().GetErrorMessagesTextColor();
	_outputText << Format("[@(%d.%d.%d) ", c.GetR(), c.GetG(), c.GetB());
	_outputText << DeQtfLf(output) << "]";
	ShowResults0();
}
		
// shows messages in error pane
void Project::ShowMessages(Vector<BuilderClass::ErrorLine> const &errLines)
{
	if(!errLines.IsEmpty())
	{
		int errors = 0;
		int warnings = 0;
		for(int i = 0; i < errLines.GetCount(); i++)
		{
			AttrText at;
			BuilderClass::ErrorLine const &el = errLines[i];
			String msg;
			if(el.isError)
			{
				errors++;
				msg = t_("Error   : ");
				at.NormalPaper(Blend(White(), LtRed(), 50));
			}
			else
			{
				warnings++;
				msg = t_("Warning : ");
				at.NormalPaper(Blend(White(), Yellow(), 50));
			}
			msg << el.message;
			at.Set(msg);
			_errorPane.Add(el.file, el.line, at, el.pos);
		}
		_bottomPane.SetCursor(1);
	}
	else
		_bottomPane.SetCursor(0);
}

// display a message pane
void Project::ShowMessagePane(int pane)
{
	if(pane < 0 || pane >= LASTPANE)
		return;
	_bottomPane.SetCursor(pane);
}

// show disassembly output
bool Project::ShowDisasm(void)
{
	// try to disassemble the file
	String disasmPath = Builder.ShowDisasm(this);
	if(disasmPath.IsEmpty())
		return false;
	
	// check if file is already opened
	bool found = false;
	for(int i = 0; i < _openedFiles.GetCount(); i++)
		if(_openedFiles[i].GetPath() == disasmPath)
		{
			found = true;
			break;
		}

	// show the file
	ShowTab(disasmPath);
	
	// refresh it if it was already opened
	// @@ we shall find a way to keep opened files in sync with files
	if(found)
		FishIDEEditor.Reload();
	
	// set to read only
	FishIDEEditor.SetReadOnly(true);
	
	return true;
}



namespace Upp
{
	template<>void Xmlize(XmlIO &xml, Project::LibraryState &state)
	{
		if(xml.IsLoading())
		{
			int s;
			Xmlize(xml, s);
			state = (Project::LibraryState)s;
		}
		else
		{
			int s = (int)state;
			Xmlize(xml, s);
		}
	}
};

