1 #include "ide.h"
2 
3 #ifdef _DEBUG
4 #define LSLOW()    // Sleep(20) // Simulate HD seeks to test package cache
5 #else
6 #define LSLOW()
7 #endif
8 
PackageMenu(Bar & menu)9 void SelectPackageDlg::PackageMenu(Bar& menu)
10 {
11 	bool b = GetCurrentName().GetCount();
12 	menu.Add("New package..", [=] { OnNew(); });
13 	menu.Separator();
14 	menu.Add(b, "Duplicate package..", [=] { RenamePackage(true); });
15 	menu.Add(b, "Rename package..", [=] { RenamePackage(false); });
16 	menu.Add(b, "Copy package to..", [=] { MovePackage(true); });
17 	menu.Add(b, "Move package to..", [=] { MovePackage(false); });
18 	menu.Add(b, "Delete package..", [=] { DeletePackage(); });
19 }
20 
RenamePackageFs(const String & upp,const String & npf,const String & nupp,bool copy)21 bool RenamePackageFs(const String& upp, const String& npf, const String& nupp, bool copy)
22 {
23 	String pf = GetFileFolder(upp);
24 	String temp_pf = AppendFileName(GetFileFolder(pf), AsString(Random()) + AsString(Random()));
25 	if(!FileMove(pf, temp_pf)) {
26 		Exclamation("Operation has failed.");
27 		return false;
28 	}
29 	RealizePath(GetFileFolder(npf));
30 	if(copy) {
31 		bool b = CopyFolder(npf, temp_pf);
32 		FileMove(temp_pf, pf);
33 		if(!b) {
34 			FileMove(temp_pf, pf);
35 			DeleteFolderDeep(npf);
36 			Exclamation("Duplicating package folder has failed.");
37 			return false;
38 		}
39 	}
40 	else
41 	if(!FileMove(temp_pf, npf)) {
42 		FileMove(temp_pf, pf);
43 		Exclamation("Renaming package folder has failed.");
44 		return false;
45 	}
46 	if(!FileMove(npf + "/" + GetFileName(upp), nupp)) {
47 		FileMove(npf, pf);
48 		Exclamation("Renaming .upp file has failed.");
49 		return false;
50 	}
51 	return true;
52 }
53 
RenamePackageFs(const String & upp,const String & newname,bool duplicate)54 bool RenamePackageFs(const String& upp, const String& newname, bool duplicate)
55 {
56 	if(IsNull(newname)) {
57 		Exclamation("Wrong name.");
58 		return false;
59 	}
60 	String npf = AppendFileName(GetPackagePathNest(GetFileFolder(upp)), newname);
61 	String nupp = npf + "/" + GetFileName(newname) + ".upp";
62 
63 	if(FileExists(nupp)) {
64 		Exclamation("Package [* \1" + newname + "\1] already exists!");
65 		return false;
66 	}
67 
68 	return RenamePackageFs(upp, npf, nupp, duplicate);
69 }
70 
RenamePackage(bool duplicate)71 void SelectPackageDlg::RenamePackage(bool duplicate)
72 {
73 	String n = GetCurrentName();
74 	if(IsNull(n))
75 		return;
76 again:
77 	if(!EditText(n, duplicate ? "Duplicate package:" : "Rename package", "Name", FilterPackageName))
78 		return;
79 	if(!RenamePackageFs(PackagePath(GetCurrentName()), n, duplicate))
80 		goto again;
81 	search <<= Null;
82 	Load(n);
83 	alist.ScrollIntoCursor();
84 }
85 
MovePackage(bool copy)86 void SelectPackageDlg::MovePackage(bool copy)
87 {
88 	WithMoveCopyPackageLayout<TopWindow> dlg;
89 	CtrlLayoutOKCancel(dlg, copy ? "Copy package to" : "Move package to");
90 
91 	String d0;
92 	for(int pass = 0; pass < 2; pass++) {
93 		Index<String> udir;
94 		FindFile ff(ConfigFile("*.var"));
95 		while(ff) {
96 			if(int(GetFileTitle(ff.GetName()) != base.GetKey()) == pass) {
97 				VectorMap<String, String> var;
98 				LoadVarFile(ff.GetPath(), var);
99 				for(String d : Split(var.Get("UPP", ""), ';'))
100 					if(DirectoryExists(d)) {
101 						udir.FindAdd(d);
102 						d0 = Nvl(d0, d);
103 					}
104 			}
105 			ff.Next();
106 		}
107 
108 		Vector<String> sd = pick(udir.PickKeys());
109 		Sort(sd, [](const String& a, const String& b) { return ToUpper(a) < ToUpper(b); });
110 		for(String d : sd)
111 			dlg.dir.AddList(d);
112 	}
113 
114 	dlg.dir <<= d0;
115 	dlg.select.SetImage(CtrlImg::Dir());
116 	dlg.select << [&] { String d = SelectDirectory(); if(d.GetCount()) dlg.dir <<= d; };
117 
118 	dlg.name <<= GetCurrentName();
119 
120 again:
121 	if(dlg.Run() != IDOK)
122 		return;
123 
124 	String dir = ~dlg.dir;
125 	if(!DirectoryExists(dir)) {
126 		Exclamation("Invalid target directory!");
127 		goto again;
128 	}
129 	String pkg = AppendFileName(dir, ~~dlg.name);
130 	if(DirectoryExists(pkg)) {
131 		Exclamation("Target package directory already exists!");
132 		goto again;
133 	}
134 	if(FileExists(pkg)) {
135 		Exclamation("Invalid target package directory - it is a file!");
136 		goto again;
137 	}
138 
139 	if(!RenamePackageFs(PackagePath(GetCurrentName()), pkg, pkg + "/" + GetFileName(~~dlg.name) + ".upp", copy))
140 		goto again;
141 
142 	Load(~~dlg.name);
143 }
144 
DeletePackage()145 void SelectPackageDlg::DeletePackage()
146 {
147 	String n = GetCurrentName();
148 	if(IsNull(n))
149 		return;
150 	String pp = GetFileFolder(PackagePath(GetCurrentName()));
151 	if(!DirectoryExists(pp)) {
152 		Exclamation("Directory does not exist!");
153 		return;
154 	}
155 	if(!PromptYesNo("Do you really want to delete package [* \1" + GetCurrentName() + "\1]?&&"
156 	                "[/ Warning:] [* Package will not be removed "
157 	                "from uses of any other package!]"))
158 		return;
159 	if(!PromptYesNo("This operation is irreversible.&Do you really want to proceed?"))
160 		return;
161 	DeleteFolderDeep(pp);
162 	Load();
163 }
164 
SelectPackageDlg(const char * title,bool selectvars_,bool main)165 SelectPackageDlg::SelectPackageDlg(const char *title, bool selectvars_, bool main)
166 : selectvars(selectvars_)
167 {
168 	CtrlLayoutOKCancel(*this, title);
169 	Sizeable().Zoomable();
170 	Icon(IdeImg::MainPackage(), IdeImg::PackageLarge());
171 	base.AutoHideSb();
172 	base.NoGrid();
173 	base.AddColumn("Assembly");
174 	base.WhenCursor = THISBACK(OnBase);
175 	base.WhenBar = THISBACK(ToolBase);
176 	base.WhenLeftDouble = THISBACK(OnBaseEdit);
177 	ok.WhenAction = clist.WhenLeftDouble = alist.WhenLeftDouble = THISBACK(OnOK);
178 	cancel.WhenAction = WhenClose = THISBACK(OnCancel);
179 	clist.Columns(4);
180 	clist.WhenEnterItem = clist.WhenKillCursor = THISBACK(ListCursor);
181 	alist.AddColumn("Package").Add(3);
182 	alist.AddColumn("Nest");
183 	alist.AddColumn("Description");
184 	alist.AddIndex();
185 	alist.ColumnWidths("108 79 317");
186 	alist.WhenCursor = THISBACK(ListCursor);
187 	alist.EvenRowColor();
188 	alist.SetLineCy(max(Zy(16), Draw::GetStdFontCy()));
189 	list.Add(clist.SizePos());
190 	list.Add(alist.SizePos());
191 
192 	parent.Add(list.SizePos());
193 	parent.AddFrame(splitter.Left(base, Zx(170)));
194 	if (!selectvars)
195 		splitter.Hide();
196 
197 	newu <<= THISBACK(OnNew);
198 	filter <<= THISBACK(OnFilter);
199 	filter.Add(MAIN|FIRST, "Main packages of first nest");
200 	filter.Add(MAIN, "All main packages");
201 	filter.Add(FIRST, "All packages of first nest");
202 	filter.Add(0, "All packages");
203 	filter <<= main ? MAIN|FIRST : 0;
204 	progress.Hide();
205 	brief <<= THISBACK(SyncBrief);
206 	search.NullText("Search (Ctrl+K)", StdFont().Italic(), SColorDisabled());
207 	search << [=] { SyncList(Null); };
208 	search.SetFilter(CharFilterDefaultToUpperAscii);
209 	SyncBrief();
210 	description.NullText("Package description (Alt+Enter)", StdFont().Italic(), SColorDisabled());
211 	description <<= THISBACK(ChangeDescription);
212 	ActiveFocus(brief ? (Ctrl&)clist : (Ctrl&)alist);
213 	clist.BackPaintHint();
214 	alist.BackPaintHint();
215 	base.BackPaintHint();
216 	loadi = 0;
217 	loading = false;
218 	clist.WhenBar = alist.WhenBar = THISBACK(PackageMenu);
219 
220 	help << [&] { LaunchWebBrowser("https://www.ultimatepp.org/app$ide$PackagesAssembliesAndNests$en-us.html"); };
221 }
222 
Key(dword key,int count)223 bool SelectPackageDlg::Key(dword key, int count)
224 {
225 	if(key == K_ALT_ENTER) {
226 		ChangeDescription();
227 		return true;
228 	}
229 	else if(key == K_CTRL_K) {
230 		search.SetFocus();
231 		return true;
232 	}
233 	if((clist.HasFocus() || alist.HasFocus()) && search.Key(key, count))
234 		return true;
235 	return TopWindow::Key(key, count);
236 }
237 
Serialize(Stream & s)238 void SelectPackageDlg::Serialize(Stream& s)
239 {
240 	SerializePlacement(s);
241 	s % brief;
242 }
243 
GetCurrentName()244 String SelectPackageDlg::GetCurrentName()
245 {
246 	if(clist.IsShown())
247 		return clist.GetCurrentName();
248 	else
249 	if(alist.IsCursor())
250 		return alist.Get(0);
251 	return Null;
252 }
253 
GetCurrentIndex()254 int   SelectPackageDlg::GetCurrentIndex()
255 {
256 	String s = GetCurrentName();
257 	for(int i = 0; i < packages.GetCount(); i++)
258 		if(packages[i].package == s)
259 			return i;
260 	return -1;
261 }
262 
ChangeDescription()263 void SelectPackageDlg::ChangeDescription()
264 {
265 	int ii = GetCurrentIndex();
266 	if(ii >= 0 && ii < packages.GetCount()) {
267 		PkInfo& p = packages[ii];
268 		WithDescriptionLayout<TopWindow> dlg;
269 		CtrlLayoutOKCancel(dlg, "Package description");
270 		String pp = PackagePath(p.package);
271 		Package pkg;
272 		if(!pkg.Load(pp)) {
273 			Exclamation("Package does not exist.");
274 			return;
275 		}
276 		dlg.text <<= pkg.description;
277 		if(dlg.Run() != IDOK)
278 			return;
279 		pkg.description = ~dlg.text;
280 		pkg.Save(pp);
281 		p.description = description <<= ~dlg.text;
282 		if(alist.IsCursor())
283 			alist.Set(2, ~dlg.text);
284 	}
285 }
286 
ListCursor()287 void SelectPackageDlg::ListCursor()
288 {
289 	int c = GetCurrentIndex();
290 	if(c >= 0 && c < packages.GetCount()) {
291 		String pp = PackagePath(GetCurrentName());
292 		Package pkg;
293 		pkg.Load(pp);
294 		description <<= pkg.description;
295 	}
296 	else
297 		description <<= Null;
298 }
299 
SyncBrief()300 void SelectPackageDlg::SyncBrief()
301 {
302 	bool b = brief;
303 	alist.Show(!b);
304 	clist.Show(b);
305 }
306 
Run(String startwith)307 String SelectPackageDlg::Run(String startwith)
308 {
309 	finished = canceled = false;
310 	if(!IsSplashOpen())
311 		Open();
312 	if(selectvars)
313 		SyncBase(GetVarsName());
314 	else
315 		OnBase();
316 	String bkvar = GetVarsName();
317 	if(finished)
318 		return GetCurrentName();
319 	if(canceled)
320 		return Null;
321 	alist.FindSetCursor(startwith);
322 	clist.FindSetCursor(startwith);
323 	ActiveFocus(alist.IsShown() ? (Ctrl&)alist : (Ctrl&)clist);
324 	switch(TopWindow::Run()) {
325 	case IDOK:  return GetCurrentName();
326 	case IDYES: return selected;
327 	default:
328 		LoadVars(bkvar);
329 		SyncBase(GetVarsName());
330 		return Null;
331 	}
332 }
333 
OnOK()334 void SelectPackageDlg::OnOK()
335 {
336 	Package pkg;
337 	int f = ~filter;
338 	String n = GetCurrentName();
339 	if(n.GetCount() && pkg.Load(PackagePath(n)) &&
340 	   (!(f & MAIN) || pkg.config.GetCount())) {
341 		loading = false;
342 		finished = true;
343 		AcceptBreak(IDOK);
344 	}
345 }
346 
OnCancel()347 void SelectPackageDlg::OnCancel()
348 {
349 	loading = false;
350 	canceled = true;
351 	RejectBreak(IDCANCEL);
352 }
353 
OnFilter()354 void SelectPackageDlg::OnFilter()
355 {
356 	SyncList(Null);
357 }
358 
OnBase()359 void SelectPackageDlg::OnBase()
360 {
361 	if(!finished && !canceled)
362 		Load();
363 }
364 
OnNew()365 void SelectPackageDlg::OnNew() {
366 	TemplateDlg dlg;
367 	LoadFromGlobal(dlg, "NewPackage");
368 	int f = ~filter;
369 	dlg.Load(GetUppDirs(), f & MAIN);
370 	while(dlg.Run() == IDOK) {
371 		String nest = ~dlg.nest;
372 		String name = NativePath(String(~dlg.package));
373 		String path = AppendFileName(nest, AppendFileName(name, GetFileName(name) + ".upp"));
374 		if(FileExists(path) && !PromptYesNo("Package [* \1" + path + "\1] already exists.&"
375 		                                    "Do you wish to recreate the files?"))
376 			continue;
377 		RealizePath(path);
378 		if(!SaveFile(path, Null)) {
379 			Exclamation("Error writing the file [* \1" + path + "\1].");
380 			continue;
381 		}
382 		dlg.Create();
383 		selected = name;
384 		Break(IDYES);
385 		break;
386 	}
387 	StoreToGlobal(dlg, "NewPackage");
388 }
389 
GetSvnDirs()390 Vector<String> SelectPackageDlg::GetSvnDirs()
391 {
392 	Vector<String> r;
393 	Vector<String> dirs = SplitDirs(GetVar("UPP"));
394 	for(int i = 0; i < dirs.GetCount(); i++) {
395 		String d = NormalizePath(dirs[i]);
396 		if(GetRepoKind(d))
397 			r.Add(d);
398 	}
399 	return r;
400 }
401 
SyncSvnDir(const String & dir)402 void SelectPackageDlg::SyncSvnDir(const String& dir)
403 {
404 	RepoSyncDirs(Vector<String>() << dir);
405 	Load();
406 }
407 
SyncSvnDirs()408 void SelectPackageDlg::SyncSvnDirs()
409 {
410 	RepoSyncDirs(GetSvnDirs());
411 	Load();
412 }
413 
ToolBase(Bar & bar)414 void SelectPackageDlg::ToolBase(Bar& bar)
415 {
416 	bar.Add("New assembly..", THISBACK(OnBaseAdd))
417 #ifdef PLATFORM_COCOA
418 		.Key(K_CTRL_N)
419 #else
420 		.Key(K_INSERT)
421 #endif
422 	;
423 	bar.Add(base.IsCursor(), "Edit assembly..", THISBACK(OnBaseEdit))
424 		.Key(K_CTRL_ENTER);
425 	bar.Add(base.IsCursor(), "Remove assembly..", THISBACK(OnBaseRemove))
426 		.Key(K_CTRL_DELETE);
427 	Vector<String> d = GetSvnDirs();
428 	if(HasSvn()) {
429 		bar.Separator();
430 		bar.Add("Checkout and setup U++ SVN trunk sources..", [=] {
431 			String vars = base.Get(0);
432 			SetupSVNTrunk();
433 			SyncBase(vars);
434 		});
435 	}
436 	if(d.GetCount()) {
437 		bar.Separator();
438 		for(int i = 0; i < d.GetCount(); i++)
439 			bar.Add("Synchronize " + d[i], IdeImg::svn_dir(), THISBACK1(SyncSvnDir, d[i]));
440 		bar.Add("Synchronize everything..", IdeImg::svn(), THISBACK(SyncSvnDirs));
441 	}
442 }
443 
OnBaseAdd()444 void SelectPackageDlg::OnBaseAdd()
445 {
446 	String vars;
447 	if(BaseSetup(vars))
448 		SyncBase(vars);
449 }
450 
OnBaseEdit()451 void SelectPackageDlg::OnBaseEdit()
452 {
453 	if(!base.IsCursor())
454 		return;
455 	String vars = base.Get(0), oldvars = vars;
456 	if(BaseSetup(vars)) {
457 		if(vars != oldvars)
458 			DeleteFile(VarFilePath(oldvars));
459 		DeleteFile(CachePath(vars));
460 		SyncBase(vars);
461 	}
462 }
463 
OnBaseRemove()464 void SelectPackageDlg::OnBaseRemove()
465 {
466 	int c = base.GetCursor();
467 	if(c < 0)
468 		return;
469 	String next;
470 	if(c + 1 < base.GetCount())
471 		next = base.Get(c + 1);
472 	else if(c > 0)
473 		next = base.Get(c - 1);
474 	String vars = base.Get(0);
475 	String varpath = VarFilePath(vars);
476 	if(PromptOKCancel(Format("Remove base file [* \1%s\1]?", varpath))) {
477 		if(!FileDelete(varpath))
478 			Exclamation(Format("Error deleting file [* \1%s\1].", varpath));
479 		else
480 			SyncBase(next);
481 	}
482 }
483 
DirSep(int c)484 int DirSep(int c)
485 {
486 	return c == '\\' || c == '/' ? c : 0;
487 }
488 
489 struct PackageDisplay : Display {
490 	Font fnt;
491 
GetStdSizePackageDisplay492 	virtual Size GetStdSize(const Value& q) const {
493 		ValueArray va = q;
494 		Size sz = GetTextSize(String(va[0]), fnt);
495 		sz.cx += Zx(20);
496 		sz.cy = max(sz.cy, Zy(16));
497 		return sz;
498 	}
499 
PaintPackageDisplay500 	virtual void Paint(Draw& w, const Rect& r, const Value& q, Color ink, Color paper, dword style) const {
501 		ValueArray va = q;
502 		String txt = va[0];
503 		Image icon = va[1];
504 		if(IsNull(icon))
505 			icon = IdeImg::Package();
506 		else
507 			icon = DPI(icon, 16);
508 		w.DrawRect(r, paper);
509 		w.DrawImage(r.left, r.top + (r.Height() - icon.GetHeight()) / 2, icon);
510 		w.DrawText(r.left + DPI(20), r.top + (r.Height() - Draw::GetStdFontCy()) / 2, txt, fnt, ink);
511 	}
512 
PackageDisplayPackageDisplay513 	PackageDisplay() { fnt = StdFont(); }
514 };
515 
SyncList(const String & find)516 void SelectPackageDlg::SyncList(const String& find)
517 {
518 	String n = Nvl(find, GetCurrentName());
519 	int asc = alist.GetScroll();
520 	int csc = clist.GetSbPos();
521 
522 	packages.Clear();
523 	String s = ~search;
524 	int f = ~filter;
525 	Index<String> added;
526 	for(int i = 0; i < min((f & FIRST) ? 1 : data.GetCount(), data.GetCount()); i++) {
527 		const ArrayMap<String, PkData>& nest = data[i];
528 		for(int i = 0; i < nest.GetCount(); i++) {
529 			const PkData& d = nest[i];
530 			if(!nest.IsUnlinked(i) &&
531 			   d.ispackage &&
532 			   (!(f & MAIN) || d.main) &&
533 			   ToUpper(d.package + d.description + d.nest).Find(s) >= 0 &&
534 			   added.Find(d.package) < 0) {
535 				packages.Add() = d;
536 				added.Add(d.package);
537 			}
538 		}
539 	}
540 	Sort(packages);
541 	alist.Clear();
542 	clist.Clear();
543 	ListCursor();
544 	static PackageDisplay pd, bpd;
545 	bpd.fnt.Bold();
546 	for(int i = 0; i < packages.GetCount(); i++) {
547 		const PkInfo& pkg = packages[i];
548 		Image icon = pkg.icon;
549 		if(IsNull(icon))
550 			icon = pkg.main ? IdeImg::MainPackage() : IdeImg::Package();
551 		clist.Add(pkg.package, DPI(icon, 16));
552 		alist.Add(pkg.package, pkg.nest, pkg.description, icon);
553 		alist.SetDisplay(alist.GetCount() - 1, 0, pkg.main ? bpd : pd);
554 	}
555 	if(!alist.FindSetCursor(n))
556 		alist.GoBegin();
557 	if(!clist.FindSetCursor(n) && clist.GetCount())
558 		clist.SetCursor(0);
559 	alist.ScrollTo(asc);
560 	clist.SetSbPos(csc);
561 	alist.HeaderTab(0).SetText("Package (" + AsString(alist.GetCount()) + ")");
562 }
563 
ScanFolder(const String & path,ArrayMap<String,PkData> & nd,const String & nest,Index<String> & dir_exists,const String & prefix)564 void SelectPackageDlg::ScanFolder(const String& path, ArrayMap<String, PkData>& nd,
565                                   const String& nest, Index<String>& dir_exists,
566                                   const String& prefix)
567 {
568 	for(FindFile ff(AppendFileName(path, "*.*")); ff; ff.Next())
569 		if(ff.IsFolder() && !ff.IsHidden()) {
570 			dir_exists.Add(ff.GetPath());
571 			String p = ff.GetPath();
572 			bool nw = nd.Find(p) < 0; // Do we have any info loaded about this package?
573 			PkData& d = nd.GetAdd(ff.GetPath());
574 			d.package = prefix + ff.GetName();
575 			d.nest = nest;
576 			if(nw) { // No cached info available about the folder
577 				d.ispackage = IsLetter(*d.package) && d.package.Find('.') < 0; // First heuristic guess
578 				d.main = d.ispackage && prefix.GetCount() == 0; // Expect it is main
579 			}
580 		}
581 }
582 
CachePath(const char * vn) const583 String SelectPackageDlg::CachePath(const char *vn) const
584 {
585 	return AppendFileName(ConfigFile("cfg"), String(vn) + ".pkg_cache");
586 }
587 
Load(const String & find)588 void SelectPackageDlg::Load(const String& find)
589 {
590 	if(selectvars && !base.IsCursor())
591 		return;
592 	if(loading) { // If we are called recursively from ProcessEvents, stop current loading and change loadi
593 		loadi++;
594 		loading = false;
595 		return;
596 	}
597 	int current_loadi = -1;
598 	while(current_loadi != loadi) {
599 		current_loadi = loadi;
600 		if(selectvars) {
601 			String assembly = (String)base.Get(0);
602 			list.Enable(base.IsCursor());
603 			if(!base.IsCursor())
604 				return;
605 			LoadVars(assembly);
606 		}
607 		Vector<String> upp = GetUppDirs();
608 		packages.Clear();
609 		description.Hide();
610 		progress.Show();
611 		loading = true;
612 		data.Clear();
613 		Index<String> dir_exists;
614 		String cache_path = CachePath(GetVarsName());
615 		LoadFromFile(data, cache_path);
616 		data.SetCount(upp.GetCount());
617 		for(int i = 0; i < upp.GetCount(); i++) // Scan nest folders for subfolders (additional package candidates)
618 			ScanFolder(upp[i], data[i], GetFileName(upp[i]), dir_exists, Null);
619 		int update = msecs();
620 		for(int i = 0; i < data.GetCount() && loading; i++) { // Now investigate individual sub folders
621 			ArrayMap<String, PkData>& nest = data[i];
622 			String nest_dir = NormalizePath(upp[i]);
623 			for(int i = 0; i < nest.GetCount() && loading; i++) {
624 				if(msecs(update) >= 100) { // each 100 ms update the list (and open select dialog after splash screen is closed)
625 					if(!IsSplashOpen() && !IsOpen())
626 						Open();
627 					progress++;
628 					SyncList(find);
629 					update = msecs();
630 				}
631 				ProcessEvents(); // keep GUI running
632 
633 				PkData& d = nest[i];
634 				String path = nest.GetKey(i);
635 				if(NormalizePath(path).StartsWith(nest_dir) && DirectoryExists(path)) {
636 					String upp_path = AppendFileName(path, GetFileName(d.package) + ".upp");
637 					LSLOW(); // this is used for testing only, normally it is NOP
638 					Time tm = FileGetTime(upp_path);
639 					if(IsNull(tm)) // .upp file does not exist - not a package
640 						d.ispackage = false;
641 					else
642 					if(tm != d.tm) { // cached info is outdated
643 						Package p;
644 						if(p.Load(upp_path)) {
645 							d.description = p.description;
646 							d.main = p.config.GetCount();
647 							d.tm = tm;
648 							d.ispackage = true;
649 						}
650 						else
651 							d.ispackage = false;
652 					}
653 					else
654 						d.ispackage = true;
655 					if(d.ispackage) {
656 						String icon_path;
657 						if(IsUHDMode())
658 							icon_path = AppendFileName(path, "icon32x32.png");
659 						if(IsNull(icon_path) || !FileExists(icon_path))
660 							icon_path = AppendFileName(path, "icon16x16.png");
661 						tm = FileGetTime(icon_path);
662 						if(IsNull(tm)) // package icon does not exist
663 							d.icon = Null;
664 						else
665 						if(tm != d.itm) { // chached package icon outdated
666 							d.icon = StreamRaster::LoadFileAny(icon_path);
667 							d.itm = tm;
668 						}
669 					}
670 					ScanFolder(path, nest, d.nest, dir_exists, d.package + '/');
671 				}
672 				else
673 					nest.Unlink(i); // cached folder was deleted or is not in nest dir
674 			}
675 			nest.Sweep();
676 		}
677 
678 		StoreToFile(data, cache_path);
679 		progress.Hide();
680 		while(IsSplashOpen())
681 			ProcessEvents();
682 		if(!IsOpen())
683 			Open();
684 		description.Show();
685 		if(loading) {
686 			loading = false;
687 			SyncList(find);
688 		}
689 	}
690 }
691 
SyncBase(String initvars)692 void SelectPackageDlg::SyncBase(String initvars)
693 {
694 	Vector<String> varlist;
695 	for(FindFile ff(ConfigFile("*.var")); ff; ff.Next())
696 		if(ff.IsFile())
697 			varlist.Add(GetFileTitle(ff.GetName()));
698 	Sort(varlist, &PackageLess);
699 	base.Clear();
700 	Append(base, varlist);
701 	if(!base.FindSetCursor(initvars)) {
702 		if(base.GetCount() > 0)
703 			base.SetCursor(0);
704 		else
705 			OnBase();
706 	}
707 }
708 
Pless(const SelectPackageDlg::PkInfo & a,const SelectPackageDlg::PkInfo & b)709 bool SelectPackageDlg::Pless(const SelectPackageDlg::PkInfo& a, const SelectPackageDlg::PkInfo& b)
710 {
711 	return PackageLess(a.package, b.package);
712 }
713 
714 INITBLOCK
715 {
716 	RegisterGlobalConfig("SelectPkgMain");
717 	RegisterGlobalConfig("SelectPkg");
718 }
719 
720 String SelectPackage(const char *title, const char *startwith, bool selectvars, bool main)
721 {
722 	SelectPackageDlg dlg(title, selectvars, main);
723 	const char *c = main ? "SelectPkgMain" : "SelectPkg";
724 	LoadFromGlobal(dlg, c);
725 	dlg.SyncBrief();
726 	String b = dlg.Run(startwith);
727 	StoreToGlobal(dlg, c);
728 	return b;
729 }
730