diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml index 51308519489..106bcf9d37a 100644 --- a/doc/classes/Tree.xml +++ b/doc/classes/Tree.xml @@ -357,6 +357,13 @@ Emitted when a cell is selected. + + + + + Emitted when [method TreeItem.propagate_check] is called. Connect to this signal to process the items that are affected when [method TreeItem.propagate_check] is invoked. The order that the items affected will be processed is as follows: the item that invoked the method, children of that item, and finally parents of that item. + + diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml index 7da98788bb5..0090fb555f8 100644 --- a/doc/classes/TreeItem.xml +++ b/doc/classes/TreeItem.xml @@ -385,6 +385,14 @@ [b]Note:[/b] You can't move to the root or move the root. + + + + + + Propagates this item's checked status to its children and parents for the given [code]column[/code]. It is possible to process the items affected by this method call by connecting to [signal Tree.check_propagated_to_item]. The order that the items affected will be processed is as follows: the item invoking this method, children of that item, and finally parents of that item. If [code]emit_signal[/code] is set to false, then [signal Tree.check_propagated_to_item] will not be emitted. + + diff --git a/editor/editor_asset_installer.cpp b/editor/editor_asset_installer.cpp index b89b20b8cdc..76c08111661 100644 --- a/editor/editor_asset_installer.cpp +++ b/editor/editor_asset_installer.cpp @@ -36,45 +36,6 @@ #include "editor_node.h" #include "progress_dialog.h" -void EditorAssetInstaller::_update_subitems(TreeItem *p_item, bool p_check, bool p_first) { - if (p_check) { - if (p_item->get_custom_color(0) == Color()) { - p_item->set_checked(0, true); - } - } else { - p_item->set_checked(0, false); - } - - if (p_item->get_first_child()) { - _update_subitems(p_item->get_first_child(), p_check); - } - - if (!p_first && p_item->get_next()) { - _update_subitems(p_item->get_next(), p_check); - } -} - -void EditorAssetInstaller::_uncheck_parent(TreeItem *p_item) { - if (!p_item) { - return; - } - - bool any_checked = false; - TreeItem *item = p_item->get_first_child(); - while (item) { - if (item->is_checked(0)) { - any_checked = true; - break; - } - item = item->get_next(); - } - - if (!any_checked) { - p_item->set_checked(0, false); - _uncheck_parent(p_item->get_parent()); - } -} - void EditorAssetInstaller::_item_edited() { if (updating) { return; @@ -85,24 +46,19 @@ void EditorAssetInstaller::_item_edited() { return; } - String path = item->get_metadata(0); - updating = true; - if (path.is_empty() || item == tree->get_root()) { //a dir or root - _update_subitems(item, item->is_checked(0), true); - } - - if (item->is_checked(0)) { - while (item) { - item->set_checked(0, true); - item = item->get_parent(); - } - } else { - _uncheck_parent(item->get_parent()); - } + item->propagate_check(0); updating = false; } +void EditorAssetInstaller::_check_propagated_to_item(Object *p_obj, int column) { + TreeItem *affected_item = Object::cast_to(p_obj); + if (affected_item && affected_item->get_custom_color(0) != Color()) { + affected_item->set_checked(0, false); + affected_item->propagate_check(0, false); + } +} + void EditorAssetInstaller::open(const String &p_path, int p_depth) { package_path = p_path; Set files_sorted; @@ -259,6 +215,7 @@ void EditorAssetInstaller::open(const String &p_path, int p_depth) { ti->set_custom_color(0, tree->get_theme_color(SNAME("error_color"), SNAME("Editor"))); ti->set_tooltip(0, vformat(TTR("%s (already exists)"), res_path)); ti->set_checked(0, false); + ti->propagate_check(0); } else { ti->set_tooltip(0, res_path); } @@ -304,7 +261,7 @@ void EditorAssetInstaller::ok_pressed() { String name = String::utf8(fname); - if (status_map.has(name) && status_map[name]->is_checked(0)) { + if (status_map.has(name) && (status_map[name]->is_checked(0) || status_map[name]->is_indeterminate(0))) { String path = status_map[name]->get_metadata(0); if (path.is_empty()) { // a dir @@ -392,6 +349,7 @@ EditorAssetInstaller::EditorAssetInstaller() { tree = memnew(Tree); tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); tree->connect("item_edited", callable_mp(this, &EditorAssetInstaller::_item_edited)); + tree->connect("check_propagated_to_item", callable_mp(this, &EditorAssetInstaller::_check_propagated_to_item)); vb->add_child(tree); error = memnew(AcceptDialog); diff --git a/editor/editor_asset_installer.h b/editor/editor_asset_installer.h index 2f592509330..f5993f73e78 100644 --- a/editor/editor_asset_installer.h +++ b/editor/editor_asset_installer.h @@ -43,9 +43,8 @@ class EditorAssetInstaller : public ConfirmationDialog { AcceptDialog *error; Map status_map; bool updating; - void _update_subitems(TreeItem *p_item, bool p_check, bool p_first = false); - void _uncheck_parent(TreeItem *p_item); void _item_edited(); + void _check_propagated_to_item(Object *p_obj, int column); virtual void ok_pressed() override; protected: diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 91c17399c2c..f95b2b40b57 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -81,8 +81,6 @@ void ThemeItemImportTree::_update_items_tree() { bool is_matching_filter = (filter_text.is_empty() || type_name.findn(filter_text) > -1); bool has_filtered_items = false; - bool any_checked = false; - bool any_checked_with_data = false; for (int i = 0; i < Theme::DATA_TYPE_MAX; i++) { Theme::DataType dt = (Theme::DataType)i; @@ -178,9 +176,6 @@ void ThemeItemImportTree::_update_items_tree() { break; // Can't happen, but silences warning. } - bool data_type_any_checked = false; - bool data_type_any_checked_with_data = false; - filtered_names.sort_custom(); for (const StringName &F : filtered_names) { TreeItem *item_node = import_items_tree->create_item(data_type_node); @@ -194,20 +189,11 @@ void ThemeItemImportTree::_update_items_tree() { item_node->set_editable(IMPORT_ITEM_DATA, true); _restore_selected_item(item_node); - if (item_node->is_checked(IMPORT_ITEM)) { - data_type_any_checked = true; - any_checked = true; - } - if (item_node->is_checked(IMPORT_ITEM_DATA)) { - data_type_any_checked_with_data = true; - any_checked_with_data = true; - } + item_node->propagate_check(IMPORT_ITEM, false); + item_node->propagate_check(IMPORT_ITEM_DATA, false); item_list->push_back(item_node); } - - data_type_node->set_checked(IMPORT_ITEM, data_type_any_checked); - data_type_node->set_checked(IMPORT_ITEM_DATA, data_type_any_checked && data_type_any_checked_with_data); } // Remove the item if it doesn't match the filter in any way. @@ -221,9 +207,6 @@ void ThemeItemImportTree::_update_items_tree() { if (!filter_text.is_empty() && has_filtered_items) { type_node->set_collapsed(false); } - - type_node->set_checked(IMPORT_ITEM, any_checked); - type_node->set_checked(IMPORT_ITEM_DATA, any_checked && any_checked_with_data); } if (color_amount > 0) { @@ -471,23 +454,26 @@ void ThemeItemImportTree::_tree_item_edited() { if (is_checked) { if (edited_column == IMPORT_ITEM_DATA) { edited_item->set_checked(IMPORT_ITEM, true); + edited_item->propagate_check(IMPORT_ITEM); } - - _select_all_subitems(edited_item, (edited_column == IMPORT_ITEM_DATA)); } else { if (edited_column == IMPORT_ITEM) { edited_item->set_checked(IMPORT_ITEM_DATA, false); + edited_item->propagate_check(IMPORT_ITEM_DATA); } - - _deselect_all_subitems(edited_item, (edited_column == IMPORT_ITEM)); } - - _update_parent_items(edited_item); - _store_selected_item(edited_item); - + edited_item->propagate_check(edited_column); updating_tree = false; } +void ThemeItemImportTree::_check_propagated_to_tree_item(Object *p_obj, int p_column) { + TreeItem *item = Object::cast_to(p_obj); + // Skip "category" tree items by checking for children. + if (item && !item->get_first_child()) { + _store_selected_item(item); + } +} + void ThemeItemImportTree::_select_all_subitems(TreeItem *p_root_item, bool p_select_with_data) { TreeItem *child_item = p_root_item->get_first_child(); while (child_item) { @@ -516,32 +502,6 @@ void ThemeItemImportTree::_deselect_all_subitems(TreeItem *p_root_item, bool p_d } } -void ThemeItemImportTree::_update_parent_items(TreeItem *p_root_item) { - TreeItem *parent_item = p_root_item->get_parent(); - if (!parent_item) { - return; - } - - bool any_checked = false; - bool any_checked_with_data = false; - - TreeItem *child_item = parent_item->get_first_child(); - while (child_item) { - if (child_item->is_checked(IMPORT_ITEM)) { - any_checked = true; - } - if (child_item->is_checked(IMPORT_ITEM_DATA)) { - any_checked_with_data = true; - } - - child_item = child_item->get_next(); - } - - parent_item->set_checked(IMPORT_ITEM, any_checked); - parent_item->set_checked(IMPORT_ITEM_DATA, any_checked && any_checked_with_data); - _update_parent_items(parent_item); -} - void ThemeItemImportTree::_select_all_items_pressed() { if (updating_tree) { return; @@ -629,7 +589,7 @@ void ThemeItemImportTree::_select_all_data_type_pressed(int p_data_type) { } child_item->set_checked(IMPORT_ITEM, true); - _update_parent_items(child_item); + child_item->propagate_check(IMPORT_ITEM, false); _store_selected_item(child_item); } @@ -685,7 +645,8 @@ void ThemeItemImportTree::_select_full_data_type_pressed(int p_data_type) { child_item->set_checked(IMPORT_ITEM, true); child_item->set_checked(IMPORT_ITEM_DATA, true); - _update_parent_items(child_item); + child_item->propagate_check(IMPORT_ITEM, false); + child_item->propagate_check(IMPORT_ITEM_DATA, false); _store_selected_item(child_item); } @@ -741,7 +702,8 @@ void ThemeItemImportTree::_deselect_all_data_type_pressed(int p_data_type) { child_item->set_checked(IMPORT_ITEM, false); child_item->set_checked(IMPORT_ITEM_DATA, false); - _update_parent_items(child_item); + child_item->propagate_check(IMPORT_ITEM, false); + child_item->propagate_check(IMPORT_ITEM_DATA, false); _store_selected_item(child_item); } @@ -937,6 +899,7 @@ ThemeItemImportTree::ThemeItemImportTree() { import_items_tree->set_h_size_flags(Control::SIZE_EXPAND_FILL); import_main_hb->add_child(import_items_tree); import_items_tree->connect("item_edited", callable_mp(this, &ThemeItemImportTree::_tree_item_edited)); + import_items_tree->connect("check_propagated_to_item", callable_mp(this, &ThemeItemImportTree::_check_propagated_to_tree_item)); import_items_tree->set_columns(3); import_items_tree->set_column_titles_visible(true); diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h index 4c6b16a68c1..c00ce3ae650 100644 --- a/editor/plugins/theme_editor_plugin.h +++ b/editor/plugins/theme_editor_plugin.h @@ -149,9 +149,9 @@ class ThemeItemImportTree : public VBoxContainer { void _update_total_selected(Theme::DataType p_data_type); void _tree_item_edited(); + void _check_propagated_to_tree_item(Object *p_obj, int p_column); void _select_all_subitems(TreeItem *p_root_item, bool p_select_with_data); void _deselect_all_subitems(TreeItem *p_root_item, bool p_deselect_completely); - void _update_parent_items(TreeItem *p_root_item); void _select_all_items_pressed(); void _select_full_items_pressed(); diff --git a/editor/project_export.cpp b/editor/project_export.cpp index 9bd8c1e2271..f39a494df80 100644 --- a/editor/project_export.cpp +++ b/editor/project_export.cpp @@ -752,12 +752,10 @@ bool ProjectExportDialog::_fill_tree(EditorFileSystemDirectory *p_dir, TreeItem p_item->set_metadata(0, p_dir->get_path()); bool used = false; - bool checked = true; for (int i = 0; i < p_dir->get_subdir_count(); i++) { TreeItem *subdir = include_files->create_item(p_item); if (_fill_tree(p_dir->get_subdir(i), subdir, current, p_only_scenes)) { used = true; - checked = checked && subdir->is_checked(0); } else { memdelete(subdir); } @@ -782,12 +780,10 @@ bool ProjectExportDialog::_fill_tree(EditorFileSystemDirectory *p_dir, TreeItem file->set_editable(0, true); file->set_checked(0, current->has_export_file(path)); file->set_metadata(0, path); - checked = checked && file->is_checked(0); + file->propagate_check(0); used = true; } - - p_item->set_checked(0, checked); return used; } @@ -806,54 +802,24 @@ void ProjectExportDialog::_tree_changed() { return; } - String path = item->get_metadata(0); - bool added = item->is_checked(0); + item->propagate_check(0); +} - if (path.ends_with("/")) { - _check_dir_recursive(item, added); - } else { +void ProjectExportDialog::_check_propagated_to_item(Object *p_obj, int column) { + Ref current = get_current_preset(); + if (current.is_null()) { + return; + } + TreeItem *item = Object::cast_to(p_obj); + String path = item->get_metadata(0); + if (item && !path.ends_with("/")) { + bool added = item->is_checked(0); if (added) { current->add_export_file(path); } else { current->remove_export_file(path); } } - _refresh_parent_checks(item); // Makes parent folder checked if all files/folders are checked. -} - -void ProjectExportDialog::_check_dir_recursive(TreeItem *p_dir, bool p_checked) { - for (TreeItem *child = p_dir->get_first_child(); child; child = child->get_next()) { - String path = child->get_metadata(0); - - child->set_checked(0, p_checked); - if (path.ends_with("/")) { - _check_dir_recursive(child, p_checked); - } else { - if (p_checked) { - get_current_preset()->add_export_file(path); - } else { - get_current_preset()->remove_export_file(path); - } - } - } -} - -void ProjectExportDialog::_refresh_parent_checks(TreeItem *p_item) { - TreeItem *parent = p_item->get_parent(); - if (!parent) { - return; - } - - bool checked = true; - for (TreeItem *child = parent->get_first_child(); child; child = child->get_next()) { - checked = checked && child->is_checked(0); - if (!checked) { - break; - } - } - parent->set_checked(0, checked); - - _refresh_parent_checks(parent); } void ProjectExportDialog::_export_pck_zip() { @@ -1126,6 +1092,7 @@ ProjectExportDialog::ProjectExportDialog() { include_files = memnew(Tree); include_margin->add_child(include_files); include_files->connect("item_edited", callable_mp(this, &ProjectExportDialog::_tree_changed)); + include_files->connect("check_propagated_to_item", callable_mp(this, &ProjectExportDialog::_check_propagated_to_item)); include_filters = memnew(LineEdit); resources_vb->add_margin_child( diff --git a/editor/project_export.h b/editor/project_export.h index af7ec083c46..3d90a0d3ff4 100644 --- a/editor/project_export.h +++ b/editor/project_export.h @@ -124,8 +124,7 @@ private: void _fill_resource_tree(); bool _fill_tree(EditorFileSystemDirectory *p_dir, TreeItem *p_item, Ref ¤t, bool p_only_scenes); void _tree_changed(); - void _check_dir_recursive(TreeItem *p_dir, bool p_checked); - void _refresh_parent_checks(TreeItem *p_item); + void _check_propagated_to_item(Object *p_obj, int column); Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index e46de43f1e6..e75d147134e 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -198,6 +198,65 @@ bool TreeItem::is_indeterminate(int p_column) const { return cells[p_column].indeterminate; } +void TreeItem::propagate_check(int p_column, bool p_emit_signal) { + bool ch = cells[p_column].checked; + + if (p_emit_signal) { + tree->emit_signal("check_propagated_to_item", this, p_column); + } + _propagate_check_through_children(p_column, ch, p_emit_signal); + _propagate_check_through_parents(p_column, p_emit_signal); +} + +void TreeItem::_propagate_check_through_children(int p_column, bool p_checked, bool p_emit_signal) { + TreeItem *current = get_first_child(); + while (current) { + current->set_checked(p_column, p_checked); + if (p_emit_signal) { + current->tree->emit_signal("check_propagated_to_item", current, p_column); + } + current->_propagate_check_through_children(p_column, p_checked, p_emit_signal); + current = current->get_next(); + } +} + +void TreeItem::_propagate_check_through_parents(int p_column, bool p_emit_signal) { + TreeItem *current = get_parent(); + if (!current) { + return; + } + + bool all_unchecked_and_not_indeterminate = true; + bool any_unchecked_or_indeterminate = false; + + TreeItem *child_item = current->get_first_child(); + while (child_item) { + if (!child_item->is_checked(p_column)) { + any_unchecked_or_indeterminate = true; + if (child_item->is_indeterminate(p_column)) { + all_unchecked_and_not_indeterminate = false; + break; + } + } else { + all_unchecked_and_not_indeterminate = false; + } + child_item = child_item->get_next(); + } + + if (all_unchecked_and_not_indeterminate) { + current->set_checked(p_column, false); + } else if (any_unchecked_or_indeterminate) { + current->set_indeterminate(p_column, true); + } else { + current->set_checked(p_column, true); + } + + if (p_emit_signal) { + current->tree->emit_signal("check_propagated_to_item", current, p_column); + } + current->_propagate_check_through_parents(p_column, p_emit_signal); +} + void TreeItem::set_text(int p_column, String p_text) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].text = p_text; @@ -1141,6 +1200,8 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("is_checked", "column"), &TreeItem::is_checked); ClassDB::bind_method(D_METHOD("is_indeterminate", "column"), &TreeItem::is_indeterminate); + ClassDB::bind_method(D_METHOD("propagate_check", "column", "emit_signal"), &TreeItem::propagate_check, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("set_text", "column", "text"), &TreeItem::set_text); ClassDB::bind_method(D_METHOD("get_text", "column"), &TreeItem::get_text); @@ -4847,6 +4908,7 @@ void Tree::_bind_methods() { ADD_SIGNAL(MethodInfo("item_custom_button_pressed")); ADD_SIGNAL(MethodInfo("item_double_clicked")); ADD_SIGNAL(MethodInfo("item_collapsed", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"))); + ADD_SIGNAL(MethodInfo("check_propagated_to_item", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"))); //ADD_SIGNAL( MethodInfo("item_double_clicked" ) ); ADD_SIGNAL(MethodInfo("button_pressed", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"), PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("custom_popup_edited", PropertyInfo(Variant::BOOL, "arrow_clicked"))); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index c60c87564e2..33170cad351 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -212,6 +212,14 @@ public: bool is_checked(int p_column) const; bool is_indeterminate(int p_column) const; + void propagate_check(int p_column, bool p_emit_signal = true); + +private: + // Check helpers. + void _propagate_check_through_children(int p_column, bool p_checked, bool p_emit_signal); + void _propagate_check_through_parents(int p_column, bool p_emit_signal); + +public: void set_text(int p_column, String p_text); String get_text(int p_column) const;