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