#include <CtrlLib/CtrlLib.h>

namespace Upp {

INITBLOCK {
	RegisterGlobalConfig("GlobalFileSelectorLRU");
}

#ifdef GUI_WIN
void AvoidPaintingCheck__();

Image ProcessSHIcon(HICON hIcon)
{
	AvoidPaintingCheck__();
	Color c = White();
	Image m[2];
	for(int i = 0; i < 2; i++) {
		ICONINFO iconinfo;
		if(!hIcon || !GetIconInfo(hIcon, &iconinfo))
			return Image();
		BITMAP bm;
		::GetObject((HGDIOBJ)iconinfo.hbmMask, sizeof(BITMAP), (LPVOID)&bm);
		Size sz(bm.bmWidth, bm.bmHeight);
		ImageDraw iw(sz);
		iw.DrawRect(sz, c);
		::DrawIconEx(iw.GetHandle(), 0, 0, hIcon, 0, 0, 0, NULL, DI_NORMAL|DI_COMPAT);
		::DeleteObject(iconinfo.hbmColor);
		::DeleteObject(iconinfo.hbmMask);
		c = Black();
		m[i] = iw;
	}
	::DestroyIcon(hIcon);
	return RecreateAlpha(m[0], m[1]);
}

struct FileIconMaker : ImageMaker {
	String file;
	bool   exe;
	bool   dir;
	bool   large;

	virtual String Key() const {
		return file + "\n" + (exe ? "1" : "0") + (dir ? "1" : "0");
	}

	virtual Image Make() const {
		SHFILEINFOW info;
		AvoidPaintingCheck__();
		SHGetFileInfoW(ToSystemCharsetW(file), dir ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL,
		               &info, sizeof(info),
		               SHGFI_ICON|(large ? SHGFI_LARGEICON : SHGFI_SMALLICON)|(exe ? 0 : SHGFI_USEFILEATTRIBUTES));
		return ProcessSHIcon(info.hIcon);
	}
};


Image GetFileIcon(const char *path, bool dir, bool force, bool large, bool quick = false)
{
	FileIconMaker m;
	String ext = GetFileExt(path);
	m.exe = false;
	m.dir = false;
	m.file = path;
	m.large = large;
	if(force)
		m.exe = true;
	else
	if(dir) {
		m.dir = true;
		m.exe = true;
	}
	else
	if(findarg(ext, ".exe", ".lnk") >= 0)
		m.exe = true;
	else
		m.file = "x" + ext;
	if(quick) {
		m.exe = false;
		m.file = "x" + ext;
	}
	return MakeImage(m);
}

#define GETFILEICON_DEFINED

#endif

#if defined(GUI_GTK)

Image GtkThemeIcon(const char *name, int sz);

Image GnomeImage(const char *s, bool large = false)
{
	return GtkThemeIcon(String("gnome-") + s, DPI(large ? 48 : 16));
}

Image SystemImage(const char *s, bool large = false)
{
	return GtkThemeIcon(s, DPI(large ? 48 : 16));
}

struct ExtToMime {
	Index<String> major;
	Index<String> minor;
	VectorMap<String, dword> map;

	void Load(const char *file);
	void Refresh();
	bool GetMime(const String& ext, String& maj, String& min);
};

void ExtToMime::Load(const char *fn)
{
	FileIn in(fn);
	if(in)
		while(!in.IsEof()) {
			String ln = TrimLeft(TrimRight(in.GetLine()));
			if(ln[0] != '#') {
				int q = ln.Find(':');
				if(q >= 0) {
					String h = ln.Mid(0, q);
					int w = h.Find('/');
					if(w >= 0) {
						int x = ln.Find("*.", q);
						if(x >= 0) {
							String ext = ln.Mid(x + 2);
							if(ext.GetCount() && map.Find(ext) < 0)
								map.Add(ext, MAKELONG(minor.FindAdd(h.Mid(w + 1)), major.FindAdd(h.Mid(0, w))));
						}
					}
				}
			}
		}
}

void ExtToMime::Refresh()
{
	major.Clear();
	minor.Clear();
	map.Clear();
	Load("/usr/local/share/mime/globs");
	Load("/usr/share/mime/globs");
}

bool ExtToMime::GetMime(const String& ext, String& maj, String& min)
{
	ONCELOCK {
		Refresh();
	}
	int q = map.Find(ext);
	if(q < 0)
		return false;
	dword x = map[q];
	maj = major[HIWORD(x)];
	min = minor[LOWORD(x)];
	return true;
}

struct FileExtMaker : ImageMaker {
	String ext;
	bool   large;

	virtual String Key() const {
		return ext;
	}

	virtual Image Make() const {
		String major;
		String minor;
		if(!Single<ExtToMime>().GetMime(ext, major, minor))
			return Null;
		Image img = SystemImage(major + '-' + minor, large);
		return IsNull(img) ? SystemImage(major) : img;
	}
};

Image PosixGetDriveImage(String dir, bool large)
{
	static bool init = false;
	static Image cdrom;
	static Image lcdrom;
	static Image harddisk;
	static Image lharddisk;
	static Image floppy;
	static Image lfloppy;
	static Image computer;
	static Image lcomputer;
	
	if (!init) {
		bool KDE = Environment().Get("KDE_FULL_SESSION", String()) == "true";
		if (KDE) {
			cdrom     = SystemImage("media-optical");
			lcdrom    = SystemImage("media-optical", true);
			harddisk  = SystemImage("drive-harddisk");
			lharddisk = SystemImage("drive-harddisk", true);
			floppy    = SystemImage("media-floppy");
			lfloppy   = SystemImage("media-floppy", true);
			computer  = SystemImage("computer");
			lcomputer = SystemImage("computer", true);
		}
		else {
			cdrom     = GnomeImage("dev-cdrom");
			lcdrom    = GnomeImage("dev-cdrom", true);
			harddisk  = GnomeImage("dev-harddisk");
			lharddisk = GnomeImage("dev-harddisk", true);
			floppy    = GnomeImage("dev-floppy");
			lfloppy   = GnomeImage("dev-floppy", true);
			computer  = GnomeImage("dev-computer");
			lcomputer = GnomeImage("dev-computer", true);
		}
		
		init = true;
	}
	if(dir.GetCount() == 0 || dir == "/") {
		Image m = large ? lcomputer : computer;
		return IsNull(m) ? CtrlImg::Computer() : m;
	}
	if(dir.Find("cdrom") == 0 || dir.Find("cdrecorder") == 0) {
		Image m = large ? lcdrom : cdrom;
		return IsNull(m) ? CtrlImg::CdRom() : m;
	}
	if(dir.Find("floppy") == 0 || dir.Find("zip") == 0) {
		Image m = large ? lfloppy : floppy;
		return IsNull(m) ? CtrlImg::Diskette() : m;
	}
	Image m = large ? lharddisk : harddisk;
	return IsNull(m) ? CtrlImg::Hd() : m;
}

Image GetFileIcon(const String& folder, const String& filename, bool isdir, bool isexe, bool large)
{
	static bool init = false;
	static bool KDE  = Environment().Get("KDE_FULL_SESSION", String()) == "true";
	
	static Image file;
	static Image lfile;
	static Image dir;
	static Image ldir;
	static Image exe;
	static Image lexe;
	static Image home;
	static Image lhome;
	static Image desktop;
	static Image ldesktop;
	static Image music;
	static Image lmusic;
	static Image pictures;
	static Image lpictures;
	static Image video;
	static Image lvideo;
	static Image documents;
	static Image ldocuments;
	static Image download;
	static Image ldownload;
	static Image help;
	static Image lhelp;
	static Image translation;
	static Image ltranslation;
	static Image layout;
	static Image llayout;
	
	static Image fileImage;
	static Image fileMusic  = SystemImage("audio-x-generic");
	static Image fileScript = SystemImage("text-x-script");
	
	if (!init) {
		if (KDE) {
			file         = SystemImage("text-plain");
			lfile        = SystemImage("text-plain", true);
			dir          = SystemImage("folder");
			ldir         = SystemImage("folder", true);
			exe          = SystemImage("application-x-executable");
			lexe         = SystemImage("application-x-executable", true);
			home         = SystemImage("user-home");
			lhome        = SystemImage("user-home", true);
			desktop      = SystemImage("user-desktop");
			ldesktop     = SystemImage("user-desktop", true);
			music        = SystemImage("folder-sound");
			lmusic       = SystemImage("folder-sound", true);
			pictures     = SystemImage("folder-image");
			lpictures    = SystemImage("folder-image", true);
			video        = SystemImage("folder-video");
			lvideo       = SystemImage("folder-video", true);
			documents    = SystemImage("folder-documents");
			ldocuments   = SystemImage("folder-documents", true);
			download     = SystemImage("folder-downloads");
			ldownload    = SystemImage("folder-downloads", true);
			help         = SystemImage("help-contents");
			lhelp        = SystemImage("help-contents", true);
			translation  = SystemImage("applications-education-language");
			ltranslation = SystemImage("applications-education-language", true);
			layout       = SystemImage("applications-development");
			llayout      = SystemImage("applications-development", true);
			
			fileImage    = SystemImage("application-x-egon");
		}
		else {
			file         = GnomeImage("fs-regular");
			lfile        = GnomeImage("fs-regular", true);
			dir          = GnomeImage("fs-directory");
			ldir         = GnomeImage("fs-directory", true);
			exe          = GnomeImage("fs-executable");
			lexe         = GnomeImage("fs-executable", true);
			home         = GnomeImage("fs-home");
			lhome        = GnomeImage("fs-home", true);
			desktop      = GnomeImage("fs-desktop");
			ldesktop     = GnomeImage("fs-desktop", true);
			music        = SystemImage("folder-music");
			lmusic       = SystemImage("folder-music", true);
			pictures     = SystemImage("folder-pictures");
			lpictures    = SystemImage("folder-pictures", true);
			video        = SystemImage("folder-video");
			lvideo       = SystemImage("folder-video", true);
			documents    = SystemImage("folder-documents");
			ldocuments   = SystemImage("folder-documents", true);
			download     = SystemImage("folder-downloads");
			ldownload    = SystemImage("folder-downloads", true);
			help         = SystemImage("help");
			lhelp        = SystemImage("help", true);
			translation  = SystemImage("preferences-desktop-locale");
			ltranslation = SystemImage("preferences-desktop-locale", true);
			layout       = SystemImage("applications-development");
			llayout      = SystemImage("applications-development", true);

			fileImage    = SystemImage("image-x-generic");
		}
		
		init = true;
	}
	if (filename == "Help Topics")
		return large ? lhelp : help;
	if(isdir) {
		Image img = dir;
		if(AppendFileName(folder, filename) == GetHomeDirectory())
			return large ? lhome : home;
		else
		if(AppendFileName(folder, filename) ==  GetDesktopFolder ())
			return large ? ldesktop : desktop;
		else
		if(AppendFileName(folder, filename) == GetMusicFolder ())
			return large ? lmusic : music;
		else
		if(AppendFileName(folder, filename) == GetPicturesFolder())
			return large ? lpictures : pictures;
		else
		if(AppendFileName(folder, filename) == GetVideoFolder())
			return large ? lvideo : video;
		else
		if(AppendFileName(folder, filename) == GetDocumentsFolder())
			return large ? ldocuments : documents;
		else
		if(AppendFileName(folder, filename) == GetDownloadFolder())
			return large ? ldownload : download;
		else
		if(folder == "/media" || filename.GetCount() == 0)
			return PosixGetDriveImage(filename, large);
		return dir;
	}
	FileExtMaker m;
	m.ext = GetFileExt(filename);
	for (int i = 1; i < m.ext.GetCount(); ++i)
		m.ext.Set (i, ToLower(m.ext[i]));
	
	// Fixing format problems
	if (m.ext == ".gz") m.ext = ".tar.gz";
	
	// Ultimate++ - files extensions
	if (m.ext == ".t" || m.ext == ".lng") return large ? ltranslation : translation;
	if (m.ext == ".lay") return large ? llayout : layout;
	if (m.ext == ".iml") return fileImage;
	if (m.ext == ".usc") return fileScript;
	
	// Binary - files extensions (It seems that KDE has problem with multimedia MIME icons handling)
	if (KDE) {
		if (m.ext == ".bmp" || m.ext == ".dib" ||
			m.ext == ".gif" ||
			m.ext == ".jpg" || m.ext == ".jpeg" || m.ext == ".jpe" ||
			m.ext == ".png" ||
			m.ext == ".tif" || m.ext == ".tiff" ||
			m.ext == ".svg" ||
			m.ext == ".ico" ||
			m.ext == ".xcf")
			return fileImage;
		if (m.ext == ".aac" || m.ext == ".ogg" || m.ext == ".mp3")  return fileMusic;
	}

	Image img;
	if(m.ext.GetCount()) {
		m.ext = m.ext.Mid(1);
		m.large = large;
		img = MakeImage(m);
		isexe = false;
	}
	return IsNull(img) ? isexe ? (large ? lexe : exe) : (large ? lfile : file) : img;
}

#define GETFILEICON_DEFINED

#endif

#ifdef GUI_COCOA
struct FileIconMaker : ImageMaker {
	String file;
	bool   exe;
	bool   dir;
	bool   large;

	virtual String Key() const {
		return file + (exe ? "1" : "0") + (dir ? "1" : "0");
	}

	virtual Image Make() const {
		return GetIconForFile(file);
	}
};

#define GETFILEICON_DEFINED

Image GetFileIcon(const char *path, bool dir, bool exe, bool large, bool quick = false)
{
	FileIconMaker m;
	m.exe = exe;
	m.dir = dir;
	m.file = path;
	m.large = large;
	return MakeImage(m);
}
#endif

#ifndef GETFILEICON_DEFINED
Image PosixGetDriveImage(String dir, bool)
{
	if(dir.GetCount() == 0 || dir == "/")
		return CtrlImg::Computer();
	if(dir.Find("cdrom") == 0 || dir.Find("cdrecorder") == 0)
		return CtrlImg::CdRom();
	if(dir.Find("floppy") == 0 || dir.Find("zip") == 0)
		return CtrlImg::Diskette();
	return CtrlImg::Hd();
}

#ifdef PLATFORM_WIN32
Image GetFileIcon(const String& folder, bool, bool, bool, bool = false)
{
	return CtrlImg::File();
}
#else
Image GetFileIcon(const String& folder, const String& filename, bool isdir, bool isexe, bool = false)
{
	return isdir ? CtrlImg::Dir() : CtrlImg::File();
}

#endif
#endif

Image NativePathIcon0(const char *path, bool folder, bool large)
{
#if defined(PLATFORM_WIN32)
	if (folder)
		return GetFileIcon(path, true, true, large);
	else
		return GetFileIcon(path, false, false, large);
#endif
#ifdef PLATFORM_POSIX
	String p = path;
	FindFile ff(path);
#ifdef GUI_COCOA
	return GetFileIcon(path, folder, ff.GetMode() & 0111, large);
#else
	bool isdrive = folder && ((p == "/media") || (p == "/mnt"));
	return isdrive ? PosixGetDriveImage(GetFileName(path), large)
				   : GetFileIcon(path, GetFileName(path), folder, ff.GetMode() & 0111, large);
#endif
#endif
}

Image NativePathIcon(const char *path, bool folder)
{
	return NativePathIcon0(path, folder, false);
}

Image NativePathIcon(const char *path)
{
	FindFile ff(path);
	return NativePathIcon(path, ff.IsFolder());
}

Image NativePathLargeIcon(const char *path, bool folder)
{
	return NativePathIcon0(path, folder, true);
}

Image NativePathLargeIcon(const char *path)
{
	FindFile ff(path);
	return NativePathLargeIcon(path, ff.IsFolder());
}

bool MatchSearch(const String& filename, const String& search)
{
	return search.GetCount() ? Filter(filename, CharFilterDefaultToUpperAscii).Find(search) >= 0 : true;
}

bool Load(FileList& list, const String& dir, const char *patterns, bool dirs,
          Event<bool, const String&, Image&> WhenIcon, FileSystemInfo& filesystem,
          const String& search, bool hidden, bool hiddenfiles, bool lazyicons)
{
	if(dir.IsEmpty()) {
		Array<FileSystemInfo::FileInfo> root = filesystem.Find(Null);
		for(int i = 0; i < root.GetCount(); i++)
			if(MatchSearch(root[i].filename, search))
				list.Add(root[i].filename,
					GetDriveImage(root[i].root_style),
					StdFont().Bold(), SColorText, true, -1, Null, SColorDisabled,
			#ifdef PLATFORM_WIN32
					Nvl(root[i].root_desc, String(" ") + t_("Local Disk")),
			#else
					root[i].root_desc,
			#endif
					StdFont()
				);
		#ifdef PLATFORM_WIN32
			list.Add(t_("Network"), CtrlImg::Network(), StdFont().Bold(), SColorText,
			         true, -1, Null, SColorDisabled, Null, StdFont());
		#endif
	}
	else {
		Array<FileSystemInfo::FileInfo> ffi =
			filesystem.Find(AppendFileName(dir, filesystem.IsWin32() ? "*.*" : "*"));
		if(ffi.IsEmpty())
			return false;
	#if defined(PLATFORM_POSIX) && !defined(GUI_COCOA)
		bool isdrive = dir == "/media" || dir == "/mnt";
	#endif
		for(int t = 0; t < ffi.GetCount(); t++) {
			const FileSystemInfo::FileInfo& fi = ffi[t];
			bool nd = dirs && !fi.is_directory;
			bool show = hidden;
			if(!show && filesystem.IsWin32() ? !fi.is_hidden : fi.filename[0] != '.')
				show = true;
			if(!show && hiddenfiles && fi.is_file)
				show = true;
			if(fi.filename != "." && fi.filename != ".." &&
			#ifdef PLATFORM_WIN32
			   (fi.is_directory || FileSel::IsLnkFile(fi.filename) || PatternMatchMulti(patterns, fi.filename)) &&
			#else
			   (fi.is_directory || PatternMatchMulti(patterns, fi.filename)) &&
			#endif
			   MatchSearch(fi.filename, search) && show) {
				Image img;
			#ifdef PLATFORM_POSIX
			#ifdef GUI_COCOA
				img = GetFileIcon(AppendFileName(dir, fi.filename), fi.is_directory, fi.unix_mode & 0111, false, lazyicons);
			#else
				img = isdrive ? PosixGetDriveImage(fi.filename, false)
				              : GetFileIcon(dir, fi.filename, fi.is_directory, fi.unix_mode & 0111, false);
			#endif
			#endif
			#ifdef GUI_WIN
				img = GetFileIcon(AppendFileName(dir, fi.filename), fi.is_directory, false, false, lazyicons);
			#endif
				if(IsNull(img))
					img = fi.is_directory ? CtrlImg::Dir() : CtrlImg::File();
				WhenIcon(fi.is_directory, fi.filename, img);
				list.Add(fi.filename, fi.is_hidden ? Contrast(img, 200) : img,
						 StdFont().Bold(fi.is_directory),
						 nd ? SColorDisabled : fi.is_hidden ? Blend(SColorText, Gray, 200) : SColorText, fi.is_directory,
						 fi.is_directory ? -1 : fi.length,
						 fi.last_write_time,
						 nd ? SColorDisabled
						    : fi.is_directory ? SColorText
						                      : fi.is_hidden ? Blend(SColorMark, Gray, 200)
						                                     : SColorMark,
				         Null, Null, Null, Null,
#ifdef PLATFORM_WIN32
                         false,
#else
						 fi.unix_mode & 0111,
#endif
				         fi.is_hidden
				);
			}
		}
	}
	return true;
}

#ifdef GUI_WIN
static Mutex       sExeMutex;
static WCHAR       sExePath[1025];
static bool        sExeRunning;
static SHFILEINFOW sExeInfo;

static auxthread_t auxthread__ sExeIconThread(void *)
{
	SHFILEINFOW info;
	WCHAR path[1025];
	CoInitialize(NULL);
	sExeMutex.Enter();
	wcscpy(path, sExePath);
	sExeMutex.Leave();
	AvoidPaintingCheck__();
	SHGetFileInfoW(path, FILE_ATTRIBUTE_NORMAL, &info, sizeof(info), SHGFI_ICON|SHGFI_SMALLICON);
	sExeMutex.Enter();
	memcpy(&sExeInfo, &info, sizeof(info));
	sExeRunning = false;
	sExeMutex.Leave();
	return 0;
}

void LazyExeFileIcons::Done(Image img)
{
	if(pos >= ndx.GetCount())
		return;
	int ii = ndx[pos];
	if(ii < 0 || ii >= list->GetCount())
		return;
	const FileList::File& f = list->Get(ii);
	WhenIcon(false, f.name, img);
	if(!IsNull(img)) {
		if(f.hidden)
			img = Contrast(img, 200);
		list->SetIcon(ii, img);
	}
	pos++;
}

WString LazyExeFileIcons::Path()
{
	if(pos >= ndx.GetCount())
		return Null;
	int ii = ndx[pos];
	if(ii < 0 || ii >= list->GetCount())
		return Null;
	const FileList::File& f = list->Get(ii);
	return NormalizePath(AppendFileName(dir, f.name)).ToWString();
}

void LazyExeFileIcons::Do()
{
	int start = msecs();
	for(;;) {
		for(;;) {
			SHFILEINFOW info;
			bool done = false;
			WString path = Path();
			Vector<char16> path16 = ToUtf16(path);
			if(IsNull(path) || path16.GetCount() > 1000)
				return;
			sExeMutex.Enter();
			bool running = sExeRunning;
			if(!running) {
				done = path == ToUtf32(sExePath);
				memcpy(&info, &sExeInfo, sizeof(info));
				*sExePath = '\0';
				memset(&sExeInfo, 0, sizeof(sExeInfo));
			}
			sExeMutex.Leave();
			Image img = ProcessSHIcon(info.hIcon);
			if(done)
				Done(img);
			if(!running)
				break;
			Sleep(0);
			if(msecs(start) > 10 || Ctrl::IsWaitingEvent()) {
				Restart(0);
				return;
			}
		}

		WString path = Path();
		Vector<WCHAR> path16 = ToUtf16(path);
		if(IsNull(path) || path16.GetCount() > 1000)
			return;
		sExeMutex.Enter();
		memset(sExePath, 0, sizeof(sExePath));
		memcpy(sExePath, path16.begin(), sizeof(char16) * path16.GetCount());
		sExeRunning = true;
		StartAuxThread(sExeIconThread, NULL);
		sExeMutex.Leave();
	}
}

void LazyExeFileIcons::ReOrder()
{ // gather .exe and .lnk files; sort based on length so that small .exe get resolved first
	ndx.Clear();
	Vector<int> len;
	for(int i = 0; i < list->GetCount(); i++) {
		const FileList::File& f = list->Get(i);
		if(findarg(ToLower(GetFileExt(f.name)), ".exe", ".lnk") >= 0 && !f.isdir) {
			ndx.Add(i);
			len.Add((int)min((int64)INT_MAX, f.length));
		}
	}
	IndexSort(len, ndx);
	Restart(0);
}

void LazyExeFileIcons::Start(FileList& list_, const String& dir_, Event<bool, const String&, Image&> WhenIcon_)
{
	list = &list_;
	dir = dir_;
	WhenIcon = WhenIcon_;
	pos = 0;
	ReOrder();
}
#endif

String FileSel::GetDir() const
{
	String s = ~dir;
	if(s.IsEmpty()) return basedir;
	if(basedir.IsEmpty()) return s;
	return AppendFileName(basedir, s);
}

void FileSel::SetDir(const String& _dir) {
#ifdef PLATFORM_WIN32
	netstack.Clear();
	netnode.Clear();
#endif
	dir <<= _dir;
	Load();
	Update();
}

String FileSel::FilePath(const String& fn) {
	return AppendFileName(GetDir(), fn);
}

Image GetDriveImage(char drive_style)
{
	switch(drive_style)
	{
	case FileSystemInfo::ROOT_NO_ROOT_DIR: return Null;
	case FileSystemInfo::ROOT_REMOTE:
	case FileSystemInfo::ROOT_NETWORK:   return CtrlImg::Share();
	case FileSystemInfo::ROOT_COMPUTER:  return CtrlImg::Computer();
	case FileSystemInfo::ROOT_REMOVABLE: return CtrlImg::Flash();
	case FileSystemInfo::ROOT_CDROM:     return CtrlImg::CdRom();
	default:                             return CtrlImg::Hd();
	}
}

String FileSel::GetMask()
{
	String emask = "*";
	if(!IsNull(type)) {
		if(IsString(~type))
			emask = ~type;
		else {
			int q = ~type;
			if(q >= 0 && q < mask.GetCount())
				emask = mask[q];
		}
	}
	return emask;
}

void FileSel::Load()
{
	search <<= Null;
	SearchLoad();
}

void FileSel::LoadNet()
{
#ifdef PLATFORM_WIN32
	list.Clear();
	for(int i = 0; i < netnode.GetCount(); i++) {
		Image m = CtrlImg::Group();
		switch(netnode[i].GetDisplayType()) {
		case NetNode::NETWORK:
			m = CtrlImg::Network();
			break;
		case NetNode::SHARE:
			m = CtrlImg::Share();
			break;
		case NetNode::SERVER:
			m = CtrlImg::Computer();
			break;
		}
		list.Add(netnode[i].GetName(), m);
	}
	places.FindSetCursor("\\");
#endif
}

void FileSel::SelectNet()
{
#ifdef PLATFORM_WIN32
	int q = list.GetCursor();
	if(q >= 0 && q < netnode.GetCount()) {
		NetNode& n = netnode[q];
		String p = n.GetPath();
		if(p.GetCount())
			SetDir(p);
		else {
			NetNode n = netnode[q];
			netstack.Add() = n;
			ScanNetwork([=] {
				return n.Enum();
			});
		}
	}
#endif
}

#ifdef PLATFORM_WIN32
bool FileSel::ScanNetwork(Function<Array<NetNode> ()> fn)
{
	Progress pi(t_("Scanning network.."));
	loading_network = true;
	for(;;) {
		Ptr<FileSel> fs = this;
		if(CoWork::TrySchedule([=] {
			Array<NetNode> n = fn();
			GuiLock __;
			if(fs) {
				fs->netnode = pick(n);
				fs->loading_network = false;
			}
		}))
			break;
		if(pi.StepCanceled()) {
			SetDir("");
			return false;
		}
		Sleep(10);
	}
	for(;;) {
		if(pi.StepCanceled()) {
			SetDir("");
			return false;
		}
		if(!loading_network) {
			LoadNet();
			break;
		}
		Sleep(10);
	}
	return true;
}

bool FileSel::netroot_loaded;
Array<NetNode> FileSel::netroot;

void FileSel::ScanNetworkRoot()
{
	if(netroot_loaded) {
		netnode = clone(netroot);
		LoadNet();
		return;
	}
	netroot_loaded = ScanNetwork([] {
		Array<NetNode> n;
		n = NetNode::EnumRoot();
		n.Append(NetNode::EnumRemembered());
		return n;
	});
	netroot = clone(netnode);
}
#endif

void FileSel::SearchLoad()
{
	loaded = true;
	list.EndEdit();
	list.Clear();
	String d = GetDir();
#ifdef PLATFORM_WIN32
	if(d == "\\") {
		ScanNetworkRoot();
		return;
	}
#endif
	String emask = GetMask();
	if(!UPP::Load(list, d, emask, mode == SELECTDIR, WhenIcon, *filesystem, ~search, ~hidden, ~hiddenfiles, true)) {
		loaded = false;
		Exclamation(t_("[A3* Unable to read the directory !]&&") + DeQtf((String)~dir) + "&&" +
		            GetErrorMessage(GetLastError()));
		if(!basedir.IsEmpty() && String(~dir).IsEmpty()) {
			Break(IDCANCEL);
			return;
		}
		dir <<= olddir;
		olddir = Null;
		SearchLoad();
	}

	places.KillCursor();
	if(d.GetCount())
		places.FindSetCursor(d);
	hiddenfiles.Enable(!hidden);
	if(d.IsEmpty()) {
		if(filesystem->IsWin32()) {
			mkdir.Disable();
			list.Renaming(false);
		}
		dir <<= d;
		dirup.Disable();
	}
	else {
		dirup.Enable();
		mkdir.Enable();
		list.Renaming(true);
	}
	if(filesystem->IsPosix())
		if(d == "/" || !IsEmpty(basedir) && String(~dir).IsEmpty())
			dirup.Disable();
	if(filesystem->IsWin32())
		if(!IsEmpty(basedir) && String(~dir).IsEmpty())
			dirup.Disable();
	olddir = ~dir;
	if(olddir.GetCount() || basedir.GetCount())
		SortBy(list, ~sortby);
	Update();
#ifdef GUI_WIN
	if(!noexeicons)
		lazyicons.Start(list, d, WhenIcon);
#endif
	StartLI();
}

StaticMutex FileSel::li_mutex;
void      (*FileSel::li_current)(const String& path, Image& result);
String      FileSel::li_path;
Image       FileSel::li_result;
bool        FileSel::li_running;
int         FileSel::li_pos;

void FileSel::LIThread()
{
	String path;
	void (*li)(const String& path, Image& result);
	{
		Mutex::Lock __(li_mutex);
		path = li_path;
		li = li_current;
	}
	Image result;
	if(path.GetCount())
		li(path, result);
	if(!IsNull(result) && max(result.GetWidth(), result.GetHeight()) > DPI(16))
		result = Rescale(result, DPI(16), DPI(16));
	{
		Mutex::Lock __(li_mutex);
		li_result = result;
		li_running = false;
	}
}

String FileSel::LIPath()
{
	return li_pos >= 0 && li_pos < list.GetCount() ? FilePath(list.Get(li_pos).name) : Null;
}

void FileSel::DoLI()
{
	int start = msecs();
	for(;;) {
		for(;;) {
			bool done = false;
			String path = LIPath();
			if(IsNull(path))
				return;
			bool running;
			Image img;
			{
				Mutex::Lock __(li_mutex);
				running = li_running;
				if(!running) {
					done = li_path == path && li_current == WhenIconLazy;
					img = li_result;
				}
			}
			if(done) {
				if(li_pos < 0 || li_pos >= list.GetCount())
					return;
				if(!IsNull(img)) {
					const FileList::File& f = list.Get(li_pos);
					WhenIcon(f.isdir, f.name, img);
					if(f.hidden)
						img = Contrast(img, 200);
					list.SetIcon(li_pos, img);
				}
				li_pos++;
			}
			if(!running)
				break;
			Sleep(0);
			if(msecs(start) > 10 || Ctrl::IsWaitingEvent()) {
				ScheduleLI();
				return;
			}
		}

		String path = LIPath();
		if(IsNull(path))
			return;
		{
			Mutex::Lock __(li_mutex);
			if(!li_running) {
				li_current = WhenIconLazy;
				li_path = path;
				li_running = true;
				Thread::Start(callback(LIThread));
			}
		}
	}
}

void FileSel::StartLI()
{
	if(WhenIconLazy) {
		li_pos = 0;
		ScheduleLI();
	}
}

String TrimDot(String f) {
	int i = f.Find('.');
	if(i >= 0 && i == f.GetLength() - 1)
		f.Trim(i);
	return f;
}

void FileSel::AddName(Vector<String>& fn, String& f) {
	if(!f.IsEmpty()) {
		f = TrimDot(f);
		if(f[0] == '\"' && f.GetCount() > 2)
			f = f.Mid(1, f.GetCount() - 2);
		int q = f.ReverseFind('.');
		String typed_ext; // typed by user
		if(q >= 0)
			typed_ext = f.Mid(q + 1);
		if(q < 0 || // no extension
		   force_ext && allowed_ext.Find(typed_ext) < 0 ||
		   Filter(f.Mid(q + 1), // "(file.xxx)" should add extension too, allow just some
		                   [](int c) { return IsAlNum(c) || findarg(c, '_', '-') >= 0 ? 0 : c; }
		   ).GetCount()) {
			String t = GetMask();
			int q = t.Find('.');
			if(q >= 0 && IsAlNum(t[q + 1])) {
				int w = q + 2;
				while(IsAlNum(t[w]))
					w++;
				f << t.Mid(q, w - q);
			}
			else
			if(defext.GetCount())
				f << '.' << defext;
		}
		fn.Add(f);
	}
	f.Clear();
}

bool FileSel::IsLnkFile(const String& p)
{
	int l = p.GetLength() - 4;
	return l >= 0 && p[l] == '.' && ToLower(p[l + 1]) == 'l' && ToLower(p[l + 2]) == 'n' && ToLower(p[l + 3]) == 'k';
}

String FileSel::ResolveLnk(const String& name) const
{
#ifdef PLATFORM_WIN32
	if(IsLnkFile(name))
		return GetSymLinkPath(AppendFileName(GetDir(), name));
#endif
	return Null;
}

String FileSel::ResolveLnkDir(const String& name) const
{
#ifdef PLATFORM_WIN32
	String p = ResolveLnk(name);
	if(p.GetCount() && DirectoryExists(p))
		return p;
#endif
	return Null;
}

String FileSel::ResolveLnkFile(const String& name) const
{
#ifdef PLATFORM_WIN32
	String p = ResolveLnk(name);
	if(p.GetCount() && FileExists(p))
		return p;
#endif
	return Null;
}

void FileSel::Finish() {
	if(filesystem->IsWin32())
		if(GetDir().IsEmpty()) {
			file.Clear();
			return;
		}
	fn.Clear();
	if(mode == SELECTDIR) {
		String p = GetDir();
		if(list.IsSelection() && multi) {
			for(int i = 0; i < list.GetCount(); i++)
				if(list.IsSelected(i)) {
					const FileList::File& m = list[i];
					if(m.isdir)
						fn.Add(AppendFileName(p, m.name));
				#ifdef PLATFORM_WIN32
					else {
						String p = ResolveLnkDir(m.name);
						if(p.GetCount())
							fn.Add(p);
					}
				#endif
				}
		}
		else {
			String p = GetDir();
			if(list.GetCursor() >= 0) {
				const FileList::File& m = list[list.GetCursor()];
				if(m.isdir)
					p = AppendFileName(p, m.name);
			#ifdef PLATFORM_WIN32
				else {
					String pp = ResolveLnkDir(m.name);
					if(p.GetCount())
						p = pp;
				}
			#endif
			}
			fn.Add(p);
		}
		Break(IDOK);
		return;
	}
	String f = file.GetText().ToString();
	if(f.IsEmpty()) return;
	String o;
	if(mode == OPEN && IsMulti()) {
		for(const char *s = f; *s; s++) {
			if(*s == ' ')
				AddName(fn, o);
			else
			if(*s == '\"') {
				AddName(fn, o);
				s++;
				for(;;) {
					if(*s == '\0' || *s == '\"') {
						AddName(fn, o);
						break;
					}
					o.Cat(*s++);
				}
			}
			else
				o.Cat(*s);
		}
		AddName(fn, o);
	}
	else {
		o = f;
		AddName(fn, o);
	}
	if(!IsMulti() && fn.GetCount())
		fn.SetCount(1);
	String d = GetDir();
	String nonexist;
	int ne = 0;
	for(int i = 0; i < fn.GetCount(); i++) {
		String p = fn[i];
		if(!IsFullPath(p))
			p = NormalizePath(AppendFileName(d, fn[i]));
		Array<FileSystemInfo::FileInfo> ff = filesystem->Find(p, 1);
		p = DeQtf(p);
		if(!ff.IsEmpty() && ff[0].is_directory) {
			Exclamation(p + t_(" is directory."));
			return;
		}
		if(asking) {
			if(mode == SAVEAS) {
				if(!ff.IsEmpty() && !PromptOKCancel(p + t_(" already exists.&Do you want to continue ?")))
					return;
			}
			else
			if(ff.IsEmpty()) {
				if(ne) nonexist << '&';
				nonexist << p;
				ne++;
			}
		}
	}
	if(ne) {
		nonexist << (ne == 1 ? t_(" does not exist.") : t_("&do not exist."));
		if(!PromptOKCancel(nonexist + t_("&Do you want to continue ?")))
			return;
	}
	Break(IDOK);
}

bool FileSel::OpenItem() {
	fw.Clear();
	if(list.IsCursor()) {
	#ifdef PLATFORM_WIN32
		if(netnode.GetCount()) {
			SelectNet();
			return true;
		}
	#endif
		const FileList::File& m = list.Get(list.GetCursor());
		String path = AppendFileName(~dir, m.name);
	#ifdef PLATFORM_WIN32
		if(IsNull(dir) && m.name == t_("Network")) {
			ScanNetworkRoot();
			return true;
		}
		String p = ResolveLnkDir(m.name);
		if(p.GetCount()) {
			SetDir(p);
			return true;
		}
	#endif
		if(m.isdir) {
			SetDir(path);
			return true;
		}
	}
	if(mode != SELECTDIR)
		Finish();
	return false;
}

void FileSel::Open() {
	if(mode == SELECTDIR) {
	#ifdef PLATFORM_WIN32
		if(netnode.GetCount())
			return;
	#endif
		Finish();
		return;
	}
	if(list.HasFocus() || type.HasFocus()) {
		if(OpenItem()) list.SetCursor(0);
	}
	else
	if(list.IsSelection())
		for(int i = 0; i < list.GetCount(); i++) {
			const FileList::File& m = list[i];
			if(!m.isdir) Finish();
		}
	else
	if(file.HasFocus()) {
		String fn = file.GetText().ToString();
	#ifdef PLATFORM_WIN32
		if(fn[0] == '\\' && fn[1] == '\\') {
			FindFile ff(AppendFileName(fn, "*.*"));
			if(ff)
				SetDir(TrimDot(fn));
			return;
		}
	#endif
		if(fn == "." || fn == "..") {
			DirUp();
			return;
		}
		if(HasWildcards(fn)) {
			file.Clear();
			int q = FindIndex(mask, fn);
			if(q >= 0)
				type.SetIndex(q);
			else {
				type.Add(fn, t_("Custom file type (") + fn + ')');
				type.SetIndex(type.GetCount() - 1);
			}
			Load();
			return;
		}
		if(fn.Find('\"') < 0) {
			if(filesystem->IsWin32())
			{
				if(fn.GetLength() >= 2 && fn[1] == ':' && fn.GetLength() <= 3) {
					fn.Set(0, ToUpper(fn[0]));
					if(fn.GetLength() == 2)
						fn.Cat('\\');
					SetDir(fn);
					return;
				}
			}
			if(!IsFullPath(fn))
				fn = AppendFileName(~dir, fn);
			if(filesystem->IsWin32() && (!fn.IsEmpty() && (*fn.Last() == '\\' || *fn.Last() == '/'))
			|| filesystem->IsPosix() && (fn != "/" && (*fn.Last() == '\\' || *fn.Last() == '/')))
			{
				fn.Trim(fn.GetLength() - 1);
				SetDir(TrimDot(fn));
				return;
			}
			Array<FileSystemInfo::FileInfo> ff = filesystem->Find(fn, 1);
			if(!ff.IsEmpty()) {
				if(ff[0].is_directory) {
					SetDir(TrimDot(fn));
					return;
				}
				else {
					SetDir(TrimDot(GetFileFolder(fn)));
					file.SetText(GetFileName(fn).ToWString());
				}
			}
		}
		if(mode != SELECTDIR)
			Finish();
	}
	else
	if(mode == SAVEAS)
		Finish();
}

String DirectoryUp(String& dir, bool basedir)
{
	while(*dir.Last() == '\\' || *dir.Last() == '/')
		dir.Trim(dir.GetCount() - 1);
	String s = dir;
	String name;
#ifdef PLATFORM_WIN32
	if(s.GetLength() < 3 || s.GetLength() == 3 && s[1] == ':') {
		dir.Clear();
		name = s;
	}
	else
#endif
#ifdef PLATFORM_POSIX
	if(s != "/")
#endif
	{
#ifdef PLATFORM_WIN32
		int i = max(s.ReverseFind('/'), s.ReverseFind('\\'));
#endif
#ifdef PLATFORM_POSIX
		int i = s.ReverseFind('/');
#endif
		if(basedir)
			if(i < 0)
				dir.Clear();
			else {
				dir = s.Mid(0, i);
				name = s.Mid(i + 1);
			}
		else {
#ifdef PLATFORM_WIN32
			if(s.GetLength() && s[1] == ':') {
				if(i > 3) {
					dir = s.Mid(0, i);
					name = s.Mid(i + 1);
				}
				else {
					dir = s.Mid(0, 3);
					name = s.Mid(3);
				}
			}
			if(s.GetLength() && s[0] == DIR_SEP && s[1] == DIR_SEP) {
				if(i > 2) {
					dir = s.Mid(0, i);
					name = s.Mid(i + 1);
				}
				else {
					dir.Clear();
					name = s;
				}
			}
#endif
#ifdef PLATFORM_POSIX
			if(i == 0 && s.GetLength() > 1) {
				dir = "/";
				name = s.Mid(1);
			}
			else
			if(s.GetLength() && s[0] == '/' && s[1] != '/') {
				dir = s.Mid(0, i);
				name = s.Mid(i + 1);
			}
#endif
		}
	}
	return name;
}

void FileSel::DirUp()
{
#ifdef PLATFORM_WIN32
	if(netstack.GetCount()) {
		netstack.Drop();
		if(netstack.GetCount()) {
			netnode = netstack.Top().Enum();
			LoadNet();
		}
		netnode = NetNode::EnumRoot();
		return;
	}
	if(netnode.GetCount()) {
		netnode.Clear();
		SetDir("");
		return;
	}
#endif
	String s = ~dir;
	fw.Add(s);
	String name = DirectoryUp(s, !basedir.IsEmpty());
#ifdef PLATFORM_WIN32
	if(s[0] == '\\' && s[1] == '\\' && s.Find('\\', 2) < 0) {
		s.Clear();
		name.Clear();
	}
#endif
	SetDir(s);
	if(list.HasFocus())
		list.FindSetCursor(name);
}

void FileSel::MkDir() {
	if(String(~dir).IsEmpty() && basedir.IsEmpty()) return;
	String name, error;
	if(EditText(name, t_("New directory"), t_("Name")) && !name.IsEmpty()) {
		if(filesystem->CreateFolder(FilePath(name), error)) {
			Load();
			list.FindSetCursor(name);
		}
		else
			Exclamation(t_("[A3* Creating directory failed !&&]") + error);
	}
}

void FileSel::Reload()
{
	String fn = list.GetCurrentName();
	int a = list.GetScroll();
	SearchLoad();
	list.ScrollTo(a);
	list.FindSetCursor(fn);
}

void FileSel::Activate()
{
	if(loaded && !loading_network && GetDir() != "\\"
#ifdef PLATFORM_WIN32
	   && netnode.GetCount() == 0
#endif
	)
		Reload();
	TopWindow::Activate();
}

void FileSel::Skin()
{
	Reload();
}

bool FileSel::Key(dword key, int count) {
	switch(key) {
	case K_F9:
#ifdef PLATFORM_WIN32
		netroot_loaded = false;
#endif
		Reload();
		return true;
	case K_MOUSE_FORWARD:
		if(fw.GetCount())
			SetDir(fw.Pop());
		return true;
	case K_MOUSE_BACKWARD:
	case '.':
	case K_CTRL_UP:
		list.SetFocus();
		dirup.PseudoPush();
		return true;
	case K_F5:
		Reload();
		return true;
	case K_F6:
		list.StartEdit();
		return true;
	case K_F7:
		mkdir.PseudoPush();
		return true;
	case K_ENTER:
		if(mode == SELECTDIR && OpenItem())
			return true;
		break;
	case K_UP:
	case K_DOWN:
	case K_PAGEUP:
	case K_PAGEDOWN:
		list.SetFocus();
		return list.Key(key, count);
	}
	if(CharFilterDefaultToUpperAscii(key) || key == K_BACKSPACE)
		return search.Key(key, count);
	return TopWindow::Key(key, count);
}

void Catq(String& s, const String& fn) {
	if(!s.IsEmpty())
		s << ' ';
	if(fn.Find(' ') >= 0)
		s << '"' << fn << '"';
	else
		s << fn;
}

String FormatFileSize(int64 n)
{
	if(n < 10000)
		return Format("%d B  ", n);
	else
	if(n < 10000 * 1024)
		return Format("%d.%d K  ", n >> 10, (n & 1023) / 103);
	else
	if(n < I64(10000000) * 1024)
		return Format("%d.%d M  ", n >> 20, (n & 1023) / 103);
	else
		return Format("%d.%d G  ", n >> 30, (n & 1023) / 103);
}

void FileSel::Update() {
	String fn;
	if(list.IsSelection()) {
		for(int i = 0; i < list.GetCount(); i++)
			if(list.IsSelected(i))
				Catq(fn, list[i].name);
	}
	else
	if(list.IsCursor()) {
		const FileList::File& m = list[list.GetCursor()];
		if(!m.isdir)
			Catq(fn, m.name);
	}
	if(mode == OPEN)
		file <<= fn;
	filename = String();
	filesize = String();
	filetime = String();
	if(preview)
		*preview <<= Null;
	if(list.IsCursor()) {
		fn = list[list.GetCursor()].name;
		if(fn[1] == ':' && fn.GetLength() <= 3)
			filename = t_("  Drive");
		else {
			String path = FilePath(fn);
			Array<FileSystemInfo::FileInfo> ff = filesystem->Find(path, 1);
			if(!ff.IsEmpty()) {
				filename = "  " + fn;
				if(ff[0].is_directory)
					filesize = t_("Directory  ");
				else {
					if(mode == SAVEAS)
						file <<= fn;
					filesize = FormatFileSize(ff[0].length);
					if(preview)
						*preview <<= path;
				}
				Time tm = ff[0].last_write_time;
				filetime.AlignCenter();
				filetime = Format(tm);
			}
		}
	}
	else {
		int drives = 0;
		int dirs = 0;
		int files = 0;
		int64 length = 0;
		for(int i = 0; i < list.GetCount(); i++)
			if(!list.IsSelection() || list.IsSelected(i)) {
				const FileList::File& f = list[i];
				if(f.isdir)
#ifdef PLATFORM_WIN32
					(*f.name.Last() == ':' ? drives : dirs)++;
#else
					dirs++;
#endif
				else {
					files++;
					length += f.length;
				}
			}
		String s;
		if(drives)
			s << drives << t_(" drive(s)");
		else {
			if(dirs)
				s << dirs << t_(" folder(s)");
			if(files) {
				if(s.GetCount())
					s << ", ";
				s << files << t_(" file(s)");
			}
		}
		filename = "  " + s;
		if(length >= 0)
			filesize = FormatFileSize(length);
	}
	FileUpdate();
}

void FileSel::FileUpdate() {
	if(mode == SELECTDIR) {
		ok.Enable(!IsNull(~dir));
		return;
	}
	bool b = list.IsCursor() || !String(~file).IsEmpty();
	ok.Enable(b);
	if(mode != SAVEAS || list.IsCursor() && list[list.GetCursor()].isdir)
		ok.SetLabel(t_("Open"));
	else
		ok.SetLabel(t_("Save"));
}

void FileSel::Rename(const String& on, const String& nn) {
	if(on == nn) return;
#ifdef PLATFORM_WIN32
	if(FileMove(FilePath(on), FilePath(nn)))
#else
	if(rename(FilePath(on), FilePath(nn)) == 0)
#endif
	{
		Load();
		list.FindSetCursor(nn);
	}
	else
		Exclamation(t_("[A3* Renaming of file failed !&&]") + GetErrorMessage(GetLastError()));
}

void FileSel::Choice() {
	Load();
}

FileSel& FileSel::Type(const char *name, const char *ext) {
	type.Add(type.GetCount(), name);
	mask.Add(ext);
	if(IsNull(type))
		type.SetIndex(0);
	return *this;
}

FileSel& FileSel::Types(const char *d) {
	Vector<String> s = Split(d, '\n');
	for(int i = 0; i < s.GetCount(); i++) {
		Vector<String> h = Split(s[i], '\t');
		if(h.GetCount() == 2)
			Type(h[0], h[1]);
		if(h.GetCount() == 1)
			Type(h[0], h[0]);
	}
	return *this;
}

FileSel& FileSel::ClearTypes()
{
	type.Clear();
	mask.Clear();
	return *this;
}

FileSel& FileSel::ActiveType(int i)
{
	activetype.Clear();
	if(i >= 0 && i < type.GetCount())
		activetype = type.GetValue(i);
	return *this;
}

FileSel& FileSel::AllFilesType() {
	return Type(t_("All files"), "*.*");
}

struct FolderDisplay : public Display {
	virtual void Paint(Draw& w, const Rect& r, const Value& q,
	                   Color ink, Color paper, dword style) const;
};

Image GetDirIcon(const String& s)
{
	Image img;
#ifdef PLATFORM_X11
	img = GetFileIcon(GetFileFolder(s), GetFileName(s), true, false, false);
#endif
#ifdef PLATFORM_WIN32
	if((byte)*s.Last() == 255)
		img = CtrlImg::Network();
	else {
		int q = s.Find(0);
		if(q >= 0 && q + 1 < s.GetCount())
			img = GetDriveImage(s[q + 1]);
		else
			img = s.GetCount() ? GetFileIcon(s, false, true, false) : CtrlImg::Computer();
	}
#endif
	if(IsNull(img))
		img = CtrlImg::Dir();
	return img;
}

void FolderDisplay::Paint(Draw& w, const Rect& r, const Value& q,
                          Color ink, Color paper, dword style) const
{
	String s = q;
	w.DrawRect(r, paper);
	Image img = GetDirIcon(s);
	w.DrawImage(r.left, r.top + (r.Height() - img.GetSize().cx) / 2, img);
	w.DrawText(r.left + Zx(20),
	           r.top + (r.Height() - StdFont().Bold().Info().GetHeight()) / 2,
			   ~s, StdFont().Bold(), ink);
}

struct HomeDisplay : public Display {
	virtual void Paint(Draw& w, const Rect& r, const Value& q,
	                   Color ink, Color paper, dword style) const {
		w.DrawRect(r, paper);
		Image img = CtrlImg::Home();
		w.DrawImage(r.left, r.top + (r.Height() - img.GetSize().cx) / 2,
			        CtrlImg::Home());
		w.DrawText(r.left + Zx(20),
		           r.top + (r.Height() - StdFont().Bold().Info().GetHeight()) / 2,
				   String(q), StdFont().Bold(), ink);
	}
};

void FileSel::Set(const String& s)
{
	fn.Clear();
	if(IsFullPath(s)) {
		ActiveDir(GetFileFolder(s));
		fn.Add(GetFileName(s));
	}
	else
		fn.Add(s);
	bidname = true;
}

void FileSel::GoToPlace()
{
	if(places.IsCursor()) {
#ifdef PLATFORM_WIN32
		netnode.Clear();
#endif
		dir <<= places.GetKey();
		Load();
	}
}

Image SynthetisePathIcon(const String& path)
{
	Size isz = DPI(16, 16);
	ImagePainter iw(isz);
	iw.Clear(RGBAZero());
	int x = FoldHash(GetHashValue(path));
	auto cl = [](int x) { return 100 + (x & 127); };
	Color c = Color(cl(x), cl(x >> 7), cl(x >> 14));
	iw.Circle(DPI(8), DPI(8), DPI(7))
	  .Fill(DPI(4), DPI(4), Blend(White(), c, 80), DPI(8), DPI(8), DPI(7), c)
	  .Stroke(1, Gray());
	WString s = GetFileTitle(path).ToWString();
	if(s.GetCount()) {
		s = s.Mid(0, 1);
		Font fnt = Serif(DPI(12));
		Size tsz = GetTextSize(s, fnt);
		iw.DrawText((isz.cx - tsz.cx) / 2, (isz.cy - tsz.cy) / 2, s, fnt, Black());
	}
	return iw;
}

String PathName(const String& path)
{
	int cx = Zx(100);
	String p = path;
	p.Replace("\\", "/");
	if(GetTextSize(p, StdFont()).cx < cx) return p;
	cx -= GetTextSize("..", StdFont()).cx;
	while(GetTextSize(p, StdFont()).cx > cx && p.GetCount() > 1)
		p = p.Mid(1);
	return ".." + p;
}

bool FileSel::Execute(int _mode) {
	mode = _mode;

	int fixed_places = places.GetCount();

	Vector<String> glru;
	LoadFromGlobal(glru, "GlobalFileSelectorLRU");
	if(glru.GetCount()) {
		AddPlaceSeparator();
		for(const String& path : glru)
			AddPlace(path, SynthetisePathIcon(path), PathName(path), "PLACES:FOLDER");
	}

	int system_row = -1;
	for(int i = places.GetCount() - 1; i >= 0; i--) {
		if(places.Get(i, 3) == "PLACES:SYSTEM") {
			system_row = i;
			places.Remove(i);
		}
	}
	AddSystemPlaces(system_row);
		
	if(mode == SELECTDIR) {
		if(!fn.IsEmpty()) {
			String h = ~dir;
			dir <<= NormalizePath(fn[0]);
			if(!DirectoryExists(~~dir))
				dir <<= h;
		}
		type.Hide();
		type_lbl.Hide();
		file.Hide();
		file_lbl.Hide();
		sortby.Hide();
		sort_lbl.Hide();
		ok.SetLabel(t_("&Select"));
		Logc p = filename.GetPos().y;
		int q = ok.GetPos().y.GetA() + ok.GetPos().y.GetB() + DPI(16);
		p.SetA(q);
		filename.SetPosY(p);
		filesize.SetPosY(p);
		filetime.SetPosY(p);
		p = splitter.Ctrl::GetPos().y;
		p.SetB(q + Zy(28));
		splitter.SetPosY(p);
		LogPos ps = search.GetPos();
		LogPos pl = sort_lbl.GetPos();
		pl.x.SetB(ps.x.GetB());
		pl.y.SetA(ok.GetPos().y.GetA());
		pl.y.SetB(ps.y.GetB());
		search.SetPos(pl);
		bidname = false;
	}
	else {
		for(Ctrl *q = GetFirstChild(); q; q = q->GetNext())
			if(q != &mkdir)
				q->Show();
		Rect r = GetRect();
		CtrlLayout(*this);
		ArrangeOKCancel(ok, cancel);
		SetRect(r);
	}

	if(file_ctrl) {
		LogPos sp = search.GetPos();
		LogPos fp = file.GetPos();
		file.HSizePos(fp.x.GetA(), 2 * sp.x.GetA() + file_ctrl_cx);
		AddChild(file_ctrl, &file);
		file_ctrl->BottomPos(fp.y.GetA(), fp.y.GetB()).RightPos(sp.x.GetA(), file_ctrl_cx);
	}

	readonly.Show(rdonly && mode == OPEN);
	list.Multi(multi && (mode == OPEN || mode == SELECTDIR));
	list.SelectDir(multi && mode == SELECTDIR);
	dir.ClearList();
	file <<= Null;
	if(basedir.IsEmpty()) {
		dir.Add(GetHomeDirectory());
	#ifdef PLATFORM_POSIX
		Array<FileSystemInfo::FileInfo> root = filesystem->Find("/media/*");
		dir.Add(GetDesktopFolder());
		dir.Add("/");
		for(int i = 0; i < root.GetCount(); i++) {
			String ugly = root[i].filename;
			if(ugly[0] != '.') {
				dir.Add("/media/" + root[i].filename);
			}
		}
	#else
		dir.Add(GetDesktopFolder());
		Array<FileSystemInfo::FileInfo> root = filesystem->Find(Null);
		for(int i = 0; i < root.GetCount(); i++) {
			String ugly = root[i].filename;
			if(ugly != "A:\\" && ugly != "B:\\") {
				ugly.Cat('\0');
				ugly.Cat(root[i].root_style);
				dir.Add(root[i].filename, ugly);
			}
		}
		if(filesystem == &StdFileSystemInfo())
			dir.Add("\\", String(t_("Network")) + String(0, 1) + "\xff");
	#endif
		if(filesystem->IsPosix() && String(~dir).IsEmpty())
			dir <<= GetHomeDirectory();
		if(lru.GetCount())
			dir.AddSeparator();
		for(int i = 0; i < lru.GetCount(); i++)
			if(IsFullPath(lru[i]) && filesystem->FolderExists(lru[i]))
				dir.Add(lru[i]);
		dir.SetDisplay(Single<FolderDisplay>(), max(16, GetStdFontCyA()));
	}
	else {
		dir.SetDisplay(Single<HomeDisplay>(), max(16, GetStdFontCyA()));
		if(filesystem->IsPosix()) {
			if(String(~dir)[0] == '/')
				dir <<= "";
		}
	}
	Rect lr = splitter.GetRect();
	Rect dr = dir.GetRect();
	int dp = max(20, dir.Ctrl::GetPos().y.GetB());
	int px = GetSize().cx - lr.right;
	if(mkdir.IsShown()) {
		mkdir.RightPos(px, dp).TopPos(dr.top, dp);
		dirup.RightPos(px + dp, dp).TopPos(dr.top, dp);
		px += 2 * dp;
	}
	else {
		dirup.RightPos(px, dp).TopPos(dr.top, dp);
		px += dp;
	}
	dir.HSizePos(dr.left, px + 4);
	if(preselect.GetCount()) {
		for(int i = 0; i < mask.GetCount(); i++) {
			if(PatternMatchMulti(mask[i], preselect)) {
				ActiveType(i);
				break;
			}
		}
	}
	int q = type.FindValue(activetype);
	if(q >= 0)
		type <<= q;
	else
	if(type.GetCount())
		type.SetIndex(0);
	int dlc = type.GetCount();
	Load();
	ActiveFocus(file.IsEditable() ? (Ctrl&)file : (Ctrl&)list);
	if(bidname) {
		String s;
		for(int i = 0; i < fn.GetCount(); i++)
			Catq(s, fn[i]);
		file <<= s;
		ActiveFocus(file);
		bidname = false;
	}
	list.SetSbPos(lastsby);
	if(preselect.GetCount()) {
		if(mode == SAVEAS)
			file <<= preselect;
		else
			for(int i = 0; i < list.GetCount(); i++)
			    if(list[i].name == preselect) {
					list.SetCursor(i);
					ActiveFocus(list);
					break;
				}
		preselect.Clear();
	}
	if(default_name.GetCount() && mode == SAVEAS)
		file <<= default_name;

	force_ext = mode == SAVEAS;
	allowed_ext.Clear();
	for(String mm : mask) {
		if(!force_ext)
			break;
		for(String m : Split(mm, ' ')) {
			if(!force_ext)
				break;
			int q = m.Find('.');
			if(q < 0)
				force_ext = false;
			else {
				m = m.Mid(q + 1);
				if(m.Find('*') >= 0 || m.Find('?') >= 0)
					force_ext = false;
				else
					allowed_ext.FindAdd(m);
			}
		}
	}

	FileUpdate();
	Update();
	int c = TopWindow::Run(appmodal);
	TopWindow::Close();
	lastsby = list.GetSbPos();
	if(IsNumber(~type)) {
		int ti = ~type;
		type.Trim(dlc);
		if(ti >= 0 && ti < type.GetCount())
			activetype = type.GetValue(ti);
	}
	else
		type.Trim(dlc);

	String d = ~dir;
	if(filesystem->IsWin32() && d.GetLength() == 3 && d[1] == ':' ||
	   filesystem->IsPosix() && d == "/" ||
	   !IsFullPath(d))
		d.Clear();

	if(d.GetCount()) {
		LruAdd(lru, d, 12);
		Vector<String> glru;
		d = NormalizePath(d);
		Index<String> h;
		for(int i = 0; i < fixed_places; i++)
			h.Add(NormalizePath(~places.Get(i, 0)));
		if(h.Find(d) < 0) {
			LoadFromGlobal(glru, "GlobalFileSelectorLRU");
			LruAdd(glru, d, 12);
			StoreToGlobal(glru, "GlobalFileSelectorLRU");
		}
	}

	places.SetCount(fixed_places);

	return c == IDOK;
}

bool FileSel::ExecuteOpen(const char *title) {
	Title(title ? title : t_("Open"));
	return Execute(OPEN);
}

bool FileSel::ExecuteSaveAs(const char *title) {
	Title(title ? title : t_("Save as"));
	ok.SetLabel(t_("Save"));
	return Execute(SAVEAS);
}

bool FileSel::ExecuteSelectDir(const char *title)
{
	Title(title ? title : t_("Select directory"));
	return Execute(SELECTDIR);
}

void FileSel::Serialize(Stream& s) {
#ifdef PLATFORM_WIN32
	if(s.IsLoading()) {
		netnode.Clear();
		netstack.Clear();
	}
#endif
	int version = 10;
	s / version;
	String ad = ~dir;
	int dummy = 0;
	if(version < 10)
		s / dummy;
	else
		s % activetype;
	s % ad;
	dir <<= ad;
	if(version < 1) {
		String n = fn.At(0);
		s % n;
		fn.At(0) = n;
	}
	else {
		if(version < 4)
			s % fn;
		else {
			Vector<String> __;
			s % __;
		}
	}
	if(version >= 2) {
		SerializePlacement(s);
		list.SerializeSettings(s);
	}
	if(version >= 3) {
		s % lastsby;
	}
	if(version >= 4) {
		s % lru;
	}
	if(version >= 5) {
		s % sortby;
	}
	if(version >= 6) {
		if(version >= 9)
			s % splitter;
		else {
			Splitter dummy;
			s % dummy;
		}
	}
	if(version >= 7) {
		s % hidden;
	}
	if(version >= 8) {
		s % hiddenfiles;
	}
}

String FileSel::GetFile(int i) const {
	String p;
	if(i >= 0 && i < fn.GetCount()) {
		p = fn[i];
		if(!IsFullPath(p))
			p = AppendFileName(dir.GetData(), p);
	}
#ifdef PLATFORM_WIN32
	if(IsLnkFile(p))
		p = Nvl(GetSymLinkPath(p), p);
#endif
	return p;
}

void FileSel::SyncSplitter()
{
	splitter.Clear();
	if(places.GetCount() && basedir.IsEmpty())
		splitter.Add(places);
	splitter.Add(list);
	if(preview)
		splitter.Add(*preview);
}

FileSel& FileSel::PreSelect(const String& path)
{
	ActiveDir(GetFileFolder(path));
	preselect = GetFileName(path);
	return *this;
}

void FileSel::InitSplitter()
{
	int n = splitter.GetCount();
	int i = 0;
	if(places.GetCount())
		splitter.SetPos(2000, i++);
	splitter.SetPos(10000 - 2000 * (n - 1), i);
}

FileSel& FileSel::Preview(Ctrl& ctrl)
{
	if(!preview) {
		Size sz = GetRect().GetSize();
		sz.cx = 5 * sz.cx / 3;
		SetRect(sz);
	}
	preview = &ctrl;
	SyncSplitter();
	InitSplitter();
	return *this;
}

FileSel& FileSel::Preview(const Display& d)
{
	preview_display.SetDisplay(d);
	return Preview(preview_display);
}

void FileSel::AddPlaceRaw(const String& path, const Image& m, const String& name, const char* group, int row)
{
	if(path.GetCount()) {
		row = row < 0 ? places.GetCount() : row;
		places.Insert(row);
		places.Set(row, 0, path);
		places.Set(row, 1, m);
		places.Set(row, 2, name);
		places.Set(row, 3, group);
		places.SetLineCy(row, max(m.GetSize().cy + 4, GetStdFontCy() + 4));
		SyncSplitter();
		InitSplitter();
	}
}

FileSel& FileSel::AddPlace(const String& path, const Image& m, const String& name, const char* group, int row)
{
	if(path.GetCount())
		AddPlaceRaw(NormalizePath(path), m, name, group, row);
	return *this;
}

FileSel& FileSel::AddPlace(const String& path, const String& name, const char* group, int row)
{
#ifdef GUI_COCOA
	return AddPlace(path, GetFileIcon(NormalizePath(path), true, false, false), name, group, row);
#else
	return AddPlace(path, GetDirIcon(NormalizePath(path)), name, group, row);
#endif
}

FileSel& FileSel::AddPlace(const String& path, const char* group, int row)
{
	return AddPlace(path, GetFileTitle(path), group, row);
}

FileSel& FileSel::AddPlaceSeparator()
{
	places.AddSeparator();
	SyncSplitter();
	InitSplitter();
	return *this;
}

FileSel& FileSel::ClearPlaces()
{
	places.Clear();
	SyncSplitter();
	return *this;
}

void FileSel::AddSystemPlaces(int row)
{
	row = row < 0 ? places.GetCount() : row;
	Array<FileSystemInfo::FileInfo> root;
#ifdef PLATFORM_WIN32
	root = filesystem->Find(Null);
	for(int i = 0; i < root.GetCount(); i++) {
		String desc = root[i].root_desc;
		String n = root[i].filename;
		if(n != "A:\\" && n != "B:\\") {
		#ifdef PLATFORM_WIN32
			if(*n.Last() == '\\')
				n.Trim(n.GetCount() - 1);
		#endif
			if(desc.GetCount() == 0)
			    desc << " " << t_("Local Disk");
			desc << " (" << n << ")";
			AddPlace(root[i].filename, GetDriveImage(root[i].root_style), desc, "PLACES:SYSTEM", row++);
		}
	}

	if(GetSystemMetrics(SM_REMOTESESSION))
		for(int drive = 'A'; drive < 'Z'; drive++) {
			String path = Format("\\\\tsclient\\%c", drive);
			if(FindFile(path + "\\*.*"))
				AddPlace(path, Format(t_("%c on client"), drive), "PLACES:SYSTEM", row++);
		}
#endif

#ifdef PLATFORM_POSIX
	root = filesystem->Find("/media/*");
	for(int i = 0; i < root.GetCount(); i++) {
		String fn = root[i].filename;
		if(*fn != '.' && fn.Find("floppy") < 0)
			AddPlace("/media/" + fn, fn, "PLACES:SYSTEM", row++);
	}
	AddPlace("/", t_("Computer"), "PLACES:SYSTEM", row++);
#endif
}

FileSel& FileSel::AddStandardPlaces()
{
	AddPlace(GetHomeDirectory(), t_("Home"), "PLACES:FOLDER");
	AddPlace(GetDesktopFolder(), t_("Desktop"), "PLACES:FOLDER");
	AddPlace(GetMusicFolder(), t_("Music"), "PLACES:FOLDER");
	AddPlace(GetPicturesFolder(), t_("Pictures"), "PLACES:FOLDER");
	AddPlace(GetVideoFolder(), t_("Videos"), "PLACES:FOLDER");
	AddPlace(GetDocumentsFolder(), t_("Documents"), "PLACES:FOLDER");
	AddPlace(GetDownloadFolder(), t_("Downloads"), "PLACES:FOLDER");
	AddPlaceSeparator();
	AddSystemPlaces();
#ifdef PLATFORM_WIN32
	AddPlaceSeparator();
	AddPlaceRaw("\\", CtrlImg::Network(), t_("Network"), "PLACES:NETWORK");
#endif
	return *this;
}

struct DisplayPlace : Display {
	virtual void Paint(Draw& w, const Rect& r, const Value& q, Color ink, Color paper,
	                   dword style) const
	{
		w.DrawRect(r, paper);
		ValueArray va = q;
		Image m = va[0];
		String txt = va[1];
		Size isz = m.GetSize();
		w.DrawImage(r.left, r.top + (r.Height() - isz.cy) / 2, m);
		w.DrawText(r.left + isz.cx + 2, r.top + (r.Height() - GetStdFontCy()) / 2, txt,
		           StdFont(), ink);
	}
	virtual Size GetStdSize(const Value& q) const {
		ValueArray va = q;
		Image m = va[0];
		String txt = va[1];
		Size isz = m.GetSize();
		return Size(isz.cx + GetTextSize(txt, StdFont()).cx + 2, max(isz.cy, GetStdFontCy()));
	}
};

FileSel::FileSel()
{
	loaded = false;
	filesystem = &StdFileSystemInfo();
	CtrlLayout(*this);
	ArrangeOKCancel(ok, cancel);
	Acceptor(ok, IDOK); ok.Ok();
	Rejector(cancel, IDCANCEL); cancel.Cancel();
	list.IconWidth(DPI(16)).Renaming().Columns(3).ClickKill();
	list.WhenLeftDouble = THISBACK(OpenItem2);
	dirup <<= THISBACK(DirUp);
	Add(dirup);
	sortby <<= THISBACK(SearchLoad);
	Add(sortby);
	hidden <<= THISBACK(SearchLoad);
	Add(hidden);
	hiddenfiles <<= THISBACK(SearchLoad);
	Add(hiddenfiles);
	mkdir <<= THISBACK(MkDir);
	Add(mkdir);

	ok <<= THISBACK(Open);
	list <<= THISBACK(Update);
	file <<= THISBACK(FileUpdate);
	list.WhenRename = THISBACK(Rename);
	Sizeable();
	dirup.SetImage(CtrlImg::DirUp()).NoWantFocus();
	dirup.Tip(t_("Dir up") + String(" (Ctrl+Up)"));
	mkdir.SetImage(CtrlImg::MkDir()).NoWantFocus();
	mkdir.Tip(t_("Create directory") + String(" (F7)"));
	type <<= THISBACK(Load);
	for(int pass = 0; pass < 2; pass++) {
		int k = pass * FILELISTSORT_DESCENDING;
		String d = pass ? t_(" descending") : "";
		sortby.Add(FILELISTSORT_NAME|k, t_("Name") + d);
		sortby.Add(FILELISTSORT_EXT|k, t_("Extension") + d);
		sortby.Add(FILELISTSORT_TIME|k, t_("Last change") + d);
		sortby.Add(FILELISTSORT_SIZE|k, t_("Size") + d);
	}
	sortby <<= FILELISTSORT_NAME;

	search.NullText(t_("Search"), StdFont().Italic(), SColorDisabled());
	search.SetFilter(CharFilterDefaultToUpperAscii);
	search <<= THISBACK(SearchLoad);

	filename.SetFont(StdFont());
	filename.SetFrame(ViewFrame());
	filesize.SetFont(StdFont()).SetAlign(ALIGN_RIGHT);
	filesize.SetFrame(ViewFrame());
	filetime.SetFont(StdFont());
	filetime.SetFrame(ViewFrame());

	dir <<= THISBACK(Choice);
	dir.DisplayAll();
	dir.SetDropLines(24);

	readonly.Hide();

	lastsby = 0;

	asking = true;
	rdonly = false;
	multi = false;
	bidname = false;
	appmodal = true;
	noexeicons = false;

	AddChildBefore(GetFirstChild(), &sizegrip);

	preview = NULL;
	preview_display.SetFrame(FieldFrame());

	SyncSplitter();

	BackPaintHint();
	
	places.AddKey();
	places.AddColumn().AddIndex().SetDisplay(Single<DisplayPlace>());
	places.AddIndex();
	places.NoHeader().NoGrid();
	places.WhenLeftClick = THISBACK(GoToPlace);
	places.NoWantFocus();
	
	list.NoRoundSize();

#ifdef PLATFORM_WIN32
	int icx = GetFileIcon(GetHomeDirectory(), true, false, false).GetSize().cx;
	if(icx)
		list.IconWidth(icx);
#endif

	AddStandardPlaces();
	
	list.AutoHideSb();
	places.AutoHideSb();

	WhenIconLazy = NULL;
}

FileSel::~FileSel() {}

}
