Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors/space_outliner/outliner_collections.c')
-rw-r--r--source/blender/editors/space_outliner/outliner_collections.c901
1 files changed, 901 insertions, 0 deletions
diff --git a/source/blender/editors/space_outliner/outliner_collections.c b/source/blender/editors/space_outliner/outliner_collections.c
new file mode 100644
index 00000000000..89f1240f86d
--- /dev/null
+++ b/source/blender/editors/space_outliner/outliner_collections.c
@@ -0,0 +1,901 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Contributor(s): Blender Foundation, Dalai Felinto
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/space_outliner/outliner_collections.c
+ * \ingroup spoutliner
+ */
+
+#include "BLI_utildefines.h"
+#include "BLI_listbase.h"
+
+#include "BKE_context.h"
+#include "BKE_collection.h"
+#include "BKE_layer.h"
+#include "BKE_main.h"
+#include "BKE_report.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_build.h"
+
+#include "DNA_group_types.h"
+
+#include "ED_screen.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+#include "RNA_enum_types.h"
+
+#include "UI_resources.h"
+
+#include "outliner_intern.h" /* own include */
+
+/* Prototypes. */
+static int collection_delete_exec(struct bContext *C, struct wmOperator *op);
+
+/* -------------------------------------------------------------------- */
+
+static LayerCollection *outliner_collection_active(bContext *C)
+{
+ return CTX_data_layer_collection(C);
+}
+
+SceneCollection *outliner_scene_collection_from_tree_element(TreeElement *te)
+{
+ TreeStoreElem *tselem = TREESTORE(te);
+
+ if (tselem->type == TSE_SCENE_COLLECTION) {
+ return te->directdata;
+ }
+ else if (tselem->type == TSE_LAYER_COLLECTION) {
+ LayerCollection *lc = te->directdata;
+ return lc->scene_collection;
+ }
+
+ return NULL;
+}
+
+/* -------------------------------------------------------------------- */
+/* Poll functions. */
+
+static int collections_editor_poll(bContext *C)
+{
+ SpaceOops *so = CTX_wm_space_outliner(C);
+ return (so != NULL) && (so->outlinevis == SO_COLLECTIONS);
+}
+
+static int view_layer_editor_poll(bContext *C)
+{
+ SpaceOops *so = CTX_wm_space_outliner(C);
+ return (so != NULL) && (so->outlinevis == SO_VIEW_LAYER);
+}
+
+static int outliner_either_collection_editor_poll(bContext *C)
+{
+ SpaceOops *so = CTX_wm_space_outliner(C);
+ return (so != NULL) && (ELEM(so->outlinevis, SO_VIEW_LAYER, SO_COLLECTIONS));
+}
+
+/* -------------------------------------------------------------------- */
+/* collection manager operators */
+
+/**
+ * Recursively get the collection for a given index
+ */
+static SceneCollection *scene_collection_from_index(ListBase *lb, const int number, int *i)
+{
+ for (SceneCollection *sc = lb->first; sc; sc = sc->next) {
+ if (*i == number) {
+ return sc;
+ }
+
+ (*i)++;
+
+ SceneCollection *sc_nested = scene_collection_from_index(&sc->scene_collections, number, i);
+ if (sc_nested) {
+ return sc_nested;
+ }
+ }
+ return NULL;
+}
+
+typedef struct TreeElementFindData {
+ SceneCollection *collection;
+ TreeElement *r_result_te;
+} TreeElementFindData;
+
+static TreeTraversalAction tree_element_find_by_scene_collection_cb(TreeElement *te, void *customdata)
+{
+ TreeElementFindData *data = customdata;
+ const SceneCollection *current_element_sc = outliner_scene_collection_from_tree_element(te);
+
+ if (current_element_sc == data->collection) {
+ data->r_result_te = te;
+ return TRAVERSE_BREAK;
+ }
+
+ return TRAVERSE_CONTINUE;
+}
+
+static TreeElement *outliner_tree_element_from_layer_collection_index(
+ SpaceOops *soops, ViewLayer *view_layer,
+ const int index)
+{
+ LayerCollection *lc = BKE_layer_collection_from_index(view_layer, index);
+
+ if (lc == NULL) {
+ return NULL;
+ }
+
+ /* Find the tree element containing the LayerCollection's scene_collection. */
+ TreeElementFindData data = {
+ .collection = lc->scene_collection,
+ .r_result_te = NULL,
+ };
+ outliner_tree_traverse(soops, &soops->tree, 0, 0, tree_element_find_by_scene_collection_cb, &data);
+
+ return data.r_result_te;
+}
+
+static int collection_link_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene = CTX_data_scene(C);
+ ViewLayer *view_layer = CTX_data_view_layer(C);
+ SceneCollection *sc_master = BKE_collection_master(&scene->id);
+ SceneCollection *sc;
+
+ int scene_collection_index = RNA_enum_get(op->ptr, "scene_collection");
+ if (scene_collection_index == 0) {
+ sc = sc_master;
+ }
+ else {
+ int index = 1;
+ sc = scene_collection_from_index(&sc_master->scene_collections, scene_collection_index, &index);
+ BLI_assert(sc);
+ }
+
+ BKE_collection_link(view_layer, sc);
+
+ DEG_relations_tag_update(CTX_data_main(C));
+
+ /* TODO(sergey): Use proper flag for tagging here. */
+ DEG_id_tag_update(&scene->id, 0);
+
+ WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL);
+ return OPERATOR_FINISHED;
+}
+
+static int collection_link_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ Scene *scene = CTX_data_scene(C);
+ SceneCollection *master_collection = BKE_collection_master(&scene->id);
+ if (master_collection->scene_collections.first == NULL) {
+ RNA_enum_set(op->ptr, "scene_collection", 0);
+ return collection_link_exec(C, op);
+ }
+ else {
+ return WM_enum_search_invoke(C, op, event);
+ }
+}
+
+static void collection_scene_collection_itemf_recursive(
+ EnumPropertyItem *tmp, EnumPropertyItem **item, int *totitem, int *value, SceneCollection *sc)
+{
+ tmp->value = *value;
+ tmp->icon = ICON_COLLAPSEMENU;
+ tmp->identifier = sc->name;
+ tmp->name = sc->name;
+ RNA_enum_item_add(item, totitem, tmp);
+
+ (*value)++;
+
+ for (SceneCollection *ncs = sc->scene_collections.first; ncs; ncs = ncs->next) {
+ collection_scene_collection_itemf_recursive(tmp, item, totitem, value, ncs);
+ }
+}
+
+static const EnumPropertyItem *collection_scene_collection_itemf(
+ bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free)
+{
+ EnumPropertyItem tmp = {0, "", 0, "", ""};
+ EnumPropertyItem *item = NULL;
+ int value = 0, totitem = 0;
+
+ Scene *scene = CTX_data_scene(C);
+ SceneCollection *sc = BKE_collection_master(&scene->id);
+
+ collection_scene_collection_itemf_recursive(&tmp, &item, &totitem, &value, sc);
+ RNA_enum_item_end(&item, &totitem);
+ *r_free = true;
+
+ return item;
+}
+
+void OUTLINER_OT_collection_link(wmOperatorType *ot)
+{
+ PropertyRNA *prop;
+
+ /* identifiers */
+ ot->name = "Link Collection";
+ ot->idname = "OUTLINER_OT_collection_link";
+ ot->description = "Link a new collection to the active layer";
+
+ /* api callbacks */
+ ot->exec = collection_link_exec;
+ ot->invoke = collection_link_invoke;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ prop = RNA_def_enum(ot->srna, "scene_collection", DummyRNA_NULL_items, 0, "Scene Collection", "");
+ RNA_def_enum_funcs(prop, collection_scene_collection_itemf);
+ RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE);
+ ot->prop = prop;
+}
+
+/**
+ * Returns true if selected element is a collection directly
+ * linked to the active ViewLayer (not a nested collection)
+ */
+static int collection_unlink_poll(bContext *C)
+{
+ if (view_layer_editor_poll(C) == 0) {
+ return 0;
+ }
+
+ LayerCollection *lc = outliner_collection_active(C);
+
+ if (lc == NULL) {
+ return 0;
+ }
+
+ ViewLayer *view_layer = CTX_data_view_layer(C);
+ return BLI_findindex(&view_layer->layer_collections, lc) != -1 ? 1 : 0;
+}
+
+static int collection_unlink_exec(bContext *C, wmOperator *op)
+{
+ LayerCollection *lc = outliner_collection_active(C);
+ SpaceOops *soops = CTX_wm_space_outliner(C);
+
+ if (lc == NULL) {
+ BKE_report(op->reports, RPT_ERROR, "Active element is not a collection");
+ return OPERATOR_CANCELLED;
+ }
+
+ ViewLayer *view_layer = CTX_data_view_layer(C);
+ BKE_collection_unlink(view_layer, lc);
+
+ if (soops) {
+ outliner_cleanup_tree(soops);
+ }
+
+ DEG_relations_tag_update(CTX_data_main(C));
+
+ /* TODO(sergey): Use proper flag for tagging here. */
+ DEG_id_tag_update(&CTX_data_scene(C)->id, 0);
+
+ WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL);
+ return OPERATOR_FINISHED;
+}
+
+void OUTLINER_OT_collection_unlink(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Unlink Collection";
+ ot->idname = "OUTLINER_OT_collection_unlink";
+ ot->description = "Unlink collection from the active layer";
+
+ /* api callbacks */
+ ot->exec = collection_unlink_exec;
+ ot->poll = collection_unlink_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/**********************************************************************************/
+/* Add new collection. */
+
+static int collection_new_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
+ ViewLayer *view_layer = CTX_data_view_layer(C);
+ SceneCollection *scene_collection = BKE_collection_add(&scene->id, NULL, COLLECTION_TYPE_NONE, NULL);
+ BKE_collection_link(view_layer, scene_collection);
+
+ DEG_relations_tag_update(bmain);
+ WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL);
+ return OPERATOR_FINISHED;
+}
+
+void OUTLINER_OT_collection_new(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "New Collection";
+ ot->idname = "OUTLINER_OT_collection_new";
+ ot->description = "Add a new collection to the scene";
+
+ /* api callbacks */
+ ot->exec = collection_new_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/**********************************************************************************/
+/* Add new nested collection. */
+
+struct CollectionNewData
+{
+ bool error;
+ SceneCollection *scene_collection;
+};
+
+static TreeTraversalAction collection_find_selected_to_add(TreeElement *te, void *customdata)
+{
+ struct CollectionNewData *data = customdata;
+ SceneCollection *scene_collection = outliner_scene_collection_from_tree_element(te);
+
+ if (!scene_collection) {
+ return TRAVERSE_SKIP_CHILDS;
+ }
+
+ if (data->scene_collection != NULL) {
+ data->error = true;
+ return TRAVERSE_BREAK;
+ }
+
+ data->scene_collection = scene_collection;
+ return TRAVERSE_CONTINUE;
+}
+
+static int collection_nested_new_exec(bContext *C, wmOperator *op)
+{
+ SpaceOops *soops = CTX_wm_space_outliner(C);
+ Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
+
+ struct CollectionNewData data = {
+ .error = false,
+ .scene_collection = NULL,
+ };
+
+ outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, collection_find_selected_to_add, &data);
+
+ if (data.error) {
+ BKE_report(op->reports, RPT_ERROR, "More than one collection is selected");
+ return OPERATOR_CANCELLED;
+ }
+
+ BKE_collection_add(
+ &scene->id,
+ data.scene_collection,
+ COLLECTION_TYPE_NONE,
+ NULL);
+
+ outliner_cleanup_tree(soops);
+ DEG_relations_tag_update(bmain);
+ WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL);
+ return OPERATOR_FINISHED;
+}
+
+void OUTLINER_OT_collection_nested_new(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "New Nested Collection";
+ ot->idname = "OUTLINER_OT_collection_nested_new";
+ ot->description = "Add a new collection inside selected collection";
+
+ /* api callbacks */
+ ot->exec = collection_nested_new_exec;
+ ot->poll = collections_editor_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/**********************************************************************************/
+/* Delete selected collection. */
+
+void OUTLINER_OT_collection_delete_selected(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Delete Selected Collections";
+ ot->idname = "OUTLINER_OT_collection_delete_selected";
+ ot->description = "Delete all the selected collections";
+
+ /* api callbacks */
+ ot->exec = collection_delete_exec;
+ ot->poll = collections_editor_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/**********************************************************************************/
+/* Add new selected objects. */
+
+struct SceneCollectionSelectedData {
+ ListBase scene_collections_array;
+};
+
+static TreeTraversalAction collection_find_selected_scene_collections(TreeElement *te, void *customdata)
+{
+ struct SceneCollectionSelectedData *data = customdata;
+ SceneCollection *scene_collection = outliner_scene_collection_from_tree_element(te);
+
+ if (!scene_collection) {
+ return TRAVERSE_SKIP_CHILDS;
+ }
+
+ BLI_addtail(&data->scene_collections_array, BLI_genericNodeN(scene_collection));
+ return TRAVERSE_CONTINUE;
+}
+
+static int collection_objects_add_exec(bContext *C, wmOperator *op)
+{
+ SpaceOops *soops = CTX_wm_space_outliner(C);
+ Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
+
+ struct SceneCollectionSelectedData data = {
+ .scene_collections_array = {NULL, NULL},
+ };
+
+ outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, collection_find_selected_scene_collections, &data);
+
+ if (BLI_listbase_is_empty(&data.scene_collections_array)) {
+ BKE_report(op->reports, RPT_ERROR, "No collection is selected");
+ return OPERATOR_CANCELLED;
+ }
+
+ CTX_DATA_BEGIN (C, struct Object *, ob, selected_objects)
+ {
+ BLI_LISTBASE_FOREACH (LinkData *, link, &data.scene_collections_array) {
+ SceneCollection *scene_collection = link->data;
+ BKE_collection_object_add(
+ &scene->id,
+ scene_collection,
+ ob);
+ }
+ }
+ CTX_DATA_END;
+ BLI_freelistN(&data.scene_collections_array);
+
+ outliner_cleanup_tree(soops);
+ DEG_relations_tag_update(bmain);
+ WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL);
+ return OPERATOR_FINISHED;
+}
+
+void OUTLINER_OT_collection_objects_add(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Add Objects";
+ ot->idname = "OUTLINER_OT_collection_objects_add";
+ ot->description = "Add selected objects to collection";
+
+ /* api callbacks */
+ ot->exec = collection_objects_add_exec;
+ ot->poll = collections_editor_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/**********************************************************************************/
+/* Remove selected objects. */
+
+
+static int collection_objects_remove_exec(bContext *C, wmOperator *op)
+{
+ SpaceOops *soops = CTX_wm_space_outliner(C);
+ Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
+
+ struct SceneCollectionSelectedData data = {
+ .scene_collections_array = {NULL, NULL},
+ };
+
+ outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, collection_find_selected_scene_collections, &data);
+
+ if (BLI_listbase_is_empty(&data.scene_collections_array)) {
+ BKE_report(op->reports, RPT_ERROR, "No collection is selected");
+ return OPERATOR_CANCELLED;
+ }
+
+ CTX_DATA_BEGIN (C, struct Object *, ob, selected_objects)
+ {
+ BLI_LISTBASE_FOREACH (LinkData *, link, &data.scene_collections_array) {
+ SceneCollection *scene_collection = link->data;
+ BKE_collection_object_remove(
+ bmain,
+ &scene->id,
+ scene_collection,
+ ob,
+ true);
+ }
+ }
+ CTX_DATA_END;
+ BLI_freelistN(&data.scene_collections_array);
+
+ outliner_cleanup_tree(soops);
+ DEG_relations_tag_update(bmain);
+ WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL);
+ return OPERATOR_FINISHED;
+}
+
+void OUTLINER_OT_collection_objects_remove(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Remove Objects";
+ ot->idname = "OUTLINER_OT_collection_objects_remove";
+ ot->description = "Remove selected objects from collection";
+
+ /* api callbacks */
+ ot->exec = collection_objects_remove_exec;
+ ot->poll = collections_editor_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+struct CollectionDeleteData {
+ Scene *scene;
+ SpaceOops *soops;
+ GSet *collections_to_delete;
+};
+
+static TreeTraversalAction collection_find_data_to_delete(TreeElement *te, void *customdata)
+{
+ struct CollectionDeleteData *data = customdata;
+ SceneCollection *scene_collection = outliner_scene_collection_from_tree_element(te);
+
+ if (!scene_collection) {
+ return TRAVERSE_SKIP_CHILDS;
+ }
+
+ if (scene_collection == BKE_collection_master(&data->scene->id)) {
+ /* skip - showing warning/error message might be missleading
+ * when deleting multiple collections, so just do nothing */
+ }
+ else {
+ BLI_gset_add(data->collections_to_delete, scene_collection);
+ return TRAVERSE_SKIP_CHILDS; /* Childs will be gone anyway, no need to recurse deeper. */
+ }
+
+ return TRAVERSE_CONTINUE;
+}
+
+static TreeTraversalAction collection_delete_elements_from_collection(TreeElement *te, void *customdata)
+{
+ struct CollectionDeleteData *data = customdata;
+ SceneCollection *scene_collection = outliner_scene_collection_from_tree_element(te);
+
+ if (!scene_collection) {
+ return TRAVERSE_SKIP_CHILDS;
+ }
+
+ const bool will_be_deleted = BLI_gset_haskey(data->collections_to_delete, scene_collection);
+ if (will_be_deleted) {
+ outliner_free_tree_element(te, te->parent ? &te->parent->subtree : &data->soops->tree);
+ /* Childs are freed now, so don't recurse into them. */
+ return TRAVERSE_SKIP_CHILDS;
+ }
+
+ return TRAVERSE_CONTINUE;
+}
+
+static int collection_delete_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Scene *scene = CTX_data_scene(C);
+ SpaceOops *soops = CTX_wm_space_outliner(C);
+ struct CollectionDeleteData data = {.scene = scene, .soops = soops};
+
+ data.collections_to_delete = BLI_gset_ptr_new(__func__);
+
+ /* We first walk over and find the SceneCollections we actually want to delete (ignoring duplicates). */
+ outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, collection_find_data_to_delete, &data);
+
+ /* Now, delete all tree elements representing a collection that will be deleted. We'll look for a
+ * new element to select in a few lines, so we can't wait until the tree is recreated on redraw. */
+ outliner_tree_traverse(soops, &soops->tree, 0, 0, collection_delete_elements_from_collection, &data);
+
+ /* Effectively delete the collections. */
+ GSetIterator collections_to_delete_iter;
+ GSET_ITER(collections_to_delete_iter, data.collections_to_delete) {
+ SceneCollection *sc = BLI_gsetIterator_getKey(&collections_to_delete_iter);
+ BKE_collection_remove(&data.scene->id, sc);
+ }
+
+ BLI_gset_free(data.collections_to_delete, NULL);
+
+ TreeElement *select_te = outliner_tree_element_from_layer_collection_index(soops, CTX_data_view_layer(C), 0);
+ if (select_te) {
+ outliner_item_select(soops, select_te, false, false);
+ }
+
+ DEG_relations_tag_update(CTX_data_main(C));
+
+ /* TODO(sergey): Use proper flag for tagging here. */
+ DEG_id_tag_update(&scene->id, 0);
+
+ soops->storeflag |= SO_TREESTORE_REDRAW;
+ WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL);
+
+ return OPERATOR_FINISHED;
+}
+
+void OUTLINER_OT_collections_delete(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Delete";
+ ot->idname = "OUTLINER_OT_collections_delete";
+ ot->description = "Delete selected overrides or collections";
+
+ /* api callbacks */
+ ot->exec = collection_delete_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+static int collection_select_exec(bContext *C, wmOperator *op)
+{
+ ViewLayer *view_layer = CTX_data_view_layer(C);
+ const int collection_index = RNA_int_get(op->ptr, "collection_index");
+ view_layer->active_collection = collection_index;
+ WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL);
+ return OPERATOR_FINISHED;
+}
+
+void OUTLINER_OT_collection_select(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Select";
+ ot->idname = "OUTLINER_OT_collection_select";
+ ot->description = "Change active collection or override";
+
+ /* api callbacks */
+ ot->exec = collection_select_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ RNA_def_int(ot->srna, "collection_index", 0, 0, INT_MAX, "Index",
+ "Index of collection to select", 0, INT_MAX);
+}
+
+#define ACTION_DISABLE 0
+#define ACTION_ENABLE 1
+#define ACTION_TOGGLE 2
+
+static int collection_toggle_exec(bContext *C, wmOperator *op)
+{
+ Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
+ int action = RNA_enum_get(op->ptr, "action");
+ LayerCollection *layer_collection = CTX_data_layer_collection(C);
+
+ if (layer_collection->flag & COLLECTION_DISABLED) {
+ if (ELEM(action, ACTION_TOGGLE, ACTION_ENABLE)) {
+ layer_collection->flag &= ~COLLECTION_DISABLED;
+ }
+ else { /* ACTION_DISABLE */
+ BKE_reportf(op->reports, RPT_ERROR, "Layer collection %s already disabled",
+ layer_collection->scene_collection->name);
+ return OPERATOR_CANCELLED;
+ }
+ }
+ else {
+ if (ELEM(action, ACTION_TOGGLE, ACTION_DISABLE)) {
+ layer_collection->flag |= COLLECTION_DISABLED;
+ }
+ else { /* ACTION_ENABLE */
+ BKE_reportf(op->reports, RPT_ERROR, "Layer collection %s already enabled",
+ layer_collection->scene_collection->name);
+ return OPERATOR_CANCELLED;
+ }
+ }
+
+ DEG_relations_tag_update(bmain);
+ /* TODO(sergey): Use proper flag for tagging here. */
+ DEG_id_tag_update(&scene->id, 0);
+
+ WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
+ WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
+
+ return OPERATOR_FINISHED;
+}
+
+void OUTLINER_OT_collection_toggle(wmOperatorType *ot)
+{
+ PropertyRNA *prop;
+
+ static EnumPropertyItem actions_items[] = {
+ {ACTION_DISABLE, "DISABLE", 0, "Disable", "Disable selected markers"},
+ {ACTION_ENABLE, "ENABLE", 0, "Enable", "Enable selected markers"},
+ {ACTION_TOGGLE, "TOGGLE", 0, "Toggle", "Toggle disabled flag for selected markers"},
+ {0, NULL, 0, NULL, NULL}
+ };
+
+ /* identifiers */
+ ot->name = "Toggle Collection";
+ ot->idname = "OUTLINER_OT_collection_toggle";
+ ot->description = "Deselect collection objects";
+
+ /* api callbacks */
+ ot->exec = collection_toggle_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ /* properties */
+ prop = RNA_def_int(ot->srna, "collection_index", -1, -1, INT_MAX, "Collection Index", "Index of collection to toggle", 0, INT_MAX);
+ RNA_def_property_flag(prop, PROP_SKIP_SAVE);
+ prop = RNA_def_enum(ot->srna, "action", actions_items, ACTION_TOGGLE, "Action", "Selection action to execute");
+ RNA_def_property_flag(prop, PROP_SKIP_SAVE);
+}
+
+#undef ACTION_TOGGLE
+#undef ACTION_ENABLE
+#undef ACTION_DISABLE
+
+struct CollectionObjectsSelectData {
+ bool error;
+ LayerCollection *layer_collection;
+};
+
+static TreeTraversalAction outliner_find_first_selected_layer_collection(TreeElement *te, void *customdata)
+{
+ struct CollectionObjectsSelectData *data = customdata;
+ TreeStoreElem *tselem = TREESTORE(te);
+
+ switch (tselem->type) {
+ case TSE_LAYER_COLLECTION:
+ data->layer_collection = te->directdata;
+ return TRAVERSE_BREAK;
+ case TSE_LAYER_COLLECTION_BASE:
+ return TRAVERSE_CONTINUE;
+ default:
+ return TRAVERSE_SKIP_CHILDS;
+ }
+}
+
+static LayerCollection *outliner_active_layer_collection(bContext *C)
+{
+ SpaceOops *soops = CTX_wm_space_outliner(C);
+
+ struct CollectionObjectsSelectData data = {
+ .layer_collection = NULL,
+ };
+
+ outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, outliner_find_first_selected_layer_collection, &data);
+ return data.layer_collection;
+}
+
+static int collection_objects_select_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ LayerCollection *layer_collection = outliner_active_layer_collection(C);
+
+ if (layer_collection == NULL) {
+ return OPERATOR_CANCELLED;
+ }
+
+ BKE_layer_collection_objects_select(layer_collection);
+ WM_main_add_notifier(NC_SCENE | ND_OB_SELECT, CTX_data_scene(C));
+
+ return OPERATOR_FINISHED;
+}
+
+void OUTLINER_OT_collection_objects_select(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Select Objects";
+ ot->idname = "OUTLINER_OT_collection_objects_select";
+ ot->description = "Select all the collection objects";
+
+ /* api callbacks */
+ ot->exec = collection_objects_select_exec;
+ ot->poll = view_layer_editor_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+struct CollectionDuplicateData {
+ TreeElement *te;
+};
+
+static TreeTraversalAction outliner_find_first_selected_collection(TreeElement *te, void *customdata)
+{
+ struct CollectionDuplicateData *data = customdata;
+ TreeStoreElem *tselem = TREESTORE(te);
+
+ switch (tselem->type) {
+ case TSE_LAYER_COLLECTION:
+ case TSE_SCENE_COLLECTION:
+ data->te = te;
+ return TRAVERSE_BREAK;
+ case TSE_LAYER_COLLECTION_BASE:
+ default:
+ return TRAVERSE_CONTINUE;
+ }
+}
+
+static TreeElement *outliner_active_collection(bContext *C)
+{
+ SpaceOops *soops = CTX_wm_space_outliner(C);
+
+ struct CollectionDuplicateData data = {
+ .te = NULL,
+ };
+
+ outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, outliner_find_first_selected_collection, &data);
+ return data.te;
+}
+
+static int collection_duplicate_exec(bContext *C, wmOperator *op)
+{
+ SpaceOops *soops = CTX_wm_space_outliner(C);
+ TreeElement *te = outliner_active_collection(C);
+
+ BLI_assert(te != NULL);
+ if (BKE_collection_master(TREESTORE(te)->id) == outliner_scene_collection_from_tree_element(te)) {
+ BKE_report(op->reports, RPT_ERROR, "You can't duplicate the master collection");
+ return OPERATOR_CANCELLED;
+ }
+
+ switch (soops->outlinevis) {
+ case SO_COLLECTIONS:
+ BKE_collection_duplicate(TREESTORE(te)->id, (SceneCollection *)te->directdata);
+ break;
+ case SO_VIEW_LAYER:
+ case SO_GROUPS:
+ BKE_layer_collection_duplicate(TREESTORE(te)->id, (LayerCollection *)te->directdata);
+ break;
+ }
+
+ DEG_relations_tag_update(CTX_data_main(C));
+ WM_main_add_notifier(NC_SCENE | ND_LAYER, CTX_data_scene(C));
+
+ return OPERATOR_FINISHED;
+}
+
+void OUTLINER_OT_collection_duplicate(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Duplicate Collection";
+ ot->idname = "OUTLINER_OT_collection_duplicate";
+ ot->description = "Duplicate collection";
+
+ /* api callbacks */
+ ot->exec = collection_duplicate_exec;
+ ot->poll = outliner_either_collection_editor_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}