project.vala 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769
  1. /*
  2. * Copyright (c) 2012-2025 Daniele Bartolini et al.
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. namespace Crown
  6. {
  7. public enum ImportResult
  8. {
  9. SUCCESS, ///< Data imported successfully.
  10. ERROR, ///< Error during import or elsewhere.
  11. CANCEL, ///< User cancelled the import.
  12. CALLBACK ///< The actual importing will happen in the provided callback.
  13. }
  14. public delegate void Import(ImportResult result);
  15. public class Project
  16. {
  17. public const string LEVEL_EDITOR_TEST_NAME = "_level_editor_test";
  18. public delegate ImportResult ImporterDelegate(ProjectStore project_store
  19. , string destination_dir
  20. , SList<string> filenames
  21. , Import import_result
  22. , Gtk.Window? parent_window
  23. );
  24. [Compact]
  25. public struct ImporterData
  26. {
  27. public unowned ImporterDelegate delegate;
  28. public Gee.ArrayList<string> extensions;
  29. public double order;
  30. public Gtk.FileFilter _filter;
  31. public unowned Import import_result;
  32. ImporterData()
  33. {
  34. delegate = null;
  35. extensions = new Gee.ArrayList<string>();
  36. order = 0.0;
  37. _filter = new Gtk.FileFilter();
  38. import_result = null;
  39. }
  40. public bool can_import_extension(string extension)
  41. {
  42. string e = extension.down();
  43. foreach (var ext in extensions) {
  44. if (e == ext)
  45. return true;
  46. }
  47. return false;
  48. }
  49. public bool can_import_filenames(GLib.SList<string> filenames)
  50. {
  51. foreach (var filename in filenames) {
  52. if (!can_import_extension(path_extension(filename)))
  53. return false;
  54. }
  55. return true;
  56. }
  57. }
  58. // Data
  59. public File? _source_dir;
  60. public File _toolchain_dir;
  61. public File _data_dir;
  62. public File _user_dir;
  63. public File _level_editor_test_level;
  64. public File _level_editor_test_package;
  65. public string _platform;
  66. public Database _files;
  67. public Gee.HashMap<string, Guid?> _map;
  68. public ImporterData _all_extensions_importer_data;
  69. public Gee.ArrayList<ImporterData?> _importers;
  70. public bool _data_compiled;
  71. public Hashtable _data_index;
  72. public signal void file_added(string type, string name, uint64 size, uint64 mtime);
  73. public signal void file_changed(string type, string name, uint64 size, uint64 mtime);
  74. public signal void file_removed(string type, string name);
  75. public signal void tree_added(string name);
  76. public signal void tree_removed(string name);
  77. public signal void project_reset();
  78. public signal void project_loaded();
  79. public Project()
  80. {
  81. #if CROWN_PLATFORM_WINDOWS
  82. _platform = "windows";
  83. #else
  84. _platform = "linux";
  85. #endif
  86. _files = new Database(this);
  87. _map = new Gee.HashMap<string, Guid?>();
  88. _all_extensions_importer_data = ImporterData();
  89. _all_extensions_importer_data.delegate = import_all_extensions;
  90. _importers = new Gee.ArrayList<ImporterData?>();
  91. _data_compiled = false;
  92. _data_index = new Hashtable();
  93. }
  94. public void data_compiled()
  95. {
  96. _data_compiled = true;
  97. try {
  98. string index_path = Path.build_filename(_data_dir.get_path(), "data_index.sjson");
  99. _data_index = SJSON.load_from_path(index_path);
  100. } catch (JsonSyntaxError e) {
  101. loge(e.message);
  102. }
  103. }
  104. public uint64 mtime(string type, string name)
  105. {
  106. var path = ResourceId.path(type, name);
  107. Guid id = _map[path];
  108. string mtime = _files.get_property_string(id, "mtime");
  109. return uint64.parse(mtime);
  110. }
  111. public void reset()
  112. {
  113. project_reset();
  114. _source_dir = null;
  115. _files.reset();
  116. _map.clear();
  117. }
  118. public bool is_loaded()
  119. {
  120. return _source_dir != null;
  121. }
  122. public void load(string source_dir)
  123. {
  124. reset();
  125. _source_dir = File.new_for_path(source_dir);
  126. _data_dir = File.new_for_path(_source_dir.get_path() + "_" + _platform);
  127. _level_editor_test_level = File.new_for_path(Path.build_filename(_source_dir.get_path(), LEVEL_EDITOR_TEST_NAME + ".level"));
  128. _level_editor_test_package = File.new_for_path(Path.build_filename(_source_dir.get_path(), LEVEL_EDITOR_TEST_NAME + ".package"));
  129. // Cleanup source directory from previous runs' garbage
  130. delete_garbage();
  131. _user_dir = GLib.File.new_for_path(GLib.Path.build_filename(Crown._data_dir.get_path(), "projects", StringId64(source_dir).to_string()));
  132. try {
  133. _user_dir.make_directory_with_parents();
  134. } catch (Error e) {
  135. /* Nobody cares */
  136. }
  137. project_loaded();
  138. }
  139. public void set_toolchain_dir(string toolchain_dir)
  140. {
  141. _toolchain_dir = File.new_for_path(toolchain_dir);
  142. }
  143. public void create_initial_files(string source_dir)
  144. {
  145. // Write boot.config
  146. {
  147. string text = "// Lua script to launch on boot"
  148. + "\nboot_script = \"core/game/boot\""
  149. + "\n"
  150. + "\n// Package to load on boot"
  151. + "\nboot_package = \"boot\""
  152. + "\n"
  153. + "\nwindow_title = \"New Project\""
  154. + "\n"
  155. + "\n// Linux-only configs"
  156. + "\nlinux = {"
  157. + "\n renderer = {"
  158. + "\n resolution = [ 1280 720 ]"
  159. + "\n }"
  160. + "\n}"
  161. + "\n"
  162. + "\n// Windows-only configs"
  163. + "\nwindows = {"
  164. + "\n renderer = {"
  165. + "\n resolution = [ 1280 720 ]"
  166. + "\n }"
  167. + "\n}"
  168. + "\n"
  169. ;
  170. string path = Path.build_filename(source_dir, "boot.config");
  171. FileStream fs = FileStream.open(path, "wb");
  172. if (fs != null)
  173. fs.write(text.data);
  174. }
  175. // Write boot.package
  176. {
  177. string text = "lua = ["
  178. + "\n \"core/game/boot\""
  179. + "\n]"
  180. + "\nshader = ["
  181. + "\n \"core/shaders/default\""
  182. + "\n]"
  183. + "\nphysics_config = ["
  184. + "\n \"global\""
  185. + "\n]"
  186. + "\nunit = ["
  187. + "\n \"core/units/camera\""
  188. + "\n]"
  189. + "\n"
  190. ;
  191. string path = Path.build_filename(source_dir, "boot.package");
  192. FileStream fs = FileStream.open(path, "wb");
  193. if (fs != null)
  194. fs.write(text.data);
  195. }
  196. // Write global.physics_config
  197. {
  198. string text = "materials = {"
  199. + "\n default = { friction = 0.8 rolling_friction = 0.5 restitution = 0.81 }"
  200. + "\n}"
  201. + "\n"
  202. + "\ncollision_filters = {"
  203. + "\n no_collision = { collides_with = [] }"
  204. + "\n default = { collides_with = [ \"default\" ] }"
  205. + "\n}"
  206. + "\n"
  207. + "\nactors = {"
  208. + "\n static = { dynamic = false }"
  209. + "\n dynamic = { dynamic = true }"
  210. + "\n keyframed = { dynamic = true kinematic = true disable_gravity = true }"
  211. + "\n}"
  212. + "\n"
  213. ;
  214. string path = Path.build_filename(source_dir, "global.physics_config");
  215. FileStream fs = FileStream.open(path, "wb");
  216. if (fs != null)
  217. fs.write(text.data);
  218. }
  219. // Write main.lua
  220. {
  221. string text = "require \"core/game/camera\""
  222. + "\n"
  223. + "\nGame = Game or {"
  224. + "\n sg = nil,"
  225. + "\n pw = nil,"
  226. + "\n rw = nil,"
  227. + "\n camera = nil,"
  228. + "\n}"
  229. + "\n"
  230. + "\nGameBase.game = Game"
  231. + "\nGameBase.game_level = nil"
  232. + "\n"
  233. + "\nfunction Game.level_loaded()"
  234. + "\n Device.enable_resource_autoload(true)"
  235. + "\n"
  236. + "\n Game.sg = World.scene_graph(GameBase.world)"
  237. + "\n Game.pw = World.physics_world(GameBase.world)"
  238. + "\n Game.rw = World.render_world(GameBase.world)"
  239. + "\n Game.camera = FPSCamera(GameBase.world, GameBase.camera_unit)"
  240. + "\nend"
  241. + "\n"
  242. + "\nfunction Game.update(dt)"
  243. + "\n -- Stop the engine when the 'ESC' key is released"
  244. + "\n if Keyboard.released(Keyboard.button_id(\"escape\")) then"
  245. + "\n Device.quit()"
  246. + "\n end"
  247. + "\n"
  248. + "\n -- Update camera"
  249. + "\n local delta = Vector3.zero()"
  250. + "\n if Mouse.pressed(Mouse.button_id(\"right\")) then move = true end"
  251. + "\n if Mouse.released(Mouse.button_id(\"right\")) then move = false end"
  252. + "\n if move then delta = Mouse.axis(Mouse.axis_id(\"cursor_delta\")) end"
  253. + "\n Game.camera:update(dt, delta.x, delta.y)"
  254. + "\nend"
  255. + "\n"
  256. + "\nfunction Game.render(dt)"
  257. + "\nend"
  258. + "\n"
  259. + "\nfunction Game.shutdown()"
  260. + "\nend"
  261. + "\n"
  262. ;
  263. string path = Path.build_filename(source_dir, "main.lua");
  264. FileStream fs = FileStream.open(path, "wb");
  265. if (fs != null)
  266. fs.write(text.data);
  267. }
  268. }
  269. public int create_script(string directory, string name, bool empty)
  270. {
  271. string script_path = Path.build_filename(directory, name + ".lua");
  272. string path = this.absolute_path(script_path);
  273. FileStream fs = FileStream.open(path, "wb");
  274. if (fs != null) {
  275. if (empty) {
  276. return fs.puts("\n");
  277. } else {
  278. string text = "local Behavior = Behavior or {}"
  279. + "\nlocal Data = Data or {}"
  280. + "\n"
  281. + "\nfunction Behavior.spawned(world, units)"
  282. + "\n if Data[world] == nil then"
  283. + "\n Data[world] = {}"
  284. + "\n end"
  285. + "\n"
  286. + "\n for uu = 1, #units do"
  287. + "\n local unit = units[uu]"
  288. + "\n"
  289. + "\n -- Store instance-specific data"
  290. + "\n if Data[world][unit] == nil then"
  291. + "\n -- Data[world][unit] = {}"
  292. + "\n end"
  293. + "\n"
  294. + "\n -- Do something with the unit"
  295. + "\n end"
  296. + "\nend"
  297. + "\n"
  298. + "\nfunction Behavior.update(world, dt)"
  299. + "\n -- Update all units"
  300. + "\nend"
  301. + "\n"
  302. + "\nfunction Behavior.unspawned(world, units)"
  303. + "\n -- Cleanup"
  304. + "\n for uu = 1, #units do"
  305. + "\n if Data[world][units] then"
  306. + "\n Data[world][units] = nil"
  307. + "\n end"
  308. + "\n end"
  309. + "\nend"
  310. + "\n"
  311. + "\nreturn Behavior"
  312. + "\n"
  313. ;
  314. return fs.puts(text);
  315. }
  316. }
  317. return -1;
  318. }
  319. public int create_unit(string directory, string name)
  320. {
  321. string unit_path = Path.build_filename(directory, name + ".unit");
  322. string path = this.absolute_path(unit_path);
  323. Database db = new Database(this, null);
  324. Guid unit_id = Guid.new_guid();
  325. Unit unit = Unit(db, unit_id);
  326. unit.create_empty();
  327. return db.dump(path, unit_id);
  328. }
  329. public int create_state_machine(string directory, string name, string? skeleton_name)
  330. {
  331. string resource_name = Path.build_filename(directory, name);
  332. Database db = new Database(this, null);
  333. Guid node_id = Guid.new_guid();
  334. StateMachineNode sm_node = StateMachineNode(db, node_id);
  335. Guid machine_id = Guid.new_guid();
  336. StateMachineResource machine;
  337. if (skeleton_name == null)
  338. machine = StateMachineResource.sprite(db, machine_id, sm_node);
  339. else
  340. machine = StateMachineResource.mesh(db, machine_id, sm_node, skeleton_name);
  341. machine.add_node(sm_node);
  342. return machine.save(this, resource_name);
  343. }
  344. public int create_material(string directory, string name)
  345. {
  346. string resource_name = Path.build_filename(directory, name);
  347. Database db = new Database(this, null);
  348. Guid material_id = Guid.new_guid();
  349. MaterialResource material_resource = MaterialResource.mesh(db, material_id);
  350. return material_resource.save(this, resource_name);
  351. }
  352. // Returns the absolute path to the source directory.
  353. public string source_dir()
  354. {
  355. if (_source_dir == null)
  356. return "";
  357. else
  358. return _source_dir.get_path();
  359. }
  360. // Returns the absolute path to the toolchain directory.
  361. public string toolchain_dir()
  362. {
  363. return _toolchain_dir.get_path();
  364. }
  365. // Returns the absolute path to the data directory.
  366. public string data_dir()
  367. {
  368. return _data_dir.get_path();
  369. }
  370. // Returns the absolute path to the user-specific data for this project.
  371. public string user_dir()
  372. {
  373. return _user_dir.get_path();
  374. }
  375. public string platform()
  376. {
  377. return _platform;
  378. }
  379. public string name()
  380. {
  381. string sd = source_dir();
  382. return sd.substring(sd.last_index_of_char(GLib.Path.DIR_SEPARATOR) + 1);
  383. }
  384. public bool path_is_within_source_dir(string path)
  385. {
  386. GLib.File file = GLib.File.new_for_path(path);
  387. return file.has_prefix(_source_dir);
  388. }
  389. public void delete_garbage()
  390. {
  391. try {
  392. _level_editor_test_level.delete();
  393. _level_editor_test_package.delete();
  394. } catch (GLib.Error e) {
  395. // Ignored
  396. }
  397. }
  398. /// Converts the @a resource_id to its corresponding human-readable @a
  399. /// resource_name. It returns true if the conversion is successful, otherwise
  400. /// it returns false and sets @a resource_name to the value of @a resource_id.
  401. public bool resource_id_to_name(out string resource_name, string resource_id)
  402. {
  403. Value? name = _data_index[resource_id];
  404. if (name != null) {
  405. resource_name = (string)name;
  406. return true;
  407. }
  408. resource_name = resource_id;
  409. return false;
  410. }
  411. public Database files()
  412. {
  413. return _files;
  414. }
  415. public void add_file(string path, uint64 size, uint64 mtime)
  416. {
  417. string type = path_extension(path);
  418. string name = type == "" ? path : path.substring(0, path.last_index_of("."));
  419. Guid id = Guid.new_guid();
  420. _files.create(id, OBJECT_TYPE_FILE);
  421. _files.set_property_string(id, "path", path);
  422. _files.set_property_string(id, "type", type);
  423. _files.set_property_string(id, "name", name);
  424. _files.set_property_string(id, "size", size.to_string());
  425. _files.set_property_string(id, "mtime", mtime.to_string());
  426. _files.add_to_set(GUID_ZERO, "data", id);
  427. _map[path] = id;
  428. file_added(type, name, size, mtime);
  429. }
  430. public void change_file(string path, uint64 size, uint64 mtime)
  431. {
  432. string type = path_extension(path);
  433. string name = type == "" ? path : path.substring(0, path.last_index_of("."));
  434. Guid id = _map[path];
  435. _files.set_property_string(id, "size", size.to_string());
  436. _files.set_property_string(id, "mtime", mtime.to_string());
  437. _data_compiled = false;
  438. file_changed(type, name, size, mtime);
  439. }
  440. public void remove_file(string path)
  441. {
  442. if (!_map.has_key(path)) {
  443. logw("remove_file: map does not contain path: %s".printf(path));
  444. return;
  445. }
  446. Guid id = _map[path];
  447. file_removed(_files.get_property_string(id, "type"), _files.get_property_string(id, "name"));
  448. _files.remove_from_set(GUID_ZERO, "data", id);
  449. _files.destroy(id);
  450. _map.unset(path);
  451. }
  452. public void add_tree(string path)
  453. {
  454. tree_added(path);
  455. }
  456. public void remove_tree(string path)
  457. {
  458. tree_removed(path);
  459. }
  460. public string resource_filename(string absolute_path)
  461. {
  462. string prefix = _source_dir.get_path();
  463. if (absolute_path.has_prefix(_toolchain_dir.get_path() + "/core"))
  464. prefix = _toolchain_dir.get_path();
  465. return File.new_for_path(prefix).get_relative_path(File.new_for_path(absolute_path));
  466. }
  467. public string absolute_path(string resource_path)
  468. {
  469. string prefix = _source_dir.get_path();
  470. if (resource_path.has_prefix("core/") || resource_path == "core")
  471. prefix = _toolchain_dir.get_path();
  472. return Path.build_filename(prefix, resource_path);
  473. }
  474. public static ImportResult import_all_extensions(ProjectStore project_store, string destination_dir, SList<string> filenames, Import import_result, Gtk.Window? parent_window)
  475. {
  476. Project project = project_store._project;
  477. Gee.ArrayList<string> paths = new Gee.ArrayList<string>();
  478. foreach (var item in filenames)
  479. paths.add(item);
  480. paths.sort((a, b) => {
  481. int ext_a = a.last_index_of_char('.');
  482. int ext_b = b.last_index_of_char('.');
  483. return strcmp(a[ext_a : a.length], b[ext_b : b.length]);
  484. });
  485. int result = 0;
  486. while (paths.size != 0 && result == ImportResult.SUCCESS) {
  487. // Find importer for the first file in the list of selected filenames.
  488. ImporterData? importer = project.find_importer_for_path(paths[0]);
  489. if (importer == null)
  490. return ImportResult.ERROR;
  491. // Create the list of all filenames importable by importer.
  492. Gee.ArrayList<string> importables = new Gee.ArrayList<string>();
  493. var cur = paths.list_iterator();
  494. for (var has_next = cur.next(); has_next; has_next = cur.next()) {
  495. string path = paths[cur.index()];
  496. if (importer.can_import_extension(path_extension(path))) {
  497. importables.add(path);
  498. cur.remove();
  499. }
  500. }
  501. // If importables is empty, filenames must have been filled with
  502. // un-importable filenames...
  503. if (importables.size == 0)
  504. return ImportResult.ERROR;
  505. // Convert importables to SList<string> to be used as delegate param.
  506. SList<string> importables_list = new SList<string>();
  507. foreach (var item in importables)
  508. importables_list.append(item);
  509. result = importer.delegate(project_store, destination_dir, importables_list, import_result, parent_window);
  510. }
  511. return result;
  512. }
  513. public class FileFilterFuncData
  514. {
  515. public string extension;
  516. public FileFilterFuncData(string ext)
  517. {
  518. extension = ext;
  519. }
  520. public bool handler(Gtk.FileFilterInfo info)
  521. {
  522. return info.filename.down().has_suffix("." + extension);
  523. }
  524. }
  525. // Returns a Gtk.FileFilter based on file @a extensions list.
  526. public Gtk.FileFilter create_gtk_file_filter(string name, Gee.ArrayList<string> extensions)
  527. {
  528. Gtk.FileFilter filter = new Gtk.FileFilter();
  529. string extensions_comma_separated = "";
  530. foreach (var ext in extensions) {
  531. extensions_comma_separated += "*.%s, ".printf(ext);
  532. FileFilterFuncData data = new FileFilterFuncData(ext);
  533. filter.add_custom(Gtk.FileFilterFlags.FILENAME, data.handler);
  534. }
  535. filter.set_filter_name(name + " (%s)".printf(extensions_comma_separated[0 : -2]));
  536. return filter;
  537. }
  538. public void register_importer_internal(string name, ref ImporterData data)
  539. {
  540. data._filter = create_gtk_file_filter(name, data.extensions);
  541. _importers.add(data);
  542. _importers.sort((a, b) => { return a.order < b.order ? -1 : 1; });
  543. // Skip duplicated extensions.
  544. foreach (string ext in data.extensions) {
  545. if (!_all_extensions_importer_data.extensions.contains(ext))
  546. _all_extensions_importer_data.extensions.add(ext);
  547. }
  548. _all_extensions_importer_data._filter = create_gtk_file_filter("All", _all_extensions_importer_data.extensions);
  549. }
  550. // Registers an @a importer for importing source data with the given @a
  551. // extensions. @a order is used to establish precedence when distinct importers
  552. // support similar extensions; lower values have higher precedence.
  553. public void register_importer(string name, string[] extensions, ImporterDelegate importer, Import import_result, double order)
  554. {
  555. ImporterData data = ImporterData();
  556. data.delegate = importer;
  557. data.extensions.add_all_array(extensions);
  558. data.order = order;
  559. data.import_result = import_result;
  560. register_importer_internal(name, ref data);
  561. }
  562. // Returns the preferable importer (lowest order values) which can import files
  563. // with the given @a extension.
  564. public ImporterData? find_importer_for_extension(string extension)
  565. {
  566. foreach (var imp in _importers) {
  567. if (imp.can_import_extension(extension))
  568. return imp;
  569. }
  570. return null;
  571. }
  572. public ImporterData? find_importer_for_path(string path)
  573. {
  574. return find_importer_for_extension(path_extension(path));
  575. }
  576. public bool is_type_importable(string type)
  577. {
  578. return find_importer_for_extension(type) != null;
  579. }
  580. public ImportResult import(string? destination_dir, Import import_result, ProjectStore project_store, Gtk.Window? parent_window = null)
  581. {
  582. Gtk.FileChooserDialog src = new Gtk.FileChooserDialog("Import..."
  583. , parent_window
  584. , Gtk.FileChooserAction.OPEN
  585. , "Cancel"
  586. , Gtk.ResponseType.CANCEL
  587. , "Open"
  588. , Gtk.ResponseType.ACCEPT
  589. );
  590. src.select_multiple = true;
  591. foreach (var importer in _importers)
  592. src.add_filter(importer._filter);
  593. src.add_filter(_all_extensions_importer_data._filter);
  594. src.set_filter(_all_extensions_importer_data._filter);
  595. if (src.run() != (int)Gtk.ResponseType.ACCEPT) {
  596. src.destroy();
  597. return ImportResult.CANCEL;
  598. }
  599. string out_dir = "";
  600. if (destination_dir == null) {
  601. Gtk.FileChooserDialog dst = new Gtk.FileChooserDialog("Select destination folder..."
  602. , parent_window
  603. , Gtk.FileChooserAction.SELECT_FOLDER
  604. , "Cancel"
  605. , Gtk.ResponseType.CANCEL
  606. , "Select"
  607. , Gtk.ResponseType.ACCEPT
  608. );
  609. dst.set_current_folder(this.source_dir());
  610. if (dst.run() != (int)Gtk.ResponseType.ACCEPT) {
  611. dst.destroy();
  612. src.destroy();
  613. return ImportResult.CANCEL;
  614. }
  615. out_dir = dst.get_filename();
  616. dst.destroy();
  617. } else {
  618. out_dir = this.absolute_path(destination_dir);
  619. }
  620. Gtk.FileFilter? current_filter = src.get_filter();
  621. GLib.SList<string> filenames = src.get_filenames();
  622. src.destroy();
  623. // Find importer callback.
  624. unowned ImporterDelegate? importer = null;
  625. foreach (var imp in _importers) {
  626. if (imp._filter == current_filter && imp.can_import_filenames(filenames)) {
  627. importer = imp.delegate;
  628. break;
  629. }
  630. }
  631. // Fallback if no importer found.
  632. if (importer == null)
  633. importer = _all_extensions_importer_data.delegate;
  634. return importer(project_store, out_dir, filenames, import_result, parent_window);
  635. }
  636. public void delete_tree(GLib.File file) throws Error
  637. {
  638. GLib.FileEnumerator fe = file.enumerate_children("standard::*"
  639. , GLib.FileQueryInfoFlags.NOFOLLOW_SYMLINKS
  640. );
  641. GLib.FileInfo info = null;
  642. while ((info = fe.next_file()) != null) {
  643. GLib.File subfile = file.resolve_relative_path(info.get_name());
  644. if (info.get_file_type() == GLib.FileType.DIRECTORY)
  645. delete_tree(subfile);
  646. else
  647. subfile.delete();
  648. }
  649. file.delete();
  650. }
  651. }
  652. } /* namespace Crown */