Merge branch 'blender2.7'
[blender.git] / source / blender / editors / space_outliner / outliner_edit.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2004 Blender Foundation.
17  * All rights reserved.
18  */
19
20 /** \file \ingroup spoutliner
21  */
22
23 #include <string.h>
24
25 #include "MEM_guardedalloc.h"
26
27 #include "DNA_anim_types.h"
28 #include "DNA_collection_types.h"
29 #include "DNA_ID.h"
30 #include "DNA_scene_types.h"
31 #include "DNA_object_types.h"
32 #include "DNA_material_types.h"
33
34 #include "BLI_blenlib.h"
35 #include "BLI_dynstr.h"
36 #include "BLI_utildefines.h"
37 #include "BLI_path_util.h"
38
39 #include "BLT_translation.h"
40
41 #include "BKE_animsys.h"
42 #include "BKE_collection.h"
43 #include "BKE_context.h"
44 #include "BKE_idcode.h"
45 #include "BKE_layer.h"
46 #include "BKE_library.h"
47 #include "BKE_library_query.h"
48 #include "BKE_library_remap.h"
49 #include "BKE_main.h"
50 #include "BKE_material.h"
51 #include "BKE_outliner_treehash.h"
52 #include "BKE_report.h"
53 #include "BKE_scene.h"
54
55 #include "DEG_depsgraph.h"
56 #include "DEG_depsgraph_build.h"
57
58 #include "../blenloader/BLO_readfile.h"
59
60 #include "ED_object.h"
61 #include "ED_outliner.h"
62 #include "ED_screen.h"
63 #include "ED_select_utils.h"
64 #include "ED_keyframing.h"
65 #include "ED_armature.h"
66
67 #include "WM_api.h"
68 #include "WM_types.h"
69
70 #include "UI_interface.h"
71 #include "UI_resources.h"
72 #include "UI_view2d.h"
73
74 #include "RNA_access.h"
75 #include "RNA_define.h"
76 #include "RNA_enum_types.h"
77
78 #include "GPU_material.h"
79
80 #include "outliner_intern.h"
81
82 /* ************************************************************** */
83
84 /* Highlight --------------------------------------------------- */
85
86 static int outliner_highlight_update(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
87 {
88         /* Drag and drop does own highlighting. */
89         wmWindowManager *wm = CTX_wm_manager(C);
90         if (wm->drags.first) {
91                 return OPERATOR_PASS_THROUGH;
92         }
93
94         ARegion *ar = CTX_wm_region(C);
95         SpaceOops *soops = CTX_wm_space_outliner(C);
96         const float my = UI_view2d_region_to_view_y(&ar->v2d, event->mval[1]);
97
98         TreeElement *hovered_te = outliner_find_item_at_y(soops, &soops->tree, my);
99         bool changed = false;
100
101         if (!hovered_te || !(hovered_te->store_elem->flag & TSE_HIGHLIGHTED)) {
102                 changed = outliner_flag_set(&soops->tree, TSE_HIGHLIGHTED | TSE_DRAG_ANY, false);
103                 if (hovered_te) {
104                         hovered_te->store_elem->flag |= TSE_HIGHLIGHTED;
105                         changed = true;
106                 }
107         }
108
109         if (changed) {
110                 ED_region_tag_redraw_no_rebuild(ar);
111         }
112
113         return OPERATOR_PASS_THROUGH;
114 }
115
116 void OUTLINER_OT_highlight_update(wmOperatorType *ot)
117 {
118         ot->name = "Update Highlight";
119         ot->idname = "OUTLINER_OT_highlight_update";
120         ot->description = "Update the item highlight based on the current mouse position";
121
122         ot->invoke = outliner_highlight_update;
123
124         ot->poll = ED_operator_outliner_active;
125 }
126
127 /* Toggle Open/Closed ------------------------------------------- */
128
129 static int do_outliner_item_openclose(bContext *C, SpaceOops *soops, TreeElement *te, const bool all, const float mval[2])
130 {
131
132         if (mval[1] > te->ys && mval[1] < te->ys + UI_UNIT_Y) {
133                 TreeStoreElem *tselem = TREESTORE(te);
134
135                 /* all below close/open? */
136                 if (all) {
137                         tselem->flag &= ~TSE_CLOSED;
138                         outliner_flag_set(&te->subtree, TSE_CLOSED, !outliner_flag_is_any_test(&te->subtree, TSE_CLOSED, 1));
139                 }
140                 else {
141                         if (tselem->flag & TSE_CLOSED) tselem->flag &= ~TSE_CLOSED;
142                         else tselem->flag |= TSE_CLOSED;
143                 }
144
145                 return 1;
146         }
147
148         for (te = te->subtree.first; te; te = te->next) {
149                 if (do_outliner_item_openclose(C, soops, te, all, mval))
150                         return 1;
151         }
152         return 0;
153
154 }
155
156 /* event can enterkey, then it opens/closes */
157 static int outliner_item_openclose(bContext *C, wmOperator *op, const wmEvent *event)
158 {
159         ARegion *ar = CTX_wm_region(C);
160         SpaceOops *soops = CTX_wm_space_outliner(C);
161         TreeElement *te;
162         float fmval[2];
163         const bool all = RNA_boolean_get(op->ptr, "all");
164
165         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
166
167         for (te = soops->tree.first; te; te = te->next) {
168                 if (do_outliner_item_openclose(C, soops, te, all, fmval))
169                         break;
170         }
171
172         ED_region_tag_redraw(ar);
173
174         return OPERATOR_FINISHED;
175 }
176
177 void OUTLINER_OT_item_openclose(wmOperatorType *ot)
178 {
179         ot->name = "Open/Close";
180         ot->idname = "OUTLINER_OT_item_openclose";
181         ot->description = "Toggle whether item under cursor is enabled or closed";
182
183         ot->invoke = outliner_item_openclose;
184
185         ot->poll = ED_operator_outliner_active;
186
187         RNA_def_boolean(ot->srna, "all", 1, "All", "Close or open all items");
188 }
189
190 /* -------------------------------------------------------------------- */
191 /** \name Object Mode Enter/Exit
192  * \{ */
193
194 static void item_object_mode_enter_exit(
195         bContext *C, ReportList *reports, Object *ob,
196         bool enter)
197 {
198         ViewLayer *view_layer = CTX_data_view_layer(C);
199         Object *obact = OBACT(view_layer);
200
201         if ((ob->type != obact->type) || ID_IS_LINKED(ob->data)) {
202                 return;
203         }
204         if (((ob->mode & obact->mode) != 0) == enter) {
205                 return;
206         }
207
208         if (ob == obact) {
209                 BKE_report(reports, RPT_WARNING, "Active object mode not changed");
210                 return;
211         }
212
213         Base *base = BKE_view_layer_base_find(view_layer, ob);
214         if (base == NULL) {
215                 return;
216         }
217         Scene *scene = CTX_data_scene(C);
218         outliner_object_mode_toggle(C, scene, view_layer, base);
219 }
220
221 void item_object_mode_enter_cb(
222         bContext *C, ReportList *reports, Scene *UNUSED(scene), TreeElement *UNUSED(te),
223         TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem, void *UNUSED(user_data))
224 {
225         Object *ob = (Object *)tselem->id;
226         item_object_mode_enter_exit(C, reports, ob, true);
227 }
228
229 void item_object_mode_exit_cb(
230         bContext *C, ReportList *reports, Scene *UNUSED(scene), TreeElement *UNUSED(te),
231         TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem, void *UNUSED(user_data))
232 {
233         Object *ob = (Object *)tselem->id;
234         item_object_mode_enter_exit(C, reports, ob, false);
235 }
236
237 /** \} */
238
239 /* Rename --------------------------------------------------- */
240
241 static void do_item_rename(ARegion *ar, TreeElement *te, TreeStoreElem *tselem,
242                            ReportList *reports)
243 {
244         bool add_textbut = false;
245
246         /* can't rename rna datablocks entries or listbases */
247         if (ELEM(tselem->type, TSE_RNA_STRUCT, TSE_RNA_PROPERTY, TSE_RNA_ARRAY_ELEM, TSE_ID_BASE, TSE_SCENE_OBJECTS_BASE)) {
248                 /* do nothing */;
249         }
250         else if (ELEM(tselem->type, TSE_ANIM_DATA, TSE_NLA, TSE_DEFGROUP_BASE, TSE_CONSTRAINT_BASE, TSE_MODIFIER_BASE,
251                       TSE_DRIVER_BASE, TSE_POSE_BASE, TSE_POSEGRP_BASE, TSE_R_LAYER_BASE, TSE_SCENE_COLLECTION_BASE,
252                       TSE_VIEW_COLLECTION_BASE))
253         {
254                 BKE_report(reports, RPT_WARNING, "Cannot edit builtin name");
255         }
256         else if (ELEM(tselem->type, TSE_SEQUENCE, TSE_SEQ_STRIP, TSE_SEQUENCE_DUP)) {
257                 BKE_report(reports, RPT_WARNING, "Cannot edit sequence name");
258         }
259         else if (outliner_is_collection_tree_element(te)) {
260                 Collection *collection = outliner_collection_from_tree_element(te);
261
262                 if (collection->flag & COLLECTION_IS_MASTER) {
263                         BKE_report(reports, RPT_WARNING, "Cannot edit name of master collection");
264                 }
265                 else {
266                         add_textbut = true;
267                 }
268         }
269         else if (ID_IS_LINKED(tselem->id)) {
270                 BKE_report(reports, RPT_WARNING, "Cannot edit external libdata");
271         }
272         else if (te->idcode == ID_LI && ((Library *)tselem->id)->parent) {
273                 BKE_report(reports, RPT_WARNING, "Cannot edit the path of an indirectly linked library");
274         }
275         else {
276                 add_textbut = true;
277         }
278
279         if (add_textbut) {
280                 tselem->flag |= TSE_TEXTBUT;
281                 ED_region_tag_redraw(ar);
282         }
283 }
284
285 void item_rename_cb(
286         bContext *C, ReportList *reports, Scene *UNUSED(scene), TreeElement *te,
287         TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem, void *UNUSED(user_data))
288 {
289         ARegion *ar = CTX_wm_region(C);
290         do_item_rename(ar, te, tselem, reports);
291 }
292
293 static int do_outliner_item_rename(ReportList *reports, ARegion *ar, TreeElement *te,
294                                    const float mval[2])
295 {
296         if (mval[1] > te->ys && mval[1] < te->ys + UI_UNIT_Y) {
297                 TreeStoreElem *tselem = TREESTORE(te);
298
299                 /* click on name */
300                 if (mval[0] > te->xs + UI_UNIT_X * 2 && mval[0] < te->xend) {
301                         do_item_rename(ar, te, tselem, reports);
302                         return 1;
303                 }
304                 return 0;
305         }
306
307         for (te = te->subtree.first; te; te = te->next) {
308                 if (do_outliner_item_rename(reports, ar, te, mval)) return 1;
309         }
310         return 0;
311 }
312
313 static int outliner_item_rename(bContext *C, wmOperator *op, const wmEvent *event)
314 {
315         ARegion *ar = CTX_wm_region(C);
316         SpaceOops *soops = CTX_wm_space_outliner(C);
317         TreeElement *te;
318         float fmval[2];
319         bool changed = false;
320
321         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
322
323         for (te = soops->tree.first; te; te = te->next) {
324                 if (do_outliner_item_rename(op->reports, ar, te, fmval)) {
325                         changed = true;
326                         break;
327                 }
328         }
329
330         return changed ? OPERATOR_FINISHED : OPERATOR_PASS_THROUGH;
331 }
332
333
334 void OUTLINER_OT_item_rename(wmOperatorType *ot)
335 {
336         ot->name = "Rename";
337         ot->idname = "OUTLINER_OT_item_rename";
338         ot->description = "Rename item under cursor";
339
340         ot->invoke = outliner_item_rename;
341
342         ot->poll = ED_operator_outliner_active;
343 }
344
345 /* ID delete --------------------------------------------------- */
346
347 static void id_delete(bContext *C, ReportList *reports, TreeElement *te, TreeStoreElem *tselem)
348 {
349         Main *bmain = CTX_data_main(C);
350         ID *id = tselem->id;
351
352         BLI_assert(te->idcode != 0 && id != NULL);
353         UNUSED_VARS_NDEBUG(te);
354
355         if (te->idcode == ID_LI && ((Library *)id)->parent != NULL) {
356                 BKE_reportf(reports, RPT_WARNING, "Cannot delete indirectly linked library '%s'", id->name);
357                 return;
358         }
359         if (id->tag & LIB_TAG_INDIRECT) {
360                 BKE_reportf(reports, RPT_WARNING, "Cannot delete indirectly linked id '%s'", id->name);
361                 return;
362         }
363         else if (BKE_library_ID_is_indirectly_used(bmain, id) && ID_REAL_USERS(id) <= 1) {
364                 BKE_reportf(reports, RPT_WARNING,
365                             "Cannot delete id '%s', indirectly used data-blocks need at least one user",
366                             id->name);
367                 return;
368         }
369
370
371         BKE_id_delete(bmain, id);
372
373         WM_event_add_notifier(C, NC_WINDOW, NULL);
374 }
375
376 void id_delete_cb(
377         bContext *C, ReportList *reports, Scene *UNUSED(scene),
378         TreeElement *te, TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem, void *UNUSED(user_data))
379 {
380         id_delete(C, reports, te, tselem);
381 }
382
383 static int outliner_id_delete_invoke_do(bContext *C, ReportList *reports, TreeElement *te, const float mval[2])
384 {
385         if (mval[1] > te->ys && mval[1] < te->ys + UI_UNIT_Y) {
386                 TreeStoreElem *tselem = TREESTORE(te);
387
388                 if (te->idcode != 0 && tselem->id) {
389                         if (te->idcode == ID_LI && ((Library *)tselem->id)->parent) {
390                                 BKE_reportf(reports, RPT_ERROR_INVALID_INPUT,
391                                             "Cannot delete indirectly linked library '%s'", ((Library *)tselem->id)->filepath);
392                                 return OPERATOR_CANCELLED;
393                         }
394                         id_delete(C, reports, te, tselem);
395                         return OPERATOR_FINISHED;
396                 }
397         }
398         else {
399                 for (te = te->subtree.first; te; te = te->next) {
400                         int ret;
401                         if ((ret = outliner_id_delete_invoke_do(C, reports, te, mval))) {
402                                 return ret;
403                         }
404                 }
405         }
406
407         return 0;
408 }
409
410 static int outliner_id_delete_invoke(bContext *C, wmOperator *op, const wmEvent *event)
411 {
412         ARegion *ar = CTX_wm_region(C);
413         SpaceOops *soops = CTX_wm_space_outliner(C);
414         TreeElement *te;
415         float fmval[2];
416
417         BLI_assert(ar && soops);
418
419         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
420
421         for (te = soops->tree.first; te; te = te->next) {
422                 int ret;
423
424                 if ((ret = outliner_id_delete_invoke_do(C, op->reports, te, fmval))) {
425                         return ret;
426                 }
427         }
428
429         return OPERATOR_CANCELLED;
430 }
431
432 void OUTLINER_OT_id_delete(wmOperatorType *ot)
433 {
434         ot->name = "Delete Data-Block";
435         ot->idname = "OUTLINER_OT_id_delete";
436         ot->description = "Delete the ID under cursor";
437
438         ot->invoke = outliner_id_delete_invoke;
439         ot->poll = ED_operator_outliner_active;
440 }
441
442 /* ID remap --------------------------------------------------- */
443
444 static int outliner_id_remap_exec(bContext *C, wmOperator *op)
445 {
446         Main *bmain = CTX_data_main(C);
447         SpaceOops *soops = CTX_wm_space_outliner(C);
448
449         const short id_type = (short)RNA_enum_get(op->ptr, "id_type");
450         ID *old_id = BLI_findlink(which_libbase(CTX_data_main(C), id_type), RNA_enum_get(op->ptr, "old_id"));
451         ID *new_id = BLI_findlink(which_libbase(CTX_data_main(C), id_type), RNA_enum_get(op->ptr, "new_id"));
452
453         /* check for invalid states */
454         if (soops == NULL) {
455                 return OPERATOR_CANCELLED;
456         }
457
458         if (!(old_id && new_id && (old_id != new_id) && (GS(old_id->name) == GS(new_id->name)))) {
459                 BKE_reportf(op->reports, RPT_ERROR_INVALID_INPUT, "Invalid old/new ID pair ('%s' / '%s')",
460                             old_id ? old_id->name : "Invalid ID", new_id ? new_id->name : "Invalid ID");
461                 return OPERATOR_CANCELLED;
462         }
463
464         if (ID_IS_LINKED(old_id)) {
465                 BKE_reportf(op->reports, RPT_WARNING,
466                             "Old ID '%s' is linked from a library, indirect usages of this data-block will not be remapped",
467                             old_id->name);
468         }
469
470         BKE_libblock_remap(bmain, old_id, new_id,
471                            ID_REMAP_SKIP_INDIRECT_USAGE | ID_REMAP_SKIP_NEVER_NULL_USAGE);
472
473         BKE_main_lib_objects_recalc_all(bmain);
474
475         /* recreate dependency graph to include new objects */
476         DEG_relations_tag_update(bmain);
477
478         /* free gpu materials, some materials depend on existing objects,
479          * such as lamps so freeing correctly refreshes */
480         GPU_materials_free(bmain);
481
482         WM_event_add_notifier(C, NC_WINDOW, NULL);
483
484         return OPERATOR_FINISHED;
485 }
486
487 static bool outliner_id_remap_find_tree_element(bContext *C, wmOperator *op, ListBase *tree, const float y)
488 {
489         TreeElement *te;
490
491         for (te = tree->first; te; te = te->next) {
492                 if (y > te->ys && y < te->ys + UI_UNIT_Y) {
493                         TreeStoreElem *tselem = TREESTORE(te);
494
495                         if (tselem->type == 0 && tselem->id) {
496                                 printf("found id %s (%p)!\n", tselem->id->name, tselem->id);
497
498                                 RNA_enum_set(op->ptr, "id_type", GS(tselem->id->name));
499                                 RNA_enum_set_identifier(C, op->ptr, "new_id", tselem->id->name + 2);
500                                 RNA_enum_set_identifier(C, op->ptr, "old_id", tselem->id->name + 2);
501                                 return true;
502                         }
503                 }
504                 if (outliner_id_remap_find_tree_element(C, op, &te->subtree, y)) {
505                         return true;
506                 }
507         }
508         return false;
509 }
510
511 static int outliner_id_remap_invoke(bContext *C, wmOperator *op, const wmEvent *event)
512 {
513         SpaceOops *soops = CTX_wm_space_outliner(C);
514         ARegion *ar = CTX_wm_region(C);
515         float fmval[2];
516
517         if (!RNA_property_is_set(op->ptr, RNA_struct_find_property(op->ptr, "id_type"))) {
518                 UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
519
520                 outliner_id_remap_find_tree_element(C, op, &soops->tree, fmval[1]);
521         }
522
523         return WM_operator_props_dialog_popup(C, op, 200, 100);
524 }
525
526 static const EnumPropertyItem *outliner_id_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free)
527 {
528         EnumPropertyItem item_tmp = {0}, *item = NULL;
529         int totitem = 0;
530         int i = 0;
531
532         short id_type = (short)RNA_enum_get(ptr, "id_type");
533         ID *id = which_libbase(CTX_data_main(C), id_type)->first;
534
535         for (; id; id = id->next) {
536                 item_tmp.identifier = item_tmp.name = id->name + 2;
537                 item_tmp.value = i++;
538                 RNA_enum_item_add(&item, &totitem, &item_tmp);
539         }
540
541         RNA_enum_item_end(&item, &totitem);
542         *r_free = true;
543
544         return item;
545 }
546
547 void OUTLINER_OT_id_remap(wmOperatorType *ot)
548 {
549         PropertyRNA *prop;
550
551         /* identifiers */
552         ot->name = "Outliner ID data Remap";
553         ot->idname = "OUTLINER_OT_id_remap";
554
555         /* callbacks */
556         ot->invoke = outliner_id_remap_invoke;
557         ot->exec = outliner_id_remap_exec;
558         ot->poll = ED_operator_outliner_active;
559
560         ot->flag = 0;
561
562         prop = RNA_def_enum(ot->srna, "id_type", rna_enum_id_type_items, ID_OB, "ID Type", "");
563         RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_ID);
564
565         prop = RNA_def_enum(ot->srna, "old_id", DummyRNA_NULL_items, 0, "Old ID", "Old ID to replace");
566         RNA_def_property_enum_funcs_runtime(prop, NULL, NULL, outliner_id_itemf);
567         RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE | PROP_HIDDEN);
568
569         ot->prop = RNA_def_enum(ot->srna, "new_id", DummyRNA_NULL_items, 0,
570                                 "New ID", "New ID to remap all selected IDs' users to");
571         RNA_def_property_enum_funcs_runtime(ot->prop, NULL, NULL, outliner_id_itemf);
572         RNA_def_property_flag(ot->prop, PROP_ENUM_NO_TRANSLATE);
573 }
574
575 void id_remap_cb(
576         bContext *C, ReportList *UNUSED(reports), Scene *UNUSED(scene), TreeElement *UNUSED(te),
577         TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem, void *UNUSED(user_data))
578 {
579         wmOperatorType *ot = WM_operatortype_find("OUTLINER_OT_id_remap", false);
580         PointerRNA op_props;
581
582         BLI_assert(tselem->id != NULL);
583
584         WM_operator_properties_create_ptr(&op_props, ot);
585
586         RNA_enum_set(&op_props, "id_type", GS(tselem->id->name));
587         RNA_enum_set_identifier(C, &op_props, "old_id", tselem->id->name + 2);
588
589         WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &op_props);
590
591         WM_operator_properties_free(&op_props);
592 }
593
594 /* Library relocate/reload --------------------------------------------------- */
595
596 static int lib_relocate(
597         bContext *C, TreeElement *te, TreeStoreElem *tselem, wmOperatorType *ot, const bool reload)
598 {
599         PointerRNA op_props;
600         int ret = 0;
601
602         BLI_assert(te->idcode == ID_LI && tselem->id != NULL);
603         UNUSED_VARS_NDEBUG(te);
604
605         WM_operator_properties_create_ptr(&op_props, ot);
606
607         RNA_string_set(&op_props, "library", tselem->id->name + 2);
608
609         if (reload) {
610                 Library *lib = (Library *)tselem->id;
611                 char dir[FILE_MAXDIR], filename[FILE_MAX];
612
613                 BLI_split_dirfile(lib->filepath, dir, filename, sizeof(dir), sizeof(filename));
614
615                 printf("%s, %s\n", tselem->id->name, lib->filepath);
616
617                 /* We assume if both paths in lib are not the same then lib->name was relative... */
618                 RNA_boolean_set(&op_props, "relative_path", BLI_path_cmp(lib->filepath, lib->name) != 0);
619
620                 RNA_string_set(&op_props, "directory", dir);
621                 RNA_string_set(&op_props, "filename", filename);
622
623                 ret = WM_operator_name_call_ptr(C, ot, WM_OP_EXEC_DEFAULT, &op_props);
624         }
625         else {
626                 ret = WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &op_props);
627         }
628
629         WM_operator_properties_free(&op_props);
630
631         return ret;
632 }
633
634 static int outliner_lib_relocate_invoke_do(
635         bContext *C, ReportList *reports, TreeElement *te, const float mval[2], const bool reload)
636 {
637         if (mval[1] > te->ys && mval[1] < te->ys + UI_UNIT_Y) {
638                 TreeStoreElem *tselem = TREESTORE(te);
639
640                 if (te->idcode == ID_LI && tselem->id) {
641                         if (((Library *)tselem->id)->parent && !reload) {
642                                 BKE_reportf(reports, RPT_ERROR_INVALID_INPUT,
643                                             "Cannot relocate indirectly linked library '%s'", ((Library *)tselem->id)->filepath);
644                                 return OPERATOR_CANCELLED;
645                         }
646                         else {
647                                 wmOperatorType *ot = WM_operatortype_find(reload ? "WM_OT_lib_reload" : "WM_OT_lib_relocate", false);
648
649                                 return lib_relocate(C, te, tselem, ot, reload);
650                         }
651                 }
652         }
653         else {
654                 for (te = te->subtree.first; te; te = te->next) {
655                         int ret;
656                         if ((ret = outliner_lib_relocate_invoke_do(C, reports, te, mval, reload))) {
657                                 return ret;
658                         }
659                 }
660         }
661
662         return 0;
663 }
664
665 static int outliner_lib_relocate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
666 {
667         ARegion *ar = CTX_wm_region(C);
668         SpaceOops *soops = CTX_wm_space_outliner(C);
669         TreeElement *te;
670         float fmval[2];
671
672         BLI_assert(ar && soops);
673
674         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
675
676         for (te = soops->tree.first; te; te = te->next) {
677                 int ret;
678
679                 if ((ret = outliner_lib_relocate_invoke_do(C, op->reports, te, fmval, false))) {
680                         return ret;
681                 }
682         }
683
684         return OPERATOR_CANCELLED;
685 }
686
687 void OUTLINER_OT_lib_relocate(wmOperatorType *ot)
688 {
689         ot->name = "Relocate Library";
690         ot->idname = "OUTLINER_OT_lib_relocate";
691         ot->description = "Relocate the library under cursor";
692
693         ot->invoke = outliner_lib_relocate_invoke;
694         ot->poll = ED_operator_outliner_active;
695 }
696
697 /* XXX This does not work with several items
698  * (it is only called once in the end, due to the 'deferred'
699  * filebrowser invocation through event system...). */
700 void lib_relocate_cb(
701         bContext *C, ReportList *UNUSED(reports), Scene *UNUSED(scene), TreeElement *te,
702         TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem, void *UNUSED(user_data))
703 {
704         wmOperatorType *ot = WM_operatortype_find("WM_OT_lib_relocate", false);
705
706         lib_relocate(C, te, tselem, ot, false);
707 }
708
709
710 static int outliner_lib_reload_invoke(bContext *C, wmOperator *op, const wmEvent *event)
711 {
712         ARegion *ar = CTX_wm_region(C);
713         SpaceOops *soops = CTX_wm_space_outliner(C);
714         TreeElement *te;
715         float fmval[2];
716
717         BLI_assert(ar && soops);
718
719         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
720
721         for (te = soops->tree.first; te; te = te->next) {
722                 int ret;
723
724                 if ((ret = outliner_lib_relocate_invoke_do(C, op->reports, te, fmval, true))) {
725                         return ret;
726                 }
727         }
728
729         return OPERATOR_CANCELLED;
730 }
731
732 void OUTLINER_OT_lib_reload(wmOperatorType *ot)
733 {
734         ot->name = "Reload Library";
735         ot->idname = "OUTLINER_OT_lib_reload";
736         ot->description = "Reload the library under cursor";
737
738         ot->invoke = outliner_lib_reload_invoke;
739         ot->poll = ED_operator_outliner_active;
740 }
741
742 void lib_reload_cb(
743         bContext *C, ReportList *UNUSED(reports), Scene *UNUSED(scene), TreeElement *te,
744         TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem, void *UNUSED(user_data))
745 {
746         wmOperatorType *ot = WM_operatortype_find("WM_OT_lib_reload", false);
747
748         lib_relocate(C, te, tselem, ot, true);
749 }
750
751 /* ************************************************************** */
752 /* Setting Toggling Operators */
753
754 /* =============================================== */
755 /* Toggling Utilities (Exported) */
756
757 /* Apply Settings ------------------------------- */
758
759 static int outliner_count_levels(ListBase *lb, const int curlevel)
760 {
761         TreeElement *te;
762         int level = curlevel, lev;
763
764         for (te = lb->first; te; te = te->next) {
765
766                 lev = outliner_count_levels(&te->subtree, curlevel + 1);
767                 if (lev > level) level = lev;
768         }
769         return level;
770 }
771
772 int outliner_flag_is_any_test(ListBase *lb, short flag, const int curlevel)
773 {
774         TreeElement *te;
775         TreeStoreElem *tselem;
776         int level;
777
778         for (te = lb->first; te; te = te->next) {
779                 tselem = TREESTORE(te);
780                 if (tselem->flag & flag) return curlevel;
781
782                 level = outliner_flag_is_any_test(&te->subtree, flag, curlevel + 1);
783                 if (level) return level;
784         }
785         return 0;
786 }
787
788 /**
789  * Set or unset \a flag for all outliner elements in \a lb and sub-trees.
790  * \return if any flag was modified.
791  */
792 bool outliner_flag_set(ListBase *lb, short flag, short set)
793 {
794         TreeElement *te;
795         TreeStoreElem *tselem;
796         bool changed = false;
797         bool has_flag;
798
799         for (te = lb->first; te; te = te->next) {
800                 tselem = TREESTORE(te);
801                 has_flag = (tselem->flag & flag);
802                 if (set == 0) {
803                         if (has_flag) {
804                                 tselem->flag &= ~flag;
805                                 changed = true;
806                         }
807                 }
808                 else if (!has_flag) {
809                         tselem->flag |= flag;
810                         changed = true;
811                 }
812                 changed |= outliner_flag_set(&te->subtree, flag, set);
813         }
814
815         return changed;
816 }
817
818 bool outliner_flag_flip(ListBase *lb, short flag)
819 {
820         TreeElement *te;
821         TreeStoreElem *tselem;
822         bool changed = false;
823
824         for (te = lb->first; te; te = te->next) {
825                 tselem = TREESTORE(te);
826                 tselem->flag ^= flag;
827                 changed |= outliner_flag_flip(&te->subtree, flag);
828         }
829
830         return changed;
831 }
832
833 /* Restriction Columns ------------------------------- */
834
835 /* same check needed for both object operation and restrict column button func
836  * return 0 when in edit mode (cannot restrict view or select)
837  * otherwise return 1 */
838 int common_restrict_check(bContext *C, Object *ob)
839 {
840         /* Don't allow hide an object in edit mode,
841          * check the bug #22153 and #21609, #23977
842          */
843         Object *obedit = CTX_data_edit_object(C);
844         if (obedit && obedit == ob) {
845                 /* found object is hidden, reset */
846                 if (ob->restrictflag & OB_RESTRICT_VIEW)
847                         ob->restrictflag &= ~OB_RESTRICT_VIEW;
848                 /* found object is unselectable, reset */
849                 if (ob->restrictflag & OB_RESTRICT_SELECT)
850                         ob->restrictflag &= ~OB_RESTRICT_SELECT;
851                 return 0;
852         }
853
854         return 1;
855 }
856
857 /* =============================================== */
858 /* Outliner setting toggles */
859
860 /* Toggle Expanded (Outliner) ---------------------------------------- */
861
862 static int outliner_toggle_expanded_exec(bContext *C, wmOperator *UNUSED(op))
863 {
864         SpaceOops *soops = CTX_wm_space_outliner(C);
865         ARegion *ar = CTX_wm_region(C);
866
867         if (outliner_flag_is_any_test(&soops->tree, TSE_CLOSED, 1))
868                 outliner_flag_set(&soops->tree, TSE_CLOSED, 0);
869         else
870                 outliner_flag_set(&soops->tree, TSE_CLOSED, 1);
871
872         ED_region_tag_redraw(ar);
873
874         return OPERATOR_FINISHED;
875 }
876
877 void OUTLINER_OT_expanded_toggle(wmOperatorType *ot)
878 {
879         /* identifiers */
880         ot->name = "Expand/Collapse All";
881         ot->idname = "OUTLINER_OT_expanded_toggle";
882         ot->description = "Expand/Collapse all items";
883
884         /* callbacks */
885         ot->exec = outliner_toggle_expanded_exec;
886         ot->poll = ED_operator_outliner_active;
887
888         /* no undo or registry, UI option */
889 }
890
891 /* Toggle Selected (Outliner) ---------------------------------------- */
892
893 static int outliner_select_all_exec(bContext *C, wmOperator *op)
894 {
895         SpaceOops *soops = CTX_wm_space_outliner(C);
896         ARegion *ar = CTX_wm_region(C);
897         Scene *scene = CTX_data_scene(C);
898         int action = RNA_enum_get(op->ptr, "action");
899         if (action == SEL_TOGGLE) {
900                 action = outliner_flag_is_any_test(&soops->tree, TSE_SELECTED, 1) ? SEL_DESELECT : SEL_SELECT;
901         }
902
903         switch (action) {
904                 case SEL_SELECT:
905                         outliner_flag_set(&soops->tree, TSE_SELECTED, 1);
906                         break;
907                 case SEL_DESELECT:
908                         outliner_flag_set(&soops->tree, TSE_SELECTED, 0);
909                         break;
910                 case SEL_INVERT:
911                         outliner_flag_flip(&soops->tree, TSE_SELECTED);
912                         break;
913         }
914
915         DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
916         WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
917         ED_region_tag_redraw_no_rebuild(ar);
918
919         return OPERATOR_FINISHED;
920 }
921
922 void OUTLINER_OT_select_all(wmOperatorType *ot)
923 {
924         /* identifiers */
925         ot->name = "Toggle Selected";
926         ot->idname = "OUTLINER_OT_select_all";
927         ot->description = "Toggle the Outliner selection of items";
928
929         /* callbacks */
930         ot->exec = outliner_select_all_exec;
931         ot->poll = ED_operator_outliner_active;
932
933         /* no undo or registry */
934
935         /* rna */
936         WM_operator_properties_select_all(ot);
937 }
938
939 /* ************************************************************** */
940 /* Hotkey Only Operators */
941
942 /* Show Active --------------------------------------------------- */
943
944 static void outliner_set_coordinates_element_recursive(SpaceOops *soops, TreeElement *te, int startx, int *starty)
945 {
946         TreeStoreElem *tselem = TREESTORE(te);
947
948         /* store coord and continue, we need coordinates for elements outside view too */
949         te->xs = (float)startx;
950         te->ys = (float)(*starty);
951         *starty -= UI_UNIT_Y;
952
953         if (TSELEM_OPEN(tselem, soops)) {
954                 TreeElement *ten;
955                 for (ten = te->subtree.first; ten; ten = ten->next) {
956                         outliner_set_coordinates_element_recursive(soops, ten, startx + UI_UNIT_X, starty);
957                 }
958         }
959 }
960
961 /* to retrieve coordinates with redrawing the entire tree */
962 void outliner_set_coordinates(ARegion *ar, SpaceOops *soops)
963 {
964         TreeElement *te;
965         int starty = (int)(ar->v2d.tot.ymax) - UI_UNIT_Y;
966
967         for (te = soops->tree.first; te; te = te->next) {
968                 outliner_set_coordinates_element_recursive(soops, te, 0, &starty);
969         }
970 }
971
972 /* return 1 when levels were opened */
973 static int outliner_open_back(TreeElement *te)
974 {
975         TreeStoreElem *tselem;
976         int retval = 0;
977
978         for (te = te->parent; te; te = te->parent) {
979                 tselem = TREESTORE(te);
980                 if (tselem->flag & TSE_CLOSED) {
981                         tselem->flag &= ~TSE_CLOSED;
982                         retval = 1;
983                 }
984         }
985         return retval;
986 }
987
988 static int outliner_show_active_exec(bContext *C, wmOperator *UNUSED(op))
989 {
990         SpaceOops *so = CTX_wm_space_outliner(C);
991         ViewLayer *view_layer = CTX_data_view_layer(C);
992         ARegion *ar = CTX_wm_region(C);
993         View2D *v2d = &ar->v2d;
994
995         TreeElement *te;
996         int xdelta, ytop;
997
998         Object *obact = OBACT(view_layer);
999
1000         if (!obact)
1001                 return OPERATOR_CANCELLED;
1002
1003
1004         te = outliner_find_id(so, &so->tree, &obact->id);
1005
1006         if (te != NULL && obact->type == OB_ARMATURE) {
1007                 /* traverse down the bone hierarchy in case of armature */
1008                 TreeElement *te_obact = te;
1009
1010                 if (obact->mode & OB_MODE_POSE) {
1011                         bPoseChannel *pchan = CTX_data_active_pose_bone(C);
1012                         if (pchan) {
1013                                 te = outliner_find_posechannel(&te_obact->subtree, pchan);
1014                         }
1015                 }
1016                 else if (obact->mode & OB_MODE_EDIT) {
1017                         EditBone *ebone = CTX_data_active_bone(C);
1018                         if (ebone) {
1019                                 te = outliner_find_editbone(&te_obact->subtree, ebone);
1020                         }
1021                 }
1022         }
1023
1024         if (te) {
1025                 /* open up tree to active object/bone */
1026                 if (outliner_open_back(te)) {
1027                         outliner_set_coordinates(ar, so);
1028                 }
1029
1030                 /* make te->ys center of view */
1031                 ytop = te->ys + BLI_rcti_size_y(&v2d->mask) / 2;
1032                 if (ytop > 0) ytop = 0;
1033
1034                 v2d->cur.ymax = (float)ytop;
1035                 v2d->cur.ymin = (float)(ytop - BLI_rcti_size_y(&v2d->mask));
1036
1037                 /* make te->xs ==> te->xend center of view */
1038                 xdelta = (int)(te->xs - v2d->cur.xmin);
1039                 v2d->cur.xmin += xdelta;
1040                 v2d->cur.xmax += xdelta;
1041         }
1042
1043         ED_region_tag_redraw_no_rebuild(ar);
1044
1045         return OPERATOR_FINISHED;
1046 }
1047
1048 void OUTLINER_OT_show_active(wmOperatorType *ot)
1049 {
1050         /* identifiers */
1051         ot->name = "Show Active";
1052         ot->idname = "OUTLINER_OT_show_active";
1053         ot->description = "Open up the tree and adjust the view so that the active Object is shown centered";
1054
1055         /* callbacks */
1056         ot->exec = outliner_show_active_exec;
1057         ot->poll = ED_operator_outliner_active;
1058 }
1059
1060 /* View Panning --------------------------------------------------- */
1061
1062 static int outliner_scroll_page_exec(bContext *C, wmOperator *op)
1063 {
1064         ARegion *ar = CTX_wm_region(C);
1065         int dy = BLI_rcti_size_y(&ar->v2d.mask);
1066         int up = 0;
1067
1068         if (RNA_boolean_get(op->ptr, "up"))
1069                 up = 1;
1070
1071         if (up == 0) dy = -dy;
1072         ar->v2d.cur.ymin += dy;
1073         ar->v2d.cur.ymax += dy;
1074
1075         ED_region_tag_redraw_no_rebuild(ar);
1076
1077         return OPERATOR_FINISHED;
1078 }
1079
1080
1081 void OUTLINER_OT_scroll_page(wmOperatorType *ot)
1082 {
1083         PropertyRNA *prop;
1084
1085         /* identifiers */
1086         ot->name = "Scroll Page";
1087         ot->idname = "OUTLINER_OT_scroll_page";
1088         ot->description = "Scroll page up or down";
1089
1090         /* callbacks */
1091         ot->exec = outliner_scroll_page_exec;
1092         ot->poll = ED_operator_outliner_active;
1093
1094         /* properties */
1095         prop = RNA_def_boolean(ot->srna, "up", 0, "Up", "Scroll up one page");
1096         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
1097 }
1098
1099 /* Search ------------------------------------------------------- */
1100 // TODO: probably obsolete now with filtering?
1101
1102 #if 0
1103
1104 /* find next element that has this name */
1105 static TreeElement *outliner_find_name(SpaceOops *soops, ListBase *lb, char *name, int flags,
1106                                        TreeElement *prev, int *prevFound)
1107 {
1108         TreeElement *te, *tes;
1109
1110         for (te = lb->first; te; te = te->next) {
1111                 int found = outliner_filter_has_name(te, name, flags);
1112
1113                 if (found) {
1114                         /* name is right, but is element the previous one? */
1115                         if (prev) {
1116                                 if ((te != prev) && (*prevFound))
1117                                         return te;
1118                                 if (te == prev) {
1119                                         *prevFound = 1;
1120                                 }
1121                         }
1122                         else
1123                                 return te;
1124                 }
1125
1126                 tes = outliner_find_name(soops, &te->subtree, name, flags, prev, prevFound);
1127                 if (tes) return tes;
1128         }
1129
1130         /* nothing valid found */
1131         return NULL;
1132 }
1133
1134 static void outliner_find_panel(Scene *UNUSED(scene), ARegion *ar, SpaceOops *soops, int again, int flags)
1135 {
1136         ReportList *reports = NULL; // CTX_wm_reports(C);
1137         TreeElement *te = NULL;
1138         TreeElement *last_find;
1139         TreeStoreElem *tselem;
1140         int ytop, xdelta, prevFound = 0;
1141         char name[sizeof(soops->search_string)];
1142
1143         /* get last found tree-element based on stored search_tse */
1144         last_find = outliner_find_tse(soops, &soops->search_tse);
1145
1146         /* determine which type of search to do */
1147         if (again && last_find) {
1148                 /* no popup panel - previous + user wanted to search for next after previous */
1149                 BLI_strncpy(name, soops->search_string, sizeof(name));
1150                 flags = soops->search_flags;
1151
1152                 /* try to find matching element */
1153                 te = outliner_find_name(soops, &soops->tree, name, flags, last_find, &prevFound);
1154                 if (te == NULL) {
1155                         /* no more matches after previous, start from beginning again */
1156                         prevFound = 1;
1157                         te = outliner_find_name(soops, &soops->tree, name, flags, last_find, &prevFound);
1158                 }
1159         }
1160         else {
1161                 /* pop up panel - no previous, or user didn't want search after previous */
1162                 name[0] = '\0';
1163 // XXX          if (sbutton(name, 0, sizeof(name) - 1, "Find: ") && name[0]) {
1164 //                      te = outliner_find_name(soops, &soops->tree, name, flags, NULL, &prevFound);
1165 //              }
1166 //              else return; /* XXX RETURN! XXX */
1167         }
1168
1169         /* do selection and reveal */
1170         if (te) {
1171                 tselem = TREESTORE(te);
1172                 if (tselem) {
1173                         /* expand branches so that it will be visible, we need to get correct coordinates */
1174                         if (outliner_open_back(soops, te))
1175                                 outliner_set_coordinates(ar, soops);
1176
1177                         /* deselect all visible, and select found element */
1178                         outliner_flag_set(soops, &soops->tree, TSE_SELECTED, 0);
1179                         tselem->flag |= TSE_SELECTED;
1180
1181                         /* make te->ys center of view */
1182                         ytop = (int)(te->ys + BLI_rctf_size_y(&ar->v2d.mask) / 2);
1183                         if (ytop > 0) ytop = 0;
1184                         ar->v2d.cur.ymax = (float)ytop;
1185                         ar->v2d.cur.ymin = (float)(ytop - BLI_rctf_size_y(&ar->v2d.mask));
1186
1187                         /* make te->xs ==> te->xend center of view */
1188                         xdelta = (int)(te->xs - ar->v2d.cur.xmin);
1189                         ar->v2d.cur.xmin += xdelta;
1190                         ar->v2d.cur.xmax += xdelta;
1191
1192                         /* store selection */
1193                         soops->search_tse = *tselem;
1194
1195                         BLI_strncpy(soops->search_string, name, sizeof(soops->search_string));
1196                         soops->search_flags = flags;
1197
1198                         /* redraw */
1199                         ED_region_tag_redraw_no_rebuild(ar);
1200                 }
1201         }
1202         else {
1203                 /* no tree-element found */
1204                 BKE_reportf(reports, RPT_WARNING, "Not found: %s", name);
1205         }
1206 }
1207 #endif
1208
1209 /* Show One Level ----------------------------------------------- */
1210
1211 /* helper function for Show/Hide one level operator */
1212 static void outliner_openclose_level(ListBase *lb, int curlevel, int level, int open)
1213 {
1214         TreeElement *te;
1215         TreeStoreElem *tselem;
1216
1217         for (te = lb->first; te; te = te->next) {
1218                 tselem = TREESTORE(te);
1219
1220                 if (open) {
1221                         if (curlevel <= level) tselem->flag &= ~TSE_CLOSED;
1222                 }
1223                 else {
1224                         if (curlevel >= level) tselem->flag |= TSE_CLOSED;
1225                 }
1226
1227                 outliner_openclose_level(&te->subtree, curlevel + 1, level, open);
1228         }
1229 }
1230
1231 static int outliner_one_level_exec(bContext *C, wmOperator *op)
1232 {
1233         SpaceOops *soops = CTX_wm_space_outliner(C);
1234         ARegion *ar = CTX_wm_region(C);
1235         const bool add = RNA_boolean_get(op->ptr, "open");
1236         int level;
1237
1238         level = outliner_flag_is_any_test(&soops->tree, TSE_CLOSED, 1);
1239         if (add == 1) {
1240                 if (level) outliner_openclose_level(&soops->tree, 1, level, 1);
1241         }
1242         else {
1243                 if (level == 0) level = outliner_count_levels(&soops->tree, 0);
1244                 if (level) outliner_openclose_level(&soops->tree, 1, level - 1, 0);
1245         }
1246
1247         ED_region_tag_redraw(ar);
1248
1249         return OPERATOR_FINISHED;
1250 }
1251
1252 void OUTLINER_OT_show_one_level(wmOperatorType *ot)
1253 {
1254         PropertyRNA *prop;
1255
1256         /* identifiers */
1257         ot->name = "Show/Hide One Level";
1258         ot->idname = "OUTLINER_OT_show_one_level";
1259         ot->description = "Expand/collapse all entries by one level";
1260
1261         /* callbacks */
1262         ot->exec = outliner_one_level_exec;
1263         ot->poll = ED_operator_outliner_active;
1264
1265         /* no undo or registry, UI option */
1266
1267         /* properties */
1268         prop = RNA_def_boolean(ot->srna, "open", 1, "Open", "Expand all entries one level deep");
1269         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
1270 }
1271
1272 /* Show Hierarchy ----------------------------------------------- */
1273
1274 /* helper function for tree_element_shwo_hierarchy() - recursively checks whether subtrees have any objects*/
1275 static int subtree_has_objects(ListBase *lb)
1276 {
1277         TreeElement *te;
1278         TreeStoreElem *tselem;
1279
1280         for (te = lb->first; te; te = te->next) {
1281                 tselem = TREESTORE(te);
1282                 if (tselem->type == 0 && te->idcode == ID_OB) return 1;
1283                 if (subtree_has_objects(&te->subtree)) return 1;
1284         }
1285         return 0;
1286 }
1287
1288 /* recursive helper function for Show Hierarchy operator */
1289 static void tree_element_show_hierarchy(Scene *scene, SpaceOops *soops, ListBase *lb)
1290 {
1291         TreeElement *te;
1292         TreeStoreElem *tselem;
1293
1294         /* open all object elems, close others */
1295         for (te = lb->first; te; te = te->next) {
1296                 tselem = TREESTORE(te);
1297
1298                 if (tselem->type == 0) {
1299                         if (te->idcode == ID_SCE) {
1300                                 if (tselem->id != (ID *)scene) tselem->flag |= TSE_CLOSED;
1301                                 else tselem->flag &= ~TSE_CLOSED;
1302                         }
1303                         else if (te->idcode == ID_OB) {
1304                                 if (subtree_has_objects(&te->subtree)) tselem->flag &= ~TSE_CLOSED;
1305                                 else tselem->flag |= TSE_CLOSED;
1306                         }
1307                 }
1308                 else {
1309                         tselem->flag |= TSE_CLOSED;
1310                 }
1311
1312                 if (TSELEM_OPEN(tselem, soops)) {
1313                         tree_element_show_hierarchy(scene, soops, &te->subtree);
1314                 }
1315         }
1316 }
1317
1318 /* show entire object level hierarchy */
1319 static int outliner_show_hierarchy_exec(bContext *C, wmOperator *UNUSED(op))
1320 {
1321         SpaceOops *soops = CTX_wm_space_outliner(C);
1322         ARegion *ar = CTX_wm_region(C);
1323         Scene *scene = CTX_data_scene(C);
1324
1325         /* recursively open/close levels */
1326         tree_element_show_hierarchy(scene, soops, &soops->tree);
1327
1328         ED_region_tag_redraw(ar);
1329
1330         return OPERATOR_FINISHED;
1331 }
1332
1333 void OUTLINER_OT_show_hierarchy(wmOperatorType *ot)
1334 {
1335         /* identifiers */
1336         ot->name = "Show Hierarchy";
1337         ot->idname = "OUTLINER_OT_show_hierarchy";
1338         ot->description = "Open all object entries and close all others";
1339
1340         /* callbacks */
1341         ot->exec = outliner_show_hierarchy_exec;
1342         ot->poll = ED_operator_outliner_active; //  TODO: shouldn't be allowed in RNA views...
1343
1344         /* no undo or registry, UI option */
1345 }
1346
1347 /* ************************************************************** */
1348 /* ANIMATO OPERATIONS */
1349 /* KeyingSet and Driver Creation - Helper functions */
1350
1351 /* specialized poll callback for these operators to work in Datablocks view only */
1352 static bool ed_operator_outliner_datablocks_active(bContext *C)
1353 {
1354         ScrArea *sa = CTX_wm_area(C);
1355         if ((sa) && (sa->spacetype == SPACE_OUTLINER)) {
1356                 SpaceOops *so = CTX_wm_space_outliner(C);
1357                 return (so->outlinevis == SO_DATA_API);
1358         }
1359         return 0;
1360 }
1361
1362
1363 /* Helper func to extract an RNA path from selected tree element
1364  * NOTE: the caller must zero-out all values of the pointers that it passes here first, as
1365  * this function does not do that yet
1366  */
1367 static void tree_element_to_path(TreeElement *te, TreeStoreElem *tselem,
1368                                  ID **id, char **path, int *array_index, short *flag, short *UNUSED(groupmode))
1369 {
1370         ListBase hierarchy = {NULL, NULL};
1371         LinkData *ld;
1372         TreeElement *tem, *temnext, *temsub;
1373         TreeStoreElem *tse /* , *tsenext */ /* UNUSED */;
1374         PointerRNA *ptr, *nextptr;
1375         PropertyRNA *prop;
1376         char *newpath = NULL;
1377
1378         /* optimize tricks:
1379          * - Don't do anything if the selected item is a 'struct', but arrays are allowed
1380          */
1381         if (tselem->type == TSE_RNA_STRUCT)
1382                 return;
1383
1384         /* Overview of Algorithm:
1385          * 1. Go up the chain of parents until we find the 'root', taking note of the
1386          *    levels encountered in reverse-order (i.e. items are added to the start of the list
1387          *    for more convenient looping later)
1388          * 2. Walk down the chain, adding from the first ID encountered
1389          *    (which will become the 'ID' for the KeyingSet Path), and build a
1390          *    path as we step through the chain
1391          */
1392
1393         /* step 1: flatten out hierarchy of parents into a flat chain */
1394         for (tem = te->parent; tem; tem = tem->parent) {
1395                 ld = MEM_callocN(sizeof(LinkData), "LinkData for tree_element_to_path()");
1396                 ld->data = tem;
1397                 BLI_addhead(&hierarchy, ld);
1398         }
1399
1400         /* step 2: step down hierarchy building the path
1401          * (NOTE: addhead in previous loop was needed so that we can loop like this) */
1402         for (ld = hierarchy.first; ld; ld = ld->next) {
1403                 /* get data */
1404                 tem = (TreeElement *)ld->data;
1405                 tse = TREESTORE(tem);
1406                 ptr = &tem->rnaptr;
1407                 prop = tem->directdata;
1408
1409                 /* check if we're looking for first ID, or appending to path */
1410                 if (*id) {
1411                         /* just 'append' property to path
1412                          * - to prevent memory leaks, we must write to newpath not path, then free old path + swap them
1413                          */
1414                         if (tse->type == TSE_RNA_PROPERTY) {
1415                                 if (RNA_property_type(prop) == PROP_POINTER) {
1416                                         /* for pointer we just append property name */
1417                                         newpath = RNA_path_append(*path, ptr, prop, 0, NULL);
1418                                 }
1419                                 else if (RNA_property_type(prop) == PROP_COLLECTION) {
1420                                         char buf[128], *name;
1421
1422                                         temnext = (TreeElement *)(ld->next->data);
1423                                         /* tsenext = TREESTORE(temnext); */ /* UNUSED */
1424
1425                                         nextptr = &temnext->rnaptr;
1426                                         name = RNA_struct_name_get_alloc(nextptr, buf, sizeof(buf), NULL);
1427
1428                                         if (name) {
1429                                                 /* if possible, use name as a key in the path */
1430                                                 newpath = RNA_path_append(*path, NULL, prop, 0, name);
1431
1432                                                 if (name != buf)
1433                                                         MEM_freeN(name);
1434                                         }
1435                                         else {
1436                                                 /* otherwise use index */
1437                                                 int index = 0;
1438
1439                                                 for (temsub = tem->subtree.first; temsub; temsub = temsub->next, index++)
1440                                                         if (temsub == temnext)
1441                                                                 break;
1442
1443                                                 newpath = RNA_path_append(*path, NULL, prop, index, NULL);
1444                                         }
1445
1446                                         ld = ld->next;
1447                                 }
1448                         }
1449
1450                         if (newpath) {
1451                                 if (*path) MEM_freeN(*path);
1452                                 *path = newpath;
1453                                 newpath = NULL;
1454                         }
1455                 }
1456                 else {
1457                         /* no ID, so check if entry is RNA-struct, and if that RNA-struct is an ID datablock to extract info from */
1458                         if (tse->type == TSE_RNA_STRUCT) {
1459                                 /* ptr->data not ptr->id.data seems to be the one we want,
1460                                  * since ptr->data is sometimes the owner of this ID? */
1461                                 if (RNA_struct_is_ID(ptr->type)) {
1462                                         *id = (ID *)ptr->data;
1463
1464                                         /* clear path */
1465                                         if (*path) {
1466                                                 MEM_freeN(*path);
1467                                                 path = NULL;
1468                                         }
1469                                 }
1470                         }
1471                 }
1472         }
1473
1474         /* step 3: if we've got an ID, add the current item to the path */
1475         if (*id) {
1476                 /* add the active property to the path */
1477                 ptr = &te->rnaptr;
1478                 prop = te->directdata;
1479
1480                 /* array checks */
1481                 if (tselem->type == TSE_RNA_ARRAY_ELEM) {
1482                         /* item is part of an array, so must set the array_index */
1483                         *array_index = te->index;
1484                 }
1485                 else if (RNA_property_array_check(prop)) {
1486                         /* entire array was selected, so keyframe all */
1487                         *flag |= KSP_FLAG_WHOLE_ARRAY;
1488                 }
1489
1490                 /* path */
1491                 newpath = RNA_path_append(*path, NULL, prop, 0, NULL);
1492                 if (*path) MEM_freeN(*path);
1493                 *path = newpath;
1494         }
1495
1496         /* free temp data */
1497         BLI_freelistN(&hierarchy);
1498 }
1499
1500 /* =============================================== */
1501 /* Driver Operations */
1502
1503 /* These operators are only available in databrowser mode for now, as
1504  * they depend on having RNA paths and/or hierarchies available.
1505  */
1506 enum {
1507         DRIVERS_EDITMODE_ADD    = 0,
1508         DRIVERS_EDITMODE_REMOVE,
1509 } /*eDrivers_EditModes*/;
1510
1511 /* Utilities ---------------------------------- */
1512
1513 /* Recursively iterate over tree, finding and working on selected items */
1514 static void do_outliner_drivers_editop(SpaceOops *soops, ListBase *tree, ReportList *reports, short mode)
1515 {
1516         TreeElement *te;
1517         TreeStoreElem *tselem;
1518
1519         for (te = tree->first; te; te = te->next) {
1520                 tselem = TREESTORE(te);
1521
1522                 /* if item is selected, perform operation */
1523                 if (tselem->flag & TSE_SELECTED) {
1524                         ID *id = NULL;
1525                         char *path = NULL;
1526                         int array_index = 0;
1527                         short flag = 0;
1528                         short groupmode = KSP_GROUP_KSNAME;
1529
1530                         /* check if RNA-property described by this selected element is an animatable prop */
1531                         if (ELEM(tselem->type, TSE_RNA_PROPERTY, TSE_RNA_ARRAY_ELEM) &&
1532                             RNA_property_animateable(&te->rnaptr, te->directdata))
1533                         {
1534                                 /* get id + path + index info from the selected element */
1535                                 tree_element_to_path(te, tselem,
1536                                                      &id, &path, &array_index, &flag, &groupmode);
1537                         }
1538
1539                         /* only if ID and path were set, should we perform any actions */
1540                         if (id && path) {
1541                                 short dflags = CREATEDRIVER_WITH_DEFAULT_DVAR;
1542                                 int arraylen = 1;
1543
1544                                 /* array checks */
1545                                 if (flag & KSP_FLAG_WHOLE_ARRAY) {
1546                                         /* entire array was selected, so add drivers for all */
1547                                         arraylen = RNA_property_array_length(&te->rnaptr, te->directdata);
1548                                 }
1549                                 else
1550                                         arraylen = array_index;
1551
1552                                 /* we should do at least one step */
1553                                 if (arraylen == array_index)
1554                                         arraylen++;
1555
1556                                 /* for each array element we should affect, add driver */
1557                                 for (; array_index < arraylen; array_index++) {
1558                                         /* action depends on mode */
1559                                         switch (mode) {
1560                                                 case DRIVERS_EDITMODE_ADD:
1561                                                 {
1562                                                         /* add a new driver with the information obtained (only if valid) */
1563                                                         ANIM_add_driver(reports, id, path, array_index, dflags, DRIVER_TYPE_PYTHON);
1564                                                         break;
1565                                                 }
1566                                                 case DRIVERS_EDITMODE_REMOVE:
1567                                                 {
1568                                                         /* remove driver matching the information obtained (only if valid) */
1569                                                         ANIM_remove_driver(reports, id, path, array_index, dflags);
1570                                                         break;
1571                                                 }
1572                                         }
1573                                 }
1574
1575                                 /* free path, since it had to be generated */
1576                                 MEM_freeN(path);
1577                         }
1578
1579
1580                 }
1581
1582                 /* go over sub-tree */
1583                 if (TSELEM_OPEN(tselem, soops))
1584                         do_outliner_drivers_editop(soops, &te->subtree, reports, mode);
1585         }
1586 }
1587
1588 /* Add Operator ---------------------------------- */
1589
1590 static int outliner_drivers_addsel_exec(bContext *C, wmOperator *op)
1591 {
1592         SpaceOops *soutliner = CTX_wm_space_outliner(C);
1593
1594         /* check for invalid states */
1595         if (soutliner == NULL)
1596                 return OPERATOR_CANCELLED;
1597
1598         /* recursively go into tree, adding selected items */
1599         do_outliner_drivers_editop(soutliner, &soutliner->tree, op->reports, DRIVERS_EDITMODE_ADD);
1600
1601         /* send notifiers */
1602         WM_event_add_notifier(C, NC_ANIMATION | ND_FCURVES_ORDER, NULL); // XXX
1603
1604         return OPERATOR_FINISHED;
1605 }
1606
1607 void OUTLINER_OT_drivers_add_selected(wmOperatorType *ot)
1608 {
1609         /* api callbacks */
1610         ot->idname = "OUTLINER_OT_drivers_add_selected";
1611         ot->name = "Add Drivers for Selected";
1612         ot->description = "Add drivers to selected items";
1613
1614         /* api callbacks */
1615         ot->exec = outliner_drivers_addsel_exec;
1616         ot->poll = ed_operator_outliner_datablocks_active;
1617
1618         /* flags */
1619         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1620 }
1621
1622
1623 /* Remove Operator ---------------------------------- */
1624
1625 static int outliner_drivers_deletesel_exec(bContext *C, wmOperator *op)
1626 {
1627         SpaceOops *soutliner = CTX_wm_space_outliner(C);
1628
1629         /* check for invalid states */
1630         if (soutliner == NULL)
1631                 return OPERATOR_CANCELLED;
1632
1633         /* recursively go into tree, adding selected items */
1634         do_outliner_drivers_editop(soutliner, &soutliner->tree, op->reports, DRIVERS_EDITMODE_REMOVE);
1635
1636         /* send notifiers */
1637         WM_event_add_notifier(C, ND_KEYS, NULL); // XXX
1638
1639         return OPERATOR_FINISHED;
1640 }
1641
1642 void OUTLINER_OT_drivers_delete_selected(wmOperatorType *ot)
1643 {
1644         /* identifiers */
1645         ot->idname = "OUTLINER_OT_drivers_delete_selected";
1646         ot->name = "Delete Drivers for Selected";
1647         ot->description = "Delete drivers assigned to selected items";
1648
1649         /* api callbacks */
1650         ot->exec = outliner_drivers_deletesel_exec;
1651         ot->poll = ed_operator_outliner_datablocks_active;
1652
1653         /* flags */
1654         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1655 }
1656
1657 /* =============================================== */
1658 /* Keying Set Operations */
1659
1660 /* These operators are only available in databrowser mode for now, as
1661  * they depend on having RNA paths and/or hierarchies available.
1662  */
1663 enum {
1664         KEYINGSET_EDITMODE_ADD  = 0,
1665         KEYINGSET_EDITMODE_REMOVE,
1666 } /*eKeyingSet_EditModes*/;
1667
1668 /* Utilities ---------------------------------- */
1669
1670 /* find the 'active' KeyingSet, and add if not found (if adding is allowed) */
1671 // TODO: should this be an API func?
1672 static KeyingSet *verify_active_keyingset(Scene *scene, short add)
1673 {
1674         KeyingSet *ks = NULL;
1675
1676         /* sanity check */
1677         if (scene == NULL)
1678                 return NULL;
1679
1680         /* try to find one from scene */
1681         if (scene->active_keyingset > 0)
1682                 ks = BLI_findlink(&scene->keyingsets, scene->active_keyingset - 1);
1683
1684         /* add if none found */
1685         // XXX the default settings have yet to evolve
1686         if ((add) && (ks == NULL)) {
1687                 ks = BKE_keyingset_add(&scene->keyingsets, NULL, NULL, KEYINGSET_ABSOLUTE, 0);
1688                 scene->active_keyingset = BLI_listbase_count(&scene->keyingsets);
1689         }
1690
1691         return ks;
1692 }
1693
1694 /* Recursively iterate over tree, finding and working on selected items */
1695 static void do_outliner_keyingset_editop(SpaceOops *soops, KeyingSet *ks, ListBase *tree, short mode)
1696 {
1697         TreeElement *te;
1698         TreeStoreElem *tselem;
1699
1700         for (te = tree->first; te; te = te->next) {
1701                 tselem = TREESTORE(te);
1702
1703                 /* if item is selected, perform operation */
1704                 if (tselem->flag & TSE_SELECTED) {
1705                         ID *id = NULL;
1706                         char *path = NULL;
1707                         int array_index = 0;
1708                         short flag = 0;
1709                         short groupmode = KSP_GROUP_KSNAME;
1710
1711                         /* check if RNA-property described by this selected element is an animatable prop */
1712                         if (ELEM(tselem->type, TSE_RNA_PROPERTY, TSE_RNA_ARRAY_ELEM) &&
1713                             RNA_property_animateable(&te->rnaptr, te->directdata))
1714                         {
1715                                 /* get id + path + index info from the selected element */
1716                                 tree_element_to_path(te, tselem,
1717                                                      &id, &path, &array_index, &flag, &groupmode);
1718                         }
1719
1720                         /* only if ID and path were set, should we perform any actions */
1721                         if (id && path) {
1722                                 /* action depends on mode */
1723                                 switch (mode) {
1724                                         case KEYINGSET_EDITMODE_ADD:
1725                                         {
1726                                                 /* add a new path with the information obtained (only if valid) */
1727                                                 /* TODO: what do we do with group name?
1728                                                  * for now, we don't supply one, and just let this use the KeyingSet name */
1729                                                 BKE_keyingset_add_path(ks, id, NULL, path, array_index, flag, groupmode);
1730                                                 ks->active_path = BLI_listbase_count(&ks->paths);
1731                                                 break;
1732                                         }
1733                                         case KEYINGSET_EDITMODE_REMOVE:
1734                                         {
1735                                                 /* find the relevant path, then remove it from the KeyingSet */
1736                                                 KS_Path *ksp = BKE_keyingset_find_path(ks, id, NULL, path, array_index, groupmode);
1737
1738                                                 if (ksp) {
1739                                                         /* free path's data */
1740                                                         BKE_keyingset_free_path(ks, ksp);
1741
1742                                                         ks->active_path = 0;
1743                                                 }
1744                                                 break;
1745                                         }
1746                                 }
1747
1748                                 /* free path, since it had to be generated */
1749                                 MEM_freeN(path);
1750                         }
1751                 }
1752
1753                 /* go over sub-tree */
1754                 if (TSELEM_OPEN(tselem, soops))
1755                         do_outliner_keyingset_editop(soops, ks, &te->subtree, mode);
1756         }
1757 }
1758
1759 /* Add Operator ---------------------------------- */
1760
1761 static int outliner_keyingset_additems_exec(bContext *C, wmOperator *op)
1762 {
1763         SpaceOops *soutliner = CTX_wm_space_outliner(C);
1764         Scene *scene = CTX_data_scene(C);
1765         KeyingSet *ks = verify_active_keyingset(scene, 1);
1766
1767         /* check for invalid states */
1768         if (ks == NULL) {
1769                 BKE_report(op->reports, RPT_ERROR, "Operation requires an active keying set");
1770                 return OPERATOR_CANCELLED;
1771         }
1772         if (soutliner == NULL)
1773                 return OPERATOR_CANCELLED;
1774
1775         /* recursively go into tree, adding selected items */
1776         do_outliner_keyingset_editop(soutliner, ks, &soutliner->tree, KEYINGSET_EDITMODE_ADD);
1777
1778         /* send notifiers */
1779         WM_event_add_notifier(C, NC_SCENE | ND_KEYINGSET, NULL);
1780
1781         return OPERATOR_FINISHED;
1782 }
1783
1784 void OUTLINER_OT_keyingset_add_selected(wmOperatorType *ot)
1785 {
1786         /* identifiers */
1787         ot->idname = "OUTLINER_OT_keyingset_add_selected";
1788         ot->name = "Keying Set Add Selected";
1789         ot->description = "Add selected items (blue-gray rows) to active Keying Set";
1790
1791         /* api callbacks */
1792         ot->exec = outliner_keyingset_additems_exec;
1793         ot->poll = ed_operator_outliner_datablocks_active;
1794
1795         /* flags */
1796         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1797 }
1798
1799
1800 /* Remove Operator ---------------------------------- */
1801
1802 static int outliner_keyingset_removeitems_exec(bContext *C, wmOperator *UNUSED(op))
1803 {
1804         SpaceOops *soutliner = CTX_wm_space_outliner(C);
1805         Scene *scene = CTX_data_scene(C);
1806         KeyingSet *ks = verify_active_keyingset(scene, 1);
1807
1808         /* check for invalid states */
1809         if (soutliner == NULL)
1810                 return OPERATOR_CANCELLED;
1811
1812         /* recursively go into tree, adding selected items */
1813         do_outliner_keyingset_editop(soutliner, ks, &soutliner->tree, KEYINGSET_EDITMODE_REMOVE);
1814
1815         /* send notifiers */
1816         WM_event_add_notifier(C, NC_SCENE | ND_KEYINGSET, NULL);
1817
1818         return OPERATOR_FINISHED;
1819 }
1820
1821 void OUTLINER_OT_keyingset_remove_selected(wmOperatorType *ot)
1822 {
1823         /* identifiers */
1824         ot->idname = "OUTLINER_OT_keyingset_remove_selected";
1825         ot->name = "Keying Set Remove Selected";
1826         ot->description = "Remove selected items (blue-gray rows) from active Keying Set";
1827
1828         /* api callbacks */
1829         ot->exec = outliner_keyingset_removeitems_exec;
1830         ot->poll = ed_operator_outliner_datablocks_active;
1831
1832         /* flags */
1833         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1834 }
1835
1836
1837 /* ************************************************************** */
1838 /* ORPHANED DATABLOCKS */
1839
1840 static bool ed_operator_outliner_id_orphans_active(bContext *C)
1841 {
1842         ScrArea *sa = CTX_wm_area(C);
1843         if ((sa) && (sa->spacetype == SPACE_OUTLINER)) {
1844                 SpaceOops *so = CTX_wm_space_outliner(C);
1845                 return (so->outlinevis == SO_ID_ORPHANS);
1846         }
1847         return 0;
1848 }
1849
1850 /* Purge Orphans Operator --------------------------------------- */
1851
1852 static void outliner_orphans_purge_tag(ID *id, int *num_tagged)
1853 {
1854         if (id->us == 0) {
1855                 id->tag |= LIB_TAG_DOIT;
1856                 num_tagged[INDEX_ID_NULL]++;
1857                 num_tagged[BKE_idcode_to_index(GS(id->name))]++;
1858         }
1859         else {
1860                 id->tag &= ~LIB_TAG_DOIT;
1861         }
1862 }
1863
1864 static int outliner_orphans_purge_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(evt))
1865 {
1866         Main *bmain = CTX_data_main(C);
1867         int num_tagged[INDEX_ID_MAX] = {0};
1868
1869         /* Tag all IDs having zero users. */
1870         ID *id;
1871         FOREACH_MAIN_ID_BEGIN(bmain, id)
1872         {
1873                 outliner_orphans_purge_tag(id, num_tagged);
1874         }
1875         FOREACH_MAIN_ID_END;
1876         RNA_int_set(op->ptr, "num_deleted", num_tagged[INDEX_ID_NULL]);
1877
1878         if (num_tagged[INDEX_ID_NULL] == 0) {
1879                 BKE_report(op->reports, RPT_INFO, "No orphanned data-blocks to purge");
1880                 return OPERATOR_CANCELLED;
1881         }
1882
1883         DynStr *dyn_str = BLI_dynstr_new();
1884         BLI_dynstr_append(dyn_str, "Purging unused data-blocks (");
1885         bool is_first = true;
1886         for (int i = 0; i < INDEX_ID_MAX - 2; i++) {
1887                 if (num_tagged[i] != 0) {
1888                         if (!is_first) {
1889                                 BLI_dynstr_append(dyn_str, ", ");
1890                         }
1891                         else {
1892                                 is_first = false;
1893                         }
1894                         BLI_dynstr_appendf(
1895                                     dyn_str, "%d %s",
1896                                     num_tagged[i], TIP_(BKE_idcode_to_name_plural(BKE_idcode_from_index(i))));
1897                 }
1898         }
1899         BLI_dynstr_append(dyn_str, TIP_("). Click here to proceed..."));
1900
1901         char *message = BLI_dynstr_get_cstring(dyn_str);
1902         int ret = WM_operator_confirm_message(C, op, message);
1903
1904         MEM_freeN(message);
1905         BLI_dynstr_free(dyn_str);
1906         return ret;
1907 }
1908
1909 static int outliner_orphans_purge_exec(bContext *C, wmOperator *op)
1910 {
1911         Main *bmain = CTX_data_main(C);
1912         SpaceOops *soops = CTX_wm_space_outliner(C);
1913         int num_tagged[INDEX_ID_MAX] = {0};
1914
1915         if ((num_tagged[INDEX_ID_NULL] = RNA_int_get(op->ptr, "num_deleted")) == 0) {
1916                 /* Tag all IDs having zero users. */
1917                 ID *id;
1918                 FOREACH_MAIN_ID_BEGIN(bmain, id)
1919                 {
1920                         outliner_orphans_purge_tag(id, num_tagged);
1921                 }
1922                 FOREACH_MAIN_ID_END;
1923
1924                 if (num_tagged[INDEX_ID_NULL] == 0) {
1925                         BKE_report(op->reports, RPT_INFO, "No orphanned data-blocks to purge");
1926                         return OPERATOR_CANCELLED;
1927                 }
1928         }
1929
1930         BKE_id_multi_tagged_delete(bmain);
1931
1932         BKE_reportf(op->reports, RPT_INFO, "Deleted %d data-blocks", num_tagged[INDEX_ID_NULL]);
1933
1934         /* XXX: tree management normally happens from draw_outliner(), but when
1935          *      you're clicking to fast on Delete object from context menu in
1936          *      outliner several mouse events can be handled in one cycle without
1937          *      handling notifiers/redraw which leads to deleting the same object twice.
1938          *      cleanup tree here to prevent such cases. */
1939         outliner_cleanup_tree(soops);
1940
1941         DEG_relations_tag_update(bmain);
1942         WM_event_add_notifier(C, NC_ID | NA_EDITED, NULL);
1943         WM_event_add_notifier(C, NC_SPACE | ND_SPACE_OUTLINER, NULL);
1944         return OPERATOR_FINISHED;
1945 }
1946
1947 void OUTLINER_OT_orphans_purge(wmOperatorType *ot)
1948 {
1949         /* identifiers */
1950         ot->idname = "OUTLINER_OT_orphans_purge";
1951         ot->name = "Purge All";
1952         ot->description = "Clear all orphaned data-blocks without any users from the file";
1953
1954         /* callbacks */
1955         ot->invoke = outliner_orphans_purge_invoke;
1956         ot->exec = outliner_orphans_purge_exec;
1957         ot->poll = ed_operator_outliner_id_orphans_active;
1958
1959         /* flags */
1960         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1961
1962         /* properties */
1963         PropertyRNA *prop = RNA_def_int(ot->srna, "num_deleted", 0, 0, INT_MAX, "", "", 0, INT_MAX);
1964         RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
1965 }