Cleanup: style, warning
[blender.git] / source / blender / editors / space_outliner / outliner_dragdrop.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2004 Blender Foundation.
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributor(s): Joshua Leung
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 /** \file blender/editors/space_outliner/outliner_dragdrop.c
29  *  \ingroup spoutliner
30  */
31
32 #include <string.h>
33
34 #include "MEM_guardedalloc.h"
35
36 #include "DNA_collection_types.h"
37 #include "DNA_material_types.h"
38 #include "DNA_object_types.h"
39 #include "DNA_space_types.h"
40
41 #include "BLI_listbase.h"
42 #include "BLI_string.h"
43
44 #include "BLT_translation.h"
45
46 #include "BKE_collection.h"
47 #include "BKE_context.h"
48 #include "BKE_layer.h"
49 #include "BKE_main.h"
50 #include "BKE_material.h"
51 #include "BKE_report.h"
52 #include "BKE_scene.h"
53 #include "BKE_object.h"
54
55 #include "DEG_depsgraph.h"
56 #include "DEG_depsgraph_build.h"
57
58 #include "ED_object.h"
59 #include "ED_outliner.h"
60 #include "ED_screen.h"
61
62 #include "UI_interface.h"
63 #include "UI_resources.h"
64 #include "UI_view2d.h"
65
66 #include "GPU_state.h"
67
68 #include "RNA_access.h"
69 #include "RNA_define.h"
70 #include "RNA_enum_types.h"
71
72 #include "WM_api.h"
73 #include "WM_types.h"
74
75 #include "outliner_intern.h"
76
77 /* ******************** Drop Target Find *********************** */
78
79 static TreeElement *outliner_dropzone_element(TreeElement *te, const float fmval[2], const bool children)
80 {
81         if ((fmval[1] > te->ys) && (fmval[1] < (te->ys + UI_UNIT_Y))) {
82                 /* name and first icon */
83                 if ((fmval[0] > te->xs + UI_UNIT_X) && (fmval[0] < te->xend))
84                         return te;
85         }
86         /* Not it.  Let's look at its children. */
87         if (children && (TREESTORE(te)->flag & TSE_CLOSED) == 0 && (te->subtree.first)) {
88                 for (te = te->subtree.first; te; te = te->next) {
89                         TreeElement *te_valid = outliner_dropzone_element(te, fmval, children);
90                         if (te_valid)
91                                 return te_valid;
92                 }
93         }
94         return NULL;
95 }
96
97 /* Find tree element to drop into. */
98 static TreeElement *outliner_dropzone_find(const SpaceOops *soops, const float fmval[2], const bool children)
99 {
100         TreeElement *te;
101
102         for (te = soops->tree.first; te; te = te->next) {
103                 TreeElement *te_valid = outliner_dropzone_element(te, fmval, children);
104                 if (te_valid)
105                         return te_valid;
106         }
107         return NULL;
108 }
109
110 static TreeElement *outliner_drop_find(bContext *C, const wmEvent *event)
111 {
112         ARegion *ar = CTX_wm_region(C);
113         SpaceOops *soops = CTX_wm_space_outliner(C);
114         float fmval[2];
115         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
116
117         return outliner_dropzone_find(soops, fmval, true);
118 }
119
120 static ID *outliner_ID_drop_find(bContext *C, const wmEvent *event, short idcode)
121 {
122         TreeElement *te = outliner_drop_find(C, event);
123         TreeStoreElem *tselem = (te) ? TREESTORE(te) : NULL;
124
125         if (te && te->idcode == idcode && tselem->type == 0) {
126                 return tselem->id;
127         }
128         else {
129                 return NULL;
130         }
131 }
132
133 /* Find tree element to drop into, with additional before and after reorder support. */
134 static TreeElement *outliner_drop_insert_find(
135         bContext *C, const wmEvent *event,
136         TreeElementInsertType *r_insert_type)
137 {
138         SpaceOops *soops = CTX_wm_space_outliner(C);
139         ARegion *ar = CTX_wm_region(C);
140         TreeElement *te_hovered;
141         float view_mval[2];
142
143         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &view_mval[0], &view_mval[1]);
144         te_hovered = outliner_find_item_at_y(soops, &soops->tree, view_mval[1]);
145
146         if (te_hovered) {
147                 /* mouse hovers an element (ignoring x-axis), now find out how to insert the dragged item exactly */
148                 const float margin = UI_UNIT_Y * (1.0f / 4);
149
150                 if (view_mval[1] < (te_hovered->ys + margin)) {
151                         if (TSELEM_OPEN(TREESTORE(te_hovered), soops)) {
152                                 /* inserting after a open item means we insert into it, but as first child */
153                                 if (BLI_listbase_is_empty(&te_hovered->subtree)) {
154                                         *r_insert_type = TE_INSERT_INTO;
155                                         return te_hovered;
156                                 }
157                                 else {
158                                         *r_insert_type = TE_INSERT_BEFORE;
159                                         return te_hovered->subtree.first;
160                                 }
161                         }
162                         else {
163                                 *r_insert_type = TE_INSERT_AFTER;
164                                 return te_hovered;
165                         }
166                 }
167                 else if (view_mval[1] > (te_hovered->ys + (3 * margin))) {
168                         *r_insert_type = TE_INSERT_BEFORE;
169                         return te_hovered;
170                 }
171                 else {
172                         *r_insert_type = TE_INSERT_INTO;
173                         return te_hovered;
174                 }
175         }
176         else {
177                 /* mouse doesn't hover any item (ignoring x-axis), so it's either above list bounds or below. */
178                 TreeElement *first = soops->tree.first;
179                 TreeElement *last = soops->tree.last;
180
181                 if (view_mval[1] < last->ys) {
182                         *r_insert_type = TE_INSERT_AFTER;
183                         return last;
184                 }
185                 else if (view_mval[1] > (first->ys + UI_UNIT_Y)) {
186                         *r_insert_type = TE_INSERT_BEFORE;
187                         return first;
188                 }
189                 else {
190                         BLI_assert(0);
191                         return NULL;
192                 }
193         }
194 }
195
196 static Collection *outliner_collection_from_tree_element_and_parents(TreeElement *te, TreeElement **r_te)
197 {
198         while (te != NULL) {
199                 Collection *collection = outliner_collection_from_tree_element(te);
200                 if (collection) {
201                         *r_te = te;
202                         return collection;
203                 }
204                 te = te->parent;
205         }
206         return NULL;
207 }
208
209 static TreeElement *outliner_drop_insert_collection_find(
210         bContext *C, const wmEvent *event,
211         TreeElementInsertType *r_insert_type)
212 {
213         TreeElement *te = outliner_drop_insert_find(C, event, r_insert_type);
214         if (!te) return NULL;
215
216         TreeElement *collection_te;
217         Collection *collection = outliner_collection_from_tree_element_and_parents(te, &collection_te);
218         if (!collection) return NULL;
219
220         if (collection_te != te) {
221                 *r_insert_type = TE_INSERT_INTO;
222         }
223
224         /* We can't insert before/after master collection. */
225         if (collection->flag & COLLECTION_IS_MASTER) {
226                 *r_insert_type = TE_INSERT_INTO;
227         }
228
229         return collection_te;
230 }
231
232 /* ******************** Parent Drop Operator *********************** */
233
234 static bool parent_drop_allowed(SpaceOops *soops, TreeElement *te, Object *potential_child)
235 {
236         TreeStoreElem *tselem = TREESTORE(te);
237         if (te->idcode != ID_OB || tselem->type != 0) {
238                 return false;
239         }
240
241         Object *potential_parent = (Object *)tselem->id;
242
243         if (potential_parent == potential_child) return false;
244         if (BKE_object_is_child_recursive(potential_child, potential_parent)) return false;
245         if (potential_parent == potential_child->parent) return false;
246
247         /* check that parent/child are both in the same scene */
248         Scene *scene = (Scene *)outliner_search_back(soops, te, ID_SCE);
249
250         /* currently outliner organized in a way that if there's no parent scene
251                 * element for object it means that all displayed objects belong to
252                 * active scene and parenting them is allowed (sergey)
253                 */
254         if (scene) {
255                 for (ViewLayer *view_layer = scene->view_layers.first;
256                      view_layer;
257                      view_layer = view_layer->next)
258                 {
259                         if (BKE_view_layer_base_find(view_layer, potential_child)) {
260                                 return true;
261                         }
262                 }
263                 return false;
264         }
265         else {
266                 return true;
267         }
268 }
269
270 static bool allow_parenting_without_modifier_key(SpaceOops *soops)
271 {
272         switch (soops->outlinevis) {
273                 case SO_VIEW_LAYER:
274                         return soops->filter & SO_FILTER_NO_COLLECTION;
275                 case SO_SCENES:
276                         return true;
277                 default:
278                         return false;
279         }
280 }
281
282 static bool parent_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event, const char **UNUSED(tooltip))
283 {
284         SpaceOops *soops = CTX_wm_space_outliner(C);
285
286         bool changed = outliner_flag_set(&soops->tree, TSE_DRAG_ANY, false);
287         if (changed) ED_region_tag_redraw_no_rebuild(CTX_wm_region(C));
288
289         Object *potential_child = (Object *)WM_drag_ID(drag, ID_OB);
290         if (!potential_child) return false;
291
292         if (!allow_parenting_without_modifier_key(soops)) {
293                 if (!event->shift) return false;
294         }
295
296         TreeElement *te = outliner_drop_find(C, event);
297         if (!te) return false;
298
299         if (parent_drop_allowed(soops, te, potential_child)) {
300                 TREESTORE(te)->flag |= TSE_DRAG_INTO;
301                 ED_region_tag_redraw_no_rebuild(CTX_wm_region(C));
302                 return true;
303         }
304
305         return false;
306 }
307
308 static int parent_drop_exec(bContext *C, wmOperator *op)
309 {
310         Object *par = NULL, *ob = NULL;
311         Main *bmain = CTX_data_main(C);
312         Scene *scene = CTX_data_scene(C);
313         int partype = -1;
314         char parname[MAX_NAME], childname[MAX_NAME];
315
316         partype = RNA_enum_get(op->ptr, "type");
317         RNA_string_get(op->ptr, "parent", parname);
318         par = (Object *)BKE_libblock_find_name(bmain, ID_OB, parname);
319         RNA_string_get(op->ptr, "child", childname);
320         ob = (Object *)BKE_libblock_find_name(bmain, ID_OB, childname);
321
322         if (ID_IS_LINKED(ob)) {
323                 BKE_report(op->reports, RPT_INFO, "Can't edit library linked object");
324                 return OPERATOR_CANCELLED;
325         }
326
327         ED_object_parent_set(op->reports, C, scene, ob, par, partype, false, false, NULL);
328
329         DEG_relations_tag_update(bmain);
330         WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL);
331         WM_event_add_notifier(C, NC_OBJECT | ND_PARENT, NULL);
332
333         return OPERATOR_FINISHED;
334 }
335
336 static int parent_drop_invoke(bContext *C, wmOperator *op, const wmEvent *event)
337 {
338         Main *bmain = CTX_data_main(C);
339         SpaceOops *soops = CTX_wm_space_outliner(C);
340         TreeElement *te = outliner_drop_find(C, event);
341         TreeStoreElem *tselem = te ? TREESTORE(te) : NULL;
342
343         if (!(te && te->idcode == ID_OB && tselem->type == 0)) {
344                 return OPERATOR_CANCELLED;
345         }
346
347         Object *par = (Object *)tselem->id;
348         Object *ob = (Object *)WM_drag_ID_from_event(event, ID_OB);
349
350         if (ELEM(NULL, ob, par)) {
351                 return OPERATOR_CANCELLED;
352         }
353         if (ob == par) {
354                 return OPERATOR_CANCELLED;
355         }
356         if (ID_IS_LINKED(ob)) {
357                 BKE_report(op->reports, RPT_INFO, "Can't edit library linked object");
358                 return OPERATOR_CANCELLED;
359         }
360
361         char childname[MAX_NAME];
362         char parname[MAX_NAME];
363         STRNCPY(childname, ob->id.name + 2);
364         STRNCPY(parname, par->id.name + 2);
365         RNA_string_set(op->ptr, "child", childname);
366         RNA_string_set(op->ptr, "parent", parname);
367
368         Scene *scene = (Scene *)outliner_search_back(soops, te, ID_SCE);
369
370         if (scene == NULL) {
371                 /* currently outlier organized in a way, that if there's no parent scene
372                  * element for object it means that all displayed objects belong to
373                  * active scene and parenting them is allowed (sergey)
374                  */
375
376                 scene = CTX_data_scene(C);
377         }
378
379         if ((par->type != OB_ARMATURE) && (par->type != OB_CURVE) && (par->type != OB_LATTICE)) {
380                 int partype = 0;
381                 if (ED_object_parent_set(op->reports, C, scene, ob, par, partype, false, false, NULL)) {
382                         DEG_relations_tag_update(bmain);
383                         WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL);
384                         WM_event_add_notifier(C, NC_OBJECT | ND_PARENT, NULL);
385                 }
386         }
387         else {
388                 /* Menu creation */
389                 wmOperatorType *ot = WM_operatortype_find("OUTLINER_OT_parent_drop", false);
390                 uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Set Parent To"), ICON_NONE);
391                 uiLayout *layout = UI_popup_menu_layout(pup);
392                 PointerRNA ptr;
393
394                 /* Cannot use uiItemEnumO()... have multiple properties to set. */
395                 uiItemFullO_ptr(layout, ot, IFACE_("Object"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
396                 RNA_string_set(&ptr, "parent", parname);
397                 RNA_string_set(&ptr, "child", childname);
398                 RNA_enum_set(&ptr, "type", PAR_OBJECT);
399
400                 /* par becomes parent, make the associated menus */
401                 if (par->type == OB_ARMATURE) {
402                         uiItemFullO_ptr(layout, ot, IFACE_("Armature Deform"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
403                         RNA_string_set(&ptr, "parent", parname);
404                         RNA_string_set(&ptr, "child", childname);
405                         RNA_enum_set(&ptr, "type", PAR_ARMATURE);
406
407                         uiItemFullO_ptr(layout, ot, IFACE_("   With Empty Groups"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
408                         RNA_string_set(&ptr, "parent", parname);
409                         RNA_string_set(&ptr, "child", childname);
410                         RNA_enum_set(&ptr, "type", PAR_ARMATURE_NAME);
411
412                         uiItemFullO_ptr(layout, ot, IFACE_("   With Envelope Weights"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
413                         RNA_string_set(&ptr, "parent", parname);
414                         RNA_string_set(&ptr, "child", childname);
415                         RNA_enum_set(&ptr, "type", PAR_ARMATURE_ENVELOPE);
416
417                         uiItemFullO_ptr(layout, ot, IFACE_("   With Automatic Weights"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
418                         RNA_string_set(&ptr, "parent", parname);
419                         RNA_string_set(&ptr, "child", childname);
420                         RNA_enum_set(&ptr, "type", PAR_ARMATURE_AUTO);
421
422                         uiItemFullO_ptr(layout, ot, IFACE_("Bone"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
423                         RNA_string_set(&ptr, "parent", parname);
424                         RNA_string_set(&ptr, "child", childname);
425                         RNA_enum_set(&ptr, "type", PAR_BONE);
426                 }
427                 else if (par->type == OB_CURVE) {
428                         uiItemFullO_ptr(layout, ot, IFACE_("Curve Deform"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
429                         RNA_string_set(&ptr, "parent", parname);
430                         RNA_string_set(&ptr, "child", childname);
431                         RNA_enum_set(&ptr, "type", PAR_CURVE);
432
433                         uiItemFullO_ptr(layout, ot, IFACE_("Follow Path"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
434                         RNA_string_set(&ptr, "parent", parname);
435                         RNA_string_set(&ptr, "child", childname);
436                         RNA_enum_set(&ptr, "type", PAR_FOLLOW);
437
438                         uiItemFullO_ptr(layout, ot, IFACE_("Path Constraint"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
439                         RNA_string_set(&ptr, "parent", parname);
440                         RNA_string_set(&ptr, "child", childname);
441                         RNA_enum_set(&ptr, "type", PAR_PATH_CONST);
442                 }
443                 else if (par->type == OB_LATTICE) {
444                         uiItemFullO_ptr(layout, ot, IFACE_("Lattice Deform"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
445                         RNA_string_set(&ptr, "parent", parname);
446                         RNA_string_set(&ptr, "child", childname);
447                         RNA_enum_set(&ptr, "type", PAR_LATTICE);
448                 }
449
450                 UI_popup_menu_end(C, pup);
451
452                 return OPERATOR_INTERFACE;
453         }
454
455         return OPERATOR_FINISHED;
456 }
457
458 void OUTLINER_OT_parent_drop(wmOperatorType *ot)
459 {
460         /* identifiers */
461         ot->name = "Drop to Set Parent";
462         ot->description = "Drag to parent in Outliner";
463         ot->idname = "OUTLINER_OT_parent_drop";
464
465         /* api callbacks */
466         ot->invoke = parent_drop_invoke;
467         ot->exec = parent_drop_exec;
468
469         ot->poll = ED_operator_outliner_active;
470
471         /* flags */
472         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
473
474         /* properties */
475         RNA_def_string(ot->srna, "child", "Object", MAX_NAME, "Child", "Child Object");
476         RNA_def_string(ot->srna, "parent", "Object", MAX_NAME, "Parent", "Parent Object");
477         RNA_def_enum(ot->srna, "type", prop_make_parent_types, 0, "Type", "");
478 }
479
480 /* ******************** Parent Clear Operator *********************** */
481
482 static bool parent_clear_poll(bContext *C, wmDrag *drag, const wmEvent *event, const char **UNUSED(tooltip))
483 {
484         SpaceOops *soops = CTX_wm_space_outliner(C);
485
486         if (!allow_parenting_without_modifier_key(soops)) {
487                 if (!event->shift) return false;
488         }
489
490         Object *ob = (Object *)WM_drag_ID(drag, ID_OB);
491         if (!ob) return false;
492         if (!ob->parent) return false;
493
494         TreeElement *te = outliner_drop_find(C, event);
495         if (te) {
496                 TreeStoreElem *tselem = TREESTORE(te);
497                 ID *id = tselem->id;
498                 if (!id) return true;
499
500                 switch (GS(id->name)) {
501                         case ID_OB:
502                                 return ELEM(tselem->type, TSE_MODIFIER_BASE, TSE_CONSTRAINT_BASE);
503                         case ID_GR:
504                                 return event->shift;
505                         default:
506                                 return true;
507                 }
508         }
509         else {
510                 return true;
511         }
512 }
513
514 static int parent_clear_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
515 {
516         Main *bmain = CTX_data_main(C);
517         Object *ob = (Object *)WM_drag_ID_from_event(event, ID_OB);
518
519         if (ob == NULL) {
520                 return OPERATOR_CANCELLED;
521         }
522
523         ED_object_parent_clear(ob, 0);
524
525         DEG_relations_tag_update(bmain);
526         WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL);
527         WM_event_add_notifier(C, NC_OBJECT | ND_PARENT, NULL);
528         return OPERATOR_FINISHED;
529 }
530
531 void OUTLINER_OT_parent_clear(wmOperatorType *ot)
532 {
533         /* identifiers */
534         ot->name = "Drop to Clear Parent";
535         ot->description = "Drag to clear parent in Outliner";
536         ot->idname = "OUTLINER_OT_parent_clear";
537
538         /* api callbacks */
539         ot->invoke = parent_clear_invoke;
540
541         ot->poll = ED_operator_outliner_active;
542
543         /* flags */
544         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
545 }
546
547 /* ******************** Scene Drop Operator *********************** */
548
549 static bool scene_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event, const char **UNUSED(tooltip))
550 {
551         /* Ensure item under cursor is valid drop target */
552         Object *ob = (Object *)WM_drag_ID(drag, ID_OB);
553         return (ob && (outliner_ID_drop_find(C, event, ID_SCE) != NULL));
554 }
555
556 static int scene_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
557 {
558         Main *bmain = CTX_data_main(C);
559         Scene *scene = (Scene *)outliner_ID_drop_find(C, event, ID_SCE);
560         Object *ob = (Object *)WM_drag_ID_from_event(event, ID_OB);
561
562         if (ELEM(NULL, ob, scene) || ID_IS_LINKED(scene)) {
563                 return OPERATOR_CANCELLED;
564         }
565
566         if (BKE_scene_has_object(scene, ob)) {
567                 return OPERATOR_CANCELLED;
568         }
569
570         Collection *collection;
571         if (scene != CTX_data_scene(C)) {
572                 /* when linking to an inactive scene link to the master collection */
573                 collection = BKE_collection_master(scene);
574         }
575         else {
576                 collection = CTX_data_collection(C);
577         }
578
579         BKE_collection_object_add(bmain, collection, ob);
580
581         for (ViewLayer *view_layer = scene->view_layers.first; view_layer; view_layer = view_layer->next) {
582                 Base *base = BKE_view_layer_base_find(view_layer, ob);
583                 if (base) {
584                         ED_object_base_select(base, BA_SELECT);
585                 }
586         }
587
588         DEG_relations_tag_update(bmain);
589
590         DEG_id_tag_update(&scene->id, DEG_TAG_SELECT_UPDATE);
591         WM_main_add_notifier(NC_SCENE | ND_OB_SELECT, scene);
592
593         return OPERATOR_FINISHED;
594 }
595
596 void OUTLINER_OT_scene_drop(wmOperatorType *ot)
597 {
598         /* identifiers */
599         ot->name = "Drop Object to Scene";
600         ot->description = "Drag object to scene in Outliner";
601         ot->idname = "OUTLINER_OT_scene_drop";
602
603         /* api callbacks */
604         ot->invoke = scene_drop_invoke;
605
606         ot->poll = ED_operator_outliner_active;
607
608         /* flags */
609         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
610 }
611
612 /* ******************** Material Drop Operator *********************** */
613
614 static bool material_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event, const char **UNUSED(tooltip))
615 {
616         /* Ensure item under cursor is valid drop target */
617         Material *ma = (Material *)WM_drag_ID(drag, ID_MA);
618         return (ma && (outliner_ID_drop_find(C, event, ID_OB) != NULL));
619 }
620
621 static int material_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
622 {
623         Main *bmain = CTX_data_main(C);
624         Object *ob = (Object *)outliner_ID_drop_find(C, event, ID_OB);
625         Material *ma = (Material *)WM_drag_ID_from_event(event, ID_MA);
626
627         if (ELEM(NULL, ob, ma)) {
628                 return OPERATOR_CANCELLED;
629         }
630
631         assign_material(bmain, ob, ma, ob->totcol + 1, BKE_MAT_ASSIGN_USERPREF);
632
633         WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, CTX_wm_view3d(C));
634         WM_event_add_notifier(C, NC_MATERIAL | ND_SHADING_LINKS, ma);
635
636         return OPERATOR_FINISHED;
637 }
638
639 void OUTLINER_OT_material_drop(wmOperatorType *ot)
640 {
641         /* identifiers */
642         ot->name = "Drop Material on Object";
643         ot->description = "Drag material to object in Outliner";
644         ot->idname = "OUTLINER_OT_material_drop";
645
646         /* api callbacks */
647         ot->invoke = material_drop_invoke;
648
649         ot->poll = ED_operator_outliner_active;
650
651         /* flags */
652         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
653 }
654
655 /* ******************** Collection Drop Operator *********************** */
656
657 typedef struct CollectionDrop {
658         Collection *from;
659         Collection *to;
660
661         TreeElement *te;
662         TreeElementInsertType insert_type;
663 } CollectionDrop;
664
665 static Collection *collection_parent_from_ID(ID *id)
666 {
667         /* Can't change linked parent collections. */
668         if (!id || ID_IS_LINKED(id)) {
669                 return NULL;
670         }
671
672         /* Also support dropping into/from scene collection. */
673         if (GS(id->name) == ID_SCE) {
674                 return ((Scene *)id)->master_collection;
675         }
676         else if (GS(id->name) == ID_GR) {
677                 return (Collection *)id;
678         }
679
680         return NULL;
681 }
682
683 static bool collection_drop_init(bContext *C, wmDrag *drag, const wmEvent *event, CollectionDrop *data)
684 {
685         SpaceOops *soops = CTX_wm_space_outliner(C);
686
687         /* Get collection to drop into. */
688         TreeElementInsertType insert_type;
689         TreeElement *te = outliner_drop_insert_collection_find(C, event, &insert_type);
690         if (!te) {
691                 return false;
692         }
693
694         Collection *to_collection = outliner_collection_from_tree_element(te);
695         if (ID_IS_LINKED(to_collection)) {
696                 return false;
697         }
698
699         /* Get drag datablocks. */
700         if (drag->type != WM_DRAG_ID) {
701                 return false;
702         }
703
704         wmDragID *drag_id = drag->ids.first;
705         if (drag_id == NULL) {
706                 return false;
707         }
708
709         ID *id = drag_id->id;
710         if (!(id && ELEM(GS(id->name), ID_GR, ID_OB))) {
711                 return false;
712         }
713
714         /* Get collection to drag out of. */
715         ID *parent = drag_id->from_parent;
716         Collection *from_collection = collection_parent_from_ID(parent);
717         if (event->ctrl || soops->outlinevis == SO_SCENES) {
718                 from_collection = NULL;
719         }
720
721         /* Get collections. */
722         if (GS(id->name) == ID_GR) {
723                 if (id == &to_collection->id) {
724                         return false;
725                 }
726         }
727         else {
728                 insert_type = TE_INSERT_INTO;
729         }
730
731         data->from = from_collection;
732         data->to = to_collection;
733         data->te = te;
734         data->insert_type = insert_type;
735
736         return true;
737 }
738
739 static bool collection_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event, const char **tooltip)
740 {
741         SpaceOops *soops = CTX_wm_space_outliner(C);
742         ARegion *ar = CTX_wm_region(C);
743         bool changed = outliner_flag_set(&soops->tree, TSE_HIGHLIGHTED | TSE_DRAG_ANY, false);
744
745         CollectionDrop data;
746         if (!event->shift && collection_drop_init(C, drag, event, &data)) {
747                 TreeElement *te = data.te;
748                 TreeStoreElem *tselem = TREESTORE(te);
749                 if (!data.from || event->ctrl) {
750                         tselem->flag |= TSE_DRAG_INTO;
751                         changed = true;
752                         *tooltip = IFACE_("Link inside Collection");
753                 }
754                 else {
755                         switch (data.insert_type) {
756                                 case TE_INSERT_BEFORE:
757                                         tselem->flag |= TSE_DRAG_BEFORE;
758                                         changed = true;
759                                         if (te->prev && outliner_is_collection_tree_element(te->prev)) {
760                                                 *tooltip = TIP_("Move between collections");
761                                         }
762                                         else {
763                                                 *tooltip = TIP_("Move before collection");
764                                         }
765                                         break;
766                                 case TE_INSERT_AFTER:
767                                         tselem->flag |= TSE_DRAG_AFTER;
768                                         changed = true;
769                                         if (te->next && outliner_is_collection_tree_element(te->next)) {
770                                                 *tooltip = TIP_("Move between collections");
771                                         }
772                                         else {
773                                                 *tooltip = TIP_("Move after collection");
774                                         }
775                                         break;
776                                 case TE_INSERT_INTO:
777                                         tselem->flag |= TSE_DRAG_INTO;
778                                         changed = true;
779                                         *tooltip = TIP_("Move inside collection (Ctrl to link, Shift to parent)");
780                                         break;
781                         }
782                 }
783                 if (changed) ED_region_tag_redraw_no_rebuild(ar);
784                 return true;
785         }
786         else {
787                 if (changed) ED_region_tag_redraw_no_rebuild(ar);
788                 return false;
789         }
790 }
791
792 static int collection_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
793 {
794         Main *bmain = CTX_data_main(C);
795         Scene *scene = CTX_data_scene(C);
796
797         if (event->custom != EVT_DATA_DRAGDROP) {
798                 return OPERATOR_CANCELLED;
799         }
800
801         ListBase *lb = event->customdata;
802         wmDrag *drag = lb->first;
803
804         CollectionDrop data;
805         if (!collection_drop_init(C, drag, event, &data)) {
806                 return OPERATOR_CANCELLED;
807         }
808
809         /* Before/after insert handling. */
810         Collection *relative = NULL;
811         bool relative_after = false;
812
813         if (ELEM(data.insert_type, TE_INSERT_BEFORE, TE_INSERT_AFTER)) {
814                 SpaceOops *soops = CTX_wm_space_outliner(C);
815
816                 relative = data.to;
817                 relative_after = (data.insert_type == TE_INSERT_AFTER);
818
819                 TreeElement *parent_te = outliner_find_parent_element(&soops->tree, NULL, data.te);
820                 data.to = (parent_te) ? outliner_collection_from_tree_element(parent_te) : NULL;
821         }
822
823         if (!data.to) {
824                 return OPERATOR_CANCELLED;
825         }
826
827         if (BKE_collection_is_empty(data.to)) {
828                 TREESTORE(data.te)->flag &= ~TSE_CLOSED;
829         }
830
831         for (wmDragID *drag_id = drag->ids.first; drag_id; drag_id = drag_id->next) {
832                 /* Ctrl enables linking, so we don't need a from collection then. */
833                 Collection *from = (event->ctrl) ? NULL : collection_parent_from_ID(drag_id->from_parent);
834
835                 if (GS(drag_id->id->name) == ID_OB) {
836                         /* Move/link object into collection. */
837                         Object *object = (Object *)drag_id->id;
838
839                         if (from) {
840                                 BKE_collection_object_move(bmain, scene, data.to, from, object);
841                         }
842                         else {
843                                 BKE_collection_object_add(bmain, data.to, object);
844                         }
845                 }
846                 else if (GS(drag_id->id->name) == ID_GR) {
847                         /* Move/link collection into collection. */
848                         Collection *collection = (Collection *)drag_id->id;
849
850                         if (collection != from) {
851                                 BKE_collection_move(bmain, data.to, from, relative, relative_after, collection);
852                         }
853                 }
854
855                 if (from) {
856                         DEG_id_tag_update(&from->id, DEG_TAG_COPY_ON_WRITE);
857                 }
858         }
859
860         /* Update dependency graph. */
861         DEG_id_tag_update(&data.to->id, DEG_TAG_COPY_ON_WRITE);
862         DEG_relations_tag_update(bmain);
863         WM_event_add_notifier(C, NC_SCENE | ND_LAYER, scene);
864
865         return OPERATOR_FINISHED;
866 }
867
868 void OUTLINER_OT_collection_drop(wmOperatorType *ot)
869 {
870         /* identifiers */
871         ot->name = "Move to Collection";
872         ot->description = "Drag to move to collection in Outliner";
873         ot->idname = "OUTLINER_OT_collection_drop";
874
875         /* api callbacks */
876         ot->invoke = collection_drop_invoke;
877         ot->poll = ED_operator_outliner_active;
878
879         /* flags */
880         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
881 }
882
883 /* ********************* Outliner Drag Operator ******************** */
884
885 static TreeElement *outliner_item_drag_element_find(SpaceOops *soops, ARegion *ar, const wmEvent *event)
886 {
887         /* note: using EVT_TWEAK_ events to trigger dragging is fine,
888          * it sends coordinates from where dragging was started */
889         const float my = UI_view2d_region_to_view_y(&ar->v2d, event->mval[1]);
890         return outliner_find_item_at_y(soops, &soops->tree, my);
891 }
892
893 static int outliner_item_drag_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
894 {
895         ARegion *ar = CTX_wm_region(C);
896         SpaceOops *soops = CTX_wm_space_outliner(C);
897         TreeElement *te = outliner_item_drag_element_find(soops, ar, event);
898
899         if (!te) {
900                 return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
901         }
902
903         TreeElementIcon data = tree_element_get_icon(TREESTORE(te), te);
904         if (!data.drag_id) {
905                 return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
906         }
907
908         wmDrag *drag = WM_event_start_drag(C, data.icon, WM_DRAG_ID, NULL, 0.0, WM_DRAG_NOP);
909
910         if (ELEM(GS(data.drag_id->name), ID_OB, ID_GR)) {
911                 /* For collections and objects we cheat and drag all selected. */
912
913                 /* Only drag element under mouse if it was not selected before. */
914                 if ((TREESTORE(te)->flag & TSE_SELECTED) == 0) {
915                         outliner_flag_set(&soops->tree, TSE_SELECTED, 0);
916                         TREESTORE(te)->flag |= TSE_SELECTED;
917                 }
918
919                 /* Gather all selected elements. */
920                 struct IDsSelectedData selected = {
921                         .selected_array  = {NULL, NULL},
922                 };
923
924                 if (GS(data.drag_id->name) == ID_OB) {
925                         outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, outliner_find_selected_objects, &selected);
926                 }
927                 else {
928                         outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, outliner_find_selected_collections, &selected);
929                 }
930
931                 LISTBASE_FOREACH (LinkData *, link, &selected.selected_array) {
932                         TreeElement *te_selected = (TreeElement *)link->data;
933                         ID *id;
934
935                         if (GS(data.drag_id->name) == ID_OB) {
936                                 id = TREESTORE(te_selected)->id;
937                         }
938                         else {
939                                 /* Keep collection hierarchies intact when dragging. */
940                                 bool parent_selected = false;
941                                 for (TreeElement *te_parent = te_selected->parent; te_parent; te_parent = te_parent->parent) {
942                                         if (outliner_is_collection_tree_element(te_parent)) {
943                                                 if (TREESTORE(te_parent)->flag & TSE_SELECTED) {
944                                                         parent_selected = true;
945                                                         break;
946                                                 }
947                                         }
948                                 }
949
950                                 if (parent_selected) {
951                                         continue;
952                                 }
953
954                                 id = &outliner_collection_from_tree_element(te_selected)->id;
955                         }
956
957                         /* Find parent collection. */
958                         Collection *parent = NULL;
959
960                         if (te_selected->parent) {
961                                 for (TreeElement *te_parent = te_selected->parent; te_parent; te_parent = te_parent->parent) {
962                                         if (outliner_is_collection_tree_element(te_parent)) {
963                                                 parent = outliner_collection_from_tree_element(te_parent);
964                                                 break;
965                                         }
966                                 }
967                         }
968                         else {
969                                 Scene *scene = CTX_data_scene(C);
970                                 parent = BKE_collection_master(scene);
971                         }
972
973                         WM_drag_add_ID(drag, id, &parent->id);
974                 }
975
976                 BLI_freelistN(&selected.selected_array);
977         }
978         else {
979                 /* Add single ID. */
980                 WM_drag_add_ID(drag, data.drag_id, data.drag_parent);
981         }
982
983         return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH);
984 }
985
986 /* Outliner drag and drop. This operator mostly exists to support dragging
987  * from outliner text instead of only from the icon, and also to show a
988  * hint in the statusbar keymap. */
989
990 void OUTLINER_OT_item_drag_drop(wmOperatorType *ot)
991 {
992         ot->name = "Drag and Drop";
993         ot->idname = "OUTLINER_OT_item_drag_drop";
994         ot->description = "Drag and drop element to another place";
995
996         ot->invoke = outliner_item_drag_drop_invoke;
997         ot->poll = ED_operator_outliner_active;
998 }
999
1000 /* *************************** Drop Boxes ************************** */
1001
1002 /* region dropbox definition */
1003 void outliner_dropboxes(void)
1004 {
1005         ListBase *lb = WM_dropboxmap_find("Outliner", SPACE_OUTLINER, RGN_TYPE_WINDOW);
1006
1007         WM_dropbox_add(lb, "OUTLINER_OT_parent_drop", parent_drop_poll, NULL);
1008         WM_dropbox_add(lb, "OUTLINER_OT_parent_clear", parent_clear_poll, NULL);
1009         WM_dropbox_add(lb, "OUTLINER_OT_scene_drop", scene_drop_poll, NULL);
1010         WM_dropbox_add(lb, "OUTLINER_OT_material_drop", material_drop_poll, NULL);
1011         WM_dropbox_add(lb, "OUTLINER_OT_collection_drop", collection_drop_poll, NULL);
1012 }