/* * ***** 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. * * The Original Code is Copyright (C) 2004 Blender Foundation. * All rights reserved. * * The Original Code is: all of this file. * * Contributor(s): Joshua Leung * * ***** END GPL LICENSE BLOCK ***** */ /** \file blender/editors/space_outliner/outliner_dragdrop.c * \ingroup spoutliner */ #include #include "MEM_guardedalloc.h" #include "DNA_collection_types.h" #include "DNA_material_types.h" #include "DNA_object_types.h" #include "DNA_space_types.h" #include "BLI_listbase.h" #include "BLI_string.h" #include "BLT_translation.h" #include "BKE_collection.h" #include "BKE_context.h" #include "BKE_layer.h" #include "BKE_library.h" #include "BKE_main.h" #include "BKE_material.h" #include "BKE_object.h" #include "BKE_report.h" #include "BKE_scene.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_build.h" #include "ED_object.h" #include "ED_outliner.h" #include "ED_screen.h" #include "UI_interface.h" #include "UI_resources.h" #include "UI_view2d.h" #include "GPU_state.h" #include "RNA_access.h" #include "RNA_define.h" #include "RNA_enum_types.h" #include "WM_api.h" #include "WM_types.h" #include "outliner_intern.h" /* ******************** Drop Target Find *********************** */ static TreeElement *outliner_dropzone_element(TreeElement *te, const float fmval[2], const bool children) { if ((fmval[1] > te->ys) && (fmval[1] < (te->ys + UI_UNIT_Y))) { /* name and first icon */ if ((fmval[0] > te->xs + UI_UNIT_X) && (fmval[0] < te->xend)) return te; } /* Not it. Let's look at its children. */ if (children && (TREESTORE(te)->flag & TSE_CLOSED) == 0 && (te->subtree.first)) { for (te = te->subtree.first; te; te = te->next) { TreeElement *te_valid = outliner_dropzone_element(te, fmval, children); if (te_valid) return te_valid; } } return NULL; } /* Find tree element to drop into. */ static TreeElement *outliner_dropzone_find(const SpaceOops *soops, const float fmval[2], const bool children) { TreeElement *te; for (te = soops->tree.first; te; te = te->next) { TreeElement *te_valid = outliner_dropzone_element(te, fmval, children); if (te_valid) return te_valid; } return NULL; } static TreeElement *outliner_drop_find(bContext *C, const wmEvent *event) { ARegion *ar = CTX_wm_region(C); SpaceOops *soops = CTX_wm_space_outliner(C); float fmval[2]; UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]); return outliner_dropzone_find(soops, fmval, true); } static ID *outliner_ID_drop_find(bContext *C, const wmEvent *event, short idcode) { TreeElement *te = outliner_drop_find(C, event); TreeStoreElem *tselem = (te) ? TREESTORE(te) : NULL; if (te && te->idcode == idcode && tselem->type == 0) { return tselem->id; } else { return NULL; } } /* Find tree element to drop into, with additional before and after reorder support. */ static TreeElement *outliner_drop_insert_find( bContext *C, const wmEvent *event, TreeElementInsertType *r_insert_type) { SpaceOops *soops = CTX_wm_space_outliner(C); ARegion *ar = CTX_wm_region(C); TreeElement *te_hovered; float view_mval[2]; UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &view_mval[0], &view_mval[1]); te_hovered = outliner_find_item_at_y(soops, &soops->tree, view_mval[1]); if (te_hovered) { /* mouse hovers an element (ignoring x-axis), now find out how to insert the dragged item exactly */ const float margin = UI_UNIT_Y * (1.0f / 4); if (view_mval[1] < (te_hovered->ys + margin)) { if (TSELEM_OPEN(TREESTORE(te_hovered), soops)) { /* inserting after a open item means we insert into it, but as first child */ if (BLI_listbase_is_empty(&te_hovered->subtree)) { *r_insert_type = TE_INSERT_INTO; return te_hovered; } else { *r_insert_type = TE_INSERT_BEFORE; return te_hovered->subtree.first; } } else { *r_insert_type = TE_INSERT_AFTER; return te_hovered; } } else if (view_mval[1] > (te_hovered->ys + (3 * margin))) { *r_insert_type = TE_INSERT_BEFORE; return te_hovered; } else { *r_insert_type = TE_INSERT_INTO; return te_hovered; } } else { /* mouse doesn't hover any item (ignoring x-axis), so it's either above list bounds or below. */ TreeElement *first = soops->tree.first; TreeElement *last = soops->tree.last; if (view_mval[1] < last->ys) { *r_insert_type = TE_INSERT_AFTER; return last; } else if (view_mval[1] > (first->ys + UI_UNIT_Y)) { *r_insert_type = TE_INSERT_BEFORE; return first; } else { BLI_assert(0); return NULL; } } } static Collection *outliner_collection_from_tree_element_and_parents(TreeElement *te, TreeElement **r_te) { while (te != NULL) { Collection *collection = outliner_collection_from_tree_element(te); if (collection) { *r_te = te; return collection; } te = te->parent; } return NULL; } static TreeElement *outliner_drop_insert_collection_find( bContext *C, const wmEvent *event, TreeElementInsertType *r_insert_type) { TreeElement *te = outliner_drop_insert_find(C, event, r_insert_type); if (!te) return NULL; TreeElement *collection_te; Collection *collection = outliner_collection_from_tree_element_and_parents(te, &collection_te); if (!collection) return NULL; if (collection_te != te) { *r_insert_type = TE_INSERT_INTO; } /* We can't insert before/after master collection. */ if (collection->flag & COLLECTION_IS_MASTER) { *r_insert_type = TE_INSERT_INTO; } return collection_te; } /* ******************** Parent Drop Operator *********************** */ static bool parent_drop_allowed(SpaceOops *soops, TreeElement *te, Object *potential_child) { TreeStoreElem *tselem = TREESTORE(te); if (te->idcode != ID_OB || tselem->type != 0) { return false; } Object *potential_parent = (Object *)tselem->id; if (potential_parent == potential_child) return false; if (BKE_object_is_child_recursive(potential_child, potential_parent)) return false; if (potential_parent == potential_child->parent) return false; /* check that parent/child are both in the same scene */ Scene *scene = (Scene *)outliner_search_back(soops, te, ID_SCE); /* currently outliner organized in a way that if there's no parent scene * element for object it means that all displayed objects belong to * active scene and parenting them is allowed (sergey) */ if (scene) { for (ViewLayer *view_layer = scene->view_layers.first; view_layer; view_layer = view_layer->next) { if (BKE_view_layer_base_find(view_layer, potential_child)) { return true; } } return false; } else { return true; } } static bool allow_parenting_without_modifier_key(SpaceOops *soops) { switch (soops->outlinevis) { case SO_VIEW_LAYER: return soops->filter & SO_FILTER_NO_COLLECTION; case SO_SCENES: return true; default: return false; } } static bool parent_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event, const char **UNUSED(tooltip)) { SpaceOops *soops = CTX_wm_space_outliner(C); bool changed = outliner_flag_set(&soops->tree, TSE_DRAG_ANY, false); if (changed) ED_region_tag_redraw_no_rebuild(CTX_wm_region(C)); Object *potential_child = (Object *)WM_drag_ID(drag, ID_OB); if (!potential_child) return false; if (!allow_parenting_without_modifier_key(soops)) { if (!event->shift) return false; } TreeElement *te = outliner_drop_find(C, event); if (!te) return false; if (parent_drop_allowed(soops, te, potential_child)) { TREESTORE(te)->flag |= TSE_DRAG_INTO; ED_region_tag_redraw_no_rebuild(CTX_wm_region(C)); return true; } return false; } static int parent_drop_exec(bContext *C, wmOperator *op) { Object *par = NULL, *ob = NULL; Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); int partype = -1; char parname[MAX_NAME], childname[MAX_NAME]; partype = RNA_enum_get(op->ptr, "type"); RNA_string_get(op->ptr, "parent", parname); par = (Object *)BKE_libblock_find_name(bmain, ID_OB, parname); RNA_string_get(op->ptr, "child", childname); ob = (Object *)BKE_libblock_find_name(bmain, ID_OB, childname); if (ID_IS_LINKED(ob)) { BKE_report(op->reports, RPT_INFO, "Can't edit library linked object"); return OPERATOR_CANCELLED; } ED_object_parent_set(op->reports, C, scene, ob, par, partype, false, false, NULL); DEG_relations_tag_update(bmain); WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL); WM_event_add_notifier(C, NC_OBJECT | ND_PARENT, NULL); return OPERATOR_FINISHED; } static int parent_drop_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Main *bmain = CTX_data_main(C); SpaceOops *soops = CTX_wm_space_outliner(C); TreeElement *te = outliner_drop_find(C, event); TreeStoreElem *tselem = te ? TREESTORE(te) : NULL; if (!(te && te->idcode == ID_OB && tselem->type == 0)) { return OPERATOR_CANCELLED; } Object *par = (Object *)tselem->id; Object *ob = (Object *)WM_drag_ID_from_event(event, ID_OB); if (ELEM(NULL, ob, par)) { return OPERATOR_CANCELLED; } if (ob == par) { return OPERATOR_CANCELLED; } if (ID_IS_LINKED(ob)) { BKE_report(op->reports, RPT_INFO, "Can't edit library linked object"); return OPERATOR_CANCELLED; } char childname[MAX_NAME]; char parname[MAX_NAME]; STRNCPY(childname, ob->id.name + 2); STRNCPY(parname, par->id.name + 2); RNA_string_set(op->ptr, "child", childname); RNA_string_set(op->ptr, "parent", parname); Scene *scene = (Scene *)outliner_search_back(soops, te, ID_SCE); if (scene == NULL) { /* currently outlier organized in a way, that if there's no parent scene * element for object it means that all displayed objects belong to * active scene and parenting them is allowed (sergey) */ scene = CTX_data_scene(C); } if ((par->type != OB_ARMATURE) && (par->type != OB_CURVE) && (par->type != OB_LATTICE)) { int partype = 0; if (ED_object_parent_set(op->reports, C, scene, ob, par, partype, false, false, NULL)) { DEG_relations_tag_update(bmain); WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL); WM_event_add_notifier(C, NC_OBJECT | ND_PARENT, NULL); } } else { /* Menu creation */ wmOperatorType *ot = WM_operatortype_find("OUTLINER_OT_parent_drop", false); uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Set Parent To"), ICON_NONE); uiLayout *layout = UI_popup_menu_layout(pup); PointerRNA ptr; /* Cannot use uiItemEnumO()... have multiple properties to set. */ uiItemFullO_ptr(layout, ot, IFACE_("Object"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr); RNA_string_set(&ptr, "parent", parname); RNA_string_set(&ptr, "child", childname); RNA_enum_set(&ptr, "type", PAR_OBJECT); /* par becomes parent, make the associated menus */ if (par->type == OB_ARMATURE) { uiItemFullO_ptr(layout, ot, IFACE_("Armature Deform"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr); RNA_string_set(&ptr, "parent", parname); RNA_string_set(&ptr, "child", childname); RNA_enum_set(&ptr, "type", PAR_ARMATURE); uiItemFullO_ptr(layout, ot, IFACE_(" With Empty Groups"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr); RNA_string_set(&ptr, "parent", parname); RNA_string_set(&ptr, "child", childname); RNA_enum_set(&ptr, "type", PAR_ARMATURE_NAME); uiItemFullO_ptr(layout, ot, IFACE_(" With Envelope Weights"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr); RNA_string_set(&ptr, "parent", parname); RNA_string_set(&ptr, "child", childname); RNA_enum_set(&ptr, "type", PAR_ARMATURE_ENVELOPE); uiItemFullO_ptr(layout, ot, IFACE_(" With Automatic Weights"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr); RNA_string_set(&ptr, "parent", parname); RNA_string_set(&ptr, "child", childname); RNA_enum_set(&ptr, "type", PAR_ARMATURE_AUTO); uiItemFullO_ptr(layout, ot, IFACE_("Bone"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr); RNA_string_set(&ptr, "parent", parname); RNA_string_set(&ptr, "child", childname); RNA_enum_set(&ptr, "type", PAR_BONE); } else if (par->type == OB_CURVE) { uiItemFullO_ptr(layout, ot, IFACE_("Curve Deform"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr); RNA_string_set(&ptr, "parent", parname); RNA_string_set(&ptr, "child", childname); RNA_enum_set(&ptr, "type", PAR_CURVE); uiItemFullO_ptr(layout, ot, IFACE_("Follow Path"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr); RNA_string_set(&ptr, "parent", parname); RNA_string_set(&ptr, "child", childname); RNA_enum_set(&ptr, "type", PAR_FOLLOW); uiItemFullO_ptr(layout, ot, IFACE_("Path Constraint"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr); RNA_string_set(&ptr, "parent", parname); RNA_string_set(&ptr, "child", childname); RNA_enum_set(&ptr, "type", PAR_PATH_CONST); } else if (par->type == OB_LATTICE) { uiItemFullO_ptr(layout, ot, IFACE_("Lattice Deform"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr); RNA_string_set(&ptr, "parent", parname); RNA_string_set(&ptr, "child", childname); RNA_enum_set(&ptr, "type", PAR_LATTICE); } UI_popup_menu_end(C, pup); return OPERATOR_INTERFACE; } return OPERATOR_FINISHED; } void OUTLINER_OT_parent_drop(wmOperatorType *ot) { /* identifiers */ ot->name = "Drop to Set Parent"; ot->description = "Drag to parent in Outliner"; ot->idname = "OUTLINER_OT_parent_drop"; /* api callbacks */ ot->invoke = parent_drop_invoke; ot->exec = parent_drop_exec; ot->poll = ED_operator_outliner_active; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; /* properties */ RNA_def_string(ot->srna, "child", "Object", MAX_NAME, "Child", "Child Object"); RNA_def_string(ot->srna, "parent", "Object", MAX_NAME, "Parent", "Parent Object"); RNA_def_enum(ot->srna, "type", prop_make_parent_types, 0, "Type", ""); } /* ******************** Parent Clear Operator *********************** */ static bool parent_clear_poll(bContext *C, wmDrag *drag, const wmEvent *event, const char **UNUSED(tooltip)) { SpaceOops *soops = CTX_wm_space_outliner(C); if (!allow_parenting_without_modifier_key(soops)) { if (!event->shift) return false; } Object *ob = (Object *)WM_drag_ID(drag, ID_OB); if (!ob) return false; if (!ob->parent) return false; TreeElement *te = outliner_drop_find(C, event); if (te) { TreeStoreElem *tselem = TREESTORE(te); ID *id = tselem->id; if (!id) return true; switch (GS(id->name)) { case ID_OB: return ELEM(tselem->type, TSE_MODIFIER_BASE, TSE_CONSTRAINT_BASE); case ID_GR: return event->shift; default: return true; } } else { return true; } } static int parent_clear_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) { Main *bmain = CTX_data_main(C); Object *ob = (Object *)WM_drag_ID_from_event(event, ID_OB); if (ob == NULL) { return OPERATOR_CANCELLED; } ED_object_parent_clear(ob, 0); DEG_relations_tag_update(bmain); WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL); WM_event_add_notifier(C, NC_OBJECT | ND_PARENT, NULL); return OPERATOR_FINISHED; } void OUTLINER_OT_parent_clear(wmOperatorType *ot) { /* identifiers */ ot->name = "Drop to Clear Parent"; ot->description = "Drag to clear parent in Outliner"; ot->idname = "OUTLINER_OT_parent_clear"; /* api callbacks */ ot->invoke = parent_clear_invoke; ot->poll = ED_operator_outliner_active; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; } /* ******************** Scene Drop Operator *********************** */ static bool scene_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event, const char **UNUSED(tooltip)) { /* Ensure item under cursor is valid drop target */ Object *ob = (Object *)WM_drag_ID(drag, ID_OB); return (ob && (outliner_ID_drop_find(C, event, ID_SCE) != NULL)); } static int scene_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) { Main *bmain = CTX_data_main(C); Scene *scene = (Scene *)outliner_ID_drop_find(C, event, ID_SCE); Object *ob = (Object *)WM_drag_ID_from_event(event, ID_OB); if (ELEM(NULL, ob, scene) || ID_IS_LINKED(scene)) { return OPERATOR_CANCELLED; } if (BKE_scene_has_object(scene, ob)) { return OPERATOR_CANCELLED; } Collection *collection; if (scene != CTX_data_scene(C)) { /* when linking to an inactive scene link to the master collection */ collection = BKE_collection_master(scene); } else { collection = CTX_data_collection(C); } BKE_collection_object_add(bmain, collection, ob); for (ViewLayer *view_layer = scene->view_layers.first; view_layer; view_layer = view_layer->next) { Base *base = BKE_view_layer_base_find(view_layer, ob); if (base) { ED_object_base_select(base, BA_SELECT); } } DEG_relations_tag_update(bmain); DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); WM_main_add_notifier(NC_SCENE | ND_OB_SELECT, scene); return OPERATOR_FINISHED; } void OUTLINER_OT_scene_drop(wmOperatorType *ot) { /* identifiers */ ot->name = "Drop Object to Scene"; ot->description = "Drag object to scene in Outliner"; ot->idname = "OUTLINER_OT_scene_drop"; /* api callbacks */ ot->invoke = scene_drop_invoke; ot->poll = ED_operator_outliner_active; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; } /* ******************** Material Drop Operator *********************** */ static bool material_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event, const char **UNUSED(tooltip)) { /* Ensure item under cursor is valid drop target */ Material *ma = (Material *)WM_drag_ID(drag, ID_MA); return (ma && (outliner_ID_drop_find(C, event, ID_OB) != NULL)); } static int material_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) { Main *bmain = CTX_data_main(C); Object *ob = (Object *)outliner_ID_drop_find(C, event, ID_OB); Material *ma = (Material *)WM_drag_ID_from_event(event, ID_MA); if (ELEM(NULL, ob, ma)) { return OPERATOR_CANCELLED; } assign_material(bmain, ob, ma, ob->totcol + 1, BKE_MAT_ASSIGN_USERPREF); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, CTX_wm_view3d(C)); WM_event_add_notifier(C, NC_MATERIAL | ND_SHADING_LINKS, ma); return OPERATOR_FINISHED; } void OUTLINER_OT_material_drop(wmOperatorType *ot) { /* identifiers */ ot->name = "Drop Material on Object"; ot->description = "Drag material to object in Outliner"; ot->idname = "OUTLINER_OT_material_drop"; /* api callbacks */ ot->invoke = material_drop_invoke; ot->poll = ED_operator_outliner_active; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; } /* ******************** Collection Drop Operator *********************** */ typedef struct CollectionDrop { Collection *from; Collection *to; TreeElement *te; TreeElementInsertType insert_type; } CollectionDrop; static Collection *collection_parent_from_ID(ID *id) { /* Can't change linked parent collections. */ if (!id || ID_IS_LINKED(id)) { return NULL; } /* Also support dropping into/from scene collection. */ if (GS(id->name) == ID_SCE) { return ((Scene *)id)->master_collection; } else if (GS(id->name) == ID_GR) { return (Collection *)id; } return NULL; } static bool collection_drop_init(bContext *C, wmDrag *drag, const wmEvent *event, CollectionDrop *data) { SpaceOops *soops = CTX_wm_space_outliner(C); /* Get collection to drop into. */ TreeElementInsertType insert_type; TreeElement *te = outliner_drop_insert_collection_find(C, event, &insert_type); if (!te) { return false; } Collection *to_collection = outliner_collection_from_tree_element(te); if (ID_IS_LINKED(to_collection)) { return false; } /* Get drag datablocks. */ if (drag->type != WM_DRAG_ID) { return false; } wmDragID *drag_id = drag->ids.first; if (drag_id == NULL) { return false; } ID *id = drag_id->id; if (!(id && ELEM(GS(id->name), ID_GR, ID_OB))) { return false; } /* Get collection to drag out of. */ ID *parent = drag_id->from_parent; Collection *from_collection = collection_parent_from_ID(parent); if (event->ctrl || soops->outlinevis == SO_SCENES) { from_collection = NULL; } /* Get collections. */ if (GS(id->name) == ID_GR) { if (id == &to_collection->id) { return false; } } else { insert_type = TE_INSERT_INTO; } data->from = from_collection; data->to = to_collection; data->te = te; data->insert_type = insert_type; return true; } static bool collection_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event, const char **tooltip) { SpaceOops *soops = CTX_wm_space_outliner(C); ARegion *ar = CTX_wm_region(C); bool changed = outliner_flag_set(&soops->tree, TSE_HIGHLIGHTED | TSE_DRAG_ANY, false); CollectionDrop data; if (!event->shift && collection_drop_init(C, drag, event, &data)) { TreeElement *te = data.te; TreeStoreElem *tselem = TREESTORE(te); if (!data.from || event->ctrl) { tselem->flag |= TSE_DRAG_INTO; changed = true; *tooltip = IFACE_("Link inside Collection"); } else { switch (data.insert_type) { case TE_INSERT_BEFORE: tselem->flag |= TSE_DRAG_BEFORE; changed = true; if (te->prev && outliner_is_collection_tree_element(te->prev)) { *tooltip = TIP_("Move between collections"); } else { *tooltip = TIP_("Move before collection"); } break; case TE_INSERT_AFTER: tselem->flag |= TSE_DRAG_AFTER; changed = true; if (te->next && outliner_is_collection_tree_element(te->next)) { *tooltip = TIP_("Move between collections"); } else { *tooltip = TIP_("Move after collection"); } break; case TE_INSERT_INTO: tselem->flag |= TSE_DRAG_INTO; changed = true; *tooltip = TIP_("Move inside collection (Ctrl to link, Shift to parent)"); break; } } if (changed) ED_region_tag_redraw_no_rebuild(ar); return true; } else { if (changed) ED_region_tag_redraw_no_rebuild(ar); return false; } } static int collection_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); if (event->custom != EVT_DATA_DRAGDROP) { return OPERATOR_CANCELLED; } ListBase *lb = event->customdata; wmDrag *drag = lb->first; CollectionDrop data; if (!collection_drop_init(C, drag, event, &data)) { return OPERATOR_CANCELLED; } /* Before/after insert handling. */ Collection *relative = NULL; bool relative_after = false; if (ELEM(data.insert_type, TE_INSERT_BEFORE, TE_INSERT_AFTER)) { SpaceOops *soops = CTX_wm_space_outliner(C); relative = data.to; relative_after = (data.insert_type == TE_INSERT_AFTER); TreeElement *parent_te = outliner_find_parent_element(&soops->tree, NULL, data.te); data.to = (parent_te) ? outliner_collection_from_tree_element(parent_te) : NULL; } if (!data.to) { return OPERATOR_CANCELLED; } if (BKE_collection_is_empty(data.to)) { TREESTORE(data.te)->flag &= ~TSE_CLOSED; } for (wmDragID *drag_id = drag->ids.first; drag_id; drag_id = drag_id->next) { /* Ctrl enables linking, so we don't need a from collection then. */ Collection *from = (event->ctrl) ? NULL : collection_parent_from_ID(drag_id->from_parent); if (GS(drag_id->id->name) == ID_OB) { /* Move/link object into collection. */ Object *object = (Object *)drag_id->id; if (from) { BKE_collection_object_move(bmain, scene, data.to, from, object); } else { BKE_collection_object_add(bmain, data.to, object); } } else if (GS(drag_id->id->name) == ID_GR) { /* Move/link collection into collection. */ Collection *collection = (Collection *)drag_id->id; if (collection != from) { BKE_collection_move(bmain, data.to, from, relative, relative_after, collection); } } if (from) { DEG_id_tag_update(&from->id, ID_RECALC_COPY_ON_WRITE); } } /* Update dependency graph. */ DEG_id_tag_update(&data.to->id, ID_RECALC_COPY_ON_WRITE); DEG_relations_tag_update(bmain); WM_event_add_notifier(C, NC_SCENE | ND_LAYER, scene); return OPERATOR_FINISHED; } void OUTLINER_OT_collection_drop(wmOperatorType *ot) { /* identifiers */ ot->name = "Move to Collection"; ot->description = "Drag to move to collection in Outliner"; ot->idname = "OUTLINER_OT_collection_drop"; /* api callbacks */ ot->invoke = collection_drop_invoke; ot->poll = ED_operator_outliner_active; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; } /* ********************* Outliner Drag Operator ******************** */ static TreeElement *outliner_item_drag_element_find(SpaceOops *soops, ARegion *ar, const wmEvent *event) { /* note: using EVT_TWEAK_ events to trigger dragging is fine, * it sends coordinates from where dragging was started */ const float my = UI_view2d_region_to_view_y(&ar->v2d, event->mval[1]); return outliner_find_item_at_y(soops, &soops->tree, my); } static int outliner_item_drag_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) { ARegion *ar = CTX_wm_region(C); SpaceOops *soops = CTX_wm_space_outliner(C); TreeElement *te = outliner_item_drag_element_find(soops, ar, event); if (!te) { return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); } TreeElementIcon data = tree_element_get_icon(TREESTORE(te), te); if (!data.drag_id) { return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); } wmDrag *drag = WM_event_start_drag(C, data.icon, WM_DRAG_ID, NULL, 0.0, WM_DRAG_NOP); if (ELEM(GS(data.drag_id->name), ID_OB, ID_GR)) { /* For collections and objects we cheat and drag all selected. */ /* Only drag element under mouse if it was not selected before. */ if ((TREESTORE(te)->flag & TSE_SELECTED) == 0) { outliner_flag_set(&soops->tree, TSE_SELECTED, 0); TREESTORE(te)->flag |= TSE_SELECTED; } /* Gather all selected elements. */ struct IDsSelectedData selected = { .selected_array = {NULL, NULL}, }; if (GS(data.drag_id->name) == ID_OB) { outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, outliner_find_selected_objects, &selected); } else { outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, outliner_find_selected_collections, &selected); } LISTBASE_FOREACH (LinkData *, link, &selected.selected_array) { TreeElement *te_selected = (TreeElement *)link->data; ID *id; if (GS(data.drag_id->name) == ID_OB) { id = TREESTORE(te_selected)->id; } else { /* Keep collection hierarchies intact when dragging. */ bool parent_selected = false; for (TreeElement *te_parent = te_selected->parent; te_parent; te_parent = te_parent->parent) { if (outliner_is_collection_tree_element(te_parent)) { if (TREESTORE(te_parent)->flag & TSE_SELECTED) { parent_selected = true; break; } } } if (parent_selected) { continue; } id = &outliner_collection_from_tree_element(te_selected)->id; } /* Find parent collection. */ Collection *parent = NULL; if (te_selected->parent) { for (TreeElement *te_parent = te_selected->parent; te_parent; te_parent = te_parent->parent) { if (outliner_is_collection_tree_element(te_parent)) { parent = outliner_collection_from_tree_element(te_parent); break; } } } else { Scene *scene = CTX_data_scene(C); parent = BKE_collection_master(scene); } WM_drag_add_ID(drag, id, &parent->id); } BLI_freelistN(&selected.selected_array); } else { /* Add single ID. */ WM_drag_add_ID(drag, data.drag_id, data.drag_parent); } return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH); } /* Outliner drag and drop. This operator mostly exists to support dragging * from outliner text instead of only from the icon, and also to show a * hint in the statusbar keymap. */ void OUTLINER_OT_item_drag_drop(wmOperatorType *ot) { ot->name = "Drag and Drop"; ot->idname = "OUTLINER_OT_item_drag_drop"; ot->description = "Drag and drop element to another place"; ot->invoke = outliner_item_drag_drop_invoke; ot->poll = ED_operator_outliner_active; } /* *************************** Drop Boxes ************************** */ /* region dropbox definition */ void outliner_dropboxes(void) { ListBase *lb = WM_dropboxmap_find("Outliner", SPACE_OUTLINER, RGN_TYPE_WINDOW); WM_dropbox_add(lb, "OUTLINER_OT_parent_drop", parent_drop_poll, NULL); WM_dropbox_add(lb, "OUTLINER_OT_parent_clear", parent_clear_poll, NULL); WM_dropbox_add(lb, "OUTLINER_OT_scene_drop", scene_drop_poll, NULL); WM_dropbox_add(lb, "OUTLINER_OT_material_drop", material_drop_poll, NULL); WM_dropbox_add(lb, "OUTLINER_OT_collection_drop", collection_drop_poll, NULL); }