installer; ide: (#712) Added language selection to 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       noDragging = true;
479       rowHeight = 18;
480       selectionColor = { 145, 150, 140 };
481
482       alwaysEdit = true;
483       opacity = 0;
484
485       bool NotifyKeyDown(ListBox listBox, DataRow row, Key key, unichar ch)
486       {
487          if(key == f2 && browse.visible)
488             browse.NotifyClicked(this, browse, 0, 0, 0);
489          return true;
490       }
491
492       bool NotifyChanged(ListBox listBox, DataRow row)
493       {
494          Component * component = ((CheckItem *)listBox.GetData(componentField))->data;
495          char path[MAX_LOCATION], relative[MAX_LOCATION] = "", * newPath;
496          char fullPath[MAX_LOCATION];
497
498          component->parent->GetFullPath(path, false);
499          strcpy(fullPath, path);
500
501          newPath = row.GetData(locationField);
502          if(newPath)
503          {
504             PathCat(fullPath, newPath);
505             if(IsPathInsideOf(fullPath, path))
506                MakePathRelative(fullPath, path, relative);
507             else
508                strcpy(relative, fullPath);
509          }
510          listBox.SetData(locationField, relative);
511          strcpy(component->installPath, relative);
512
513          //SetAvailableSpace(component, path);
514          {
515             ComponentID c;
516             install.disabled = false;
517             for(c = 0; components[c].name; c++)
518             {
519                SetAvailableSpace(components[c], installDir);
520             }
521          }
522          return true;
523       }
524
525       bool NotifyEditDone(ListBox listBox, DataRow row)
526       {
527          browse.Destroy(0);
528          return true;
529       }
530
531       bool NotifyEdited(ListBox listBox, DataRow row)
532       {
533          Window e;
534          browse.parent = listBox;
535          browse.position = { componentField.width + locationField.width + 18, (listBox.currentIndex+1) * listBox.rowHeight - 2 };
536          browse.size = { 30, listBox.rowHeight + 3 };
537          for(e = listBox.firstChild; e; e = e.next)
538          {
539             if(e._class == class(DataBox))
540                break;
541          }
542          if(e)
543             e.Activate();
544          browse.Create();
545          return true;
546       }
547
548       void NotifyChecked(CheckListBox listBox, DataRow row)
549       {
550          Component * component = ((CheckItem *)row.GetData(componentField))->data;
551          int c;
552          component->selected = listBox.IsChecked(row);
553
554          if(!component->parent) totalSize -= component->requiredSize;
555          component->requiredSize = 0;
556          if(component->selected)
557          {
558             component->requiredSize += component->size;
559             if(component->subComponents)
560                for(c = 0; component->subComponents[c].name; c++)
561                   component->requiredSize += component->subComponents[c].requiredSize;
562          }
563          if(component->requiredSize)
564          {
565             uint requiredSize = component->requiredSize;
566             row.SetData(reqField, requiredSize);
567          }
568          else
569             row.UnsetData(reqField);
570          if(!component->parent)
571          {
572             totalSize += component->requiredSize;
573             {
574                char sizeString[100];
575                PrintSize(sizeString, totalSize, 2);
576                totalSpaceValue.text = sizeString;
577             }
578          }
579       }
580    };
581    Label agreementLbl { parent = this, text = $"By installing the Ecere SDK, you agree to the", font = { "Tahoma", 8.25f }, anchor = Anchor { right = 399, top = 448 } };
582    Button licenseButton
583    {
584       this, inactive = true, offset = false, bevel = false, foreground = blue, font = { "Tahoma", 8.25f, underline = true, bold = true },
585       // text = $"terms and conditions", anchor = Anchor { left = 241, top = 421 };
586       text = $"terms and conditions", anchor = Anchor { left = 235, top = 445 };
587       cursor = ((GuiApplication)__thisModule).GetCursor(hand);
588
589       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
590       {
591          // LicenseBox { master = this, sourceFile = ":ecere-sdk/doc/LICENSE" }.Modal();
592          LicensesForm { master = this }.Modal();
593          return true;
594       }
595    };
596    Label dotLbl { parent = this, text = ".", font = { "Tahoma", 8.25f }, anchor = Anchor { left = 372, top = 448 } };
597    CheckListBox optionsBox
598    {
599       this, size = { 460, 114 }, position = { 160, 284 };
600       fullRowSelect = false, collapseControl = true, treeBranches = true, rootCollapseButton = true,
601       noDragging = true;
602       rowHeight = 18;
603       opacity = 0;
604
605       void NotifyChecked(CheckListBox listBox, DataRow row)
606       {
607          CheckItem * item = row.GetData(optionField);
608          InstallOption * option = item->data;
609          option->selected = listBox.IsChecked(row);
610          // Update default samples/extras path whether we're installing for All Users or not
611          if(option == &options[0])
612          {
613             char appData[MAX_LOCATION];
614
615             options[5].name = options[0].selected ? $"Add binaries location to the system environment paths" : $"Add binaries location to the user environment paths";
616             if(options[5].row)
617                ((CheckItem *)options[5].row.GetData(optionField))->name = options[5].name;
618
619             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";
620             if(pathOptions[PathOptions::AddECEREPaths].row)
621                ((CheckItem *)pathOptions[PathOptions::AddECEREPaths].row.GetData(optionField))->name = pathOptions[PathOptions::AddECEREPaths].name;
622
623             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";
624             if(pathOptions[PathOptions::AddMinGWPaths].row)
625                ((CheckItem *)pathOptions[PathOptions::AddMinGWPaths].row.GetData(optionField))->name = pathOptions[PathOptions::AddMinGWPaths].name;
626
627             GetEnvironment(options[0].selected ? "ALLUSERSPROFILE" : "APPDATA", appData, sizeof(appData));
628             if(appData && appData[0])
629             {
630                char defPath[MAX_LOCATION];
631
632                char * s = components[ComponentID::samples].installPath;
633                strcpy(defPath, installDir);
634                PathCat(defPath, components[ComponentID::samples].defInstallPath);
635                ChangeCh(defPath, '/', DIR_SEP);
636                if(!strcmp(defPath, components[ComponentID::samples].installPath))
637                {
638                   static char defSamplesPath[MAX_LOCATION];
639                   strcpy(defSamplesPath, appData);
640                   PathCat(defSamplesPath, "Ecere SDK\\Samples");
641                   components[ComponentID::samples].defInstallPath = defSamplesPath;
642
643                   strcpy(components[ComponentID::samples].installPath, components[ComponentID::samples].defInstallPath);
644                   ChangeCh(components[ComponentID::samples].installPath, '/', DIR_SEP);
645                   components[ComponentID::samples].row.SetData(locationField, components[ComponentID::samples].installPath);
646                }
647
648                strcpy(defPath, installDir);
649                PathCat(defPath, additional[AdditionalID::extras].defInstallPath);
650                ChangeCh(defPath, '/', DIR_SEP);
651                if(!strcmp(additional[AdditionalID::extras].installPath, additional[AdditionalID::extras].installPath))
652                {
653                   static char defExtrasPath[MAX_LOCATION];
654                   strcpy(defExtrasPath, appData);
655                   PathCat(defExtrasPath, "Ecere SDK\\extras");
656                   additional[AdditionalID::extras].defInstallPath = defExtrasPath;
657
658                   strcpy(additional[AdditionalID::extras].installPath, additional[AdditionalID::extras].defInstallPath);
659                   ChangeCh(additional[AdditionalID::extras].installPath, '/', DIR_SEP);
660                   additional[AdditionalID::extras].row.SetData(locationField, additional[AdditionalID::extras].installPath);
661                }
662             }
663          }
664       }
665    };
666    Button install
667    {
668       parent = this, text = $"Install", isDefault = true, size = { 75, 23 }, position = { 432, 440 };
669
670       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
671       {
672          installProgress.Create();
673          Destroy(0);
674          // installProgress.thread.Main();
675          installProgress.thread.Create();
676          return true;
677       }
678    };
679    Button button3 { parent = this, text = $"Cancel", hotKey = altX, size = Size { 75, 23 }, anchor = Anchor { left = 544, top = 440 }, NotifyClicked = ButtonCloseDialog };
680    DropBox languageBox
681    {
682       this, position = { 14, 374 }, size = { 142, 0 }, caption = "Language:";
683
684       bool NotifySelect(DropBox dropBox, DataRow row, Modifiers mods)
685       {
686          LanguageOption * option = row.GetData(null);
687          // If the language is already set, we need to override it
688          {
689             IDESettings settings = null;
690             IDESettingsContainer settingsContainer
691             {
692                driver = "JSON";
693                dataOwner = &settings;
694                dataClass = class(IDESettings);
695                allUsers = options[0].selected;
696             };
697             settingsContainer.Load();
698             if(settings.language)
699             {
700                settings.language = option->code;
701                settingsContainer.Save();
702             }
703             delete settingsContainer;
704             delete settings;
705          }
706          ((GuiApplication)__thisModule.application).desktop.Destroy(0);
707          LanguageRestart(option->code, __thisModule.application, null, null, null, null, false);
708          return true;
709       }
710    };
711    Label lblLanguageBox { this, position = { 14, 354 }, labeledWindow = languageBox };
712    Label label1 { labeledWindow = destBox, tabCycle = true, isGroupBox = true, parent = this, inactive = false, size = Size { 458, 50 }, anchor = Anchor { left = 160, top = 96 } };
713    PathBox destBox
714    {
715       parent = label1, master = this, text = $" Destination Folder", size = Size { 336, 22 }, anchor = Anchor { left = 12, top = 20, right = 12 };
716       typeExpected = directory;
717       browseDialog = fileDialog;
718       opacity = 0;
719
720       bool NotifyModified(PathBox pathBox)
721       {
722          ComponentID c;
723          strcpy(installDir, destBox.path);
724          install.disabled = false;
725          for(c = 0; components[c].name; c++)
726          {
727             SetAvailableSpace(components[c], installDir);
728          }
729          return true;
730       }
731    };
732    /*Button button1
733    {
734       label1, this, $"Browse", altB, size = { 83, 24 }, position = { 360, 16 };
735
736       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
737       {
738          strcpy(fileDialog.filePath, installDir);
739          StripLastDirectory(installDir, fileDialog.currentDirectory);
740          if(fileDialog.Modal())
741          {
742             strcpy(installDir, fileDialog.filePath);
743             destBox.contents = installDir;
744             // TOCHECK: Should setting contents call NotifyModified?
745             destBox.NotifyModified(destBox.master, destBox);
746          }
747          return true;
748       }
749    };*/
750    EditBox label5
751    {
752       this, multiLine = true,
753       opacity = 0, borderStyle = none, inactive = true, size = { 136, 53 }, position = { 14, 96 }, noSelect = true,
754       contents = $"Select the default root\n"
755          "folder where to install\n"
756          "all components:"
757    };
758    EditBox label6
759    {
760       this, opacity = 0, borderStyle = none, inactive = true, size = { 136, 132 }, position = { 14, 152 }, noSelect = true,
761       multiLine = true,
762       contents = $"Select the optional\n"
763          "components you wish\n"
764          "to install:\n\n"
765          "You may customize the\n"
766          "install location for each\n"
767          "of them, or use the default\n"
768          "based on the main\n"
769          "destination folder."
770    };
771    EditBox label7
772    {
773       this, opacity = 0, borderStyle = none, inactive = true, size = { 136, 83 }, position = { 14, 280 }, noSelect = true,
774       multiLine = true,
775       contents = $"Select icons to install, file\n"
776       "associations, and system\n"
777       "environment modifications:"
778    };
779    Label totalSpaceLabel
780    {
781       this, anchor = { right = 72, top = 404 }, text = $"Space Required: "
782    };
783    Label totalSpaceValue
784    {
785       this, anchor = { right = 14, top = 404 }, text = "0 mb"
786    };
787    EditBox editBox1
788    {
789       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"
790          "of the SDK to install, as well as where to install program icons."
791    };
792    Label label2 { parent = this, text = buildString, position = { 16, 422 }, font = { "Tahoma", 10, true }, disabled = true, opacity = 0, background = activeBorder };
793    Picture picture1
794    {
795       image = BitmapResource { ":ecere.png", alphaBlend = true }, filter = true, parent = label3, text = "picture1", anchor = Anchor { left = 16, top = 4 };
796       cursor = ((GuiApplication)__thisModule).GetCursor(hand);
797
798       bool OnLeftButtonDown(int x, int y, Modifiers mods)
799       {
800          ShellOpen("http://www.ecere.com/");
801          return true;
802       }
803    };
804    Label label4 { label3, font = { "Tahoma", 8.25f, bold = true }, /*size = { 326, 16 }, */position = { 248, 24 }, text = $"Choose Components, Locations and Install Options" };
805    DataField componentField { "CheckItem", width = 160, header = $"Component" };
806    DataField locationField { "char *", width = 108, header = $"Destination Folder", editable = true };
807    DataField reqField { "FileSize", width = 70, header = $"Req. Space", alignment = right };
808    DataField avField { "FileSize64", width = 70, header = $"Avail. Space", alignment = right };
809    DataField optionField { "CheckItem" };
810
811    DataField languageField { class(LanguageOption) };
812
813    void SetAvailableSpace(Component component, char * parentPath)
814    {
815       char path[MAX_LOCATION];
816       int c;
817       FileSize64 size = 0;
818
819       strcpy(path, parentPath);
820       PathCat(path, component.installPath);
821
822       if(component.subComponents)
823          for(c = 0; component.subComponents[c].name; c++)
824             SetAvailableSpace(component.subComponents[c], path);
825
826       while(!FileExists(path) && path[0])
827          StripLastDirectory(path, path);
828
829       if(path[0])
830          GetFreeSpace(path, &size);
831       component.row.SetData(avField, size);
832       if(!size) install.disabled = true;
833    }
834
835    FileSize ComputeSize(char * path)
836    {
837       FileSize size = 0;
838       FileAttribs attribs = FileExists(path);
839       if(attribs.isDirectory)
840       {
841          FileListing listing { path };
842          while(listing.Find())
843          {
844             if(listing.stats.attribs.isDirectory)
845                size += ComputeSize(listing.path);
846             else
847                size += listing.stats.size;
848          }
849       }
850       else
851          FileGetSize(path, &size);
852       return size;
853    }
854
855    void AddComponent(Component component, Component parent, char * parentPath)
856    {
857       DataRow row = (parent != null) ? parent.row.AddRow() : componentsBox.AddRow();
858       FileSize size = 0;
859       FileSize64 avSize = 0;
860       char path[MAX_LOCATION];
861
862       component.row = row;
863       strcpy(path, parentPath);
864       if(component.defInstallPath)
865          PathCat(path, component.defInstallPath);
866       component.parent = parent;
867
868       row.SetData(null, CheckItem { component.name, component, (component.arch == bits32 && osIS64bit) } );
869
870       if(component.defInstallPath)
871       {
872          strcpy(component.installPath, component.defInstallPath);
873          ChangeCh(component.installPath, '/', DIR_SEP);
874          row.SetData(locationField, component.installPath);
875       }
876
877       if(component.mandatory)
878       {
879          if(component.arch != bits32 || !osIS64bit) // || component == &coreSDK[gdb32])
880             componentsBox.SetDisabled(row, true);
881          /*else
882             component.selected = false;*/
883       }
884       else if(component.arch == bits32 && osIS64bit)
885          ; //component.selected = false;
886
887       componentsBox.SetCheck(row, component.selected);
888
889       if(component.dataPath)
890       {
891          char path[MAX_LOCATION];
892          strcpy(path, ":");
893          PathCat(path, component.dataPath);
894          component.size = ComputeSize(path);
895       }
896       if(component.subComponents)
897       {
898          int c;
899          for(c = 0; component.subComponents[c].name; c++)
900          {
901             Component * sub = &component.subComponents[c];
902             if(sub->available && (osIS64bit || sub->arch == bits32 || sub->arch == none))
903             {
904                AddComponent(sub, component, path);
905                size += sub->requiredSize;
906             }
907             else
908                sub->selected = false;
909          }
910       }
911
912       component.requiredSize = component.selected ? (size + component.size) : 0;
913       if(component.requiredSize)
914          row.SetData(reqField, component.requiredSize);
915
916       while(!FileExists(path) && path[0])
917          StripLastDirectory(path, path);
918
919       if(path[0])
920          GetFreeSpace(path, &avSize);
921       else
922          avSize = 0;
923       row.SetData(avField, avSize);
924       row.collapsed = true;
925    }
926
927    void AddOption(InstallOption option, InstallOption parent)
928    {
929       DataRow row = option.row = (parent != null) ? parent.row.AddRow() : optionsBox.AddRow();
930       row.SetData(null, CheckItem { option.name, option } );
931       optionsBox.SetCheck(row, option.selected);
932       if(option.subOptions)
933       {
934          int c;
935          for(c = 0; option.subOptions[c].name; c++)
936          {
937             AddOption(option.subOptions[c], option);
938          }
939       }
940       row.collapsed = true;
941    }
942
943    Installer()
944    {
945       int c;
946       char programFilesDir[MAX_LOCATION];
947       char appData[MAX_LOCATION];
948       char homeDrive[MAX_LOCATION];
949       char winDir[MAX_LOCATION];
950       char * x86 = null;
951
952       bool isAdministrator = IsAdministrator();
953
954       if(!isAdministrator)
955       {
956          options[0].available = false;
957          options[0].selected = false;
958       }
959
960       // If the SDK is already installed, use currently selected language
961       {
962          IDESettings settings = null;
963          IDESettingsContainer settingsContainer
964          {
965             driver = "JSON";
966             dataOwner = &settings;
967             dataClass = class(IDESettings);
968             allUsers = options[0].selected;
969          };
970
971          settingsContainer.Load();
972
973          if(settings.language)
974          {
975             String language = GetLanguageString();
976             if(settings.language.OnCompare(language))
977             {
978                // Relaunch the installer with previously selected language
979                LanguageRestart(settings.language, __thisModule.application, null, null, null, null, false);
980                return false;
981             }
982          }
983          delete settingsContainer;
984          delete settings;
985       }
986
987       GetEnvironment("HOMEDRIVE", homeDrive, sizeof(homeDrive));
988       GetEnvironment("windir", winDir, sizeof(winDir));
989
990       GetEnvironment(options[0].selected ? "ALLUSERSPROFILE" : "APPDATA", appData, sizeof(appData));
991
992       componentsBox.AddField(componentField);
993       componentsBox.AddField(locationField);
994       componentsBox.AddField(reqField);
995       componentsBox.AddField(avField);
996
997       optionsBox.AddField(optionField);
998
999       languageBox.AddField(languageField);
1000
1001       programFilesDir[0] = 0;
1002       if(GetEnvironment("ProgramFiles", programFilesDir, MAX_LOCATION))
1003       {
1004          x86 = strstr(programFilesDir, " (x86)");
1005          if(x86)
1006             osIS64bit = true;
1007       }
1008
1009       if(isAdministrator && programFilesDir[0])
1010       {
1011          if(x86)
1012          {
1013             strcpy(installDir32, programFilesDir);
1014             PathCat(installDir32, "Ecere SDK");
1015             *x86 = 0;
1016             strcpy(installDir, programFilesDir);
1017             PathCat(installDir, "Ecere SDK");
1018          }
1019          else
1020          {
1021             strcpy(installDir, programFilesDir);
1022             PathCat(installDir, "Ecere SDK");
1023             strcpy(installDir32, installDir);
1024          }
1025       }
1026       else if(homeDrive && homeDrive[0])
1027       {
1028          strcpy(installDir, homeDrive);
1029          PathCat(installDir, "Ecere SDK");
1030          strcpy(installDir32, installDir);
1031          strcat(installDir32, " (32)");
1032       }
1033       else if(winDir && winDir[0])
1034       {
1035          strcpy(installDir, winDir);
1036          PathCat(installDir, "..\\Ecere SDK");
1037          strcpy(installDir32, installDir);
1038          strcat(installDir32, " (32)");
1039       }
1040       else
1041       {
1042          strcpy(installDir, "C:\\Ecere SDK");
1043          strcpy(installDir32, installDir);
1044          strcat(installDir32, " (32)");
1045       }
1046
1047       if(appData && appData[0])
1048       {
1049          static char defSamplesPath[MAX_LOCATION];
1050          static char defExtrasPath[MAX_LOCATION];
1051          strcpy(defSamplesPath, appData);
1052          PathCat(defSamplesPath, "Ecere SDK\\Samples");
1053          components[ComponentID::samples].defInstallPath = defSamplesPath;
1054
1055          strcpy(defExtrasPath, appData);
1056          PathCat(defExtrasPath, "Ecere SDK\\extras");
1057          additional[AdditionalID::extras].defInstallPath = defExtrasPath;
1058       }
1059
1060       destBox.path = installDir;
1061
1062       {
1063          ComponentID c;
1064          for(c = 0; components[c].name; c++)
1065             AddComponent(components[c], null, installDir);
1066       }
1067
1068       // Compute size apart because ToggleCheck will change it
1069       totalSize = 0;
1070       {
1071          ComponentID c;
1072          for(c = 0; components[c].name; c++)
1073             totalSize += components[c].requiredSize;
1074       }
1075       {
1076          char sizeString[100];
1077          PrintSize(sizeString, totalSize, 2);
1078          totalSpaceValue.text = sizeString;
1079       }
1080       for(c = 0; options[c].name; c++)
1081       {
1082          if(options[c].available)
1083             AddOption(options[c], null);
1084       }
1085    }
1086
1087    void OnDestroy()
1088    {
1089       for(l : languages)
1090          delete l.res;
1091    }
1092
1093    bool OnPostCreate()
1094    {
1095       dotLbl.position.x = licenseButton.position.x + licenseButton.size.w - 4;
1096       return true;
1097    }
1098
1099    bool OnCreate()
1100    {
1101       // Constructor happens before Languages is instantiated...
1102       for(l : languages)
1103       {
1104          l.res = { l.bitmap, window = this };
1105          incref l.res;
1106       }
1107
1108       if(!loaded)
1109       {
1110          String language = GetLanguageString();
1111          for(l : languages)
1112          {
1113             LanguageOption option = l;
1114             DataRow row = languageBox.AddRow();
1115             row.SetData(null, option); // TOFIX: l used directly here
1116             if(!language.OnCompare(l.code))
1117                languageBox.currentRow = row;
1118          }
1119          if(!languageBox.currentRow)
1120             languageBox.currentRow = languageBox.firstRow;
1121          loaded = true;
1122       }
1123
1124       destBox.Activate();
1125       return true;
1126    }
1127
1128    void OnDrawOverChildren(Surface surface)
1129    {
1130       int tw = label2.size.w;
1131       surface.SetForeground(Color { 128, 128, 128 });
1132       surface.HLine(label2.position.x + tw + 6, 620, 430);
1133       surface.SetForeground(white);
1134       surface.HLine(label2.position.x + tw + 6, 621, 431);
1135       surface.PutPixel(621, 400);
1136    }
1137    Label label3
1138    {
1139       parent = this, opacity = 0, borderStyle = deep, size = Size { 644, 93 }, anchor = Anchor { left = -8, top = -8 };
1140    };
1141 }
1142
1143 class InstallProgress : Window
1144 {
1145    text = $"Ecere Software Development Kit Setup - v0.44.10 \"Ryōan-ji\" 64 Bit Edition";
1146    background = activeBorder;
1147    borderStyle = fixed;
1148    hasMinimize = true;
1149    hasClose = true;
1150    tabCycle = true;
1151    // size = Size { 640, 480 };
1152    clientSize = { 636, 476 };
1153    //clientSize = { 796, 576 };
1154    icon = { ":icon.png" };
1155
1156    Picture back { image = BitmapResource { ":ryoanji-progress.png" }, parent = this, position = { 0, 0 } };
1157    Label installing { this, position = { 32, 160 } };
1158    ProgressBar progressBar { parent = this, size = Size { 588, 24 }, anchor = Anchor { left = 24, top = 184 } };
1159    Button finish
1160    {
1161       parent = this, text = $"Install", disabled = true, isDefault = true, size = Size { 75, 23 }, anchor = Anchor { left = 432, top = 440 };
1162
1163       NotifyClicked = ButtonCloseDialog
1164    };
1165    Button cancel
1166    {
1167       this, text = $"Cancel", hotKey = altX, size = Size { 75, 23 }, anchor = Anchor { left = 544, top = 440 };
1168
1169       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1170       {
1171          abortInstall = true;
1172          return true;
1173       }
1174    };
1175    EditBox titleInfo
1176    {
1177       inactive = true, noSelect = true,
1178       multiLine = true, parent = label3, opacity = 0, borderStyle = none, size = Size { 350, 35 }, anchor = Anchor { horz = 111, vert = 13 },
1179       contents = $"Please wait while the Ecere Software Development Kit is being installed."
1180    };
1181    Label label2 { parent = this, text = buildString, position = { 16, 422 }, font = { "Tahoma", 10, true }, disabled = true, opacity = 0, background = activeBorder };
1182    Picture picture1
1183    {
1184       image = BitmapResource { ":ecere.png", alphaBlend = true }, filter = true, parent = label3, anchor = Anchor { left = 16, top = 4 };
1185       cursor = ((GuiApplication)__thisModule).GetCursor(hand);
1186
1187       bool OnLeftButtonDown(int x, int y, Modifiers mods)
1188       {
1189          ShellOpen("http://www.ecere.com/");
1190          return true;
1191       }
1192    };
1193    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 } };
1194
1195    void OnDrawOverChildren(Surface surface)
1196    {
1197       int tw = label2.size.w;
1198       surface.SetForeground(Color { 128, 128, 128 });
1199       surface.HLine(label2.position.x + tw + 6, 620, 430);
1200       surface.SetForeground(white);
1201       surface.HLine(label2.position.x + tw + 6, 621, 431);
1202       surface.PutPixel(621, 400);
1203    }
1204
1205    Label label3
1206    {
1207       parent = this, opacity = 0, borderStyle = deep, size = Size { 644, 93 }, anchor = Anchor { left = -8, top = -8 };
1208    };
1209    InstallThread thread
1210    {
1211    };
1212 }
1213
1214 Installer installer {};
1215 InstallProgress installProgress { autoCreate = false };
1216
1217 static void AddPath(char * sysPaths[200], int sysCount, char * paths[200], int * count, char * oldPath, char * userPath, char * path)
1218 {
1219    int p;
1220    bool found = false;
1221    for(p = 0; p<sysCount; p++)
1222       if(!fstrcmp(sysPaths[p], path))
1223       {
1224          found = true;
1225          break;
1226       }
1227    for(p = 0; !found && p<*count; p++)
1228       if(!fstrcmp(paths[p], path))
1229       {
1230          found = true;
1231          break;
1232       }
1233    if(!found)
1234    {
1235       char * start;
1236       if(*count)
1237       {
1238          strcat(userPath, ";");
1239          start = paths[(*count)-1] + strlen(paths[(*count)-1])+1;
1240       }
1241       else
1242          start = oldPath;
1243
1244       strcpy(start, path);
1245       *(start + strlen(path)) = '\0';
1246       paths[(*count)++] = start;
1247
1248       strcat(userPath, path);
1249    }
1250 }
1251
1252
1253 void ModifyPath(char * systemPath, char * userPath)
1254 {
1255    char oldPath[8192], * paths[200], * sysPaths[200];
1256    int count, sysCount = 0;
1257
1258    if(userPath)
1259    {
1260       strcpy(oldPath, userPath);
1261       count = TokenizeWith(oldPath, sizeof(paths) / sizeof(char *), paths, ";", false);
1262       sysCount = TokenizeWith(systemPath, sizeof(sysPaths) / sizeof(char *), sysPaths, ";", false);
1263    }
1264    else
1265    {
1266       strcpy(oldPath, systemPath);
1267       count = TokenizeWith(oldPath, sizeof(paths) / sizeof(char *), paths, ";", false);
1268    }
1269
1270    {
1271       CoreSDKID c;
1272       for(c = 0; coreSDK[c].name; c++)
1273       {
1274          bool found = false;
1275          char path[MAX_LOCATION];
1276          if(!coreSDK[c].selected) continue;
1277          coreSDK[c].GetFullPath(path, false);
1278          if(c != ide && c != runtime && c != ec &&
1279             c != ide32 && c != runtime32 && c != ec32)
1280          {
1281             if(!pathOptions[PathOptions::AddMinGWPaths].available || !pathOptions[PathOptions::AddMinGWPaths].selected)
1282                continue;
1283             PathCat(path, "bin");
1284          }
1285          else if(!pathOptions[PathOptions::AddECEREPaths].selected) continue;
1286
1287          AddPath(sysPaths, sysCount, paths, &count, oldPath, userPath ? userPath : systemPath, path);
1288       }
1289    }
1290    {
1291       AdditionalID c;
1292       // Up to C++
1293       for(c = 0; c <= cpp; c++)
1294       {
1295          bool found = false;
1296          char path[MAX_LOCATION];
1297          if(!additional[c].selected || c == vanilla || c == vanilla32 || c == extras) continue;
1298          if((c != eda && c != eda32 && c != upx) && (!pathOptions[PathOptions::AddMinGWPaths].available || !pathOptions[PathOptions::AddMinGWPaths].selected))
1299             continue;
1300          additional[c].GetFullPath(path, false);
1301          if(c != eda && c != eda32 && c != upx)
1302             PathCat(path, "bin");
1303          AddPath(sysPaths, sysCount, paths, &count, oldPath, userPath ? userPath : systemPath, path);
1304       }
1305    }
1306 }
1307
1308 void AssociateExtension(char * extension, char * description, char *name, char * action, char * path)
1309 {
1310    HKEY key;
1311    uint status, size;
1312    char keyName[1024];
1313
1314    RegCreateKeyEx(HKEY_CLASSES_ROOT, extension, 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1315    RegSetValueEx(key, null, 0, REG_SZ, name, (uint)strlen(name)+1);
1316    RegCloseKey(key);
1317
1318    RegCreateKeyEx(HKEY_CLASSES_ROOT, name, 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1319    RegSetValueEx(key, null, 0, REG_SZ, description, (uint)strlen(description)+1);
1320    RegCloseKey(key);
1321
1322    sprintf(keyName, "%s\\shell", extension);
1323    RegCreateKeyEx(HKEY_CLASSES_ROOT, keyName, 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1324    RegSetValueEx(key, null, 0, REG_SZ, action, (uint)strlen(action)+1);
1325    RegCloseKey(key);
1326
1327    sprintf(keyName, "%s\\shell\\%s", name, action);
1328    RegCreateKeyEx(HKEY_CLASSES_ROOT, keyName, 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1329    RegCloseKey(key);
1330
1331    sprintf(keyName, "%s\\shell\\%s\\command", name, action);
1332    RegCreateKeyEx(HKEY_CLASSES_ROOT, keyName, 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1333
1334    sprintf(keyName, path);
1335    strcat(keyName, " \"%L\"");
1336    {
1337       uint16 wKeyName[2048];
1338       UTF8toUTF16Buffer(keyName, wKeyName, sizeof(wKeyName) / sizeof(uint16));
1339       RegSetValueExW(key, null, 0, REG_SZ, (byte *)wKeyName, (uint)(wcslen(wKeyName) + 1)*sizeof(uint16));
1340    }
1341    RegCloseKey(key);
1342 }
1343
1344 class InstallThread : Thread
1345 {
1346    unsigned int Main()
1347    {
1348       ComponentID c;
1349       ((GuiApplication)__thisModule).Lock();
1350       installProgress.progressBar.range = totalSize;
1351
1352       if(!osIS64bit)
1353          strcpy(installDir32, installDir);
1354
1355       for(c = 0; components[c].name && !abortInstall; c++)
1356          components[c].Install(installDir, installDir32);
1357       if(abortInstall)
1358       {
1359          installProgress.progressBar.range = 0;
1360          installProgress.finish.Destroy(0);
1361          installProgress.cancel.text = $"Close";
1362          installProgress.cancel.isDefault = true;
1363          installProgress.cancel.disabled = false;
1364          installProgress.cancel.NotifyClicked = Window::ButtonCloseDialog;
1365          installProgress.installing.text = $"Installation Cancelled.";
1366          installProgress.title.text = $"Installation Cancelled";
1367          installProgress.titleInfo.contents = $"The installation was not completed.";
1368       }
1369       else
1370       {
1371          char idePath[MAX_LOCATION];
1372          char userProfile[MAX_LOCATION];
1373
1374          // Configure IDE
1375          IDESettings settings = null; // Don't instantiate yet so we can pick up old settings
1376
1377          IDESettingsContainer settingsContainer
1378          {
1379             driver = "JSON";
1380             dataOwner = &settings;
1381             dataClass = class(IDESettings);
1382             allUsers = options[0].selected;
1383          };
1384          CompilerConfig compiler;
1385          installProgress.installing.text = $"Configuring Ecere IDE...";
1386          ((GuiApplication)__thisModule).Unlock();
1387          ((GuiApplication)__thisModule).SignalEvent();
1388
1389          settingsContainer.Load();
1390          compiler = settings.GetCompilerConfig(defaultCompilerName);
1391          if(compiler)
1392          {
1393             {
1394                CoreSDKID c;
1395                for(c = 0; coreSDK[c].name; c++)
1396                {
1397                   char path[MAX_LOCATION];
1398                   if(!coreSDK[c].selected || !coreSDK[c].available) continue;
1399
1400                   coreSDK[c].GetFullPath(path, false);
1401                   if(c != ide && c != runtime && c != ec &&
1402                      c != ide32 && c != runtime32 && c != ec32)
1403                      PathCat(path, "bin");
1404                   MakeSlashPath(path);
1405                   if((c == ide && osIS64bit) || (c == ide32 && !osIS64bit))
1406                   {
1407                      coreSDK[c].GetFullPath(idePath, false);
1408                      PathCat(idePath, "ide.exe");
1409                   }
1410
1411                   if(c == runtime || c == runtime32)
1412                   {
1413                      if(!compiler.libraryDirs.Find(path))
1414                         compiler.libraryDirs.Add(CopyString(path));
1415                   }
1416                }
1417             }
1418             {
1419                AdditionalID c;
1420                // Up to C++
1421                for(c = 0; c <= cpp; c++)
1422                {
1423                   char path[MAX_LOCATION];
1424                   if(c == extras || !additional[c].selected || !additional[c].available) continue;
1425                   additional[c].GetFullPath(path, false);
1426                   if(c != upx && c != eda && c != vanilla && c != eda32 && c != vanilla32)
1427                      PathCat(path, "bin");
1428                   MakeSlashPath(path);
1429                   if(c == vanilla || c == vanilla32)
1430                   {
1431                      if(!compiler.libraryDirs.Find(path))
1432                         compiler.libraryDirs.Add(CopyString(path));
1433                   }
1434                   else
1435                   {
1436                      if(!compiler.executableDirs.Find(path))
1437                         compiler.executableDirs.Add(CopyString(path));
1438                   }
1439                }
1440             }
1441          }
1442
1443          {
1444             char path[MAX_LOCATION] = "";
1445
1446             if(components[ComponentID::samples].selected)
1447                components[ComponentID::samples].GetFullPath(path, false);
1448             // IDE will now default to HOME for the default project/files locations
1449
1450             if(!settings.ideProjectFileDialogLocation[0])
1451                settings.ideProjectFileDialogLocation = path;
1452             if(!settings.ideFileDialogLocation[0])
1453                settings.ideFileDialogLocation = path;
1454
1455             if(documentation[DocumentationID::apiRef].selected)
1456             {
1457                documentation[DocumentationID::apiRef].GetFullPath(path, false);
1458                if(!settings.docDir[0])
1459                   settings.docDir = path;
1460             }
1461          }
1462
1463          settings.language = GetLanguageString();
1464
1465          settingsContainer.Save();
1466          delete settingsContainer;
1467          delete settings;
1468
1469          // Set up Uninstaller
1470          ((GuiApplication)__thisModule).Lock();
1471          installProgress.installing.text = $"Registering uninstaller...";
1472          ((GuiApplication)__thisModule).Unlock();
1473          ((GuiApplication)__thisModule).SignalEvent();
1474
1475          {
1476             HKEY key;
1477             uint status, size;
1478             char * displayName = "Ecere SDK 0.44";
1479             char uninstaller[MAX_LOCATION];
1480             bool nomodify = true;
1481
1482             strcpy(uninstaller, installDir);
1483             PathCat(uninstaller, "uninstall_ecere.exe");
1484
1485             RegCreateKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Ecere SDK", 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1486
1487             RegSetValueEx(key, "DisplayName", 0, REG_SZ, displayName, (uint)strlen(displayName)+1);
1488             RegSetValueEx(key, "UninstallString", 0, REG_SZ, uninstaller, (uint)strlen(uninstaller)+1);
1489             RegSetValueEx(key, "DisplayIcon", 0, REG_SZ, idePath, (uint)strlen(idePath)+1);
1490             //RegSetValueEx(key, "NoModify", 0, REG_DWORD, (byte *)&nomodify, sizeof(nomodify));
1491             //RegSetValueEx(key, "NoRepair", 0, REG_DWORD, (byte *)&nomodify, sizeof(nomodify));
1492             RegCloseKey(key);
1493          }
1494
1495          // Add paths
1496          if(pathOptions[PathOptions::AddECEREPaths].selected
1497 #ifndef NOMINGW
1498             || pathOptions[PathOptions::AddMinGWPaths].selected
1499 #endif
1500             )
1501          {
1502             HKEY userKey = null, systemKey = null;
1503             uint status, size;
1504             char userPath[8192] = "";
1505             char systemPath[8192] = "";
1506             uint16 wUserPath[8192];
1507             uint16 wSystemPath[8192];
1508
1509             wUserPath[0] = 0;
1510             wSystemPath[0] = 0;
1511
1512             ((GuiApplication)__thisModule).Lock();
1513             installProgress.installing.text = "Registering paths...";
1514             ((GuiApplication)__thisModule).Unlock();
1515             ((GuiApplication)__thisModule).SignalEvent();
1516
1517             if(options[0].selected)
1518             {
1519                if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", 0, KEY_ALL_ACCESS, &systemKey) == ERROR_SUCCESS)
1520                {
1521                   size = sizeof(wSystemPath);
1522                   RegQueryValueExW(systemKey, L"path", null, null, (byte *)wSystemPath, &size);
1523                   UTF16toUTF8Buffer(wSystemPath, systemPath, sizeof(systemPath));
1524                   ModifyPath(systemPath, null);
1525
1526                   UTF8toUTF16Buffer(systemPath, wSystemPath, sizeof(wSystemPath) / sizeof(uint16));
1527                   RegSetValueExW(systemKey, L"path", 0, REG_EXPAND_SZ, (byte *)wSystemPath, (uint)(wcslen(wSystemPath)+1) * 2);
1528                   RegCloseKey(systemKey);
1529                }
1530             }
1531             else
1532             {
1533                if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", 0, KEY_QUERY_VALUE, &systemKey) == ERROR_SUCCESS)
1534                {
1535                   size = sizeof(wSystemPath);
1536                   RegQueryValueExW(systemKey, L"path", null, null, (byte *)wSystemPath, &size);
1537                   UTF16toUTF8Buffer(wSystemPath, systemPath, sizeof(systemPath));
1538                   RegCloseKey(systemKey);
1539                }
1540
1541                RegCreateKeyEx(HKEY_CURRENT_USER, "Environment", 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &userKey, &status);
1542                if(status == REG_OPENED_EXISTING_KEY)
1543                {
1544                   size = sizeof(wUserPath);
1545                   RegQueryValueExW(userKey, L"path", null, null, (byte *)wUserPath, &size);
1546                   UTF16toUTF8Buffer(wUserPath, userPath, sizeof(userPath));
1547                }
1548                ModifyPath(systemPath, userPath);
1549                UTF8toUTF16Buffer(userPath, wUserPath, sizeof(wUserPath) / sizeof(uint16));
1550                RegSetValueExW(userKey, L"path", 0, REG_EXPAND_SZ, (byte *)wUserPath, (uint)(wcslen(wUserPath)+1) * 2);
1551                RegCloseKey(userKey);
1552             }
1553             SendMessageTimeout (HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)"Environment", SMTO_NORMAL, 1000, NULL);
1554          }
1555
1556          // Install Program Group Icons
1557          GetEnvironment(options[0].selected ? "ALLUSERSPROFILE" : "USERPROFILE", userProfile, sizeof(userProfile));
1558
1559          if(options[IconOptions::StartMenuIcon].selected)
1560          {
1561             char destPath[MAX_LOCATION];
1562             char startMenuPath[MAX_LOCATION] = "";
1563             HKEY key;
1564
1565             ((GuiApplication)__thisModule).Lock();
1566             installProgress.installing.text = $"Installing Start Menu Icons...";
1567             ((GuiApplication)__thisModule).Unlock();
1568             ((GuiApplication)__thisModule).SignalEvent();
1569
1570             strcpy(destPath, userProfile);
1571
1572             if(RegOpenKeyEx(options[0].selected ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER,
1573                "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", 0, KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
1574             {
1575                uint16 wStartMenuPath[2048];
1576                uint size = sizeof(wStartMenuPath);
1577                // RegQueryValueEx(key, "Start Menu", null, null, startMenuPath, &size);
1578                RegQueryValueExW(key, options[0].selected ? L"Common Programs" : L"Programs", null, null, (byte *)wStartMenuPath, &size);
1579                UTF16toUTF8Buffer(wStartMenuPath, startMenuPath, sizeof(startMenuPath));
1580                RegCloseKey(key);
1581             }
1582             if(!startMenuPath[0] && userProfile[0])
1583             {
1584                strcpy(startMenuPath, userProfile);
1585                PathCat(startMenuPath, "Start Menu\\Programs");
1586             }
1587
1588             if(startMenuPath[0])
1589             {
1590                strcpy(destPath, startMenuPath);
1591                PathCat(destPath, "Ecere SDK");
1592                MakeDir(destPath);
1593
1594                strcpy(destPath, startMenuPath);
1595                PathCat(destPath, "Ecere SDK\\Ecere IDE.lnk");
1596                CreateLink(idePath, destPath, null); //"Ecere IDE");
1597                if(components[ComponentID::samples].selected)
1598                {
1599                   char samplesPath[MAX_LOCATION] = "";
1600                   components[ComponentID::samples].GetFullPath(samplesPath, false);
1601
1602                   strcpy(destPath, startMenuPath);
1603                   PathCat(destPath, "Ecere SDK\\Sample Projects.lnk");
1604                   CreateLink(samplesPath, destPath, null);//"Sample Projects");
1605                }
1606                if(components[ComponentID::documentation].selected && documentation[DocumentationID::ecereBook].selected)
1607                {
1608                   char docPath[MAX_LOCATION] = "";
1609                   documentation[DocumentationID::ecereBook].GetFullPath(docPath, false);
1610                   PathCat(docPath, "Ecere Tao of Programming [work in progress].pdf");
1611
1612                   {
1613                      char tao[MAX_LOCATION] ;
1614                      documentation[DocumentationID::ecereBook].GetFullPath(tao, false);
1615                      PathCat(tao, "tao.pdf");
1616                      RenameFile(tao, docPath);
1617                   }
1618
1619                   strcpy(destPath, startMenuPath);
1620                   PathCat(destPath, "Ecere SDK\\The Ecere Tao of Programming.lnk");
1621                   CreateLink(docPath, destPath, null);
1622                }
1623             }
1624          }
1625
1626          // Install Desktop Icon
1627          if(options[IconOptions::DesktopIcon].selected)
1628          {
1629             HKEY key;
1630             char desktopPath[MAX_LOCATION];
1631
1632             if(RegOpenKeyEx(options[0].selected ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER,
1633                "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", 0, KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
1634             {
1635                uint16 wDesktopPath[MAX_LOCATION];
1636                uint size = sizeof(wDesktopPath);
1637                RegQueryValueExW(key, options[0].selected ? L"Common Desktop" : L"Desktop", null, null, (byte *)wDesktopPath, &size);
1638                UTF16toUTF8Buffer(wDesktopPath, desktopPath, sizeof(desktopPath));
1639                RegCloseKey(key);
1640             }
1641             if(!desktopPath[0] && userProfile[0])
1642             {
1643                strcpy(desktopPath, userProfile);
1644                PathCat(desktopPath, "Desktop");
1645             }
1646
1647             if(desktopPath[0])
1648             {
1649                PathCat(desktopPath, "Ecere IDE.lnk");
1650
1651                ((GuiApplication)__thisModule).Lock();
1652                installProgress.installing.text = $"Installing Desktop Icon...";
1653                ((GuiApplication)__thisModule).Unlock();
1654                ((GuiApplication)__thisModule).SignalEvent();
1655
1656                CreateLink(idePath, desktopPath, null);//"Ecere IDE");
1657             }
1658          }
1659
1660          // Install QuickLaunch Icon
1661          if(options[IconOptions::QuickLaunchIcon].selected)
1662          {
1663             char appData[MAX_LOCATION];
1664             GetEnvironment("APPDATA", appData, sizeof(appData));
1665             if(appData[0])
1666             {
1667                char destPath[MAX_LOCATION];
1668
1669                if(appData[0] && options[0].selected)
1670                {
1671                   char dir[MAX_FILENAME];
1672                   GetLastDirectory(appData, dir);
1673                   if(!strcmpi(dir, "Roaming"))
1674                      PathCat(appData, "../../../Default/AppData/Roaming");
1675                   else
1676                      PathCat(appData, "../Default");
1677                }
1678
1679                ((GuiApplication)__thisModule).Lock();
1680                installProgress.installing.text = $"Installing Quicklaunch Icon...";
1681                ((GuiApplication)__thisModule).Unlock();
1682                ((GuiApplication)__thisModule).SignalEvent();
1683
1684                strcpy(destPath, appData);
1685                PathCat(destPath, "Microsoft\\Internet Explorer\\Quick Launch\\Ecere IDE.lnk");
1686                CreateLink(idePath, destPath, null);
1687
1688                // Set it up on the dock for Windows 7 -- not working
1689                /*
1690                StripLastDirectory(destPath, destPath);
1691                PathCat(destPath, "User Pinned\\TaskBar");
1692                MakeDir(destPath);
1693                PathCat(destPath, "Ecere IDE.lnk");
1694                CreateLink(idePath, destPath, null);
1695                */
1696             }
1697          }
1698
1699          // Register File Types
1700          if(associateOptions[AssociateOptions::AssociateEPJ].selected ||
1701             associateOptions[AssociateOptions::AssociateEC].selected ||
1702             associateOptions[AssociateOptions::AssociateC].selected ||
1703             associateOptions[AssociateOptions::AssociateCPP].selected ||
1704             associateOptions[AssociateOptions::AssociateTXT].selected ||
1705             associateOptions[AssociateOptions::Associate3DS].selected ||
1706             associateOptions[AssociateOptions::AssociateIMG].selected)
1707          {
1708             ((GuiApplication)__thisModule).Lock();
1709             installProgress.installing.text = $"Resgistering File Types...";
1710             ((GuiApplication)__thisModule).Unlock();
1711             ((GuiApplication)__thisModule).SignalEvent();
1712
1713             if(associateOptions[AssociateOptions::AssociateEPJ].selected)
1714             {
1715                AssociateExtension(".epj", "Ecere IDE Project", "epj_file", "Open", idePath);
1716             }
1717             if(associateOptions[AssociateOptions::AssociateEC].selected)
1718             {
1719                AssociateExtension(".ec", "eC Source File", "ec_file", "Open", idePath);
1720                AssociateExtension(".eh", "eC Header File", "eh_file", "Open", idePath);
1721             }
1722             if(associateOptions[AssociateOptions::AssociateC].selected)
1723             {
1724                AssociateExtension(".c", "C Source File", "c_file", "Open", idePath);
1725                AssociateExtension(".h", "C Header File", "h_file", "Open", idePath);
1726             }
1727             if(associateOptions[AssociateOptions::AssociateCPP].selected)
1728             {
1729                AssociateExtension(".cpp", "C++ Source File", "cpp_file", "Open", idePath);
1730                AssociateExtension(".cc", "C++ Source File", "cpp_file", "Open", idePath);
1731                AssociateExtension(".cxx", "C++ Source File", "cpp_file", "Open", idePath);
1732                AssociateExtension(".chh", "C++ Header File", "chh_file", "Open", idePath);
1733                AssociateExtension(".hh", "C++ Header File", "chh_file", "Open", idePath);
1734                AssociateExtension(".hxx", "C++ Header File", "chh_file", "Open", idePath);
1735             }
1736             if(associateOptions[AssociateOptions::AssociateTXT].selected)
1737             {
1738                AssociateExtension(".txt", "Text File", "txt_file", "Open", idePath);
1739             }
1740             if(associateOptions[AssociateOptions::Associate3DS].selected)
1741             {
1742                AssociateExtension(".3ds", "3D Studio Model", "3ds_file", "View", idePath);
1743             }
1744             if(associateOptions[AssociateOptions::AssociateIMG].selected)
1745             {
1746                AssociateExtension(".bmp", "BMP Image", "bmp_file", "View", idePath);
1747                AssociateExtension(".pcx", "PCX Image", "pcx_file", "View", idePath);
1748                AssociateExtension(".png", "PNG Image", "png_file", "View", idePath);
1749                AssociateExtension(".jpg", "JPEG Image", "jpg_file", "View", idePath);
1750                AssociateExtension(".jpeg", "JPEG Image", "jpg_file", "View", idePath);
1751                AssociateExtension(".gif", "GIF Image", "gif_file", "View", idePath);
1752             }
1753          }
1754          ((GuiApplication)__thisModule).Lock();
1755
1756          installProgress.cancel.Destroy(0);
1757          installProgress.finish.text = $"Finish";
1758          installProgress.finish.disabled = false;
1759          installProgress.finish.Activate();
1760          installProgress.installing.text = $"Installation Complete.";
1761          installProgress.title.text = $"Installation Complete";
1762          installProgress.titleInfo.contents = $"Thank you for using the Ecere SDK.";
1763       }
1764       ((GuiApplication)__thisModule).Unlock();
1765       return 0;
1766    }
1767 }