svn merge ^/trunk/blender -r43278:43294
[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 <math.h>
33 #include <string.h>
34 #include <stdlib.h>
35 #include <stddef.h>
36
37 #include "MEM_guardedalloc.h"
38
39 #include "DNA_anim_types.h"
40 #include "DNA_armature_types.h"
41 #include "DNA_constraint_types.h"
42 #include "DNA_camera_types.h"
43 #include "DNA_group_types.h"
44 #include "DNA_key_types.h"
45 #include "DNA_lamp_types.h"
46 #include "DNA_material_types.h"
47 #include "DNA_mesh_types.h"
48 #include "DNA_meta_types.h"
49 #include "DNA_particle_types.h"
50 #include "DNA_scene_types.h"
51 #include "DNA_world_types.h"
52 #include "DNA_sequence_types.h"
53 #include "DNA_object_types.h"
54
55 #include "BLI_blenlib.h"
56 #include "BLI_utildefines.h"
57 #include "BLI_math_base.h"
58
59 #if defined WIN32 && !defined _LIBC
60 # include "BLI_fnmatch.h" /* use fnmatch included in blenlib */
61 #else
62 #  ifndef _GNU_SOURCE
63 #    define _GNU_SOURCE
64 #  endif
65 # include <fnmatch.h>
66 #endif
67
68
69 #include "BKE_animsys.h"
70 #include "BKE_context.h"
71 #include "BKE_deform.h"
72 #include "BKE_depsgraph.h"
73 #include "BKE_fcurve.h"
74 #include "BKE_global.h"
75 #include "BKE_group.h"
76 #include "BKE_library.h"
77 #include "BKE_main.h"
78 #include "BKE_modifier.h"
79 #include "BKE_report.h"
80 #include "BKE_scene.h"
81 #include "BKE_sequencer.h"
82
83 #include "ED_armature.h"
84 #include "ED_object.h"
85 #include "ED_screen.h"
86 #include "ED_util.h"
87
88 #include "WM_api.h"
89 #include "WM_types.h"
90
91 #include "BIF_gl.h"
92 #include "BIF_glutil.h"
93
94 #include "UI_interface.h"
95 #include "UI_interface_icons.h"
96 #include "UI_resources.h"
97 #include "UI_view2d.h"
98
99 #include "RNA_access.h"
100 #include "RNA_define.h"
101 #include "RNA_enum_types.h"
102
103 #include "ED_keyframing.h"
104
105 #include "outliner_intern.h"
106
107 /* ************************************************************** */
108 /* Unused Utilities */
109 // XXX: where to place these?
110
111 /* This is not used anywhere at the moment */
112 #if 0
113 /* return 1 when levels were opened */
114 static int outliner_open_back(SpaceOops *soops, TreeElement *te)
115 {
116         TreeStoreElem *tselem;
117         int retval= 0;
118         
119         for (te= te->parent; te; te= te->parent) {
120                 tselem= TREESTORE(te);
121                 if (tselem->flag & TSE_CLOSED) { 
122                         tselem->flag &= ~TSE_CLOSED;
123                         retval= 1;
124                 }
125         }
126         return retval;
127 }
128
129 static void outliner_open_reveal(SpaceOops *soops, ListBase *lb, TreeElement *teFind, int *found)
130 {
131         TreeElement *te;
132         TreeStoreElem *tselem;
133         
134         for (te= lb->first; te; te= te->next) {
135                 /* check if this tree-element was the one we're seeking */
136                 if (te == teFind) {
137                         *found= 1;
138                         return;
139                 }
140                 
141                 /* try to see if sub-tree contains it then */
142                 outliner_open_reveal(soops, &te->subtree, teFind, found);
143                 if (*found) {
144                         tselem= TREESTORE(te);
145                         if (tselem->flag & TSE_CLOSED) 
146                                 tselem->flag &= ~TSE_CLOSED;
147                         return;
148                 }
149         }
150 }
151 #endif
152
153 /* ************************************************************** */
154 /* Click Activated */
155
156 /* Toggle Open/Closed ------------------------------------------- */
157
158 static int do_outliner_item_openclose(bContext *C, SpaceOops *soops, TreeElement *te, int all, const float mval[2])
159 {
160         
161         if(mval[1]>te->ys && mval[1]<te->ys+UI_UNIT_Y) {
162                 TreeStoreElem *tselem= TREESTORE(te);
163                 
164                 /* all below close/open? */
165                 if(all) {
166                         tselem->flag &= ~TSE_CLOSED;
167                         outliner_set_flag(soops, &te->subtree, TSE_CLOSED, !outliner_has_one_flag(soops, &te->subtree, TSE_CLOSED, 1));
168                 }
169                 else {
170                         if(tselem->flag & TSE_CLOSED) tselem->flag &= ~TSE_CLOSED;
171                         else tselem->flag |= TSE_CLOSED;
172                 }
173                 
174                 return 1;
175         }
176         
177         for(te= te->subtree.first; te; te= te->next) {
178                 if(do_outliner_item_openclose(C, soops, te, all, mval)) 
179                         return 1;
180         }
181         return 0;
182         
183 }
184
185 /* event can enterkey, then it opens/closes */
186 static int outliner_item_openclose(bContext *C, wmOperator *op, wmEvent *event)
187 {
188         ARegion *ar= CTX_wm_region(C);
189         SpaceOops *soops= CTX_wm_space_outliner(C);
190         TreeElement *te;
191         float fmval[2];
192         int all= RNA_boolean_get(op->ptr, "all");
193         
194         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], fmval, fmval+1);
195         
196         for(te= soops->tree.first; te; te= te->next) {
197                 if(do_outliner_item_openclose(C, soops, te, all, fmval)) 
198                         break;
199         }
200
201         ED_region_tag_redraw(ar);
202         
203         return OPERATOR_FINISHED;
204 }
205
206 void OUTLINER_OT_item_openclose(wmOperatorType *ot)
207 {
208         ot->name= "Open/Close Item";
209         ot->idname= "OUTLINER_OT_item_openclose";
210         ot->description= "Toggle whether item under cursor is enabled or closed";
211         
212         ot->invoke= outliner_item_openclose;
213         
214         ot->poll= ED_operator_outliner_active;
215         
216         RNA_def_boolean(ot->srna, "all", 1, "All", "Close or open all items");
217 }
218
219 /* Rename --------------------------------------------------- */
220
221 static void do_item_rename(ARegion *ar, TreeElement *te, TreeStoreElem *tselem, ReportList *reports)
222 {
223         /* can't rename rna datablocks entries */
224         if(ELEM3(tselem->type, TSE_RNA_STRUCT, TSE_RNA_PROPERTY, TSE_RNA_ARRAY_ELEM)) {
225                         /* do nothing */;
226         }
227         else if(ELEM10(tselem->type, TSE_ANIM_DATA, TSE_NLA, TSE_DEFGROUP_BASE, TSE_CONSTRAINT_BASE, TSE_MODIFIER_BASE,
228                                      TSE_SCRIPT_BASE, TSE_POSE_BASE, TSE_POSEGRP_BASE, TSE_R_LAYER_BASE, TSE_R_PASS))
229         {
230                         BKE_report(reports, RPT_WARNING, "Cannot edit builtin name");
231         }
232         else if(ELEM3(tselem->type, TSE_SEQUENCE, TSE_SEQ_STRIP, TSE_SEQUENCE_DUP)) {
233                 BKE_report(reports, RPT_WARNING, "Cannot edit sequence name");
234         }
235         else if(tselem->id->lib) {
236                 // XXX                                          error_libdata();
237         } 
238         else if(te->idcode == ID_LI && te->parent) {
239                 BKE_report(reports, RPT_WARNING, "Cannot edit the path of an indirectly linked library");
240         } 
241         else {
242                 tselem->flag |= TSE_TEXTBUT;
243                 ED_region_tag_redraw(ar);
244         }
245 }
246
247 void item_rename_cb(bContext *C, Scene *UNUSED(scene), TreeElement *te, TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem)
248 {
249         ARegion *ar= CTX_wm_region(C);
250         ReportList *reports= CTX_wm_reports(C); // XXX
251         do_item_rename(ar, te, tselem, reports) ;
252 }
253
254 static int do_outliner_item_rename(bContext *C, ARegion *ar, SpaceOops *soops, TreeElement *te, const float mval[2])
255 {       
256         ReportList *reports= CTX_wm_reports(C); // XXX
257         
258         if(mval[1]>te->ys && mval[1]<te->ys+UI_UNIT_Y) {
259                 TreeStoreElem *tselem= TREESTORE(te);
260                 
261                 /* name and first icon */
262                 if(mval[0]>te->xs+UI_UNIT_X && mval[0]<te->xend) {
263                         
264                         do_item_rename(ar, te, tselem, reports) ;
265                 }
266                 return 1;
267         }
268         
269         for(te= te->subtree.first; te; te= te->next) {
270                 if(do_outliner_item_rename(C, ar, soops, te, mval)) return 1;
271         }
272         return 0;
273 }
274
275 static int outliner_item_rename(bContext *C, wmOperator *UNUSED(op), wmEvent *event)
276 {
277         ARegion *ar= CTX_wm_region(C);
278         SpaceOops *soops= CTX_wm_space_outliner(C);
279         TreeElement *te;
280         float fmval[2];
281         
282         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], fmval, fmval+1);
283         
284         for(te= soops->tree.first; te; te= te->next) {
285                 if(do_outliner_item_rename(C, ar, soops, te, fmval)) break;
286         }
287         
288         return OPERATOR_FINISHED;
289 }
290
291
292 void OUTLINER_OT_item_rename(wmOperatorType *ot)
293 {
294         ot->name= "Rename Item";
295         ot->idname= "OUTLINER_OT_item_rename";
296         ot->description= "Rename item under cursor";
297         
298         ot->invoke= outliner_item_rename;
299         
300         ot->poll= ED_operator_outliner_active;
301 }
302
303 /* ************************************************************** */
304 /* Setting Toggling Operators */
305
306 /* =============================================== */
307 /* Toggling Utilities (Exported) */
308
309 /* Apply Settings ------------------------------- */
310
311 static int outliner_count_levels(SpaceOops *soops, ListBase *lb, int curlevel)
312 {
313         TreeElement *te;
314         int level=curlevel, lev;
315         
316         for(te= lb->first; te; te= te->next) {
317                 
318                 lev= outliner_count_levels(soops, &te->subtree, curlevel+1);
319                 if(lev>level) level= lev;
320         }
321         return level;
322 }
323
324 int outliner_has_one_flag(SpaceOops *soops, ListBase *lb, short flag, short curlevel)
325 {
326         TreeElement *te;
327         TreeStoreElem *tselem;
328         int level;
329         
330         for(te= lb->first; te; te= te->next) {
331                 tselem= TREESTORE(te);
332                 if(tselem->flag & flag) return curlevel;
333                 
334                 level= outliner_has_one_flag(soops, &te->subtree, flag, curlevel+1);
335                 if(level) return level;
336         }
337         return 0;
338 }
339
340 void outliner_set_flag(SpaceOops *soops, ListBase *lb, short flag, short set)
341 {
342         TreeElement *te;
343         TreeStoreElem *tselem;
344         
345         for(te= lb->first; te; te= te->next) {
346                 tselem= TREESTORE(te);
347                 if(set==0) tselem->flag &= ~flag;
348                 else tselem->flag |= flag;
349                 outliner_set_flag(soops, &te->subtree, flag, set);
350         }
351 }
352
353 /* Restriction Columns ------------------------------- */
354
355 /* same check needed for both object operation and restrict column button func
356  * return 0 when in edit mode (cannot restrict view or select)
357  * otherwise return 1 */
358 int common_restrict_check(bContext *C, Object *ob)
359 {
360         /* Don't allow hide an object in edit mode,
361          * check the bug #22153 and #21609, #23977
362          */
363         Object *obedit= CTX_data_edit_object(C);
364         if (obedit && obedit == ob) {
365                 /* found object is hidden, reset */
366                 if (ob->restrictflag & OB_RESTRICT_VIEW)
367                         ob->restrictflag &= ~OB_RESTRICT_VIEW;
368                 /* found object is unselectable, reset */
369                 if (ob->restrictflag & OB_RESTRICT_SELECT)
370                         ob->restrictflag &= ~OB_RESTRICT_SELECT;
371                 return 0;
372         }
373         
374         return 1;
375 }
376
377 /* =============================================== */
378 /* Restriction toggles */
379
380 /* Toggle Visibility ---------------------------------------- */
381
382 void object_toggle_visibility_cb(bContext *C, Scene *scene, TreeElement *te, TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem)
383 {
384         Base *base= (Base *)te->directdata;
385         Object *ob = (Object *)tselem->id;
386         
387         /* add check for edit mode */
388         if(!common_restrict_check(C, ob)) return;
389         
390         if(base || (base= object_in_scene(ob, scene))) {
391                 if((base->object->restrictflag ^= OB_RESTRICT_VIEW)) {
392                         ED_base_object_select(base, BA_DESELECT);
393                 }
394         }
395 }
396
397 void group_toggle_visibility_cb(bContext *UNUSED(C), Scene *scene, TreeElement *UNUSED(te), TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem)
398 {
399         Group *group= (Group *)tselem->id;
400         restrictbutton_gr_restrict_flag(scene, group, OB_RESTRICT_VIEW);
401 }
402
403 static int outliner_toggle_visibility_exec(bContext *C, wmOperator *UNUSED(op))
404 {
405         SpaceOops *soops= CTX_wm_space_outliner(C);
406         Scene *scene= CTX_data_scene(C);
407         ARegion *ar= CTX_wm_region(C);
408         
409         outliner_do_object_operation(C, scene, soops, &soops->tree, object_toggle_visibility_cb);
410         
411         WM_event_add_notifier(C, NC_SCENE|ND_OB_VISIBLE, scene);
412         ED_region_tag_redraw(ar);
413         
414         return OPERATOR_FINISHED;
415 }
416
417 void OUTLINER_OT_visibility_toggle(wmOperatorType *ot)
418 {
419         /* identifiers */
420         ot->name= "Toggle Visibility";
421         ot->idname= "OUTLINER_OT_visibility_toggle";
422         ot->description= "Toggle the visibility of selected items";
423         
424         /* callbacks */
425         ot->exec= outliner_toggle_visibility_exec;
426         ot->poll= ED_operator_outliner_active_no_editobject;
427         
428         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
429 }
430
431 /* Toggle Selectability ---------------------------------------- */
432
433 void object_toggle_selectability_cb(bContext *UNUSED(C), Scene *scene, TreeElement *te, TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem)
434 {
435         Base *base= (Base *)te->directdata;
436         
437         if(base==NULL) base= object_in_scene((Object *)tselem->id, scene);
438         if(base) {
439                 base->object->restrictflag^=OB_RESTRICT_SELECT;
440         }
441 }
442
443 void group_toggle_selectability_cb(bContext *UNUSED(C), Scene *scene, TreeElement *UNUSED(te), TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem)
444 {
445         Group *group= (Group *)tselem->id;
446         restrictbutton_gr_restrict_flag(scene, group, OB_RESTRICT_SELECT);
447 }
448
449 static int outliner_toggle_selectability_exec(bContext *C, wmOperator *UNUSED(op))
450 {
451         SpaceOops *soops= CTX_wm_space_outliner(C);
452         Scene *scene= CTX_data_scene(C);
453         ARegion *ar= CTX_wm_region(C);
454         
455         outliner_do_object_operation(C, scene, soops, &soops->tree, object_toggle_selectability_cb);
456         
457         WM_event_add_notifier(C, NC_SCENE|ND_OB_SELECT, scene);
458         ED_region_tag_redraw(ar);
459         
460         return OPERATOR_FINISHED;
461 }
462
463 void OUTLINER_OT_selectability_toggle(wmOperatorType *ot)
464 {
465         /* identifiers */
466         ot->name= "Toggle Selectability";
467         ot->idname= "OUTLINER_OT_selectability_toggle";
468         ot->description= "Toggle the selectability";
469         
470         /* callbacks */
471         ot->exec= outliner_toggle_selectability_exec;
472         ot->poll= ED_operator_outliner_active_no_editobject;
473         
474         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
475 }
476
477 /* Toggle Renderability ---------------------------------------- */
478
479 void object_toggle_renderability_cb(bContext *UNUSED(C), Scene *scene, TreeElement *te, TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem)
480 {
481         Base *base= (Base *)te->directdata;
482         
483         if(base==NULL) base= object_in_scene((Object *)tselem->id, scene);
484         if(base) {
485                 base->object->restrictflag^=OB_RESTRICT_RENDER;
486         }
487 }
488
489 void group_toggle_renderability_cb(bContext *UNUSED(C), Scene *scene, TreeElement *UNUSED(te), TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem)
490 {
491         Group *group= (Group *)tselem->id;
492         restrictbutton_gr_restrict_flag(scene, group, OB_RESTRICT_RENDER);
493 }
494
495 static int outliner_toggle_renderability_exec(bContext *C, wmOperator *UNUSED(op))
496 {
497         SpaceOops *soops= CTX_wm_space_outliner(C);
498         Scene *scene= CTX_data_scene(C);
499         ARegion *ar= CTX_wm_region(C);
500         
501         outliner_do_object_operation(C, scene, soops, &soops->tree, object_toggle_renderability_cb);
502         
503         ED_region_tag_redraw(ar);
504         
505         return OPERATOR_FINISHED;
506 }
507
508 void OUTLINER_OT_renderability_toggle(wmOperatorType *ot)
509 {
510         /* identifiers */
511         ot->name= "Toggle Renderability";
512         ot->idname= "OUTLINER_OT_renderability_toggle";
513         ot->description= "Toggle the renderability of selected items";
514         
515         /* callbacks */
516         ot->exec= outliner_toggle_renderability_exec;
517         ot->poll= ED_operator_outliner_active;
518         
519         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
520 }
521
522 /* =============================================== */
523 /* Outliner setting toggles */
524
525 /* Toggle Expanded (Outliner) ---------------------------------------- */
526
527 static int outliner_toggle_expanded_exec(bContext *C, wmOperator *UNUSED(op))
528 {
529         SpaceOops *soops= CTX_wm_space_outliner(C);
530         ARegion *ar= CTX_wm_region(C);
531         
532         if (outliner_has_one_flag(soops, &soops->tree, TSE_CLOSED, 1))
533                 outliner_set_flag(soops, &soops->tree, TSE_CLOSED, 0);
534         else 
535                 outliner_set_flag(soops, &soops->tree, TSE_CLOSED, 1);
536         
537         ED_region_tag_redraw(ar);
538         
539         return OPERATOR_FINISHED;
540 }
541
542 void OUTLINER_OT_expanded_toggle(wmOperatorType *ot)
543 {
544         /* identifiers */
545         ot->name= "Expand/Collapse All";
546         ot->idname= "OUTLINER_OT_expanded_toggle";
547         ot->description= "Expand/Collapse all items";
548         
549         /* callbacks */
550         ot->exec= outliner_toggle_expanded_exec;
551         ot->poll= ED_operator_outliner_active;
552         
553         /* no undo or registry, UI option */
554 }
555
556 /* Toggle Selected (Outliner) ---------------------------------------- */
557
558 static int outliner_toggle_selected_exec(bContext *C, wmOperator *UNUSED(op))
559 {
560         SpaceOops *soops= CTX_wm_space_outliner(C);
561         ARegion *ar= CTX_wm_region(C);
562         Scene *scene= CTX_data_scene(C);
563         
564         if (outliner_has_one_flag(soops, &soops->tree, TSE_SELECTED, 1))
565                 outliner_set_flag(soops, &soops->tree, TSE_SELECTED, 0);
566         else 
567                 outliner_set_flag(soops, &soops->tree, TSE_SELECTED, 1);
568         
569         soops->storeflag |= SO_TREESTORE_REDRAW;
570         
571         WM_event_add_notifier(C, NC_SCENE|ND_OB_SELECT, scene);
572         ED_region_tag_redraw(ar);
573         
574         return OPERATOR_FINISHED;
575 }
576
577 void OUTLINER_OT_selected_toggle(wmOperatorType *ot)
578 {
579         /* identifiers */
580         ot->name= "Toggle Selected";
581         ot->idname= "OUTLINER_OT_selected_toggle";
582         ot->description= "Toggle the Outliner selection of items";
583         
584         /* callbacks */
585         ot->exec= outliner_toggle_selected_exec;
586         ot->poll= ED_operator_outliner_active;
587         
588         /* no undo or registry, UI option */
589 }
590
591 /* ************************************************************** */
592 /* Hotkey Only Operators */
593
594 /* Show Active --------------------------------------------------- */
595
596 static int outliner_show_active_exec(bContext *C, wmOperator *UNUSED(op))
597 {
598         SpaceOops *so= CTX_wm_space_outliner(C);
599         Scene *scene= CTX_data_scene(C);
600         ARegion *ar= CTX_wm_region(C);
601         View2D *v2d= &ar->v2d;
602         
603         TreeElement *te;
604         int xdelta, ytop;
605         
606         // TODO: make this get this info from context instead...
607         if (OBACT == NULL) 
608                 return OPERATOR_CANCELLED;
609         
610         te= outliner_find_id(so, &so->tree, (ID *)OBACT);
611         if (te) {
612                 /* make te->ys center of view */
613                 ytop= (int)(te->ys + (v2d->mask.ymax - v2d->mask.ymin)/2);
614                 if (ytop>0) ytop= 0;
615                 
616                 v2d->cur.ymax= (float)ytop;
617                 v2d->cur.ymin= (float)(ytop-(v2d->mask.ymax - v2d->mask.ymin));
618                 
619                 /* make te->xs ==> te->xend center of view */
620                 xdelta = (int)(te->xs - v2d->cur.xmin);
621                 v2d->cur.xmin += xdelta;
622                 v2d->cur.xmax += xdelta;
623                 
624                 so->storeflag |= SO_TREESTORE_REDRAW;
625         }
626         
627         ED_region_tag_redraw(ar);
628         
629         return OPERATOR_FINISHED;
630 }
631
632 void OUTLINER_OT_show_active(wmOperatorType *ot)
633 {
634         /* identifiers */
635         ot->name= "Show Active";
636         ot->idname= "OUTLINER_OT_show_active";
637         ot->description= "Adjust the view so that the active Object is shown centered";
638         
639         /* callbacks */
640         ot->exec= outliner_show_active_exec;
641         ot->poll= ED_operator_outliner_active;
642 }
643
644 /* View Panning --------------------------------------------------- */
645
646 static int outliner_scroll_page_exec(bContext *C, wmOperator *op)
647 {
648         ARegion *ar= CTX_wm_region(C);
649         int dy= ar->v2d.mask.ymax - ar->v2d.mask.ymin;
650         int up= 0;
651         
652         if(RNA_boolean_get(op->ptr, "up"))
653                 up= 1;
654
655         if(up == 0) dy= -dy;
656         ar->v2d.cur.ymin+= dy;
657         ar->v2d.cur.ymax+= dy;
658         
659         ED_region_tag_redraw(ar);
660         
661         return OPERATOR_FINISHED;
662 }
663
664
665 void OUTLINER_OT_scroll_page(wmOperatorType *ot)
666 {
667         /* identifiers */
668         ot->name= "Scroll Page";
669         ot->idname= "OUTLINER_OT_scroll_page";
670         ot->description= "Scroll page up or down";
671         
672         /* callbacks */
673         ot->exec= outliner_scroll_page_exec;
674         ot->poll= ED_operator_outliner_active;
675         
676         /* properties */
677         RNA_def_boolean(ot->srna, "up", 0, "Up", "Scroll up one page");
678 }
679
680 /* Search ------------------------------------------------------- */
681 // TODO: probably obsolete now with filtering?
682
683 #if 0
684
685 /* recursive helper for function below */
686 static void outliner_set_coordinates_element(SpaceOops *soops, TreeElement *te, int startx, int *starty)
687 {
688         TreeStoreElem *tselem= TREESTORE(te);
689         
690         /* store coord and continue, we need coordinates for elements outside view too */
691         te->xs= (float)startx;
692         te->ys= (float)(*starty);
693         *starty-= UI_UNIT_Y;
694         
695         if(TSELEM_OPEN(tselem,soops)) {
696                 TreeElement *ten;
697                 for(ten= te->subtree.first; ten; ten= ten->next) {
698                         outliner_set_coordinates_element(soops, ten, startx+UI_UNIT_X, starty);
699                 }
700         }
701         
702 }
703
704 /* to retrieve coordinates with redrawing the entire tree */
705 static void outliner_set_coordinates(ARegion *ar, SpaceOops *soops)
706 {
707         TreeElement *te;
708         int starty= (int)(ar->v2d.tot.ymax)-UI_UNIT_Y;
709         int startx= 0;
710         
711         for(te= soops->tree.first; te; te= te->next) {
712                 outliner_set_coordinates_element(soops, te, startx, &starty);
713         }
714 }
715
716 /* find next element that has this name */
717 static TreeElement *outliner_find_named(SpaceOops *soops, ListBase *lb, char *name, int flags, TreeElement *prev, int *prevFound)
718 {
719         TreeElement *te, *tes;
720         
721         for (te= lb->first; te; te= te->next) {
722                 int found = outliner_filter_has_name(te, name, flags);
723                 
724                 if(found) {
725                         /* name is right, but is element the previous one? */
726                         if (prev) {
727                                 if ((te != prev) && (*prevFound)) 
728                                         return te;
729                                 if (te == prev) {
730                                         *prevFound = 1;
731                                 }
732                         }
733                         else
734                                 return te;
735                 }
736                 
737                 tes= outliner_find_named(soops, &te->subtree, name, flags, prev, prevFound);
738                 if(tes) return tes;
739         }
740
741         /* nothing valid found */
742         return NULL;
743 }
744
745 static void outliner_find_panel(Scene *UNUSED(scene), ARegion *ar, SpaceOops *soops, int again, int flags) 
746 {
747         ReportList *reports = NULL; // CTX_wm_reports(C);
748         TreeElement *te= NULL;
749         TreeElement *last_find;
750         TreeStoreElem *tselem;
751         int ytop, xdelta, prevFound=0;
752         char name[sizeof(soops->search_string)];
753         
754         /* get last found tree-element based on stored search_tse */
755         last_find= outliner_find_tse(soops, &soops->search_tse);
756         
757         /* determine which type of search to do */
758         if (again && last_find) {
759                 /* no popup panel - previous + user wanted to search for next after previous */         
760                 BLI_strncpy(name, soops->search_string, sizeof(name));
761                 flags= soops->search_flags;
762                 
763                 /* try to find matching element */
764                 te= outliner_find_named(soops, &soops->tree, name, flags, last_find, &prevFound);
765                 if (te==NULL) {
766                         /* no more matches after previous, start from beginning again */
767                         prevFound= 1;
768                         te= outliner_find_named(soops, &soops->tree, name, flags, last_find, &prevFound);
769                 }
770         }
771         else {
772                 /* pop up panel - no previous, or user didn't want search after previous */
773                 name[0]= '\0';
774 // XXX          if (sbutton(name, 0, sizeof(name)-1, "Find: ") && name[0]) {
775 //                      te= outliner_find_named(soops, &soops->tree, name, flags, NULL, &prevFound);
776 //              }
777 //              else return; /* XXX RETURN! XXX */
778         }
779
780         /* do selection and reveal */
781         if (te) {
782                 tselem= TREESTORE(te);
783                 if (tselem) {
784                         /* expand branches so that it will be visible, we need to get correct coordinates */
785                         if( outliner_open_back(soops, te))
786                                 outliner_set_coordinates(ar, soops);
787                         
788                         /* deselect all visible, and select found element */
789                         outliner_set_flag(soops, &soops->tree, TSE_SELECTED, 0);
790                         tselem->flag |= TSE_SELECTED;
791                         
792                         /* make te->ys center of view */
793                         ytop= (int)(te->ys + (ar->v2d.mask.ymax-ar->v2d.mask.ymin)/2);
794                         if(ytop>0) ytop= 0;
795                         ar->v2d.cur.ymax= (float)ytop;
796                         ar->v2d.cur.ymin= (float)(ytop-(ar->v2d.mask.ymax-ar->v2d.mask.ymin));
797                         
798                         /* make te->xs ==> te->xend center of view */
799                         xdelta = (int)(te->xs - ar->v2d.cur.xmin);
800                         ar->v2d.cur.xmin += xdelta;
801                         ar->v2d.cur.xmax += xdelta;
802                         
803                         /* store selection */
804                         soops->search_tse= *tselem;
805                         
806                         BLI_strncpy(soops->search_string, name, sizeof(soops->search_string));
807                         soops->search_flags= flags;
808                         
809                         /* redraw */
810                         soops->storeflag |= SO_TREESTORE_REDRAW;
811                 }
812         }
813         else {
814                 /* no tree-element found */
815                 BKE_report(reports, RPT_WARNING, "Not found: %s", name);
816         }
817 }
818 #endif
819
820 /* Show One Level ----------------------------------------------- */
821
822 /* helper function for Show/Hide one level operator */
823 static void outliner_openclose_level(SpaceOops *soops, ListBase *lb, int curlevel, int level, int open)
824 {
825         TreeElement *te;
826         TreeStoreElem *tselem;
827         
828         for(te= lb->first; te; te= te->next) {
829                 tselem= TREESTORE(te);
830                 
831                 if(open) {
832                         if(curlevel<=level) tselem->flag &= ~TSE_CLOSED;
833                 }
834                 else {
835                         if(curlevel>=level) tselem->flag |= TSE_CLOSED;
836                 }
837                 
838                 outliner_openclose_level(soops, &te->subtree, curlevel+1, level, open);
839         }
840 }
841
842 static int outliner_one_level_exec(bContext *C, wmOperator *op)
843 {
844         SpaceOops *soops= CTX_wm_space_outliner(C);
845         ARegion *ar= CTX_wm_region(C);
846         int add= RNA_boolean_get(op->ptr, "open");
847         int level;
848         
849         level= outliner_has_one_flag(soops, &soops->tree, TSE_CLOSED, 1);
850         if(add==1) {
851                 if(level) outliner_openclose_level(soops, &soops->tree, 1, level, 1);
852         }
853         else {
854                 if(level==0) level= outliner_count_levels(soops, &soops->tree, 0);
855                 if(level) outliner_openclose_level(soops, &soops->tree, 1, level-1, 0);
856         }
857         
858         ED_region_tag_redraw(ar);
859         
860         return OPERATOR_FINISHED;
861 }
862
863 void OUTLINER_OT_show_one_level(wmOperatorType *ot)
864 {
865         /* identifiers */
866         ot->name= "Show/Hide One Level";
867         ot->idname= "OUTLINER_OT_show_one_level";
868         ot->description= "Expand/collapse all entries by one level";
869         
870         /* callbacks */
871         ot->exec= outliner_one_level_exec;
872         ot->poll= ED_operator_outliner_active;
873         
874         /* no undo or registry, UI option */
875         
876         /* properties */
877         RNA_def_boolean(ot->srna, "open", 1, "Open", "Expand all entries one level deep");
878 }
879
880 /* Show Hierarchy ----------------------------------------------- */
881
882 /* helper function for tree_element_shwo_hierarchy() - recursively checks whether subtrees have any objects*/
883 static int subtree_has_objects(SpaceOops *soops, ListBase *lb)
884 {
885         TreeElement *te;
886         TreeStoreElem *tselem;
887         
888         for(te= lb->first; te; te= te->next) {
889                 tselem= TREESTORE(te);
890                 if(tselem->type==0 && te->idcode==ID_OB) return 1;
891                 if( subtree_has_objects(soops, &te->subtree)) return 1;
892         }
893         return 0;
894 }
895
896 /* recursive helper function for Show Hierarchy operator */
897 static void tree_element_show_hierarchy(Scene *scene, SpaceOops *soops, ListBase *lb)
898 {
899         TreeElement *te;
900         TreeStoreElem *tselem;
901
902         /* open all object elems, close others */
903         for(te= lb->first; te; te= te->next) {
904                 tselem= TREESTORE(te);
905                 
906                 if(tselem->type==0) {
907                         if(te->idcode==ID_SCE) {
908                                 if(tselem->id!=(ID *)scene) tselem->flag |= TSE_CLOSED;
909                                         else tselem->flag &= ~TSE_CLOSED;
910                         }
911                         else if(te->idcode==ID_OB) {
912                                 if(subtree_has_objects(soops, &te->subtree)) tselem->flag &= ~TSE_CLOSED;
913                                 else tselem->flag |= TSE_CLOSED;
914                         }
915                 }
916                 else tselem->flag |= TSE_CLOSED;
917                 
918                 if(TSELEM_OPEN(tselem,soops)) tree_element_show_hierarchy(scene, soops, &te->subtree);
919         }
920 }
921
922 /* show entire object level hierarchy */
923 static int outliner_show_hierarchy_exec(bContext *C, wmOperator *UNUSED(op))
924 {
925         SpaceOops *soops= CTX_wm_space_outliner(C);
926         ARegion *ar= CTX_wm_region(C);
927         Scene *scene= CTX_data_scene(C);
928         
929         /* recursively open/close levels */
930         tree_element_show_hierarchy(scene, soops, &soops->tree);
931         
932         ED_region_tag_redraw(ar);
933         
934         return OPERATOR_FINISHED;
935 }
936
937 void OUTLINER_OT_show_hierarchy(wmOperatorType *ot)
938 {
939         /* identifiers */
940         ot->name= "Show Hierarchy";
941         ot->idname= "OUTLINER_OT_show_hierarchy";
942         ot->description= "Open all object entries and close all others";
943         
944         /* callbacks */
945         ot->exec= outliner_show_hierarchy_exec;
946         ot->poll= ED_operator_outliner_active; //  TODO: shouldn't be allowed in RNA views...
947         
948         /* no undo or registry, UI option */
949 }
950
951 /* ************************************************************** */
952 /* ANIMATO OPERATIONS */
953 /* KeyingSet and Driver Creation - Helper functions */
954
955 /* specialized poll callback for these operators to work in Datablocks view only */
956 static int ed_operator_outliner_datablocks_active(bContext *C)
957 {
958         ScrArea *sa= CTX_wm_area(C);
959         if ((sa) && (sa->spacetype==SPACE_OUTLINER)) {
960                 SpaceOops *so= CTX_wm_space_outliner(C);
961                 return (so->outlinevis == SO_DATABLOCKS);
962         }
963         return 0;
964 }
965
966
967 /* Helper func to extract an RNA path from selected tree element 
968  * NOTE: the caller must zero-out all values of the pointers that it passes here first, as
969  * this function does not do that yet 
970  */
971 static void tree_element_to_path(SpaceOops *soops, TreeElement *te, TreeStoreElem *tselem, 
972                                                         ID **id, char **path, int *array_index, short *flag, short *UNUSED(groupmode))
973 {
974         ListBase hierarchy = {NULL, NULL};
975         LinkData *ld;
976         TreeElement *tem, *temnext, *temsub;
977         TreeStoreElem *tse /* , *tsenext */ /* UNUSED */;
978         PointerRNA *ptr, *nextptr;
979         PropertyRNA *prop;
980         char *newpath=NULL;
981         
982         /* optimise tricks:
983          *      - Don't do anything if the selected item is a 'struct', but arrays are allowed
984          */
985         if (tselem->type == TSE_RNA_STRUCT)
986                 return;
987         
988         /* Overview of Algorithm:
989          *      1. Go up the chain of parents until we find the 'root', taking note of the 
990          *         levels encountered in reverse-order (i.e. items are added to the start of the list
991          *      for more convenient looping later)
992          *      2. Walk down the chain, adding from the first ID encountered 
993          *         (which will become the 'ID' for the KeyingSet Path), and build a  
994          *              path as we step through the chain
995          */
996          
997         /* step 1: flatten out hierarchy of parents into a flat chain */
998         for (tem= te->parent; tem; tem= tem->parent) {
999                 ld= MEM_callocN(sizeof(LinkData), "LinkData for tree_element_to_path()");
1000                 ld->data= tem;
1001                 BLI_addhead(&hierarchy, ld);
1002         }
1003         
1004         /* step 2: step down hierarchy building the path (NOTE: addhead in previous loop was needed so that we can loop like this) */
1005         for (ld= hierarchy.first; ld; ld= ld->next) {
1006                 /* get data */
1007                 tem= (TreeElement *)ld->data;
1008                 tse= TREESTORE(tem);
1009                 ptr= &tem->rnaptr;
1010                 prop= tem->directdata;
1011                 
1012                 /* check if we're looking for first ID, or appending to path */
1013                 if (*id) {
1014                         /* just 'append' property to path 
1015                          *      - to prevent memory leaks, we must write to newpath not path, then free old path + swap them
1016                          */
1017                         if(tse->type == TSE_RNA_PROPERTY) {
1018                                 if(RNA_property_type(prop) == PROP_POINTER) {
1019                                         /* for pointer we just append property name */
1020                                         newpath= RNA_path_append(*path, ptr, prop, 0, NULL);
1021                                 }
1022                                 else if(RNA_property_type(prop) == PROP_COLLECTION) {
1023                                         char buf[128], *name;
1024                                         
1025                                         temnext= (TreeElement*)(ld->next->data);
1026                                         /* tsenext= TREESTORE(temnext); */ /* UNUSED */
1027                                         
1028                                         nextptr= &temnext->rnaptr;
1029                                         name= RNA_struct_name_get_alloc(nextptr, buf, sizeof(buf), NULL);
1030                                         
1031                                         if(name) {
1032                                                 /* if possible, use name as a key in the path */
1033                                                 newpath= RNA_path_append(*path, NULL, prop, 0, name);
1034                                                 
1035                                                 if(name != buf)
1036                                                         MEM_freeN(name);
1037                                         }
1038                                         else {
1039                                                 /* otherwise use index */
1040                                                 int index= 0;
1041                                                 
1042                                                 for(temsub=tem->subtree.first; temsub; temsub=temsub->next, index++)
1043                                                         if(temsub == temnext)
1044                                                                 break;
1045                                                 
1046                                                 newpath= RNA_path_append(*path, NULL, prop, index, NULL);
1047                                         }
1048                                         
1049                                         ld= ld->next;
1050                                 }
1051                         }
1052                         
1053                         if(newpath) {
1054                                 if (*path) MEM_freeN(*path);
1055                                 *path= newpath;
1056                                 newpath= NULL;
1057                         }
1058                 }
1059                 else {
1060                         /* no ID, so check if entry is RNA-struct, and if that RNA-struct is an ID datablock to extract info from */
1061                         if (tse->type == TSE_RNA_STRUCT) {
1062                                 /* ptr->data not ptr->id.data seems to be the one we want, since ptr->data is sometimes the owner of this ID? */
1063                                 if(RNA_struct_is_ID(ptr->type)) {
1064                                         *id= (ID *)ptr->data;
1065                                         
1066                                         /* clear path */
1067                                         if(*path) {
1068                                                 MEM_freeN(*path);
1069                                                 path= NULL;
1070                                         }
1071                                 }
1072                         }
1073                 }
1074         }
1075
1076         /* step 3: if we've got an ID, add the current item to the path */
1077         if (*id) {
1078                 /* add the active property to the path */
1079                 ptr= &te->rnaptr;
1080                 prop= te->directdata;
1081                 
1082                 /* array checks */
1083                 if (tselem->type == TSE_RNA_ARRAY_ELEM) {
1084                         /* item is part of an array, so must set the array_index */
1085                         *array_index= te->index;
1086                 }
1087                 else if (RNA_property_array_length(ptr, prop)) {
1088                         /* entire array was selected, so keyframe all */
1089                         *flag |= KSP_FLAG_WHOLE_ARRAY;
1090                 }
1091                 
1092                 /* path */
1093                 newpath= RNA_path_append(*path, NULL, prop, 0, NULL);
1094                 if (*path) MEM_freeN(*path);
1095                 *path= newpath;
1096         }
1097
1098         /* free temp data */
1099         BLI_freelistN(&hierarchy);
1100 }
1101
1102 /* =============================================== */
1103 /* Driver Operations */
1104
1105 /* These operators are only available in databrowser mode for now, as
1106  * they depend on having RNA paths and/or hierarchies available.
1107  */
1108 enum {
1109         DRIVERS_EDITMODE_ADD    = 0,
1110         DRIVERS_EDITMODE_REMOVE,
1111 } /*eDrivers_EditModes*/;
1112
1113 /* Utilities ---------------------------------- */ 
1114
1115 /* Recursively iterate over tree, finding and working on selected items */
1116 static void do_outliner_drivers_editop(SpaceOops *soops, ListBase *tree, ReportList *reports, short mode)
1117 {
1118         TreeElement *te;
1119         TreeStoreElem *tselem;
1120         
1121         for (te= tree->first; te; te=te->next) {
1122                 tselem= TREESTORE(te);
1123                 
1124                 /* if item is selected, perform operation */
1125                 if (tselem->flag & TSE_SELECTED) {
1126                         ID *id= NULL;
1127                         char *path= NULL;
1128                         int array_index= 0;
1129                         short flag= 0;
1130                         short groupmode= KSP_GROUP_KSNAME;
1131                         
1132                         /* check if RNA-property described by this selected element is an animateable prop */
1133                         if (ELEM(tselem->type, TSE_RNA_PROPERTY, TSE_RNA_ARRAY_ELEM) && RNA_property_animateable(&te->rnaptr, te->directdata)) {
1134                                 /* get id + path + index info from the selected element */
1135                                 tree_element_to_path(soops, te, tselem, 
1136                                                 &id, &path, &array_index, &flag, &groupmode);
1137                         }
1138                         
1139                         /* only if ID and path were set, should we perform any actions */
1140                         if (id && path) {
1141                                 short dflags = CREATEDRIVER_WITH_DEFAULT_DVAR;
1142                                 int arraylen = 1;
1143                                 
1144                                 /* array checks */
1145                                 if (flag & KSP_FLAG_WHOLE_ARRAY) {
1146                                         /* entire array was selected, so add drivers for all */
1147                                         arraylen= RNA_property_array_length(&te->rnaptr, te->directdata);
1148                                 }
1149                                 else
1150                                         arraylen= array_index;
1151                                 
1152                                 /* we should do at least one step */
1153                                 if (arraylen == array_index)
1154                                         arraylen++;
1155                                 
1156                                 /* for each array element we should affect, add driver */
1157                                 for (; array_index < arraylen; array_index++) {
1158                                         /* action depends on mode */
1159                                         switch (mode) {
1160                                                 case DRIVERS_EDITMODE_ADD:
1161                                                 {
1162                                                         /* add a new driver with the information obtained (only if valid) */
1163                                                         ANIM_add_driver(reports, id, path, array_index, dflags, DRIVER_TYPE_PYTHON);
1164                                                 }
1165                                                         break;
1166                                                 case DRIVERS_EDITMODE_REMOVE:
1167                                                 {
1168                                                         /* remove driver matching the information obtained (only if valid) */
1169                                                         ANIM_remove_driver(reports, id, path, array_index, dflags);
1170                                                 }
1171                                                         break;
1172                                         }
1173                                 }
1174                                 
1175                                 /* free path, since it had to be generated */
1176                                 MEM_freeN(path);
1177                         }
1178                         
1179                         
1180                 }
1181                 
1182                 /* go over sub-tree */
1183                 if (TSELEM_OPEN(tselem,soops))
1184                         do_outliner_drivers_editop(soops, &te->subtree, reports, mode);
1185         }
1186 }
1187
1188 /* Add Operator ---------------------------------- */
1189
1190 static int outliner_drivers_addsel_exec(bContext *C, wmOperator *op)
1191 {
1192         SpaceOops *soutliner= CTX_wm_space_outliner(C);
1193         
1194         /* check for invalid states */
1195         if (soutliner == NULL)
1196                 return OPERATOR_CANCELLED;
1197         
1198         /* recursively go into tree, adding selected items */
1199         do_outliner_drivers_editop(soutliner, &soutliner->tree, op->reports, DRIVERS_EDITMODE_ADD);
1200         
1201         /* send notifiers */
1202         WM_event_add_notifier(C, NC_ANIMATION|ND_FCURVES_ORDER, NULL); // XXX
1203         
1204         return OPERATOR_FINISHED;
1205 }
1206
1207 void OUTLINER_OT_drivers_add_selected(wmOperatorType *ot)
1208 {
1209         /* api callbacks */
1210         ot->idname= "OUTLINER_OT_drivers_add_selected";
1211         ot->name= "Add Drivers for Selected";
1212         ot->description= "Add drivers to selected items";
1213         
1214         /* api callbacks */
1215         ot->exec= outliner_drivers_addsel_exec;
1216         ot->poll= ed_operator_outliner_datablocks_active;
1217         
1218         /* flags */
1219         ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
1220 }
1221
1222
1223 /* Remove Operator ---------------------------------- */
1224
1225 static int outliner_drivers_deletesel_exec(bContext *C, wmOperator *op)
1226 {
1227         SpaceOops *soutliner= CTX_wm_space_outliner(C);
1228         
1229         /* check for invalid states */
1230         if (soutliner == NULL)
1231                 return OPERATOR_CANCELLED;
1232         
1233         /* recursively go into tree, adding selected items */
1234         do_outliner_drivers_editop(soutliner, &soutliner->tree, op->reports, DRIVERS_EDITMODE_REMOVE);
1235         
1236         /* send notifiers */
1237         WM_event_add_notifier(C, ND_KEYS, NULL); // XXX
1238         
1239         return OPERATOR_FINISHED;
1240 }
1241
1242 void OUTLINER_OT_drivers_delete_selected(wmOperatorType *ot)
1243 {
1244         /* identifiers */
1245         ot->idname= "OUTLINER_OT_drivers_delete_selected";
1246         ot->name= "Delete Drivers for Selected";
1247         ot->description= "Delete drivers assigned to selected items";
1248         
1249         /* api callbacks */
1250         ot->exec= outliner_drivers_deletesel_exec;
1251         ot->poll= ed_operator_outliner_datablocks_active;
1252         
1253         /* flags */
1254         ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
1255 }
1256
1257 /* =============================================== */
1258 /* Keying Set Operations */
1259
1260 /* These operators are only available in databrowser mode for now, as
1261  * they depend on having RNA paths and/or hierarchies available.
1262  */
1263 enum {
1264         KEYINGSET_EDITMODE_ADD  = 0,
1265         KEYINGSET_EDITMODE_REMOVE,
1266 } /*eKeyingSet_EditModes*/;
1267
1268 /* Utilities ---------------------------------- */ 
1269  
1270 /* find the 'active' KeyingSet, and add if not found (if adding is allowed) */
1271 // TODO: should this be an API func?
1272 static KeyingSet *verify_active_keyingset(Scene *scene, short add)
1273 {
1274         KeyingSet *ks= NULL;
1275         
1276         /* sanity check */
1277         if (scene == NULL)
1278                 return NULL;
1279         
1280         /* try to find one from scene */
1281         if (scene->active_keyingset > 0)
1282                 ks= BLI_findlink(&scene->keyingsets, scene->active_keyingset-1);
1283                 
1284         /* add if none found */
1285         // XXX the default settings have yet to evolve
1286         if ((add) && (ks==NULL)) {
1287                 ks= BKE_keyingset_add(&scene->keyingsets, NULL, KEYINGSET_ABSOLUTE, 0);
1288                 scene->active_keyingset= BLI_countlist(&scene->keyingsets);
1289         }
1290         
1291         return ks;
1292 }
1293
1294 /* Recursively iterate over tree, finding and working on selected items */
1295 static void do_outliner_keyingset_editop(SpaceOops *soops, KeyingSet *ks, ListBase *tree, short mode)
1296 {
1297         TreeElement *te;
1298         TreeStoreElem *tselem;
1299         
1300         for (te= tree->first; te; te=te->next) {
1301                 tselem= TREESTORE(te);
1302                 
1303                 /* if item is selected, perform operation */
1304                 if (tselem->flag & TSE_SELECTED) {
1305                         ID *id= NULL;
1306                         char *path= NULL;
1307                         int array_index= 0;
1308                         short flag= 0;
1309                         short groupmode= KSP_GROUP_KSNAME;
1310                         
1311                         /* check if RNA-property described by this selected element is an animateable prop */
1312                         if (ELEM(tselem->type, TSE_RNA_PROPERTY, TSE_RNA_ARRAY_ELEM) && RNA_property_animateable(&te->rnaptr, te->directdata)) {
1313                                 /* get id + path + index info from the selected element */
1314                                 tree_element_to_path(soops, te, tselem, 
1315                                                 &id, &path, &array_index, &flag, &groupmode);
1316                         }
1317                         
1318                         /* only if ID and path were set, should we perform any actions */
1319                         if (id && path) {
1320                                 /* action depends on mode */
1321                                 switch (mode) {
1322                                         case KEYINGSET_EDITMODE_ADD:
1323                                         {
1324                                                 /* add a new path with the information obtained (only if valid) */
1325                                                 // TODO: what do we do with group name? for now, we don't supply one, and just let this use the KeyingSet name
1326                                                 BKE_keyingset_add_path(ks, id, NULL, path, array_index, flag, groupmode);
1327                                                 ks->active_path= BLI_countlist(&ks->paths);
1328                                         }
1329                                                 break;
1330                                         case KEYINGSET_EDITMODE_REMOVE:
1331                                         {
1332                                                 /* find the relevant path, then remove it from the KeyingSet */
1333                                                 KS_Path *ksp= BKE_keyingset_find_path(ks, id, NULL, path, array_index, groupmode);
1334                                                 
1335                                                 if (ksp) {
1336                                                         /* free path's data */
1337                                                         BKE_keyingset_free_path(ks, ksp);
1338
1339                                                         ks->active_path= 0;
1340                                                 }
1341                                         }
1342                                                 break;
1343                                 }
1344                                 
1345                                 /* free path, since it had to be generated */
1346                                 MEM_freeN(path);
1347                         }
1348                 }
1349                 
1350                 /* go over sub-tree */
1351                 if (TSELEM_OPEN(tselem,soops))
1352                         do_outliner_keyingset_editop(soops, ks, &te->subtree, mode);
1353         }
1354 }
1355
1356 /* Add Operator ---------------------------------- */
1357
1358 static int outliner_keyingset_additems_exec(bContext *C, wmOperator *op)
1359 {
1360         SpaceOops *soutliner= CTX_wm_space_outliner(C);
1361         Scene *scene= CTX_data_scene(C);
1362         KeyingSet *ks= verify_active_keyingset(scene, 1);
1363         
1364         /* check for invalid states */
1365         if (ks == NULL) {
1366                 BKE_report(op->reports, RPT_ERROR, "Operation requires an Active Keying Set");
1367                 return OPERATOR_CANCELLED;
1368         }
1369         if (soutliner == NULL)
1370                 return OPERATOR_CANCELLED;
1371         
1372         /* recursively go into tree, adding selected items */
1373         do_outliner_keyingset_editop(soutliner, ks, &soutliner->tree, KEYINGSET_EDITMODE_ADD);
1374         
1375         /* send notifiers */
1376         WM_event_add_notifier(C, NC_SCENE|ND_KEYINGSET, NULL);
1377         
1378         return OPERATOR_FINISHED;
1379 }
1380
1381 void OUTLINER_OT_keyingset_add_selected(wmOperatorType *ot)
1382 {
1383         /* identifiers */
1384         ot->idname= "OUTLINER_OT_keyingset_add_selected";
1385         ot->name= "Keying Set Add Selected";
1386         ot->description= "Add selected items (blue-grey rows) to active Keying Set";
1387         
1388         /* api callbacks */
1389         ot->exec= outliner_keyingset_additems_exec;
1390         ot->poll= ed_operator_outliner_datablocks_active;
1391         
1392         /* flags */
1393         ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
1394 }
1395
1396
1397 /* Remove Operator ---------------------------------- */
1398
1399 static int outliner_keyingset_removeitems_exec(bContext *C, wmOperator *UNUSED(op))
1400 {
1401         SpaceOops *soutliner= CTX_wm_space_outliner(C);
1402         Scene *scene= CTX_data_scene(C);
1403         KeyingSet *ks= verify_active_keyingset(scene, 1);
1404         
1405         /* check for invalid states */
1406         if (soutliner == NULL)
1407                 return OPERATOR_CANCELLED;
1408         
1409         /* recursively go into tree, adding selected items */
1410         do_outliner_keyingset_editop(soutliner, ks, &soutliner->tree, KEYINGSET_EDITMODE_REMOVE);
1411         
1412         /* send notifiers */
1413         WM_event_add_notifier(C, NC_SCENE|ND_KEYINGSET, NULL);
1414         
1415         return OPERATOR_FINISHED;
1416 }
1417
1418 void OUTLINER_OT_keyingset_remove_selected(wmOperatorType *ot)
1419 {
1420         /* identifiers */
1421         ot->idname= "OUTLINER_OT_keyingset_remove_selected";
1422         ot->name= "Keying Set Remove Selected";
1423         ot->description = "Remove selected items (blue-grey rows) from active Keying Set";
1424         
1425         /* api callbacks */
1426         ot->exec= outliner_keyingset_removeitems_exec;
1427         ot->poll= ed_operator_outliner_datablocks_active;
1428         
1429         /* flags */
1430         ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
1431 }