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