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