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