project_manager.cpp 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727
  1. /*************************************************************************/
  2. /* project_manager.cpp */
  3. /*************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /*************************************************************************/
  8. /* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
  9. /* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /*************************************************************************/
  30. #include "project_manager.h"
  31. #include "editor_initialize_ssl.h"
  32. #include "editor_scale.h"
  33. #include "editor_settings.h"
  34. #include "editor_themes.h"
  35. #include "io/config_file.h"
  36. #include "io/resource_saver.h"
  37. #include "io/stream_peer_ssl.h"
  38. #include "io/zip_io.h"
  39. #include "os/dir_access.h"
  40. #include "os/file_access.h"
  41. #include "os/keyboard.h"
  42. #include "os/os.h"
  43. #include "scene/gui/center_container.h"
  44. #include "scene/gui/line_edit.h"
  45. #include "scene/gui/margin_container.h"
  46. #include "scene/gui/panel_container.h"
  47. #include "scene/gui/separator.h"
  48. #include "scene/gui/texture_rect.h"
  49. #include "scene/gui/tool_button.h"
  50. #include "version.h"
  51. #include "version_hash.gen.h"
  52. class ProjectDialog : public ConfirmationDialog {
  53. GDCLASS(ProjectDialog, ConfirmationDialog);
  54. public:
  55. enum Mode {
  56. MODE_NEW,
  57. MODE_IMPORT,
  58. MODE_INSTALL,
  59. MODE_RENAME
  60. };
  61. private:
  62. enum MessageType {
  63. MESSAGE_ERROR,
  64. MESSAGE_WARNING,
  65. MESSAGE_SUCCESS
  66. };
  67. Mode mode;
  68. Button *browse;
  69. Button *create_dir;
  70. Container *name_container;
  71. Container *path_container;
  72. Label *msg;
  73. LineEdit *project_path;
  74. LineEdit *project_name;
  75. ToolButton *status_btn;
  76. FileDialog *fdialog;
  77. String zip_path;
  78. String zip_title;
  79. AcceptDialog *dialog_error;
  80. String fav_dir;
  81. String created_folder_path;
  82. void set_message(const String &p_msg, MessageType p_type = MESSAGE_SUCCESS) {
  83. msg->set_text(p_msg);
  84. if (p_msg == "") {
  85. status_btn->set_icon(get_icon("StatusSuccess", "EditorIcons"));
  86. return;
  87. }
  88. msg->hide();
  89. switch (p_type) {
  90. case MESSAGE_ERROR:
  91. msg->add_color_override("font_color", get_color("error_color", "Editor"));
  92. status_btn->set_icon(get_icon("StatusError", "EditorIcons"));
  93. msg->show();
  94. break;
  95. case MESSAGE_WARNING:
  96. msg->add_color_override("font_color", get_color("warning_color", "Editor"));
  97. status_btn->set_icon(get_icon("StatusWarning", "EditorIcons"));
  98. break;
  99. case MESSAGE_SUCCESS:
  100. msg->add_color_override("font_color", get_color("success_color", "Editor"));
  101. status_btn->set_icon(get_icon("StatusSuccess", "EditorIcons"));
  102. break;
  103. }
  104. }
  105. String _test_path() {
  106. set_message(" ");
  107. get_ok()->set_disabled(true);
  108. DirAccess *d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
  109. String valid_path;
  110. if (d->change_dir(project_path->get_text()) == OK) {
  111. valid_path = project_path->get_text();
  112. } else if (d->change_dir(project_path->get_text().strip_edges()) == OK) {
  113. valid_path = project_path->get_text().strip_edges();
  114. }
  115. if (valid_path == "") {
  116. set_message(TTR("The path does not exists."), MESSAGE_ERROR);
  117. memdelete(d);
  118. return "";
  119. }
  120. if (mode == MODE_IMPORT || mode == MODE_RENAME) {
  121. if (valid_path != "" && !d->file_exists("project.godot")) {
  122. set_message(TTR("Please choose a 'project.godot' file."), MESSAGE_ERROR);
  123. memdelete(d);
  124. return "";
  125. }
  126. } else if (mode == MODE_NEW) {
  127. // check if the specified folder is empty, even though this is not an error, it is good to check here
  128. d->list_dir_begin();
  129. bool is_empty = true;
  130. String n = d->get_next();
  131. while (n != String()) {
  132. if (!n.begins_with(".")) { // i dont know if this is enough to guarantee an empty dir
  133. is_empty = false;
  134. break;
  135. }
  136. n = d->get_next();
  137. }
  138. d->list_dir_end();
  139. if (!is_empty) {
  140. set_message(TTR("Your project will be created in a non empty folder (you might want to create a new folder)."), MESSAGE_WARNING);
  141. }
  142. } else {
  143. if (d->file_exists("project.godot")) {
  144. set_message(TTR("Please choose a folder that does not contain a 'project.godot' file."), MESSAGE_ERROR);
  145. memdelete(d);
  146. return "";
  147. }
  148. }
  149. memdelete(d);
  150. get_ok()->set_disabled(false);
  151. return valid_path;
  152. }
  153. void _path_text_changed(const String &p_path) {
  154. String sp = _test_path();
  155. if (sp != "") {
  156. // set the project name to the select folder name
  157. if (project_name->get_text() == "") {
  158. sp = sp.replace("\\", "/");
  159. int lidx = sp.find_last("/");
  160. if (lidx != -1) {
  161. sp = sp.substr(lidx + 1, sp.length());
  162. }
  163. if (sp == "" && mode == MODE_IMPORT)
  164. sp = TTR("Imported Project");
  165. project_name->set_text(sp);
  166. }
  167. }
  168. if (created_folder_path != "" && created_folder_path != p_path) {
  169. _remove_created_folder();
  170. }
  171. }
  172. void _file_selected(const String &p_path) {
  173. String p = p_path;
  174. if (mode == MODE_IMPORT) {
  175. if (p.ends_with("project.godot")) {
  176. p = p.get_base_dir();
  177. get_ok()->set_disabled(false);
  178. } else {
  179. set_message(TTR("Please choose a 'project.godot' file."), MESSAGE_ERROR);
  180. get_ok()->set_disabled(true);
  181. return;
  182. }
  183. }
  184. String sp = p.simplify_path();
  185. project_path->set_text(sp);
  186. set_message(TTR(" ")); // just so it does not disappear
  187. get_ok()->call_deferred("grab_focus");
  188. }
  189. void _path_selected(const String &p_path) {
  190. String p = p_path;
  191. String sp = p.simplify_path();
  192. project_path->set_text(sp);
  193. get_ok()->call_deferred("grab_focus");
  194. }
  195. void _browse_path() {
  196. fdialog->set_current_dir(project_path->get_text());
  197. if (mode == MODE_IMPORT) {
  198. fdialog->set_mode(FileDialog::MODE_OPEN_FILE);
  199. fdialog->clear_filters();
  200. fdialog->add_filter("project.godot ; " _MKSTR(VERSION_NAME) " Project");
  201. } else {
  202. fdialog->set_mode(FileDialog::MODE_OPEN_DIR);
  203. }
  204. fdialog->popup_centered_ratio();
  205. }
  206. void _create_folder() {
  207. if (project_name->get_text() == "" || created_folder_path != "") {
  208. return;
  209. }
  210. DirAccess *d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
  211. if (d->change_dir(project_path->get_text()) == OK) {
  212. if (!d->dir_exists(project_name->get_text())) {
  213. if (d->make_dir(project_name->get_text()) == OK) {
  214. d->change_dir(project_name->get_text());
  215. project_path->set_text(d->get_current_dir());
  216. created_folder_path = d->get_current_dir();
  217. create_dir->set_disabled(true);
  218. }
  219. }
  220. }
  221. memdelete(d);
  222. }
  223. void _text_changed(const String &p_text) {
  224. if (mode != MODE_NEW)
  225. return;
  226. _test_path();
  227. if (p_text == "")
  228. set_message(TTR("It would be a good idea to name your project."), MESSAGE_WARNING);
  229. }
  230. void ok_pressed() {
  231. String dir = project_path->get_text();
  232. if (mode == MODE_RENAME) {
  233. String dir = _test_path();
  234. if (dir == "") {
  235. set_message(TTR("Invalid project path (changed anything?)."), MESSAGE_ERROR);
  236. return;
  237. }
  238. ProjectSettings *current = memnew(ProjectSettings);
  239. current->add_singleton(ProjectSettings::Singleton("Current"));
  240. if (current->setup(dir, "")) {
  241. set_message(TTR("Couldn't get project.godot in project path."), MESSAGE_ERROR);
  242. } else {
  243. ProjectSettings::CustomMap edited_settings;
  244. edited_settings["application/config/name"] = project_name->get_text();
  245. if (current->save_custom(dir.plus_file("/project.godot"), edited_settings, Vector<String>(), true)) {
  246. set_message(TTR("Couldn't edit project.godot in project path."), MESSAGE_ERROR);
  247. }
  248. }
  249. hide();
  250. emit_signal("project_renamed");
  251. } else {
  252. if (mode == MODE_IMPORT) {
  253. // nothing to do
  254. } else {
  255. if (mode == MODE_NEW) {
  256. ProjectSettings::CustomMap initial_settings;
  257. initial_settings["application/config/name"] = project_name->get_text();
  258. initial_settings["application/config/icon"] = "res://icon.png";
  259. initial_settings["rendering/environment/default_environment"] = "res://default_env.tres";
  260. if (ProjectSettings::get_singleton()->save_custom(dir.plus_file("/project.godot"), initial_settings, Vector<String>(), false)) {
  261. set_message(TTR("Couldn't create project.godot in project path."), MESSAGE_ERROR);
  262. } else {
  263. ResourceSaver::save(dir.plus_file("/icon.png"), get_icon("DefaultProjectIcon", "EditorIcons"));
  264. FileAccess *f = FileAccess::open(dir.plus_file("/default_env.tres"), FileAccess::WRITE);
  265. if (!f) {
  266. set_message(TTR("Couldn't create project.godot in project path."), MESSAGE_ERROR);
  267. } else {
  268. f->store_line("[gd_resource type=\"Environment\" load_steps=2 format=2]");
  269. f->store_line("[sub_resource type=\"ProceduralSky\" id=1]");
  270. f->store_line("[resource]");
  271. f->store_line("background_mode = 2");
  272. f->store_line("background_sky = SubResource( 1 )");
  273. memdelete(f);
  274. }
  275. }
  276. } else if (mode == MODE_INSTALL) {
  277. FileAccess *src_f = NULL;
  278. zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
  279. unzFile pkg = unzOpen2(zip_path.utf8().get_data(), &io);
  280. if (!pkg) {
  281. dialog_error->set_text(TTR("Error opening package file, not in zip format."));
  282. return;
  283. }
  284. int ret = unzGoToFirstFile(pkg);
  285. Vector<String> failed_files;
  286. int idx = 0;
  287. while (ret == UNZ_OK) {
  288. //get filename
  289. unz_file_info info;
  290. char fname[16384];
  291. ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, NULL, 0, NULL, 0);
  292. String path = fname;
  293. int depth = 1; //stuff from github comes with tag
  294. bool skip = false;
  295. while (depth > 0) {
  296. int pp = path.find("/");
  297. if (pp == -1) {
  298. skip = true;
  299. break;
  300. }
  301. path = path.substr(pp + 1, path.length());
  302. depth--;
  303. }
  304. if (skip || path == String()) {
  305. //
  306. } else if (path.ends_with("/")) { // a dir
  307. path = path.substr(0, path.length() - 1);
  308. DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
  309. da->make_dir(dir.plus_file(path));
  310. memdelete(da);
  311. } else {
  312. Vector<uint8_t> data;
  313. data.resize(info.uncompressed_size);
  314. //read
  315. unzOpenCurrentFile(pkg);
  316. unzReadCurrentFile(pkg, data.ptr(), data.size());
  317. unzCloseCurrentFile(pkg);
  318. FileAccess *f = FileAccess::open(dir.plus_file(path), FileAccess::WRITE);
  319. if (f) {
  320. f->store_buffer(data.ptr(), data.size());
  321. memdelete(f);
  322. } else {
  323. failed_files.push_back(path);
  324. }
  325. }
  326. idx++;
  327. ret = unzGoToNextFile(pkg);
  328. }
  329. unzClose(pkg);
  330. if (failed_files.size()) {
  331. String msg = TTR("The following files failed extraction from package:") + "\n\n";
  332. for (int i = 0; i < failed_files.size(); i++) {
  333. if (i > 15) {
  334. msg += "\nAnd " + itos(failed_files.size() - i) + " more files.";
  335. break;
  336. }
  337. msg += failed_files[i] + "\n";
  338. }
  339. dialog_error->set_text(msg);
  340. dialog_error->popup_centered_minsize();
  341. } else {
  342. dialog_error->set_text(TTR("Package Installed Successfully!"));
  343. dialog_error->popup_centered_minsize();
  344. }
  345. }
  346. }
  347. dir = dir.replace("\\", "/");
  348. if (dir.ends_with("/"))
  349. dir = dir.substr(0, dir.length() - 1);
  350. String proj = dir.replace("/", "::");
  351. EditorSettings::get_singleton()->set("projects/" + proj, dir);
  352. EditorSettings::get_singleton()->save();
  353. hide();
  354. emit_signal("project_created", dir);
  355. }
  356. }
  357. void _remove_created_folder() {
  358. if (created_folder_path != "") {
  359. DirAccess *d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
  360. d->remove(created_folder_path);
  361. memdelete(d);
  362. create_dir->set_disabled(false);
  363. created_folder_path = "";
  364. }
  365. }
  366. void _toggle_message() {
  367. msg->set_visible(!msg->is_visible());
  368. }
  369. void cancel_pressed() {
  370. _remove_created_folder();
  371. project_path->clear();
  372. project_name->clear();
  373. }
  374. protected:
  375. static void _bind_methods() {
  376. ClassDB::bind_method("_browse_path", &ProjectDialog::_browse_path);
  377. ClassDB::bind_method("_create_folder", &ProjectDialog::_create_folder);
  378. ClassDB::bind_method("_text_changed", &ProjectDialog::_text_changed);
  379. ClassDB::bind_method("_path_text_changed", &ProjectDialog::_path_text_changed);
  380. ClassDB::bind_method("_path_selected", &ProjectDialog::_path_selected);
  381. ClassDB::bind_method("_file_selected", &ProjectDialog::_file_selected);
  382. ClassDB::bind_method("_toggle_message", &ProjectDialog::_toggle_message);
  383. ADD_SIGNAL(MethodInfo("project_created"));
  384. ADD_SIGNAL(MethodInfo("project_renamed"));
  385. }
  386. public:
  387. void set_zip_path(const String &p_path) {
  388. zip_path = p_path;
  389. }
  390. void set_zip_title(const String &p_title) {
  391. zip_title = p_title;
  392. }
  393. void set_mode(Mode p_mode) {
  394. mode = p_mode;
  395. }
  396. void set_project_path(const String &p_path) {
  397. project_path->set_text(p_path);
  398. }
  399. void show_dialog() {
  400. if (mode == MODE_RENAME) {
  401. project_path->set_editable(false);
  402. browse->hide();
  403. set_title(TTR("Rename Project"));
  404. get_ok()->set_text(TTR("Rename"));
  405. name_container->show();
  406. ProjectSettings *current = memnew(ProjectSettings);
  407. current->add_singleton(ProjectSettings::Singleton("Current"));
  408. if (current->setup(project_path->get_text(), "")) {
  409. set_message(TTR("Couldn't get project.godot in the project path."), MESSAGE_ERROR);
  410. } else if (current->has("application/config/name")) {
  411. project_name->set_text(current->get("application/config/name"));
  412. }
  413. project_name->grab_focus();
  414. create_dir->hide();
  415. status_btn->hide();
  416. } else {
  417. fav_dir = EditorSettings::get_singleton()->get("filesystem/directories/default_project_path");
  418. if (fav_dir != "") {
  419. project_path->set_text(fav_dir);
  420. fdialog->set_current_dir(fav_dir);
  421. } else {
  422. DirAccess *d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
  423. project_path->set_text(d->get_current_dir());
  424. fdialog->set_current_dir(d->get_current_dir());
  425. memdelete(d);
  426. }
  427. project_name->set_text(TTR("New Game Project"));
  428. project_path->set_editable(true);
  429. browse->set_disabled(false);
  430. browse->show();
  431. create_dir->show();
  432. status_btn->show();
  433. if (mode == MODE_IMPORT) {
  434. set_title(TTR("Import Existing Project"));
  435. get_ok()->set_text(TTR("Import"));
  436. name_container->hide();
  437. project_path->grab_focus();
  438. } else if (mode == MODE_NEW) {
  439. set_title(TTR("Create New Project"));
  440. get_ok()->set_text(TTR("Create"));
  441. name_container->show();
  442. project_name->grab_focus();
  443. } else if (mode == MODE_INSTALL) {
  444. set_title(TTR("Install Project:") + " " + zip_title);
  445. get_ok()->set_text(TTR("Install"));
  446. name_container->hide();
  447. project_path->grab_focus();
  448. }
  449. _test_path();
  450. }
  451. popup_centered(Size2(500, 125) * EDSCALE);
  452. }
  453. ProjectDialog() {
  454. VBoxContainer *vb = memnew(VBoxContainer);
  455. add_child(vb);
  456. name_container = memnew(VBoxContainer);
  457. vb->add_child(name_container);
  458. Label *l = memnew(Label);
  459. l->set_text(TTR("Project Name:"));
  460. name_container->add_child(l);
  461. HBoxContainer *pnhb = memnew(HBoxContainer);
  462. name_container->add_child(pnhb);
  463. project_name = memnew(LineEdit);
  464. project_name->set_h_size_flags(SIZE_EXPAND_FILL);
  465. pnhb->add_child(project_name);
  466. create_dir = memnew(Button);
  467. pnhb->add_child(create_dir);
  468. create_dir->set_text(TTR("Create folder"));
  469. create_dir->connect("pressed", this, "_create_folder");
  470. path_container = memnew(VBoxContainer);
  471. vb->add_child(path_container);
  472. l = memnew(Label);
  473. l->set_text(TTR("Project Path:"));
  474. path_container->add_child(l);
  475. HBoxContainer *pphb = memnew(HBoxContainer);
  476. path_container->add_child(pphb);
  477. project_path = memnew(LineEdit);
  478. project_path->set_h_size_flags(SIZE_EXPAND_FILL);
  479. pphb->add_child(project_path);
  480. // status button
  481. status_btn = memnew(ToolButton);
  482. status_btn->connect("pressed", this, "_toggle_message");
  483. pphb->add_child(status_btn);
  484. browse = memnew(Button);
  485. browse->set_text(TTR("Browse"));
  486. browse->connect("pressed", this, "_browse_path");
  487. pphb->add_child(browse);
  488. msg = memnew(Label);
  489. msg->set_text(TTR("That's a BINGO!"));
  490. msg->set_align(Label::ALIGN_CENTER);
  491. msg->hide();
  492. vb->add_child(msg);
  493. fdialog = memnew(FileDialog);
  494. fdialog->set_access(FileDialog::ACCESS_FILESYSTEM);
  495. add_child(fdialog);
  496. project_name->connect("text_changed", this, "_text_changed");
  497. project_path->connect("text_changed", this, "_path_text_changed");
  498. fdialog->connect("dir_selected", this, "_path_selected");
  499. fdialog->connect("file_selected", this, "_file_selected");
  500. set_hide_on_ok(false);
  501. mode = MODE_NEW;
  502. dialog_error = memnew(AcceptDialog);
  503. add_child(dialog_error);
  504. }
  505. };
  506. struct ProjectItem {
  507. String project;
  508. String path;
  509. String conf;
  510. uint64_t last_modified;
  511. bool favorite;
  512. bool grayed;
  513. ProjectItem() {}
  514. ProjectItem(const String &p_project, const String &p_path, const String &p_conf, uint64_t p_last_modified, bool p_favorite = false, bool p_grayed = false) {
  515. project = p_project;
  516. path = p_path;
  517. conf = p_conf;
  518. last_modified = p_last_modified;
  519. favorite = p_favorite;
  520. grayed = p_grayed;
  521. }
  522. _FORCE_INLINE_ bool operator<(const ProjectItem &l) const { return last_modified > l.last_modified; }
  523. _FORCE_INLINE_ bool operator==(const ProjectItem &l) const { return project == l.project; }
  524. };
  525. void ProjectManager::_notification(int p_what) {
  526. if (p_what == NOTIFICATION_ENTER_TREE) {
  527. Engine::get_singleton()->set_editor_hint(false);
  528. } else if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
  529. set_process_unhandled_input(is_visible_in_tree());
  530. }
  531. }
  532. void ProjectManager::_panel_draw(Node *p_hb) {
  533. HBoxContainer *hb = Object::cast_to<HBoxContainer>(p_hb);
  534. hb->draw_line(Point2(0, hb->get_size().y + 1), Point2(hb->get_size().x - 10, hb->get_size().y + 1), get_color("guide_color", "Tree"));
  535. if (selected_list.has(hb->get_meta("name"))) {
  536. hb->draw_style_box(gui_base->get_stylebox("selected", "Tree"), Rect2(Point2(), hb->get_size() - Size2(10, 0) * EDSCALE));
  537. }
  538. }
  539. void ProjectManager::_update_project_buttons() {
  540. for (int i = 0; i < scroll_childs->get_child_count(); i++) {
  541. CanvasItem *item = Object::cast_to<CanvasItem>(scroll_childs->get_child(i));
  542. item->update();
  543. }
  544. erase_btn->set_disabled(selected_list.size() < 1);
  545. open_btn->set_disabled(selected_list.size() < 1);
  546. rename_btn->set_disabled(selected_list.size() < 1);
  547. }
  548. void ProjectManager::_panel_input(const Ref<InputEvent> &p_ev, Node *p_hb) {
  549. Ref<InputEventMouseButton> mb = p_ev;
  550. if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
  551. String clicked = p_hb->get_meta("name");
  552. String clicked_main_scene = p_hb->get_meta("main_scene");
  553. if (mb->get_shift() && selected_list.size() > 0 && last_clicked != "" && clicked != last_clicked) {
  554. int clicked_id = -1;
  555. int last_clicked_id = -1;
  556. for (int i = 0; i < scroll_childs->get_child_count(); i++) {
  557. HBoxContainer *hb = Object::cast_to<HBoxContainer>(scroll_childs->get_child(i));
  558. if (!hb) continue;
  559. if (hb->get_meta("name") == clicked) clicked_id = i;
  560. if (hb->get_meta("name") == last_clicked) last_clicked_id = i;
  561. }
  562. if (last_clicked_id != -1 && clicked_id != -1) {
  563. int min = clicked_id < last_clicked_id ? clicked_id : last_clicked_id;
  564. int max = clicked_id > last_clicked_id ? clicked_id : last_clicked_id;
  565. for (int i = 0; i < scroll_childs->get_child_count(); ++i) {
  566. HBoxContainer *hb = Object::cast_to<HBoxContainer>(scroll_childs->get_child(i));
  567. if (!hb) continue;
  568. if (i != clicked_id && (i < min || i > max) && !mb->get_control()) {
  569. selected_list.erase(hb->get_meta("name"));
  570. } else if (i >= min && i <= max) {
  571. selected_list.insert(hb->get_meta("name"), hb->get_meta("main_scene"));
  572. }
  573. }
  574. }
  575. } else if (selected_list.has(clicked) && mb->get_control()) {
  576. selected_list.erase(clicked);
  577. } else {
  578. last_clicked = clicked;
  579. if (mb->get_control() || selected_list.size() == 0) {
  580. selected_list.insert(clicked, clicked_main_scene);
  581. } else {
  582. selected_list.clear();
  583. selected_list.insert(clicked, clicked_main_scene);
  584. }
  585. }
  586. _update_project_buttons();
  587. if (mb->is_doubleclick())
  588. _open_project(); //open if doubleclicked
  589. }
  590. }
  591. void ProjectManager::_unhandled_input(const Ref<InputEvent> &p_ev) {
  592. Ref<InputEventKey> k = p_ev;
  593. if (k.is_valid()) {
  594. if (!k->is_pressed())
  595. return;
  596. bool scancode_handled = true;
  597. switch (k->get_scancode()) {
  598. case KEY_ENTER: {
  599. _open_project();
  600. } break;
  601. case KEY_HOME: {
  602. for (int i = 0; i < scroll_childs->get_child_count(); i++) {
  603. HBoxContainer *hb = Object::cast_to<HBoxContainer>(scroll_childs->get_child(i));
  604. if (hb) {
  605. selected_list.clear();
  606. selected_list.insert(hb->get_meta("name"), hb->get_meta("main_scene"));
  607. scroll->set_v_scroll(0);
  608. _update_project_buttons();
  609. break;
  610. }
  611. }
  612. } break;
  613. case KEY_END: {
  614. for (int i = scroll_childs->get_child_count() - 1; i >= 0; i--) {
  615. HBoxContainer *hb = Object::cast_to<HBoxContainer>(scroll_childs->get_child(i));
  616. if (hb) {
  617. selected_list.clear();
  618. selected_list.insert(hb->get_meta("name"), hb->get_meta("main_scene"));
  619. scroll->set_v_scroll(scroll_childs->get_size().y);
  620. _update_project_buttons();
  621. break;
  622. }
  623. }
  624. } break;
  625. case KEY_UP: {
  626. if (k->get_shift())
  627. break;
  628. if (selected_list.size()) {
  629. bool found = false;
  630. for (int i = scroll_childs->get_child_count() - 1; i >= 0; i--) {
  631. HBoxContainer *hb = Object::cast_to<HBoxContainer>(scroll_childs->get_child(i));
  632. if (!hb) continue;
  633. String current = hb->get_meta("name");
  634. if (found) {
  635. selected_list.clear();
  636. selected_list.insert(current, hb->get_meta("main_scene"));
  637. int offset_diff = scroll->get_v_scroll() - hb->get_position().y;
  638. if (offset_diff > 0)
  639. scroll->set_v_scroll(scroll->get_v_scroll() - offset_diff);
  640. _update_project_buttons();
  641. break;
  642. } else if (current == selected_list.back()->key()) {
  643. found = true;
  644. }
  645. }
  646. break;
  647. }
  648. // else fallthrough to key_down
  649. }
  650. case KEY_DOWN: {
  651. if (k->get_shift())
  652. break;
  653. bool found = selected_list.empty();
  654. for (int i = 0; i < scroll_childs->get_child_count(); i++) {
  655. HBoxContainer *hb = Object::cast_to<HBoxContainer>(scroll_childs->get_child(i));
  656. if (!hb) continue;
  657. String current = hb->get_meta("name");
  658. if (found) {
  659. selected_list.clear();
  660. selected_list.insert(current, hb->get_meta("main_scene"));
  661. int last_y_visible = scroll->get_v_scroll() + scroll->get_size().y;
  662. int offset_diff = (hb->get_position().y + hb->get_size().y) - last_y_visible;
  663. if (offset_diff > 0)
  664. scroll->set_v_scroll(scroll->get_v_scroll() + offset_diff);
  665. _update_project_buttons();
  666. break;
  667. } else if (current == selected_list.back()->key()) {
  668. found = true;
  669. }
  670. }
  671. } break;
  672. case KEY_F: {
  673. if (k->get_command())
  674. this->project_filter->search_box->grab_focus();
  675. else
  676. scancode_handled = false;
  677. } break;
  678. default: {
  679. scancode_handled = false;
  680. } break;
  681. }
  682. if (scancode_handled) {
  683. accept_event();
  684. }
  685. }
  686. }
  687. void ProjectManager::_favorite_pressed(Node *p_hb) {
  688. String clicked = p_hb->get_meta("name");
  689. bool favorite = !p_hb->get_meta("favorite");
  690. String proj = clicked.replace(":::", ":/");
  691. proj = proj.replace("::", "/");
  692. if (favorite) {
  693. EditorSettings::get_singleton()->set("favorite_projects/" + clicked, proj);
  694. } else {
  695. EditorSettings::get_singleton()->erase("favorite_projects/" + clicked);
  696. }
  697. EditorSettings::get_singleton()->save();
  698. call_deferred("_load_recent_projects");
  699. }
  700. void ProjectManager::_load_recent_projects() {
  701. ProjectListFilter::FilterOption filter_option = project_filter->get_filter_option();
  702. String search_term = project_filter->get_search_term();
  703. while (scroll_childs->get_child_count() > 0) {
  704. memdelete(scroll_childs->get_child(0));
  705. }
  706. Map<String, String> selected_list_copy = selected_list;
  707. List<PropertyInfo> properties;
  708. EditorSettings::get_singleton()->get_property_list(&properties);
  709. Color font_color = gui_base->get_color("font_color", "Tree");
  710. List<ProjectItem> projects;
  711. List<ProjectItem> favorite_projects;
  712. for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
  713. String _name = E->get().name;
  714. if (!_name.begins_with("projects/") && !_name.begins_with("favorite_projects/"))
  715. continue;
  716. String path = EditorSettings::get_singleton()->get(_name);
  717. if (filter_option == ProjectListFilter::FILTER_PATH && search_term != "" && path.findn(search_term) == -1)
  718. continue;
  719. String project = _name.get_slice("/", 1);
  720. String conf = path.plus_file("project.godot");
  721. bool favorite = (_name.begins_with("favorite_projects/")) ? true : false;
  722. bool grayed = false;
  723. uint64_t last_modified = 0;
  724. if (FileAccess::exists(conf)) {
  725. last_modified = FileAccess::get_modified_time(conf);
  726. String fscache = path.plus_file(".fscache");
  727. if (FileAccess::exists(fscache)) {
  728. uint64_t cache_modified = FileAccess::get_modified_time(fscache);
  729. if (cache_modified > last_modified)
  730. last_modified = cache_modified;
  731. }
  732. } else {
  733. grayed = true;
  734. }
  735. ProjectItem item(project, path, conf, last_modified, favorite, grayed);
  736. if (favorite)
  737. favorite_projects.push_back(item);
  738. else
  739. projects.push_back(item);
  740. }
  741. projects.sort();
  742. favorite_projects.sort();
  743. for (List<ProjectItem>::Element *E = projects.front(); E;) {
  744. List<ProjectItem>::Element *next = E->next();
  745. if (favorite_projects.find(E->get()) != NULL)
  746. projects.erase(E->get());
  747. E = next;
  748. }
  749. for (List<ProjectItem>::Element *E = favorite_projects.back(); E; E = E->prev()) {
  750. projects.push_front(E->get());
  751. }
  752. Ref<Texture> favorite_icon = get_icon("Favorites", "EditorIcons");
  753. for (List<ProjectItem>::Element *E = projects.front(); E; E = E->next()) {
  754. ProjectItem &item = E->get();
  755. String project = item.project;
  756. String path = item.path;
  757. String conf = item.conf;
  758. bool is_favorite = item.favorite;
  759. bool is_grayed = item.grayed;
  760. Ref<ConfigFile> cf = memnew(ConfigFile);
  761. Error cf_err = cf->load(conf);
  762. String project_name = TTR("Unnamed Project");
  763. if (cf_err == OK && cf->has_section_key("application", "config/name")) {
  764. project_name = static_cast<String>(cf->get_value("application", "config/name")).xml_unescape();
  765. }
  766. if (filter_option == ProjectListFilter::FILTER_NAME && search_term != "" && project_name.findn(search_term) == -1)
  767. continue;
  768. Ref<Texture> icon;
  769. if (cf_err == OK && cf->has_section_key("application", "config/icon")) {
  770. String appicon = cf->get_value("application", "config/icon");
  771. if (appicon != "") {
  772. Ref<Image> img;
  773. img.instance();
  774. Error err = img->load(appicon.replace_first("res://", path + "/"));
  775. if (err == OK) {
  776. Ref<Texture> default_icon = get_icon("DefaultProjectIcon", "EditorIcons");
  777. img->resize(default_icon->get_width(), default_icon->get_height());
  778. Ref<ImageTexture> it = memnew(ImageTexture);
  779. it->create_from_image(img);
  780. icon = it;
  781. }
  782. }
  783. }
  784. if (icon.is_null()) {
  785. icon = get_icon("DefaultProjectIcon", "EditorIcons");
  786. }
  787. String main_scene;
  788. if (cf_err == OK && cf->has_section_key("application", "run/main_scene")) {
  789. main_scene = cf->get_value("application", "run/main_scene");
  790. } else {
  791. main_scene = "";
  792. }
  793. selected_list_copy.erase(project);
  794. HBoxContainer *hb = memnew(HBoxContainer);
  795. hb->set_meta("name", project);
  796. hb->set_meta("main_scene", main_scene);
  797. hb->set_meta("favorite", is_favorite);
  798. hb->connect("draw", this, "_panel_draw", varray(hb));
  799. hb->connect("gui_input", this, "_panel_input", varray(hb));
  800. hb->add_constant_override("separation", 10 * EDSCALE);
  801. VBoxContainer *favorite_box = memnew(VBoxContainer);
  802. TextureButton *favorite = memnew(TextureButton);
  803. favorite->set_normal_texture(favorite_icon);
  804. if (!is_favorite)
  805. favorite->set_modulate(Color(1, 1, 1, 0.2));
  806. favorite->set_v_size_flags(SIZE_EXPAND);
  807. favorite->connect("pressed", this, "_favorite_pressed", varray(hb));
  808. favorite_box->add_child(favorite);
  809. hb->add_child(favorite_box);
  810. TextureRect *tf = memnew(TextureRect);
  811. tf->set_texture(icon);
  812. hb->add_child(tf);
  813. VBoxContainer *vb = memnew(VBoxContainer);
  814. if (is_grayed)
  815. vb->set_modulate(Color(0.5, 0.5, 0.5));
  816. vb->set_name("project");
  817. vb->set_h_size_flags(SIZE_EXPAND_FILL);
  818. hb->add_child(vb);
  819. Control *ec = memnew(Control);
  820. ec->set_custom_minimum_size(Size2(0, 1));
  821. vb->add_child(ec);
  822. Label *title = memnew(Label(project_name));
  823. title->add_font_override("font", gui_base->get_font("large", "Fonts"));
  824. title->add_color_override("font_color", font_color);
  825. title->set_clip_text(true);
  826. vb->add_child(title);
  827. Label *fpath = memnew(Label(path));
  828. fpath->set_name("path");
  829. vb->add_child(fpath);
  830. fpath->set_modulate(Color(1, 1, 1, 0.5));
  831. fpath->add_color_override("font_color", font_color);
  832. fpath->set_clip_text(true);
  833. scroll_childs->add_child(hb);
  834. }
  835. for (Map<String, String>::Element *E = selected_list_copy.front(); E; E = E->next()) {
  836. String key = E->key();
  837. selected_list.erase(key);
  838. }
  839. scroll->set_v_scroll(0);
  840. _update_project_buttons();
  841. EditorSettings::get_singleton()->save();
  842. tabs->set_current_tab(0);
  843. }
  844. void ProjectManager::_on_project_renamed() {
  845. _load_recent_projects();
  846. }
  847. void ProjectManager::_on_project_created(const String &dir) {
  848. bool has_already = false;
  849. for (int i = 0; i < scroll_childs->get_child_count(); i++) {
  850. HBoxContainer *hb = Object::cast_to<HBoxContainer>(scroll_childs->get_child(i));
  851. Label *fpath = Object::cast_to<Label>(hb->get_node(NodePath("project/path")));
  852. if (fpath->get_text() == dir) {
  853. has_already = true;
  854. break;
  855. }
  856. }
  857. if (has_already) {
  858. _update_scroll_position(dir);
  859. } else {
  860. _load_recent_projects();
  861. _update_scroll_position(dir);
  862. }
  863. _open_project();
  864. }
  865. void ProjectManager::_update_scroll_position(const String &dir) {
  866. for (int i = 0; i < scroll_childs->get_child_count(); i++) {
  867. HBoxContainer *hb = Object::cast_to<HBoxContainer>(scroll_childs->get_child(i));
  868. Label *fpath = Object::cast_to<Label>(hb->get_node(NodePath("project/path")));
  869. if (fpath->get_text() == dir) {
  870. last_clicked = hb->get_meta("name");
  871. selected_list.clear();
  872. selected_list.insert(hb->get_meta("name"), hb->get_meta("main_scene"));
  873. _update_project_buttons();
  874. int last_y_visible = scroll->get_v_scroll() + scroll->get_size().y;
  875. int offset_diff = (hb->get_position().y + hb->get_size().y) - last_y_visible;
  876. if (offset_diff > 0)
  877. scroll->set_v_scroll(scroll->get_v_scroll() + offset_diff);
  878. break;
  879. }
  880. }
  881. }
  882. void ProjectManager::_open_project_confirm() {
  883. for (Map<String, String>::Element *E = selected_list.front(); E; E = E->next()) {
  884. const String &selected = E->key();
  885. String path = EditorSettings::get_singleton()->get("projects/" + selected);
  886. String conf = path + "/project.godot";
  887. if (!FileAccess::exists(conf)) {
  888. dialog_error->set_text(TTR("Can't open project"));
  889. dialog_error->popup_centered_minsize();
  890. return;
  891. }
  892. print_line("OPENING: " + path + " (" + selected + ")");
  893. List<String> args;
  894. args.push_back("--path");
  895. args.push_back(path);
  896. args.push_back("--editor");
  897. if (OS::get_singleton()->is_disable_crash_handler()) {
  898. args.push_back("--disable-crash-handler");
  899. }
  900. String exec = OS::get_singleton()->get_executable_path();
  901. OS::ProcessID pid = 0;
  902. Error err = OS::get_singleton()->execute(exec, args, false, &pid);
  903. ERR_FAIL_COND(err);
  904. }
  905. get_tree()->quit();
  906. }
  907. void ProjectManager::_open_project() {
  908. if (selected_list.size() < 1) {
  909. return;
  910. }
  911. if (selected_list.size() > 1) {
  912. multi_open_ask->set_text(TTR("Are you sure to open more than one project?"));
  913. multi_open_ask->popup_centered_minsize();
  914. } else {
  915. _open_project_confirm();
  916. }
  917. }
  918. void ProjectManager::_run_project_confirm() {
  919. for (Map<String, String>::Element *E = selected_list.front(); E; E = E->next()) {
  920. const String &selected_main = E->get();
  921. if (selected_main == "") {
  922. run_error_diag->set_text(TTR("Can't run project: no main scene defined.\nPlease edit the project and set the main scene in \"Project Settings\" under the \"Application\" category."));
  923. run_error_diag->popup_centered();
  924. return;
  925. }
  926. const String &selected = E->key();
  927. String path = EditorSettings::get_singleton()->get("projects/" + selected);
  928. if (!DirAccess::exists(path + "/.import")) {
  929. run_error_diag->set_text(TTR("Can't run project: Assets need to be imported.\nPlease edit the project to trigger the initial import."));
  930. run_error_diag->popup_centered();
  931. return;
  932. }
  933. print_line("OPENING: " + path + " (" + selected + ")");
  934. List<String> args;
  935. args.push_back("--path");
  936. args.push_back(path);
  937. if (OS::get_singleton()->is_disable_crash_handler()) {
  938. args.push_back("--disable-crash-handler");
  939. }
  940. String exec = OS::get_singleton()->get_executable_path();
  941. OS::ProcessID pid = 0;
  942. Error err = OS::get_singleton()->execute(exec, args, false, &pid);
  943. ERR_FAIL_COND(err);
  944. }
  945. //get_scene()->quit(); do not quit
  946. }
  947. void ProjectManager::_run_project() {
  948. if (selected_list.size() < 1) {
  949. return;
  950. }
  951. if (selected_list.size() > 1) {
  952. multi_run_ask->set_text(TTR("Are you sure to run more than one project?"));
  953. multi_run_ask->popup_centered_minsize();
  954. } else {
  955. _run_project_confirm();
  956. }
  957. }
  958. void ProjectManager::_scan_dir(DirAccess *da, float pos, float total, List<String> *r_projects) {
  959. List<String> subdirs;
  960. da->list_dir_begin();
  961. String n = da->get_next();
  962. while (n != String()) {
  963. if (da->current_is_dir() && !n.begins_with(".")) {
  964. subdirs.push_front(n);
  965. } else if (n == "project.godot") {
  966. r_projects->push_back(da->get_current_dir());
  967. }
  968. n = da->get_next();
  969. }
  970. da->list_dir_end();
  971. int m = 0;
  972. for (List<String>::Element *E = subdirs.front(); E; E = E->next()) {
  973. da->change_dir(E->get());
  974. float slice = total / subdirs.size();
  975. _scan_dir(da, pos + slice * m, slice, r_projects);
  976. da->change_dir("..");
  977. m++;
  978. }
  979. }
  980. void ProjectManager::_scan_begin(const String &p_base) {
  981. print_line("SCAN PROJECTS AT: " + p_base);
  982. List<String> projects;
  983. DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
  984. da->change_dir(p_base);
  985. _scan_dir(da, 0, 1, &projects);
  986. memdelete(da);
  987. print_line("found: " + itos(projects.size()) + " projects.");
  988. for (List<String>::Element *E = projects.front(); E; E = E->next()) {
  989. String proj = E->get().replace("/", "::");
  990. EditorSettings::get_singleton()->set("projects/" + proj, E->get());
  991. }
  992. EditorSettings::get_singleton()->save();
  993. _load_recent_projects();
  994. }
  995. void ProjectManager::_scan_projects() {
  996. scan_dir->popup_centered_ratio();
  997. }
  998. void ProjectManager::_new_project() {
  999. npdialog->set_mode(ProjectDialog::MODE_NEW);
  1000. npdialog->show_dialog();
  1001. }
  1002. void ProjectManager::_import_project() {
  1003. npdialog->set_mode(ProjectDialog::MODE_IMPORT);
  1004. npdialog->show_dialog();
  1005. }
  1006. void ProjectManager::_rename_project() {
  1007. if (selected_list.size() == 0) {
  1008. return;
  1009. }
  1010. for (Map<String, String>::Element *E = selected_list.front(); E; E = E->next()) {
  1011. const String &selected = E->key();
  1012. String path = EditorSettings::get_singleton()->get("projects/" + selected);
  1013. npdialog->set_project_path(path);
  1014. npdialog->set_mode(ProjectDialog::MODE_RENAME);
  1015. npdialog->show_dialog();
  1016. }
  1017. }
  1018. void ProjectManager::_erase_project_confirm() {
  1019. if (selected_list.size() == 0) {
  1020. return;
  1021. }
  1022. for (Map<String, String>::Element *E = selected_list.front(); E; E = E->next()) {
  1023. EditorSettings::get_singleton()->erase("projects/" + E->key());
  1024. EditorSettings::get_singleton()->erase("favorite_projects/" + E->key());
  1025. }
  1026. EditorSettings::get_singleton()->save();
  1027. selected_list.clear();
  1028. last_clicked = "";
  1029. _load_recent_projects();
  1030. }
  1031. void ProjectManager::_erase_project() {
  1032. if (selected_list.size() == 0)
  1033. return;
  1034. erase_ask->set_text(TTR("Remove project from the list? (Folder contents will not be modified)"));
  1035. erase_ask->popup_centered_minsize();
  1036. }
  1037. void ProjectManager::_exit_dialog() {
  1038. get_tree()->quit();
  1039. }
  1040. void ProjectManager::_install_project(const String &p_zip_path, const String &p_title) {
  1041. npdialog->set_mode(ProjectDialog::MODE_INSTALL);
  1042. npdialog->set_zip_path(p_zip_path);
  1043. npdialog->set_zip_title(p_title);
  1044. npdialog->show_dialog();
  1045. }
  1046. void ProjectManager::_files_dropped(PoolStringArray p_files, int p_screen) {
  1047. Set<String> folders_set;
  1048. DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
  1049. for (int i = 0; i < p_files.size(); i++) {
  1050. String file = p_files[i];
  1051. folders_set.insert(da->dir_exists(file) ? file : file.get_base_dir());
  1052. }
  1053. memdelete(da);
  1054. if (folders_set.size() > 0) {
  1055. PoolStringArray folders;
  1056. for (Set<String>::Element *E = folders_set.front(); E; E = E->next()) {
  1057. folders.append(E->get());
  1058. }
  1059. bool confirm = true;
  1060. if (folders.size() == 1) {
  1061. DirAccess *dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
  1062. if (dir->change_dir(folders[0]) == OK) {
  1063. dir->list_dir_begin();
  1064. String file = dir->get_next();
  1065. while (confirm && file != String()) {
  1066. if (!dir->current_is_dir() && file.ends_with("project.godot")) {
  1067. confirm = false;
  1068. }
  1069. file = dir->get_next();
  1070. }
  1071. dir->list_dir_end();
  1072. }
  1073. memdelete(dir);
  1074. }
  1075. if (confirm) {
  1076. multi_scan_ask->get_ok()->disconnect("pressed", this, "_scan_multiple_folders");
  1077. multi_scan_ask->get_ok()->connect("pressed", this, "_scan_multiple_folders", varray(folders));
  1078. multi_scan_ask->set_text(vformat(TTR("You are about the scan %s folders for existing Godot projects. Do you confirm?"), folders.size()));
  1079. multi_scan_ask->popup_centered_minsize();
  1080. } else {
  1081. _scan_multiple_folders(folders);
  1082. }
  1083. }
  1084. }
  1085. void ProjectManager::_scan_multiple_folders(PoolStringArray p_files) {
  1086. for (int i = 0; i < p_files.size(); i++) {
  1087. _scan_begin(p_files.get(i));
  1088. }
  1089. }
  1090. void ProjectManager::_bind_methods() {
  1091. ClassDB::bind_method("_open_project", &ProjectManager::_open_project);
  1092. ClassDB::bind_method("_open_project_confirm", &ProjectManager::_open_project_confirm);
  1093. ClassDB::bind_method("_run_project", &ProjectManager::_run_project);
  1094. ClassDB::bind_method("_run_project_confirm", &ProjectManager::_run_project_confirm);
  1095. ClassDB::bind_method("_scan_projects", &ProjectManager::_scan_projects);
  1096. ClassDB::bind_method("_scan_begin", &ProjectManager::_scan_begin);
  1097. ClassDB::bind_method("_import_project", &ProjectManager::_import_project);
  1098. ClassDB::bind_method("_new_project", &ProjectManager::_new_project);
  1099. ClassDB::bind_method("_rename_project", &ProjectManager::_rename_project);
  1100. ClassDB::bind_method("_erase_project", &ProjectManager::_erase_project);
  1101. ClassDB::bind_method("_erase_project_confirm", &ProjectManager::_erase_project_confirm);
  1102. ClassDB::bind_method("_exit_dialog", &ProjectManager::_exit_dialog);
  1103. ClassDB::bind_method("_load_recent_projects", &ProjectManager::_load_recent_projects);
  1104. ClassDB::bind_method("_on_project_renamed", &ProjectManager::_on_project_renamed);
  1105. ClassDB::bind_method("_on_project_created", &ProjectManager::_on_project_created);
  1106. ClassDB::bind_method("_update_scroll_position", &ProjectManager::_update_scroll_position);
  1107. ClassDB::bind_method("_panel_draw", &ProjectManager::_panel_draw);
  1108. ClassDB::bind_method("_panel_input", &ProjectManager::_panel_input);
  1109. ClassDB::bind_method("_unhandled_input", &ProjectManager::_unhandled_input);
  1110. ClassDB::bind_method("_favorite_pressed", &ProjectManager::_favorite_pressed);
  1111. ClassDB::bind_method("_install_project", &ProjectManager::_install_project);
  1112. ClassDB::bind_method("_files_dropped", &ProjectManager::_files_dropped);
  1113. ClassDB::bind_method(D_METHOD("_scan_multiple_folders", "files"), &ProjectManager::_scan_multiple_folders);
  1114. }
  1115. ProjectManager::ProjectManager() {
  1116. // load settings
  1117. if (!EditorSettings::get_singleton())
  1118. EditorSettings::create();
  1119. EditorSettings::get_singleton()->set_optimize_save(false); //just write settings as they came
  1120. {
  1121. int dpi_mode = EditorSettings::get_singleton()->get("interface/editor/hidpi_mode");
  1122. if (dpi_mode == 0) {
  1123. const int screen = OS::get_singleton()->get_current_screen();
  1124. editor_set_scale(OS::get_singleton()->get_screen_dpi(screen) >= 192 && OS::get_singleton()->get_screen_size(screen).x > 2000 ? 2.0 : 1.0);
  1125. } else if (dpi_mode == 1) {
  1126. editor_set_scale(0.75);
  1127. } else if (dpi_mode == 2) {
  1128. editor_set_scale(1.0);
  1129. } else if (dpi_mode == 3) {
  1130. editor_set_scale(1.5);
  1131. } else if (dpi_mode == 4) {
  1132. editor_set_scale(2.0);
  1133. }
  1134. }
  1135. FileDialog::set_default_show_hidden_files(EditorSettings::get_singleton()->get("filesystem/file_dialog/show_hidden_files"));
  1136. set_anchors_and_margins_preset(Control::PRESET_WIDE);
  1137. set_theme(create_editor_theme());
  1138. gui_base = memnew(Control);
  1139. add_child(gui_base);
  1140. gui_base->set_anchors_and_margins_preset(Control::PRESET_WIDE);
  1141. gui_base->set_theme(create_custom_theme());
  1142. Panel *panel = memnew(Panel);
  1143. gui_base->add_child(panel);
  1144. panel->set_anchors_and_margins_preset(Control::PRESET_WIDE);
  1145. VBoxContainer *vb = memnew(VBoxContainer);
  1146. panel->add_child(vb);
  1147. vb->set_anchors_and_margins_preset(Control::PRESET_WIDE, Control::PRESET_MODE_MINSIZE, 20 * EDSCALE);
  1148. vb->set_margin(MARGIN_TOP, 4 * EDSCALE);
  1149. vb->set_margin(MARGIN_BOTTOM, -4 * EDSCALE);
  1150. vb->add_constant_override("separation", 15 * EDSCALE);
  1151. String cp;
  1152. cp.push_back(0xA9);
  1153. cp.push_back(0);
  1154. OS::get_singleton()->set_window_title(_MKSTR(VERSION_NAME) + String(" - ") + TTR("Project Manager") + " - " + cp + " 2008-2017 Juan Linietsky, Ariel Manzur & Godot Contributors");
  1155. HBoxContainer *top_hb = memnew(HBoxContainer);
  1156. vb->add_child(top_hb);
  1157. CenterContainer *ccl = memnew(CenterContainer);
  1158. Label *l = memnew(Label);
  1159. l->set_text(_MKSTR(VERSION_NAME) + String(" - ") + TTR("Project Manager"));
  1160. l->add_font_override("font", gui_base->get_font("doc", "EditorFonts"));
  1161. ccl->add_child(l);
  1162. top_hb->add_child(ccl);
  1163. top_hb->add_spacer();
  1164. l = memnew(Label);
  1165. String hash = String(VERSION_HASH);
  1166. if (hash.length() != 0)
  1167. hash = "." + hash.left(7);
  1168. l->set_text("v" VERSION_MKSTRING "" + hash);
  1169. //l->add_font_override("font",get_font("bold","Fonts"));
  1170. l->set_align(Label::ALIGN_CENTER);
  1171. top_hb->add_child(l);
  1172. //vb->add_child(memnew(HSeparator));
  1173. //vb->add_margin_child("\n",memnew(Control));
  1174. tabs = memnew(TabContainer);
  1175. vb->add_child(tabs);
  1176. tabs->set_v_size_flags(SIZE_EXPAND_FILL);
  1177. HBoxContainer *tree_hb = memnew(HBoxContainer);
  1178. projects_hb = tree_hb;
  1179. projects_hb->set_name(TTR("Project List"));
  1180. tabs->add_child(tree_hb);
  1181. VBoxContainer *search_tree_vb = memnew(VBoxContainer);
  1182. search_tree_vb->set_h_size_flags(SIZE_EXPAND_FILL);
  1183. tree_hb->add_child(search_tree_vb);
  1184. HBoxContainer *search_box = memnew(HBoxContainer);
  1185. search_box->add_spacer(true);
  1186. project_filter = memnew(ProjectListFilter);
  1187. search_box->add_child(project_filter);
  1188. project_filter->connect("filter_changed", this, "_load_recent_projects");
  1189. project_filter->set_custom_minimum_size(Size2(250, 10));
  1190. search_tree_vb->add_child(search_box);
  1191. PanelContainer *pc = memnew(PanelContainer);
  1192. pc->add_style_override("panel", gui_base->get_stylebox("bg", "Tree"));
  1193. search_tree_vb->add_child(pc);
  1194. pc->set_v_size_flags(SIZE_EXPAND_FILL);
  1195. scroll = memnew(ScrollContainer);
  1196. pc->add_child(scroll);
  1197. scroll->set_enable_h_scroll(false);
  1198. VBoxContainer *tree_vb = memnew(VBoxContainer);
  1199. tree_hb->add_child(tree_vb);
  1200. scroll_childs = memnew(VBoxContainer);
  1201. scroll_childs->set_h_size_flags(SIZE_EXPAND_FILL);
  1202. scroll->add_child(scroll_childs);
  1203. //HBoxContainer *hb = memnew( HBoxContainer );
  1204. //vb->add_child(hb);
  1205. Button *open = memnew(Button);
  1206. open->set_text(TTR("Edit"));
  1207. tree_vb->add_child(open);
  1208. open->connect("pressed", this, "_open_project");
  1209. open_btn = open;
  1210. Button *run = memnew(Button);
  1211. run->set_text(TTR("Run"));
  1212. tree_vb->add_child(run);
  1213. run->connect("pressed", this, "_run_project");
  1214. run_btn = run;
  1215. tree_vb->add_child(memnew(HSeparator));
  1216. Button *scan = memnew(Button);
  1217. scan->set_text(TTR("Scan"));
  1218. tree_vb->add_child(scan);
  1219. scan->connect("pressed", this, "_scan_projects");
  1220. tree_vb->add_child(memnew(HSeparator));
  1221. scan_dir = memnew(FileDialog);
  1222. scan_dir->set_access(FileDialog::ACCESS_FILESYSTEM);
  1223. scan_dir->set_mode(FileDialog::MODE_OPEN_DIR);
  1224. scan_dir->set_title(TTR("Select a Folder to Scan")); // must be after mode or it's overridden
  1225. scan_dir->set_current_dir(EditorSettings::get_singleton()->get("filesystem/directories/default_project_path"));
  1226. gui_base->add_child(scan_dir);
  1227. scan_dir->connect("dir_selected", this, "_scan_begin");
  1228. Button *create = memnew(Button);
  1229. create->set_text(TTR("New Project"));
  1230. tree_vb->add_child(create);
  1231. create->connect("pressed", this, "_new_project");
  1232. Button *import = memnew(Button);
  1233. import->set_text(TTR("Import"));
  1234. tree_vb->add_child(import);
  1235. import->connect("pressed", this, "_import_project");
  1236. Button *rename = memnew(Button);
  1237. rename->set_text(TTR("Rename"));
  1238. tree_vb->add_child(rename);
  1239. rename->connect("pressed", this, "_rename_project");
  1240. rename_btn = rename;
  1241. Button *erase = memnew(Button);
  1242. erase->set_text(TTR("Remove"));
  1243. tree_vb->add_child(erase);
  1244. erase->connect("pressed", this, "_erase_project");
  1245. erase_btn = erase;
  1246. tree_vb->add_spacer();
  1247. if (StreamPeerSSL::is_available()) {
  1248. asset_library = memnew(EditorAssetLibrary(true));
  1249. asset_library->set_name(TTR("Templates"));
  1250. tabs->add_child(asset_library);
  1251. asset_library->connect("install_asset", this, "_install_project");
  1252. } else {
  1253. WARN_PRINT("Asset Library not available, as it requires SSL to work.");
  1254. }
  1255. CenterContainer *cc = memnew(CenterContainer);
  1256. Button *cancel = memnew(Button);
  1257. cancel->set_text(TTR("Exit"));
  1258. cancel->set_custom_minimum_size(Size2(100, 1) * EDSCALE);
  1259. cc->add_child(cancel);
  1260. cancel->connect("pressed", this, "_exit_dialog");
  1261. vb->add_child(cc);
  1262. //
  1263. erase_ask = memnew(ConfirmationDialog);
  1264. erase_ask->get_ok()->set_text(TTR("Remove"));
  1265. erase_ask->get_ok()->connect("pressed", this, "_erase_project_confirm");
  1266. gui_base->add_child(erase_ask);
  1267. multi_open_ask = memnew(ConfirmationDialog);
  1268. multi_open_ask->get_ok()->set_text(TTR("Edit"));
  1269. multi_open_ask->get_ok()->connect("pressed", this, "_open_project_confirm");
  1270. gui_base->add_child(multi_open_ask);
  1271. multi_run_ask = memnew(ConfirmationDialog);
  1272. multi_run_ask->get_ok()->set_text(TTR("Run"));
  1273. multi_run_ask->get_ok()->connect("pressed", this, "_run_project_confirm");
  1274. gui_base->add_child(multi_run_ask);
  1275. multi_scan_ask = memnew(ConfirmationDialog);
  1276. multi_scan_ask->get_ok()->set_text(TTR("Scan"));
  1277. gui_base->add_child(multi_scan_ask);
  1278. OS::get_singleton()->set_low_processor_usage_mode(true);
  1279. npdialog = memnew(ProjectDialog);
  1280. gui_base->add_child(npdialog);
  1281. npdialog->connect("project_renamed", this, "_on_project_renamed");
  1282. npdialog->connect("project_created", this, "_on_project_created");
  1283. _load_recent_projects();
  1284. if (EditorSettings::get_singleton()->get("filesystem/directories/autoscan_project_path")) {
  1285. _scan_begin(EditorSettings::get_singleton()->get("filesystem/directories/autoscan_project_path"));
  1286. }
  1287. last_clicked = "";
  1288. SceneTree::get_singleton()->connect("files_dropped", this, "_files_dropped");
  1289. run_error_diag = memnew(AcceptDialog);
  1290. gui_base->add_child(run_error_diag);
  1291. run_error_diag->set_title(TTR("Can't run project"));
  1292. dialog_error = memnew(AcceptDialog);
  1293. gui_base->add_child(dialog_error);
  1294. }
  1295. ProjectManager::~ProjectManager() {
  1296. if (EditorSettings::get_singleton())
  1297. EditorSettings::destroy();
  1298. }
  1299. void ProjectListFilter::_setup_filters() {
  1300. filter_option->clear();
  1301. filter_option->add_item(TTR("Name"));
  1302. filter_option->add_item(TTR("Path"));
  1303. }
  1304. void ProjectListFilter::_command(int p_command) {
  1305. switch (p_command) {
  1306. case CMD_CLEAR_FILTER: {
  1307. if (search_box->get_text() != "") {
  1308. search_box->clear();
  1309. emit_signal("filter_changed");
  1310. }
  1311. } break;
  1312. }
  1313. }
  1314. void ProjectListFilter::_search_text_changed(const String &p_newtext) {
  1315. emit_signal("filter_changed");
  1316. }
  1317. String ProjectListFilter::get_search_term() {
  1318. return search_box->get_text().strip_edges();
  1319. }
  1320. ProjectListFilter::FilterOption ProjectListFilter::get_filter_option() {
  1321. return _current_filter;
  1322. }
  1323. void ProjectListFilter::_filter_option_selected(int p_idx) {
  1324. FilterOption selected = (FilterOption)(filter_option->get_selected());
  1325. if (_current_filter != selected) {
  1326. _current_filter = selected;
  1327. emit_signal("filter_changed");
  1328. }
  1329. }
  1330. void ProjectListFilter::_notification(int p_what) {
  1331. switch (p_what) {
  1332. case NOTIFICATION_ENTER_TREE: {
  1333. clear_search_button->set_icon(get_icon("Close", "EditorIcons"));
  1334. } break;
  1335. }
  1336. }
  1337. void ProjectListFilter::_bind_methods() {
  1338. ClassDB::bind_method(D_METHOD("_command"), &ProjectListFilter::_command);
  1339. ClassDB::bind_method(D_METHOD("_search_text_changed"), &ProjectListFilter::_search_text_changed);
  1340. ClassDB::bind_method(D_METHOD("_filter_option_selected"), &ProjectListFilter::_filter_option_selected);
  1341. ADD_SIGNAL(MethodInfo("filter_changed"));
  1342. }
  1343. ProjectListFilter::ProjectListFilter() {
  1344. editor_initialize_certificates(); //for asset sharing
  1345. _current_filter = FILTER_NAME;
  1346. filter_option = memnew(OptionButton);
  1347. filter_option->set_custom_minimum_size(Size2(80 * EDSCALE, 10 * EDSCALE));
  1348. filter_option->set_clip_text(true);
  1349. filter_option->connect("item_selected", this, "_filter_option_selected");
  1350. add_child(filter_option);
  1351. _setup_filters();
  1352. search_box = memnew(LineEdit);
  1353. search_box->connect("text_changed", this, "_search_text_changed");
  1354. search_box->set_h_size_flags(SIZE_EXPAND_FILL);
  1355. add_child(search_box);
  1356. clear_search_button = memnew(ToolButton);
  1357. clear_search_button->connect("pressed", this, "_command", make_binds(CMD_CLEAR_FILTER));
  1358. add_child(clear_search_button);
  1359. }