project_store.vala 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. /*
  2. * Copyright (c) 2012-2026 Daniele Bartolini et al.
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. namespace Crown
  6. {
  7. public class ProjectStore
  8. {
  9. public const string ROOT_FOLDER = "";
  10. public enum Column
  11. {
  12. TYPE,
  13. NAME,
  14. SIZE,
  15. MTIME,
  16. VISIBLE,
  17. USER_DATA,
  18. COUNT
  19. }
  20. // Data
  21. public Project _project;
  22. public Gtk.ListStore _list_store;
  23. public Gtk.TreeStore _tree_store;
  24. public Gee.HashMap<string, Gtk.TreeRowReference> _folders;
  25. public Gtk.TreeRowReference _favorites_root;
  26. public ProjectStore(Project project)
  27. {
  28. // Data
  29. _project = project;
  30. _project.file_added.connect(on_project_file_added);
  31. _project.file_changed.connect(on_project_file_changed);
  32. _project.file_removed.connect(on_project_file_removed);
  33. _project.tree_added.connect(on_project_tree_added);
  34. _project.tree_removed.connect(on_project_tree_removed);
  35. _project.files_reset.connect(on_project_reset);
  36. _project.project_reset.connect(on_project_reset);
  37. _list_store = new Gtk.ListStore(Column.COUNT
  38. , typeof(string) // resource type
  39. , typeof(string) // resource name
  40. , typeof(uint64) // resource size
  41. , typeof(uint64) // resource mtime
  42. , typeof(bool) // visible
  43. , typeof(uint32) // user data
  44. );
  45. _tree_store = new Gtk.TreeStore(Column.COUNT
  46. , typeof(string) // resource type
  47. , typeof(string) // resource name
  48. , typeof(uint64) // resource size
  49. , typeof(uint64) // resource mtime
  50. , typeof(bool) // visible
  51. , typeof(uint32) // user data
  52. );
  53. _folders = new Gee.HashMap<string, Gtk.TreeRowReference>();
  54. reset();
  55. }
  56. public void reset()
  57. {
  58. _folders.clear();
  59. _tree_store.clear();
  60. _list_store.clear();
  61. // Add favorites root.
  62. Gtk.TreeIter iter;
  63. _tree_store.insert_with_values(out iter
  64. , null
  65. , -1
  66. , Column.TYPE
  67. , "<favorites>"
  68. , Column.NAME
  69. , ROOT_FOLDER
  70. , Column.SIZE
  71. , 0u
  72. , Column.MTIME
  73. , 0u
  74. , Column.VISIBLE
  75. , true
  76. , -1
  77. );
  78. _favorites_root = new Gtk.TreeRowReference(_tree_store, _tree_store.get_path(iter));
  79. _tree_store.insert_with_values(out iter
  80. , null
  81. , -1
  82. , Column.TYPE
  83. , "<folder>"
  84. , Column.NAME
  85. , ROOT_FOLDER
  86. , Column.SIZE
  87. , 0u
  88. , Column.MTIME
  89. , 0u
  90. , Column.VISIBLE
  91. , true
  92. , -1
  93. );
  94. _folders[ROOT_FOLDER] = new Gtk.TreeRowReference(_tree_store, _tree_store.get_path(iter));
  95. }
  96. public bool path_for_resource_type_name(out Gtk.TreePath path, string type, string name)
  97. {
  98. if (type == "<folder>") {
  99. path = _folders[name].get_path();
  100. return true;
  101. }
  102. string parent_folder = ResourceId.parent_folder(name);
  103. if (_folders.has_key(parent_folder)) {
  104. // Find the name inside the folder.
  105. Gtk.TreeIter parent_iter;
  106. _tree_store.get_iter(out parent_iter, _folders[parent_folder].get_path());
  107. Gtk.TreeIter child;
  108. if (_tree_store.iter_children(out child, parent_iter)) {
  109. Value iter_type;
  110. Value iter_name;
  111. while (true) {
  112. _tree_store.get_value(child, Column.TYPE, out iter_type);
  113. _tree_store.get_value(child, Column.NAME, out iter_name);
  114. if ((string)iter_name == name && (string)iter_type == type) {
  115. path = _tree_store.get_path(child);
  116. return true;
  117. }
  118. if (!_tree_store.iter_next(ref child))
  119. break;
  120. }
  121. }
  122. }
  123. path = _folders[ROOT_FOLDER].get_path();
  124. return false;
  125. }
  126. public Gtk.TreePath? project_root_path()
  127. {
  128. if (!_folders.has_key(ROOT_FOLDER))
  129. return null;
  130. return _folders[ROOT_FOLDER].get_path();
  131. }
  132. public Gtk.TreePath? favorites_root_path()
  133. {
  134. return _favorites_root.get_path();
  135. }
  136. public void add_to_favorites(string type, string name)
  137. {
  138. Gtk.TreeIter favorites_root_iter;
  139. _tree_store.get_iter(out favorites_root_iter, favorites_root_path());
  140. // Avoid duplicates.
  141. Gtk.TreeIter child;
  142. if (_tree_store.iter_children(out child, favorites_root_iter)) {
  143. Value iter_type;
  144. Value iter_name;
  145. while (true) {
  146. _tree_store.get_value(child, Column.TYPE, out iter_type);
  147. _tree_store.get_value(child, Column.NAME, out iter_name);
  148. if ((string)iter_name == name && (string)iter_type == type)
  149. return;
  150. if (!_tree_store.iter_next(ref child))
  151. break;
  152. }
  153. }
  154. // Add to favorites.
  155. Gtk.TreeIter iter;
  156. _tree_store.insert_with_values(out iter
  157. , favorites_root_iter
  158. , -1
  159. , Column.TYPE
  160. , type
  161. , Column.NAME
  162. , name
  163. , Column.SIZE
  164. , 0u
  165. , Column.MTIME
  166. , 0u
  167. , Column.VISIBLE
  168. , true
  169. , -1
  170. );
  171. }
  172. public void remove_from_favorites(string type, string name)
  173. {
  174. // Remove from tree store.
  175. Gtk.TreeIter parent_iter;
  176. _tree_store.get_iter(out parent_iter, favorites_root_path());
  177. Gtk.TreeIter child;
  178. if (_tree_store.iter_children(out child, parent_iter)) {
  179. Value iter_type;
  180. Value iter_name;
  181. while (true) {
  182. _tree_store.get_value(child, Column.TYPE, out iter_type);
  183. _tree_store.get_value(child, Column.NAME, out iter_name);
  184. if ((string)iter_name == name && (string)iter_type == type) {
  185. _tree_store.remove(ref child);
  186. break;
  187. } else {
  188. if (!_tree_store.iter_next(ref child))
  189. break;
  190. }
  191. }
  192. }
  193. }
  194. public Gtk.TreeIter make_tree_internal(string folder, int start_index, Gtk.TreeRowReference parent)
  195. {
  196. // Folder can be:
  197. // "", root folder
  198. // "folder", one word
  199. // "folder/another_folder", any number of words concatenated by '/'
  200. int first_slash = folder.index_of_char('/', start_index);
  201. if (first_slash == -1) {
  202. Gtk.TreeIter parent_iter;
  203. _tree_store.get_iter(out parent_iter, parent.get_path());
  204. Gtk.TreeIter iter;
  205. _tree_store.insert_with_values(out iter
  206. , parent_iter
  207. , -1
  208. , Column.TYPE
  209. , "<folder>"
  210. , Column.NAME
  211. , folder
  212. , Column.SIZE
  213. , 0u
  214. , Column.MTIME
  215. , 0u
  216. , Column.VISIBLE
  217. , true
  218. , -1
  219. );
  220. _folders[folder] = new Gtk.TreeRowReference(_tree_store, _tree_store.get_path(iter));
  221. return iter;
  222. } else {
  223. if (_folders.has_key(folder.substring(0, first_slash))) {
  224. return make_tree_internal(folder, first_slash + 1, _folders[folder.substring(0, first_slash)]);
  225. } else {
  226. Gtk.TreeIter parent_iter;
  227. _tree_store.get_iter(out parent_iter, parent.get_path());
  228. Gtk.TreeIter iter;
  229. _tree_store.insert_with_values(out iter
  230. , parent_iter
  231. , -1
  232. , Column.TYPE
  233. , "<folder>"
  234. , Column.NAME
  235. , folder.substring(0, first_slash)
  236. , Column.SIZE
  237. , 0u
  238. , Column.MTIME
  239. , 0u
  240. , Column.VISIBLE
  241. , true
  242. , -1
  243. );
  244. Gtk.TreeRowReference trr = new Gtk.TreeRowReference(_tree_store, _tree_store.get_path(iter));
  245. _folders[folder.substring(0, first_slash)] = trr;
  246. return make_tree_internal(folder, first_slash + 1, trr);
  247. }
  248. }
  249. }
  250. public Gtk.TreeIter find_or_make_tree(string folder)
  251. {
  252. if (_folders.has_key(folder)) {
  253. Gtk.TreeIter iter;
  254. _tree_store.get_iter(out iter, _folders[folder].get_path());
  255. return iter;
  256. }
  257. return make_tree_internal(folder, 0, _folders[ROOT_FOLDER]);
  258. }
  259. public void on_project_file_added(string type, string name, uint64 size, uint64 mtime)
  260. {
  261. string parent_folder = ResourceId.parent_folder(name);
  262. Gtk.TreeIter parent = find_or_make_tree(parent_folder);
  263. Gtk.TreeIter iter;
  264. _list_store.insert_with_values(out iter
  265. , -1
  266. , Column.TYPE
  267. , type
  268. , Column.NAME
  269. , name
  270. , Column.SIZE
  271. , size
  272. , Column.MTIME
  273. , mtime
  274. , Column.VISIBLE
  275. , true
  276. , -1
  277. );
  278. _tree_store.insert_with_values(out iter
  279. , parent
  280. , -1
  281. , Column.TYPE
  282. , type
  283. , Column.NAME
  284. , name
  285. , Column.SIZE
  286. , size
  287. , Column.MTIME
  288. , mtime
  289. , Column.VISIBLE
  290. , true
  291. , -1
  292. );
  293. }
  294. public void on_project_file_changed(string type, string name, uint64 size, uint64 mtime)
  295. {
  296. string parent_folder = ResourceId.parent_folder(name);
  297. Gtk.TreeIter child;
  298. if (!_folders.has_key(parent_folder))
  299. return;
  300. // Update the list store entry.
  301. if (_list_store.iter_children(out child, null)) {
  302. Value iter_type;
  303. Value iter_name;
  304. while (true) {
  305. _list_store.get_value(child, Column.TYPE, out iter_type);
  306. _list_store.get_value(child, Column.NAME, out iter_name);
  307. if ((string)iter_name == name && (string)iter_type == type) {
  308. _list_store.set(child
  309. , Column.TYPE
  310. , type
  311. , Column.NAME
  312. , name
  313. , Column.SIZE
  314. , size
  315. , Column.MTIME
  316. , mtime
  317. , Column.VISIBLE
  318. , true
  319. , -1
  320. );
  321. break;
  322. }
  323. if (!_list_store.iter_next(ref child))
  324. break;
  325. }
  326. }
  327. // Update the tree store entry.
  328. Gtk.TreeIter parent_iter;
  329. _tree_store.get_iter(out parent_iter, _folders[parent_folder].get_path());
  330. if (_tree_store.iter_children(out child, parent_iter)) {
  331. Value iter_name;
  332. Value iter_type;
  333. while (true) {
  334. _tree_store.get_value(child, Column.TYPE, out iter_type);
  335. _tree_store.get_value(child, Column.NAME, out iter_name);
  336. if ((string)iter_name == name && (string)iter_type == type) {
  337. _tree_store.set(child
  338. , Column.TYPE
  339. , type
  340. , Column.NAME
  341. , name
  342. , Column.SIZE
  343. , size
  344. , Column.MTIME
  345. , mtime
  346. , Column.VISIBLE
  347. , true
  348. , -1
  349. );
  350. break;
  351. }
  352. if (!_tree_store.iter_next(ref child))
  353. break;
  354. }
  355. }
  356. }
  357. public void on_project_file_removed(string type, string name)
  358. {
  359. string parent_folder = ResourceId.parent_folder(name);
  360. Gtk.TreeIter child;
  361. if (!_folders.has_key(parent_folder))
  362. return;
  363. remove_from_favorites(type, name);
  364. // Remove from list store.
  365. if (_list_store.iter_children(out child, null)) {
  366. Value iter_type;
  367. Value iter_name;
  368. while (true) {
  369. _list_store.get_value(child, Column.TYPE, out iter_type);
  370. _list_store.get_value(child, Column.NAME, out iter_name);
  371. if ((string)iter_name == name && (string)iter_type == type) {
  372. _list_store.remove(ref child);
  373. break;
  374. } else {
  375. if (!_list_store.iter_next(ref child))
  376. break;
  377. }
  378. }
  379. }
  380. // Remove from tree store.
  381. Gtk.TreeIter parent_iter;
  382. _tree_store.get_iter(out parent_iter, _folders[parent_folder].get_path());
  383. if (_tree_store.iter_children(out child, parent_iter)) {
  384. Value iter_type;
  385. Value iter_name;
  386. while (true) {
  387. _tree_store.get_value(child, Column.TYPE, out iter_type);
  388. _tree_store.get_value(child, Column.NAME, out iter_name);
  389. if ((string)iter_name == name && (string)iter_type == type) {
  390. _tree_store.remove(ref child);
  391. break;
  392. } else {
  393. if (!_tree_store.iter_next(ref child))
  394. break;
  395. }
  396. }
  397. }
  398. }
  399. public void on_project_tree_added(string name)
  400. {
  401. Gtk.TreeIter iter;
  402. _list_store.insert_with_values(out iter
  403. , -1
  404. , Column.TYPE
  405. , "<folder>"
  406. , Column.NAME
  407. , name
  408. , Column.SIZE
  409. , 0u
  410. , Column.MTIME
  411. , 0u
  412. , Column.VISIBLE
  413. , true
  414. , -1
  415. );
  416. find_or_make_tree(name);
  417. }
  418. public void on_project_tree_removed(string name)
  419. {
  420. if (name == ROOT_FOLDER)
  421. return;
  422. if (!_folders.has_key(name))
  423. return;
  424. remove_from_favorites("<folder>", name);
  425. // Remove from list store.
  426. Gtk.TreeIter child;
  427. if (_list_store.iter_children(out child, null)) {
  428. Value iter_type;
  429. Value iter_name;
  430. while (true) {
  431. _list_store.get_value(child, Column.TYPE, out iter_type);
  432. _list_store.get_value(child, Column.NAME, out iter_name);
  433. if (((string)iter_name == name && (string)iter_type == "<folder>")
  434. || ((string)iter_name).has_prefix(name + "/")
  435. ) {
  436. if (!_list_store.remove(ref child))
  437. break;
  438. } else {
  439. if (!_list_store.iter_next(ref child))
  440. break;
  441. }
  442. }
  443. }
  444. // Remove the tree.
  445. Gtk.TreeIter iter;
  446. _tree_store.get_iter(out iter, _folders[name].get_path());
  447. _tree_store.remove(ref iter);
  448. // Remove any stale TreeRowRerefence
  449. var it = _folders.map_iterator();
  450. for (var has_next = it.next(); has_next; has_next = it.next()) {
  451. string ff = it.get_key();
  452. if (ff.has_prefix(name + "/"))
  453. it.unset();
  454. }
  455. _folders.unset(name);
  456. }
  457. public void on_project_reset()
  458. {
  459. reset();
  460. }
  461. public Gee.ArrayList<Value?> encode_favorites()
  462. {
  463. Gee.ArrayList<Value?> favorites = new Gee.ArrayList<Value?>();
  464. Gtk.TreeIter parent_iter;
  465. _tree_store.get_iter(out parent_iter, favorites_root_path());
  466. Gtk.TreeIter child;
  467. if (_tree_store.iter_children(out child, parent_iter)) {
  468. Value iter_type;
  469. Value iter_name;
  470. while (true) {
  471. _tree_store.get_value(child, Column.TYPE, out iter_type);
  472. _tree_store.get_value(child, Column.NAME, out iter_name);
  473. Hashtable resource = new Hashtable();
  474. resource["type"] = (string)iter_type;
  475. resource["name"] = (string)iter_name;
  476. favorites.add(resource);
  477. if (!_tree_store.iter_next(ref child))
  478. break;
  479. }
  480. }
  481. return favorites;
  482. }
  483. public Hashtable encode()
  484. {
  485. Hashtable h = new Hashtable();
  486. h["favorites"] = encode_favorites();
  487. return h;
  488. }
  489. public void decode_favorites(Gee.ArrayList<Value?> favorites)
  490. {
  491. foreach (var entry in favorites) {
  492. if (entry == null || !entry.holds(typeof(Hashtable)))
  493. continue;
  494. Hashtable resource = (Hashtable)entry;
  495. if (!resource.has_key("type") || !resource.has_key("name"))
  496. continue;
  497. Value type = resource["type"];
  498. if (!type.holds(typeof(string)))
  499. continue;
  500. Value name = resource["name"];
  501. if (!name.holds(typeof(string)))
  502. continue;
  503. add_to_favorites((string)type, (string)name);
  504. }
  505. }
  506. public void decode(Hashtable h)
  507. {
  508. if (h.has_key("favorites")) {
  509. Value favorites = h["favorites"];
  510. if (favorites.holds(typeof(Gee.ArrayList)))
  511. decode_favorites((Gee.ArrayList<Value?>)favorites);
  512. }
  513. }
  514. public void make_visible(bool visible)
  515. {
  516. _tree_store.foreach((model, path, iter) => {
  517. _tree_store.set(iter, Column.VISIBLE, visible, -1);
  518. return false; // Continue iterating.
  519. });
  520. _list_store.foreach((model, path, iter) => {
  521. _list_store.set(iter, Column.VISIBLE, visible, -1);
  522. return false; // Continue iterating.
  523. });
  524. }
  525. public bool is_visible(string type, string name, string needle)
  526. {
  527. // Always show the roots.
  528. if ((type == "<folder>" && name == "") || type == "<favorites>")
  529. return true;
  530. string basename = GLib.Path.get_basename(name);
  531. return basename != null
  532. && (needle == ""
  533. || basename.down().index_of(needle) > -1
  534. || type.down().index_of(needle) > -1
  535. );
  536. }
  537. public void filter(string needle)
  538. {
  539. _tree_store.foreach((model, path, iter) => {
  540. string type;
  541. string name;
  542. Value val;
  543. model.get_value(iter, ProjectStore.Column.TYPE, out val);
  544. type = (string)val;
  545. model.get_value(iter, ProjectStore.Column.NAME, out val);
  546. name = (string)val;
  547. if (is_visible(type, name, needle)) {
  548. // Make this iter and all its ancestors visible.
  549. Gtk.TreeIter it = iter;
  550. _tree_store.set(it, Column.VISIBLE, true, -1);
  551. while (_tree_store.iter_parent(out it, it))
  552. _tree_store.set(it, Column.VISIBLE, true, -1);
  553. }
  554. return false; // Continue iterating.
  555. });
  556. _list_store.foreach((model, path, iter) => {
  557. string type;
  558. string name;
  559. Value val;
  560. model.get_value(iter, ProjectStore.Column.TYPE, out val);
  561. type = (string)val;
  562. model.get_value(iter, ProjectStore.Column.NAME, out val);
  563. name = (string)val;
  564. _list_store.set(iter, ProjectStore.Column.VISIBLE, is_visible(type, name, needle), -1);
  565. return false; // Continue iterating.
  566. });
  567. }
  568. }
  569. } /* namespace Crown */