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 }