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.fromId("OPTION_NEW_WORKSPACE"c) : UIString.fromId("OPTION_NEW_PROJECT"c), 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: OPTION_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: OPTION_TEMPLATE_DESCR }
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: OPTION_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: OPTION_CREATE_NEW_SOLUTION; checked: true }
97                             TextWidget { text: OPTION_WORKSPACE_NAME }
98                             EditLine { id: edWorkspaceName; text: "newworkspace"; layoutWidth: fill }
99                             TextWidget { text: "" }
100                             CheckBox { id: cbCreateWorkspaceSubdir; text: OPTION_CREATE_SUBDIRECTORY_FOR_WORKSPACE; checked: true }
101                             TextWidget { text: OPTION_PROJECT_NAME }
102                             EditLine { id: edProjectName; text: "newproject"; layoutWidth: fill }
103                             TextWidget { text: "" }
104                             CheckBox { id: cbCreateSubdir; text: OPTION_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.fromId("IDE_FILES"c), "*.dlangidews;*.d;*.dd;*.di;*.ddoc;*.dh;*.json;*.xml;*.ini"));
134         _edLocation.caption = UIString.fromId("MSG_SELECT_DIR"c);
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         _edLocation.setDefaultPopupMenu();
206         _edWorkspaceName.setDefaultPopupMenu();
207         _edProjectName.setDefaultPopupMenu();
208 
209         addChild(content);
210         addChild(createButtonsPanel([_newWorkspace ? ACTION_FILE_NEW_WORKSPACE : ACTION_FILE_NEW_PROJECT, ACTION_CANCEL], 0, 0));
211     }
212 
213     bool _newWorkspace;
214     StringListWidget _projectTypeList;
215     StringListWidget _projectTemplateList;
216     EditBox _templateDescription;
217     EditBox _directoryLayout;
218     DirEditLine _edLocation;
219     EditLine _edWorkspaceName;
220     EditLine _edProjectName;
221     CheckBox _cbCreateSubdir;
222     CheckBox _cbCreateWorkspace;
223     CheckBox _cbCreateWorkspaceSubdir;
224     TextWidget _statusText;
225     string _projectName = "newproject";
226     string _workspaceName = "newworkspace";
227     string _location;
228 
229     int _currentTemplateIndex = -1;
230     ProjectTemplate _currentTemplate;
231     ProjectTemplate[] _templates;
232 
233     protected void templateSelected(int index) {
234         if (_currentTemplateIndex == index)
235             return;
236         _currentTemplateIndex = index;
237         _currentTemplate = _templates[index];
238         _templateDescription.text = _currentTemplate.description;
239         updateDirLayout();
240     }
241 
242     protected void updateDirLayout() {
243         dchar[] buf;
244         dstring level = "";
245         if (_cbCreateWorkspaceSubdir.checked) {
246             buf ~= toUTF32(_workspaceName) ~ "/\n";
247             level ~= "    ";
248         }
249         if (_cbCreateWorkspace.checked) {
250             buf ~= level ~ toUTF32(_workspaceName) ~ toUTF32(WORKSPACE_EXTENSION) ~ "\n";
251         }
252         if (_cbCreateSubdir.checked) {
253             buf ~= level ~ toUTF32(_projectName) ~ "/\n";
254             level ~= "    ";
255         }
256         buf ~= level ~ "dub.json" ~ "\n";
257         buf ~= level ~ "source/" ~ "\n";
258         if (!_currentTemplate.srcfile.empty) {
259             buf ~= level ~ "    " ~ toUTF32(_currentTemplate.srcfile) ~ "\n";
260         }
261         _directoryLayout.text = buf.dup;
262         validate();
263     }
264 
265     bool setError(dstring msg) {
266         _statusText.text = msg;
267         return msg.empty;
268     }
269     dstring getError() {
270         return _statusText.text;
271     }
272 
273     ProjectCreationResult _result;
274 
275     override void close(const Action action) {
276         Action newaction = action.clone();
277         if (action.id == IDEActions.FileNewWorkspace || action.id == IDEActions.FileNewProject) {
278             if (!exists(_location)) {
279                 // show message box with OK and CANCEL buttons, cancel by default, and handle its result
280                 window.showMessageBox(UIString.fromId("ERROR_CANNOT_CREATE_PROJECT"c), UIString.fromId("QUESTION_CREATE_DIR"c), [ACTION_YES, ACTION_CANCEL], 1, delegate(const Action a) {
281                     if (a.id == StandardAction.Yes) {
282                         try {
283                             mkdirRecurse(_location);
284                             close(action);
285                         } catch (Exception e) {
286                             setError("Cannot create target location");
287                             window.showMessageBox(UIString.fromId("ERROR_CANNOT_CREATE_PROJECT"c), UIString.fromRaw(getError()));
288                         }
289                     }
290                     return true;
291                 });
292                 return;
293             }
294             if (!validate()) {
295                 window.showMessageBox(UIString.fromId("ERROR_CANNOT_CREATE_PROJECT"c), UIString.fromRaw(getError()));
296                 return;
297             }
298             if (!createProject()) {
299                 window.showMessageBox(UIString.fromId("ERROR_CANNOT_CREATE_PROJECT"c), UIString.fromId("ERROR_FAILED_CREATE_PROJECT"c));
300                 return;
301             }
302             newaction.objectParam = _result;
303         }
304         super.close(newaction);
305     }
306 
307     bool validate() {
308         if (!exists(_location)) {
309             return setError("The location directory does not exist");
310         }
311         if(!isDir(_location)) {
312             return setError("The location is not a directory");
313         }
314         if (!isValidProjectName(_projectName))
315             return setError("Invalid project name");
316         if (!isValidProjectName(_workspaceName))
317             return setError("Invalid workspace name");
318         return setError("");
319     }
320 
321     bool createProject() {
322         if (!validate())
323             return false;
324         Workspace ws = _currentWorkspace;
325         string wsdir = _location;
326         if (_newWorkspace) {
327             if (_cbCreateWorkspaceSubdir.checked) {
328                 wsdir = buildNormalizedPath(wsdir, _workspaceName);
329                 if (wsdir.exists) {
330                     if (!wsdir.isDir)
331                         return setError("Cannot create workspace directory");
332                 } else {
333                     try {
334                         mkdir(wsdir);
335                     } catch (Exception e) {
336                         return setError("Cannot create workspace directory");
337                     }
338                 }
339             }
340             string wsfilename = buildNormalizedPath(wsdir, _workspaceName ~ WORKSPACE_EXTENSION);
341             ws = new Workspace(_ide, wsfilename);
342             ws.name = toUTF32(_workspaceName);
343             if (!ws.save())
344                 return setError("Cannot create workspace file");
345         }
346         string pdir = wsdir;
347         if (_cbCreateSubdir.checked) {
348             pdir = buildNormalizedPath(pdir, _projectName);
349             if (pdir.exists) {
350                 if (!pdir.isDir)
351                     return setError("Cannot create project directory");
352             } else {
353                 try {
354                     mkdir(pdir);
355                 } catch (Exception e) {
356                     return setError("Cannot create project directory");
357                 }
358             }
359         }
360         string pfile = buildNormalizedPath(pdir, "dub.json");
361         Project project = new Project(ws, pfile);
362         project.name = toUTF32(_projectName);
363         if (!project.save())
364             return setError("Cannot save project");
365         project.content.setString("targetName", _projectName);
366         if (_currentTemplate.isLibrary) {
367             project.content.setString("targetType", "staticLibrary");
368             project.content.setString("targetPath", "lib");
369         } else {
370             project.content.setString("targetType", "executable");
371             project.content.setString("targetPath", "bin");
372         }
373         if (_currentTemplate.json)
374             project.content.merge(_currentTemplate.json);
375         project.save();
376         ws.addProject(project);
377         if (ws.startupProject is null)
378             ws.startupProject = project;
379         string srcdir = buildNormalizedPath(pdir, "source");
380         if (!exists(srcdir))
381             mkdir(srcdir);
382         if (!_currentTemplate.srcfile.empty && !_currentTemplate.srccode.empty) {
383             string srcfile = buildNormalizedPath(srcdir, _currentTemplate.srcfile);
384             write(srcfile, _currentTemplate.srccode);
385         }
386         if (!project.save())
387             return setError("Cannot save project file");
388         if (!ws.save())
389             return setError("Cannot save workspace file");
390         project.load();
391         _result = new ProjectCreationResult(ws, project);
392         return true;
393     }
394 
395     void initTemplates() {
396         _templates ~= new ProjectTemplate("Hello world app"d, "Hello world application."d, "app.d",
397                     SOURCE_CODE_HELLOWORLD, false);
398         _templates ~= new ProjectTemplate("DlangUI: hello world app"d, "Hello world application\nbased on DlangUI library"d, "app.d",
399                     SOURCE_CODE_DLANGUI_HELLOWORLD, false, DUB_JSON_DLANGUI_HELLOWORLD);
400         _templates ~= new ProjectTemplate("vibe.d: Hello world app"d, "Hello world application\nbased on vibe.d framework"d, "app.d",
401                     SOURCE_CODE_VIBED_HELLOWORLD, false, DUB_JSON_VIBED_HELLOWORLD);
402         _templates ~= new ProjectTemplate("Empty app project"d, "Empty application project.\nNo source files."d, null, null, false);
403         _templates ~= new ProjectTemplate("Empty library project"d, "Empty library project.\nNo Source files."d, null, null, true);
404     }
405 }
406 
407 immutable string SOURCE_CODE_HELLOWORLD = q{
408 import std.stdio;
409 void main(string[] args) {
410     writeln("Hello World!");
411     writeln("Press enter...");
412     readln();
413 }
414 
415 };
416 
417 immutable string SOURCE_CODE_VIBED_HELLOWORLD = q{
418 import vibe.d;
419 
420 shared static this()
421 {
422     auto settings = new HTTPServerSettings;
423     settings.port = 8080;
424 
425     listenHTTP(settings, &handleRequest);
426 }
427 
428 void handleRequest(HTTPServerRequest req,
429                     HTTPServerResponse res)
430 {
431     if (req.path == "/")
432         res.writeBody("Hello, World!", "text/plain");
433 }
434 
435 };
436 
437 immutable string DUB_JSON_VIBED_HELLOWORLD = q{
438 {
439     "dependencies": {
440         "vibe-d": "~>0.8.1"
441     },
442     "versions": ["VibeDefaultMain"]
443 }
444 
445 };
446 
447 immutable string SOURCE_CODE_DLANGUI_HELLOWORLD = q{
448 // DlangUI application
449 import dlangui;
450 
451 mixin APP_ENTRY_POINT;
452 
453 /// entry point for dlangui based application
454 extern (C) int UIAppMain(string[] args) {
455     // create window
456     Window window = Platform.instance.createWindow("DlangUI HelloWorld", null);
457 
458     // create some widget to show in window
459     //window.mainWidget = (new Button()).text("Hello, world!"d).margins(Rect(20,20,20,20));
460     window.mainWidget = parseML(q{
461         VerticalLayout {
462             margins: 10
463             padding: 10
464             // red bold text with size = 150% of base style size and font face Arial
465             TextWidget { text: "Hello World example for DlangUI"; textColor: "red"; fontSize: 150%; fontWeight: 800; fontFace: "Arial" }
466             // arrange controls as form - table with two columns
467             TableLayout {
468                 colCount: 2
469                 TextWidget { text: "param 1" }
470                 EditLine { id: edit1; text: "some text" }
471                 TextWidget { text: "param 2" }
472                 EditLine { id: edit2; text: "some text for param2" }
473                 TextWidget { text: "some radio buttons" }
474                 // arrange some radio buttons vertically
475                 VerticalLayout {
476                     RadioButton { id: rb1; text: "Item 1" }
477                     RadioButton { id: rb2; text: "Item 2" }
478                     RadioButton { id: rb3; text: "Item 3" }
479                 }
480                 TextWidget { text: "and checkboxes" }
481                 // arrange some checkboxes horizontally
482                 HorizontalLayout {
483                     CheckBox { id: cb1; text: "checkbox 1" }
484                     CheckBox { id: cb2; text: "checkbox 2" }
485                 }
486             }
487             HorizontalLayout {
488                 Button { id: btnOk; text: "Ok" }
489                 Button { id: btnCancel; text: "Cancel" }
490             }
491         }
492     });
493     // you can access loaded items by id - e.g. to assign signal listeners
494     auto edit1 = window.mainWidget.childById!EditLine("edit1");
495     auto edit2 = window.mainWidget.childById!EditLine("edit2");
496     // close window on Cancel button click
497     window.mainWidget.childById!Button("btnCancel").click = delegate(Widget w) {
498         window.close();
499         return true;
500     };
501     // show message box with content of editors
502     window.mainWidget.childById!Button("btnOk").click = delegate(Widget w) {
503         window.showMessageBox(UIString.fromId("MSG_OK_BUTTON"c), 
504                               UIString.fromId("EDITOR_CONTENT"c) ~ "\nEdit1: "d ~ edit1.text ~ "\nEdit2: "d ~ edit2.text);
505         return true;
506     };
507 
508     // show window
509     window.show();
510 
511     // run message loop
512     return Platform.instance.enterMessageLoop();
513 }
514 
515 };
516 
517 immutable string DUB_JSON_DLANGUI_HELLOWORLD = q{
518 {
519     "dependencies": {
520         "dlangui": "~>0.9.99"
521     }
522 }
523 
524 };
525 
526 class ProjectTemplate {
527     dstring name;
528     dstring description;
529     string srcfile;
530     string srccode;
531     bool isLibrary;
532     string json;
533     this(dstring name, dstring description, string srcfile, string srccode, bool isLibrary, string json = null) {
534         this.name = name;
535         this.description = description;
536         this.srcfile = srcfile;
537         this.srccode = srccode;
538         this.isLibrary = isLibrary;
539         this.json = json;
540     }
541 }