Merge branch 'master' into blender2.8
[blender.git] / source / blender / editors / space_outliner / outliner_edit.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_edit.c
29  *  \ingroup spoutliner
30  */
31
32 #include <string.h>
33
34 #include "MEM_guardedalloc.h"
35
36 #include "DNA_anim_types.h"
37 #include "DNA_group_types.h"
38 #include "DNA_ID.h"
39 #include "DNA_scene_types.h"
40 #include "DNA_object_types.h"
41 #include "DNA_material_types.h"
42
43 #include "BLI_blenlib.h"
44 #include "BLI_utildefines.h"
45 #include "BLI_path_util.h"
46 #include "BLI_mempool.h"
47 #include "BLI_stack.h"
48 #include "BLI_string.h"
49
50 #include "BLT_translation.h"
51
52 #include "BKE_animsys.h"
53 #include "BKE_collection.h"
54 #include "BKE_context.h"
55 #include "BKE_idcode.h"
56 #include "BKE_layer.h"
57 #include "BKE_library.h"
58 #include "BKE_library_query.h"
59 #include "BKE_library_remap.h"
60 #include "BKE_main.h"
61 #include "BKE_outliner_treehash.h"
62 #include "BKE_report.h"
63 #include "BKE_scene.h"
64 #include "BKE_material.h"
65
66 #include "DEG_depsgraph.h"
67 #include "DEG_depsgraph_build.h"
68
69 #include "../blenloader/BLO_readfile.h"
70
71 #include "ED_object.h"
72 #include "ED_outliner.h"
73 #include "ED_screen.h"
74 #include "ED_keyframing.h"
75 #include "ED_armature.h"
76
77 #include "WM_api.h"
78 #include "WM_types.h"
79
80 #include "UI_interface.h"
81 #include "UI_resources.h"
82 #include "UI_view2d.h"
83
84 #include "RNA_access.h"
85 #include "RNA_define.h"
86 #include "RNA_enum_types.h"
87
88 #include "GPU_material.h"
89
90 #include "outliner_intern.h"
91
92 /* ************************************************************** */
93 /* Unused Utilities */
94 // XXX: where to place these?
95
96 /* This is not used anywhere at the moment */
97 #if 0
98 static void outliner_open_reveal(SpaceOops *soops, ListBase *lb, TreeElement *teFind, int *found)
99 {
100         TreeElement *te;
101         TreeStoreElem *tselem;
102
103         for (te = lb->first; te; te = te->next) {
104                 /* check if this tree-element was the one we're seeking */
105                 if (te == teFind) {
106                         *found = 1;
107                         return;
108                 }
109
110                 /* try to see if sub-tree contains it then */
111                 outliner_open_reveal(soops, &te->subtree, teFind, found);
112                 if (*found) {
113                         tselem = TREESTORE(te);
114                         if (tselem->flag & TSE_CLOSED)
115                                 tselem->flag &= ~TSE_CLOSED;
116                         return;
117                 }
118         }
119 }
120 #endif
121
122 static TreeElement *outliner_dropzone_element(TreeElement *te, const float fmval[2], const bool children)
123 {
124         if ((fmval[1] > te->ys) && (fmval[1] < (te->ys + UI_UNIT_Y))) {
125                 /* name and first icon */
126                 if ((fmval[0] > te->xs + UI_UNIT_X) && (fmval[0] < te->xend))
127                         return te;
128         }
129         /* Not it.  Let's look at its children. */
130         if (children && (TREESTORE(te)->flag & TSE_CLOSED) == 0 && (te->subtree.first)) {
131                 for (te = te->subtree.first; te; te = te->next) {
132                         TreeElement *te_valid = outliner_dropzone_element(te, fmval, children);
133                         if (te_valid)
134                                 return te_valid;
135                 }
136         }
137         return NULL;
138 }
139
140 /* Used for drag and drop parenting */
141 TreeElement *outliner_dropzone_find(const SpaceOops *soops, const float fmval[2], const bool children)
142 {
143         TreeElement *te;
144
145         for (te = soops->tree.first; te; te = te->next) {
146                 TreeElement *te_valid = outliner_dropzone_element(te, fmval, children);
147                 if (te_valid)
148                         return te_valid;
149         }
150         return NULL;
151 }
152
153
154 /* ************************************************************** */
155
156 /* Highlight --------------------------------------------------- */
157
158 static int outliner_highlight_update(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
159 {
160         ARegion *ar = CTX_wm_region(C);
161         SpaceOops *soops = CTX_wm_space_outliner(C);
162         const float my = UI_view2d_region_to_view_y(&ar->v2d, event->mval[1]);
163
164         TreeElement *hovered_te = outliner_find_item_at_y(soops, &soops->tree, my);
165         bool changed = false;
166
167         if (!hovered_te || !(hovered_te->store_elem->flag & TSE_HIGHLIGHTED)) {
168                 changed = outliner_flag_set(&soops->tree, TSE_HIGHLIGHTED, false);
169                 if (hovered_te) {
170                         hovered_te->store_elem->flag |= TSE_HIGHLIGHTED;
171                         changed = true;
172                 }
173         }
174
175         if (changed) {
176                 ED_region_tag_redraw_no_rebuild(ar);
177         }
178
179         return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH);
180 }
181
182 void OUTLINER_OT_highlight_update(wmOperatorType *ot)
183 {
184         ot->name = "Update Highlight";
185         ot->idname = "OUTLINER_OT_highlight_update";
186         ot->description = "Update the item highlight based on the current mouse position";
187
188         ot->invoke = outliner_highlight_update;
189
190         ot->poll = ED_operator_outliner_active;
191 }
192
193 /* Toggle Open/Closed ------------------------------------------- */
194
195 static int do_outliner_item_openclose(bContext *C, SpaceOops *soops, TreeElement *te, const bool all, const float mval[2])
196 {
197
198         if (mval[1] > te->ys && mval[1] < te->ys + UI_UNIT_Y) {
199                 TreeStoreElem *tselem = TREESTORE(te);
200
201                 /* all below close/open? */
202                 if (all) {
203                         tselem->flag &= ~TSE_CLOSED;
204                         outliner_flag_set(&te->subtree, TSE_CLOSED, !outliner_flag_is_any_test(&te->subtree, TSE_CLOSED, 1));
205                 }
206                 else {
207                         if (tselem->flag & TSE_CLOSED) tselem->flag &= ~TSE_CLOSED;
208                         else tselem->flag |= TSE_CLOSED;
209                 }
210
211                 return 1;
212         }
213
214         for (te = te->subtree.first; te; te = te->next) {
215                 if (do_outliner_item_openclose(C, soops, te, all, mval))
216                         return 1;
217         }
218         return 0;
219
220 }
221
222 /* event can enterkey, then it opens/closes */
223 static int outliner_item_openclose(bContext *C, wmOperator *op, const wmEvent *event)
224 {
225         ARegion *ar = CTX_wm_region(C);
226         SpaceOops *soops = CTX_wm_space_outliner(C);
227         TreeElement *te;
228         float fmval[2];
229         const bool all = RNA_boolean_get(op->ptr, "all");
230
231         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
232
233         for (te = soops->tree.first; te; te = te->next) {
234                 if (do_outliner_item_openclose(C, soops, te, all, fmval))
235                         break;
236         }
237
238         ED_region_tag_redraw(ar);
239
240         return OPERATOR_FINISHED;
241 }
242
243 void OUTLINER_OT_item_openclose(wmOperatorType *ot)
244 {
245         ot->name = "Open/Close";
246         ot->idname = "OUTLINER_OT_item_openclose";
247         ot->description = "Toggle whether item under cursor is enabled or closed";
248
249         ot->invoke = outliner_item_openclose;
250
251         ot->poll = ED_operator_outliner_active;
252
253         RNA_def_boolean(ot->srna, "all", 1, "All", "Close or open all items");
254 }
255
256 /* -------------------------------------------------------------------- */
257 /** \name Object Mode Enter/Exit
258  * \{ */
259
260 static void item_object_mode_enter_exit(
261         bContext *C, ReportList *reports, Object *ob,
262         bool enter)
263 {
264         ViewLayer *view_layer = CTX_data_view_layer(C);
265         Object *obact = OBACT(view_layer);
266
267         if ((ob->type != obact->type) || ID_IS_LINKED(ob->data)) {
268                 return;
269         }
270         if (((ob->mode & obact->mode) != 0) == enter) {
271                 return;
272         }
273
274         if (ob == obact) {
275                 BKE_report(reports, RPT_WARNING, "Active object mode not changed");
276                 return;
277         }
278
279         Base *base = BKE_view_layer_base_find(view_layer, ob);
280         if (base == NULL) {
281                 return;
282         }
283         Scene *scene = CTX_data_scene(C);
284         outliner_object_mode_toggle(C, scene, view_layer, base);
285 }
286
287 void item_object_mode_enter_cb(
288         bContext *C, ReportList *reports, Scene *UNUSED(scene), TreeElement *UNUSED(te),
289         TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem, void *UNUSED(user_data))
290 {
291         Object *ob = (Object *)tselem->id;
292         item_object_mode_enter_exit(C, reports, ob, true);
293 }
294
295 void item_object_mode_exit_cb(
296         bContext *C, ReportList *reports, Scene *UNUSED(scene), TreeElement *UNUSED(te),
297         TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem, void *UNUSED(user_data))
298 {
299         Object *ob = (Object *)tselem->id;
300         item_object_mode_enter_exit(C, reports, ob, false);
301 }
302
303 /** \} */
304
305 /* Rename --------------------------------------------------- */
306
307 static void do_item_rename(ARegion *ar, TreeElement *te, TreeStoreElem *tselem,
308                            ReportList *reports)
309 {
310         bool add_textbut = false;
311
312         /* can't rename rna datablocks entries or listbases */
313         if (ELEM(tselem->type, TSE_RNA_STRUCT, TSE_RNA_PROPERTY, TSE_RNA_ARRAY_ELEM, TSE_ID_BASE, TSE_SCENE_OBJECTS_BASE)) {
314                 /* do nothing */;
315         }
316         else if (ELEM(tselem->type, TSE_ANIM_DATA, TSE_NLA, TSE_DEFGROUP_BASE, TSE_CONSTRAINT_BASE, TSE_MODIFIER_BASE,
317                       TSE_DRIVER_BASE, TSE_POSE_BASE, TSE_POSEGRP_BASE, TSE_R_LAYER_BASE, TSE_SCENE_COLLECTION_BASE,
318                       TSE_VIEW_COLLECTION_BASE))
319         {
320                 BKE_report(reports, RPT_WARNING, "Cannot edit builtin name");
321         }
322         else if (ELEM(tselem->type, TSE_SEQUENCE, TSE_SEQ_STRIP, TSE_SEQUENCE_DUP)) {
323                 BKE_report(reports, RPT_WARNING, "Cannot edit sequence name");
324         }
325         else if (outliner_is_collection_tree_element(te)) {
326                 Collection *collection = outliner_collection_from_tree_element(te);
327
328                 if (collection->flag & COLLECTION_IS_MASTER) {
329                         BKE_report(reports, RPT_WARNING, "Cannot edit name of master collection");
330                 }
331                 else {
332                         add_textbut = true;
333                 }
334         }
335         else if (ID_IS_LINKED(tselem->id)) {
336                 BKE_report(reports, RPT_WARNING, "Cannot edit external libdata");
337         }
338         else if (te->idcode == ID_LI && ((Library *)tselem->id)->parent) {
339                 BKE_report(reports, RPT_WARNING, "Cannot edit the path of an indirectly linked library");
340         }
341         else {
342                 add_textbut = true;
343         }
344
345         if (add_textbut) {
346                 tselem->flag |= TSE_TEXTBUT;
347                 ED_region_tag_redraw(ar);
348         }
349 }
350
351 void item_rename_cb(
352         bContext *C, ReportList *reports, Scene *UNUSED(scene), TreeElement *te,
353         TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem, void *UNUSED(user_data))
354 {
355         ARegion *ar = CTX_wm_region(C);
356         do_item_rename(ar, te, tselem, reports);
357 }
358
359 static int do_outliner_item_rename(ReportList *reports, ARegion *ar, TreeElement *te,
360                                    const float mval[2])
361 {
362         if (mval[1] > te->ys && mval[1] < te->ys + UI_UNIT_Y) {
363                 TreeStoreElem *tselem = TREESTORE(te);
364
365                 /* click on name */
366                 if (mval[0] > te->xs + UI_UNIT_X * 2 && mval[0] < te->xend) {
367                         do_item_rename(ar, te, tselem, reports);
368                         return 1;
369                 }
370                 return 0;
371         }
372
373         for (te = te->subtree.first; te; te = te->next) {
374                 if (do_outliner_item_rename(reports, ar, te, mval)) return 1;
375         }
376         return 0;
377 }
378
379 static int outliner_item_rename(bContext *C, wmOperator *op, const wmEvent *event)
380 {
381         ARegion *ar = CTX_wm_region(C);
382         SpaceOops *soops = CTX_wm_space_outliner(C);
383         TreeElement *te;
384         float fmval[2];
385         bool changed = false;
386
387         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
388
389         for (te = soops->tree.first; te; te = te->next) {
390                 if (do_outliner_item_rename(op->reports, ar, te, fmval)) {
391                         changed = true;
392                         break;
393                 }
394         }
395
396         return changed ? OPERATOR_FINISHED : OPERATOR_PASS_THROUGH;
397 }
398
399
400 void OUTLINER_OT_item_rename(wmOperatorType *ot)
401 {
402         ot->name = "Rename";
403         ot->idname = "OUTLINER_OT_item_rename";
404         ot->description = "Rename item under cursor";
405
406         ot->invoke = outliner_item_rename;
407
408         ot->poll = ED_operator_outliner_active;
409 }
410
411 /* ID delete --------------------------------------------------- */
412
413 static void id_delete(bContext *C, ReportList *reports, TreeElement *te, TreeStoreElem *tselem)
414 {
415         Main *bmain = CTX_data_main(C);
416         ID *id = tselem->id;
417
418         BLI_assert(te->idcode != 0 && id != NULL);
419         UNUSED_VARS_NDEBUG(te);
420
421         if (te->idcode == ID_LI && ((Library *)id)->parent != NULL) {
422                 BKE_reportf(reports, RPT_WARNING, "Cannot delete indirectly linked library '%s'", id->name);
423                 return;
424         }
425         if (id->tag & LIB_TAG_INDIRECT) {
426                 BKE_reportf(reports, RPT_WARNING, "Cannot delete indirectly linked id '%s'", id->name);
427                 return;
428         }
429         else if (BKE_library_ID_is_indirectly_used(bmain, id) && ID_REAL_USERS(id) <= 1) {
430                 BKE_reportf(reports, RPT_WARNING,
431                             "Cannot delete id '%s', indirectly used data-blocks need at least one user",
432                             id->name);
433                 return;
434         }
435
436
437         BKE_libblock_delete(bmain, id);
438
439         WM_event_add_notifier(C, NC_WINDOW, NULL);
440 }
441
442 void id_delete_cb(
443         bContext *C, ReportList *reports, Scene *UNUSED(scene),
444         TreeElement *te, TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem, void *UNUSED(user_data))
445 {
446         id_delete(C, reports, te, tselem);
447 }
448
449 static int outliner_id_delete_invoke_do(bContext *C, ReportList *reports, TreeElement *te, const float mval[2])
450 {
451         if (mval[1] > te->ys && mval[1] < te->ys + UI_UNIT_Y) {
452                 TreeStoreElem *tselem = TREESTORE(te);
453
454                 if (te->idcode != 0 && tselem->id) {
455                         if (te->idcode == ID_LI && ((Library *)tselem->id)->parent) {
456                                 BKE_reportf(reports, RPT_ERROR_INVALID_INPUT,
457                                             "Cannot delete indirectly linked library '%s'", ((Library *)tselem->id)->filepath);
458                                 return OPERATOR_CANCELLED;
459                         }
460                         id_delete(C, reports, te, tselem);
461                         return OPERATOR_FINISHED;
462                 }
463         }
464         else {
465                 for (te = te->subtree.first; te; te = te->next) {
466                         int ret;
467                         if ((ret = outliner_id_delete_invoke_do(C, reports, te, mval))) {
468                                 return ret;
469                         }
470                 }
471         }
472
473         return 0;
474 }
475
476 static int outliner_id_delete_invoke(bContext *C, wmOperator *op, const wmEvent *event)
477 {
478         ARegion *ar = CTX_wm_region(C);
479         SpaceOops *soops = CTX_wm_space_outliner(C);
480         TreeElement *te;
481         float fmval[2];
482
483         BLI_assert(ar && soops);
484
485         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
486
487         for (te = soops->tree.first; te; te = te->next) {
488                 int ret;
489
490                 if ((ret = outliner_id_delete_invoke_do(C, op->reports, te, fmval))) {
491                         return ret;
492                 }
493         }
494
495         return OPERATOR_CANCELLED;
496 }
497
498 void OUTLINER_OT_id_delete(wmOperatorType *ot)
499 {
500         ot->name = "Delete Data-Block";
501         ot->idname = "OUTLINER_OT_id_delete";
502         ot->description = "Delete the ID under cursor";
503
504         ot->invoke = outliner_id_delete_invoke;
505         ot->poll = ED_operator_outliner_active;
506 }
507
508 /* ID remap --------------------------------------------------- */
509
510 static int outliner_id_remap_exec(bContext *C, wmOperator *op)
511 {
512         Main *bmain = CTX_data_main(C);
513         SpaceOops *soops = CTX_wm_space_outliner(C);
514
515         const short id_type = (short)RNA_enum_get(op->ptr, "id_type");
516         ID *old_id = BLI_findlink(which_libbase(CTX_data_main(C), id_type), RNA_enum_get(op->ptr, "old_id"));
517         ID *new_id = BLI_findlink(which_libbase(CTX_data_main(C), id_type), RNA_enum_get(op->ptr, "new_id"));
518
519         /* check for invalid states */
520         if (soops == NULL) {
521                 return OPERATOR_CANCELLED;
522         }
523
524         if (!(old_id && new_id && (old_id != new_id) && (GS(old_id->name) == GS(new_id->name)))) {
525                 BKE_reportf(op->reports, RPT_ERROR_INVALID_INPUT, "Invalid old/new ID pair ('%s' / '%s')",
526                             old_id ? old_id->name : "Invalid ID", new_id ? new_id->name : "Invalid ID");
527                 return OPERATOR_CANCELLED;
528         }
529
530         if (ID_IS_LINKED(old_id)) {
531                 BKE_reportf(op->reports, RPT_WARNING,
532                             "Old ID '%s' is linked from a library, indirect usages of this data-block will not be remapped",
533                             old_id->name);
534         }
535
536         BKE_libblock_remap(bmain, old_id, new_id,
537                            ID_REMAP_SKIP_INDIRECT_USAGE | ID_REMAP_SKIP_NEVER_NULL_USAGE);
538
539         BKE_main_lib_objects_recalc_all(bmain);
540
541         /* recreate dependency graph to include new objects */
542         DEG_relations_tag_update(bmain);
543
544         /* free gpu materials, some materials depend on existing objects, such as lamps so freeing correctly refreshes */
545         GPU_materials_free(bmain);
546
547         WM_event_add_notifier(C, NC_WINDOW, NULL);
548
549         return OPERATOR_FINISHED;
550 }
551
552 static bool outliner_id_remap_find_tree_element(bContext *C, wmOperator *op, ListBase *tree, const float y)
553 {
554         TreeElement *te;
555
556         for (te = tree->first; te; te = te->next) {
557                 if (y > te->ys && y < te->ys + UI_UNIT_Y) {
558                         TreeStoreElem *tselem = TREESTORE(te);
559
560                         if (tselem->type == 0 && tselem->id) {
561                                 printf("found id %s (%p)!\n", tselem->id->name, tselem->id);
562
563                                 RNA_enum_set(op->ptr, "id_type", GS(tselem->id->name));
564                                 RNA_enum_set_identifier(C, op->ptr, "new_id", tselem->id->name + 2);
565                                 RNA_enum_set_identifier(C, op->ptr, "old_id", tselem->id->name + 2);
566                                 return true;
567                         }
568                 }
569                 if (outliner_id_remap_find_tree_element(C, op, &te->subtree, y)) {
570                         return true;
571                 }
572         }
573         return false;
574 }
575
576 static int outliner_id_remap_invoke(bContext *C, wmOperator *op, const wmEvent *event)
577 {
578         SpaceOops *soops = CTX_wm_space_outliner(C);
579         ARegion *ar = CTX_wm_region(C);
580         float fmval[2];
581
582         if (!RNA_property_is_set(op->ptr, RNA_struct_find_property(op->ptr, "id_type"))) {
583                 UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
584
585                 outliner_id_remap_find_tree_element(C, op, &soops->tree, fmval[1]);
586         }
587
588         return WM_operator_props_dialog_popup(C, op, 200, 100);
589 }
590
591 static const EnumPropertyItem *outliner_id_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free)
592 {
593         EnumPropertyItem item_tmp = {0}, *item = NULL;
594         int totitem = 0;
595         int i = 0;
596
597         short id_type = (short)RNA_enum_get(ptr, "id_type");
598         ID *id = which_libbase(CTX_data_main(C), id_type)->first;
599
600         for (; id; id = id->next) {
601                 item_tmp.identifier = item_tmp.name = id->name + 2;
602                 item_tmp.value = i++;
603                 RNA_enum_item_add(&item, &totitem, &item_tmp);
604         }
605
606         RNA_enum_item_end(&item, &totitem);
607         *r_free = true;
608
609         return item;
610 }
611
612 void OUTLINER_OT_id_remap(wmOperatorType *ot)
613 {
614         PropertyRNA *prop;
615
616         /* identifiers */
617         ot->name = "Outliner ID data Remap";
618         ot->idname = "OUTLINER_OT_id_remap";
619         ot->description = "";
620
621         /* callbacks */
622         ot->invoke = outliner_id_remap_invoke;
623         ot->exec = outliner_id_remap_exec;
624         ot->poll = ED_operator_outliner_active;
625
626         ot->flag = 0;
627
628         prop = RNA_def_enum(ot->srna, "id_type", rna_enum_id_type_items, ID_OB, "ID Type", "");
629         RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_ID);
630
631         prop = RNA_def_enum(ot->srna, "old_id", DummyRNA_NULL_items, 0, "Old ID", "Old ID to replace");
632         RNA_def_property_enum_funcs_runtime(prop, NULL, NULL, outliner_id_itemf);
633         RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE | PROP_HIDDEN);
634
635         ot->prop = RNA_def_enum(ot->srna, "new_id", DummyRNA_NULL_items, 0,
636                                 "New ID", "New ID to remap all selected IDs' users to");
637         RNA_def_property_enum_funcs_runtime(ot->prop, NULL, NULL, outliner_id_itemf);
638         RNA_def_property_flag(ot->prop, PROP_ENUM_NO_TRANSLATE);
639 }
640
641 void id_remap_cb(
642         bContext *C, ReportList *UNUSED(reports), Scene *UNUSED(scene), TreeElement *UNUSED(te),
643         TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem, void *UNUSED(user_data))
644 {
645         wmOperatorType *ot = WM_operatortype_find("OUTLINER_OT_id_remap", false);
646         PointerRNA op_props;
647
648         BLI_assert(tselem->id != NULL);
649
650         WM_operator_properties_create_ptr(&op_props, ot);
651
652         RNA_enum_set(&op_props, "id_type", GS(tselem->id->name));
653         RNA_enum_set_identifier(C, &op_props, "old_id", tselem->id->name + 2);
654
655         WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &op_props);
656
657         WM_operator_properties_free(&op_props);
658 }
659
660 /* Library relocate/reload --------------------------------------------------- */
661
662 static int lib_relocate(
663         bContext *C, TreeElement *te, TreeStoreElem *tselem, wmOperatorType *ot, const bool reload)
664 {
665         PointerRNA op_props;
666         int ret = 0;
667
668         BLI_assert(te->idcode == ID_LI && tselem->id != NULL);
669         UNUSED_VARS_NDEBUG(te);
670
671         WM_operator_properties_create_ptr(&op_props, ot);
672
673         RNA_string_set(&op_props, "library", tselem->id->name + 2);
674
675         if (reload) {
676                 Library *lib = (Library *)tselem->id;
677                 char dir[FILE_MAXDIR], filename[FILE_MAX];
678
679                 BLI_split_dirfile(lib->filepath, dir, filename, sizeof(dir), sizeof(filename));
680
681                 printf("%s, %s\n", tselem->id->name, lib->filepath);
682
683                 /* We assume if both paths in lib are not the same then lib->name was relative... */
684                 RNA_boolean_set(&op_props, "relative_path", BLI_path_cmp(lib->filepath, lib->name) != 0);
685
686                 RNA_string_set(&op_props, "directory", dir);
687                 RNA_string_set(&op_props, "filename", filename);
688
689                 ret = WM_operator_name_call_ptr(C, ot, WM_OP_EXEC_DEFAULT, &op_props);
690         }
691         else {
692                 ret = WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &op_props);
693         }
694
695         WM_operator_properties_free(&op_props);
696
697         return ret;
698 }
699
700 static int outliner_lib_relocate_invoke_do(
701         bContext *C, ReportList *reports, TreeElement *te, const float mval[2], const bool reload)
702 {
703         if (mval[1] > te->ys && mval[1] < te->ys + UI_UNIT_Y) {
704                 TreeStoreElem *tselem = TREESTORE(te);
705
706                 if (te->idcode == ID_LI && tselem->id) {
707                         if (((Library *)tselem->id)->parent && !reload) {
708                                 BKE_reportf(reports, RPT_ERROR_INVALID_INPUT,
709                                             "Cannot relocate indirectly linked library '%s'", ((Library *)tselem->id)->filepath);
710                                 return OPERATOR_CANCELLED;
711                         }
712                         else {
713                                 wmOperatorType *ot = WM_operatortype_find(reload ? "WM_OT_lib_reload" : "WM_OT_lib_relocate", false);
714
715                                 return lib_relocate(C, te, tselem, ot, reload);
716                         }
717                 }
718         }
719         else {
720                 for (te = te->subtree.first; te; te = te->next) {
721                         int ret;
722                         if ((ret = outliner_lib_relocate_invoke_do(C, reports, te, mval, reload))) {
723                                 return ret;
724                         }
725                 }
726         }
727
728         return 0;
729 }
730
731 static int outliner_lib_relocate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
732 {
733         ARegion *ar = CTX_wm_region(C);
734         SpaceOops *soops = CTX_wm_space_outliner(C);
735         TreeElement *te;
736         float fmval[2];
737
738         BLI_assert(ar && soops);
739
740         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
741
742         for (te = soops->tree.first; te; te = te->next) {
743                 int ret;
744
745                 if ((ret = outliner_lib_relocate_invoke_do(C, op->reports, te, fmval, false))) {
746                         return ret;
747                 }
748         }
749
750         return OPERATOR_CANCELLED;
751 }
752
753 void OUTLINER_OT_lib_relocate(wmOperatorType *ot)
754 {
755         ot->name = "Relocate Library";
756         ot->idname = "OUTLINER_OT_lib_relocate";
757         ot->description = "Relocate the library under cursor";
758
759         ot->invoke = outliner_lib_relocate_invoke;
760         ot->poll = ED_operator_outliner_active;
761 }
762
763 /* XXX This does not work with several items
764  *     (it is only called once in the end, due to the 'deferred' filebrowser invocation through event system...). */
765 void lib_relocate_cb(
766         bContext *C, ReportList *UNUSED(reports), Scene *UNUSED(scene), TreeElement *te,
767         TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem, void *UNUSED(user_data))
768 {
769         wmOperatorType *ot = WM_operatortype_find("WM_OT_lib_relocate", false);
770
771         lib_relocate(C, te, tselem, ot, false);
772 }
773
774
775 static int outliner_lib_reload_invoke(bContext *C, wmOperator *op, const wmEvent *event)
776 {
777         ARegion *ar = CTX_wm_region(C);
778         SpaceOops *soops = CTX_wm_space_outliner(C);
779         TreeElement *te;
780         float fmval[2];
781
782         BLI_assert(ar && soops);
783
784         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
785
786         for (te = soops->tree.first; te; te = te->next) {
787                 int ret;
788
789                 if ((ret = outliner_lib_relocate_invoke_do(C, op->reports, te, fmval, true))) {
790                         return ret;
791                 }
792         }
793
794         return OPERATOR_CANCELLED;
795 }
796
797 void OUTLINER_OT_lib_reload(wmOperatorType *ot)
798 {
799         ot->name = "Reload Library";
800         ot->idname = "OUTLINER_OT_lib_reload";
801         ot->description = "Reload the library under cursor";
802
803         ot->invoke = outliner_lib_reload_invoke;
804         ot->poll = ED_operator_outliner_active;
805 }
806
807 void lib_reload_cb(
808         bContext *C, ReportList *UNUSED(reports), Scene *UNUSED(scene), TreeElement *te,
809         TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem, void *UNUSED(user_data))
810 {
811         wmOperatorType *ot = WM_operatortype_find("WM_OT_lib_reload", false);
812
813         lib_relocate(C, te, tselem, ot, true);
814 }
815
816 /* ************************************************************** */
817 /* Setting Toggling Operators */
818
819 /* =============================================== */
820 /* Toggling Utilities (Exported) */
821
822 /* Apply Settings ------------------------------- */
823
824 static int outliner_count_levels(ListBase *lb, const int curlevel)
825 {
826         TreeElement *te;
827         int level = curlevel, lev;
828
829         for (te = lb->first; te; te = te->next) {
830
831                 lev = outliner_count_levels(&te->subtree, curlevel + 1);
832                 if (lev > level) level = lev;
833         }
834         return level;
835 }
836
837 int outliner_flag_is_any_test(ListBase *lb, short flag, const int curlevel)
838 {
839         TreeElement *te;
840         TreeStoreElem *tselem;
841         int level;
842
843         for (te = lb->first; te; te = te->next) {
844                 tselem = TREESTORE(te);
845                 if (tselem->flag & flag) return curlevel;
846
847                 level = outliner_flag_is_any_test(&te->subtree, flag, curlevel + 1);
848                 if (level) return level;
849         }
850         return 0;
851 }
852
853 /**
854  * Set or unset \a flag for all outliner elements in \a lb and sub-trees.
855  * \return if any flag was modified.
856  */
857 bool outliner_flag_set(ListBase *lb, short flag, short set)
858 {
859         TreeElement *te;
860         TreeStoreElem *tselem;
861         bool changed = false;
862         bool has_flag;
863
864         for (te = lb->first; te; te = te->next) {
865                 tselem = TREESTORE(te);
866                 has_flag = (tselem->flag & flag);
867                 if (set == 0) {
868                         if (has_flag) {
869                                 tselem->flag &= ~flag;
870                                 changed = true;
871                         }
872                 }
873                 else if (!has_flag) {
874                         tselem->flag |= flag;
875                         changed = true;
876                 }
877                 changed |= outliner_flag_set(&te->subtree, flag, set);
878         }
879
880         return changed;
881 }
882
883 /* Restriction Columns ------------------------------- */
884
885 /* same check needed for both object operation and restrict column button func
886  * return 0 when in edit mode (cannot restrict view or select)
887  * otherwise return 1 */
888 int common_restrict_check(bContext *C, Object *ob)
889 {
890         /* Don't allow hide an object in edit mode,
891          * check the bug #22153 and #21609, #23977
892          */
893         Object *obedit = CTX_data_edit_object(C);
894         if (obedit && obedit == ob) {
895                 /* found object is hidden, reset */
896                 if (ob->restrictflag & OB_RESTRICT_VIEW)
897                         ob->restrictflag &= ~OB_RESTRICT_VIEW;
898                 /* found object is unselectable, reset */
899                 if (ob->restrictflag & OB_RESTRICT_SELECT)
900                         ob->restrictflag &= ~OB_RESTRICT_SELECT;
901                 return 0;
902         }
903
904         return 1;
905 }
906
907 /* =============================================== */
908 /* Outliner setting toggles */
909
910 /* Toggle Expanded (Outliner) ---------------------------------------- */
911
912 static int outliner_toggle_expanded_exec(bContext *C, wmOperator *UNUSED(op))
913 {
914         SpaceOops *soops = CTX_wm_space_outliner(C);
915         ARegion *ar = CTX_wm_region(C);
916
917         if (outliner_flag_is_any_test(&soops->tree, TSE_CLOSED, 1))
918                 outliner_flag_set(&soops->tree, TSE_CLOSED, 0);
919         else
920                 outliner_flag_set(&soops->tree, TSE_CLOSED, 1);
921
922         ED_region_tag_redraw(ar);
923
924         return OPERATOR_FINISHED;
925 }
926
927 void OUTLINER_OT_expanded_toggle(wmOperatorType *ot)
928 {
929         /* identifiers */
930         ot->name = "Expand/Collapse All";
931         ot->idname = "OUTLINER_OT_expanded_toggle";
932         ot->description = "Expand/Collapse all items";
933
934         /* callbacks */
935         ot->exec = outliner_toggle_expanded_exec;
936         ot->poll = ED_operator_outliner_active;
937
938         /* no undo or registry, UI option */
939 }
940
941 /* Toggle Selected (Outliner) ---------------------------------------- */
942
943 static int outliner_toggle_selected_exec(bContext *C, wmOperator *UNUSED(op))
944 {
945         SpaceOops *soops = CTX_wm_space_outliner(C);
946         ARegion *ar = CTX_wm_region(C);
947         Scene *scene = CTX_data_scene(C);
948
949         if (outliner_flag_is_any_test(&soops->tree, TSE_SELECTED, 1))
950                 outliner_flag_set(&soops->tree, TSE_SELECTED, 0);
951         else
952                 outliner_flag_set(&soops->tree, TSE_SELECTED, 1);
953
954         DEG_id_tag_update(&scene->id, DEG_TAG_SELECT_UPDATE);
955         WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
956         ED_region_tag_redraw_no_rebuild(ar);
957
958         return OPERATOR_FINISHED;
959 }
960
961 void OUTLINER_OT_selected_toggle(wmOperatorType *ot)
962 {
963         /* identifiers */
964         ot->name = "Toggle Selected";
965         ot->idname = "OUTLINER_OT_selected_toggle";
966         ot->description = "Toggle the Outliner selection of items";
967
968         /* callbacks */
969         ot->exec = outliner_toggle_selected_exec;
970         ot->poll = ED_operator_outliner_active;
971
972         /* no undo or registry, UI option */
973 }
974
975 /* ************************************************************** */
976 /* Hotkey Only Operators */
977
978 /* Show Active --------------------------------------------------- */
979
980 static void outliner_set_coordinates_element_recursive(SpaceOops *soops, TreeElement *te, int startx, int *starty)
981 {
982         TreeStoreElem *tselem = TREESTORE(te);
983
984         /* store coord and continue, we need coordinates for elements outside view too */
985         te->xs = (float)startx;
986         te->ys = (float)(*starty);
987         *starty -= UI_UNIT_Y;
988
989         if (TSELEM_OPEN(tselem, soops)) {
990                 TreeElement *ten;
991                 for (ten = te->subtree.first; ten; ten = ten->next) {
992                         outliner_set_coordinates_element_recursive(soops, ten, startx + UI_UNIT_X, starty);
993                 }
994         }
995 }
996
997 /* to retrieve coordinates with redrawing the entire tree */
998 void outliner_set_coordinates(ARegion *ar, SpaceOops *soops)
999 {
1000         TreeElement *te;
1001         int starty = (int)(ar->v2d.tot.ymax) - UI_UNIT_Y;
1002
1003         for (te = soops->tree.first; te; te = te->next) {
1004                 outliner_set_coordinates_element_recursive(soops, te, 0, &starty);
1005         }
1006 }
1007
1008 /* return 1 when levels were opened */
1009 static int outliner_open_back(TreeElement *te)
1010 {
1011         TreeStoreElem *tselem;
1012         int retval = 0;
1013
1014         for (te = te->parent; te; te = te->parent) {
1015                 tselem = TREESTORE(te);
1016                 if (tselem->flag & TSE_CLOSED) {
1017                         tselem->flag &= ~TSE_CLOSED;
1018                         retval = 1;
1019                 }
1020         }
1021         return retval;
1022 }
1023
1024 static int outliner_show_active_exec(bContext *C, wmOperator *UNUSED(op))
1025 {
1026         SpaceOops *so = CTX_wm_space_outliner(C);
1027         ViewLayer *view_layer = CTX_data_view_layer(C);
1028         ARegion *ar = CTX_wm_region(C);
1029         View2D *v2d = &ar->v2d;
1030
1031         TreeElement *te;
1032         int xdelta, ytop;
1033
1034         Object *obact = OBACT(view_layer);
1035
1036         if (!obact)
1037                 return OPERATOR_CANCELLED;
1038
1039
1040         te = outliner_find_id(so, &so->tree, &obact->id);
1041
1042         if (te != NULL && obact->type == OB_ARMATURE) {
1043                 /* traverse down the bone hierarchy in case of armature */
1044                 TreeElement *te_obact = te;
1045
1046                 if (obact->mode & OB_MODE_POSE) {
1047                         bPoseChannel *pchan = CTX_data_active_pose_bone(C);
1048                         if (pchan) {
1049                                 te = outliner_find_posechannel(&te_obact->subtree, pchan);
1050                         }
1051                 }
1052                 else if (obact->mode & OB_MODE_EDIT) {
1053                         EditBone *ebone = CTX_data_active_bone(C);
1054                         if (ebone) {
1055                                 te = outliner_find_editbone(&te_obact->subtree, ebone);
1056                         }
1057                 }
1058         }
1059
1060         if (te) {
1061                 /* open up tree to active object/bone */
1062                 if (outliner_open_back(te)) {
1063                         outliner_set_coordinates(ar, so);
1064                 }
1065
1066                 /* make te->ys center of view */
1067                 ytop = te->ys + BLI_rcti_size_y(&v2d->mask) / 2;
1068                 if (ytop > 0) ytop = 0;
1069
1070                 v2d->cur.ymax = (float)ytop;
1071                 v2d->cur.ymin = (float)(ytop - BLI_rcti_size_y(&v2d->mask));
1072
1073                 /* make te->xs ==> te->xend center of view */
1074                 xdelta = (int)(te->xs - v2d->cur.xmin);
1075                 v2d->cur.xmin += xdelta;
1076                 v2d->cur.xmax += xdelta;
1077         }
1078
1079         ED_region_tag_redraw_no_rebuild(ar);
1080
1081         return OPERATOR_FINISHED;
1082 }
1083
1084 void OUTLINER_OT_show_active(wmOperatorType *ot)
1085 {
1086         /* identifiers */
1087         ot->name = "Show Active";
1088         ot->idname = "OUTLINER_OT_show_active";
1089         ot->description = "Open up the tree and adjust the view so that the active Object is shown centered";
1090
1091         /* callbacks */
1092         ot->exec = outliner_show_active_exec;
1093         ot->poll = ED_operator_outliner_active;
1094 }
1095
1096 /* View Panning --------------------------------------------------- */
1097
1098 static int outliner_scroll_page_exec(bContext *C, wmOperator *op)
1099 {
1100         ARegion *ar = CTX_wm_region(C);
1101         int dy = BLI_rcti_size_y(&ar->v2d.mask);
1102         int up = 0;
1103
1104         if (RNA_boolean_get(op->ptr, "up"))
1105                 up = 1;
1106
1107         if (up == 0) dy = -dy;
1108         ar->v2d.cur.ymin += dy;
1109         ar->v2d.cur.ymax += dy;
1110
1111         ED_region_tag_redraw_no_rebuild(ar);
1112
1113         return OPERATOR_FINISHED;
1114 }
1115
1116
1117 void OUTLINER_OT_scroll_page(wmOperatorType *ot)
1118 {
1119         PropertyRNA *prop;
1120
1121         /* identifiers */
1122         ot->name = "Scroll Page";
1123         ot->idname = "OUTLINER_OT_scroll_page";
1124         ot->description = "Scroll page up or down";
1125
1126         /* callbacks */
1127         ot->exec = outliner_scroll_page_exec;
1128         ot->poll = ED_operator_outliner_active;
1129
1130         /* properties */
1131         prop = RNA_def_boolean(ot->srna, "up", 0, "Up", "Scroll up one page");
1132         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
1133 }
1134
1135 /* Search ------------------------------------------------------- */
1136 // TODO: probably obsolete now with filtering?
1137
1138 #if 0
1139
1140 /* find next element that has this name */
1141 static TreeElement *outliner_find_name(SpaceOops *soops, ListBase *lb, char *name, int flags,
1142                                        TreeElement *prev, int *prevFound)
1143 {
1144         TreeElement *te, *tes;
1145
1146         for (te = lb->first; te; te = te->next) {
1147                 int found = outliner_filter_has_name(te, name, flags);
1148
1149                 if (found) {
1150                         /* name is right, but is element the previous one? */
1151                         if (prev) {
1152                                 if ((te != prev) && (*prevFound))
1153                                         return te;
1154                                 if (te == prev) {
1155                                         *prevFound = 1;
1156                                 }
1157                         }
1158                         else
1159                                 return te;
1160                 }
1161
1162                 tes = outliner_find_name(soops, &te->subtree, name, flags, prev, prevFound);
1163                 if (tes) return tes;
1164         }
1165
1166         /* nothing valid found */
1167         return NULL;
1168 }
1169
1170 static void outliner_find_panel(Scene *UNUSED(scene), ARegion *ar, SpaceOops *soops, int again, int flags)
1171 {
1172         ReportList *reports = NULL; // CTX_wm_reports(C);
1173         TreeElement *te = NULL;
1174         TreeElement *last_find;
1175         TreeStoreElem *tselem;
1176         int ytop, xdelta, prevFound = 0;
1177         char name[sizeof(soops->search_string)];
1178
1179         /* get last found tree-element based on stored search_tse */
1180         last_find = outliner_find_tse(soops, &soops->search_tse);
1181
1182         /* determine which type of search to do */
1183         if (again && last_find) {
1184                 /* no popup panel - previous + user wanted to search for next after previous */
1185                 BLI_strncpy(name, soops->search_string, sizeof(name));
1186                 flags = soops->search_flags;
1187
1188                 /* try to find matching element */
1189                 te = outliner_find_name(soops, &soops->tree, name, flags, last_find, &prevFound);
1190                 if (te == NULL) {
1191                         /* no more matches after previous, start from beginning again */
1192                         prevFound = 1;
1193                         te = outliner_find_name(soops, &soops->tree, name, flags, last_find, &prevFound);
1194                 }
1195         }
1196         else {
1197                 /* pop up panel - no previous, or user didn't want search after previous */
1198                 name[0] = '\0';
1199 // XXX          if (sbutton(name, 0, sizeof(name) - 1, "Find: ") && name[0]) {
1200 //                      te = outliner_find_name(soops, &soops->tree, name, flags, NULL, &prevFound);
1201 //              }
1202 //              else return; /* XXX RETURN! XXX */
1203         }
1204
1205         /* do selection and reveal */
1206         if (te) {
1207                 tselem = TREESTORE(te);
1208                 if (tselem) {
1209                         /* expand branches so that it will be visible, we need to get correct coordinates */
1210                         if (outliner_open_back(soops, te))
1211                                 outliner_set_coordinates(ar, soops);
1212
1213                         /* deselect all visible, and select found element */
1214                         outliner_flag_set(soops, &soops->tree, TSE_SELECTED, 0);
1215                         tselem->flag |= TSE_SELECTED;
1216
1217                         /* make te->ys center of view */
1218                         ytop = (int)(te->ys + BLI_rctf_size_y(&ar->v2d.mask) / 2);
1219                         if (ytop > 0) ytop = 0;
1220                         ar->v2d.cur.ymax = (float)ytop;
1221                         ar->v2d.cur.ymin = (float)(ytop - BLI_rctf_size_y(&ar->v2d.mask));
1222
1223                         /* make te->xs ==> te->xend center of view */
1224                         xdelta = (int)(te->xs - ar->v2d.cur.xmin);
1225                         ar->v2d.cur.xmin += xdelta;
1226                         ar->v2d.cur.xmax += xdelta;
1227
1228                         /* store selection */
1229                         soops->search_tse = *tselem;
1230
1231                         BLI_strncpy(soops->search_string, name, sizeof(soops->search_string));
1232                         soops->search_flags = flags;
1233
1234                         /* redraw */
1235                         ED_region_tag_redraw_no_rebuild(ar);
1236                 }
1237         }
1238         else {
1239                 /* no tree-element found */
1240                 BKE_reportf(reports, RPT_WARNING, "Not found: %s", name);
1241         }
1242 }
1243 #endif
1244
1245 /* Show One Level ----------------------------------------------- */
1246
1247 /* helper function for Show/Hide one level operator */
1248 static void outliner_openclose_level(ListBase *lb, int curlevel, int level, int open)
1249 {
1250         TreeElement *te;
1251         TreeStoreElem *tselem;
1252
1253         for (te = lb->first; te; te = te->next) {
1254                 tselem = TREESTORE(te);
1255
1256                 if (open) {
1257                         if (curlevel <= level) tselem->flag &= ~TSE_CLOSED;
1258                 }
1259                 else {
1260                         if (curlevel >= level) tselem->flag |= TSE_CLOSED;
1261                 }
1262
1263                 outliner_openclose_level(&te->subtree, curlevel + 1, level, open);
1264         }
1265 }
1266
1267 static int outliner_one_level_exec(bContext *C, wmOperator *op)
1268 {
1269         SpaceOops *soops = CTX_wm_space_outliner(C);
1270         ARegion *ar = CTX_wm_region(C);
1271         const bool add = RNA_boolean_get(op->ptr, "open");
1272         int level;
1273
1274         level = outliner_flag_is_any_test(&soops->tree, TSE_CLOSED, 1);
1275         if (add == 1) {
1276                 if (level) outliner_openclose_level(&soops->tree, 1, level, 1);
1277         }
1278         else {
1279                 if (level == 0) level = outliner_count_levels(&soops->tree, 0);
1280                 if (level) outliner_openclose_level(&soops->tree, 1, level - 1, 0);
1281         }
1282
1283         ED_region_tag_redraw(ar);
1284
1285         return OPERATOR_FINISHED;
1286 }
1287
1288 void OUTLINER_OT_show_one_level(wmOperatorType *ot)
1289 {
1290         PropertyRNA *prop;
1291
1292         /* identifiers */
1293         ot->name = "Show/Hide One Level";
1294         ot->idname = "OUTLINER_OT_show_one_level";
1295         ot->description = "Expand/collapse all entries by one level";
1296
1297         /* callbacks */
1298         ot->exec = outliner_one_level_exec;
1299         ot->poll = ED_operator_outliner_active;
1300
1301         /* no undo or registry, UI option */
1302
1303         /* properties */
1304         prop = RNA_def_boolean(ot->srna, "open", 1, "Open", "Expand all entries one level deep");
1305         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
1306 }
1307
1308 /* Show Hierarchy ----------------------------------------------- */
1309
1310 /* helper function for tree_element_shwo_hierarchy() - recursively checks whether subtrees have any objects*/
1311 static int subtree_has_objects(ListBase *lb)
1312 {
1313         TreeElement *te;
1314         TreeStoreElem *tselem;
1315
1316         for (te = lb->first; te; te = te->next) {
1317                 tselem = TREESTORE(te);
1318                 if (tselem->type == 0 && te->idcode == ID_OB) return 1;
1319                 if (subtree_has_objects(&te->subtree)) return 1;
1320         }
1321         return 0;
1322 }
1323
1324 /* recursive helper function for Show Hierarchy operator */
1325 static void tree_element_show_hierarchy(Scene *scene, SpaceOops *soops, ListBase *lb)
1326 {
1327         TreeElement *te;
1328         TreeStoreElem *tselem;
1329
1330         /* open all object elems, close others */
1331         for (te = lb->first; te; te = te->next) {
1332                 tselem = TREESTORE(te);
1333
1334                 if (tselem->type == 0) {
1335                         if (te->idcode == ID_SCE) {
1336                                 if (tselem->id != (ID *)scene) tselem->flag |= TSE_CLOSED;
1337                                 else tselem->flag &= ~TSE_CLOSED;
1338                         }
1339                         else if (te->idcode == ID_OB) {
1340                                 if (subtree_has_objects(&te->subtree)) tselem->flag &= ~TSE_CLOSED;
1341                                 else tselem->flag |= TSE_CLOSED;
1342                         }
1343                 }
1344                 else {
1345                         tselem->flag |= TSE_CLOSED;
1346                 }
1347
1348                 if (TSELEM_OPEN(tselem, soops)) {
1349                         tree_element_show_hierarchy(scene, soops, &te->subtree);
1350                 }
1351         }
1352 }
1353
1354 /* show entire object level hierarchy */
1355 static int outliner_show_hierarchy_exec(bContext *C, wmOperator *UNUSED(op))
1356 {
1357         SpaceOops *soops = CTX_wm_space_outliner(C);
1358         ARegion *ar = CTX_wm_region(C);
1359         Scene *scene = CTX_data_scene(C);
1360
1361         /* recursively open/close levels */
1362         tree_element_show_hierarchy(scene, soops, &soops->tree);
1363
1364         ED_region_tag_redraw(ar);
1365
1366         return OPERATOR_FINISHED;
1367 }
1368
1369 void OUTLINER_OT_show_hierarchy(wmOperatorType *ot)
1370 {
1371         /* identifiers */
1372         ot->name = "Show Hierarchy";
1373         ot->idname = "OUTLINER_OT_show_hierarchy";
1374         ot->description = "Open all object entries and close all others";
1375
1376         /* callbacks */
1377         ot->exec = outliner_show_hierarchy_exec;
1378         ot->poll = ED_operator_outliner_active; //  TODO: shouldn't be allowed in RNA views...
1379
1380         /* no undo or registry, UI option */
1381 }
1382
1383 /* ************************************************************** */
1384 /* ANIMATO OPERATIONS */
1385 /* KeyingSet and Driver Creation - Helper functions */
1386
1387 /* specialized poll callback for these operators to work in Datablocks view only */
1388 static bool ed_operator_outliner_datablocks_active(bContext *C)
1389 {
1390         ScrArea *sa = CTX_wm_area(C);
1391         if ((sa) && (sa->spacetype == SPACE_OUTLINER)) {
1392                 SpaceOops *so = CTX_wm_space_outliner(C);
1393                 return (so->outlinevis == SO_DATA_API);
1394         }
1395         return 0;
1396 }
1397
1398
1399 /* Helper func to extract an RNA path from selected tree element
1400  * NOTE: the caller must zero-out all values of the pointers that it passes here first, as
1401  * this function does not do that yet
1402  */
1403 static void tree_element_to_path(TreeElement *te, TreeStoreElem *tselem,
1404                                  ID **id, char **path, int *array_index, short *flag, short *UNUSED(groupmode))
1405 {
1406         ListBase hierarchy = {NULL, NULL};
1407         LinkData *ld;
1408         TreeElement *tem, *temnext, *temsub;
1409         TreeStoreElem *tse /* , *tsenext */ /* UNUSED */;
1410         PointerRNA *ptr, *nextptr;
1411         PropertyRNA *prop;
1412         char *newpath = NULL;
1413
1414         /* optimize tricks:
1415          *      - Don't do anything if the selected item is a 'struct', but arrays are allowed
1416          */
1417         if (tselem->type == TSE_RNA_STRUCT)
1418                 return;
1419
1420         /* Overview of Algorithm:
1421          *  1. Go up the chain of parents until we find the 'root', taking note of the
1422          *         levels encountered in reverse-order (i.e. items are added to the start of the list
1423          *      for more convenient looping later)
1424          *  2. Walk down the chain, adding from the first ID encountered
1425          *         (which will become the 'ID' for the KeyingSet Path), and build a
1426          *      path as we step through the chain
1427          */
1428
1429         /* step 1: flatten out hierarchy of parents into a flat chain */
1430         for (tem = te->parent; tem; tem = tem->parent) {
1431                 ld = MEM_callocN(sizeof(LinkData), "LinkData for tree_element_to_path()");
1432                 ld->data = tem;
1433                 BLI_addhead(&hierarchy, ld);
1434         }
1435
1436         /* step 2: step down hierarchy building the path
1437          * (NOTE: addhead in previous loop was needed so that we can loop like this) */
1438         for (ld = hierarchy.first; ld; ld = ld->next) {
1439                 /* get data */
1440                 tem = (TreeElement *)ld->data;
1441                 tse = TREESTORE(tem);
1442                 ptr = &tem->rnaptr;
1443                 prop = tem->directdata;
1444
1445                 /* check if we're looking for first ID, or appending to path */
1446                 if (*id) {
1447                         /* just 'append' property to path
1448                          * - to prevent memory leaks, we must write to newpath not path, then free old path + swap them
1449                          */
1450                         if (tse->type == TSE_RNA_PROPERTY) {
1451                                 if (RNA_property_type(prop) == PROP_POINTER) {
1452                                         /* for pointer we just append property name */
1453                                         newpath = RNA_path_append(*path, ptr, prop, 0, NULL);
1454                                 }
1455                                 else if (RNA_property_type(prop) == PROP_COLLECTION) {
1456                                         char buf[128], *name;
1457
1458                                         temnext = (TreeElement *)(ld->next->data);
1459                                         /* tsenext = TREESTORE(temnext); */ /* UNUSED */
1460
1461                                         nextptr = &temnext->rnaptr;
1462                                         name = RNA_struct_name_get_alloc(nextptr, buf, sizeof(buf), NULL);
1463
1464                                         if (name) {
1465                                                 /* if possible, use name as a key in the path */
1466                                                 newpath = RNA_path_append(*path, NULL, prop, 0, name);
1467
1468                                                 if (name != buf)
1469                                                         MEM_freeN(name);
1470                                         }
1471                                         else {
1472                                                 /* otherwise use index */
1473                                                 int index = 0;
1474
1475                                                 for (temsub = tem->subtree.first; temsub; temsub = temsub->next, index++)
1476                                                         if (temsub == temnext)
1477                                                                 break;
1478
1479                                                 newpath = RNA_path_append(*path, NULL, prop, index, NULL);
1480                                         }
1481
1482                                         ld = ld->next;
1483                                 }
1484                         }
1485
1486                         if (newpath) {
1487                                 if (*path) MEM_freeN(*path);
1488                                 *path = newpath;
1489                                 newpath = NULL;
1490                         }
1491                 }
1492                 else {
1493                         /* no ID, so check if entry is RNA-struct, and if that RNA-struct is an ID datablock to extract info from */
1494                         if (tse->type == TSE_RNA_STRUCT) {
1495                                 /* ptr->data not ptr->id.data seems to be the one we want,
1496                                  * since ptr->data is sometimes the owner of this ID? */
1497                                 if (RNA_struct_is_ID(ptr->type)) {
1498                                         *id = (ID *)ptr->data;
1499
1500                                         /* clear path */
1501                                         if (*path) {
1502                                                 MEM_freeN(*path);
1503                                                 path = NULL;
1504                                         }
1505                                 }
1506                         }
1507                 }
1508         }
1509
1510         /* step 3: if we've got an ID, add the current item to the path */
1511         if (*id) {
1512                 /* add the active property to the path */
1513                 ptr = &te->rnaptr;
1514                 prop = te->directdata;
1515
1516                 /* array checks */
1517                 if (tselem->type == TSE_RNA_ARRAY_ELEM) {
1518                         /* item is part of an array, so must set the array_index */
1519                         *array_index = te->index;
1520                 }
1521                 else if (RNA_property_array_check(prop)) {
1522                         /* entire array was selected, so keyframe all */
1523                         *flag |= KSP_FLAG_WHOLE_ARRAY;
1524                 }
1525
1526                 /* path */
1527                 newpath = RNA_path_append(*path, NULL, prop, 0, NULL);
1528                 if (*path) MEM_freeN(*path);
1529                 *path = newpath;
1530         }
1531
1532         /* free temp data */
1533         BLI_freelistN(&hierarchy);
1534 }
1535
1536 /* =============================================== */
1537 /* Driver Operations */
1538
1539 /* These operators are only available in databrowser mode for now, as
1540  * they depend on having RNA paths and/or hierarchies available.
1541  */
1542 enum {
1543         DRIVERS_EDITMODE_ADD    = 0,
1544         DRIVERS_EDITMODE_REMOVE,
1545 } /*eDrivers_EditModes*/;
1546
1547 /* Utilities ---------------------------------- */
1548
1549 /* Recursively iterate over tree, finding and working on selected items */
1550 static void do_outliner_drivers_editop(SpaceOops *soops, ListBase *tree, ReportList *reports, short mode)
1551 {
1552         TreeElement *te;
1553         TreeStoreElem *tselem;
1554
1555         for (te = tree->first; te; te = te->next) {
1556                 tselem = TREESTORE(te);
1557
1558                 /* if item is selected, perform operation */
1559                 if (tselem->flag & TSE_SELECTED) {
1560                         ID *id = NULL;
1561                         char *path = NULL;
1562                         int array_index = 0;
1563                         short flag = 0;
1564                         short groupmode = KSP_GROUP_KSNAME;
1565
1566                         /* check if RNA-property described by this selected element is an animatable prop */
1567                         if (ELEM(tselem->type, TSE_RNA_PROPERTY, TSE_RNA_ARRAY_ELEM) &&
1568                             RNA_property_animateable(&te->rnaptr, te->directdata))
1569                         {
1570                                 /* get id + path + index info from the selected element */
1571                                 tree_element_to_path(te, tselem,
1572                                                      &id, &path, &array_index, &flag, &groupmode);
1573                         }
1574
1575                         /* only if ID and path were set, should we perform any actions */
1576                         if (id && path) {
1577                                 short dflags = CREATEDRIVER_WITH_DEFAULT_DVAR;
1578                                 int arraylen = 1;
1579
1580                                 /* array checks */
1581                                 if (flag & KSP_FLAG_WHOLE_ARRAY) {
1582                                         /* entire array was selected, so add drivers for all */
1583                                         arraylen = RNA_property_array_length(&te->rnaptr, te->directdata);
1584                                 }
1585                                 else
1586                                         arraylen = array_index;
1587
1588                                 /* we should do at least one step */
1589                                 if (arraylen == array_index)
1590                                         arraylen++;
1591
1592                                 /* for each array element we should affect, add driver */
1593                                 for (; array_index < arraylen; array_index++) {
1594                                         /* action depends on mode */
1595                                         switch (mode) {
1596                                                 case DRIVERS_EDITMODE_ADD:
1597                                                 {
1598                                                         /* add a new driver with the information obtained (only if valid) */
1599                                                         ANIM_add_driver(reports, id, path, array_index, dflags, DRIVER_TYPE_PYTHON);
1600                                                         break;
1601                                                 }
1602                                                 case DRIVERS_EDITMODE_REMOVE:
1603                                                 {
1604                                                         /* remove driver matching the information obtained (only if valid) */
1605                                                         ANIM_remove_driver(reports, id, path, array_index, dflags);
1606                                                         break;
1607                                                 }
1608                                         }
1609                                 }
1610
1611                                 /* free path, since it had to be generated */
1612                                 MEM_freeN(path);
1613                         }
1614
1615
1616                 }
1617
1618                 /* go over sub-tree */
1619                 if (TSELEM_OPEN(tselem, soops))
1620                         do_outliner_drivers_editop(soops, &te->subtree, reports, mode);
1621         }
1622 }
1623
1624 /* Add Operator ---------------------------------- */
1625
1626 static int outliner_drivers_addsel_exec(bContext *C, wmOperator *op)
1627 {
1628         SpaceOops *soutliner = CTX_wm_space_outliner(C);
1629
1630         /* check for invalid states */
1631         if (soutliner == NULL)
1632                 return OPERATOR_CANCELLED;
1633
1634         /* recursively go into tree, adding selected items */
1635         do_outliner_drivers_editop(soutliner, &soutliner->tree, op->reports, DRIVERS_EDITMODE_ADD);
1636
1637         /* send notifiers */
1638         WM_event_add_notifier(C, NC_ANIMATION | ND_FCURVES_ORDER, NULL); // XXX
1639
1640         return OPERATOR_FINISHED;
1641 }
1642
1643 void OUTLINER_OT_drivers_add_selected(wmOperatorType *ot)
1644 {
1645         /* api callbacks */
1646         ot->idname = "OUTLINER_OT_drivers_add_selected";
1647         ot->name = "Add Drivers for Selected";
1648         ot->description = "Add drivers to selected items";
1649
1650         /* api callbacks */
1651         ot->exec = outliner_drivers_addsel_exec;
1652         ot->poll = ed_operator_outliner_datablocks_active;
1653
1654         /* flags */
1655         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1656 }
1657
1658
1659 /* Remove Operator ---------------------------------- */
1660
1661 static int outliner_drivers_deletesel_exec(bContext *C, wmOperator *op)
1662 {
1663         SpaceOops *soutliner = CTX_wm_space_outliner(C);
1664
1665         /* check for invalid states */
1666         if (soutliner == NULL)
1667                 return OPERATOR_CANCELLED;
1668
1669         /* recursively go into tree, adding selected items */
1670         do_outliner_drivers_editop(soutliner, &soutliner->tree, op->reports, DRIVERS_EDITMODE_REMOVE);
1671
1672         /* send notifiers */
1673         WM_event_add_notifier(C, ND_KEYS, NULL); // XXX
1674
1675         return OPERATOR_FINISHED;
1676 }
1677
1678 void OUTLINER_OT_drivers_delete_selected(wmOperatorType *ot)
1679 {
1680         /* identifiers */
1681         ot->idname = "OUTLINER_OT_drivers_delete_selected";
1682         ot->name = "Delete Drivers for Selected";
1683         ot->description = "Delete drivers assigned to selected items";
1684
1685         /* api callbacks */
1686         ot->exec = outliner_drivers_deletesel_exec;
1687         ot->poll = ed_operator_outliner_datablocks_active;
1688
1689         /* flags */
1690         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1691 }
1692
1693 /* =============================================== */
1694 /* Keying Set Operations */
1695
1696 /* These operators are only available in databrowser mode for now, as
1697  * they depend on having RNA paths and/or hierarchies available.
1698  */
1699 enum {
1700         KEYINGSET_EDITMODE_ADD  = 0,
1701         KEYINGSET_EDITMODE_REMOVE,
1702 } /*eKeyingSet_EditModes*/;
1703
1704 /* Utilities ---------------------------------- */
1705
1706 /* find the 'active' KeyingSet, and add if not found (if adding is allowed) */
1707 // TODO: should this be an API func?
1708 static KeyingSet *verify_active_keyingset(Scene *scene, short add)
1709 {
1710         KeyingSet *ks = NULL;
1711
1712         /* sanity check */
1713         if (scene == NULL)
1714                 return NULL;
1715
1716         /* try to find one from scene */
1717         if (scene->active_keyingset > 0)
1718                 ks = BLI_findlink(&scene->keyingsets, scene->active_keyingset - 1);
1719
1720         /* add if none found */
1721         // XXX the default settings have yet to evolve
1722         if ((add) && (ks == NULL)) {
1723                 ks = BKE_keyingset_add(&scene->keyingsets, NULL, NULL, KEYINGSET_ABSOLUTE, 0);
1724                 scene->active_keyingset = BLI_listbase_count(&scene->keyingsets);
1725         }
1726
1727         return ks;
1728 }
1729
1730 /* Recursively iterate over tree, finding and working on selected items */
1731 static void do_outliner_keyingset_editop(SpaceOops *soops, KeyingSet *ks, ListBase *tree, short mode)
1732 {
1733         TreeElement *te;
1734         TreeStoreElem *tselem;
1735
1736         for (te = tree->first; te; te = te->next) {
1737                 tselem = TREESTORE(te);
1738
1739                 /* if item is selected, perform operation */
1740                 if (tselem->flag & TSE_SELECTED) {
1741                         ID *id = NULL;
1742                         char *path = NULL;
1743                         int array_index = 0;
1744                         short flag = 0;
1745                         short groupmode = KSP_GROUP_KSNAME;
1746
1747                         /* check if RNA-property described by this selected element is an animatable prop */
1748                         if (ELEM(tselem->type, TSE_RNA_PROPERTY, TSE_RNA_ARRAY_ELEM) &&
1749                             RNA_property_animateable(&te->rnaptr, te->directdata))
1750                         {
1751                                 /* get id + path + index info from the selected element */
1752                                 tree_element_to_path(te, tselem,
1753                                                      &id, &path, &array_index, &flag, &groupmode);
1754                         }
1755
1756                         /* only if ID and path were set, should we perform any actions */
1757                         if (id && path) {
1758                                 /* action depends on mode */
1759                                 switch (mode) {
1760                                         case KEYINGSET_EDITMODE_ADD:
1761                                         {
1762                                                 /* add a new path with the information obtained (only if valid) */
1763                                                 /* TODO: what do we do with group name?
1764                                                  * for now, we don't supply one, and just let this use the KeyingSet name */
1765                                                 BKE_keyingset_add_path(ks, id, NULL, path, array_index, flag, groupmode);
1766                                                 ks->active_path = BLI_listbase_count(&ks->paths);
1767                                                 break;
1768                                         }
1769                                         case KEYINGSET_EDITMODE_REMOVE:
1770                                         {
1771                                                 /* find the relevant path, then remove it from the KeyingSet */
1772                                                 KS_Path *ksp = BKE_keyingset_find_path(ks, id, NULL, path, array_index, groupmode);
1773
1774                                                 if (ksp) {
1775                                                         /* free path's data */
1776                                                         BKE_keyingset_free_path(ks, ksp);
1777
1778                                                         ks->active_path = 0;
1779                                                 }
1780                                                 break;
1781                                         }
1782                                 }
1783
1784                                 /* free path, since it had to be generated */
1785                                 MEM_freeN(path);
1786                         }
1787                 }
1788
1789                 /* go over sub-tree */
1790                 if (TSELEM_OPEN(tselem, soops))
1791                         do_outliner_keyingset_editop(soops, ks, &te->subtree, mode);
1792         }
1793 }
1794
1795 /* Add Operator ---------------------------------- */
1796
1797 static int outliner_keyingset_additems_exec(bContext *C, wmOperator *op)
1798 {
1799         SpaceOops *soutliner = CTX_wm_space_outliner(C);
1800         Scene *scene = CTX_data_scene(C);
1801         KeyingSet *ks = verify_active_keyingset(scene, 1);
1802
1803         /* check for invalid states */
1804         if (ks == NULL) {
1805                 BKE_report(op->reports, RPT_ERROR, "Operation requires an active keying set");
1806                 return OPERATOR_CANCELLED;
1807         }
1808         if (soutliner == NULL)
1809                 return OPERATOR_CANCELLED;
1810
1811         /* recursively go into tree, adding selected items */
1812         do_outliner_keyingset_editop(soutliner, ks, &soutliner->tree, KEYINGSET_EDITMODE_ADD);
1813
1814         /* send notifiers */
1815         WM_event_add_notifier(C, NC_SCENE | ND_KEYINGSET, NULL);
1816
1817         return OPERATOR_FINISHED;
1818 }
1819
1820 void OUTLINER_OT_keyingset_add_selected(wmOperatorType *ot)
1821 {
1822         /* identifiers */
1823         ot->idname = "OUTLINER_OT_keyingset_add_selected";
1824         ot->name = "Keying Set Add Selected";
1825         ot->description = "Add selected items (blue-gray rows) to active Keying Set";
1826
1827         /* api callbacks */
1828         ot->exec = outliner_keyingset_additems_exec;
1829         ot->poll = ed_operator_outliner_datablocks_active;
1830
1831         /* flags */
1832         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1833 }
1834
1835
1836 /* Remove Operator ---------------------------------- */
1837
1838 static int outliner_keyingset_removeitems_exec(bContext *C, wmOperator *UNUSED(op))
1839 {
1840         SpaceOops *soutliner = CTX_wm_space_outliner(C);
1841         Scene *scene = CTX_data_scene(C);
1842         KeyingSet *ks = verify_active_keyingset(scene, 1);
1843
1844         /* check for invalid states */
1845         if (soutliner == NULL)
1846                 return OPERATOR_CANCELLED;
1847
1848         /* recursively go into tree, adding selected items */
1849         do_outliner_keyingset_editop(soutliner, ks, &soutliner->tree, KEYINGSET_EDITMODE_REMOVE);
1850
1851         /* send notifiers */
1852         WM_event_add_notifier(C, NC_SCENE | ND_KEYINGSET, NULL);
1853
1854         return OPERATOR_FINISHED;
1855 }
1856
1857 void OUTLINER_OT_keyingset_remove_selected(wmOperatorType *ot)
1858 {
1859         /* identifiers */
1860         ot->idname = "OUTLINER_OT_keyingset_remove_selected";
1861         ot->name = "Keying Set Remove Selected";
1862         ot->description = "Remove selected items (blue-gray rows) from active Keying Set";
1863
1864         /* api callbacks */
1865         ot->exec = outliner_keyingset_removeitems_exec;
1866         ot->poll = ed_operator_outliner_datablocks_active;
1867
1868         /* flags */
1869         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1870 }
1871
1872
1873 /* ************************************************************** */
1874 /* ORPHANED DATABLOCKS */
1875
1876 static bool ed_operator_outliner_id_orphans_active(bContext *C)
1877 {
1878         ScrArea *sa = CTX_wm_area(C);
1879         if ((sa) && (sa->spacetype == SPACE_OUTLINER)) {
1880                 SpaceOops *so = CTX_wm_space_outliner(C);
1881                 return (so->outlinevis == SO_ID_ORPHANS);
1882         }
1883         return 0;
1884 }
1885
1886 /* Purge Orphans Operator --------------------------------------- */
1887
1888 static int outliner_orphans_purge_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(evt))
1889 {
1890         /* present a prompt to informing users that this change is irreversible */
1891         return WM_operator_confirm_message(C, op,
1892                                            "Purging unused data-blocks cannot be undone and saves to current .blend file. "
1893                                            "Click here to proceed...");
1894 }
1895
1896 static int outliner_orphans_purge_exec(bContext *C, wmOperator *UNUSED(op))
1897 {
1898         /* Firstly, ensure that the file has been saved,
1899          * so that the latest changes since the last save
1900          * are retained...
1901          */
1902         WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_EXEC_DEFAULT, NULL);
1903
1904         /* Now, reload the file to get rid of the orphans... */
1905         WM_operator_name_call(C, "WM_OT_revert_mainfile", WM_OP_EXEC_DEFAULT, NULL);
1906         return OPERATOR_FINISHED;
1907 }
1908
1909 void OUTLINER_OT_orphans_purge(wmOperatorType *ot)
1910 {
1911         /* identifiers */
1912         ot->idname = "OUTLINER_OT_orphans_purge";
1913         ot->name = "Purge All";
1914         ot->description = "Clear all orphaned data-blocks without any users from the file "
1915                           "(cannot be undone, saves to current .blend file)";
1916
1917         /* callbacks */
1918         ot->invoke = outliner_orphans_purge_invoke;
1919         ot->exec = outliner_orphans_purge_exec;
1920         ot->poll = ed_operator_outliner_id_orphans_active;
1921
1922         /* flags */
1923         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1924 }
1925
1926 /* ************************************************************** */
1927 /* DRAG AND DROP OPERATORS */
1928
1929 /* ******************** Parent Drop Operator *********************** */
1930
1931 static int parent_drop_exec(bContext *C, wmOperator *op)
1932 {
1933         Object *par = NULL, *ob = NULL;
1934         Main *bmain = CTX_data_main(C);
1935         Scene *scene = CTX_data_scene(C);
1936         int partype = -1;
1937         char parname[MAX_ID_NAME], childname[MAX_ID_NAME];
1938
1939         partype = RNA_enum_get(op->ptr, "type");
1940         RNA_string_get(op->ptr, "parent", parname);
1941         par = (Object *)BKE_libblock_find_name(bmain, ID_OB, parname);
1942         RNA_string_get(op->ptr, "child", childname);
1943         ob = (Object *)BKE_libblock_find_name(bmain, ID_OB, childname);
1944
1945         if (ID_IS_LINKED(ob)) {
1946                 BKE_report(op->reports, RPT_INFO, "Can't edit library linked object");
1947                 return OPERATOR_CANCELLED;
1948         }
1949
1950         ED_object_parent_set(op->reports, C, scene, ob, par, partype, false, false, NULL);
1951
1952         DEG_relations_tag_update(bmain);
1953         WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL);
1954         WM_event_add_notifier(C, NC_OBJECT | ND_PARENT, NULL);
1955
1956         return OPERATOR_FINISHED;
1957 }
1958
1959 static int parent_drop_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1960 {
1961         Object *par = NULL;
1962         Object *ob = NULL;
1963         SpaceOops *soops = CTX_wm_space_outliner(C);
1964         ARegion *ar = CTX_wm_region(C);
1965         Main *bmain = CTX_data_main(C);
1966         Scene *scene = NULL;
1967         TreeElement *te = NULL;
1968         char childname[MAX_ID_NAME];
1969         char parname[MAX_ID_NAME];
1970         int partype = 0;
1971         float fmval[2];
1972
1973         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
1974
1975         /* Find object hovered over */
1976         te = outliner_dropzone_find(soops, fmval, true);
1977
1978         if (te) {
1979                 RNA_string_set(op->ptr, "parent", te->name);
1980                 /* Identify parent and child */
1981                 RNA_string_get(op->ptr, "child", childname);
1982                 ob = (Object *)BKE_libblock_find_name(bmain, ID_OB, childname);
1983                 RNA_string_get(op->ptr, "parent", parname);
1984                 par = (Object *)BKE_libblock_find_name(bmain, ID_OB, parname);
1985
1986                 if (ELEM(NULL, ob, par)) {
1987                         if (par == NULL) printf("par==NULL\n");
1988                         return OPERATOR_CANCELLED;
1989                 }
1990                 if (ob == par) {
1991                         return OPERATOR_CANCELLED;
1992                 }
1993                 if (ID_IS_LINKED(ob)) {
1994                         BKE_report(op->reports, RPT_INFO, "Can't edit library linked object");
1995                         return OPERATOR_CANCELLED;
1996                 }
1997
1998                 scene = (Scene *)outliner_search_back(soops, te, ID_SCE);
1999
2000                 if (scene == NULL) {
2001                         /* currently outlier organized in a way, that if there's no parent scene
2002                          * element for object it means that all displayed objects belong to
2003                          * active scene and parenting them is allowed (sergey)
2004                          */
2005
2006                         scene = CTX_data_scene(C);
2007                 }
2008
2009                 if ((par->type != OB_ARMATURE) && (par->type != OB_CURVE) && (par->type != OB_LATTICE)) {
2010                         if (ED_object_parent_set(op->reports, C, scene, ob, par, partype, false, false, NULL)) {
2011                                 DEG_relations_tag_update(bmain);
2012                                 WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL);
2013                                 WM_event_add_notifier(C, NC_OBJECT | ND_PARENT, NULL);
2014                         }
2015                 }
2016                 else {
2017                         /* Menu creation */
2018                         wmOperatorType *ot = WM_operatortype_find("OUTLINER_OT_parent_drop", false);
2019                         uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Set Parent To"), ICON_NONE);
2020                         uiLayout *layout = UI_popup_menu_layout(pup);
2021                         PointerRNA ptr;
2022
2023                         /* Cannot use uiItemEnumO()... have multiple properties to set. */
2024                         uiItemFullO_ptr(layout, ot, IFACE_("Object"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
2025                         RNA_string_set(&ptr, "parent", parname);
2026                         RNA_string_set(&ptr, "child", childname);
2027                         RNA_enum_set(&ptr, "type", PAR_OBJECT);
2028
2029                         /* par becomes parent, make the associated menus */
2030                         if (par->type == OB_ARMATURE) {
2031                                 uiItemFullO_ptr(layout, ot, IFACE_("Armature Deform"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
2032                                 RNA_string_set(&ptr, "parent", parname);
2033                                 RNA_string_set(&ptr, "child", childname);
2034                                 RNA_enum_set(&ptr, "type", PAR_ARMATURE);
2035
2036                                 uiItemFullO_ptr(layout, ot, IFACE_("   With Empty Groups"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
2037                                 RNA_string_set(&ptr, "parent", parname);
2038                                 RNA_string_set(&ptr, "child", childname);
2039                                 RNA_enum_set(&ptr, "type", PAR_ARMATURE_NAME);
2040
2041                                 uiItemFullO_ptr(layout, ot, IFACE_("   With Envelope Weights"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
2042                                 RNA_string_set(&ptr, "parent", parname);
2043                                 RNA_string_set(&ptr, "child", childname);
2044                                 RNA_enum_set(&ptr, "type", PAR_ARMATURE_ENVELOPE);
2045
2046                                 uiItemFullO_ptr(layout, ot, IFACE_("   With Automatic Weights"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
2047                                 RNA_string_set(&ptr, "parent", parname);
2048                                 RNA_string_set(&ptr, "child", childname);
2049                                 RNA_enum_set(&ptr, "type", PAR_ARMATURE_AUTO);
2050
2051                                 uiItemFullO_ptr(layout, ot, IFACE_("Bone"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
2052                                 RNA_string_set(&ptr, "parent", parname);
2053                                 RNA_string_set(&ptr, "child", childname);
2054                                 RNA_enum_set(&ptr, "type", PAR_BONE);
2055                         }
2056                         else if (par->type == OB_CURVE) {
2057                                 uiItemFullO_ptr(layout, ot, IFACE_("Curve Deform"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
2058                                 RNA_string_set(&ptr, "parent", parname);
2059                                 RNA_string_set(&ptr, "child", childname);
2060                                 RNA_enum_set(&ptr, "type", PAR_CURVE);
2061
2062                                 uiItemFullO_ptr(layout, ot, IFACE_("Follow Path"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
2063                                 RNA_string_set(&ptr, "parent", parname);
2064                                 RNA_string_set(&ptr, "child", childname);
2065                                 RNA_enum_set(&ptr, "type", PAR_FOLLOW);
2066
2067                                 uiItemFullO_ptr(layout, ot, IFACE_("Path Constraint"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
2068                                 RNA_string_set(&ptr, "parent", parname);
2069                                 RNA_string_set(&ptr, "child", childname);
2070                                 RNA_enum_set(&ptr, "type", PAR_PATH_CONST);
2071                         }
2072                         else if (par->type == OB_LATTICE) {
2073                                 uiItemFullO_ptr(layout, ot, IFACE_("Lattice Deform"), 0, NULL, WM_OP_EXEC_DEFAULT, 0, &ptr);
2074                                 RNA_string_set(&ptr, "parent", parname);
2075                                 RNA_string_set(&ptr, "child", childname);
2076                                 RNA_enum_set(&ptr, "type", PAR_LATTICE);
2077                         }
2078
2079                         UI_popup_menu_end(C, pup);
2080
2081                         return OPERATOR_INTERFACE;
2082                 }
2083         }
2084         else {
2085                 return OPERATOR_CANCELLED;
2086         }
2087
2088         return OPERATOR_FINISHED;
2089 }
2090
2091 void OUTLINER_OT_parent_drop(wmOperatorType *ot)
2092 {
2093         /* identifiers */
2094         ot->name = "Drop to Set Parent";
2095         ot->description = "Drag to parent in Outliner";
2096         ot->idname = "OUTLINER_OT_parent_drop";
2097
2098         /* api callbacks */
2099         ot->invoke = parent_drop_invoke;
2100         ot->exec = parent_drop_exec;
2101
2102         ot->poll = ED_operator_outliner_active;
2103
2104         /* flags */
2105         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
2106
2107         /* properties */
2108         RNA_def_string(ot->srna, "child", "Object", MAX_ID_NAME, "Child", "Child Object");
2109         RNA_def_string(ot->srna, "parent", "Object", MAX_ID_NAME, "Parent", "Parent Object");
2110         RNA_def_enum(ot->srna, "type", prop_make_parent_types, 0, "Type", "");
2111 }
2112
2113 static bool outliner_parenting_poll(bContext *C)
2114 {
2115         SpaceOops *soops = CTX_wm_space_outliner(C);
2116
2117         if (soops) {
2118                 if (soops->outlinevis == SO_SCENES) {
2119                         return true;
2120                 }
2121                 else if ((soops->outlinevis == SO_VIEW_LAYER) &&
2122                          (soops->filter & SO_FILTER_NO_COLLECTION))
2123                 {
2124                         return true;
2125                 }
2126         }
2127
2128         return false;
2129 }
2130
2131 static int parent_clear_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
2132 {
2133         Main *bmain = CTX_data_main(C);
2134         Object *ob = NULL;
2135         SpaceOops *soops = CTX_wm_space_outliner(C);
2136         char obname[MAX_ID_NAME];
2137
2138         RNA_string_get(op->ptr, "dragged_obj", obname);
2139         ob = (Object *)BKE_libblock_find_name(bmain, ID_OB, obname);
2140
2141         /* search forwards to find the object */
2142         outliner_find_id(soops, &soops->tree, (ID *)ob);
2143
2144         ED_object_parent_clear(ob, RNA_enum_get(op->ptr, "type"));
2145
2146         DEG_relations_tag_update(bmain);
2147         WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL);
2148         WM_event_add_notifier(C, NC_OBJECT | ND_PARENT, NULL);
2149         return OPERATOR_FINISHED;
2150 }
2151
2152 void OUTLINER_OT_parent_clear(wmOperatorType *ot)
2153 {
2154         /* identifiers */
2155         ot->name = "Drop to Clear Parent";
2156         ot->description = "Drag to clear parent in Outliner";
2157         ot->idname = "OUTLINER_OT_parent_clear";
2158
2159         /* api callbacks */
2160         ot->invoke = parent_clear_invoke;
2161
2162         ot->poll = outliner_parenting_poll;
2163
2164         /* flags */
2165         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
2166
2167         /* properties */
2168         RNA_def_string(ot->srna, "dragged_obj", "Object", MAX_ID_NAME, "Child", "Child Object");
2169         RNA_def_enum(ot->srna, "type", prop_clear_parent_types, 0, "Type", "");
2170 }
2171
2172 static int scene_drop_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2173 {
2174         Scene *scene = NULL;
2175         Object *ob = NULL;
2176         SpaceOops *soops = CTX_wm_space_outliner(C);
2177         ARegion *ar = CTX_wm_region(C);
2178         Main *bmain = CTX_data_main(C);
2179         TreeElement *te = NULL;
2180         char obname[MAX_ID_NAME];
2181         float fmval[2];
2182
2183         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
2184
2185         /* Find object hovered over */
2186         te = outliner_dropzone_find(soops, fmval, false);
2187
2188         if (te) {
2189                 RNA_string_set(op->ptr, "scene", te->name);
2190                 scene = (Scene *)BKE_libblock_find_name(bmain, ID_SCE, te->name);
2191
2192                 RNA_string_get(op->ptr, "object", obname);
2193                 ob = (Object *)BKE_libblock_find_name(bmain, ID_OB, obname);
2194
2195                 if (ELEM(NULL, ob, scene) || ID_IS_LINKED(scene)) {
2196                         return OPERATOR_CANCELLED;
2197                 }
2198
2199                 if (BKE_scene_has_object(scene, ob)) {
2200                         return OPERATOR_CANCELLED;
2201                 }
2202
2203                 Collection *collection;
2204                 if (scene != CTX_data_scene(C)) {
2205                         /* when linking to an inactive scene link to the master collection */
2206                         collection = BKE_collection_master(scene);
2207                 }
2208                 else {
2209                         collection = CTX_data_collection(C);
2210                 }
2211
2212                 BKE_collection_object_add(bmain, collection, ob);
2213
2214                 for (ViewLayer *view_layer = scene->view_layers.first; view_layer; view_layer = view_layer->next) {
2215                         Base *base = BKE_view_layer_base_find(view_layer, ob);
2216                         if (base) {
2217                                 ED_object_base_select(base, BA_SELECT);
2218                         }
2219                 }
2220
2221                 DEG_relations_tag_update(bmain);
2222
2223                 DEG_id_tag_update(&scene->id, DEG_TAG_SELECT_UPDATE);
2224                 WM_main_add_notifier(NC_SCENE | ND_OB_SELECT, scene);
2225
2226                 return OPERATOR_FINISHED;
2227         }
2228
2229         return OPERATOR_CANCELLED;
2230 }
2231
2232 void OUTLINER_OT_scene_drop(wmOperatorType *ot)
2233 {
2234         /* identifiers */
2235         ot->name = "Drop Object to Scene";
2236         ot->description = "Drag object to scene in Outliner";
2237         ot->idname = "OUTLINER_OT_scene_drop";
2238
2239         /* api callbacks */
2240         ot->invoke = scene_drop_invoke;
2241
2242         ot->poll = ED_operator_outliner_active;
2243
2244         /* flags */
2245         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
2246
2247         /* properties */
2248         RNA_def_string(ot->srna, "object", "Object", MAX_ID_NAME, "Object", "Target Object");
2249         RNA_def_string(ot->srna, "scene", "Scene", MAX_ID_NAME, "Scene", "Target Scene");
2250 }
2251
2252 static int material_drop_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2253 {
2254         Material *ma = NULL;
2255         Object *ob = NULL;
2256         Main *bmain = CTX_data_main(C);
2257         SpaceOops *soops = CTX_wm_space_outliner(C);
2258         ARegion *ar = CTX_wm_region(C);
2259         TreeElement *te = NULL;
2260         char mat_name[MAX_ID_NAME - 2];
2261         float fmval[2];
2262
2263         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
2264
2265         /* Find object hovered over */
2266         te = outliner_dropzone_find(soops, fmval, true);
2267
2268         if (te) {
2269                 RNA_string_set(op->ptr, "object", te->name);
2270                 ob = (Object *)BKE_libblock_find_name(bmain, ID_OB, te->name);
2271
2272                 RNA_string_get(op->ptr, "material", mat_name);
2273                 ma = (Material *)BKE_libblock_find_name(bmain, ID_MA, mat_name);
2274
2275                 if (ELEM(NULL, ob, ma)) {
2276                         return OPERATOR_CANCELLED;
2277                 }
2278
2279                 assign_material(bmain, ob, ma, ob->totcol + 1, BKE_MAT_ASSIGN_USERPREF);
2280
2281                 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, CTX_wm_view3d(C));
2282                 WM_event_add_notifier(C, NC_MATERIAL | ND_SHADING_LINKS, ma);
2283
2284                 return OPERATOR_FINISHED;
2285         }
2286
2287         return OPERATOR_CANCELLED;
2288 }
2289
2290 void OUTLINER_OT_material_drop(wmOperatorType *ot)
2291 {
2292         /* identifiers */
2293         ot->name = "Drop Material on Object";
2294         ot->description = "Drag material to object in Outliner";
2295         ot->idname = "OUTLINER_OT_material_drop";
2296
2297         /* api callbacks */
2298         ot->invoke = material_drop_invoke;
2299
2300         ot->poll = ED_operator_outliner_active;
2301
2302         /* flags */
2303         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
2304
2305         /* properties */
2306         RNA_def_string(ot->srna, "object", "Object", MAX_ID_NAME, "Object", "Target Object");
2307         RNA_def_string(ot->srna, "material", "Material", MAX_ID_NAME, "Material", "Target Material");
2308 }
2309
2310 /* ******************** Collection Drop Operator *********************** */
2311
2312 static int collection_drop_exec(bContext *UNUSED(C), wmOperator *UNUSED(op))
2313 {
2314         /* TODO: implement */
2315 #if 0
2316         Object *par = NULL, *ob = NULL;
2317         Main *bmain = CTX_data_main(C);
2318         Scene *scene = CTX_data_scene(C);
2319         int partype = -1;
2320         char parname[MAX_ID_NAME], childname[MAX_ID_NAME];
2321
2322         RNA_string_get(op->ptr, "parent", parname);
2323         par = (Object *)BKE_libblock_find_name(ID_OB, parname);
2324         RNA_string_get(op->ptr, "child", childname);
2325         ob = (Object *)BKE_libblock_find_name(ID_OB, childname);
2326
2327         if (ID_IS_LINKED(ob)) {
2328                 BKE_report(op->reports, RPT_INFO, "Can't edit library linked object");
2329                 return OPERATOR_CANCELLED;
2330         }
2331
2332         ED_object_parent_set(op->reports, C, scene, ob, par, partype, false, false, NULL);
2333
2334         DEG_relations_tag_update(bmain);
2335         WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL);
2336         WM_event_add_notifier(C, NC_OBJECT | ND_PARENT, NULL);
2337 #endif
2338
2339         return OPERATOR_FINISHED;
2340 }
2341
2342 static int collection_drop_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2343 {
2344         SpaceOops *soops = CTX_wm_space_outliner(C);
2345         ARegion *ar = CTX_wm_region(C);
2346         Main *bmain = CTX_data_main(C);
2347         char childname[MAX_ID_NAME];
2348         float fmval[2];
2349
2350         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
2351
2352         /* Find object hovered over */
2353         TreeElement *te = outliner_dropzone_find(soops, fmval, true);
2354
2355         if (!te || !outliner_is_collection_tree_element(te)) {
2356                 return OPERATOR_CANCELLED;
2357         }
2358
2359         Collection *collection = outliner_collection_from_tree_element(te);
2360
2361         // TODO: don't use scene, makes no sense anymore
2362         // TODO: move rather than link, change hover text
2363         Scene *scene = BKE_scene_find_from_collection(bmain, collection);
2364         BLI_assert(scene);
2365         RNA_string_get(op->ptr, "child", childname);
2366         Object *ob = (Object *)BKE_libblock_find_name(bmain, ID_OB, childname);
2367         BKE_collection_object_add(bmain, collection, ob);
2368
2369         DEG_id_tag_update(&collection->id, DEG_TAG_COPY_ON_WRITE);
2370         DEG_relations_tag_update(bmain);
2371         WM_event_add_notifier(C, NC_SCENE | ND_LAYER, scene);
2372
2373         return OPERATOR_FINISHED;
2374 }
2375
2376 void OUTLINER_OT_collection_drop(wmOperatorType *ot)
2377 {
2378         /* identifiers */
2379         ot->name = "Link to Collection"; // TODO: rename to move?
2380         ot->description = "Drag to move to collection in Outliner";
2381         ot->idname = "OUTLINER_OT_collection_drop";
2382
2383         /* api callbacks */
2384         ot->invoke = collection_drop_invoke;
2385         ot->exec = collection_drop_exec;
2386
2387         ot->poll = ED_operator_outliner_active;
2388
2389         /* flags */
2390         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
2391
2392         /* properties */
2393         RNA_def_string(ot->srna, "child", "Object", MAX_ID_NAME, "Child", "Child Object");
2394         RNA_def_string(ot->srna, "parent", "Collection", MAX_ID_NAME, "Parent", "Parent Collection");
2395 }