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 }