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 }