i18n: Checking ECERE_LANGUAGE first; Setting environment variable from IDE and installer
[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          for(l : languages)
1114          {
1115             LanguageOption option = l;
1116             DataRow row = languageBox.AddRow();
1117             row.SetData(null, option); // TOFIX: l used directly here
1118             if(!language.OnCompare(l.code))
1119                languageBox.currentRow = row;
1120          }
1121          if(!languageBox.currentRow)
1122             languageBox.currentRow = languageBox.firstRow;
1123          loaded = true;
1124       }
1125
1126       destBox.Activate();
1127       return true;
1128    }
1129
1130    void OnDrawOverChildren(Surface surface)
1131    {
1132       int tw = label2.size.w;
1133       surface.SetForeground(Color { 128, 128, 128 });
1134       surface.HLine(label2.position.x + tw + 6, 620, 430);
1135       surface.SetForeground(white);
1136       surface.HLine(label2.position.x + tw + 6, 621, 431);
1137       surface.PutPixel(621, 400);
1138    }
1139    Label label3
1140    {
1141       parent = this, opacity = 0, borderStyle = deep, size = Size { 644, 93 }, anchor = Anchor { left = -8, top = -8 };
1142    };
1143 }
1144
1145 class InstallProgress : Window
1146 {
1147    text = $"Ecere Software Development Kit Setup - v0.44.10 \"Ryōan-ji\" 64 Bit Edition";
1148    background = activeBorder;
1149    borderStyle = fixed;
1150    hasMinimize = true;
1151    hasClose = true;
1152    tabCycle = true;
1153    // size = Size { 640, 480 };
1154    clientSize = { 636, 476 };
1155    //clientSize = { 796, 576 };
1156    icon = { ":icon.png" };
1157
1158    Picture back { image = BitmapResource { ":ryoanji-progress.png" }, parent = this, position = { 0, 0 } };
1159    Label installing { this, position = { 32, 160 } };
1160    ProgressBar progressBar { parent = this, size = Size { 588, 24 }, anchor = Anchor { left = 24, top = 184 } };
1161    Button finish
1162    {
1163       parent = this, text = $"Install", disabled = true, isDefault = true, size = Size { 75, 23 }, anchor = Anchor { left = 432, top = 440 };
1164
1165       NotifyClicked = ButtonCloseDialog
1166    };
1167    Button cancel
1168    {
1169       this, text = $"Cancel", hotKey = altX, size = Size { 75, 23 }, anchor = Anchor { left = 544, top = 440 };
1170
1171       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1172       {
1173          abortInstall = true;
1174          return true;
1175       }
1176    };
1177    EditBox titleInfo
1178    {
1179       inactive = true, noSelect = true,
1180       multiLine = true, parent = label3, opacity = 0, borderStyle = none, size = Size { 350, 35 }, anchor = Anchor { horz = 111, vert = 13 },
1181       contents = $"Please wait while the Ecere Software Development Kit is being installed."
1182    };
1183    Label label2 { parent = this, text = buildString, position = { 16, 422 }, font = { "Tahoma", 10, true }, disabled = true, opacity = 0, background = activeBorder };
1184    Picture picture1
1185    {
1186       image = BitmapResource { ":ecere.png", alphaBlend = true }, filter = true, parent = label3, anchor = Anchor { left = 16, top = 4 };
1187       cursor = ((GuiApplication)__thisModule).GetCursor(hand);
1188
1189       bool OnLeftButtonDown(int x, int y, Modifiers mods)
1190       {
1191          ShellOpen("http://www.ecere.com/");
1192          return true;
1193       }
1194    };
1195    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 } };
1196
1197    void OnDrawOverChildren(Surface surface)
1198    {
1199       int tw = label2.size.w;
1200       surface.SetForeground(Color { 128, 128, 128 });
1201       surface.HLine(label2.position.x + tw + 6, 620, 430);
1202       surface.SetForeground(white);
1203       surface.HLine(label2.position.x + tw + 6, 621, 431);
1204       surface.PutPixel(621, 400);
1205    }
1206
1207    Label label3
1208    {
1209       parent = this, opacity = 0, borderStyle = deep, size = Size { 644, 93 }, anchor = Anchor { left = -8, top = -8 };
1210    };
1211    InstallThread thread
1212    {
1213    };
1214 }
1215
1216 Installer installer {};
1217 InstallProgress installProgress { autoCreate = false };
1218
1219 static void AddPath(char * sysPaths[200], int sysCount, char * paths[200], int * count, char * oldPath, char * userPath, char * path)
1220 {
1221    int p;
1222    bool found = false;
1223    for(p = 0; p<sysCount; p++)
1224       if(!fstrcmp(sysPaths[p], path))
1225       {
1226          found = true;
1227          break;
1228       }
1229    for(p = 0; !found && p<*count; p++)
1230       if(!fstrcmp(paths[p], path))
1231       {
1232          found = true;
1233          break;
1234       }
1235    if(!found)
1236    {
1237       char * start;
1238       if(*count)
1239       {
1240          strcat(userPath, ";");
1241          start = paths[(*count)-1] + strlen(paths[(*count)-1])+1;
1242       }
1243       else
1244          start = oldPath;
1245
1246       strcpy(start, path);
1247       *(start + strlen(path)) = '\0';
1248       paths[(*count)++] = start;
1249
1250       strcat(userPath, path);
1251    }
1252 }
1253
1254
1255 void ModifyPath(char * systemPath, char * userPath)
1256 {
1257    char oldPath[8192], * paths[200], * sysPaths[200];
1258    int count, sysCount = 0;
1259
1260    if(userPath)
1261    {
1262       strcpy(oldPath, userPath);
1263       count = TokenizeWith(oldPath, sizeof(paths) / sizeof(char *), paths, ";", false);
1264       sysCount = TokenizeWith(systemPath, sizeof(sysPaths) / sizeof(char *), sysPaths, ";", false);
1265    }
1266    else
1267    {
1268       strcpy(oldPath, systemPath);
1269       count = TokenizeWith(oldPath, sizeof(paths) / sizeof(char *), paths, ";", false);
1270    }
1271
1272    {
1273       CoreSDKID c;
1274       for(c = 0; coreSDK[c].name; c++)
1275       {
1276          bool found = false;
1277          char path[MAX_LOCATION];
1278          if(!coreSDK[c].selected) continue;
1279          coreSDK[c].GetFullPath(path, false);
1280          if(c != ide && c != runtime && c != ec &&
1281             c != ide32 && c != runtime32 && c != ec32)
1282          {
1283             if(!pathOptions[PathOptions::AddMinGWPaths].available || !pathOptions[PathOptions::AddMinGWPaths].selected)
1284                continue;
1285             PathCat(path, "bin");
1286          }
1287          else if(!pathOptions[PathOptions::AddECEREPaths].selected) continue;
1288
1289          AddPath(sysPaths, sysCount, paths, &count, oldPath, userPath ? userPath : systemPath, path);
1290       }
1291    }
1292    {
1293       AdditionalID c;
1294       // Up to C++
1295       for(c = 0; c <= cpp; c++)
1296       {
1297          bool found = false;
1298          char path[MAX_LOCATION];
1299          if(!additional[c].selected || c == vanilla || c == vanilla32 || c == extras) continue;
1300          if((c != eda && c != eda32 && c != upx) && (!pathOptions[PathOptions::AddMinGWPaths].available || !pathOptions[PathOptions::AddMinGWPaths].selected))
1301             continue;
1302          additional[c].GetFullPath(path, false);
1303          if(c != eda && c != eda32 && c != upx)
1304             PathCat(path, "bin");
1305          AddPath(sysPaths, sysCount, paths, &count, oldPath, userPath ? userPath : systemPath, path);
1306       }
1307    }
1308 }
1309
1310 void AssociateExtension(char * extension, char * description, char *name, char * action, char * path)
1311 {
1312    HKEY key;
1313    uint status, size;
1314    char keyName[1024];
1315
1316    RegCreateKeyEx(HKEY_CLASSES_ROOT, extension, 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1317    RegSetValueEx(key, null, 0, REG_SZ, name, (uint)strlen(name)+1);
1318    RegCloseKey(key);
1319
1320    RegCreateKeyEx(HKEY_CLASSES_ROOT, name, 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1321    RegSetValueEx(key, null, 0, REG_SZ, description, (uint)strlen(description)+1);
1322    RegCloseKey(key);
1323
1324    sprintf(keyName, "%s\\shell", extension);
1325    RegCreateKeyEx(HKEY_CLASSES_ROOT, keyName, 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1326    RegSetValueEx(key, null, 0, REG_SZ, action, (uint)strlen(action)+1);
1327    RegCloseKey(key);
1328
1329    sprintf(keyName, "%s\\shell\\%s", name, action);
1330    RegCreateKeyEx(HKEY_CLASSES_ROOT, keyName, 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1331    RegCloseKey(key);
1332
1333    sprintf(keyName, "%s\\shell\\%s\\command", name, action);
1334    RegCreateKeyEx(HKEY_CLASSES_ROOT, keyName, 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1335
1336    sprintf(keyName, path);
1337    strcat(keyName, " \"%L\"");
1338    {
1339       uint16 wKeyName[2048];
1340       UTF8toUTF16Buffer(keyName, wKeyName, sizeof(wKeyName) / sizeof(uint16));
1341       RegSetValueExW(key, null, 0, REG_SZ, (byte *)wKeyName, (uint)(wcslen(wKeyName) + 1)*sizeof(uint16));
1342    }
1343    RegCloseKey(key);
1344 }
1345
1346 class InstallThread : Thread
1347 {
1348    unsigned int Main()
1349    {
1350       ComponentID c;
1351       ((GuiApplication)__thisModule).Lock();
1352       installProgress.progressBar.range = totalSize;
1353
1354       if(!osIS64bit)
1355          strcpy(installDir32, installDir);
1356
1357       for(c = 0; components[c].name && !abortInstall; c++)
1358          components[c].Install(installDir, installDir32);
1359       if(abortInstall)
1360       {
1361          installProgress.progressBar.range = 0;
1362          installProgress.finish.Destroy(0);
1363          installProgress.cancel.text = $"Close";
1364          installProgress.cancel.isDefault = true;
1365          installProgress.cancel.disabled = false;
1366          installProgress.cancel.NotifyClicked = Window::ButtonCloseDialog;
1367          installProgress.installing.text = $"Installation Cancelled.";
1368          installProgress.title.text = $"Installation Cancelled";
1369          installProgress.titleInfo.contents = $"The installation was not completed.";
1370       }
1371       else
1372       {
1373          char idePath[MAX_LOCATION];
1374          char userProfile[MAX_LOCATION];
1375
1376          // Configure IDE
1377          IDESettings settings = null; // Don't instantiate yet so we can pick up old settings
1378
1379          IDESettingsContainer settingsContainer
1380          {
1381             driver = "JSON";
1382             dataOwner = &settings;
1383             dataClass = class(IDESettings);
1384             allUsers = options[0].selected;
1385          };
1386          CompilerConfig compiler;
1387          installProgress.installing.text = $"Configuring Ecere IDE...";
1388          ((GuiApplication)__thisModule).Unlock();
1389          ((GuiApplication)__thisModule).SignalEvent();
1390
1391          settingsContainer.Load();
1392          compiler = settings.GetCompilerConfig(defaultCompilerName);
1393          if(compiler)
1394          {
1395             {
1396                CoreSDKID c;
1397                for(c = 0; coreSDK[c].name; c++)
1398                {
1399                   char path[MAX_LOCATION];
1400                   if(!coreSDK[c].selected || !coreSDK[c].available) continue;
1401
1402                   coreSDK[c].GetFullPath(path, false);
1403                   if(c != ide && c != runtime && c != ec &&
1404                      c != ide32 && c != runtime32 && c != ec32)
1405                      PathCat(path, "bin");
1406                   MakeSlashPath(path);
1407                   if((c == ide && osIS64bit) || (c == ide32 && !osIS64bit))
1408                   {
1409                      coreSDK[c].GetFullPath(idePath, false);
1410                      PathCat(idePath, "ide.exe");
1411                   }
1412
1413                   if(c == runtime || c == runtime32)
1414                   {
1415                      if(!compiler.libraryDirs.Find(path))
1416                         compiler.libraryDirs.Add(CopyString(path));
1417                   }
1418                }
1419             }
1420             {
1421                AdditionalID c;
1422                // Up to C++
1423                for(c = 0; c <= cpp; c++)
1424                {
1425                   char path[MAX_LOCATION];
1426                   if(c == extras || !additional[c].selected || !additional[c].available) continue;
1427                   additional[c].GetFullPath(path, false);
1428                   if(c != upx && c != eda && c != vanilla && c != eda32 && c != vanilla32)
1429                      PathCat(path, "bin");
1430                   MakeSlashPath(path);
1431                   if(c == vanilla || c == vanilla32)
1432                   {
1433                      if(!compiler.libraryDirs.Find(path))
1434                         compiler.libraryDirs.Add(CopyString(path));
1435                   }
1436                   else
1437                   {
1438                      if(!compiler.executableDirs.Find(path))
1439                         compiler.executableDirs.Add(CopyString(path));
1440                   }
1441                }
1442             }
1443          }
1444
1445          {
1446             char path[MAX_LOCATION] = "";
1447
1448             if(components[ComponentID::samples].selected)
1449                components[ComponentID::samples].GetFullPath(path, false);
1450             // IDE will now default to HOME for the default project/files locations
1451
1452             if(!settings.ideProjectFileDialogLocation[0])
1453                settings.ideProjectFileDialogLocation = path;
1454             if(!settings.ideFileDialogLocation[0])
1455                settings.ideFileDialogLocation = path;
1456
1457             if(documentation[DocumentationID::apiRef].selected)
1458             {
1459                documentation[DocumentationID::apiRef].GetFullPath(path, false);
1460                if(!settings.docDir[0])
1461                   settings.docDir = path;
1462             }
1463          }
1464
1465          settings.language = GetLanguageString();
1466
1467          // Set LANGUAGE environment variable
1468          {
1469             HKEY key = null;
1470             uint16 wLanguage[256];
1471             HRESULT status;
1472             wLanguage[0] = 0;
1473
1474             if(options[0].selected)
1475                RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", 0, KEY_ALL_ACCESS, &key);
1476             else
1477                RegCreateKeyEx(HKEY_CURRENT_USER, "Environment", 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1478             if(key)
1479             {
1480                UTF8toUTF16Buffer(settings.language, wLanguage, sizeof(wLanguage) / sizeof(uint16));
1481                RegSetValueExW(key, L"ECERE_LANGUAGE", 0, REG_EXPAND_SZ, (byte *)wLanguage, (uint)(wcslen(wLanguage)+1) * 2);
1482                RegCloseKey(key);
1483             }
1484          }
1485
1486          settingsContainer.Save();
1487          delete settingsContainer;
1488          delete settings;
1489
1490          // Set up Uninstaller
1491          ((GuiApplication)__thisModule).Lock();
1492          installProgress.installing.text = $"Registering uninstaller...";
1493          ((GuiApplication)__thisModule).Unlock();
1494          ((GuiApplication)__thisModule).SignalEvent();
1495
1496          {
1497             HKEY key;
1498             uint status, size;
1499             char * displayName = "Ecere SDK 0.44";
1500             char uninstaller[MAX_LOCATION];
1501             bool nomodify = true;
1502
1503             strcpy(uninstaller, installDir);
1504             PathCat(uninstaller, "uninstall_ecere.exe");
1505
1506             RegCreateKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Ecere SDK", 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1507
1508             RegSetValueEx(key, "DisplayName", 0, REG_SZ, displayName, (uint)strlen(displayName)+1);
1509             RegSetValueEx(key, "UninstallString", 0, REG_SZ, uninstaller, (uint)strlen(uninstaller)+1);
1510             RegSetValueEx(key, "DisplayIcon", 0, REG_SZ, idePath, (uint)strlen(idePath)+1);
1511             //RegSetValueEx(key, "NoModify", 0, REG_DWORD, (byte *)&nomodify, sizeof(nomodify));
1512             //RegSetValueEx(key, "NoRepair", 0, REG_DWORD, (byte *)&nomodify, sizeof(nomodify));
1513             RegCloseKey(key);
1514          }
1515
1516          // Add paths
1517          if(pathOptions[PathOptions::AddECEREPaths].selected
1518 #ifndef NOMINGW
1519             || pathOptions[PathOptions::AddMinGWPaths].selected
1520 #endif
1521             )
1522          {
1523             HKEY userKey = null, systemKey = null;
1524             uint status, size;
1525             char userPath[8192] = "";
1526             char systemPath[8192] = "";
1527             uint16 wUserPath[8192];
1528             uint16 wSystemPath[8192];
1529
1530             wUserPath[0] = 0;
1531             wSystemPath[0] = 0;
1532
1533             ((GuiApplication)__thisModule).Lock();
1534             installProgress.installing.text = "Registering paths...";
1535             ((GuiApplication)__thisModule).Unlock();
1536             ((GuiApplication)__thisModule).SignalEvent();
1537
1538             if(options[0].selected)
1539             {
1540                if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", 0, KEY_ALL_ACCESS, &systemKey) == ERROR_SUCCESS)
1541                {
1542                   size = sizeof(wSystemPath);
1543                   RegQueryValueExW(systemKey, L"path", null, null, (byte *)wSystemPath, &size);
1544                   UTF16toUTF8Buffer(wSystemPath, systemPath, sizeof(systemPath));
1545                   ModifyPath(systemPath, null);
1546
1547                   UTF8toUTF16Buffer(systemPath, wSystemPath, sizeof(wSystemPath) / sizeof(uint16));
1548                   RegSetValueExW(systemKey, L"path", 0, REG_EXPAND_SZ, (byte *)wSystemPath, (uint)(wcslen(wSystemPath)+1) * 2);
1549                   RegCloseKey(systemKey);
1550                }
1551             }
1552             else
1553             {
1554                if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", 0, KEY_QUERY_VALUE, &systemKey) == ERROR_SUCCESS)
1555                {
1556                   size = sizeof(wSystemPath);
1557                   RegQueryValueExW(systemKey, L"path", null, null, (byte *)wSystemPath, &size);
1558                   UTF16toUTF8Buffer(wSystemPath, systemPath, sizeof(systemPath));
1559                   RegCloseKey(systemKey);
1560                }
1561
1562                RegCreateKeyEx(HKEY_CURRENT_USER, "Environment", 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &userKey, &status);
1563                if(status == REG_OPENED_EXISTING_KEY)
1564                {
1565                   size = sizeof(wUserPath);
1566                   RegQueryValueExW(userKey, L"path", null, null, (byte *)wUserPath, &size);
1567                   UTF16toUTF8Buffer(wUserPath, userPath, sizeof(userPath));
1568                }
1569                ModifyPath(systemPath, userPath);
1570                UTF8toUTF16Buffer(userPath, wUserPath, sizeof(wUserPath) / sizeof(uint16));
1571                RegSetValueExW(userKey, L"path", 0, REG_EXPAND_SZ, (byte *)wUserPath, (uint)(wcslen(wUserPath)+1) * 2);
1572                RegCloseKey(userKey);
1573             }
1574             // SendMessageTimeout (HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)"Environment", SMTO_NORMAL, 1000, NULL);
1575          }
1576
1577          // Install Program Group Icons
1578          GetEnvironment(options[0].selected ? "ALLUSERSPROFILE" : "USERPROFILE", userProfile, sizeof(userProfile));
1579
1580          if(options[IconOptions::StartMenuIcon].selected)
1581          {
1582             char destPath[MAX_LOCATION];
1583             char startMenuPath[MAX_LOCATION] = "";
1584             HKEY key;
1585
1586             ((GuiApplication)__thisModule).Lock();
1587             installProgress.installing.text = $"Installing Start Menu Icons...";
1588             ((GuiApplication)__thisModule).Unlock();
1589             ((GuiApplication)__thisModule).SignalEvent();
1590
1591             strcpy(destPath, userProfile);
1592
1593             if(RegOpenKeyEx(options[0].selected ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER,
1594                "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", 0, KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
1595             {
1596                uint16 wStartMenuPath[2048];
1597                uint size = sizeof(wStartMenuPath);
1598                // RegQueryValueEx(key, "Start Menu", null, null, startMenuPath, &size);
1599                RegQueryValueExW(key, options[0].selected ? L"Common Programs" : L"Programs", null, null, (byte *)wStartMenuPath, &size);
1600                UTF16toUTF8Buffer(wStartMenuPath, startMenuPath, sizeof(startMenuPath));
1601                RegCloseKey(key);
1602             }
1603             if(!startMenuPath[0] && userProfile[0])
1604             {
1605                strcpy(startMenuPath, userProfile);
1606                PathCat(startMenuPath, "Start Menu\\Programs");
1607             }
1608
1609             if(startMenuPath[0])
1610             {
1611                strcpy(destPath, startMenuPath);
1612                PathCat(destPath, "Ecere SDK");
1613                MakeDir(destPath);
1614
1615                strcpy(destPath, startMenuPath);
1616                PathCat(destPath, "Ecere SDK\\Ecere IDE.lnk");
1617                CreateLink(idePath, destPath, null); //"Ecere IDE");
1618                if(components[ComponentID::samples].selected)
1619                {
1620                   char samplesPath[MAX_LOCATION] = "";
1621                   components[ComponentID::samples].GetFullPath(samplesPath, false);
1622
1623                   strcpy(destPath, startMenuPath);
1624                   PathCat(destPath, "Ecere SDK\\Sample Projects.lnk");
1625                   CreateLink(samplesPath, destPath, null);//"Sample Projects");
1626                }
1627                if(components[ComponentID::documentation].selected && documentation[DocumentationID::ecereBook].selected)
1628                {
1629                   char docPath[MAX_LOCATION] = "";
1630                   documentation[DocumentationID::ecereBook].GetFullPath(docPath, false);
1631                   PathCat(docPath, "Ecere Tao of Programming [work in progress].pdf");
1632
1633                   {
1634                      char tao[MAX_LOCATION] ;
1635                      documentation[DocumentationID::ecereBook].GetFullPath(tao, false);
1636                      PathCat(tao, "tao.pdf");
1637                      RenameFile(tao, docPath);
1638                   }
1639
1640                   strcpy(destPath, startMenuPath);
1641                   PathCat(destPath, "Ecere SDK\\The Ecere Tao of Programming.lnk");
1642                   CreateLink(docPath, destPath, null);
1643                }
1644             }
1645          }
1646
1647          // Install Desktop Icon
1648          if(options[IconOptions::DesktopIcon].selected)
1649          {
1650             HKEY key;
1651             char desktopPath[MAX_LOCATION];
1652
1653             if(RegOpenKeyEx(options[0].selected ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER,
1654                "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", 0, KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
1655             {
1656                uint16 wDesktopPath[MAX_LOCATION];
1657                uint size = sizeof(wDesktopPath);
1658                RegQueryValueExW(key, options[0].selected ? L"Common Desktop" : L"Desktop", null, null, (byte *)wDesktopPath, &size);
1659                UTF16toUTF8Buffer(wDesktopPath, desktopPath, sizeof(desktopPath));
1660                RegCloseKey(key);
1661             }
1662             if(!desktopPath[0] && userProfile[0])
1663             {
1664                strcpy(desktopPath, userProfile);
1665                PathCat(desktopPath, "Desktop");
1666             }
1667
1668             if(desktopPath[0])
1669             {
1670                PathCat(desktopPath, "Ecere IDE.lnk");
1671
1672                ((GuiApplication)__thisModule).Lock();
1673                installProgress.installing.text = $"Installing Desktop Icon...";
1674                ((GuiApplication)__thisModule).Unlock();
1675                ((GuiApplication)__thisModule).SignalEvent();
1676
1677                CreateLink(idePath, desktopPath, null);//"Ecere IDE");
1678             }
1679          }
1680
1681          SendMessageTimeout (HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)"Environment", SMTO_NORMAL, 1000, NULL);
1682
1683          // Install QuickLaunch Icon
1684          if(options[IconOptions::QuickLaunchIcon].selected)
1685          {
1686             char appData[MAX_LOCATION];
1687             GetEnvironment("APPDATA", appData, sizeof(appData));
1688             if(appData[0])
1689             {
1690                char destPath[MAX_LOCATION];
1691
1692                if(appData[0] && options[0].selected)
1693                {
1694                   char dir[MAX_FILENAME];
1695                   GetLastDirectory(appData, dir);
1696                   if(!strcmpi(dir, "Roaming"))
1697                      PathCat(appData, "../../../Default/AppData/Roaming");
1698                   else
1699                      PathCat(appData, "../Default");
1700                }
1701
1702                ((GuiApplication)__thisModule).Lock();
1703                installProgress.installing.text = $"Installing Quicklaunch Icon...";
1704                ((GuiApplication)__thisModule).Unlock();
1705                ((GuiApplication)__thisModule).SignalEvent();
1706
1707                strcpy(destPath, appData);
1708                PathCat(destPath, "Microsoft\\Internet Explorer\\Quick Launch\\Ecere IDE.lnk");
1709                CreateLink(idePath, destPath, null);
1710
1711                // Set it up on the dock for Windows 7 -- not working
1712                /*
1713                StripLastDirectory(destPath, destPath);
1714                PathCat(destPath, "User Pinned\\TaskBar");
1715                MakeDir(destPath);
1716                PathCat(destPath, "Ecere IDE.lnk");
1717                CreateLink(idePath, destPath, null);
1718                */
1719             }
1720          }
1721
1722          // Register File Types
1723          if(associateOptions[AssociateOptions::AssociateEPJ].selected ||
1724             associateOptions[AssociateOptions::AssociateEC].selected ||
1725             associateOptions[AssociateOptions::AssociateC].selected ||
1726             associateOptions[AssociateOptions::AssociateCPP].selected ||
1727             associateOptions[AssociateOptions::AssociateTXT].selected ||
1728             associateOptions[AssociateOptions::Associate3DS].selected ||
1729             associateOptions[AssociateOptions::AssociateIMG].selected)
1730          {
1731             ((GuiApplication)__thisModule).Lock();
1732             installProgress.installing.text = $"Resgistering File Types...";
1733             ((GuiApplication)__thisModule).Unlock();
1734             ((GuiApplication)__thisModule).SignalEvent();
1735
1736             if(associateOptions[AssociateOptions::AssociateEPJ].selected)
1737             {
1738                AssociateExtension(".epj", "Ecere IDE Project", "epj_file", "Open", idePath);
1739             }
1740             if(associateOptions[AssociateOptions::AssociateEC].selected)
1741             {
1742                AssociateExtension(".ec", "eC Source File", "ec_file", "Open", idePath);
1743                AssociateExtension(".eh", "eC Header File", "eh_file", "Open", idePath);
1744             }
1745             if(associateOptions[AssociateOptions::AssociateC].selected)
1746             {
1747                AssociateExtension(".c", "C Source File", "c_file", "Open", idePath);
1748                AssociateExtension(".h", "C Header File", "h_file", "Open", idePath);
1749             }
1750             if(associateOptions[AssociateOptions::AssociateCPP].selected)
1751             {
1752                AssociateExtension(".cpp", "C++ Source File", "cpp_file", "Open", idePath);
1753                AssociateExtension(".cc", "C++ Source File", "cpp_file", "Open", idePath);
1754                AssociateExtension(".cxx", "C++ Source File", "cpp_file", "Open", idePath);
1755                AssociateExtension(".chh", "C++ Header File", "chh_file", "Open", idePath);
1756                AssociateExtension(".hh", "C++ Header File", "chh_file", "Open", idePath);
1757                AssociateExtension(".hxx", "C++ Header File", "chh_file", "Open", idePath);
1758             }
1759             if(associateOptions[AssociateOptions::AssociateTXT].selected)
1760             {
1761                AssociateExtension(".txt", "Text File", "txt_file", "Open", idePath);
1762             }
1763             if(associateOptions[AssociateOptions::Associate3DS].selected)
1764             {
1765                AssociateExtension(".3ds", "3D Studio Model", "3ds_file", "View", idePath);
1766             }
1767             if(associateOptions[AssociateOptions::AssociateIMG].selected)
1768             {
1769                AssociateExtension(".bmp", "BMP Image", "bmp_file", "View", idePath);
1770                AssociateExtension(".pcx", "PCX Image", "pcx_file", "View", idePath);
1771                AssociateExtension(".png", "PNG Image", "png_file", "View", idePath);
1772                AssociateExtension(".jpg", "JPEG Image", "jpg_file", "View", idePath);
1773                AssociateExtension(".jpeg", "JPEG Image", "jpg_file", "View", idePath);
1774                AssociateExtension(".gif", "GIF Image", "gif_file", "View", idePath);
1775             }
1776          }
1777          ((GuiApplication)__thisModule).Lock();
1778
1779          installProgress.cancel.Destroy(0);
1780          installProgress.finish.text = $"Finish";
1781          installProgress.finish.disabled = false;
1782          installProgress.finish.Activate();
1783          installProgress.installing.text = $"Installation Complete.";
1784          installProgress.title.text = $"Installation Complete";
1785          installProgress.titleInfo.contents = $"Thank you for using the Ecere SDK.";
1786       }
1787       ((GuiApplication)__thisModule).Unlock();
1788       return 0;
1789    }
1790 }