level_tree_view.vala 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  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 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.set_tooltip_text("Sort items.");
  151. _sort_items.add(new Gtk.Image.from_icon_name("list-sort", Gtk.IconSize.SMALL_TOOLBAR));
  152. _sort_items.get_style_context().add_class("flat");
  153. _sort_items.get_style_context().add_class("image-button");
  154. _sort_items.can_focus = false;
  155. _sort_items.set_popover(_sort_items_popover);
  156. var tree_control = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
  157. tree_control.pack_start(_filter_entry, true, true);
  158. tree_control.pack_end(_sort_items, false, false);
  159. this.pack_start(tree_control, false, true, 0);
  160. this.pack_start(_scrolled_window, true, true, 0);
  161. }
  162. public void on_button_pressed(int n_press, double x, double y)
  163. {
  164. if (_gesture_click.get_current_button() == Gdk.BUTTON_SECONDARY) {
  165. int bx;
  166. int by;
  167. Gtk.TreePath path;
  168. Gtk.TreeViewColumn column;
  169. _tree_view.convert_widget_to_bin_window_coords((int)x, (int)y, out bx, out by);
  170. if (!_tree_view.get_path_at_pos(bx, by, out path, out column, null, null))
  171. return; // Clicked on empty space.
  172. if (!_tree_selection.path_is_selected(path)) {
  173. _tree_selection.unselect_all();
  174. _tree_selection.select_path(path);
  175. }
  176. GLib.Menu menu_model = new GLib.Menu();
  177. GLib.MenuItem mi;
  178. if (_tree_selection.count_selected_rows() == 1) {
  179. GLib.List<Gtk.TreePath> selected_paths = _tree_selection.get_selected_rows(null);
  180. Gtk.TreeIter iter;
  181. if (_tree_view.model.get_iter(out iter, selected_paths.nth(0).data)) {
  182. Value val;
  183. _tree_view.model.get_value(iter, Column.TYPE, out val);
  184. if ((int)val != ItemType.FOLDER) {
  185. _tree_view.model.get_value(iter, Column.GUID, out val);
  186. Guid object_id = (Guid)val;
  187. mi = new GLib.MenuItem("Rename...", null);
  188. mi.set_action_and_target_value("app.rename", new GLib.Variant.tuple({ object_id.to_string(), "" }));
  189. menu_model.append_item(mi);
  190. }
  191. }
  192. }
  193. mi = new GLib.MenuItem("Duplicate", null);
  194. mi.set_action_and_target_value("database.duplicate", null);
  195. menu_model.append_item(mi);
  196. mi = new GLib.MenuItem("Delete", null);
  197. mi.set_action_and_target_value("database.delete", null);
  198. menu_model.append_item(mi);
  199. if (_tree_selection.count_selected_rows() == 1) {
  200. GLib.List<Gtk.TreePath> selected_paths = _tree_selection.get_selected_rows(null);
  201. Gtk.TreeIter iter;
  202. if (_tree_view.model.get_iter(out iter, selected_paths.nth(0).data)) {
  203. Value val;
  204. _tree_view.model.get_value(iter, Column.GUID, out val);
  205. Guid object_id = (Guid)val;
  206. _tree_view.model.get_value(iter, Column.NAME, out val);
  207. string name = (string)val;
  208. if (_db.object_type(object_id) == OBJECT_TYPE_UNIT) {
  209. mi = new GLib.MenuItem("Save as Prefab...", null);
  210. mi.set_action_and_target_value("app.unit-save-as-prefab", new GLib.Variant.tuple({ object_id.to_string(), name }));
  211. menu_model.append_item(mi);
  212. }
  213. }
  214. }
  215. Gtk.Popover menu = new Gtk.Popover.from_model(null, menu_model);
  216. menu.set_relative_to(_tree_view);
  217. menu.set_pointing_to({ (int)x, (int)y, 1, 1 });
  218. menu.set_position(Gtk.PositionType.BOTTOM);
  219. menu.popup();
  220. _gesture_click.set_state(Gtk.EventSequenceState.CLAIMED);
  221. } else if (_gesture_click.get_current_button() == Gdk.BUTTON_PRIMARY && n_press == 2) {
  222. GLib.Application.get_default().activate_action("camera-frame-selected", null);
  223. }
  224. }
  225. public void on_tree_selection_changed()
  226. {
  227. Gee.ArrayList<Guid?> ids = new Gee.ArrayList<Guid?>();
  228. _tree_selection.selected_foreach((model, path, iter) => {
  229. Value type;
  230. model.get_value(iter, Column.TYPE, out type);
  231. if ((int)type == ItemType.FOLDER)
  232. return;
  233. Value id;
  234. model.get_value(iter, Column.GUID, out id);
  235. ids.add((Guid)id);
  236. });
  237. selection_changed(ids);
  238. }
  239. public void read_selection(Guid?[] selection)
  240. {
  241. _tree_selection.changed.disconnect(on_tree_selection_changed);
  242. _tree_selection.unselect_all();
  243. Gtk.TreePath? last_selected = null;
  244. _tree_sort.foreach ((model, path, iter) => {
  245. Value type;
  246. model.get_value(iter, Column.TYPE, out type);
  247. if ((int)type == ItemType.FOLDER)
  248. return false;
  249. Value id;
  250. model.get_value(iter, Column.GUID, out id);
  251. foreach (Guid? guid in selection) {
  252. if ((Guid)id == guid) {
  253. _tree_selection.select_iter(iter);
  254. last_selected = path;
  255. return false;
  256. }
  257. }
  258. return false;
  259. });
  260. if (last_selected != null)
  261. _tree_view.scroll_to_cell(last_selected, null, false, 0.0f, 0.0f);
  262. _tree_selection.changed.connect(on_tree_selection_changed);
  263. }
  264. public void on_objects_changed(Guid?[] object_ids, uint32 flags)
  265. {
  266. foreach (var id in object_ids) {
  267. _tree_sort.foreach ((model, path, iter) => {
  268. Value type;
  269. model.get_value(iter, Column.TYPE, out type);
  270. if ((int)type == ItemType.FOLDER)
  271. return false;
  272. Value guid;
  273. model.get_value(iter, Column.GUID, out guid);
  274. Guid guid_model = (Guid)guid;
  275. if (guid_model == id) {
  276. Gtk.TreeIter iter_filter;
  277. Gtk.TreeIter iter_model;
  278. _tree_sort.convert_iter_to_child_iter(out iter_filter, iter);
  279. _tree_filter.convert_iter_to_child_iter(out iter_model, iter_filter);
  280. _tree_store.set(iter_model
  281. , Column.NAME
  282. , _db.name(id)
  283. , -1
  284. );
  285. return true;
  286. }
  287. return false;
  288. });
  289. }
  290. }
  291. public ItemType item_type(Unit u)
  292. {
  293. if (u.is_light())
  294. return ItemType.LIGHT;
  295. else if (u.is_camera())
  296. return ItemType.CAMERA;
  297. else
  298. return ItemType.UNIT;
  299. }
  300. // Sets the level object to show.
  301. public void set_level(Level level)
  302. {
  303. Gtk.TreeIter iter;
  304. _tree_view.model = null;
  305. _tree_store.clear();
  306. _tree_store.insert_with_values(out iter
  307. , null
  308. , -1
  309. , Column.TYPE
  310. , ItemType.FOLDER
  311. , Column.GUID
  312. , GUID_ZERO
  313. , Column.NAME
  314. , "Units"
  315. , Column.VISIBLE
  316. , true
  317. , Column.SAVE_STATE
  318. , 0u
  319. , -1
  320. );
  321. _units_root = new Gtk.TreeRowReference(_tree_store, _tree_store.get_path(iter));
  322. _tree_store.insert_with_values(out iter
  323. , null
  324. , -1
  325. , Column.TYPE
  326. , ItemType.FOLDER
  327. , Column.GUID
  328. , GUID_ZERO
  329. , Column.NAME
  330. , "Sounds"
  331. , Column.VISIBLE
  332. , true
  333. , Column.SAVE_STATE
  334. , 0u
  335. , -1
  336. );
  337. _sounds_root = new Gtk.TreeRowReference(_tree_store, _tree_store.get_path(iter));
  338. _tree_view.model = _tree_sort;
  339. _level = level;
  340. on_objects_created(_db.get_set(_level._id, "units", new Gee.HashSet<Guid?>()).to_array());
  341. on_objects_created(_db.get_set(_level._id, "sounds", new Gee.HashSet<Guid?>()).to_array());
  342. _tree_view.expand_all();
  343. }
  344. public int insert_units(Guid?[] object_ids)
  345. {
  346. Gtk.TreeIter iter;
  347. int i;
  348. if (object_ids.length > 1 && Unit.is_component(object_ids[1], _db)) {
  349. for (i = 1; i < object_ids.length; ++i) {
  350. if (!Unit.is_component(object_ids[i], _db))
  351. break;
  352. }
  353. } else {
  354. for (i = 0; i < object_ids.length; ++i) {
  355. if (_db.object_type(object_ids[i]) != OBJECT_TYPE_UNIT)
  356. break;
  357. Unit u = Unit(_level._db, object_ids[i]);
  358. Gtk.TreeIter units_iter;
  359. _tree_store.get_iter(out units_iter, _units_root.get_path());
  360. _tree_store.insert_with_values(out iter
  361. , units_iter
  362. , -1
  363. , Column.TYPE
  364. , item_type(u)
  365. , Column.GUID
  366. , u._id
  367. , Column.NAME
  368. , _db.name(u._id)
  369. , Column.VISIBLE
  370. , true
  371. , Column.SAVE_STATE
  372. , 0u
  373. , -1
  374. );
  375. }
  376. }
  377. return i;
  378. }
  379. public int insert_sounds(Guid?[] object_ids)
  380. {
  381. int i;
  382. for (i = 0; i < object_ids.length; ++i) {
  383. if (_db.object_type(object_ids[i]) != OBJECT_TYPE_SOUND_SOURCE)
  384. break;
  385. Gtk.TreeIter sounds_iter;
  386. Gtk.TreeIter iter;
  387. _tree_store.get_iter(out sounds_iter, _sounds_root.get_path());
  388. _tree_store.insert_with_values(out iter
  389. , sounds_iter
  390. , -1
  391. , Column.TYPE
  392. , ItemType.SOUND
  393. , Column.GUID
  394. , object_ids[i]
  395. , Column.NAME
  396. , _db.name(object_ids[i])
  397. , Column.VISIBLE
  398. , true
  399. , Column.SAVE_STATE
  400. , 0u
  401. , -1
  402. );
  403. }
  404. return i;
  405. }
  406. public void on_objects_created(Guid?[] object_ids, uint32 flags = 0)
  407. {
  408. int i = 0;
  409. while (i < object_ids.length) {
  410. if (_db.object_type(object_ids[i]) == OBJECT_TYPE_UNIT) {
  411. i += insert_units(object_ids[i:object_ids.length]);
  412. } else if (_db.object_type(object_ids[i]) == OBJECT_TYPE_SOUND_SOURCE) {
  413. i += insert_sounds(object_ids[i:object_ids.length]);
  414. } else {
  415. ++i; // Skip object.
  416. }
  417. }
  418. _tree_filter.refilter();
  419. }
  420. public int remove_units(Guid?[] object_ids)
  421. {
  422. int i;
  423. _tree_selection.changed.disconnect(on_tree_selection_changed);
  424. if (object_ids.length > 1 && Unit.is_component(object_ids[1], _db)) {
  425. for (i = 1; i < object_ids.length; ++i) {
  426. if (!Unit.is_component(object_ids[i], _db))
  427. break;
  428. }
  429. } else {
  430. for (i = 0; i < object_ids.length; ++i) {
  431. if (_db.object_type(object_ids[i]) != OBJECT_TYPE_UNIT)
  432. break;
  433. Gtk.TreeIter parent_iter;
  434. _tree_store.get_iter(out parent_iter, _units_root.get_path());
  435. remove_item(object_ids[i], parent_iter);
  436. }
  437. }
  438. _tree_selection.changed.connect(on_tree_selection_changed);
  439. on_tree_selection_changed();
  440. return i;
  441. }
  442. public int remove_sounds(Guid?[] object_ids)
  443. {
  444. int i;
  445. _tree_selection.changed.disconnect(on_tree_selection_changed);
  446. for (i = 0; i < object_ids.length; ++i) {
  447. if (_db.object_type(object_ids[i]) != OBJECT_TYPE_SOUND_SOURCE)
  448. break;
  449. Gtk.TreeIter parent_iter;
  450. _tree_store.get_iter(out parent_iter, _sounds_root.get_path());
  451. remove_item(object_ids[i], parent_iter);
  452. }
  453. _tree_selection.changed.connect(on_tree_selection_changed);
  454. on_tree_selection_changed();
  455. return i;
  456. }
  457. public void on_objects_destroyed(Guid?[] object_ids, uint32 flags = 0)
  458. {
  459. int i = 0;
  460. while (i < object_ids.length) {
  461. if (_db.object_type(object_ids[i]) == OBJECT_TYPE_UNIT) {
  462. i += remove_units(object_ids[i:object_ids.length]);
  463. } else if (_db.object_type(object_ids[i]) == OBJECT_TYPE_SOUND_SOURCE) {
  464. i += remove_sounds(object_ids[i:object_ids.length]);
  465. } else {
  466. ++i; // Skip object.
  467. }
  468. }
  469. }
  470. public void remove_item(Guid id, Gtk.TreeIter parent_iter)
  471. {
  472. Gtk.TreeIter child;
  473. if (_tree_store.iter_children(out child, parent_iter)) {
  474. Value column_id;
  475. while (true) {
  476. _tree_store.get_value(child, Column.GUID, out column_id);
  477. if (Guid.equal_func((Guid)column_id, id)) {
  478. _tree_store.remove(ref child);
  479. break;
  480. } else {
  481. if (!_tree_store.iter_next(ref child))
  482. break;
  483. }
  484. }
  485. }
  486. }
  487. public Gtk.RadioButton add_sort_item(Gtk.RadioButton? group, SortMode mode)
  488. {
  489. var button = new Gtk.RadioButton.with_label_from_widget(group, mode.to_label());
  490. button.toggled.connect(() => {
  491. if (mode == SortMode.NAME_AZ)
  492. _tree_sort.set_sort_column_id(Column.NAME, Gtk.SortType.ASCENDING);
  493. else if (mode == SortMode.NAME_ZA)
  494. _tree_sort.set_sort_column_id(Column.NAME, Gtk.SortType.DESCENDING);
  495. else if (mode == SortMode.TYPE_AZ)
  496. _tree_sort.set_sort_column_id(Column.TYPE, Gtk.SortType.ASCENDING);
  497. else if (mode == SortMode.TYPE_ZA)
  498. _tree_sort.set_sort_column_id(Column.TYPE, Gtk.SortType.DESCENDING);
  499. _tree_filter.refilter();
  500. _sort_items_popover.popdown();
  501. });
  502. _sort_items_box.pack_start(button, false, false);
  503. return button;
  504. }
  505. public bool save_tree_state(Gtk.TreeModel model, Gtk.TreePath path, Gtk.TreeIter iter)
  506. {
  507. Gtk.TreePath filter_path = _tree_filter.convert_child_path_to_path(path);
  508. if (filter_path == null) {
  509. // Either the path is not valid or points to a non-visible row in the model.
  510. return false;
  511. }
  512. Gtk.TreePath sort_path = _tree_sort.convert_child_path_to_path(filter_path);
  513. if (sort_path == null) {
  514. // The path is not valid.
  515. assert(false);
  516. return false;
  517. }
  518. bool expanded = _tree_view.is_row_expanded(sort_path);
  519. bool selected = _tree_view.get_selection().path_is_selected(sort_path);
  520. uint32 user_data = 0;
  521. user_data |= (uint32)expanded << 0;
  522. user_data |= (uint32)selected << 1;
  523. _tree_store.set(iter, Column.SAVE_STATE, user_data, -1);
  524. return false; // Continue iterating.
  525. }
  526. public bool restore_tree_state(Gtk.TreeModel model, Gtk.TreePath path, Gtk.TreeIter iter)
  527. {
  528. uint32 user_data;
  529. Value val;
  530. _tree_store.get_value(iter, Column.SAVE_STATE, out val);
  531. user_data = (uint32)val;
  532. bool expanded = (bool)((user_data & 0x1) >> 0);
  533. bool selected = (bool)((user_data & 0x2) >> 1);
  534. Gtk.TreePath filter_path = _tree_filter.convert_child_path_to_path(path);
  535. if (filter_path == null) {
  536. // Either the path is not valid or points to a non-visible row in the model.
  537. return false;
  538. }
  539. Gtk.TreePath sort_path = _tree_sort.convert_child_path_to_path(filter_path);
  540. if (sort_path == null) {
  541. // The path is not valid.
  542. return false;
  543. }
  544. if (expanded)
  545. _tree_view.expand_to_path(sort_path);
  546. else
  547. _tree_view.collapse_row(sort_path);
  548. if (selected)
  549. _tree_view.get_selection().select_path(sort_path);
  550. return false; // Continue iterating.
  551. }
  552. public void make_visible(bool visible)
  553. {
  554. _tree_store.foreach((model, path, iter) => {
  555. _tree_store.set(iter, Column.VISIBLE, visible, -1);
  556. return false; // Continue iterating.
  557. });
  558. }
  559. public void filter(string needle)
  560. {
  561. make_visible(false);
  562. _tree_filter.refilter();
  563. _tree_store.foreach((model, path, iter) => {
  564. int type;
  565. string name;
  566. Value val;
  567. model.get_value(iter, Column.TYPE, out val);
  568. type = (int)val;
  569. bool visible = false;
  570. // Always show the roots.
  571. if (type == ItemType.FOLDER) {
  572. visible = true;
  573. } else {
  574. model.get_value(iter, Column.NAME, out val);
  575. name = (string)val;
  576. visible = needle == "" || name.down().index_of(needle) > -1;
  577. }
  578. if (visible) {
  579. // Make this iter and all its ancestors visible.
  580. Gtk.TreeIter it = iter;
  581. _tree_store.set(it, Column.VISIBLE, true, -1);
  582. while (_tree_store.iter_parent(out it, it))
  583. _tree_store.set(it, Column.VISIBLE, true, -1);
  584. }
  585. return false; // Continue iterating.
  586. });
  587. _tree_view.expand_all();
  588. }
  589. public void on_search_started()
  590. {
  591. _tree_selection.changed.disconnect(on_tree_selection_changed);
  592. // Save the current tree state (expanded branches + selection)
  593. // to restore it later when the search is done.
  594. _tree_store.foreach(save_tree_state);
  595. filter(_needle);
  596. _tree_selection.changed.connect(on_tree_selection_changed);
  597. }
  598. public void on_search_changed()
  599. {
  600. _tree_selection.changed.disconnect(on_tree_selection_changed);
  601. filter(_needle);
  602. _tree_selection.changed.connect(on_tree_selection_changed);
  603. }
  604. public void on_search_stopped()
  605. {
  606. // Only restore the old selection if it has not been
  607. // modified while searching (i.e. nothing is selected
  608. // because entering search clears it).
  609. Gtk.TreeModel selected_model;
  610. GLib.List<Gtk.TreePath> selected_rows = _tree_view.get_selection().get_selected_rows(out selected_model);
  611. Gtk.TreeRowReference[] selected_refs = {};
  612. for (uint i = 0, n = selected_rows.length(); i < n; ++i)
  613. selected_refs += new Gtk.TreeRowReference(selected_model, selected_rows.nth(i).data);
  614. _tree_selection.changed.disconnect(on_tree_selection_changed);
  615. make_visible(true);
  616. _tree_filter.refilter();
  617. // Restore the previous tree state (old expanded branches + old selection).
  618. _tree_view.get_selection().unselect_all();
  619. _tree_store.foreach(restore_tree_state);
  620. _tree_selection.changed.connect(on_tree_selection_changed);
  621. // If the selection changed while searching, restore it as well.
  622. for (int i = 0; i < selected_refs.length; ++i) {
  623. Gtk.TreePath path = selected_refs[i].get_path();
  624. _tree_view.expand_to_path(path);
  625. _tree_view.get_selection().select_path(path);
  626. _tree_view.scroll_to_cell(path, null, false, 0.0f, 0.0f);
  627. }
  628. }
  629. public void on_stop_search()
  630. {
  631. _filter_entry._entry.set_text("");
  632. }
  633. public void on_filter_entry_text_changed()
  634. {
  635. string old_needle = _needle;
  636. _needle = _filter_entry.text.strip().down();
  637. if (old_needle == "" && _needle != "") {
  638. on_search_started();
  639. } else if (old_needle != "" && _needle == "") {
  640. on_search_stopped();
  641. } else if (_needle != "") {
  642. on_search_changed();
  643. }
  644. }
  645. }
  646. } /* namespace Crown */