#include 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 major; Index minor; VectorMap 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().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 WhenIcon, FileSystemInfo& filesystem, const String& search, bool hidden, bool hiddenfiles, bool lazyicons) { if(dir.IsEmpty()) { Array 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 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 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 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 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 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 ()> fn) { Progress pi(t_("Scanning network..")); loading_network = true; for(;;) { Ptr fs = this; if(CoWork::TrySchedule([=] { Array 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 FileSel::netroot; void FileSel::ScanNetworkRoot() { if(netroot_loaded) { netnode = clone(netroot); LoadNet(); return; } netroot_loaded = ScanNetwork([] { Array 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& 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 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 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 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 s = Split(d, '\n'); for(int i = 0; i < s.GetCount(); i++) { Vector 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 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() + Zy(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 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 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(), max(16, GetStdFontCyA())); } else { dir.SetDisplay(Single(), 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 glru; d = NormalizePath(d); Index 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 __; 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 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()); 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() {} }