level_tree_view.vala 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  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 class LevelTreeView : Gtk.Box
  8. {
  9. public enum ItemType
  10. {
  11. FOLDER,
  12. CAMERA,
  13. LIGHT,
  14. SOUND,
  15. UNIT
  16. }
  17. public enum Column
  18. {
  19. TYPE,
  20. GUID,
  21. NAME,
  22. VISIBLE,
  23. SAVE_STATE,
  24. COUNT
  25. }
  26. public enum SortMode
  27. {
  28. NAME_AZ,
  29. NAME_ZA,
  30. TYPE_AZ,
  31. TYPE_ZA,
  32. COUNT;
  33. public string to_label()
  34. {
  35. switch (this) {
  36. case NAME_AZ:
  37. return "Name A-Z";
  38. case NAME_ZA:
  39. return "Name Z-A";
  40. case TYPE_AZ:
  41. return "Type A-Z";
  42. case TYPE_ZA:
  43. return "Type Z-A";
  44. default:
  45. return "Unknown";
  46. }
  47. }
  48. }
  49. // Data
  50. public Level _level;
  51. public Database _db;
  52. // Widgets
  53. public string _needle;
  54. public EntrySearch _filter_entry;
  55. public Gtk.TreeStore _tree_store;
  56. public Gtk.TreeModelFilter _tree_filter;
  57. public Gtk.TreeModelSort _tree_sort;
  58. public Gtk.TreeView _tree_view;
  59. public Gtk.TreeSelection _tree_selection;
  60. public Gtk.ScrolledWindow _scrolled_window;
  61. public Gtk.Box _sort_items_box;
  62. public Gtk.Popover _sort_items_popover;
  63. public Gtk.MenuButton _sort_items;
  64. public Gtk.GestureMultiPress _gesture_click;
  65. public Gtk.TreeRowReference _units_root;
  66. public Gtk.TreeRowReference _sounds_root;
  67. public signal void selection_changed(Gee.ArrayList<Guid?> selection);
  68. public LevelTreeView(Database db, Level level)
  69. {
  70. Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0);
  71. // Data
  72. _level = level;
  73. _db = db;
  74. // Widgets
  75. _needle = "";
  76. _filter_entry = new EntrySearch();
  77. _filter_entry.set_placeholder_text("Search...");
  78. _filter_entry.search_changed.connect(on_filter_entry_text_changed);
  79. _filter_entry._entry.stop_search.connect(on_stop_search);
  80. _tree_store = new Gtk.TreeStore(Column.COUNT
  81. , typeof(int) // Column.TYPE
  82. , typeof(Guid) // Column.GUID
  83. , typeof(string) // Column.NAME
  84. , typeof(bool) // Column.VISIBLE
  85. , typeof(uint32) // Column.SAVED_STATE
  86. );
  87. _tree_filter = new Gtk.TreeModelFilter(_tree_store, null);
  88. _tree_filter.set_visible_column(Column.VISIBLE);
  89. _tree_sort = new Gtk.TreeModelSort.with_model(_tree_filter);
  90. _tree_sort.set_sort_column_id(Column.NAME, Gtk.SortType.ASCENDING);
  91. Gtk.TreeViewColumn column = new Gtk.TreeViewColumn();
  92. Gtk.CellRendererPixbuf cell_pixbuf = new Gtk.CellRendererPixbuf();
  93. Gtk.CellRendererText cell_text = new Gtk.CellRendererText();
  94. column.pack_start(cell_pixbuf, false);
  95. column.pack_start(cell_text, true);
  96. column.set_cell_data_func(cell_pixbuf, (cell_layout, cell, model, iter) => {
  97. Value type;
  98. model.get_value(iter, LevelTreeView.Column.TYPE, out type);
  99. if ((int)type == LevelTreeView.ItemType.FOLDER)
  100. cell.set_property("icon-name", "browser-folder-symbolic");
  101. else if ((int)type == LevelTreeView.ItemType.UNIT)
  102. cell.set_property("icon-name", "level-object-unit");
  103. else if ((int)type == LevelTreeView.ItemType.SOUND)
  104. cell.set_property("icon-name", "level-object-sound");
  105. else if ((int)type == LevelTreeView.ItemType.LIGHT)
  106. cell.set_property("icon-name", "level-object-light");
  107. else if ((int)type == LevelTreeView.ItemType.CAMERA)
  108. cell.set_property("icon-name", "level-object-camera");
  109. else
  110. cell.set_property("icon-name", "level-object-unknown");
  111. });
  112. column.set_cell_data_func(cell_text, (cell_layout, cell, model, iter) => {
  113. Value name;
  114. model.get_value(iter, LevelTreeView.Column.NAME, out name);
  115. cell.set_property("text", (string)name);
  116. });
  117. _tree_view = new Gtk.TreeView();
  118. _tree_view.append_column(column);
  119. #if 0
  120. // For debugging.
  121. _tree_view.insert_column_with_attributes(-1
  122. , "Guids"
  123. , new gtk.CellRendererText()
  124. , "text"
  125. , Column.GUID
  126. , null
  127. );
  128. #endif
  129. _tree_view.headers_clickable = false;
  130. _tree_view.headers_visible = false;
  131. _tree_view.model = _tree_sort;
  132. _gesture_click = new Gtk.GestureMultiPress(_tree_view);
  133. _gesture_click.set_propagation_phase(Gtk.PropagationPhase.CAPTURE);
  134. _gesture_click.set_button(0);
  135. _gesture_click.pressed.connect(on_button_pressed);
  136. _tree_selection = _tree_view.get_selection();
  137. _tree_selection.set_mode(Gtk.SelectionMode.MULTIPLE);
  138. _tree_selection.changed.connect(on_tree_selection_changed);
  139. _scrolled_window = new Gtk.ScrolledWindow(null, null);
  140. _scrolled_window.add(_tree_view);
  141. // Setup sort menu button popover.
  142. _sort_items_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
  143. Gtk.RadioButton? button = null;
  144. for (int i = 0; i < SortMode.COUNT; ++i)
  145. button = add_sort_item(button, (SortMode)i);
  146. _sort_items_box.show_all();
  147. _sort_items_popover = new Gtk.Popover(null);
  148. _sort_items_popover.add(_sort_items_box);
  149. _sort_items = new Gtk.MenuButton();
  150. _sort_items.add(new Gtk.Image.from_icon_name("list-sort", Gtk.IconSize.SMALL_TOOLBAR));
  151. _sort_items.get_style_context().add_class("flat");
  152. _sort_items.get_style_context().add_class("image-button");
  153. _sort_items.can_focus = false;
  154. _sort_items.set_popover(_sort_items_popover);
  155. var tree_control = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
  156. tree_control.pack_start(_filter_entry, true, true);
  157. tree_control.pack_end(_sort_items, false, false);
  158. this.pack_start(tree_control, false, true, 0);
  159. this.pack_start(_scrolled_window, true, true, 0);
  160. }
  161. public void on_button_pressed(int n_press, double x, double y)
  162. {
  163. if (_gesture_click.get_current_button() == Gdk.BUTTON_SECONDARY) {
  164. int bx;
  165. int by;
  166. Gtk.TreePath path;
  167. Gtk.TreeViewColumn column;
  168. _tree_view.convert_widget_to_bin_window_coords((int)x, (int)y, out bx, out by);
  169. if (!_tree_view.get_path_at_pos(bx, by, out path, out column, null, null))
  170. return; // Clicked on empty space.
  171. if (!_tree_selection.path_is_selected(path)) {
  172. _tree_selection.unselect_all();
  173. _tree_selection.select_path(path);
  174. }
  175. GLib.Menu menu_model = new GLib.Menu();
  176. GLib.MenuItem mi;
  177. if (_tree_selection.count_selected_rows() == 1) {
  178. GLib.List<Gtk.TreePath> selected_paths = _tree_selection.get_selected_rows(null);
  179. Gtk.TreeIter iter;
  180. if (_tree_view.model.get_iter(out iter, selected_paths.nth(0).data)) {
  181. Value val;
  182. _tree_view.model.get_value(iter, Column.TYPE, out val);
  183. if ((int)val != ItemType.FOLDER) {
  184. _tree_view.model.get_value(iter, Column.GUID, out val);
  185. Guid object_id = (Guid)val;
  186. mi = new GLib.MenuItem("Rename...", null);
  187. mi.set_action_and_target_value("app.rename", new GLib.Variant.tuple({ object_id.to_string(), "" }));
  188. menu_model.append_item(mi);
  189. }
  190. }
  191. }
  192. mi = new GLib.MenuItem("Duplicate", null);
  193. mi.set_action_and_target_value("database.duplicate", null);
  194. menu_model.append_item(mi);
  195. mi = new GLib.MenuItem("Delete", null);
  196. mi.set_action_and_target_value("database.delete", null);
  197. menu_model.append_item(mi);
  198. if (_tree_selection.count_selected_rows() == 1) {
  199. GLib.List<Gtk.TreePath> selected_paths = _tree_selection.get_selected_rows(null);
  200. Gtk.TreeIter iter;
  201. if (_tree_view.model.get_iter(out iter, selected_paths.nth(0).data)) {
  202. Value val;
  203. _tree_view.model.get_value(iter, Column.GUID, out val);
  204. Guid object_id = (Guid)val;
  205. _tree_view.model.get_value(iter, Column.NAME, out val);
  206. string name = (string)val;
  207. if (_db.object_type(object_id) == OBJECT_TYPE_UNIT) {
  208. mi = new GLib.MenuItem("Save as Prefab...", null);
  209. mi.set_action_and_target_value("app.unit-save-as-prefab", new GLib.Variant.tuple({ object_id.to_string(), name }));
  210. menu_model.append_item(mi);
  211. }
  212. }
  213. }
  214. Gtk.Popover menu = new Gtk.Popover.from_model(null, menu_model);
  215. menu.set_relative_to(_tree_view);
  216. menu.set_pointing_to({ (int)x, (int)y, 1, 1 });
  217. menu.set_position(Gtk.PositionType.BOTTOM);
  218. menu.popup();
  219. _gesture_click.set_state(Gtk.EventSequenceState.CLAIMED);
  220. } else if (_gesture_click.get_current_button() == Gdk.BUTTON_PRIMARY && n_press == 2) {
  221. GLib.Application.get_default().activate_action("camera-frame-selected", null);
  222. }
  223. }
  224. public void on_tree_selection_changed()
  225. {
  226. Gee.ArrayList<Guid?> ids = new Gee.ArrayList<Guid?>();
  227. _tree_selection.selected_foreach((model, path, iter) => {
  228. Value type;
  229. model.get_value(iter, Column.TYPE, out type);
  230. if ((int)type == ItemType.FOLDER)
  231. return;
  232. Value id;
  233. model.get_value(iter, Column.GUID, out id);
  234. ids.add((Guid)id);
  235. });
  236. selection_changed(ids);
  237. }
  238. public void read_selection(Guid?[] selection)
  239. {
  240. _tree_selection.changed.disconnect(on_tree_selection_changed);
  241. _tree_selection.unselect_all();
  242. Gtk.TreePath? last_selected = null;
  243. _tree_sort.foreach ((model, path, iter) => {
  244. Value type;
  245. model.get_value(iter, Column.TYPE, out type);
  246. if ((int)type == ItemType.FOLDER)
  247. return false;
  248. Value id;
  249. model.get_value(iter, Column.GUID, out id);
  250. foreach (Guid? guid in selection) {
  251. if ((Guid)id == guid) {
  252. _tree_selection.select_iter(iter);
  253. last_selected = path;
  254. return false;
  255. }
  256. }
  257. return false;
  258. });
  259. if (last_selected != null)
  260. _tree_view.scroll_to_cell(last_selected, null, false, 0.0f, 0.0f);
  261. _tree_selection.changed.connect(on_tree_selection_changed);
  262. }
  263. public void on_objects_changed(Guid?[] object_ids, uint32 flags)
  264. {
  265. foreach (var id in object_ids) {
  266. _tree_sort.foreach ((model, path, iter) => {
  267. Value type;
  268. model.get_value(iter, Column.TYPE, out type);
  269. if ((int)type == ItemType.FOLDER)
  270. return false;
  271. Value guid;
  272. model.get_value(iter, Column.GUID, out guid);
  273. Guid guid_model = (Guid)guid;
  274. if (guid_model == id) {
  275. Gtk.TreeIter iter_filter;
  276. Gtk.TreeIter iter_model;
  277. _tree_sort.convert_iter_to_child_iter(out iter_filter, iter);
  278. _tree_filter.convert_iter_to_child_iter(out iter_model, iter_filter);
  279. _tree_store.set(iter_model
  280. , Column.NAME
  281. , _db.name(id)
  282. , -1
  283. );
  284. return true;
  285. }
  286. return false;
  287. });
  288. }
  289. }
  290. public ItemType item_type(Unit u)
  291. {
  292. if (u.is_light())
  293. return ItemType.LIGHT;
  294. else if (u.is_camera())
  295. return ItemType.CAMERA;
  296. else
  297. return ItemType.UNIT;
  298. }
  299. // Sets the level object to show.
  300. public void set_level(Level level)
  301. {
  302. Gtk.TreeIter iter;
  303. _tree_view.model = null;
  304. _tree_store.clear();
  305. _tree_store.insert_with_values(out iter
  306. , null
  307. , -1
  308. , Column.TYPE
  309. , ItemType.FOLDER
  310. , Column.GUID
  311. , GUID_ZERO
  312. , Column.NAME
  313. , "Units"
  314. , Column.VISIBLE
  315. , true
  316. , Column.SAVE_STATE
  317. , 0u
  318. , -1
  319. );
  320. _units_root = new Gtk.TreeRowReference(_tree_store, _tree_store.get_path(iter));
  321. _tree_store.insert_with_values(out iter
  322. , null
  323. , -1
  324. , Column.TYPE
  325. , ItemType.FOLDER
  326. , Column.GUID
  327. , GUID_ZERO
  328. , Column.NAME
  329. , "Sounds"
  330. , Column.VISIBLE
  331. , true
  332. , Column.SAVE_STATE
  333. , 0u
  334. , -1
  335. );
  336. _sounds_root = new Gtk.TreeRowReference(_tree_store, _tree_store.get_path(iter));
  337. _tree_view.model = _tree_sort;
  338. _level = level;
  339. on_objects_created(_db.get_set(_level._id, "units", new Gee.HashSet<Guid?>()).to_array());
  340. on_objects_created(_db.get_set(_level._id, "sounds", new Gee.HashSet<Guid?>()).to_array());
  341. _tree_view.expand_all();
  342. }
  343. public int insert_units(Guid?[] object_ids)
  344. {
  345. Gtk.TreeIter iter;
  346. int i;
  347. if (object_ids.length > 1 && Unit.is_component(object_ids[1], _db)) {
  348. for (i = 1; i < object_ids.length; ++i) {
  349. if (!Unit.is_component(object_ids[i], _db))
  350. break;
  351. }
  352. } else {
  353. for (i = 0; i < object_ids.length; ++i) {
  354. if (_db.object_type(object_ids[i]) != OBJECT_TYPE_UNIT)
  355. break;
  356. Unit u = Unit(_level._db, object_ids[i]);
  357. Gtk.TreeIter units_iter;
  358. _tree_store.get_iter(out units_iter, _units_root.get_path());
  359. _tree_store.insert_with_values(out iter
  360. , units_iter
  361. , -1
  362. , Column.TYPE
  363. , item_type(u)
  364. , Column.GUID
  365. , u._id
  366. , Column.NAME
  367. , _db.name(u._id)
  368. , Column.VISIBLE
  369. , true
  370. , Column.SAVE_STATE
  371. , 0u
  372. , -1
  373. );
  374. }
  375. }
  376. return i;
  377. }
  378. public int insert_sounds(Guid?[] object_ids)
  379. {
  380. int i;
  381. for (i = 0; i < object_ids.length; ++i) {
  382. if (_db.object_type(object_ids[i]) != OBJECT_TYPE_SOUND_SOURCE)
  383. break;
  384. Gtk.TreeIter sounds_iter;
  385. Gtk.TreeIter iter;
  386. _tree_store.get_iter(out sounds_iter, _sounds_root.get_path());
  387. _tree_store.insert_with_values(out iter
  388. , sounds_iter
  389. , -1
  390. , Column.TYPE
  391. , ItemType.SOUND
  392. , Column.GUID
  393. , object_ids[i]
  394. , Column.NAME
  395. , _db.name(object_ids[i])
  396. , Column.VISIBLE
  397. , true
  398. , Column.SAVE_STATE
  399. , 0u
  400. , -1
  401. );
  402. }
  403. return i;
  404. }
  405. public void on_objects_created(Guid?[] object_ids, uint32 flags = 0)
  406. {
  407. int i = 0;
  408. while (i < object_ids.length) {
  409. if (_db.object_type(object_ids[i]) == OBJECT_TYPE_UNIT) {
  410. i += insert_units(object_ids[i:object_ids.length]);
  411. } else if (_db.object_type(object_ids[i]) == OBJECT_TYPE_SOUND_SOURCE) {
  412. i += insert_sounds(object_ids[i:object_ids.length]);
  413. } else {
  414. ++i; // Skip object.
  415. }
  416. }
  417. _tree_filter.refilter();
  418. }
  419. public int remove_units(Guid?[] object_ids)
  420. {
  421. int i;
  422. _tree_selection.changed.disconnect(on_tree_selection_changed);
  423. if (object_ids.length > 1 && Unit.is_component(object_ids[1], _db)) {
  424. for (i = 1; i < object_ids.length; ++i) {
  425. if (!Unit.is_component(object_ids[i], _db))
  426. break;
  427. }
  428. } else {
  429. for (i = 0; i < object_ids.length; ++i) {
  430. if (_db.object_type(object_ids[i]) != OBJECT_TYPE_UNIT)
  431. break;
  432. Gtk.TreeIter parent_iter;
  433. _tree_store.get_iter(out parent_iter, _units_root.get_path());
  434. remove_item(object_ids[i], parent_iter);
  435. }
  436. }
  437. _tree_selection.changed.connect(on_tree_selection_changed);
  438. on_tree_selection_changed();
  439. return i;
  440. }
  441. public int remove_sounds(Guid?[] object_ids)
  442. {
  443. int i;
  444. _tree_selection.changed.disconnect(on_tree_selection_changed);
  445. for (i = 0; i < object_ids.length; ++i) {
  446. if (_db.object_type(object_ids[i]) != OBJECT_TYPE_SOUND_SOURCE)
  447. break;
  448. Gtk.TreeIter parent_iter;
  449. _tree_store.get_iter(out parent_iter, _sounds_root.get_path());
  450. remove_item(object_ids[i], parent_iter);
  451. }
  452. _tree_selection.changed.connect(on_tree_selection_changed);
  453. on_tree_selection_changed();
  454. return i;
  455. }
  456. public void on_objects_destroyed(Guid?[] object_ids, uint32 flags = 0)
  457. {
  458. int i = 0;
  459. while (i < object_ids.length) {
  460. if (_db.object_type(object_ids[i]) == OBJECT_TYPE_UNIT) {
  461. i += remove_units(object_ids[i:object_ids.length]);
  462. } else if (_db.object_type(object_ids[i]) == OBJECT_TYPE_SOUND_SOURCE) {
  463. i += remove_sounds(object_ids[i:object_ids.length]);
  464. } else {
  465. ++i; // Skip object.
  466. }
  467. }
  468. }
  469. public void remove_item(Guid id, Gtk.TreeIter parent_iter)
  470. {
  471. Gtk.TreeIter child;
  472. if (_tree_store.iter_children(out child, parent_iter)) {
  473. Value column_id;
  474. while (true) {
  475. _tree_store.get_value(child, Column.GUID, out column_id);
  476. if (Guid.equal_func((Guid)column_id, id)) {
  477. _tree_store.remove(ref child);
  478. break;
  479. } else {
  480. if (!_tree_store.iter_next(ref child))
  481. break;
  482. }
  483. }
  484. }
  485. }
  486. public Gtk.RadioButton add_sort_item(Gtk.RadioButton? group, SortMode mode)
  487. {
  488. var button = new Gtk.RadioButton.with_label_from_widget(group, mode.to_label());
  489. button.toggled.connect(() => {
  490. if (mode == SortMode.NAME_AZ)
  491. _tree_sort.set_sort_column_id(Column.NAME, Gtk.SortType.ASCENDING);
  492. else if (mode == SortMode.NAME_ZA)
  493. _tree_sort.set_sort_column_id(Column.NAME, Gtk.SortType.DESCENDING);
  494. else if (mode == SortMode.TYPE_AZ)
  495. _tree_sort.set_sort_column_id(Column.TYPE, Gtk.SortType.ASCENDING);
  496. else if (mode == SortMode.TYPE_ZA)
  497. _tree_sort.set_sort_column_id(Column.TYPE, Gtk.SortType.DESCENDING);
  498. _tree_filter.refilter();
  499. _sort_items_popover.popdown();
  500. });
  501. _sort_items_box.pack_start(button, false, false);
  502. return button;
  503. }
  504. public bool save_tree_state(Gtk.TreeModel model, Gtk.TreePath path, Gtk.TreeIter iter)
  505. {
  506. Gtk.TreePath filter_path = _tree_filter.convert_child_path_to_path(path);
  507. if (filter_path == null) {
  508. // Either the path is not valid or points to a non-visible row in the model.
  509. return false;
  510. }
  511. Gtk.TreePath sort_path = _tree_sort.convert_child_path_to_path(filter_path);
  512. if (sort_path == null) {
  513. // The path is not valid.
  514. assert(false);
  515. return false;
  516. }
  517. bool expanded = _tree_view.is_row_expanded(sort_path);
  518. bool selected = _tree_view.get_selection().path_is_selected(sort_path);
  519. uint32 user_data = 0;
  520. user_data |= (uint32)expanded << 0;
  521. user_data |= (uint32)selected << 1;
  522. _tree_store.set(iter, Column.SAVE_STATE, user_data, -1);
  523. return false; // Continue iterating.
  524. }
  525. public bool restore_tree_state(Gtk.TreeModel model, Gtk.TreePath path, Gtk.TreeIter iter)
  526. {
  527. uint32 user_data;
  528. Value val;
  529. _tree_store.get_value(iter, Column.SAVE_STATE, out val);
  530. user_data = (uint32)val;
  531. bool expanded = (bool)((user_data & 0x1) >> 0);
  532. bool selected = (bool)((user_data & 0x2) >> 1);
  533. Gtk.TreePath filter_path = _tree_filter.convert_child_path_to_path(path);
  534. if (filter_path == null) {
  535. // Either the path is not valid or points to a non-visible row in the model.
  536. return false;
  537. }
  538. Gtk.TreePath sort_path = _tree_sort.convert_child_path_to_path(filter_path);
  539. if (sort_path == null) {
  540. // The path is not valid.
  541. return false;
  542. }
  543. if (expanded)
  544. _tree_view.expand_to_path(sort_path);
  545. else
  546. _tree_view.collapse_row(sort_path);
  547. if (selected)
  548. _tree_view.get_selection().select_path(sort_path);
  549. return false; // Continue iterating.
  550. }
  551. public void make_visible(bool visible)
  552. {
  553. _tree_store.foreach((model, path, iter) => {
  554. _tree_store.set(iter, Column.VISIBLE, visible, -1);
  555. return false; // Continue iterating.
  556. });
  557. }
  558. public void filter(string needle)
  559. {
  560. make_visible(false);
  561. _tree_filter.refilter();
  562. _tree_store.foreach((model, path, iter) => {
  563. int type;
  564. string name;
  565. Value val;
  566. model.get_value(iter, Column.TYPE, out val);
  567. type = (int)val;
  568. bool visible = false;
  569. // Always show the roots.
  570. if (type == ItemType.FOLDER) {
  571. visible = true;
  572. } else {
  573. model.get_value(iter, Column.NAME, out val);
  574. name = (string)val;
  575. visible = needle == "" || name.down().index_of(needle) > -1;
  576. }
  577. if (visible) {
  578. // Make this iter and all its ancestors visible.
  579. Gtk.TreeIter it = iter;
  580. _tree_store.set(it, Column.VISIBLE, true, -1);
  581. while (_tree_store.iter_parent(out it, it))
  582. _tree_store.set(it, Column.VISIBLE, true, -1);
  583. }
  584. return false; // Continue iterating.
  585. });
  586. _tree_view.expand_all();
  587. }
  588. public void on_search_started()
  589. {
  590. _tree_selection.changed.disconnect(on_tree_selection_changed);
  591. // Save the current tree state (expanded branches + selection)
  592. // to restore it later when the search is done.
  593. _tree_store.foreach(save_tree_state);
  594. filter(_needle);
  595. _tree_selection.changed.connect(on_tree_selection_changed);
  596. }
  597. public void on_search_changed()
  598. {
  599. _tree_selection.changed.disconnect(on_tree_selection_changed);
  600. filter(_needle);
  601. _tree_selection.changed.connect(on_tree_selection_changed);
  602. }
  603. public void on_search_stopped()
  604. {
  605. // Only restore the old selection if it has not been
  606. // modified while searching (i.e. nothing is selected
  607. // because entering search clears it).
  608. Gtk.TreeModel selected_model;
  609. GLib.List<Gtk.TreePath> selected_rows = _tree_view.get_selection().get_selected_rows(out selected_model);
  610. Gtk.TreeRowReference[] selected_refs = {};
  611. for (uint i = 0, n = selected_rows.length(); i < n; ++i)
  612. selected_refs += new Gtk.TreeRowReference(selected_model, selected_rows.nth(i).data);
  613. _tree_selection.changed.disconnect(on_tree_selection_changed);
  614. make_visible(true);
  615. _tree_filter.refilter();
  616. // Restore the previous tree state (old expanded branches + old selection).
  617. _tree_view.get_selection().unselect_all();
  618. _tree_store.foreach(restore_tree_state);
  619. _tree_selection.changed.connect(on_tree_selection_changed);
  620. // If the selection changed while searching, restore it as well.
  621. for (int i = 0; i < selected_refs.length; ++i) {
  622. Gtk.TreePath path = selected_refs[i].get_path();
  623. _tree_view.expand_to_path(path);
  624. _tree_view.get_selection().select_path(path);
  625. _tree_view.scroll_to_cell(path, null, false, 0.0f, 0.0f);
  626. }
  627. }
  628. public void on_stop_search()
  629. {
  630. _filter_entry._entry.set_text("");
  631. }
  632. public void on_filter_entry_text_changed()
  633. {
  634. string old_needle = _needle;
  635. _needle = _filter_entry.text.strip().down();
  636. if (old_needle == "" && _needle != "") {
  637. on_search_started();
  638. } else if (old_needle != "" && _needle == "") {
  639. on_search_stopped();
  640. } else if (_needle != "") {
  641. on_search_changed();
  642. }
  643. }
  644. }
  645. } /* namespace Crown */