2018-08-07 10:23:07 +02:00
|
|
|
/*
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
|
|
|
|
|
2019-02-18 08:08:12 +11:00
|
|
|
/** \file
|
|
|
|
|
* \ingroup spoutliner
|
2018-08-07 10:23:07 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
|
|
2018-08-29 15:32:50 +02:00
|
|
|
#include "DNA_collection_types.h"
|
2018-08-07 10:23:07 +02:00
|
|
|
#include "DNA_material_types.h"
|
|
|
|
|
#include "DNA_object_types.h"
|
|
|
|
|
#include "DNA_space_types.h"
|
|
|
|
|
|
|
|
|
|
#include "BLI_listbase.h"
|
2018-08-07 10:57:09 +02:00
|
|
|
#include "BLI_string.h"
|
2018-08-07 10:23:07 +02:00
|
|
|
|
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
|
|
|
|
|
|
#include "BKE_collection.h"
|
|
|
|
|
#include "BKE_context.h"
|
|
|
|
|
#include "BKE_layer.h"
|
2020-02-10 12:58:59 +01:00
|
|
|
#include "BKE_lib_id.h"
|
2018-08-07 10:23:07 +02:00
|
|
|
#include "BKE_main.h"
|
|
|
|
|
#include "BKE_material.h"
|
2018-11-07 18:00:24 +01:00
|
|
|
#include "BKE_object.h"
|
2018-08-07 10:23:07 +02:00
|
|
|
#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 "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 *********************** */
|
|
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
static TreeElement *outliner_dropzone_element(TreeElement *te,
|
|
|
|
|
const float fmval[2],
|
|
|
|
|
const bool children)
|
2018-08-07 10:23:07 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
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;
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Find tree element to drop into. */
|
2020-08-07 11:47:23 -06:00
|
|
|
static TreeElement *outliner_dropzone_find(const SpaceOutliner *space_outliner,
|
2019-04-17 06:17:24 +02:00
|
|
|
const float fmval[2],
|
|
|
|
|
const bool children)
|
2018-08-07 10:23:07 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
TreeElement *te;
|
|
|
|
|
|
2020-08-07 11:47:23 -06:00
|
|
|
for (te = space_outliner->tree.first; te; te = te->next) {
|
2019-04-17 06:17:24 +02:00
|
|
|
TreeElement *te_valid = outliner_dropzone_element(te, fmval, children);
|
|
|
|
|
if (te_valid) {
|
|
|
|
|
return te_valid;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
2018-08-07 10:57:09 +02:00
|
|
|
static TreeElement *outliner_drop_find(bContext *C, const wmEvent *event)
|
2018-08-07 10:23:07 +02:00
|
|
|
{
|
2020-03-06 16:56:42 +01:00
|
|
|
ARegion *region = CTX_wm_region(C);
|
2020-08-07 11:47:23 -06:00
|
|
|
SpaceOutliner *space_outliner = CTX_wm_space_outliner(C);
|
2019-04-17 06:17:24 +02:00
|
|
|
float fmval[2];
|
2020-03-06 16:56:42 +01:00
|
|
|
UI_view2d_region_to_view(®ion->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2020-08-07 11:47:23 -06:00
|
|
|
return outliner_dropzone_find(space_outliner, fmval, true);
|
2018-08-07 10:57:09 +02:00
|
|
|
}
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2018-08-07 10:57:09 +02:00
|
|
|
static ID *outliner_ID_drop_find(bContext *C, const wmEvent *event, short idcode)
|
|
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
TreeElement *te = outliner_drop_find(C, event);
|
|
|
|
|
TreeStoreElem *tselem = (te) ? TREESTORE(te) : NULL;
|
|
|
|
|
|
|
|
|
|
if (te && te->idcode == idcode && tselem->type == 0) {
|
|
|
|
|
return tselem->id;
|
|
|
|
|
}
|
2020-07-03 17:20:22 +02:00
|
|
|
return NULL;
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
2018-08-10 17:04:05 +02:00
|
|
|
/* Find tree element to drop into, with additional before and after reorder support. */
|
2019-04-17 06:17:24 +02:00
|
|
|
static TreeElement *outliner_drop_insert_find(bContext *C,
|
|
|
|
|
const wmEvent *event,
|
|
|
|
|
TreeElementInsertType *r_insert_type)
|
2018-08-10 17:04:05 +02:00
|
|
|
{
|
2020-08-07 11:47:23 -06:00
|
|
|
SpaceOutliner *space_outliner = CTX_wm_space_outliner(C);
|
2020-03-06 16:56:42 +01:00
|
|
|
ARegion *region = CTX_wm_region(C);
|
2019-04-17 06:17:24 +02:00
|
|
|
TreeElement *te_hovered;
|
|
|
|
|
float view_mval[2];
|
|
|
|
|
|
2020-03-06 16:56:42 +01:00
|
|
|
UI_view2d_region_to_view(
|
|
|
|
|
®ion->v2d, event->mval[0], event->mval[1], &view_mval[0], &view_mval[1]);
|
2020-08-07 11:47:23 -06:00
|
|
|
te_hovered = outliner_find_item_at_y(space_outliner, &space_outliner->tree, view_mval[1]);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
|
|
|
|
if (te_hovered) {
|
2019-04-22 00:18:34 +10:00
|
|
|
/* Mouse hovers an element (ignoring x-axis),
|
|
|
|
|
* now find out how to insert the dragged item exactly. */
|
2019-04-17 06:17:24 +02:00
|
|
|
const float margin = UI_UNIT_Y * (1.0f / 4);
|
|
|
|
|
|
|
|
|
|
if (view_mval[1] < (te_hovered->ys + margin)) {
|
2020-08-07 11:47:23 -06:00
|
|
|
if (TSELEM_OPEN(TREESTORE(te_hovered), space_outliner)) {
|
2019-04-17 06:17:24 +02:00
|
|
|
/* 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;
|
|
|
|
|
}
|
2020-07-03 17:20:22 +02:00
|
|
|
*r_insert_type = TE_INSERT_BEFORE;
|
|
|
|
|
return te_hovered->subtree.first;
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2020-07-03 17:20:22 +02:00
|
|
|
*r_insert_type = TE_INSERT_AFTER;
|
2019-04-17 06:17:24 +02:00
|
|
|
return te_hovered;
|
|
|
|
|
}
|
2020-07-03 17:20:22 +02:00
|
|
|
if (view_mval[1] > (te_hovered->ys + (3 * margin))) {
|
|
|
|
|
*r_insert_type = TE_INSERT_BEFORE;
|
2019-04-17 06:17:24 +02:00
|
|
|
return te_hovered;
|
|
|
|
|
}
|
2020-07-03 17:20:22 +02:00
|
|
|
*r_insert_type = TE_INSERT_INTO;
|
|
|
|
|
return te_hovered;
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
|
2020-07-03 17:20:22 +02:00
|
|
|
/* Mouse doesn't hover any item (ignoring x-axis),
|
|
|
|
|
* so it's either above list bounds or below. */
|
2020-08-07 11:47:23 -06:00
|
|
|
TreeElement *first = space_outliner->tree.first;
|
|
|
|
|
TreeElement *last = space_outliner->tree.last;
|
2020-07-03 17:20:22 +02:00
|
|
|
|
|
|
|
|
if (view_mval[1] < last->ys) {
|
|
|
|
|
*r_insert_type = TE_INSERT_AFTER;
|
|
|
|
|
return last;
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2020-07-03 17:20:22 +02:00
|
|
|
if (view_mval[1] > (first->ys + UI_UNIT_Y)) {
|
|
|
|
|
*r_insert_type = TE_INSERT_BEFORE;
|
|
|
|
|
return first;
|
|
|
|
|
}
|
|
|
|
|
BLI_assert(0);
|
|
|
|
|
return NULL;
|
2018-08-10 17:04:05 +02:00
|
|
|
}
|
|
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
static Collection *outliner_collection_from_tree_element_and_parents(TreeElement *te,
|
|
|
|
|
TreeElement **r_te)
|
2018-10-22 13:53:36 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
while (te != NULL) {
|
|
|
|
|
Collection *collection = outliner_collection_from_tree_element(te);
|
|
|
|
|
if (collection) {
|
|
|
|
|
*r_te = te;
|
|
|
|
|
return collection;
|
|
|
|
|
}
|
|
|
|
|
te = te->parent;
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
2018-10-22 13:53:36 +02:00
|
|
|
}
|
|
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
static TreeElement *outliner_drop_insert_collection_find(bContext *C,
|
|
|
|
|
const wmEvent *event,
|
|
|
|
|
TreeElementInsertType *r_insert_type)
|
2018-08-10 17:04:05 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
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;
|
2018-08-10 17:04:05 +02:00
|
|
|
}
|
|
|
|
|
|
2018-08-07 10:57:09 +02:00
|
|
|
/* ******************** Parent Drop Operator *********************** */
|
|
|
|
|
|
2020-03-07 11:19:39 -07:00
|
|
|
static bool parent_drop_allowed(TreeElement *te, Object *potential_child)
|
2018-08-07 10:23:07 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
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 */
|
2020-03-07 11:19:39 -07:00
|
|
|
Scene *scene = (Scene *)outliner_search_back(te, ID_SCE);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
|
|
|
|
/* 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) {
|
2020-04-03 19:15:01 +02:00
|
|
|
LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) {
|
2019-04-17 06:17:24 +02:00
|
|
|
if (BKE_view_layer_base_find(view_layer, potential_child)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-07-03 17:20:22 +02:00
|
|
|
return true;
|
2018-10-22 13:53:36 +02:00
|
|
|
}
|
2018-08-07 10:57:09 +02:00
|
|
|
|
2020-08-07 11:47:23 -06:00
|
|
|
static bool allow_parenting_without_modifier_key(SpaceOutliner *space_outliner)
|
2018-10-22 13:53:36 +02:00
|
|
|
{
|
2020-08-07 11:47:23 -06:00
|
|
|
switch (space_outliner->outlinevis) {
|
2019-04-17 06:17:24 +02:00
|
|
|
case SO_VIEW_LAYER:
|
2020-08-07 11:47:23 -06:00
|
|
|
return space_outliner->filter & SO_FILTER_NO_COLLECTION;
|
2019-04-17 06:17:24 +02:00
|
|
|
case SO_SCENES:
|
|
|
|
|
return true;
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2018-10-22 13:53:36 +02:00
|
|
|
}
|
2018-08-07 10:57:09 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
static bool parent_drop_poll(bContext *C,
|
|
|
|
|
wmDrag *drag,
|
|
|
|
|
const wmEvent *event,
|
2020-03-25 17:58:58 +11:00
|
|
|
const char **UNUSED(r_tooltip))
|
2018-10-22 13:53:36 +02:00
|
|
|
{
|
2020-08-07 11:47:23 -06:00
|
|
|
SpaceOutliner *space_outliner = CTX_wm_space_outliner(C);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2020-08-07 11:47:23 -06:00
|
|
|
bool changed = outliner_flag_set(&space_outliner->tree, TSE_DRAG_ANY, false);
|
2019-04-17 06:17:24 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-07 11:47:23 -06:00
|
|
|
if (!allow_parenting_without_modifier_key(space_outliner)) {
|
2019-04-17 06:17:24 +02:00
|
|
|
if (!event->shift) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TreeElement *te = outliner_drop_find(C, event);
|
|
|
|
|
if (!te) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-07 11:19:39 -07:00
|
|
|
if (parent_drop_allowed(te, potential_child)) {
|
2019-04-17 06:17:24 +02:00
|
|
|
TREESTORE(te)->flag |= TSE_DRAG_INTO;
|
|
|
|
|
ED_region_tag_redraw_no_rebuild(CTX_wm_region(C));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
2020-03-12 10:51:49 +01:00
|
|
|
static void parent_drop_set_parents(bContext *C,
|
|
|
|
|
ReportList *reports,
|
|
|
|
|
wmDragID *drag,
|
|
|
|
|
Object *parent,
|
|
|
|
|
short parent_type,
|
|
|
|
|
const bool keep_transform)
|
2018-08-07 10:23:07 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
Main *bmain = CTX_data_main(C);
|
2020-08-07 11:47:23 -06:00
|
|
|
SpaceOutliner *space_outliner = CTX_wm_space_outliner(C);
|
2019-08-09 13:55:58 -06:00
|
|
|
|
2020-08-07 11:47:23 -06:00
|
|
|
TreeElement *te = outliner_find_id(space_outliner, &space_outliner->tree, &parent->id);
|
2020-03-07 11:19:39 -07:00
|
|
|
Scene *scene = (Scene *)outliner_search_back(te, ID_SCE);
|
2019-08-09 13:55:58 -06:00
|
|
|
|
|
|
|
|
if (scene == NULL) {
|
|
|
|
|
/* 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)
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
scene = CTX_data_scene(C);
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-09 13:55:58 -06:00
|
|
|
bool parent_set = false;
|
|
|
|
|
bool linked_objects = false;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2019-08-09 13:55:58 -06:00
|
|
|
for (wmDragID *drag_id = drag; drag_id; drag_id = drag_id->next) {
|
|
|
|
|
if (GS(drag_id->id->name) == ID_OB) {
|
|
|
|
|
Object *object = (Object *)drag_id->id;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2019-08-09 13:55:58 -06:00
|
|
|
/* Do nothing to linked data */
|
|
|
|
|
if (ID_IS_LINKED(object)) {
|
|
|
|
|
linked_objects = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ED_object_parent_set(
|
2020-03-12 10:51:49 +01:00
|
|
|
reports, C, scene, object, parent, parent_type, false, keep_transform, NULL)) {
|
2019-08-09 13:55:58 -06:00
|
|
|
parent_set = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (linked_objects) {
|
|
|
|
|
BKE_report(reports, RPT_INFO, "Can't edit library linked object(s)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (parent_set) {
|
|
|
|
|
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);
|
|
|
|
|
}
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int parent_drop_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-09 13:55:58 -06:00
|
|
|
if (event->custom != EVT_DATA_DRAGDROP) {
|
|
|
|
|
return OPERATOR_CANCELLED;
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-09 13:55:58 -06:00
|
|
|
ListBase *lb = event->customdata;
|
|
|
|
|
wmDrag *drag = lb->first;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2020-03-12 10:51:49 +01:00
|
|
|
parent_drop_set_parents(C, op->reports, drag->ids.first, par, PAR_OBJECT, event->alt);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
|
|
|
|
return OPERATOR_FINISHED;
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OUTLINER_OT_parent_drop(wmOperatorType *ot)
|
|
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
/* identifiers */
|
2020-03-12 10:51:49 +01:00
|
|
|
ot->name = "Drop to Set Parent [+Alt keeps transforms]";
|
2019-04-17 06:17:24 +02:00
|
|
|
ot->description = "Drag to parent in Outliner";
|
|
|
|
|
ot->idname = "OUTLINER_OT_parent_drop";
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* api callbacks */
|
|
|
|
|
ot->invoke = parent_drop_invoke;
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
ot->poll = ED_operator_outliner_active;
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* flags */
|
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ******************** Parent Clear Operator *********************** */
|
|
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
static bool parent_clear_poll(bContext *C,
|
|
|
|
|
wmDrag *drag,
|
|
|
|
|
const wmEvent *event,
|
2020-03-25 17:58:58 +11:00
|
|
|
const char **UNUSED(r_tooltip))
|
2018-08-07 10:23:07 +02:00
|
|
|
{
|
2020-08-07 11:47:23 -06:00
|
|
|
SpaceOutliner *space_outliner = CTX_wm_space_outliner(C);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2020-08-07 11:47:23 -06:00
|
|
|
if (!allow_parenting_without_modifier_key(space_outliner)) {
|
2019-04-17 06:17:24 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
2018-08-07 10:57:09 +02:00
|
|
|
static int parent_clear_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
|
2018-08-07 10:23:07 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
Main *bmain = CTX_data_main(C);
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-08-09 13:55:58 -06:00
|
|
|
if (event->custom != EVT_DATA_DRAGDROP) {
|
2019-04-17 06:17:24 +02:00
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
}
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-08-09 13:55:58 -06:00
|
|
|
ListBase *lb = event->customdata;
|
|
|
|
|
wmDrag *drag = lb->first;
|
|
|
|
|
|
2020-04-03 19:15:01 +02:00
|
|
|
LISTBASE_FOREACH (wmDragID *, drag_id, &drag->ids) {
|
2019-08-09 13:55:58 -06:00
|
|
|
if (GS(drag_id->id->name) == ID_OB) {
|
|
|
|
|
Object *object = (Object *)drag_id->id;
|
|
|
|
|
|
2020-03-12 10:51:49 +01:00
|
|
|
ED_object_parent_clear(object, event->alt ? CLEAR_PARENT_KEEP_TRANSFORM : CLEAR_PARENT_ALL);
|
2019-08-09 13:55:58 -06:00
|
|
|
}
|
|
|
|
|
}
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
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;
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OUTLINER_OT_parent_clear(wmOperatorType *ot)
|
|
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
/* identifiers */
|
2020-03-12 10:51:49 +01:00
|
|
|
ot->name = "Drop to Clear Parent [+Alt keeps transforms]";
|
2019-04-17 06:17:24 +02:00
|
|
|
ot->description = "Drag to clear parent in Outliner";
|
|
|
|
|
ot->idname = "OUTLINER_OT_parent_clear";
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* api callbacks */
|
|
|
|
|
ot->invoke = parent_clear_invoke;
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
ot->poll = ED_operator_outliner_active;
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* flags */
|
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ******************** Scene Drop Operator *********************** */
|
|
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
static bool scene_drop_poll(bContext *C,
|
|
|
|
|
wmDrag *drag,
|
|
|
|
|
const wmEvent *event,
|
2020-03-25 17:58:58 +11:00
|
|
|
const char **UNUSED(r_tooltip))
|
2018-08-07 10:23:07 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
/* 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));
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
2018-08-07 10:57:09 +02:00
|
|
|
static int scene_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
|
2018-08-07 10:23:07 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
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 */
|
2019-09-02 14:31:19 +02:00
|
|
|
collection = scene->master_collection;
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
collection = CTX_data_collection(C);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BKE_collection_object_add(bmain, collection, ob);
|
|
|
|
|
|
2020-04-03 19:15:01 +02:00
|
|
|
LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) {
|
2019-04-17 06:17:24 +02:00
|
|
|
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;
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OUTLINER_OT_scene_drop(wmOperatorType *ot)
|
|
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
/* identifiers */
|
|
|
|
|
ot->name = "Drop Object to Scene";
|
|
|
|
|
ot->description = "Drag object to scene in Outliner";
|
|
|
|
|
ot->idname = "OUTLINER_OT_scene_drop";
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* api callbacks */
|
|
|
|
|
ot->invoke = scene_drop_invoke;
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
ot->poll = ED_operator_outliner_active;
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* flags */
|
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ******************** Material Drop Operator *********************** */
|
|
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
static bool material_drop_poll(bContext *C,
|
|
|
|
|
wmDrag *drag,
|
|
|
|
|
const wmEvent *event,
|
2020-03-25 17:58:58 +11:00
|
|
|
const char **UNUSED(r_tooltip))
|
2018-08-07 10:23:07 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
/* 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));
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
2018-08-07 10:57:09 +02:00
|
|
|
static int material_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
|
2018-08-07 10:23:07 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
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);
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
if (ELEM(NULL, ob, ma)) {
|
|
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
}
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* only drop grease pencil material on grease pencil objects */
|
|
|
|
|
if ((ma->gp_style != NULL) && (ob->type != OB_GPENCIL)) {
|
|
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
}
|
2019-03-29 11:58:28 +01:00
|
|
|
|
2020-02-05 11:23:58 +01:00
|
|
|
BKE_object_material_assign(bmain, ob, ma, ob->totcol + 1, BKE_MAT_ASSIGN_USERPREF);
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
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);
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
return OPERATOR_FINISHED;
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OUTLINER_OT_material_drop(wmOperatorType *ot)
|
|
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
/* identifiers */
|
|
|
|
|
ot->name = "Drop Material on Object";
|
|
|
|
|
ot->description = "Drag material to object in Outliner";
|
|
|
|
|
ot->idname = "OUTLINER_OT_material_drop";
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* api callbacks */
|
|
|
|
|
ot->invoke = material_drop_invoke;
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
ot->poll = ED_operator_outliner_active;
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* flags */
|
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ******************** Collection Drop Operator *********************** */
|
|
|
|
|
|
2018-08-10 17:04:05 +02:00
|
|
|
typedef struct CollectionDrop {
|
2019-04-17 06:17:24 +02:00
|
|
|
Collection *from;
|
|
|
|
|
Collection *to;
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
TreeElement *te;
|
|
|
|
|
TreeElementInsertType insert_type;
|
2018-08-10 17:04:05 +02:00
|
|
|
} CollectionDrop;
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2018-08-10 17:04:05 +02:00
|
|
|
static Collection *collection_parent_from_ID(ID *id)
|
2018-08-07 10:23:07 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
/* 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;
|
|
|
|
|
}
|
2020-07-03 17:20:22 +02:00
|
|
|
if (GS(id->name) == ID_GR) {
|
2019-04-17 06:17:24 +02:00
|
|
|
return (Collection *)id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
static bool collection_drop_init(bContext *C,
|
|
|
|
|
wmDrag *drag,
|
|
|
|
|
const wmEvent *event,
|
|
|
|
|
CollectionDrop *data)
|
2018-08-07 10:23:07 +02:00
|
|
|
{
|
2020-08-07 11:47:23 -06:00
|
|
|
SpaceOutliner *space_outliner = CTX_wm_space_outliner(C);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
2020-05-13 17:30:39 +02:00
|
|
|
/* Currently this should not be allowed (might be supported in the future though...). */
|
|
|
|
|
if (ID_IS_OVERRIDE_LIBRARY(to_collection)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
|
|
|
|
/* 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);
|
2020-08-07 11:47:23 -06:00
|
|
|
if (event->ctrl || space_outliner->outlinevis == SO_SCENES) {
|
2019-04-17 06:17:24 +02:00
|
|
|
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;
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
static bool collection_drop_poll(bContext *C,
|
|
|
|
|
wmDrag *drag,
|
|
|
|
|
const wmEvent *event,
|
2020-03-25 17:58:58 +11:00
|
|
|
const char **r_tooltip)
|
2018-08-07 10:23:07 +02:00
|
|
|
{
|
2020-08-07 11:47:23 -06:00
|
|
|
SpaceOutliner *space_outliner = CTX_wm_space_outliner(C);
|
2020-03-06 16:56:42 +01:00
|
|
|
ARegion *region = CTX_wm_region(C);
|
2020-08-07 11:47:23 -06:00
|
|
|
bool changed = outliner_flag_set(&space_outliner->tree, TSE_HIGHLIGHTED | TSE_DRAG_ANY, false);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
|
|
|
|
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;
|
2020-03-25 17:58:58 +11:00
|
|
|
*r_tooltip = TIP_("Link inside Collection");
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
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)) {
|
2020-03-25 17:58:58 +11:00
|
|
|
*r_tooltip = TIP_("Move between collections");
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2020-03-25 17:58:58 +11:00
|
|
|
*r_tooltip = TIP_("Move before collection");
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case TE_INSERT_AFTER:
|
|
|
|
|
tselem->flag |= TSE_DRAG_AFTER;
|
|
|
|
|
changed = true;
|
|
|
|
|
if (te->next && outliner_is_collection_tree_element(te->next)) {
|
2020-03-25 17:58:58 +11:00
|
|
|
*r_tooltip = TIP_("Move between collections");
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2020-03-25 17:58:58 +11:00
|
|
|
*r_tooltip = TIP_("Move after collection");
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case TE_INSERT_INTO:
|
|
|
|
|
tselem->flag |= TSE_DRAG_INTO;
|
|
|
|
|
changed = true;
|
2020-03-25 17:58:58 +11:00
|
|
|
*r_tooltip = TIP_("Move inside collection (Ctrl to link, Shift to parent)");
|
2019-04-17 06:17:24 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (changed) {
|
2020-03-06 16:56:42 +01:00
|
|
|
ED_region_tag_redraw_no_rebuild(region);
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2020-07-03 17:20:22 +02:00
|
|
|
if (changed) {
|
|
|
|
|
ED_region_tag_redraw_no_rebuild(region);
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2020-07-03 17:20:22 +02:00
|
|
|
return false;
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
2018-08-10 17:04:05 +02:00
|
|
|
static int collection_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
|
2018-08-07 10:23:07 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
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)) {
|
2020-08-07 11:47:23 -06:00
|
|
|
SpaceOutliner *space_outliner = CTX_wm_space_outliner(C);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
|
|
|
|
relative = data.to;
|
|
|
|
|
relative_after = (data.insert_type == TE_INSERT_AFTER);
|
|
|
|
|
|
2020-08-07 11:47:23 -06:00
|
|
|
TreeElement *parent_te = outliner_find_parent_element(&space_outliner->tree, NULL, data.te);
|
2019-04-17 06:17:24 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-03 19:15:01 +02:00
|
|
|
LISTBASE_FOREACH (wmDragID *, drag_id, &drag->ids) {
|
2019-04-17 06:17:24 +02:00
|
|
|
/* 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;
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
2018-08-10 17:04:05 +02:00
|
|
|
void OUTLINER_OT_collection_drop(wmOperatorType *ot)
|
2018-08-07 10:23:07 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
/* identifiers */
|
|
|
|
|
ot->name = "Move to Collection";
|
|
|
|
|
ot->description = "Drag to move to collection in Outliner";
|
|
|
|
|
ot->idname = "OUTLINER_OT_collection_drop";
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* api callbacks */
|
|
|
|
|
ot->invoke = collection_drop_invoke;
|
|
|
|
|
ot->poll = ED_operator_outliner_active;
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* flags */
|
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
|
2018-08-10 17:04:05 +02:00
|
|
|
}
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2018-08-10 17:04:05 +02:00
|
|
|
/* ********************* Outliner Drag Operator ******************** */
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2020-07-21 10:12:35 -04:00
|
|
|
#define OUTLINER_DRAG_SCOLL_OUTSIDE_PAD 7 /* In UI units */
|
|
|
|
|
|
2020-08-07 11:47:23 -06:00
|
|
|
static TreeElement *outliner_item_drag_element_find(SpaceOutliner *space_outliner,
|
2020-03-06 16:56:42 +01:00
|
|
|
ARegion *region,
|
2019-04-17 06:17:24 +02:00
|
|
|
const wmEvent *event)
|
2018-08-10 17:04:05 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
/* note: using EVT_TWEAK_ events to trigger dragging is fine,
|
|
|
|
|
* it sends coordinates from where dragging was started */
|
2020-03-06 16:56:42 +01:00
|
|
|
const float my = UI_view2d_region_to_view_y(®ion->v2d, event->mval[1]);
|
2020-08-07 11:47:23 -06:00
|
|
|
return outliner_find_item_at_y(space_outliner, &space_outliner->tree, my);
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
static int outliner_item_drag_drop_invoke(bContext *C,
|
|
|
|
|
wmOperator *UNUSED(op),
|
|
|
|
|
const wmEvent *event)
|
2018-08-07 10:23:07 +02:00
|
|
|
{
|
2020-03-06 16:56:42 +01:00
|
|
|
ARegion *region = CTX_wm_region(C);
|
2020-08-07 11:47:23 -06:00
|
|
|
SpaceOutliner *space_outliner = CTX_wm_space_outliner(C);
|
|
|
|
|
TreeElement *te = outliner_item_drag_element_find(space_outliner, region, event);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-09 14:00:49 -06:00
|
|
|
float view_mval[2];
|
2020-03-06 16:56:42 +01:00
|
|
|
UI_view2d_region_to_view(
|
|
|
|
|
®ion->v2d, event->mval[0], event->mval[1], &view_mval[0], &view_mval[1]);
|
2019-08-09 14:00:49 -06:00
|
|
|
if (outliner_item_is_co_within_close_toggle(te, view_mval[0])) {
|
|
|
|
|
return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-21 10:12:35 -04:00
|
|
|
/* Scroll the view when dragging near edges, but not
|
|
|
|
|
* when the drag goes too far outside the region. */
|
|
|
|
|
{
|
|
|
|
|
wmOperatorType *ot = WM_operatortype_find("VIEW2D_OT_edge_pan", true);
|
|
|
|
|
PointerRNA op_ptr;
|
|
|
|
|
WM_operator_properties_create_ptr(&op_ptr, ot);
|
|
|
|
|
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_properties_free(&op_ptr);
|
|
|
|
|
}
|
2020-06-29 20:23:28 -06:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
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) {
|
2020-08-07 11:47:23 -06:00
|
|
|
outliner_flag_set(&space_outliner->tree, TSE_SELECTED, 0);
|
2019-04-17 06:17:24 +02:00
|
|
|
TREESTORE(te)->flag |= TSE_SELECTED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Gather all selected elements. */
|
|
|
|
|
struct IDsSelectedData selected = {
|
|
|
|
|
.selected_array = {NULL, NULL},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (GS(data.drag_id->name) == ID_OB) {
|
2020-08-07 11:47:23 -06:00
|
|
|
outliner_tree_traverse(space_outliner,
|
|
|
|
|
&space_outliner->tree,
|
|
|
|
|
0,
|
|
|
|
|
TSE_SELECTED,
|
|
|
|
|
outliner_find_selected_objects,
|
|
|
|
|
&selected);
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2020-08-07 11:47:23 -06:00
|
|
|
outliner_tree_traverse(space_outliner,
|
|
|
|
|
&space_outliner->tree,
|
|
|
|
|
0,
|
|
|
|
|
TSE_SELECTED,
|
|
|
|
|
outliner_find_selected_collections,
|
|
|
|
|
&selected);
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2019-09-02 14:31:19 +02:00
|
|
|
parent = scene->master_collection;
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-24 20:18:39 -06:00
|
|
|
ED_outliner_select_sync_from_all_tag(C);
|
|
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH);
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
2018-08-10 17:04:05 +02:00
|
|
|
/* 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. */
|
|
|
|
|
|
2018-08-07 10:23:07 +02:00
|
|
|
void OUTLINER_OT_item_drag_drop(wmOperatorType *ot)
|
|
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
ot->name = "Drag and Drop";
|
|
|
|
|
ot->idname = "OUTLINER_OT_item_drag_drop";
|
|
|
|
|
ot->description = "Drag and drop element to another place";
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
ot->invoke = outliner_item_drag_drop_invoke;
|
|
|
|
|
ot->poll = ED_operator_outliner_active;
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|
|
|
|
|
|
2020-07-21 10:12:35 -04:00
|
|
|
#undef OUTLINER_DRAG_SCOLL_OUTSIDE_PAD
|
|
|
|
|
|
2018-08-07 10:23:07 +02:00
|
|
|
/* *************************** Drop Boxes ************************** */
|
|
|
|
|
|
|
|
|
|
/* region dropbox definition */
|
|
|
|
|
void outliner_dropboxes(void)
|
|
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
ListBase *lb = WM_dropboxmap_find("Outliner", SPACE_OUTLINER, RGN_TYPE_WINDOW);
|
2018-08-07 10:23:07 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
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);
|
2018-08-07 10:23:07 +02:00
|
|
|
}
|