installer: Handling generalizing locales on language dropbox selection
[sdk] / installer / src / installer.ec
1 #ifdef NOMINGW
2 static define buildString = $"Ecere SDK v0.44.10 (Without MinGW) -- built on March 9th, 2014 ";
3 #else
4 static define buildString = $"Ecere SDK v0.44.10 -- built on March 9th, 2014 ";
5 #endif
6
7 #define WIN32_LEAN_AND_MEAN
8 #define GetFreeSpace _GetFreeSpace
9 #include <windows.h>
10 #undef GetFreeSpace
11
12 #ifdef ECERE_STATIC
13 import static "ecere"
14 #else
15 import "ecere"
16 #endif
17 import "IDESettings"
18 import "createLink"
19 import "licensing"
20 import "CheckListBox"
21
22 static LanguageOption dummy; // TOFIX
23
24 static bool IsAdministrator()
25 {
26    bool b;
27    SID_IDENTIFIER_AUTHORITY NtAuthority = { SECURITY_NT_AUTHORITY };
28    PSID AdministratorsGroup;
29    b = AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &AdministratorsGroup);
30    if(b)
31    {
32       if(!CheckTokenMembership(NULL, AdministratorsGroup, &b))
33          b = FALSE;
34        FreeSid(AdministratorsGroup);
35    }
36    return b;
37 }
38
39 struct CheckItem
40 {
41    char * name;
42    void * data;
43    bool add32Bit;
44    char * OnGetString(char * tempString, void * fieldData, bool * needClass)
45    {
46       if(add32Bit)
47       {
48          strcpy(tempString, name);
49          strcat(tempString, " (32)");
50          return tempString;
51       }
52       return name;
53    }
54
55    void OnDisplay(Surface surface, int x, int y, int width, void * fieldData, Alignment alignment, DataDisplayFlags displayFlags)
56    {
57       if(!displayFlags.active) { displayFlags.current = false; displayFlags.selected = false; }
58       class::OnDisplay(surface, x + 22, y, width - 22, fieldData, alignment, displayFlags);
59    }
60 };
61
62 //#define BUFFERSIZE 0x10000
63 #define BUFFERSIZE 0x1000
64 bool abortInstall = false;
65
66 void ExtractFileFromArchive(ProgressBar progressBar, char * path, char * outputFile)
67 {
68    char fileName[MAX_LOCATION];
69    FileAttribs exists = FileExists(path);
70    bool setTime = false;
71    FileStats stats;
72
73    if(exists.isDirectory)
74    {
75       FileListing listing { path };
76
77       if(outputFile[0])
78       {
79          if(MakeDir(outputFile))
80          {
81             setTime = true;
82             FileGetStats(path, &stats);
83          }
84       }
85
86       while(listing.Find() && !abortInstall)
87       {
88          strcpy(fileName, outputFile);
89
90          // Tweak file name if out
91          if(outputFile[0])
92          {
93             if(!strcmp(fileName, ".")) fileName[0] = '\0';
94             if(listing.name[0] == '/' || listing.name[0] == '\\')
95             {
96                char * afterSlash, rest[MAX_LOCATION];
97                for(afterSlash = fileName; *afterSlash == '/' || *afterSlash == '\\'; afterSlash++);
98                strcpy(rest, afterSlash);
99                PathCat(fileName, "_root");
100                PathCat(fileName, rest);
101             }
102             else if(listing.name[1] == ':')
103             {
104                char letter[10];
105                sprintf(letter, "_%cdrive", toupper(listing.name[0]));
106                PathCat(fileName, letter);
107                PathCat(fileName, listing.name[2] ? (listing.name + 3) : (listing.name + 2));
108             }
109             else
110                PathCat(fileName, listing.name);
111          }
112          else
113             PathCat(fileName, listing.name);
114          if(!strcmp(fileName, "/") || !strcmp(fileName, "\\"))
115             strcpy(fileName, DIR_SEPS);
116          ExtractFileFromArchive(progressBar, listing.path, fileName);
117       }
118       listing.Stop();
119    }
120    else if(exists)
121    {
122       File input = FileOpen(path, read);
123       if(input)
124       {
125          File output = FileOpen(outputFile, write);
126          if(output)
127          {
128             char fileName[MAX_FILENAME];
129             uint position = progressBar.progress;
130             FileSize dataSize;
131             int c;
132             byte buffer[BUFFERSIZE];
133
134             FileGetSize(path, &dataSize);
135             GetLastDirectory(outputFile, fileName);
136
137             ((GuiApplication)__thisModule).SignalEvent();
138             //((GuiApplication)__thisModule).ProcessInput();
139             //((GuiApplication)__thisModule).UpdateDisplay();
140
141             for(c = 0; c<dataSize && !abortInstall; c += BUFFERSIZE)
142             {
143                uint size = (dataSize > c + BUFFERSIZE) ? BUFFERSIZE : (dataSize - c);
144
145                ((GuiApplication)__thisModule).Unlock();
146
147                input.Read(buffer, 1, size);
148                output.Write(buffer, 1, size);
149
150                ((GuiApplication)__thisModule).Lock();
151
152                progressBar.progress = position + c + size;
153                ((GuiApplication)__thisModule).SignalEvent();
154
155                //((GuiApplication)__thisModule).ProcessInput();
156                //((GuiApplication)__thisModule).UpdateDisplay();
157             }
158
159             delete output;
160             setTime = true;
161             FileGetStats(path, &stats);
162          }
163          delete input;
164       }
165    }
166    if(setTime)
167       FileSetTime(outputFile, stats.created, 0, stats.modified);
168 }
169
170 public enum BitArch { none, bits32, bits64 };
171
172 struct Component
173 {
174    char * name;
175    char * dataPath;
176    char * defInstallPath;
177    Component * subComponents;
178    bool mandatory;
179    bool selected;
180    bool available;
181    BitArch arch;
182
183    Component * parent;
184
185    uint requiredSize;
186    uint size;
187    char installPath[MAX_LOCATION];
188    DataRow row;
189
190    void GetFullPath(char * path, bool is32bit)
191    {
192       if(this != null && parent)
193          parent->GetFullPath(path, is32bit || (arch == bits32 && osIS64bit));
194       else
195          strcpy(path, (this && (is32bit || (arch == bits32 && osIS64bit))) ? installDir32 : installDir);
196
197       if(this != null)
198          PathCat(path, installPath);
199    }
200
201    void Install(char * parentPath, char * parentPath32)
202    {
203       int c;
204       if(selected && (arch == none || arch == bits32 || osIS64bit))
205       {
206          char path64[MAX_LOCATION];
207          char path32[MAX_LOCATION];
208          strcpy(path64, parentPath);
209          PathCat(path64, installPath);
210
211          strcpy(path32, parentPath32);
212          PathCat(path32, installPath);
213
214          installProgress.installing.SetText($"Installing %s...", name);
215          ((GuiApplication)__thisModule).UpdateDisplay();
216
217          if(dataPath)
218          {
219             char * path = path64;
220             char source[MAX_LOCATION];
221             strcpy(source, ":");
222             strcat(source, dataPath);
223
224             if(arch == bits32)
225                path = path32;
226
227             MakeDir(path);
228
229             if(FileExists(source).isFile)
230             {
231                char name[MAX_FILENAME];
232                GetLastDirectory(source, name);
233                PathCat(path, name);
234             }
235             if(requiredSize)
236             {
237                uint p = installProgress.progressBar.progress;
238                ExtractFileFromArchive(installProgress.progressBar, source, path);
239             }
240          }
241          if(subComponents)
242          {
243             for(c = 0; subComponents[c].name; c++)
244                subComponents[c].Install(path64, path32);
245          }
246       }
247    }
248 };
249
250 #ifndef NOMINGW
251 define minGWIncluded = true;
252 #else
253 define minGWIncluded = false;
254 #endif
255
256 Array<Component> samples
257 { [
258    { "Ecere Chess",     "samples/chess",     "chess",       null, false, true, true, none },
259    { "Ecere Fractals",  "samples/fractals",  "fractals",    null, false, true, true, none },
260    { "3D",              "samples/3D",        "3D",          null, false, true, true, none },
261    { "Android",         "samples/android",   "android",          null, false, true, true, none },
262    { $"Audio",           "samples/audio",     "audio",       null, false, true, true, none },
263    { $"Database",        "samples/db",        "db",          null, false, true, true, none },
264    { "eC",              "samples/eC",        "eC",          null, false, true, true, none },
265    { $"Games",           "samples/games",     "games",       null, false, true, true, none },
266    { $"GUI & Graphics",  "samples/guiAndGfx", "guiAndGfx",   null, false, true, true, none },
267    { $"Miscellaneous",   "samples/misc",      "misc",        null, false, true, true, none },
268    { $"Networking",      "samples/net",       "net",         null, false, true, true, none },
269    { $"WIA Scanning",    "samples/scanning",  "scanning",    null, false, true, true, none },
270    { $"Threading",       "samples/threads",   "threads",     null, false, true, true, none },
271    { null }
272 ] };
273
274 public enum CoreSDKID
275 {
276    ide, ide32, runtime, runtime32, ec, ec32,
277    gcc, gdb, mingw, binutils, make,
278    none
279 };
280
281 Array<Component> coreSDK
282 { [
283    { "Ecere IDE",       "ecere-sdk/ide",                 "bin",      null, true,  true, true, bits64 },
284    { "Ecere IDE",       "ecere-sdk32/ide",                 "bin",      null, true,  true, true, bits32 },
285    { $"Runtime Library", "ecere-sdk/ecere.dll",           "bin",      null, true,  true, true, bits64 },
286    { $"Runtime Library", "ecere-sdk32/ecere.dll",           "bin",      null, true,  true, true, bits32 },
287    { $"eC Compiler",     "ecere-sdk/compiler",            "bin",      null, true,  true, true, bits64 },
288    { $"eC Compiler",     "ecere-sdk32/compiler",            "bin",      null, true,  true, true, bits32 },
289    { $"GNU C Compiler",  "tdm/gcc/core",   "tdm", null, true, true, minGWIncluded, none },
290    { $"GNU Debugger",    "tdm/gdb",        "tdm", null, true, true, minGWIncluded, none },
291    { $"MinGW-w64 Runtime",   "tdm/mingwrt",    "tdm", null, true, true, minGWIncluded, none },
292    { $"Binary Utils",    "tdm/binutils",   "tdm", null, true, true, minGWIncluded, none },
293    { $"GNU Make",        "tdm/make",       "tdm", null, true, true, minGWIncluded, none },
294    { null }
295 ] };
296
297 public enum AdditionalID
298 {
299    eda, eda32, vanilla, vanilla32, extras, upx, gnurx, gnurx32, /*pthreads, */cpp, /*w32api, gcci18n, gdbi18n, makei18n, binutilsi18n, */none
300 };
301
302 Array<Component> additional
303 { [
304    { $"Data Access",     "ecere-sdk/eda",                 "bin",      null, false, true, true, bits64 },
305    { $"Data Access",     "ecere-sdk32/eda",               "bin",      null, false, true, true, bits32 },
306    { $"Ecere Vanilla",   "ecere-sdk/libecereVanilla.a",   "lib",      null, false, true, true, bits64 },
307    { $"Ecere Vanilla",   "ecere-sdk32/libecereVanilla.a", "lib",      null, false, true, true, bits32 },
308    { $"Ecere Extras",    "extras",                        "extras",   null, false, true, true, none },
309    { "UPX",             "upx/bin",                       "upx/bin",      null, false, true, true, none },
310    { $"GNU Regexp",      "tdm/gnurx",                     "tdm",    null, false, true, true, bits64 },
311    { $"GNU Regexp",      "tdm/gnurx32",                   "tdm",    null, false, true, true, bits32 },
312 //   { "pthreads",        "tdm/pthreads",                   "mingw",    null, false, true, minGWIncluded, none },
313    { $"C++ Compiler",    "tdm/gcc/c++",                   "tdm",    null, false, true, minGWIncluded, none },
314 //   { "Win32 APIs",      "mingw/w32api",                  "mingw",    null, false, true, minGWIncluded, none },
315 /*   { "GCC I18n",        "mingw/locale/gcc",              "tdm",    null, false, false, minGWIncluded, none },
316    { "GDB I18n",        "mingw/locale/gdb",              "tdm",    null, false, false, minGWIncluded, none },
317    { "Make I18n",       "mingw/locale/make",             "tdm",    null, false, false, minGWIncluded, none },
318    { "Binutils I18n",   "mingw/locale/binutils",         "tdm",    null, false, false, minGWIncluded, none },
319 */
320    { null }
321 ] };
322
323 public enum DocumentationID
324 {
325    ecereBook, apiRef, tutorials, coursework,
326    gccDoc, gppDocs, gdbDocs, makeDocs, binDocs, mingwDocs, gnurxDocs, upxDocs,
327    none
328 };
329
330 Array<Component> documentation
331 { [
332    { $"Ecere Book",         "ecere-sdk/book",       "doc",            null, false, true, true, none },
333    { $"API Reference",      "ecere-sdk/doc",        "doc",            null, false, true, true, none },
334    { $"Ecere Tutorials",    "ecere-sdk/tutorials",  "doc",            null, false, true, true, none },
335    { $"Ecere Coursework",   "ecere-sdk/coursework", "doc",            null, false, true, true, none },
336    { $"GCC Docs",           "tdm/doc/gcc",          "tdm",          null, false, false, minGWIncluded, none },
337    { $"G++ Docs",           "tdm/doc/g++",          "tdm",          null, false, false, minGWIncluded, none },
338    { $"GDB Docs",           "tdm/doc/gdb",          "tdm",          null, false, false, minGWIncluded, none },
339    { $"Make Docs",          "tdm/doc/make",         "tdm",          null, false, false, minGWIncluded, none },
340    { $"Binutils Docs",      "tdm/doc/binutils",     "tdm",          null, false, false, minGWIncluded, none },
341    { $"gnurx Docs",         "tdm/doc/gnurx",        "tdm",          null, false, false, minGWIncluded, none },
342    { $"UPX Docs",           "upx/doc",              "upx/doc",  null, false, false, minGWIncluded, none },
343    { null }
344 ] };
345
346 public enum ComponentID
347 {
348    coreSDK,
349    additional,
350    documentation,
351    samples,
352    none
353 };
354
355 Array<Component> components
356 { [
357    { $"Core SDK Files", null, null, coreSDK.array, true, true, true, none },
358    { $"Additional Support", null, null, additional.array, false, true, true, none },
359    { $"Documentation", null /*"doc"*/, null /*"doc"*/, documentation.array, false, true, true, none },
360    { $"Samples", null, "samples", samples.array, false, true, true, none },
361    { null }
362 ] };
363 FileSize totalSize;
364 FileSize totalInstalled;
365
366 struct InstallOption
367 {
368    char * name;
369    InstallOption * subOptions;
370    bool selected;
371    bool available;
372    DataRow row;
373 };
374
375 enum AssociateOptions
376 {
377    AssociateEPJ,
378    AssociateEC,
379    AssociateC,
380    AssociateCPP,
381    AssociateTXT,
382    Associate3DS,
383    AssociateIMG
384 };
385
386 Array<InstallOption> associateOptions
387 { [
388    { $"Associate with Ecere Project Files (*.epj)", null, true },
389    { $"Associate with eC Files (*.ec, *.eh)", null, true },
390    { $"Associate with C files (*.c, *.h)", null, false },
391    { $"Associate with C++ Files (*.cpp, *.hpp, *.cc, *.hh, *.cxx, *.hxx)", null, false },
392    { $"Associate with text files (*.txt)", null, false },
393    { $"Associate with 3D Studio Model Files (*.3ds)", null, true },
394    { $"Associate with Image Files (*.png, *.jpg, *.pcx, *.bmp, *.gif)", null, false },
395    { null }
396 ] };
397
398 enum PathOptions
399 {
400    AddECEREPaths, AddMinGWPaths
401 };
402
403 Array<InstallOption> pathOptions
404 { [
405    { $"Add Ecere binaries location to the user environment path", null, true, true },
406    { $"Add MinGW to the user environment path", null, true, minGWIncluded },
407    { null }
408 ] };
409
410 enum IconOptions
411 {
412    StartMenuIcon = 1,
413    DesktopIcon = 2,
414    QuickLaunchIcon = 3
415 };
416
417 Array<InstallOption> options
418 { [
419    { $"Install for All Users", null, true, true },
420    { $"Start Menu Group", null, true, true },
421    { $"Desktop Icon", null, true, true },
422    { $"Quicklaunch Icon", null, true, true },
423    { $"Associate the Ecere IDE with Supported File Types", associateOptions.array, true, true },
424    { $"Add binaries location to the user environment paths", pathOptions.array, true, minGWIncluded },
425    { null }
426 ] };
427
428 char sourceDir[MAX_LOCATION] = ":";
429 char installDir[MAX_LOCATION];
430 char installDir32[MAX_LOCATION];
431 bool osIS64bit;
432
433 class Installer : Window
434 {
435    background = formColor;
436    borderStyle = fixed;
437    hasMinimize = true;
438    hasClose = true;
439    tabCycle = true;
440    clientSize = { 636, 476 };
441    icon = { ":icon.png" };
442    text = $"Ecere Software Development Kit Setup - v0.44.10 \"Ryōan-ji\" 64 Bit Edition";
443
444    // clientSize = { 796, 576 };
445    bool loaded;
446
447    Picture back { image = BitmapResource { ":ryoanji.png" }, parent = this, position = { 0, 0 } };
448    FileDialog fileDialog
449    {
450       master = this, type = selectDir,
451       text = $"Select a new location"
452    };
453    Button browse
454    {
455       master = this, autoCreate = false, inactive = true, text = "...";
456
457       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
458       {
459          DataRow row = componentsBox.currentRow;
460          Component * component = ((CheckItem *)row.GetData(componentField))->data;
461          component->GetFullPath(fileDialog.filePath, false);
462          StripLastDirectory(fileDialog.filePath, fileDialog.currentDirectory);
463
464          if(fileDialog.Modal() == ok)
465          {
466             componentsBox.StopEditing(false);
467             row.SetData(locationField, fileDialog.filePath);
468             componentsBox.NotifyChanged(this, componentsBox, row);
469             componentsBox.currentField = componentsBox.currentField;
470          }
471          return true;
472       }
473    };
474    CheckListBox componentsBox
475    {
476       this, size = { 460, 112 }, position = { 160, 160 }, hasHeader = true;
477       fullRowSelect = false, collapseControl = true, treeBranches = true, rootCollapseButton = true,
478       hasHorzScroll = true;
479       resizable = true,
480       noDragging = true;
481       rowHeight = 18;
482       selectionColor = { 145, 150, 140 };
483
484       alwaysEdit = true;
485       opacity = 0;
486
487       bool NotifyKeyDown(ListBox listBox, DataRow row, Key key, unichar ch)
488       {
489          if(key == f2 && browse.visible)
490             browse.NotifyClicked(this, browse, 0, 0, 0);
491          return true;
492       }
493
494       bool NotifyChanged(ListBox listBox, DataRow row)
495       {
496          Component * component = ((CheckItem *)listBox.GetData(componentField))->data;
497          char path[MAX_LOCATION], relative[MAX_LOCATION] = "", * newPath;
498          char fullPath[MAX_LOCATION];
499
500          component->parent->GetFullPath(path, false);
501          strcpy(fullPath, path);
502
503          newPath = row.GetData(locationField);
504          if(newPath)
505          {
506             PathCat(fullPath, newPath);
507             if(IsPathInsideOf(fullPath, path))
508                MakePathRelative(fullPath, path, relative);
509             else
510                strcpy(relative, fullPath);
511          }
512          listBox.SetData(locationField, relative);
513          strcpy(component->installPath, relative);
514
515          //SetAvailableSpace(component, path);
516          {
517             ComponentID c;
518             install.disabled = false;
519             for(c = 0; components[c].name; c++)
520             {
521                SetAvailableSpace(components[c], installDir);
522             }
523          }
524          return true;
525       }
526
527       bool NotifyEditDone(ListBox listBox, DataRow row)
528       {
529          browse.Destroy(0);
530          return true;
531       }
532
533       bool NotifyEdited(ListBox listBox, DataRow row)
534       {
535          Window e;
536          browse.parent = listBox;
537          browse.position = { componentField.width + locationField.width + 18, (listBox.currentIndex+1) * listBox.rowHeight - 2 };
538          browse.size = { 30, listBox.rowHeight + 3 };
539          for(e = listBox.firstChild; e; e = e.next)
540          {
541             if(e._class == class(DataBox))
542                break;
543          }
544          if(e)
545             e.Activate();
546          browse.Create();
547          return true;
548       }
549
550       void NotifyChecked(CheckListBox listBox, DataRow row)
551       {
552          Component * component = ((CheckItem *)row.GetData(componentField))->data;
553          int c;
554          component->selected = listBox.IsChecked(row);
555
556          if(!component->parent) totalSize -= component->requiredSize;
557          component->requiredSize = 0;
558          if(component->selected)
559          {
560             component->requiredSize += component->size;
561             if(component->subComponents)
562                for(c = 0; component->subComponents[c].name; c++)
563                   component->requiredSize += component->subComponents[c].requiredSize;
564          }
565          if(component->requiredSize)
566          {
567             uint requiredSize = component->requiredSize;
568             row.SetData(reqField, requiredSize);
569          }
570          else
571             row.UnsetData(reqField);
572          if(!component->parent)
573          {
574             totalSize += component->requiredSize;
575             {
576                char sizeString[100];
577                PrintSize(sizeString, totalSize, 2);
578                totalSpaceValue.text = sizeString;
579             }
580          }
581       }
582    };
583    Label agreementLbl { parent = this, text = $"By installing the Ecere SDK, you agree to the", font = { "Tahoma", 8.25f }, anchor = Anchor { right = 399, top = 448 } };
584    Button licenseButton
585    {
586       this, inactive = true, offset = false, bevel = false, foreground = blue, font = { "Tahoma", 8.25f, underline = true, bold = true },
587       // text = $"terms and conditions", anchor = Anchor { left = 241, top = 421 };
588       text = $"terms and conditions", anchor = Anchor { left = 235, top = 445 };
589       cursor = ((GuiApplication)__thisModule).GetCursor(hand);
590
591       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
592       {
593          // LicenseBox { master = this, sourceFile = ":ecere-sdk/doc/LICENSE" }.Modal();
594          LicensesForm { master = this }.Modal();
595          return true;
596       }
597    };
598    Label dotLbl { parent = this, text = ".", font = { "Tahoma", 8.25f }, anchor = Anchor { left = 372, top = 448 } };
599    CheckListBox optionsBox
600    {
601       this, size = { 460, 114 }, position = { 160, 284 };
602       fullRowSelect = false, collapseControl = true, treeBranches = true, rootCollapseButton = true,
603       noDragging = true;
604       rowHeight = 18;
605       opacity = 0;
606
607       void NotifyChecked(CheckListBox listBox, DataRow row)
608       {
609          CheckItem * item = row.GetData(optionField);
610          InstallOption * option = item->data;
611          option->selected = listBox.IsChecked(row);
612          // Update default samples/extras path whether we're installing for All Users or not
613          if(option == &options[0])
614          {
615             char appData[MAX_LOCATION];
616
617             options[5].name = options[0].selected ? $"Add binaries location to the system environment paths" : $"Add binaries location to the user environment paths";
618             if(options[5].row)
619                ((CheckItem *)options[5].row.GetData(optionField))->name = options[5].name;
620
621             pathOptions[PathOptions::AddECEREPaths].name = options[0].selected ? $"Add Ecere binaries location to the system environment path" : $"Add Ecere binaries location to the user environment path";
622             if(pathOptions[PathOptions::AddECEREPaths].row)
623                ((CheckItem *)pathOptions[PathOptions::AddECEREPaths].row.GetData(optionField))->name = pathOptions[PathOptions::AddECEREPaths].name;
624
625             pathOptions[PathOptions::AddMinGWPaths].name = options[0].selected ? $"Add TDM-GCC/MinGW-w64 to the system environment path" : $"Add TDM-GCC/MinGW-w64 to the user environment path";
626             if(pathOptions[PathOptions::AddMinGWPaths].row)
627                ((CheckItem *)pathOptions[PathOptions::AddMinGWPaths].row.GetData(optionField))->name = pathOptions[PathOptions::AddMinGWPaths].name;
628
629             GetEnvironment(options[0].selected ? "ALLUSERSPROFILE" : "APPDATA", appData, sizeof(appData));
630             if(appData && appData[0])
631             {
632                char defPath[MAX_LOCATION];
633
634                char * s = components[ComponentID::samples].installPath;
635                strcpy(defPath, installDir);
636                PathCat(defPath, components[ComponentID::samples].defInstallPath);
637                ChangeCh(defPath, '/', DIR_SEP);
638                if(!strcmp(defPath, components[ComponentID::samples].installPath))
639                {
640                   static char defSamplesPath[MAX_LOCATION];
641                   strcpy(defSamplesPath, appData);
642                   PathCat(defSamplesPath, "Ecere SDK\\Samples");
643                   components[ComponentID::samples].defInstallPath = defSamplesPath;
644
645                   strcpy(components[ComponentID::samples].installPath, components[ComponentID::samples].defInstallPath);
646                   ChangeCh(components[ComponentID::samples].installPath, '/', DIR_SEP);
647                   components[ComponentID::samples].row.SetData(locationField, components[ComponentID::samples].installPath);
648                }
649
650                strcpy(defPath, installDir);
651                PathCat(defPath, additional[AdditionalID::extras].defInstallPath);
652                ChangeCh(defPath, '/', DIR_SEP);
653                if(!strcmp(additional[AdditionalID::extras].installPath, additional[AdditionalID::extras].installPath))
654                {
655                   static char defExtrasPath[MAX_LOCATION];
656                   strcpy(defExtrasPath, appData);
657                   PathCat(defExtrasPath, "Ecere SDK\\extras");
658                   additional[AdditionalID::extras].defInstallPath = defExtrasPath;
659
660                   strcpy(additional[AdditionalID::extras].installPath, additional[AdditionalID::extras].defInstallPath);
661                   ChangeCh(additional[AdditionalID::extras].installPath, '/', DIR_SEP);
662                   additional[AdditionalID::extras].row.SetData(locationField, additional[AdditionalID::extras].installPath);
663                }
664             }
665          }
666       }
667    };
668    Button install
669    {
670       parent = this, text = $"Install", isDefault = true, size = { 75, 23 }, position = { 432, 440 };
671
672       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
673       {
674          installProgress.Create();
675          Destroy(0);
676          // installProgress.thread.Main();
677          installProgress.thread.Create();
678          return true;
679       }
680    };
681    Button button3 { parent = this, text = $"Cancel", hotKey = altX, size = Size { 75, 23 }, anchor = Anchor { left = 544, top = 440 }, NotifyClicked = ButtonCloseDialog };
682    DropBox languageBox
683    {
684       this, position = { 14, 374 }, size = { 142, 0 }, caption = "Language:";
685
686       bool NotifySelect(DropBox dropBox, DataRow row, Modifiers mods)
687       {
688          LanguageOption * option = row.GetData(null);
689          // If the language is already set, we need to override it
690          {
691             IDESettings settings = null;
692             IDESettingsContainer settingsContainer
693             {
694                driver = "JSON";
695                dataOwner = &settings;
696                dataClass = class(IDESettings);
697                allUsers = options[0].selected;
698             };
699             settingsContainer.Load();
700             if(settings.language)
701             {
702                settings.language = option->code;
703                settingsContainer.Save();
704             }
705             delete settingsContainer;
706             delete settings;
707          }
708          ((GuiApplication)__thisModule.application).desktop.Destroy(0);
709          LanguageRestart(option->code, __thisModule.application, null, null, null, null, false);
710          return true;
711       }
712    };
713    Label lblLanguageBox { this, position = { 14, 354 }, labeledWindow = languageBox };
714    Label label1 { labeledWindow = destBox, tabCycle = true, isGroupBox = true, parent = this, inactive = false, size = Size { 458, 50 }, anchor = Anchor { left = 160, top = 96 } };
715    PathBox destBox
716    {
717       parent = label1, master = this, text = $" Destination Folder", size = Size { 336, 22 }, anchor = Anchor { left = 12, top = 20, right = 12 };
718       typeExpected = directory;
719       browseDialog = fileDialog;
720       opacity = 0;
721
722       bool NotifyModified(PathBox pathBox)
723       {
724          ComponentID c;
725          strcpy(installDir, destBox.path);
726          install.disabled = false;
727          for(c = 0; components[c].name; c++)
728          {
729             SetAvailableSpace(components[c], installDir);
730          }
731          return true;
732       }
733    };
734    /*Button button1
735    {
736       label1, this, $"Browse", altB, size = { 83, 24 }, position = { 360, 16 };
737
738       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
739       {
740          strcpy(fileDialog.filePath, installDir);
741          StripLastDirectory(installDir, fileDialog.currentDirectory);
742          if(fileDialog.Modal())
743          {
744             strcpy(installDir, fileDialog.filePath);
745             destBox.contents = installDir;
746             // TOCHECK: Should setting contents call NotifyModified?
747             destBox.NotifyModified(destBox.master, destBox);
748          }
749          return true;
750       }
751    };*/
752    EditBox label5
753    {
754       this, multiLine = true,
755       opacity = 0, borderStyle = none, inactive = true, size = { 136, 53 }, position = { 14, 96 }, noSelect = true,
756       contents = $"Select the default root\n"
757          "folder where to install\n"
758          "all components:"
759    };
760    EditBox label6
761    {
762       this, opacity = 0, borderStyle = none, inactive = true, size = { 136, 132 }, position = { 14, 152 }, noSelect = true,
763       multiLine = true,
764       contents = $"Select the optional\n"
765          "components you wish\n"
766          "to install:\n\n"
767          "You may customize the\n"
768          "install location for each\n"
769          "of them, or use the default\n"
770          "based on the main\n"
771          "destination folder."
772    };
773    EditBox label7
774    {
775       this, opacity = 0, borderStyle = none, inactive = true, size = { 136, 83 }, position = { 14, 280 }, noSelect = true,
776       multiLine = true,
777       contents = $"Select icons to install, file\n"
778       "associations, and system\n"
779       "environment modifications:"
780    };
781    Label totalSpaceLabel
782    {
783       this, anchor = { right = 72, top = 404 }, text = $"Space Required: "
784    };
785    Label totalSpaceValue
786    {
787       this, anchor = { right = 14, top = 404 }, text = "0 mb"
788    };
789    EditBox editBox1
790    {
791       label3, caption = "editBox1", opacity = 0, borderStyle = none, inactive = true, size = { 350, 35 }, position = { 256, 40 }, multiLine = true, noSelect = true, contents = $"Choose in which folder to install the Ecere SDK, which features\n"
792          "of the SDK to install, as well as where to install program icons."
793    };
794    Label label2 { parent = this, text = buildString, position = { 16, 422 }, font = { "Tahoma", 10, true }, disabled = true, opacity = 0, background = activeBorder };
795    Picture picture1
796    {
797       image = BitmapResource { ":ecere.png", alphaBlend = true }, filter = true, parent = label3, text = "picture1", anchor = Anchor { left = 16, top = 4 };
798       cursor = ((GuiApplication)__thisModule).GetCursor(hand);
799
800       bool OnLeftButtonDown(int x, int y, Modifiers mods)
801       {
802          ShellOpen("http://www.ecere.com/");
803          return true;
804       }
805    };
806    Label label4 { label3, font = { "Tahoma", 8.25f, bold = true }, /*size = { 326, 16 }, */position = { 248, 24 }, text = $"Choose Components, Locations and Install Options" };
807    DataField componentField { "CheckItem", width = 160, header = $"Component" };
808    DataField locationField { "char *", width = 108, header = $"Destination Folder", editable = true };
809    DataField reqField { "FileSize", width = 70, header = $"Req. Space", alignment = right };
810    DataField avField { "FileSize64", width = 70, header = $"Avail. Space", alignment = right };
811    DataField optionField { "CheckItem" };
812
813    DataField languageField { class(LanguageOption) };
814
815    void SetAvailableSpace(Component component, char * parentPath)
816    {
817       char path[MAX_LOCATION];
818       int c;
819       FileSize64 size = 0;
820
821       strcpy(path, parentPath);
822       PathCat(path, component.installPath);
823
824       if(component.subComponents)
825          for(c = 0; component.subComponents[c].name; c++)
826             SetAvailableSpace(component.subComponents[c], path);
827
828       while(!FileExists(path) && path[0])
829          StripLastDirectory(path, path);
830
831       if(path[0])
832          GetFreeSpace(path, &size);
833       component.row.SetData(avField, size);
834       if(!size) install.disabled = true;
835    }
836
837    FileSize ComputeSize(char * path)
838    {
839       FileSize size = 0;
840       FileAttribs attribs = FileExists(path);
841       if(attribs.isDirectory)
842       {
843          FileListing listing { path };
844          while(listing.Find())
845          {
846             if(listing.stats.attribs.isDirectory)
847                size += ComputeSize(listing.path);
848             else
849                size += listing.stats.size;
850          }
851       }
852       else
853          FileGetSize(path, &size);
854       return size;
855    }
856
857    void AddComponent(Component component, Component parent, char * parentPath)
858    {
859       DataRow row = (parent != null) ? parent.row.AddRow() : componentsBox.AddRow();
860       FileSize size = 0;
861       FileSize64 avSize = 0;
862       char path[MAX_LOCATION];
863
864       component.row = row;
865       strcpy(path, parentPath);
866       if(component.defInstallPath)
867          PathCat(path, component.defInstallPath);
868       component.parent = parent;
869
870       row.SetData(null, CheckItem { component.name, component, (component.arch == bits32 && osIS64bit) } );
871
872       if(component.defInstallPath)
873       {
874          strcpy(component.installPath, component.defInstallPath);
875          ChangeCh(component.installPath, '/', DIR_SEP);
876          row.SetData(locationField, component.installPath);
877       }
878
879       if(component.mandatory)
880       {
881          if(component.arch != bits32 || !osIS64bit) // || component == &coreSDK[gdb32])
882             componentsBox.SetDisabled(row, true);
883          /*else
884             component.selected = false;*/
885       }
886       else if(component.arch == bits32 && osIS64bit)
887          ; //component.selected = false;
888
889       componentsBox.SetCheck(row, component.selected);
890
891       if(component.dataPath)
892       {
893          char path[MAX_LOCATION];
894          strcpy(path, ":");
895          PathCat(path, component.dataPath);
896          component.size = ComputeSize(path);
897       }
898       if(component.subComponents)
899       {
900          int c;
901          for(c = 0; component.subComponents[c].name; c++)
902          {
903             Component * sub = &component.subComponents[c];
904             if(sub->available && (osIS64bit || sub->arch == bits32 || sub->arch == none))
905             {
906                AddComponent(sub, component, path);
907                size += sub->requiredSize;
908             }
909             else
910                sub->selected = false;
911          }
912       }
913
914       component.requiredSize = component.selected ? (size + component.size) : 0;
915       if(component.requiredSize)
916          row.SetData(reqField, component.requiredSize);
917
918       while(!FileExists(path) && path[0])
919          StripLastDirectory(path, path);
920
921       if(path[0])
922          GetFreeSpace(path, &avSize);
923       else
924          avSize = 0;
925       row.SetData(avField, avSize);
926       row.collapsed = true;
927    }
928
929    void AddOption(InstallOption option, InstallOption parent)
930    {
931       DataRow row = option.row = (parent != null) ? parent.row.AddRow() : optionsBox.AddRow();
932       row.SetData(null, CheckItem { option.name, option } );
933       optionsBox.SetCheck(row, option.selected);
934       if(option.subOptions)
935       {
936          int c;
937          for(c = 0; option.subOptions[c].name; c++)
938          {
939             AddOption(option.subOptions[c], option);
940          }
941       }
942       row.collapsed = true;
943    }
944
945    Installer()
946    {
947       int c;
948       char programFilesDir[MAX_LOCATION];
949       char appData[MAX_LOCATION];
950       char homeDrive[MAX_LOCATION];
951       char winDir[MAX_LOCATION];
952       char * x86 = null;
953
954       bool isAdministrator = IsAdministrator();
955
956       if(!isAdministrator)
957       {
958          options[0].available = false;
959          options[0].selected = false;
960       }
961
962       // If the SDK is already installed, use currently selected language
963       {
964          IDESettings settings = null;
965          IDESettingsContainer settingsContainer
966          {
967             driver = "JSON";
968             dataOwner = &settings;
969             dataClass = class(IDESettings);
970             allUsers = options[0].selected;
971          };
972
973          settingsContainer.Load();
974
975          if(settings.language)
976          {
977             String language = GetLanguageString();
978             if(settings.language.OnCompare(language))
979             {
980                // Relaunch the installer with previously selected language
981                LanguageRestart(settings.language, __thisModule.application, null, null, null, null, false);
982                return false;
983             }
984          }
985          delete settingsContainer;
986          delete settings;
987       }
988
989       GetEnvironment("HOMEDRIVE", homeDrive, sizeof(homeDrive));
990       GetEnvironment("windir", winDir, sizeof(winDir));
991
992       GetEnvironment(options[0].selected ? "ALLUSERSPROFILE" : "APPDATA", appData, sizeof(appData));
993
994       componentsBox.AddField(componentField);
995       componentsBox.AddField(locationField);
996       componentsBox.AddField(reqField);
997       componentsBox.AddField(avField);
998
999       optionsBox.AddField(optionField);
1000
1001       languageBox.AddField(languageField);
1002
1003       programFilesDir[0] = 0;
1004       if(GetEnvironment("ProgramFiles", programFilesDir, MAX_LOCATION))
1005       {
1006          x86 = strstr(programFilesDir, " (x86)");
1007          if(x86)
1008             osIS64bit = true;
1009       }
1010
1011       if(isAdministrator && programFilesDir[0])
1012       {
1013          if(x86)
1014          {
1015             strcpy(installDir32, programFilesDir);
1016             PathCat(installDir32, "Ecere SDK");
1017             *x86 = 0;
1018             strcpy(installDir, programFilesDir);
1019             PathCat(installDir, "Ecere SDK");
1020          }
1021          else
1022          {
1023             strcpy(installDir, programFilesDir);
1024             PathCat(installDir, "Ecere SDK");
1025             strcpy(installDir32, installDir);
1026          }
1027       }
1028       else if(homeDrive && homeDrive[0])
1029       {
1030          strcpy(installDir, homeDrive);
1031          PathCat(installDir, "Ecere SDK");
1032          strcpy(installDir32, installDir);
1033          strcat(installDir32, " (32)");
1034       }
1035       else if(winDir && winDir[0])
1036       {
1037          strcpy(installDir, winDir);
1038          PathCat(installDir, "..\\Ecere SDK");
1039          strcpy(installDir32, installDir);
1040          strcat(installDir32, " (32)");
1041       }
1042       else
1043       {
1044          strcpy(installDir, "C:\\Ecere SDK");
1045          strcpy(installDir32, installDir);
1046          strcat(installDir32, " (32)");
1047       }
1048
1049       if(appData && appData[0])
1050       {
1051          static char defSamplesPath[MAX_LOCATION];
1052          static char defExtrasPath[MAX_LOCATION];
1053          strcpy(defSamplesPath, appData);
1054          PathCat(defSamplesPath, "Ecere SDK\\Samples");
1055          components[ComponentID::samples].defInstallPath = defSamplesPath;
1056
1057          strcpy(defExtrasPath, appData);
1058          PathCat(defExtrasPath, "Ecere SDK\\extras");
1059          additional[AdditionalID::extras].defInstallPath = defExtrasPath;
1060       }
1061
1062       destBox.path = installDir;
1063
1064       {
1065          ComponentID c;
1066          for(c = 0; components[c].name; c++)
1067             AddComponent(components[c], null, installDir);
1068       }
1069
1070       // Compute size apart because ToggleCheck will change it
1071       totalSize = 0;
1072       {
1073          ComponentID c;
1074          for(c = 0; components[c].name; c++)
1075             totalSize += components[c].requiredSize;
1076       }
1077       {
1078          char sizeString[100];
1079          PrintSize(sizeString, totalSize, 2);
1080          totalSpaceValue.text = sizeString;
1081       }
1082       for(c = 0; options[c].name; c++)
1083       {
1084          if(options[c].available)
1085             AddOption(options[c], null);
1086       }
1087    }
1088
1089    void OnDestroy()
1090    {
1091       for(l : languages)
1092          delete l.res;
1093    }
1094
1095    bool OnPostCreate()
1096    {
1097       dotLbl.position.x = licenseButton.position.x + licenseButton.size.w - 4;
1098       return true;
1099    }
1100
1101    bool OnCreate()
1102    {
1103       // Constructor happens before Languages is instantiated...
1104       for(l : languages)
1105       {
1106          l.res = { l.bitmap, window = this };
1107          incref l.res;
1108       }
1109
1110       if(!loaded)
1111       {
1112          String language = GetLanguageString();
1113          bool found = false;
1114          DataRow row;
1115
1116          // Try to find country-specific language first
1117          for(l : languages)
1118          {
1119             LanguageOption option = l;
1120             row = languageBox.AddRow();
1121             row.SetData(null, option); // TOFIX: l used directly here
1122
1123             if(!found && (!strcmpi(l.code, language) || (!row.GetPrevRow() && !strcmpi("en", language))))
1124             {
1125                languageBox.currentRow = row;
1126                found = true;
1127             }
1128          }
1129
1130          // Try generalizing locale
1131          if(!found)
1132          {
1133             char * under;
1134             char genericLocale[256];
1135             strncpy(genericLocale, language, sizeof(genericLocale));
1136             genericLocale[sizeof(genericLocale)] = 0;
1137
1138             under = strchr(genericLocale, '_');
1139             if(under)
1140                *under = 0;
1141             if(!strcmpi(genericLocale, "zh"))
1142                strcpy(genericLocale, "zh_CN");
1143             if(strcmp(genericLocale, language))
1144             {
1145                row = languageBox.firstRow;
1146                for(l : languages)
1147                {
1148                   if(!strcmpi(l.code, genericLocale) || (!row.GetPrevRow() && !strcmpi("en", genericLocale)))
1149                   {
1150                      languageBox.currentRow = row;
1151                      found = true;
1152                      break;
1153                   }
1154                   row = row.GetNextRow();
1155                }
1156             }
1157          }
1158
1159          if(!found)
1160             languageBox.currentRow = languageBox.firstRow;
1161          loaded = true;
1162       }
1163
1164       destBox.Activate();
1165       return true;
1166    }
1167
1168    void OnDrawOverChildren(Surface surface)
1169    {
1170       int tw = label2.size.w;
1171       surface.SetForeground(Color { 128, 128, 128 });
1172       surface.HLine(label2.position.x + tw + 6, 620, 430);
1173       surface.SetForeground(white);
1174       surface.HLine(label2.position.x + tw + 6, 621, 431);
1175       surface.PutPixel(621, 400);
1176    }
1177    Label label3
1178    {
1179       parent = this, opacity = 0, borderStyle = deep, size = Size { 644, 93 }, anchor = Anchor { left = -8, top = -8 };
1180    };
1181 }
1182
1183 class InstallProgress : Window
1184 {
1185    text = $"Ecere Software Development Kit Setup - v0.44.10 \"Ryōan-ji\" 64 Bit Edition";
1186    background = activeBorder;
1187    borderStyle = fixed;
1188    hasMinimize = true;
1189    hasClose = true;
1190    tabCycle = true;
1191    // size = Size { 640, 480 };
1192    clientSize = { 636, 476 };
1193    //clientSize = { 796, 576 };
1194    icon = { ":icon.png" };
1195
1196    Picture back { image = BitmapResource { ":ryoanji-progress.png" }, parent = this, position = { 0, 0 } };
1197    Label installing { this, position = { 32, 160 } };
1198    ProgressBar progressBar { parent = this, size = Size { 588, 24 }, anchor = Anchor { left = 24, top = 184 } };
1199    Button finish
1200    {
1201       parent = this, text = $"Install", disabled = true, isDefault = true, size = Size { 75, 23 }, anchor = Anchor { left = 432, top = 440 };
1202
1203       NotifyClicked = ButtonCloseDialog
1204    };
1205    Button cancel
1206    {
1207       this, text = $"Cancel", hotKey = altX, size = Size { 75, 23 }, anchor = Anchor { left = 544, top = 440 };
1208
1209       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1210       {
1211          abortInstall = true;
1212          return true;
1213       }
1214    };
1215    EditBox titleInfo
1216    {
1217       inactive = true, noSelect = true,
1218       multiLine = true, parent = label3, opacity = 0, borderStyle = none, size = Size { 350, 35 }, anchor = Anchor { horz = 111, vert = 13 },
1219       contents = $"Please wait while the Ecere Software Development Kit is being installed."
1220    };
1221    Label label2 { parent = this, text = buildString, position = { 16, 422 }, font = { "Tahoma", 10, true }, disabled = true, opacity = 0, background = activeBorder };
1222    Picture picture1
1223    {
1224       image = BitmapResource { ":ecere.png", alphaBlend = true }, filter = true, parent = label3, anchor = Anchor { left = 16, top = 4 };
1225       cursor = ((GuiApplication)__thisModule).GetCursor(hand);
1226
1227       bool OnLeftButtonDown(int x, int y, Modifiers mods)
1228       {
1229          ShellOpen("http://www.ecere.com/");
1230          return true;
1231       }
1232    };
1233    Label title { parent = label3, text = $"Installing the Ecere SDK", font = FontResource { "Tahoma", 8.25f, bold = true }, size = Size { 326, 16 }, anchor = Anchor { horz = 91, vert = -12 } };
1234
1235    void OnDrawOverChildren(Surface surface)
1236    {
1237       int tw = label2.size.w;
1238       surface.SetForeground(Color { 128, 128, 128 });
1239       surface.HLine(label2.position.x + tw + 6, 620, 430);
1240       surface.SetForeground(white);
1241       surface.HLine(label2.position.x + tw + 6, 621, 431);
1242       surface.PutPixel(621, 400);
1243    }
1244
1245    Label label3
1246    {
1247       parent = this, opacity = 0, borderStyle = deep, size = Size { 644, 93 }, anchor = Anchor { left = -8, top = -8 };
1248    };
1249    InstallThread thread
1250    {
1251    };
1252 }
1253
1254 Installer installer {};
1255 InstallProgress installProgress { autoCreate = false };
1256
1257 static void AddPath(char * sysPaths[200], int sysCount, char * paths[200], int * count, char * oldPath, char * userPath, char * path)
1258 {
1259    int p;
1260    bool found = false;
1261    for(p = 0; p<sysCount; p++)
1262       if(!fstrcmp(sysPaths[p], path))
1263       {
1264          found = true;
1265          break;
1266       }
1267    for(p = 0; !found && p<*count; p++)
1268       if(!fstrcmp(paths[p], path))
1269       {
1270          found = true;
1271          break;
1272       }
1273    if(!found)
1274    {
1275       char * start;
1276       if(*count)
1277       {
1278          strcat(userPath, ";");
1279          start = paths[(*count)-1] + strlen(paths[(*count)-1])+1;
1280       }
1281       else
1282          start = oldPath;
1283
1284       strcpy(start, path);
1285       *(start + strlen(path)) = '\0';
1286       paths[(*count)++] = start;
1287
1288       strcat(userPath, path);
1289    }
1290 }
1291
1292
1293 void ModifyPath(char * systemPath, char * userPath)
1294 {
1295    char oldPath[8192], * paths[200], * sysPaths[200];
1296    int count, sysCount = 0;
1297
1298    if(userPath)
1299    {
1300       strcpy(oldPath, userPath);
1301       count = TokenizeWith(oldPath, sizeof(paths) / sizeof(char *), paths, ";", false);
1302       sysCount = TokenizeWith(systemPath, sizeof(sysPaths) / sizeof(char *), sysPaths, ";", false);
1303    }
1304    else
1305    {
1306       strcpy(oldPath, systemPath);
1307       count = TokenizeWith(oldPath, sizeof(paths) / sizeof(char *), paths, ";", false);
1308    }
1309
1310    {
1311       CoreSDKID c;
1312       for(c = 0; coreSDK[c].name; c++)
1313       {
1314          bool found = false;
1315          char path[MAX_LOCATION];
1316          if(!coreSDK[c].selected) continue;
1317          coreSDK[c].GetFullPath(path, false);
1318          if(c != ide && c != runtime && c != ec &&
1319             c != ide32 && c != runtime32 && c != ec32)
1320          {
1321             if(!pathOptions[PathOptions::AddMinGWPaths].available || !pathOptions[PathOptions::AddMinGWPaths].selected)
1322                continue;
1323             PathCat(path, "bin");
1324          }
1325          else if(!pathOptions[PathOptions::AddECEREPaths].selected) continue;
1326
1327          AddPath(sysPaths, sysCount, paths, &count, oldPath, userPath ? userPath : systemPath, path);
1328       }
1329    }
1330    {
1331       AdditionalID c;
1332       // Up to C++
1333       for(c = 0; c <= cpp; c++)
1334       {
1335          bool found = false;
1336          char path[MAX_LOCATION];
1337          if(!additional[c].selected || c == vanilla || c == vanilla32 || c == extras) continue;
1338          if((c != eda && c != eda32 && c != upx) && (!pathOptions[PathOptions::AddMinGWPaths].available || !pathOptions[PathOptions::AddMinGWPaths].selected))
1339             continue;
1340          additional[c].GetFullPath(path, false);
1341          if(c != eda && c != eda32 && c != upx)
1342             PathCat(path, "bin");
1343          AddPath(sysPaths, sysCount, paths, &count, oldPath, userPath ? userPath : systemPath, path);
1344       }
1345    }
1346 }
1347
1348 void AssociateExtension(char * extension, char * description, char *name, char * action, char * path)
1349 {
1350    HKEY key;
1351    uint status, size;
1352    char keyName[1024];
1353
1354    RegCreateKeyEx(HKEY_CLASSES_ROOT, extension, 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1355    RegSetValueEx(key, null, 0, REG_SZ, name, (uint)strlen(name)+1);
1356    RegCloseKey(key);
1357
1358    RegCreateKeyEx(HKEY_CLASSES_ROOT, name, 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1359    RegSetValueEx(key, null, 0, REG_SZ, description, (uint)strlen(description)+1);
1360    RegCloseKey(key);
1361
1362    sprintf(keyName, "%s\\shell", extension);
1363    RegCreateKeyEx(HKEY_CLASSES_ROOT, keyName, 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1364    RegSetValueEx(key, null, 0, REG_SZ, action, (uint)strlen(action)+1);
1365    RegCloseKey(key);
1366
1367    sprintf(keyName, "%s\\shell\\%s", name, action);
1368    RegCreateKeyEx(HKEY_CLASSES_ROOT, keyName, 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1369    RegCloseKey(key);
1370
1371    sprintf(keyName, "%s\\shell\\%s\\command", name, action);
1372    RegCreateKeyEx(HKEY_CLASSES_ROOT, keyName, 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1373
1374    sprintf(keyName, path);
1375    strcat(keyName, " \"%L\"");
1376    {
1377       uint16 wKeyName[2048];
1378       UTF8toUTF16Buffer(keyName, wKeyName, sizeof(wKeyName) / sizeof(uint16));
1379       RegSetValueExW(key, null, 0, REG_SZ, (byte *)wKeyName, (uint)(wcslen(wKeyName) + 1)*sizeof(uint16));
1380    }
1381    RegCloseKey(key);
1382 }
1383
1384 class InstallThread : Thread
1385 {
1386    unsigned int Main()
1387    {
1388       ComponentID c;
1389       ((GuiApplication)__thisModule).Lock();
1390       installProgress.progressBar.range = totalSize;
1391
1392       if(!osIS64bit)
1393          strcpy(installDir32, installDir);
1394
1395       for(c = 0; components[c].name && !abortInstall; c++)
1396          components[c].Install(installDir, installDir32);
1397       if(abortInstall)
1398       {
1399          installProgress.progressBar.range = 0;
1400          installProgress.finish.Destroy(0);
1401          installProgress.cancel.text = $"Close";
1402          installProgress.cancel.isDefault = true;
1403          installProgress.cancel.disabled = false;
1404          installProgress.cancel.NotifyClicked = Window::ButtonCloseDialog;
1405          installProgress.installing.text = $"Installation Cancelled.";
1406          installProgress.title.text = $"Installation Cancelled";
1407          installProgress.titleInfo.contents = $"The installation was not completed.";
1408       }
1409       else
1410       {
1411          char idePath[MAX_LOCATION];
1412          char userProfile[MAX_LOCATION];
1413
1414          // Configure IDE
1415          IDESettings settings = null; // Don't instantiate yet so we can pick up old settings
1416
1417          IDESettingsContainer settingsContainer
1418          {
1419             driver = "JSON";
1420             dataOwner = &settings;
1421             dataClass = class(IDESettings);
1422             allUsers = options[0].selected;
1423          };
1424          CompilerConfig compiler;
1425          installProgress.installing.text = $"Configuring Ecere IDE...";
1426          ((GuiApplication)__thisModule).Unlock();
1427          ((GuiApplication)__thisModule).SignalEvent();
1428
1429          settingsContainer.Load();
1430          compiler = settings.GetCompilerConfig(defaultCompilerName);
1431          if(compiler)
1432          {
1433             {
1434                CoreSDKID c;
1435                for(c = 0; coreSDK[c].name; c++)
1436                {
1437                   char path[MAX_LOCATION];
1438                   if(!coreSDK[c].selected || !coreSDK[c].available) continue;
1439
1440                   coreSDK[c].GetFullPath(path, false);
1441                   if(c != ide && c != runtime && c != ec &&
1442                      c != ide32 && c != runtime32 && c != ec32)
1443                      PathCat(path, "bin");
1444                   MakeSlashPath(path);
1445                   if((c == ide && osIS64bit) || (c == ide32 && !osIS64bit))
1446                   {
1447                      coreSDK[c].GetFullPath(idePath, false);
1448                      PathCat(idePath, "ide.exe");
1449                   }
1450
1451                   if(c == runtime || c == runtime32)
1452                   {
1453                      if(!compiler.libraryDirs.Find(path))
1454                         compiler.libraryDirs.Add(CopyString(path));
1455                   }
1456                }
1457             }
1458             {
1459                AdditionalID c;
1460                // Up to C++
1461                for(c = 0; c <= cpp; c++)
1462                {
1463                   char path[MAX_LOCATION];
1464                   if(c == extras || !additional[c].selected || !additional[c].available) continue;
1465                   additional[c].GetFullPath(path, false);
1466                   if(c != upx && c != eda && c != vanilla && c != eda32 && c != vanilla32)
1467                      PathCat(path, "bin");
1468                   MakeSlashPath(path);
1469                   if(c == vanilla || c == vanilla32)
1470                   {
1471                      if(!compiler.libraryDirs.Find(path))
1472                         compiler.libraryDirs.Add(CopyString(path));
1473                   }
1474                   else
1475                   {
1476                      if(!compiler.executableDirs.Find(path))
1477                         compiler.executableDirs.Add(CopyString(path));
1478                   }
1479                }
1480             }
1481          }
1482
1483          {
1484             char path[MAX_LOCATION] = "";
1485
1486             if(components[ComponentID::samples].selected)
1487                components[ComponentID::samples].GetFullPath(path, false);
1488             // IDE will now default to HOME for the default project/files locations
1489
1490             if(!settings.ideProjectFileDialogLocation[0])
1491                settings.ideProjectFileDialogLocation = path;
1492             if(!settings.ideFileDialogLocation[0])
1493                settings.ideFileDialogLocation = path;
1494
1495             if(documentation[DocumentationID::apiRef].selected)
1496             {
1497                documentation[DocumentationID::apiRef].GetFullPath(path, false);
1498                if(!settings.docDir[0])
1499                   settings.docDir = path;
1500             }
1501          }
1502
1503          settings.language = GetLanguageString();
1504
1505          // Set LANGUAGE environment variable
1506          {
1507             HKEY key = null;
1508             uint16 wLanguage[256];
1509             HRESULT status;
1510             wLanguage[0] = 0;
1511
1512             if(options[0].selected)
1513                RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", 0, KEY_ALL_ACCESS, &key);
1514             else
1515                RegCreateKeyEx(HKEY_CURRENT_USER, "Environment", 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1516             if(key)
1517             {
1518                UTF8toUTF16Buffer(settings.language, wLanguage, sizeof(wLanguage) / sizeof(uint16));
1519                RegSetValueExW(key, L"ECERE_LANGUAGE", 0, REG_EXPAND_SZ, (byte *)wLanguage, (uint)(wcslen(wLanguage)+1) * 2);
1520                RegCloseKey(key);
1521             }
1522          }
1523
1524          settingsContainer.Save();
1525          delete settingsContainer;
1526          delete settings;
1527
1528          // Set up Uninstaller
1529          ((GuiApplication)__thisModule).Lock();
1530          installProgress.installing.text = $"Registering uninstaller...";
1531          ((GuiApplication)__thisModule).Unlock();
1532          ((GuiApplication)__thisModule).SignalEvent();
1533
1534          {
1535             HKEY key;
1536             uint status, size;
1537             char * displayName = "Ecere SDK 0.44";
1538             char uninstaller[MAX_LOCATION];
1539             bool nomodify = true;
1540
1541             strcpy(uninstaller, installDir);
1542             PathCat(uninstaller, "uninstall_ecere.exe");
1543
1544             RegCreateKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Ecere SDK", 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1545
1546             RegSetValueEx(key, "DisplayName", 0, REG_SZ, displayName, (uint)strlen(displayName)+1);
1547             RegSetValueEx(key, "UninstallString", 0, REG_SZ, uninstaller, (uint)strlen(uninstaller)+1);
1548             RegSetValueEx(key, "DisplayIcon", 0, REG_SZ, idePath, (uint)strlen(idePath)+1);
1549             //RegSetValueEx(key, "NoModify", 0, REG_DWORD, (byte *)&nomodify, sizeof(nomodify));
1550             //RegSetValueEx(key, "NoRepair", 0, REG_DWORD, (byte *)&nomodify, sizeof(nomodify));
1551             RegCloseKey(key);
1552          }
1553
1554          // Add paths
1555          if(pathOptions[PathOptions::AddECEREPaths].selected
1556 #ifndef NOMINGW
1557             || pathOptions[PathOptions::AddMinGWPaths].selected
1558 #endif
1559             )
1560          {
1561             HKEY userKey = null, systemKey = null;
1562             uint status, size;
1563             char userPath[8192] = "";
1564             char systemPath[8192] = "";
1565             uint16 wUserPath[8192];
1566             uint16 wSystemPath[8192];
1567
1568             wUserPath[0] = 0;
1569             wSystemPath[0] = 0;
1570
1571             ((GuiApplication)__thisModule).Lock();
1572             installProgress.installing.text = "Registering paths...";
1573             ((GuiApplication)__thisModule).Unlock();
1574             ((GuiApplication)__thisModule).SignalEvent();
1575
1576             if(options[0].selected)
1577             {
1578                if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", 0, KEY_ALL_ACCESS, &systemKey) == ERROR_SUCCESS)
1579                {
1580                   size = sizeof(wSystemPath);
1581                   RegQueryValueExW(systemKey, L"path", null, null, (byte *)wSystemPath, &size);
1582                   UTF16toUTF8Buffer(wSystemPath, systemPath, sizeof(systemPath));
1583                   ModifyPath(systemPath, null);
1584
1585                   UTF8toUTF16Buffer(systemPath, wSystemPath, sizeof(wSystemPath) / sizeof(uint16));
1586                   RegSetValueExW(systemKey, L"path", 0, REG_EXPAND_SZ, (byte *)wSystemPath, (uint)(wcslen(wSystemPath)+1) * 2);
1587                   RegCloseKey(systemKey);
1588                }
1589             }
1590             else
1591             {
1592                if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", 0, KEY_QUERY_VALUE, &systemKey) == ERROR_SUCCESS)
1593                {
1594                   size = sizeof(wSystemPath);
1595                   RegQueryValueExW(systemKey, L"path", null, null, (byte *)wSystemPath, &size);
1596                   UTF16toUTF8Buffer(wSystemPath, systemPath, sizeof(systemPath));
1597                   RegCloseKey(systemKey);
1598                }
1599
1600                RegCreateKeyEx(HKEY_CURRENT_USER, "Environment", 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &userKey, &status);
1601                if(status == REG_OPENED_EXISTING_KEY)
1602                {
1603                   size = sizeof(wUserPath);
1604                   RegQueryValueExW(userKey, L"path", null, null, (byte *)wUserPath, &size);
1605                   UTF16toUTF8Buffer(wUserPath, userPath, sizeof(userPath));
1606                }
1607                ModifyPath(systemPath, userPath);
1608                UTF8toUTF16Buffer(userPath, wUserPath, sizeof(wUserPath) / sizeof(uint16));
1609                RegSetValueExW(userKey, L"path", 0, REG_EXPAND_SZ, (byte *)wUserPath, (uint)(wcslen(wUserPath)+1) * 2);
1610                RegCloseKey(userKey);
1611             }
1612             // SendMessageTimeout (HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)"Environment", SMTO_NORMAL, 1000, NULL);
1613          }
1614
1615          // Install Program Group Icons
1616          GetEnvironment(options[0].selected ? "ALLUSERSPROFILE" : "USERPROFILE", userProfile, sizeof(userProfile));
1617
1618          if(options[IconOptions::StartMenuIcon].selected)
1619          {
1620             char destPath[MAX_LOCATION];
1621             char startMenuPath[MAX_LOCATION] = "";
1622             HKEY key;
1623
1624             ((GuiApplication)__thisModule).Lock();
1625             installProgress.installing.text = $"Installing Start Menu Icons...";
1626             ((GuiApplication)__thisModule).Unlock();
1627             ((GuiApplication)__thisModule).SignalEvent();
1628
1629             strcpy(destPath, userProfile);
1630
1631             if(RegOpenKeyEx(options[0].selected ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER,
1632                "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", 0, KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
1633             {
1634                uint16 wStartMenuPath[2048];
1635                uint size = sizeof(wStartMenuPath);
1636                // RegQueryValueEx(key, "Start Menu", null, null, startMenuPath, &size);
1637                RegQueryValueExW(key, options[0].selected ? L"Common Programs" : L"Programs", null, null, (byte *)wStartMenuPath, &size);
1638                UTF16toUTF8Buffer(wStartMenuPath, startMenuPath, sizeof(startMenuPath));
1639                RegCloseKey(key);
1640             }
1641             if(!startMenuPath[0] && userProfile[0])
1642             {
1643                strcpy(startMenuPath, userProfile);
1644                PathCat(startMenuPath, "Start Menu\\Programs");
1645             }
1646
1647             if(startMenuPath[0])
1648             {
1649                strcpy(destPath, startMenuPath);
1650                PathCat(destPath, "Ecere SDK");
1651                MakeDir(destPath);
1652
1653                strcpy(destPath, startMenuPath);
1654                PathCat(destPath, "Ecere SDK\\Ecere IDE.lnk");
1655                CreateLink(idePath, destPath, null); //"Ecere IDE");
1656                if(components[ComponentID::samples].selected)
1657                {
1658                   char samplesPath[MAX_LOCATION] = "";
1659                   components[ComponentID::samples].GetFullPath(samplesPath, false);
1660
1661                   strcpy(destPath, startMenuPath);
1662                   PathCat(destPath, "Ecere SDK\\Sample Projects.lnk");
1663                   CreateLink(samplesPath, destPath, null);//"Sample Projects");
1664                }
1665                if(components[ComponentID::documentation].selected && documentation[DocumentationID::ecereBook].selected)
1666                {
1667                   char docPath[MAX_LOCATION] = "";
1668                   documentation[DocumentationID::ecereBook].GetFullPath(docPath, false);
1669                   PathCat(docPath, "Ecere Tao of Programming [work in progress].pdf");
1670
1671                   {
1672                      char tao[MAX_LOCATION] ;
1673                      documentation[DocumentationID::ecereBook].GetFullPath(tao, false);
1674                      PathCat(tao, "tao.pdf");
1675                      RenameFile(tao, docPath);
1676                   }
1677
1678                   strcpy(destPath, startMenuPath);
1679                   PathCat(destPath, "Ecere SDK\\The Ecere Tao of Programming.lnk");
1680                   CreateLink(docPath, destPath, null);
1681                }
1682             }
1683          }
1684
1685          // Install Desktop Icon
1686          if(options[IconOptions::DesktopIcon].selected)
1687          {
1688             HKEY key;
1689             char desktopPath[MAX_LOCATION];
1690
1691             if(RegOpenKeyEx(options[0].selected ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER,
1692                "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", 0, KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
1693             {
1694                uint16 wDesktopPath[MAX_LOCATION];
1695                uint size = sizeof(wDesktopPath);
1696                RegQueryValueExW(key, options[0].selected ? L"Common Desktop" : L"Desktop", null, null, (byte *)wDesktopPath, &size);
1697                UTF16toUTF8Buffer(wDesktopPath, desktopPath, sizeof(desktopPath));
1698                RegCloseKey(key);
1699             }
1700             if(!desktopPath[0] && userProfile[0])
1701             {
1702                strcpy(desktopPath, userProfile);
1703                PathCat(desktopPath, "Desktop");
1704             }
1705
1706             if(desktopPath[0])
1707             {
1708                PathCat(desktopPath, "Ecere IDE.lnk");
1709
1710                ((GuiApplication)__thisModule).Lock();
1711                installProgress.installing.text = $"Installing Desktop Icon...";
1712                ((GuiApplication)__thisModule).Unlock();
1713                ((GuiApplication)__thisModule).SignalEvent();
1714
1715                CreateLink(idePath, desktopPath, null);//"Ecere IDE");
1716             }
1717          }
1718
1719          SendMessageTimeout (HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)"Environment", SMTO_NORMAL, 1000, NULL);
1720
1721          // Install QuickLaunch Icon
1722          if(options[IconOptions::QuickLaunchIcon].selected)
1723          {
1724             char appData[MAX_LOCATION];
1725             GetEnvironment("APPDATA", appData, sizeof(appData));
1726             if(appData[0])
1727             {
1728                char destPath[MAX_LOCATION];
1729
1730                if(appData[0] && options[0].selected)
1731                {
1732                   char dir[MAX_FILENAME];
1733                   GetLastDirectory(appData, dir);
1734                   if(!strcmpi(dir, "Roaming"))
1735                      PathCat(appData, "../../../Default/AppData/Roaming");
1736                   else
1737                      PathCat(appData, "../Default");
1738                }
1739
1740                ((GuiApplication)__thisModule).Lock();
1741                installProgress.installing.text = $"Installing Quicklaunch Icon...";
1742                ((GuiApplication)__thisModule).Unlock();
1743                ((GuiApplication)__thisModule).SignalEvent();
1744
1745                strcpy(destPath, appData);
1746                PathCat(destPath, "Microsoft\\Internet Explorer\\Quick Launch\\Ecere IDE.lnk");
1747                CreateLink(idePath, destPath, null);
1748
1749                // Set it up on the dock for Windows 7 -- not working
1750                /*
1751                StripLastDirectory(destPath, destPath);
1752                PathCat(destPath, "User Pinned\\TaskBar");
1753                MakeDir(destPath);
1754                PathCat(destPath, "Ecere IDE.lnk");
1755                CreateLink(idePath, destPath, null);
1756                */
1757             }
1758          }
1759
1760          // Register File Types
1761          if(associateOptions[AssociateOptions::AssociateEPJ].selected ||
1762             associateOptions[AssociateOptions::AssociateEC].selected ||
1763             associateOptions[AssociateOptions::AssociateC].selected ||
1764             associateOptions[AssociateOptions::AssociateCPP].selected ||
1765             associateOptions[AssociateOptions::AssociateTXT].selected ||
1766             associateOptions[AssociateOptions::Associate3DS].selected ||
1767             associateOptions[AssociateOptions::AssociateIMG].selected)
1768          {
1769             ((GuiApplication)__thisModule).Lock();
1770             installProgress.installing.text = $"Resgistering File Types...";
1771             ((GuiApplication)__thisModule).Unlock();
1772             ((GuiApplication)__thisModule).SignalEvent();
1773
1774             if(associateOptions[AssociateOptions::AssociateEPJ].selected)
1775             {
1776                AssociateExtension(".epj", "Ecere IDE Project", "epj_file", "Open", idePath);
1777             }
1778             if(associateOptions[AssociateOptions::AssociateEC].selected)
1779             {
1780                AssociateExtension(".ec", "eC Source File", "ec_file", "Open", idePath);
1781                AssociateExtension(".eh", "eC Header File", "eh_file", "Open", idePath);
1782             }
1783             if(associateOptions[AssociateOptions::AssociateC].selected)
1784             {
1785                AssociateExtension(".c", "C Source File", "c_file", "Open", idePath);
1786                AssociateExtension(".h", "C Header File", "h_file", "Open", idePath);
1787             }
1788             if(associateOptions[AssociateOptions::AssociateCPP].selected)
1789             {
1790                AssociateExtension(".cpp", "C++ Source File", "cpp_file", "Open", idePath);
1791                AssociateExtension(".cc", "C++ Source File", "cpp_file", "Open", idePath);
1792                AssociateExtension(".cxx", "C++ Source File", "cpp_file", "Open", idePath);
1793                AssociateExtension(".chh", "C++ Header File", "chh_file", "Open", idePath);
1794                AssociateExtension(".hh", "C++ Header File", "chh_file", "Open", idePath);
1795                AssociateExtension(".hxx", "C++ Header File", "chh_file", "Open", idePath);
1796             }
1797             if(associateOptions[AssociateOptions::AssociateTXT].selected)
1798             {
1799                AssociateExtension(".txt", "Text File", "txt_file", "Open", idePath);
1800             }
1801             if(associateOptions[AssociateOptions::Associate3DS].selected)
1802             {
1803                AssociateExtension(".3ds", "3D Studio Model", "3ds_file", "View", idePath);
1804             }
1805             if(associateOptions[AssociateOptions::AssociateIMG].selected)
1806             {
1807                AssociateExtension(".bmp", "BMP Image", "bmp_file", "View", idePath);
1808                AssociateExtension(".pcx", "PCX Image", "pcx_file", "View", idePath);
1809                AssociateExtension(".png", "PNG Image", "png_file", "View", idePath);
1810                AssociateExtension(".jpg", "JPEG Image", "jpg_file", "View", idePath);
1811                AssociateExtension(".jpeg", "JPEG Image", "jpg_file", "View", idePath);
1812                AssociateExtension(".gif", "GIF Image", "gif_file", "View", idePath);
1813             }
1814          }
1815          ((GuiApplication)__thisModule).Lock();
1816
1817          installProgress.cancel.Destroy(0);
1818          installProgress.finish.text = $"Finish";
1819          installProgress.finish.disabled = false;
1820          installProgress.finish.Activate();
1821          installProgress.installing.text = $"Installation Complete.";
1822          installProgress.title.text = $"Installation Complete";
1823          installProgress.titleInfo.contents = $"Thank you for using the Ecere SDK.";
1824       }
1825       ((GuiApplication)__thisModule).Unlock();
1826       return 0;
1827    }
1828 }