Changeset View
Standalone View
source/blender/editors/space_outliner/outliner_dragdrop.c
| Show All 20 Lines | |||||
| * \ingroup spoutliner | * \ingroup spoutliner | ||||
| */ | */ | ||||
| #include <string.h> | #include <string.h> | ||||
| #include "MEM_guardedalloc.h" | #include "MEM_guardedalloc.h" | ||||
| #include "DNA_collection_types.h" | #include "DNA_collection_types.h" | ||||
| #include "DNA_constraint_types.h" | |||||
| #include "DNA_material_types.h" | #include "DNA_material_types.h" | ||||
| #include "DNA_modifier_types.h" | |||||
| #include "DNA_object_types.h" | #include "DNA_object_types.h" | ||||
| #include "DNA_space_types.h" | #include "DNA_space_types.h" | ||||
| #include "BLI_listbase.h" | #include "BLI_listbase.h" | ||||
| #include "BLI_string.h" | #include "BLI_string.h" | ||||
| #include "BLT_translation.h" | #include "BLT_translation.h" | ||||
| #include "BKE_collection.h" | #include "BKE_collection.h" | ||||
| #include "BKE_constraint.h" | |||||
| #include "BKE_context.h" | #include "BKE_context.h" | ||||
| #include "BKE_layer.h" | #include "BKE_layer.h" | ||||
| #include "BKE_lib_id.h" | #include "BKE_lib_id.h" | ||||
| #include "BKE_main.h" | #include "BKE_main.h" | ||||
| #include "BKE_material.h" | #include "BKE_material.h" | ||||
| #include "BKE_object.h" | #include "BKE_object.h" | ||||
| #include "BKE_report.h" | #include "BKE_report.h" | ||||
| #include "BKE_scene.h" | #include "BKE_scene.h" | ||||
| #include "BKE_shader_fx.h" | |||||
| #include "DEG_depsgraph.h" | #include "DEG_depsgraph.h" | ||||
| #include "DEG_depsgraph_build.h" | #include "DEG_depsgraph_build.h" | ||||
| #include "ED_object.h" | #include "ED_object.h" | ||||
| #include "ED_outliner.h" | #include "ED_outliner.h" | ||||
| #include "ED_screen.h" | #include "ED_screen.h" | ||||
| #include "UI_interface.h" | #include "UI_interface.h" | ||||
| #include "UI_resources.h" | #include "UI_resources.h" | ||||
| #include "UI_view2d.h" | #include "UI_view2d.h" | ||||
| #include "RNA_access.h" | #include "RNA_access.h" | ||||
| #include "RNA_define.h" | #include "RNA_define.h" | ||||
| #include "RNA_enum_types.h" | #include "RNA_enum_types.h" | ||||
| #include "WM_api.h" | #include "WM_api.h" | ||||
| #include "WM_types.h" | #include "WM_types.h" | ||||
| #include "outliner_intern.h" | #include "outliner_intern.h" | ||||
| static Collection *collection_parent_from_ID(ID *id); | |||||
| /* ******************** Drop Target Find *********************** */ | /* ******************** Drop Target Find *********************** */ | ||||
| static TreeElement *outliner_dropzone_element(TreeElement *te, | static TreeElement *outliner_dropzone_element(TreeElement *te, | ||||
| const float fmval[2], | const float fmval[2], | ||||
| const bool children) | const bool children) | ||||
| { | { | ||||
| if ((fmval[1] > te->ys) && (fmval[1] < (te->ys + UI_UNIT_Y))) { | if ((fmval[1] > te->ys) && (fmval[1] < (te->ys + UI_UNIT_Y))) { | ||||
| /* name and first icon */ | /* name and first icon */ | ||||
| if ((fmval[0] > te->xs + UI_UNIT_X) && (fmval[0] < te->xend)) { | if ((fmval[0] > te->xs + UI_UNIT_X) && (fmval[0] < te->xend)) { | ||||
| return te; | return te; | ||||
| } | } | ||||
| } | } | ||||
| /* Not it. Let's look at its children. */ | /* Not it. Let's look at its children. */ | ||||
| if (children && (TREESTORE(te)->flag & TSE_CLOSED) == 0 && (te->subtree.first)) { | if (children && (TREESTORE(te)->flag & TSE_CLOSED) == 0 && (te->subtree.first)) { | ||||
| for (te = te->subtree.first; te; te = te->next) { | for (te = te->subtree.first; te; te = te->next) { | ||||
| TreeElement *te_valid = outliner_dropzone_element(te, fmval, children); | TreeElement *te_valid = outliner_dropzone_element(te, fmval, children); | ||||
| if (te_valid) { | if (te_valid) { | ||||
| return te_valid; | return te_valid; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
Severin: Minor suggestion: I'd suggest using `sizeof(*drop_data)` here, which means the type will always… | |||||
| /* Find tree element to drop into. */ | /* Find tree element to drop into. */ | ||||
| static TreeElement *outliner_dropzone_find(const SpaceOutliner *space_outliner, | static TreeElement *outliner_dropzone_find(const SpaceOutliner *space_outliner, | ||||
| const float fmval[2], | const float fmval[2], | ||||
| const bool children) | const bool children) | ||||
| { | { | ||||
| TreeElement *te; | TreeElement *te; | ||||
| ▲ Show 20 Lines • Show All 42 Lines • ▼ Show 20 Lines | static TreeElement *outliner_drop_insert_find(bContext *C, | ||||
| te_hovered = outliner_find_item_at_y(space_outliner, &space_outliner->tree, view_mval[1]); | te_hovered = outliner_find_item_at_y(space_outliner, &space_outliner->tree, view_mval[1]); | ||||
| if (te_hovered) { | if (te_hovered) { | ||||
| /* Mouse hovers an element (ignoring x-axis), | /* Mouse hovers an element (ignoring x-axis), | ||||
| * now find out how to insert the dragged item exactly. */ | * now find out how to insert the dragged item exactly. */ | ||||
| const float margin = UI_UNIT_Y * (1.0f / 4); | const float margin = UI_UNIT_Y * (1.0f / 4); | ||||
| if (view_mval[1] < (te_hovered->ys + margin)) { | if (view_mval[1] < (te_hovered->ys + margin)) { | ||||
| if (TSELEM_OPEN(TREESTORE(te_hovered), space_outliner)) { | if (TSELEM_OPEN(TREESTORE(te_hovered), space_outliner) && | ||||
| !BLI_listbase_is_empty(&te_hovered->subtree)) { | |||||
| /* inserting after a open item means we insert into it, but as first child */ | /* inserting after a open item means we insert into it, but as first child */ | ||||
| if (BLI_listbase_is_empty(&te_hovered->subtree)) { | if (BLI_listbase_is_empty(&te_hovered->subtree)) { | ||||
| *r_insert_type = TE_INSERT_INTO; | *r_insert_type = TE_INSERT_INTO; | ||||
| return te_hovered; | return te_hovered; | ||||
| } | } | ||||
| *r_insert_type = TE_INSERT_BEFORE; | *r_insert_type = TE_INSERT_BEFORE; | ||||
| return te_hovered->subtree.first; | return te_hovered->subtree.first; | ||||
| } | } | ||||
| Show All 20 Lines | static TreeElement *outliner_drop_insert_find(bContext *C, | ||||
| if (view_mval[1] > (first->ys + UI_UNIT_Y)) { | if (view_mval[1] > (first->ys + UI_UNIT_Y)) { | ||||
| *r_insert_type = TE_INSERT_BEFORE; | *r_insert_type = TE_INSERT_BEFORE; | ||||
| return first; | return first; | ||||
| } | } | ||||
| BLI_assert(0); | BLI_assert(0); | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| static Collection *outliner_collection_from_tree_element_and_parents(TreeElement *te, | typedef bool (*CheckTypeFn)(TreeElement *te, void **r_data); | ||||
SeverinUnsubmitted Done Inline ActionsCouldn't you just use a non-NULL return value as condition? Don't see a need for both, the bool and the argument return value. Severin: Couldn't you just use a non-`NULL` return value as condition? Don't see a need for both, the… | |||||
| static void *outliner_data_from_tree_element_and_parents(CheckTypeFn check_type, | |||||
| TreeElement *te, | |||||
| TreeElement **r_te) | TreeElement **r_te) | ||||
| { | { | ||||
| void *data; | |||||
| while (te != NULL) { | while (te != NULL) { | ||||
| Collection *collection = outliner_collection_from_tree_element(te); | if (check_type(te, &data)) { | ||||
| if (collection) { | |||||
| *r_te = te; | *r_te = te; | ||||
| return collection; | return data; | ||||
| } | } | ||||
| te = te->parent; | te = te->parent; | ||||
| } | } | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| static bool is_collection_element(TreeElement *te, void **r_collection) | |||||
| { | |||||
| Collection *collection = outliner_collection_from_tree_element(te); | |||||
| *r_collection = collection; | |||||
| return collection != NULL; | |||||
| } | |||||
| static bool is_object_element(TreeElement *te, void **r_object) | |||||
| { | |||||
| TreeStoreElem *tselem = TREESTORE(te); | |||||
| if (tselem->type == 0 && te->idcode == ID_OB) { | |||||
| *r_object = (Object *)tselem->id; | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| static bool is_pchan_element(TreeElement *te, void **r_pchan) | |||||
| { | |||||
| TreeStoreElem *tselem = TREESTORE(te); | |||||
| if (tselem->type == TSE_POSE_CHANNEL) { | |||||
| *r_pchan = (bPoseChannel *)te->directdata; | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| static TreeElement *outliner_drop_insert_collection_find(bContext *C, | static TreeElement *outliner_drop_insert_collection_find(bContext *C, | ||||
| const wmEvent *event, | const wmEvent *event, | ||||
| TreeElementInsertType *r_insert_type) | TreeElementInsertType *r_insert_type) | ||||
| { | { | ||||
| TreeElement *te = outliner_drop_insert_find(C, event, r_insert_type); | TreeElement *te = outliner_drop_insert_find(C, event, r_insert_type); | ||||
| if (!te) { | if (!te) { | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| TreeElement *collection_te; | TreeElement *collection_te; | ||||
| Collection *collection = outliner_collection_from_tree_element_and_parents(te, &collection_te); | Collection *collection = outliner_data_from_tree_element_and_parents( | ||||
| is_collection_element, te, &collection_te); | |||||
| if (!collection) { | if (!collection) { | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| if (collection_te != te) { | if (collection_te != te) { | ||||
| *r_insert_type = TE_INSERT_INTO; | *r_insert_type = TE_INSERT_INTO; | ||||
| } | } | ||||
| /* We can't insert before/after master collection. */ | /* We can't insert before/after master collection. */ | ||||
| if (collection->flag & COLLECTION_IS_MASTER) { | if (collection->flag & COLLECTION_IS_MASTER) { | ||||
| *r_insert_type = TE_INSERT_INTO; | *r_insert_type = TE_INSERT_INTO; | ||||
| } | } | ||||
| return collection_te; | return collection_te; | ||||
| } | } | ||||
| static int outliner_get_insert_index(TreeElement *drag_te, | |||||
| TreeElement *drop_te, | |||||
| TreeElementInsertType insert_type, | |||||
| ListBase *listbase) | |||||
| { | |||||
| /* Find the element to insert after. NULL is the start of the list. */ | |||||
| if (drag_te->index < drop_te->index) { | |||||
| if (insert_type == TE_INSERT_BEFORE) { | |||||
| drop_te = drop_te->prev; | |||||
| } | |||||
| } | |||||
| else { | |||||
Done Inline ActionsThis loop is repeated 3 times, with only minor differences. You could write a utility function for this. Severin: This loop is repeated 3 times, with only minor differences. You could write a utility function… | |||||
| if (insert_type == TE_INSERT_AFTER) { | |||||
| drop_te = drop_te->next; | |||||
| } | |||||
| } | |||||
| if (drop_te == NULL) { | |||||
| return 0; | |||||
| } | |||||
| return BLI_findindex(listbase, drop_te->directdata); | |||||
| } | |||||
| /* ******************** Parent Drop Operator *********************** */ | /* ******************** Parent Drop Operator *********************** */ | ||||
| static bool parent_drop_allowed(TreeElement *te, Object *potential_child) | static bool parent_drop_allowed(TreeElement *te, Object *potential_child) | ||||
| { | { | ||||
| TreeStoreElem *tselem = TREESTORE(te); | TreeStoreElem *tselem = TREESTORE(te); | ||||
| if (te->idcode != ID_OB || tselem->type != 0) { | if (te->idcode != ID_OB || tselem->type != 0) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 376 Lines • ▼ Show 20 Lines | void OUTLINER_OT_material_drop(wmOperatorType *ot) | ||||
| ot->invoke = material_drop_invoke; | ot->invoke = material_drop_invoke; | ||||
| ot->poll = ED_operator_outliner_active; | ot->poll = ED_operator_outliner_active; | ||||
| /* flags */ | /* flags */ | ||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; | ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; | ||||
| } | } | ||||
| /* ******************** Data Stack Drop Operator *********************** */ | |||||
| /* A generic operator to allow drag and drop for modifiers, constraints, | |||||
| * and shader effects which all share the same UI stack layout. | |||||
| * | |||||
| * The following operations are allowed: | |||||
| * - Reordering within an object. | |||||
| * - Copying a single modifier/constraint/effect to another object. | |||||
| * - Copying (linking) an object's modifiers/constraints/effects to another. */ | |||||
| typedef enum eDataStackDropAction { | |||||
| DATA_STACK_DROP_REORDER, | |||||
| DATA_STACK_DROP_COPY, | |||||
| DATA_STACK_DROP_LINK, | |||||
| } eDataStackDropAction; | |||||
| typedef struct StackDropData { | |||||
| Object *ob_parent; | |||||
| bPoseChannel *pchan_parent; | |||||
Done Inline ActionsI'd call this pchan_parent, otherwise it's confusing/misleading since it's not actually the bone. Severin: I'd call this `pchan_parent`, otherwise it's confusing/misleading since it's not actually the… | |||||
| TreeStoreElem *drag_tselem; | |||||
| void *drag_directdata; | |||||
| int drag_index; | |||||
| eDataStackDropAction drop_action; | |||||
| TreeElement *drop_te; | |||||
| TreeElementInsertType insert_type; | |||||
| } StackDropData; | |||||
| static void datastack_drop_data_init(wmDrag *drag, | |||||
| Object *ob, | |||||
| bPoseChannel *pchan, | |||||
| TreeElement *te, | |||||
| TreeStoreElem *tselem, | |||||
| void *directdata) | |||||
| { | |||||
| StackDropData *drop_data = MEM_callocN(sizeof(*drop_data), "datastack drop data"); | |||||
| drop_data->ob_parent = ob; | |||||
| drop_data->pchan_parent = pchan; | |||||
| drop_data->drag_tselem = tselem; | |||||
| drop_data->drag_directdata = directdata; | |||||
| drop_data->drag_index = te->index; | |||||
| drag->poin = drop_data; | |||||
| drag->flags |= WM_DRAG_FREE_DATA; | |||||
| } | |||||
| static bool datastack_drop_init(bContext *C, const wmEvent *event, StackDropData *drop_data) | |||||
| { | |||||
| if (!ELEM(drop_data->drag_tselem->type, | |||||
| TSE_MODIFIER, | |||||
Done Inline ActionsThis function does much more than polling, it actually specifies the operation and input data for the dropping. I'd rather avoid that. Could this logic be moved into an own function to determine operation and input data? The poll could then use that to check if the drop is valid, and set the TSE_DRAG_ flag. The drop-invoke could call it again to retrieve the needed data for the actual operation. Note that collection_drop_init() does the same actually. Severin: This function does much more than polling, it actually specifies the operation and input data… | |||||
| TSE_MODIFIER_BASE, | |||||
| TSE_CONSTRAINT, | |||||
| TSE_CONSTRAINT_BASE, | |||||
| TSE_GPENCIL_EFFECT, | |||||
| TSE_GPENCIL_EFFECT_BASE)) { | |||||
| return false; | |||||
| } | |||||
| TreeElement *te_target = outliner_drop_insert_find(C, event, &drop_data->insert_type); | |||||
| if (!te_target) { | |||||
| return false; | |||||
| } | |||||
| TreeStoreElem *tselem_target = TREESTORE(te_target); | |||||
| if (drop_data->drag_tselem == tselem_target) { | |||||
| return false; | |||||
| } | |||||
| TreeElement *object_te; | |||||
| TreeElement *pchan_te; | |||||
Done Inline ActionsSame here, would suggest pchan_te? Severin: Same here, would suggest `pchan_te`? | |||||
| Object *ob = outliner_data_from_tree_element_and_parents( | |||||
| is_object_element, te_target, &object_te); | |||||
| bPoseChannel *pchan = outliner_data_from_tree_element_and_parents( | |||||
| is_pchan_element, te_target, &pchan_te); | |||||
| if (pchan) { | |||||
| ob = NULL; | |||||
| } | |||||
| if (ob && ID_IS_LINKED(&ob->id)) { | |||||
| return false; | |||||
| } | |||||
| /* Drag a base for linking. */ | |||||
| if (ELEM(drop_data->drag_tselem->type, | |||||
| TSE_MODIFIER_BASE, | |||||
| TSE_CONSTRAINT_BASE, | |||||
| TSE_GPENCIL_EFFECT_BASE)) { | |||||
| drop_data->insert_type = TE_INSERT_INTO; | |||||
| drop_data->drop_action = DATA_STACK_DROP_LINK; | |||||
| if (pchan && pchan != drop_data->pchan_parent) { | |||||
| drop_data->drop_te = pchan_te; | |||||
| tselem_target = TREESTORE(pchan_te); | |||||
| } | |||||
| else if (ob && ob != drop_data->ob_parent) { | |||||
| drop_data->drop_te = object_te; | |||||
| tselem_target = TREESTORE(object_te); | |||||
| } | |||||
| else { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| else if (ob || pchan) { | |||||
| /* Drag a single item. */ | |||||
| if (pchan && pchan != drop_data->pchan_parent) { | |||||
| drop_data->insert_type = TE_INSERT_INTO; | |||||
| drop_data->drop_action = DATA_STACK_DROP_COPY; | |||||
| drop_data->drop_te = pchan_te; | |||||
| tselem_target = TREESTORE(pchan_te); | |||||
| } | |||||
| else if (ob && ob != drop_data->ob_parent) { | |||||
| drop_data->insert_type = TE_INSERT_INTO; | |||||
| drop_data->drop_action = DATA_STACK_DROP_COPY; | |||||
| drop_data->drop_te = object_te; | |||||
| tselem_target = TREESTORE(object_te); | |||||
| } | |||||
| else if (tselem_target->type == drop_data->drag_tselem->type) { | |||||
| if (drop_data->insert_type == TE_INSERT_INTO) { | |||||
| return false; | |||||
| } | |||||
| drop_data->drop_action = DATA_STACK_DROP_REORDER; | |||||
| drop_data->drop_te = te_target; | |||||
| } | |||||
| else { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| else { | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| /* Ensure that grease pencil and object data remain separate. */ | |||||
Done Inline ActionsUse a switch here, so it's obvious on first sight that this logic only handles the different types (there could be other checks in the `else if'-blocks below, you have to read them to see if that's the case). Severin: Use a switch here, so it's obvious on first sight that this logic only handles the different… | |||||
| static bool datastack_drop_are_types_valid(StackDropData *drop_data) | |||||
| { | |||||
| TreeStoreElem *tselem = TREESTORE(drop_data->drop_te); | |||||
Done Inline ActionsI'd add these function calls to ED_object_link_modifiers(). Same for the ones below. Severin: I'd add these function calls to `ED_object_link_modifiers()`. Same for the ones below. | |||||
| Object *ob_parent = drop_data->ob_parent; | |||||
| Object *ob_dst = (Object *)tselem->id; | |||||
| /* Don't allow data to be moved between objects and bones. */ | |||||
| if (tselem->type == TSE_CONSTRAINT) { | |||||
| } | |||||
| else if ((drop_data->pchan_parent && tselem->type != TSE_POSE_CHANNEL) || | |||||
| (!drop_data->pchan_parent && tselem->type == TSE_POSE_CHANNEL)) { | |||||
| return false; | |||||
| } | |||||
| switch (drop_data->drag_tselem->type) { | |||||
| case TSE_MODIFIER_BASE: | |||||
| case TSE_MODIFIER: | |||||
| if (ob_parent->type == OB_GPENCIL) { | |||||
| return ob_dst->type == OB_GPENCIL; | |||||
| } | |||||
| else if (ob_parent->type != OB_GPENCIL) { | |||||
| return ob_dst->type != OB_GPENCIL; | |||||
| } | |||||
| break; | |||||
| case TSE_CONSTRAINT_BASE: | |||||
| case TSE_CONSTRAINT: | |||||
| break; | |||||
| case TSE_GPENCIL_EFFECT_BASE: | |||||
| case TSE_GPENCIL_EFFECT: | |||||
| return ob_parent->type == OB_GPENCIL && ob_dst->type == OB_GPENCIL; | |||||
| break; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| static bool datastack_drop_poll(bContext *C, | |||||
| wmDrag *drag, | |||||
| const wmEvent *event, | |||||
| const char **r_tooltip) | |||||
| { | |||||
| SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); | |||||
| ARegion *region = CTX_wm_region(C); | |||||
| bool changed = outliner_flag_set(&space_outliner->tree, TSE_HIGHLIGHTED | TSE_DRAG_ANY, false); | |||||
| StackDropData *drop_data = drag->poin; | |||||
| if (!drop_data) { | |||||
Done Inline ActionsSame here, better use a switch. Severin: Same here, better use a switch. | |||||
| return false; | |||||
| } | |||||
| if (!datastack_drop_init(C, event, drop_data)) { | |||||
| return false; | |||||
| } | |||||
| if (!datastack_drop_are_types_valid(drop_data)) { | |||||
| return false; | |||||
| } | |||||
| TreeStoreElem *tselem_target = TREESTORE(drop_data->drop_te); | |||||
| switch (drop_data->insert_type) { | |||||
| case TE_INSERT_BEFORE: | |||||
| tselem_target->flag |= TSE_DRAG_BEFORE; | |||||
| break; | |||||
Done Inline ActionsWhat I meant with my earlier comment is that I think all these things should be handled in ED_ functions. The Outliner shouldn't know which notifiers have to be sent or which depsgraph tags have to be set if an operation is executed. The ED code should do that. Note that you don't need the context for that, you can call WM_main_add_notifier(). The context is only needed to get the window for specific screen-level handling. Severin: What I meant with my earlier comment is that I think all these things should be handled in… | |||||
| case TE_INSERT_AFTER: | |||||
| tselem_target->flag |= TSE_DRAG_AFTER; | |||||
| break; | |||||
| case TE_INSERT_INTO: | |||||
| tselem_target->flag |= TSE_DRAG_INTO; | |||||
| break; | |||||
| } | |||||
| switch (drop_data->drop_action) { | |||||
| case DATA_STACK_DROP_REORDER: | |||||
| *r_tooltip = TIP_("Reorder"); | |||||
| break; | |||||
| case DATA_STACK_DROP_COPY: | |||||
| if (drop_data->pchan_parent) { | |||||
| *r_tooltip = TIP_("Copy to bone"); | |||||
Done Inline ActionsWhy is this lower-level logic here? I'd expect there would be functions for this, likely on ED_ level. Severin: Why is this lower-level logic here? I'd expect there would be functions for this, likely on… | |||||
| } | |||||
| else { | |||||
| *r_tooltip = TIP_("Copy to object"); | |||||
| } | |||||
| break; | |||||
| case DATA_STACK_DROP_LINK: | |||||
| if (drop_data->pchan_parent) { | |||||
| *r_tooltip = TIP_("Link all to bone"); | |||||
| } | |||||
| else { | |||||
| *r_tooltip = TIP_("Link all to object"); | |||||
| } | |||||
| break; | |||||
| } | |||||
| if (changed) { | |||||
| ED_region_tag_redraw_no_rebuild(region); | |||||
| } | |||||
| return true; | |||||
| } | |||||
| static void datastack_drop_link(bContext *C, StackDropData *drop_data) | |||||
| { | |||||
| Main *bmain = CTX_data_main(C); | |||||
| TreeStoreElem *tselem = TREESTORE(drop_data->drop_te); | |||||
| Object *ob_dst = (Object *)tselem->id; | |||||
| switch (drop_data->drag_tselem->type) { | |||||
| case TSE_MODIFIER_BASE: | |||||
| ED_object_modifier_link(C, ob_dst, drop_data->ob_parent); | |||||
| break; | |||||
| case TSE_CONSTRAINT_BASE: { | |||||
| ListBase *src; | |||||
| if (drop_data->pchan_parent) { | |||||
| src = &drop_data->pchan_parent->constraints; | |||||
| } | |||||
| else { | |||||
| src = &drop_data->ob_parent->constraints; | |||||
| } | |||||
| ListBase *dst; | |||||
| if (tselem->type == TSE_POSE_CHANNEL) { | |||||
| bPoseChannel *pchan = (bPoseChannel *)drop_data->drop_te->directdata; | |||||
| dst = &pchan->constraints; | |||||
| } | |||||
| else { | |||||
| dst = &ob_dst->constraints; | |||||
| } | |||||
| ED_object_constraint_link(bmain, ob_dst, dst, src); | |||||
| break; | |||||
| } | |||||
| case TSE_GPENCIL_EFFECT_BASE: | |||||
| if (ob_dst->type != OB_GPENCIL) { | |||||
| return; | |||||
| } | |||||
| ED_object_shaderfx_link(ob_dst, drop_data->ob_parent); | |||||
| break; | |||||
| } | |||||
| } | |||||
| static void datastack_drop_copy(bContext *C, StackDropData *drop_data) | |||||
| { | |||||
| Main *bmain = CTX_data_main(C); | |||||
| TreeStoreElem *tselem = TREESTORE(drop_data->drop_te); | |||||
| Object *ob_dst = (Object *)tselem->id; | |||||
| switch (drop_data->drag_tselem->type) { | |||||
| case TSE_MODIFIER: | |||||
| if (drop_data->ob_parent->type == OB_GPENCIL && ob_dst->type == OB_GPENCIL) { | |||||
| ED_object_gpencil_modifier_copy_to_object(ob_dst, drop_data->drag_directdata); | |||||
| } | |||||
| else if (drop_data->ob_parent->type != OB_GPENCIL && ob_dst->type != OB_GPENCIL) { | |||||
| ED_object_modifier_copy_to_object( | |||||
| ob_dst, drop_data->ob_parent, drop_data->drag_directdata); | |||||
| } | |||||
| break; | |||||
| case TSE_CONSTRAINT: | |||||
| if (tselem->type == TSE_POSE_CHANNEL) { | |||||
| ED_object_constraint_copy_for_pose( | |||||
| bmain, ob_dst, drop_data->drop_te->directdata, drop_data->drag_directdata); | |||||
Done Inline ActionsI find UI stack a bit misleading, since were dealing with scene data, not UI. Maybe call it data-stack then? Not too discriptive either, but at least not misleading I think. Severin: I find UI stack a bit misleading, since were dealing with scene data, not UI. Maybe call it… | |||||
| } | |||||
| else { | |||||
| ED_object_constraint_copy_for_object(bmain, ob_dst, drop_data->drag_directdata); | |||||
| } | |||||
| break; | |||||
| case TSE_GPENCIL_EFFECT: { | |||||
| if (ob_dst->type != OB_GPENCIL) { | |||||
| return; | |||||
| } | |||||
| ED_object_shaderfx_copy(ob_dst, drop_data->drag_directdata); | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| static void datastack_drop_reorder(bContext *C, ReportList *reports, StackDropData *drop_data) | |||||
| { | |||||
| SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); | |||||
| TreeElement *drag_te = outliner_find_tree_element(&space_outliner->tree, drop_data->drag_tselem); | |||||
| if (!drag_te) { | |||||
| return; | |||||
| } | |||||
| TreeElement *drop_te = drop_data->drop_te; | |||||
| TreeElementInsertType insert_type = drop_data->insert_type; | |||||
| Object *ob = drop_data->ob_parent; | |||||
| int index = 0; | |||||
| switch (drop_data->drag_tselem->type) { | |||||
| case TSE_MODIFIER: | |||||
| if (ob->type == OB_GPENCIL) { | |||||
| index = outliner_get_insert_index( | |||||
| drag_te, drop_te, insert_type, &ob->greasepencil_modifiers); | |||||
| ED_object_gpencil_modifier_move_to_index(reports, ob, drop_data->drag_directdata, index); | |||||
| } | |||||
| else if (ob->type != OB_GPENCIL) { | |||||
| index = outliner_get_insert_index(drag_te, drop_te, insert_type, &ob->modifiers); | |||||
| ED_object_modifier_move_to_index(reports, ob, drop_data->drag_directdata, index); | |||||
| } | |||||
| break; | |||||
| case TSE_CONSTRAINT: | |||||
| if (drop_data->pchan_parent) { | |||||
| index = outliner_get_insert_index( | |||||
| drag_te, drop_te, insert_type, &drop_data->pchan_parent->constraints); | |||||
| } | |||||
| else { | |||||
| index = outliner_get_insert_index(drag_te, drop_te, insert_type, &ob->constraints); | |||||
| } | |||||
| ED_object_constraint_move_to_index(ob, drop_data->drag_directdata, index); | |||||
| break; | |||||
| case TSE_GPENCIL_EFFECT: | |||||
| index = outliner_get_insert_index(drag_te, drop_te, insert_type, &ob->shader_fx); | |||||
| ED_object_shaderfx_move_to_index(reports, ob, drop_data->drag_directdata, index); | |||||
| } | |||||
| } | |||||
| static int datastack_drop_invoke(bContext *C, wmOperator *op, const wmEvent *event) | |||||
| { | |||||
| if (event->custom != EVT_DATA_DRAGDROP) { | |||||
| return OPERATOR_CANCELLED; | |||||
| } | |||||
| ListBase *lb = event->customdata; | |||||
| wmDrag *drag = lb->first; | |||||
| StackDropData *drop_data = drag->poin; | |||||
| switch (drop_data->drop_action) { | |||||
| case DATA_STACK_DROP_LINK: | |||||
| datastack_drop_link(C, drop_data); | |||||
| break; | |||||
| case DATA_STACK_DROP_COPY: | |||||
| datastack_drop_copy(C, drop_data); | |||||
| break; | |||||
| case DATA_STACK_DROP_REORDER: | |||||
| datastack_drop_reorder(C, op->reports, drop_data); | |||||
| break; | |||||
| } | |||||
| return OPERATOR_FINISHED; | |||||
| } | |||||
| void OUTLINER_OT_datastack_drop(wmOperatorType *ot) | |||||
| { | |||||
| /* identifiers */ | |||||
| ot->name = "Data Stack Drop"; | |||||
| ot->description = "Copy or reorder modifiers, constraints, and effects"; | |||||
| ot->idname = "OUTLINER_OT_datastack_drop"; | |||||
| /* api callbacks */ | |||||
| ot->invoke = datastack_drop_invoke; | |||||
| ot->poll = ED_operator_outliner_active; | |||||
| /* flags */ | |||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; | |||||
| } | |||||
| /* ******************** Collection Drop Operator *********************** */ | /* ******************** Collection Drop Operator *********************** */ | ||||
| typedef struct CollectionDrop { | typedef struct CollectionDrop { | ||||
| Collection *from; | Collection *from; | ||||
| Collection *to; | Collection *to; | ||||
| TreeElement *te; | TreeElement *te; | ||||
| TreeElementInsertType insert_type; | TreeElementInsertType insert_type; | ||||
| ▲ Show 20 Lines • Show All 250 Lines • ▼ Show 20 Lines | static int outliner_item_drag_drop_invoke(bContext *C, | ||||
| ARegion *region = CTX_wm_region(C); | ARegion *region = CTX_wm_region(C); | ||||
| SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); | SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); | ||||
| TreeElement *te = outliner_item_drag_element_find(space_outliner, region, event); | TreeElement *te = outliner_item_drag_element_find(space_outliner, region, event); | ||||
| if (!te) { | if (!te) { | ||||
| return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); | return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); | ||||
| } | } | ||||
| TreeElementIcon data = tree_element_get_icon(TREESTORE(te), te); | TreeStoreElem *tselem = TREESTORE(te); | ||||
| TreeElementIcon data = tree_element_get_icon(tselem, te); | |||||
| if (!data.drag_id) { | if (!data.drag_id) { | ||||
| return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); | return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); | ||||
| } | } | ||||
| float view_mval[2]; | float view_mval[2]; | ||||
| UI_view2d_region_to_view( | UI_view2d_region_to_view( | ||||
| ®ion->v2d, event->mval[0], event->mval[1], &view_mval[0], &view_mval[1]); | ®ion->v2d, event->mval[0], event->mval[1], &view_mval[0], &view_mval[1]); | ||||
| if (outliner_item_is_co_within_close_toggle(te, view_mval[0])) { | if (outliner_item_is_co_within_close_toggle(te, view_mval[0])) { | ||||
| Show All 11 Lines | * when the drag goes too far outside the region. */ | ||||
| WM_operator_properties_create_ptr(&op_ptr, ot); | WM_operator_properties_create_ptr(&op_ptr, ot); | ||||
| RNA_int_set(&op_ptr, "outside_padding", OUTLINER_DRAG_SCOLL_OUTSIDE_PAD); | RNA_int_set(&op_ptr, "outside_padding", OUTLINER_DRAG_SCOLL_OUTSIDE_PAD); | ||||
| WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &op_ptr); | WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &op_ptr); | ||||
| WM_operator_properties_free(&op_ptr); | WM_operator_properties_free(&op_ptr); | ||||
| } | } | ||||
| wmDrag *drag = WM_event_start_drag(C, data.icon, WM_DRAG_ID, NULL, 0.0, WM_DRAG_NOP); | 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)) { | if (ELEM(tselem->type, | ||||
| TSE_MODIFIER, | |||||
| TSE_MODIFIER_BASE, | |||||
| TSE_CONSTRAINT, | |||||
| TSE_CONSTRAINT_BASE, | |||||
| TSE_GPENCIL_EFFECT, | |||||
| TSE_GPENCIL_EFFECT_BASE)) { | |||||
| TreeElement *te_bone = NULL; | |||||
| bPoseChannel *pchan = outliner_find_parent_bone(te, &te_bone); | |||||
| datastack_drop_data_init(drag, (Object *)tselem->id, pchan, te, tselem, te->directdata); | |||||
| } | |||||
| else if (ELEM(GS(data.drag_id->name), ID_OB, ID_GR)) { | |||||
| /* For collections and objects we cheat and drag all selected. */ | /* For collections and objects we cheat and drag all selected. */ | ||||
| /* Only drag element under mouse if it was not selected before. */ | /* Only drag element under mouse if it was not selected before. */ | ||||
| if ((TREESTORE(te)->flag & TSE_SELECTED) == 0) { | if ((tselem->flag & TSE_SELECTED) == 0) { | ||||
| outliner_flag_set(&space_outliner->tree, TSE_SELECTED, 0); | outliner_flag_set(&space_outliner->tree, TSE_SELECTED, 0); | ||||
| TREESTORE(te)->flag |= TSE_SELECTED; | tselem->flag |= TSE_SELECTED; | ||||
| } | } | ||||
| /* Gather all selected elements. */ | /* Gather all selected elements. */ | ||||
| struct IDsSelectedData selected = { | struct IDsSelectedData selected = { | ||||
| .selected_array = {NULL, NULL}, | .selected_array = {NULL, NULL}, | ||||
| }; | }; | ||||
| if (GS(data.drag_id->name) == ID_OB) { | if (GS(data.drag_id->name) == ID_OB) { | ||||
| ▲ Show 20 Lines • Show All 62 Lines • ▼ Show 20 Lines | else if (ELEM(GS(data.drag_id->name), ID_OB, ID_GR)) { | ||||
| BLI_freelistN(&selected.selected_array); | BLI_freelistN(&selected.selected_array); | ||||
| } | } | ||||
| else { | else { | ||||
| /* Add single ID. */ | /* Add single ID. */ | ||||
| WM_drag_add_ID(drag, data.drag_id, data.drag_parent); | WM_drag_add_ID(drag, data.drag_id, data.drag_parent); | ||||
| } | } | ||||
| ED_outliner_select_sync_from_all_tag(C); | ED_outliner_select_sync_from_outliner(C, space_outliner); | ||||
| return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH); | return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH); | ||||
| } | } | ||||
| /* Outliner drag and drop. This operator mostly exists to support dragging | /* 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 | * from outliner text instead of only from the icon, and also to show a | ||||
| * hint in the statusbar keymap. */ | * hint in the statusbar keymap. */ | ||||
| Show All 15 Lines | |||||
| void outliner_dropboxes(void) | void outliner_dropboxes(void) | ||||
| { | { | ||||
| ListBase *lb = WM_dropboxmap_find("Outliner", SPACE_OUTLINER, RGN_TYPE_WINDOW); | 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_drop", parent_drop_poll, NULL); | ||||
| WM_dropbox_add(lb, "OUTLINER_OT_parent_clear", parent_clear_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_scene_drop", scene_drop_poll, NULL); | ||||
| WM_dropbox_add(lb, "OUTLINER_OT_material_drop", material_drop_poll, NULL); | WM_dropbox_add(lb, "OUTLINER_OT_material_drop", material_drop_poll, NULL); | ||||
| WM_dropbox_add(lb, "OUTLINER_OT_datastack_drop", datastack_drop_poll, NULL); | |||||
| WM_dropbox_add(lb, "OUTLINER_OT_collection_drop", collection_drop_poll, NULL); | WM_dropbox_add(lb, "OUTLINER_OT_collection_drop", collection_drop_poll, NULL); | ||||
| } | } | ||||
Minor suggestion: I'd suggest using sizeof(*drop_data) here, which means the type will always follows the one of the variable.