ide, libec, ecs; added project name and project path validation. chars "*|:\",<>...
[sdk] / ide / src / dialogs / NewProjectDialog.ec
1 import "ide"
2
3 FileDialog fileDialog { type = selectDir, text = $"Select project directory" };
4
5 class NewProjectDialog : Window
6 {
7    background = formColor;
8    minClientSize = { 316, 170 };
9    maxClientSize = { 640, 170 };
10    borderStyle = sizable;
11    tabCycle = true;
12    hasClose = true;
13    text = $"New Project";
14
15    char path[MAX_LOCATION];
16    char name[MAX_FILENAME];
17
18    Project project;
19    Workspace workspace;
20
21    char projectLocation[MAX_LOCATION];
22    bool createFormOption;
23
24    PathBox locationEditBox
25    {
26       this, position = { 10, 80 }, size = { 120, 22 }, anchor = { left = 10, top = 80, right = 10 };
27       hotKey = altL, text = $"Location";
28       typeExpected = directory, browseDialog = fileDialog;
29
30       NotifyModified = NotifyModifiedLocation;
31    };
32    Label { this, position = { 10, 60 }, labeledWindow = locationEditBox };
33
34    DropBox targetType { this, position = { 10, 130 }, size = { 130 }, hotKey = altT, text = $"Target Type" };
35
36    Label { this, position = { 10, 110 }, labeledWindow = targetType };
37
38    Button okBtn
39    {
40       parent = this, isDefault = true, disabled = true, position = { 170, 130 }, size = { 60 }, text = $"OK";
41       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
42       {
43          char * prjName = projectName.contents;
44          char filePath[MAX_LOCATION];
45          char extension[MAX_EXTENSION];
46          ProjectConfig debug, release;
47
48          strcpy(projectLocation, locationEditBox.slashPath);
49          strcpy(filePath, projectLocation);
50          PathCatSlash(filePath, prjName);
51          GetExtension(filePath, extension);
52          //if(!extension[0])
53             ChangeExtension(filePath, ProjectExtension, filePath);
54
55          debug = ProjectConfig
56          {
57             name = CopyString("Debug");
58             options =
59             {
60                optimization = none;
61                fastMath = false;
62                debug = true;
63                preprocessorDefinitions = { [ CopyString("_DEBUG") ] };
64             };
65             makingModified = true;
66             compilingModified = true;
67             linkingModified = true;
68          };
69
70          release = ProjectConfig
71          {
72             name = CopyString("Release");
73             options =
74             {
75                optimization = speed;
76                fastMath = true;
77                debug = false;
78             };
79             makingModified = true;
80             compilingModified = true;
81             linkingModified = true;
82          };
83
84          /* error: too few arguments to function ‘__ecereProp_DirExpression_Set_char__PTR_’ -- debug.objDir = "debug";*/
85
86          project = Project
87          {
88             property::filePath = filePath;
89             moduleName = CopyString(name);
90             topNode.type = NodeTypes::project;
91             config = debug;
92          };
93          /*project.topNode.options = */project.options =
94          {
95             warnings = all;
96             // TOFIX: Precomp problems withou the extra ( )
97             targetType = ((TargetTypes)targetType.GetTag());
98             targetFileName = /*CopyString(*/name/*)*/;
99          };
100          if(project.options.targetType != staticLibrary)
101          {
102             project.options.libraries = { [ CopyString("ecere") ] };
103          }
104
105          project.topNode.configurations = /*project.configurations = */{ [ debug, release ] };
106          project.resNode = project.topNode.Add(project, "Resources", null, resources, archiveFile, false);
107
108          createFormOption = createForm.checked;
109
110          Destroy(DialogResult::ok);
111          return true;
112       }
113    };
114
115    void CreateNewProject()
116    {
117       char fileName[MAX_LOCATION];  // Windows Friendly path
118       ProjectView projectWindow;
119       Project prj = project;
120
121       if(!FileExists(projectLocation).isDirectory)
122       {
123          if(MessageBox { type = yesNo, master = ide,
124                text = $"Directory doesn't exist", contents = $"Create directory?" }.Modal() == yes)
125          {
126             if(!MakeDir(projectLocation))
127             {
128                MessageBox { type = ok, master = ide, text = projectLocation, contents = $"Error creating directory" }.Modal();
129                return;
130             }
131          }
132          else
133             return;
134       }
135
136       GetSystemPathBuffer(fileName, prj.filePath);
137
138       if(FileExists(prj.filePath))
139       {
140          if(MessageBox { type = yesNo, master = ide,
141                text = $"Project Already Exists", contents = $"Replace existing project?" }.Modal() == no)
142             return;
143       }
144
145       {
146          char workspaceFile[MAX_LOCATION];
147          strcpy(workspaceFile, prj.filePath);
148          ChangeExtension(workspaceFile, WorkspaceExtension, workspaceFile);
149          workspace = Workspace { compiler = ideSettings.defaultCompiler, workspaceFile = workspaceFile };
150       }
151       workspace.projects.Add(prj);
152       ide.findInFilesDialog.AddProjectItem(prj);
153       ide.findInFilesDialog.mode = FindInFilesMode::project;
154       ide.findInFilesDialog.currentDirectory = prj.topNode.path;
155
156       if(!prj.Save(prj.filePath))
157       {
158          MessageBox { type = ok, master = this, text = prj.filePath, contents = $"Error writing project file" }.Modal();
159          delete prj;
160          return;
161       }
162
163       projectWindow = ide.CreateProjectView(workspace, fileName);
164
165       {
166          char newWorkingDir[MAX_LOCATION];
167          StripLastDirectory(prj.filePath, newWorkingDir);
168
169          ide.ChangeFileDialogsDirectory(newWorkingDir, false);
170
171          StripLastDirectory(newWorkingDir, newWorkingDir);
172          ide.ChangeProjectFileDialogDirectory(newWorkingDir);
173       }
174
175       if(createFormOption)
176       {
177          char className[256];
178          char varName[256];
179          CodeEditor codeEditor = projectWindow.CreateNew("Form", "form", "Window", className);
180          EditBox editBox = codeEditor.editBox;
181          strcpy(varName, className);
182          if(islower(varName[0]))
183          {
184             memmove(varName+1, varName, strlen(varName)+1);
185             varName[0] = '_';
186          }
187          else
188             varName[0] = (char)tolower(varName[0]);
189
190          editBox.End();
191          editBox.Printf("\n%s %s {};\n", className, varName);
192
193          codeEditor.EnsureUpToDate();
194
195          prj.Save(prj.filePath);
196
197          projectWindow.modifiedDocument = false;
198          prj.topNode.modified = false;
199
200          /*
201          editBox.Printf("class FormApplication : GuiApplication\n");
202          editBox.Printf("{\n");
203          editBox.Printf("   %s %s {};\n", className, varName);
204          editBox.Printf("}\n");
205          editBox.Home();
206          */
207       }
208       prj.StartMonitoring();
209
210       if(prj && projectWindow)
211       {
212          CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
213          ProjectConfig config = prj.config;
214          projectWindow.ShowOutputBuildLog(true);
215          projectWindow.DisplayCompiler(compiler, false);
216          projectWindow.ProjectPrepareCompiler(prj, compiler, false);
217          projectWindow.ProjectPrepareMakefile(prj, force, compiler, config);
218          delete compiler;
219
220          ide.UpdateToolBarActiveConfigs(false);
221       }
222    }
223
224    Button
225    {
226       parent = this, position = { 240, 130 }, size = { 60 }, hotKey = escape, text = $"Cancel";
227       NotifyClicked = ButtonCloseDialog;
228    };
229    Button createForm
230    {
231       parent = this, text = $"Create Form", hotKey = altF, position = { 200, 30 };
232       isCheckbox = true, checked = true;
233    };
234    
235    EditBox projectName
236    {
237       parent = this, textHorzScroll = true, position = { 10, 30 }, size = { 160 };
238       hotKey = altP, text = $"Project Name";
239       NotifyUpdate = EditBoxUpdate;
240
241       bool NotifyModified(EditBox editBox)
242       {
243          char name[MAX_FILENAME];
244          char tmp[MAX_FILENAME];
245          char lastPart[MAX_LOCATION];
246          char * text = editBox.contents;
247
248          // drop leading path stuff that has no business here
249          GetLastDirectory(text, tmp);
250          ValidProjectNameBufCopy(name, tmp);
251
252          GetLastDirectory(path, lastPart);
253          if(name[0] && (!strcmp(path, lastPart) || !path[0] || !strcmp(this.name, lastPart)))
254          {
255             if(strcmp(path, lastPart))
256                StripLastDirectory(path, path);
257             PathCatSlash(path, name);
258             locationEditBox.path = path;
259          }
260          else if(!name[0] && !strcmp(this.name, lastPart))
261          {
262             if(strcmp(path, lastPart))
263                StripLastDirectory(path, path);
264             locationEditBox.path = path;
265          }
266          else if(strcmp(name, lastPart))
267          {
268             PathCatSlash(path, name);
269             locationEditBox.path = path;
270          }
271          strcpy(this.name, name);
272          editBox.contents = name;
273          return true;
274       }
275    };
276    Label { this, position = { 10, 10 }, labeledWindow = projectName };
277
278    void EditBoxUpdate(EditBox editBox)
279    {
280       okBtn.disabled = !(locationEditBox.path[0] && projectName.contents[0]);
281    }
282
283    bool NotifyModifiedLocation(PathBox pathBox)
284    {
285       char location[MAX_LOCATION];
286       char lastPart[MAX_FILENAME];
287       char * text;
288
289       BasicValidatePathBoxPath(pathBox);
290
291       text = pathBox.slashPath;
292
293       //replacing this: NotifyUpdate = EditBoxUpdate;
294       okBtn.disabled = !(text[0] && projectName.contents[0]);
295
296       GetWorkingDir(location, sizeof(location) - 1);
297       PathCatSlash(location, text);
298
299       GetLastDirectory(path, lastPart);
300       /*if(text[0] && (!name[0] || !strcmp(lastPart, name)))
301       {
302          char newName[MAX_FILENAME];
303          GetLastDirectory(location, newName);
304          if(strcmp(newName, location))
305          {
306             strcpy(name, newName);
307             projectName.contents = name;
308          }
309       }*/
310       strcpy(path, location);
311       pathBox.path = path;
312       return true;
313    }
314
315    NewProjectDialog()
316    {
317       char location[MAX_LOCATION];
318
319       if(ideSettings.ideProjectFileDialogLocation)
320          strcpy(location, ideSettings.ideProjectFileDialogLocation);
321       else
322          GetWorkingDir(location, sizeof(location) - 1);
323
324       locationEditBox.path = location;
325       strcpy(path, location);
326
327       {
328          DataRow row;
329
330          //targetType.AddField("String", null, 0);
331
332          row = targetType.AddRow();
333          row.tag = TargetTypes::executable;
334          row.SetData(null, $"Executable");
335
336          row = targetType.AddRow();
337          row.tag = TargetTypes::sharedLibrary;
338          row.SetData(null, $"Shared Library");
339
340          row = targetType.AddRow();
341          row.tag = TargetTypes::staticLibrary;
342          row.SetData(null, $"Static Library");
343
344          targetType.currentRow = targetType.FindRow(TargetTypes::executable);
345       }
346    }
347 }
348
349 class QuickProjectDialog : Window
350 {
351    background = formColor;
352    minClientSize = { 316, 110 };
353    maxClientSize = { 640, 110 };
354    borderStyle = sizable;
355    tabCycle = true;
356    hasClose = true;
357    text = $"Quick Project";
358
359    char path[MAX_LOCATION];
360    char name[MAX_FILENAME];
361
362    Label message { this, position = { 10, 10 }, text = $"Do you want to quickly create a temporary project?" };
363
364    DropBox targetType { this, position = { 10, 70 }, size = { 130 }, hotKey = altT, text = $"Target Type" };
365    Label { this, position = { 10, 50 }, labeledWindow = targetType };
366
367    Button okBtn
368    {
369       parent = this, isDefault = true, position = { 170, 70 }, size = { 60 }, text = $"OK";
370       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
371       {
372          char tempDir[MAX_LOCATION] = "";
373          char filePath[MAX_LOCATION];
374          char prjName[MAX_LOCATION] = "quick_project";
375          Project project;
376          Workspace workspace;
377          ProjectView projectWindow;
378          ProjectConfig debug, release;
379
380          /*if(!*/CreateTemporaryDir(tempDir, "ecereide");/*)
381          {
382             MessageBox { type = ok, master = this, text = tempDir, contents = "Error creating temporary directory." }.Modal();
383             return true;
384          }*/
385
386          GetLastDirectory(tempDir, prjName);
387          {
388             char * dot;
389             if((dot = strstr(prjName, ".")))
390                dot[0] = '_';
391          }
392
393          if(!FileExists(tempDir).isDirectory)
394          {
395             MessageBox { type = ok, master = this, text = tempDir, contents = $"Temporary directory does not exist." }.Modal();
396             return true;
397          }
398          
399          ide.tmpPrjDir = tempDir;
400
401          strcpy(filePath, tempDir);
402          PathCatSlash(filePath, prjName);
403          //GetExtension(filePath, extension);
404          //if(!extension[0])
405             ChangeExtension(filePath, ProjectExtension, filePath);
406          GetSystemPathBuffer(filePath, filePath);
407
408          debug = ProjectConfig
409          {
410             name = CopyString("Debug");
411             options = 
412             {
413                optimization = none;
414                fastMath = false;
415                debug = true;
416                preprocessorDefinitions = { [ CopyString("_DEBUG") ] };
417             };
418             makingModified = true;
419             compilingModified = true;
420             linkingModified = true;
421          };
422
423          release = ProjectConfig
424          {
425             name = CopyString("Release");
426             options =
427             {
428                optimization = speed;
429                fastMath = true;
430                debug = false;
431             };
432             makingModified = true;
433             compilingModified = true;
434             linkingModified = true;
435          };
436
437          project = Project
438          {
439             filePath = filePath;
440             moduleName = CopyString(prjName);
441             topNode.type = NodeTypes::project;
442             config = debug;
443          };
444          /*project.topNode.options = */project.options =
445          {
446             warnings = all;
447             // TOFIX: Precomp problems withou the extra ( )
448             targetType = ((TargetTypes)targetType.GetTag());
449             targetFileName = /*CopyString(*/prjName/*)*/;
450          };
451
452          if(project.options.targetType != staticLibrary)
453          {
454             project.options.libraries = { [ CopyString("ecere") ] };
455          }
456
457          {
458             char workspaceFile[MAX_LOCATION];
459             strcpy(workspaceFile, filePath);
460             ChangeExtension(workspaceFile, WorkspaceExtension, workspaceFile);
461             workspace = Workspace { compiler = ideSettings.defaultCompiler, workspaceFile = workspaceFile };
462          } 
463
464          workspace.projects.Add(project);
465          ide.findInFilesDialog.AddProjectItem(project);
466          ide.findInFilesDialog.mode = FindInFilesMode::project;
467          ide.findInFilesDialog.currentDirectory = project.topNode.path;
468
469          // *** Don't set this directly on project, it must be set on top ProjectNode ***
470          project.topNode.configurations = { [ debug, release ] };
471
472          project.resNode = project.topNode.Add(project, "Resources", null, resources, archiveFile, false);
473
474          if(!project.Save(filePath))
475          {
476             MessageBox { type = ok, master = this, text = filePath, contents = $"Error writing project file" }.Modal();
477             delete project;
478             delete workspace;
479             ide.DestroyTemporaryProjectDir();
480             return true;
481          }
482
483          projectWindow = ide.CreateProjectView(workspace, filePath);
484
485          {
486             char extension[MAX_EXTENSION];
487             Window document = null;
488             for(document = ide.firstChild; document; document = document.next)
489             {
490                if(document.created && document.isDocument && document._class == class(CodeEditor))
491                {
492                   char * fileName = document.fileName;
493                   if(strstr(fileName, "http://") == fileName)
494                   {
495                      char name[MAX_LOCATION];
496                      char newFileName[MAX_LOCATION];
497                      GetLastDirectory(fileName, name);
498                      strcpy(newFileName, tempDir);
499                      PathCat(newFileName, name);
500
501                      // TODO: this should be in Windows::SaveAs(char* asFileName)
502                      // start
503                      //document.saving = true;
504                      if(document.OnSaveFile(newFileName))
505                      {
506                         document.fileName = newFileName;
507                         document.NotifySaved(document.master, /*this*/document, newFileName);
508                      }
509                      //document.saving = false;
510                      // end
511                      // TODO else MessageBox unable to save and cancel the whole project creation thing
512                      fileName = document.fileName;
513                   }
514                   if(fileName)
515                   {
516                      CodeEditor codeEditor = (CodeEditor)document;
517                      ide.projectView.AddFile(project.topNode, fileName, false, false);
518                      codeEditor.AdjustDebugMenus(ide.areDebugMenusUnavailable, ide.isBreakpointTogglingUnavailable, ide.isDebuggerExecuting);
519                   }
520                }
521             }
522          }
523
524          if(project.topNode.modified)
525          {
526             project.Save(filePath);
527             projectWindow.modifiedDocument = false;
528             project.topNode.modified = false;
529          }
530          else
531          {
532             // TOCHECK: Why is the Quick project creating a form???
533             char className[256];
534             char varName[256];
535             CodeEditor codeEditor = projectWindow.CreateNew("Form", "form", "Window", className);
536             EditBox editBox = codeEditor.editBox;
537             strcpy(varName, className);
538             if(islower(varName[0]))
539             {
540                memmove(varName+1, varName, strlen(varName)+1);
541                varName[0] = '_';
542             }
543             else
544                varName[0] = (char)tolower(varName[0]);
545
546             editBox.End();
547             editBox.Printf("\n%s %s {};\n", className, varName);
548
549             codeEditor.EnsureUpToDate();
550
551             // TODO: how to save that new file?
552
553             project.Save(filePath);
554             projectWindow.modifiedDocument = false;
555             project.topNode.modified = false;
556          }
557          project.StartMonitoring();
558
559          visible = false;
560
561          if(project && projectWindow)
562          {
563             CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
564             ProjectConfig config = project.config;
565             projectWindow.ShowOutputBuildLog(true);
566             projectWindow.DisplayCompiler(compiler, false);
567             projectWindow.ProjectPrepareCompiler(project, compiler, false);
568             projectWindow.ProjectPrepareMakefile(project, force, compiler, config);
569             delete compiler;
570          }
571
572          ide.projectView.ProjectBuild(null, Modifiers { });
573
574          Destroy(0);
575          return true;
576       }
577    };
578    
579    Button
580    {
581       parent = this, position = { 240, 70 }, size = { 60 }, hotKey = escape, text = $"Cancel";
582       NotifyClicked = ButtonCloseDialog;
583    };
584    
585    QuickProjectDialog()
586    {
587       DataRow row;
588
589       row = targetType.AddRow();
590       row.tag = TargetTypes::executable;
591       row.SetData(null, $"Executable");
592
593       row = targetType.AddRow();
594       row.tag = TargetTypes::sharedLibrary;
595       row.SetData(null, $"Shared Library");
596
597       row = targetType.AddRow();
598       row.tag = TargetTypes::staticLibrary;
599       row.SetData(null, $"Static Library");
600
601       targetType.currentRow = targetType.FindRow(TargetTypes::executable);
602    }
603
604    bool OnPostCreate()
605    {
606       okBtn.Activate();
607       return true;
608    }
609 }
610
611 void ValidProjectNameBufCopy(char *output, char *input)
612 {
613    strcpy(output, input);
614    TrimLSpaces(output, output);
615    TrimRSpaces(output, output);
616    {
617       // todo: support '&', '.' and ' ' in project name on windows so that it may be used by all platforms.
618       char * chars = "*|:\",<>?\\/&. ";
619       char ch, * s = output, * o = output;
620       while((ch = *s++)) { if(!strchr(chars, ch)) *o++ = ch; }
621       *o = '\0';
622    }
623 }