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