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