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