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