1 module dlangide.ui.newproject;
2 
3 import dlangui.core.types;
4 import dlangui.core.i18n;
5 import dlangui.platforms.common.platform;
6 import dlangui.dialogs.dialog;
7 import dlangui.dialogs.filedlg;
8 import dlangui.widgets.widget;
9 import dlangui.widgets.layouts;
10 import dlangui.widgets.editors;
11 import dlangui.widgets.controls;
12 import dlangui.widgets.lists;
13 import dlangui.dml.parser;
14 import dlangui.core.stdaction;
15 import dlangui.core.files;
16 import dlangide.workspace.project;
17 import dlangide.workspace.workspace;
18 import dlangide.ui.commands;
19 import dlangide.ui.frame;
20 
21 import std.path;
22 import std.file;
23 import std.array : empty;
24 import std.utf : toUTF32;
25 
26 class ProjectCreationResult {
27     Workspace workspace;
28     Project project;
29     this(Workspace workspace, Project project) {
30         this.workspace = workspace;
31         this.project = project;
32     }
33 }
34 
35 class NewProjectDlg : Dialog {
36 
37     Workspace _currentWorkspace;
38     IDEFrame _ide;
39 
40     this(IDEFrame parent, bool newWorkspace, Workspace currentWorkspace, string dir) {
41         super(newWorkspace ? UIString.fromRaw("New Workspace"d) : UIString.fromRaw("New Project"d), parent.window, 
42               DialogFlag.Modal | DialogFlag.Resizable | DialogFlag.Popup, 500, 400);
43         _ide = parent;
44         _icon = "dlangui-logo1";
45         this._currentWorkspace = currentWorkspace;
46         _newWorkspace = newWorkspace;
47         _location = dir !is null ? dir : (currentWorkspace !is null ? currentWorkspace.dir : currentDir);
48     }
49 
50     /// override to implement creation of dialog controls
51     override void initialize() {
52         super.initialize();
53         initTemplates();
54         Widget content;
55         try {
56             content = parseML(q{
57                     VerticalLayout {
58                         id: vlayout
59                         padding: Rect { 5, 5, 5, 5 }
60                         layoutWidth: fill; layoutHeight: fill
61                         HorizontalLayout {
62                             layoutWidth: fill; layoutHeight: fill
63                             VerticalLayout {
64                                 margins: 5
65                                 layoutWidth: 25%; layoutHeight: fill
66                                 TextWidget { text: "Project template" }
67                                 StringListWidget { 
68                                     id: projectTemplateList 
69                                     layoutWidth: wrap; layoutHeight: fill
70                                 }
71                             }
72                             VerticalLayout {
73                                 margins: 5
74                                 layoutWidth: 40%; layoutHeight: fill
75                                 TextWidget { text: "Template description" }
76                                 EditBox { 
77                                     id: templateDescription; readOnly: true 
78                                     layoutWidth: fill; layoutHeight: fill
79                                 }
80                             }
81                             VerticalLayout {
82                                 layoutWidth: 35%; layoutHeight: fill
83                                 margins: 5
84                                 TextWidget { text: "Directory layout" }
85                                 EditBox { 
86                                     id: directoryLayout; readOnly: true
87                                     layoutWidth: fill; layoutHeight: fill
88                                 }
89                             }
90                         }
91                         TableLayout {
92                             margins: 5
93                             colCount: 2
94                             layoutWidth: fill; layoutHeight: wrap
95                             TextWidget { text: "" }
96                             CheckBox { id: cbCreateWorkspace; text: "Create new solution"; checked: true }
97                             TextWidget { text: "Workspace name" }
98                             EditLine { id: edWorkspaceName; text: "newworkspace"; layoutWidth: fill }
99                             TextWidget { text: "" }
100                             CheckBox { id: cbCreateWorkspaceSubdir; text: "Create subdirectory for workspace"; checked: true }
101                             TextWidget { text: "Project name" }
102                             EditLine { id: edProjectName; text: "newproject"; layoutWidth: fill }
103                             TextWidget { text: "" }
104                             CheckBox { id: cbCreateSubdir; text: "Create subdirectory for project"; checked: true }
105                             TextWidget { text: "Location" }
106                             DirEditLine { id: edLocation; layoutWidth: fill }
107                         }
108                         TextWidget { id: statusText; text: ""; layoutWidth: fill }
109                     }
110                 });
111         } catch (Exception e) {
112             Log.e("Exceptin while parsing DML", e);
113             throw e;
114         }
115 
116 
117         _projectTemplateList = content.childById!StringListWidget("projectTemplateList");
118         _templateDescription = content.childById!EditBox("templateDescription");
119         _directoryLayout = content.childById!EditBox("directoryLayout");
120         _edProjectName = content.childById!EditLine("edProjectName");
121         _edWorkspaceName = content.childById!EditLine("edWorkspaceName");
122         _cbCreateSubdir = content.childById!CheckBox("cbCreateSubdir");
123         _cbCreateWorkspace = content.childById!CheckBox("cbCreateWorkspace");
124         _cbCreateWorkspaceSubdir = content.childById!CheckBox("cbCreateWorkspaceSubdir");
125         _edLocation = content.childById!DirEditLine("edLocation");
126         _edLocation.text = toUTF32(_location);
127         _statusText = content.childById!TextWidget("statusText");
128 
129         _edLocation.filetypeIcons[".d"] = "text-d";
130         _edLocation.filetypeIcons["dub.json"] = "project-d";
131         _edLocation.filetypeIcons["package.json"] = "project-d";
132         _edLocation.filetypeIcons[".dlangidews"] = "project-development";
133         _edLocation.addFilter(FileFilterEntry(UIString.fromRaw("DlangIDE files"d), "*.dlangidews;*.d;*.dd;*.di;*.ddoc;*.dh;*.json;*.xml;*.ini"));
134         _edLocation.caption = "Select directory"d;
135 
136         if (_currentWorkspace) {
137             _workspaceName = toUTF8(_currentWorkspace.name);
138             _edWorkspaceName.text = toUTF32(_workspaceName);
139             _cbCreateWorkspaceSubdir.enabled = false;
140             _cbCreateWorkspaceSubdir.checked = false;
141         } else {
142             _cbCreateWorkspace.checked = true;
143             _cbCreateWorkspace.enabled = false;
144         }
145         if (!_newWorkspace) {
146             _cbCreateWorkspace.checked = false;
147             _cbCreateWorkspace.enabled = _currentWorkspace !is null;
148             _edWorkspaceName.readOnly = true;
149         }
150 
151 
152         // fill templates
153         dstring[] names;
154         foreach(t; _templates)
155             names ~= t.name;
156         _projectTemplateList.items = names;
157         _projectTemplateList.selectedItemIndex = 0;
158 
159         templateSelected(0);
160         updateDirLayout();
161 
162         // listeners
163         _edProjectName.contentChange = delegate (EditableContent source) {
164             _projectName = toUTF8(source.text);
165             updateDirLayout();
166         };
167 
168         _edWorkspaceName.contentChange = delegate (EditableContent source) {
169             _workspaceName = toUTF8(source.text);
170             updateDirLayout();
171         };
172 
173         _edLocation.contentChange = delegate (EditableContent source) {
174             _location = toUTF8(source.text);
175             updateDirLayout();
176         };
177 
178         _cbCreateWorkspace.checkChange = delegate (Widget source, bool checked) {
179             _edWorkspaceName.readOnly = !checked;
180             _cbCreateWorkspaceSubdir.enabled = checked;
181             updateDirLayout();
182             return true;
183         };
184 
185         _cbCreateSubdir.checkChange = delegate (Widget source, bool checked) {
186             updateDirLayout();
187             return true;
188         };
189 
190         _cbCreateWorkspaceSubdir.checkChange = delegate (Widget source, bool checked) {
191             _edWorkspaceName.readOnly = !checked;
192             updateDirLayout();
193             return true;
194         };
195 
196         _projectTemplateList.itemSelected = delegate (Widget source, int itemIndex) {
197             templateSelected(itemIndex);
198             return true;
199         };
200         _projectTemplateList.itemClick = delegate (Widget source, int itemIndex) {
201             templateSelected(itemIndex);
202             return true;
203         };
204 
205         addChild(content);
206         addChild(createButtonsPanel([_newWorkspace ? ACTION_FILE_NEW_WORKSPACE : ACTION_FILE_NEW_PROJECT, ACTION_CANCEL], 0, 0));
207     }
208 
209     bool _newWorkspace;
210     StringListWidget _projectTypeList;
211     StringListWidget _projectTemplateList;
212     EditBox _templateDescription;
213     EditBox _directoryLayout;
214     DirEditLine _edLocation;
215     EditLine _edWorkspaceName;
216     EditLine _edProjectName;
217     CheckBox _cbCreateSubdir;
218     CheckBox _cbCreateWorkspace;
219     CheckBox _cbCreateWorkspaceSubdir;
220     TextWidget _statusText;
221     string _projectName = "newproject";
222     string _workspaceName = "newworkspace";
223     string _location;
224 
225     int _currentTemplateIndex = -1;
226     ProjectTemplate _currentTemplate;
227     ProjectTemplate[] _templates;
228 
229     protected void templateSelected(int index) {
230         if (_currentTemplateIndex == index)
231             return;
232         _currentTemplateIndex = index;
233         _currentTemplate = _templates[index];
234         _templateDescription.text = _currentTemplate.description;
235         updateDirLayout();
236     }
237 
238     protected void updateDirLayout() {
239         dchar[] buf;
240         dstring level = "";
241         if (_cbCreateWorkspaceSubdir.checked) {
242             buf ~= toUTF32(_workspaceName) ~ "/\n";
243             level ~= "    ";
244         }
245         if (_cbCreateWorkspace.checked) {
246             buf ~= level ~ toUTF32(_workspaceName) ~ toUTF32(WORKSPACE_EXTENSION) ~ "\n";
247         }
248         if (_cbCreateSubdir.checked) {
249             buf ~= level ~ toUTF32(_projectName) ~ "/\n";
250             level ~= "    ";
251         }
252         buf ~= level ~ "dub.json" ~ "\n";
253         buf ~= level ~ "source/" ~ "\n";
254         if (!_currentTemplate.srcfile.empty) {
255             buf ~= level ~ "    " ~ toUTF32(_currentTemplate.srcfile) ~ "\n";
256         }
257         _directoryLayout.text = buf.dup;
258         validate();
259     }
260 
261     bool setError(dstring msg) {
262         _statusText.text = msg;
263         return msg.empty;
264     }
265     dstring getError() {
266         return _statusText.text;
267     }
268 
269     ProjectCreationResult _result;
270 
271     override void close(const Action action) {
272         Action newaction = action.clone();
273         if (action.id == IDEActions.FileNewWorkspace || action.id == IDEActions.FileNewProject) {
274             if (!exists(_location)) {
275                 // show message box with OK and CANCEL buttons, cancel by default, and handle its result
276                 window.showMessageBox(UIString.fromRaw("Cannot create project"d), UIString.fromRaw("The target location does not exist.\nDo you want to create the target directory?"), [ACTION_YES, ACTION_CANCEL], 1, delegate(const Action a) {
277                     if (a.id == StandardAction.Yes) {
278                         try {
279                             mkdirRecurse(_location);
280                             close(action);
281                         } catch (Exception e) {
282                             setError("Cannot create target location");
283                             window.showMessageBox(UIString.fromRaw("Cannot create project"d), UIString.fromRaw(getError()));
284                         }
285                     }
286                     return true;
287                 });
288                 return;
289             }
290             if (!validate()) {
291                 window.showMessageBox(UIString.fromRaw("Cannot create project"d), UIString.fromRaw(getError()));
292                 return;
293             }
294             if (!createProject()) {
295                 window.showMessageBox(UIString.fromRaw("Cannot create project"d), UIString.fromRaw("Failed to create project"));
296                 return;
297             }
298             newaction.objectParam = _result;
299         }
300         super.close(newaction);
301     }
302 
303     bool validate() {
304         if (!exists(_location)) {
305             return setError("The location directory does not exist");
306         }
307         if(!isDir(_location)) {
308             return setError("The location is not a directory");
309         }
310         if (!isValidProjectName(_projectName))
311             return setError("Invalid project name");
312         if (!isValidProjectName(_workspaceName))
313             return setError("Invalid workspace name");
314         return setError("");
315     }
316 
317     bool createProject() {
318         if (!validate())
319             return false;
320         Workspace ws = _currentWorkspace;
321         string wsdir = _location;
322         if (_newWorkspace) {
323             if (_cbCreateWorkspaceSubdir.checked) {
324                 wsdir = buildNormalizedPath(wsdir, _workspaceName);
325                 if (wsdir.exists) {
326                     if (!wsdir.isDir)
327                         return setError("Cannot create workspace directory");
328                 } else {
329                     try {
330                         mkdir(wsdir);
331                     } catch (Exception e) {
332                         return setError("Cannot create workspace directory");
333                     }
334                 }
335             }
336             string wsfilename = buildNormalizedPath(wsdir, _workspaceName ~ WORKSPACE_EXTENSION);
337             ws = new Workspace(_ide, wsfilename);
338             ws.name = toUTF32(_workspaceName);
339             if (!ws.save())
340                 return setError("Cannot create workspace file");
341         }
342         string pdir = wsdir;
343         if (_cbCreateSubdir.checked) {
344             pdir = buildNormalizedPath(pdir, _projectName);
345             if (pdir.exists) {
346                 if (!pdir.isDir)
347                     return setError("Cannot create project directory");
348             } else {
349                 try {
350                     mkdir(pdir);
351                 } catch (Exception e) {
352                     return setError("Cannot create project directory");
353                 }
354             }
355         }
356         string pfile = buildNormalizedPath(pdir, "dub.json");
357         Project project = new Project(ws, pfile);
358         project.name = toUTF32(_projectName);
359         if (!project.save())
360             return setError("Cannot save project");
361         project.content.setString("targetName", _projectName);
362         if (_currentTemplate.isLibrary) {
363             project.content.setString("targetType", "staticLibrary");
364             project.content.setString("targetPath", "lib");
365         } else {
366             project.content.setString("targetType", "executable");
367             project.content.setString("targetPath", "bin");
368         }
369         if (_currentTemplate.json)
370             project.content.merge(_currentTemplate.json);
371         project.save();
372         ws.addProject(project);
373         if (ws.startupProject is null)
374             ws.startupProject = project;
375         string srcdir = buildNormalizedPath(pdir, "source");
376         if (!exists(srcdir))
377             mkdir(srcdir);
378         if (!_currentTemplate.srcfile.empty && !_currentTemplate.srccode.empty) {
379             string srcfile = buildNormalizedPath(srcdir, _currentTemplate.srcfile);
380             write(srcfile, _currentTemplate.srccode);
381         }
382         if (!project.save())
383             return setError("Cannot save project file");
384         if (!ws.save())
385             return setError("Cannot save workspace file");
386         project.load();
387         _result = new ProjectCreationResult(ws, project);
388         return true;
389     }
390 
391     void initTemplates() {
392         _templates ~= new ProjectTemplate("Hello world app"d, "Hello world application."d, "app.d",
393                     SOURCE_CODE_HELLOWORLD, false);
394         _templates ~= new ProjectTemplate("DlangUI: hello world app"d, "Hello world application\nbased on DlangUI library"d, "app.d",
395                     SOURCE_CODE_DLANGUI_HELLOWORLD, false, DUB_JSON_DLANGUI_HELLOWORLD);
396         _templates ~= new ProjectTemplate("vibe.d: Hello world app"d, "Hello world application\nbased on vibe.d framework"d, "app.d",
397                     SOURCE_CODE_VIBED_HELLOWORLD, false, DUB_JSON_VIBED_HELLOWORLD);
398         _templates ~= new ProjectTemplate("Empty app project"d, "Empty application project.\nNo source files."d, null, null, false);
399         _templates ~= new ProjectTemplate("Empty library project"d, "Empty library project.\nNo Source files."d, null, null, true);
400     }
401 }
402 
403 immutable string SOURCE_CODE_HELLOWORLD = q{
404 import std.stdio;
405 void main(string[] args) {
406     writeln("Hello World!");
407     writeln("Press enter...");
408     readln();
409 }
410 
411 };
412 
413 immutable string SOURCE_CODE_VIBED_HELLOWORLD = q{
414 import vibe.d;
415 
416 shared static this()
417 {
418     auto settings = new HTTPServerSettings;
419     settings.port = 8080;
420 
421     listenHTTP(settings, &handleRequest);
422 }
423 
424 void handleRequest(HTTPServerRequest req,
425                     HTTPServerResponse res)
426 {
427     if (req.path == "/")
428         res.writeBody("Hello, World!", "text/plain");
429 }
430 
431 };
432 
433 immutable string DUB_JSON_VIBED_HELLOWORLD = q{
434 {
435     "dependencies": {
436         "vibe-d": "~>0.7.30-rc.1"
437     },
438     "versions": ["VibeDefaultMain"]
439 }
440 
441 };
442 
443 immutable string SOURCE_CODE_DLANGUI_HELLOWORLD = q{
444 // DlangUI application
445 import dlangui;
446 
447 mixin APP_ENTRY_POINT;
448 
449 /// entry point for dlangui based application
450 extern (C) int UIAppMain(string[] args) {
451     // create window
452     Window window = Platform.instance.createWindow("DlangUI HelloWorld", null);
453 
454     // create some widget to show in window
455     //window.mainWidget = (new Button()).text("Hello, world!"d).margins(Rect(20,20,20,20));
456     window.mainWidget = parseML(q{
457         VerticalLayout {
458             margins: 10
459             padding: 10
460             // red bold text with size = 150% of base style size and font face Arial
461             TextWidget { text: "Hello World example for DlangUI"; textColor: "red"; fontSize: 150%; fontWeight: 800; fontFace: "Arial" }
462             // arrange controls as form - table with two columns
463             TableLayout {
464                 colCount: 2
465                 TextWidget { text: "param 1" }
466                 EditLine { id: edit1; text: "some text" }
467                 TextWidget { text: "param 2" }
468                 EditLine { id: edit2; text: "some text for param2" }
469                 TextWidget { text: "some radio buttons" }
470                 // arrange some radio buttons vertically
471                 VerticalLayout {
472                     RadioButton { id: rb1; text: "Item 1" }
473                     RadioButton { id: rb2; text: "Item 2" }
474                     RadioButton { id: rb3; text: "Item 3" }
475                 }
476                 TextWidget { text: "and checkboxes" }
477                 // arrange some checkboxes horizontally
478                 HorizontalLayout {
479                     CheckBox { id: cb1; text: "checkbox 1" }
480                     CheckBox { id: cb2; text: "checkbox 2" }
481                 }
482             }
483             HorizontalLayout {
484                 Button { id: btnOk; text: "Ok" }
485                 Button { id: btnCancel; text: "Cancel" }
486             }
487         }
488     });
489     // you can access loaded items by id - e.g. to assign signal listeners
490     auto edit1 = window.mainWidget.childById!EditLine("edit1");
491     auto edit2 = window.mainWidget.childById!EditLine("edit2");
492     // close window on Cancel button click
493     window.mainWidget.childById!Button("btnCancel").click = delegate(Widget w) {
494         window.close();
495         return true;
496     };
497     // show message box with content of editors
498     window.mainWidget.childById!Button("btnOk").click = delegate(Widget w) {
499         window.showMessageBox(UIString.fromRaw("Ok button pressed"d), 
500                               UIString.fromRaw("Editors content\nEdit1: "d ~ edit1.text ~ "\nEdit2: "d ~ edit2.text));
501         return true;
502     };
503 
504     // show window
505     window.show();
506 
507     // run message loop
508     return Platform.instance.enterMessageLoop();
509 }
510 
511 };
512 
513 immutable string DUB_JSON_DLANGUI_HELLOWORLD = q{
514 {
515     "dependencies": {
516         "dlangui": "~>0.9.24"
517     }
518 }
519 
520 };
521 
522 class ProjectTemplate {
523     dstring name;
524     dstring description;
525     string srcfile;
526     string srccode;
527     bool isLibrary;
528     string json;
529     this(dstring name, dstring description, string srcfile, string srccode, bool isLibrary, string json = null) {
530         this.name = name;
531         this.description = description;
532         this.srcfile = srcfile;
533         this.srccode = srccode;
534         this.isLibrary = isLibrary;
535         this.json = json;
536     }
537 }