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