Py-Driver: add 'self' option
[blender.git] / source / blender / editors / space_graph / graph_buttons.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) 2009 Blender Foundation.
19  * All rights reserved.
20  *
21  * 
22  * Contributor(s): Blender Foundation, Joshua Leung
23  *
24  * ***** END GPL LICENSE BLOCK *****
25  */
26
27 /** \file blender/editors/space_graph/graph_buttons.c
28  *  \ingroup spgraph
29  */
30
31
32 #include <string.h>
33 #include <stdio.h>
34 #include <math.h>
35 #include <float.h>
36
37 #include "DNA_anim_types.h"
38 #include "DNA_object_types.h"
39 #include "DNA_scene_types.h"
40
41 #include "MEM_guardedalloc.h"
42
43 #include "BLI_math.h"
44 #include "BLI_blenlib.h"
45 #include "BLI_utildefines.h"
46
47 #include "BLT_translation.h"
48
49 #include "BKE_context.h"
50 #include "BKE_curve.h"
51 #include "BKE_depsgraph.h"
52 #include "BKE_fcurve.h"
53 #include "BKE_main.h"
54 #include "BKE_global.h"
55 #include "BKE_screen.h"
56 #include "BKE_unit.h"
57
58
59 #include "WM_api.h"
60 #include "WM_types.h"
61
62 #include "RNA_access.h"
63
64 #include "ED_anim_api.h"
65 #include "ED_keyframing.h"
66 #include "ED_screen.h"
67
68 #include "UI_interface.h"
69 #include "UI_resources.h"
70
71 #include "graph_intern.h"   // own include
72
73 /* ******************* graph editor space & buttons ************** */
74
75 #define B_REDR 1
76
77 /* -------------- */
78
79 static int graph_panel_context(const bContext *C, bAnimListElem **ale, FCurve **fcu)
80 {
81         bAnimContext ac;
82         bAnimListElem *elem = NULL;
83         
84         /* for now, only draw if we could init the anim-context info (necessary for all animation-related tools) 
85          * to work correctly is able to be correctly retrieved. There's no point showing empty panels?
86          */
87         if (ANIM_animdata_get_context(C, &ac) == 0) 
88                 return 0;
89         
90         /* try to find 'active' F-Curve */
91         elem = get_active_fcurve_channel(&ac);
92         if (elem == NULL) 
93                 return 0;
94         
95         if (fcu)
96                 *fcu = (FCurve *)elem->data;
97         if (ale)
98                 *ale = elem;
99         else
100                 MEM_freeN(elem);
101         
102         return 1;
103 }
104
105 static int graph_panel_poll(const bContext *C, PanelType *UNUSED(pt))
106 {
107         return graph_panel_context(C, NULL, NULL);
108 }
109
110 /* -------------- */
111
112 /* Graph Editor View Settings */
113 static void graph_panel_view(const bContext *C, Panel *pa)
114 {
115         bScreen *sc = CTX_wm_screen(C);
116         SpaceIpo *sipo = CTX_wm_space_graph(C);
117         Scene *scene = CTX_data_scene(C);
118         PointerRNA spaceptr, sceneptr;
119         uiLayout *col, *sub, *row;
120         
121         /* get RNA pointers for use when creating the UI elements */
122         RNA_id_pointer_create(&scene->id, &sceneptr);
123         RNA_pointer_create(&sc->id, &RNA_SpaceGraphEditor, sipo, &spaceptr);
124
125         /* 2D-Cursor */
126         col = uiLayoutColumn(pa->layout, false);
127         uiItemR(col, &spaceptr, "show_cursor", 0, NULL, ICON_NONE);
128                 
129         sub = uiLayoutColumn(col, true);
130         uiLayoutSetActive(sub, RNA_boolean_get(&spaceptr, "show_cursor"));
131         uiItemO(sub, IFACE_("Cursor from Selection"), ICON_NONE, "GRAPH_OT_frame_jump");
132
133         sub = uiLayoutColumn(col, true);
134         uiLayoutSetActive(sub, RNA_boolean_get(&spaceptr, "show_cursor"));
135         row = uiLayoutSplit(sub, 0.7f, true);
136         if (sipo->mode == SIPO_MODE_DRIVERS)
137                 uiItemR(row, &spaceptr, "cursor_position_x", 0, IFACE_("Cursor X"), ICON_NONE);
138         else
139                 uiItemR(row, &sceneptr, "frame_current", 0, IFACE_("Cursor X"), ICON_NONE);
140         uiItemEnumO(row, "GRAPH_OT_snap", IFACE_("To Keys"), 0, "type", GRAPHKEYS_SNAP_CFRA);
141         
142         row = uiLayoutSplit(sub, 0.7f, true);
143         uiItemR(row, &spaceptr, "cursor_position_y", 0, IFACE_("Cursor Y"), ICON_NONE);
144         uiItemEnumO(row, "GRAPH_OT_snap", IFACE_("To Keys"), 0, "type", GRAPHKEYS_SNAP_VALUE);
145 }
146
147 /* ******************* active F-Curve ************** */
148
149 static void graph_panel_properties(const bContext *C, Panel *pa)
150 {
151         bAnimListElem *ale;
152         FCurve *fcu;
153         PointerRNA fcu_ptr;
154         uiLayout *layout = pa->layout;
155         uiLayout *col, *row, *sub;
156         // uiBlock *block;  // UNUSED
157         char name[256];
158         int icon = 0;
159
160         if (!graph_panel_context(C, &ale, &fcu))
161                 return;
162         
163         // UNUSED
164         // block = uiLayoutGetBlock(layout);
165         // UI_block_func_handle_set(block, do_graph_region_buttons, NULL);
166         
167         /* F-Curve pointer */
168         RNA_pointer_create(ale->id, &RNA_FCurve, fcu, &fcu_ptr);
169         
170         /* user-friendly 'name' for F-Curve */
171         col = uiLayoutColumn(layout, false);
172         if (ale->type == ANIMTYPE_FCURVE) {
173                 /* get user-friendly name for F-Curve */
174                 icon = getname_anim_fcurve(name, ale->id, fcu);
175         }
176         else {
177                 /* NLA Control Curve, etc. */
178                 const bAnimChannelType *acf = ANIM_channel_get_typeinfo(ale);
179                 
180                 /* get name */
181                 if (acf && acf->name) {
182                         acf->name(ale, name);
183                 }
184                 else {
185                         strcpy(name, IFACE_("<invalid>"));
186                         icon = ICON_ERROR;
187                 }
188                 
189                 /* icon */
190                 if (ale->type == ANIMTYPE_NLACURVE)
191                         icon = ICON_NLA;
192         }
193         uiItemL(col, name, icon);
194                 
195         /* RNA-Path Editing - only really should be enabled when things aren't working */
196         col = uiLayoutColumn(layout, true);
197         uiLayoutSetEnabled(col, (fcu->flag & FCURVE_DISABLED) != 0);
198         uiItemR(col, &fcu_ptr, "data_path", 0, "", ICON_RNA);
199         uiItemR(col, &fcu_ptr, "array_index", 0, NULL, ICON_NONE);
200                 
201         /* color settings */
202         col = uiLayoutColumn(layout, true);
203         uiItemL(col, IFACE_("Display Color:"), ICON_NONE);
204                 
205         row = uiLayoutRow(col, true);
206         uiItemR(row, &fcu_ptr, "color_mode", 0, "", ICON_NONE);
207                         
208         sub = uiLayoutRow(row, true);
209         uiLayoutSetEnabled(sub, (fcu->color_mode == FCURVE_COLOR_CUSTOM));
210         uiItemR(sub, &fcu_ptr, "color", 0, "", ICON_NONE);
211         
212         MEM_freeN(ale);
213 }
214
215 /* ******************* active Keyframe ************** */
216
217 /* get 'active' keyframe for panel editing */
218 static short get_active_fcurve_keyframe_edit(FCurve *fcu, BezTriple **bezt, BezTriple **prevbezt)
219 {
220         BezTriple *b;
221         int i;
222         
223         /* zero the pointers */
224         *bezt = *prevbezt = NULL;
225         
226         /* sanity checks */
227         if ((fcu->bezt == NULL) || (fcu->totvert == 0))
228                 return 0;
229                 
230         /* find first selected keyframe for now, and call it the active one 
231          *      - this is a reasonable assumption, given that whenever anyone 
232          *        wants to edit numerically, there is likely to only be 1 vert selected
233          */
234         for (i = 0, b = fcu->bezt; i < fcu->totvert; i++, b++) {
235                 if (BEZT_ISSEL_ANY(b)) {
236                         /* found 
237                          *      - 'previous' is either the one before, of the keyframe itself (which is still fine)
238                          *              XXX: we can just make this null instead if needed
239                          */
240                         *prevbezt = (i > 0) ? b - 1 : b;
241                         *bezt = b;
242                         
243                         return 1;
244                 }
245         }
246         
247         /* not found */
248         return 0;
249 }
250
251 /* update callback for active keyframe properties - base updates stuff */
252 static void graphedit_activekey_update_cb(bContext *UNUSED(C), void *fcu_ptr, void *UNUSED(bezt_ptr))
253 {
254         FCurve *fcu = (FCurve *)fcu_ptr;
255         
256         /* make sure F-Curve and its handles are still valid after this editing */
257         sort_time_fcurve(fcu);
258         calchandles_fcurve(fcu);
259 }
260
261 /* update callback for active keyframe properties - handle-editing wrapper */
262 static void graphedit_activekey_handles_cb(bContext *C, void *fcu_ptr, void *bezt_ptr)
263 {
264         BezTriple *bezt = (BezTriple *)bezt_ptr;
265         
266         /* since editing the handles, make sure they're set to types which are receptive to editing 
267          * see transform_conversions.c :: createTransGraphEditData(), last step in second loop
268          */
269         if (ELEM(bezt->h1, HD_AUTO, HD_AUTO_ANIM) && ELEM(bezt->h2, HD_AUTO, HD_AUTO_ANIM)) {
270                 /* by changing to aligned handles, these can now be moved... */
271                 bezt->h1 = HD_ALIGN;
272                 bezt->h2 = HD_ALIGN;
273         }
274         else {
275                 BKE_nurb_bezt_handle_test(bezt, true);
276         }
277         
278         /* now call standard updates */
279         graphedit_activekey_update_cb(C, fcu_ptr, bezt_ptr);
280 }
281
282 /* update callback for editing coordinates of right handle in active keyframe properties
283  * NOTE: we cannot just do graphedit_activekey_handles_cb() due to "order of computation"
284  *       weirdness (see calchandleNurb_intern() and T39911)
285  */
286 static void graphedit_activekey_left_handle_coord_cb(bContext *C, void *fcu_ptr, void *bezt_ptr)
287 {
288         BezTriple *bezt = (BezTriple *)bezt_ptr;
289
290         const char f1 = bezt->f1;
291         const char f3 = bezt->f3;
292
293         bezt->f1 |= SELECT;
294         bezt->f3 &= ~SELECT;
295
296         /* perform normal updates NOW */
297         graphedit_activekey_handles_cb(C, fcu_ptr, bezt_ptr);
298
299         /* restore selection state so that no-one notices this hack */
300         bezt->f1 = f1;
301         bezt->f3 = f3;
302 }
303
304 static void graphedit_activekey_right_handle_coord_cb(bContext *C, void *fcu_ptr, void *bezt_ptr)
305 {
306         BezTriple *bezt = (BezTriple *)bezt_ptr;
307         
308         /* original state of handle selection - to be restored after performing the recalculation */
309         const char f1 = bezt->f1;
310         const char f3 = bezt->f3;
311         
312         /* temporarily make it so that only the right handle is selected, so that updates go correctly 
313          * (i.e. it now acts as if we've just transforming the vert when it is selected by itself)
314          */
315         bezt->f1 &= ~SELECT;
316         bezt->f3 |= SELECT;
317         
318         /* perform normal updates NOW */
319         graphedit_activekey_handles_cb(C, fcu_ptr, bezt_ptr);
320         
321         /* restore selection state so that no-one notices this hack */
322         bezt->f1 = f1;
323         bezt->f3 = f3;
324 }
325
326 static void graph_panel_key_properties(const bContext *C, Panel *pa)
327 {
328         bAnimListElem *ale;
329         FCurve *fcu;
330         BezTriple *bezt, *prevbezt;
331         
332         uiLayout *layout = pa->layout;
333         uiLayout *col;
334         uiBlock *block;
335
336         if (!graph_panel_context(C, &ale, &fcu))
337                 return;
338         
339         block = uiLayoutGetBlock(layout);
340         /* UI_block_func_handle_set(block, do_graph_region_buttons, NULL); */
341         
342         /* only show this info if there are keyframes to edit */
343         if (get_active_fcurve_keyframe_edit(fcu, &bezt, &prevbezt)) {
344                 PointerRNA bezt_ptr, id_ptr, fcu_prop_ptr;
345                 PropertyRNA *fcu_prop = NULL;
346                 uiBut *but;
347                 int unit = B_UNIT_NONE;
348                 
349                 /* RNA pointer to keyframe, to allow editing */
350                 RNA_pointer_create(ale->id, &RNA_Keyframe, bezt, &bezt_ptr);
351                 
352                 /* get property that F-Curve affects, for some unit-conversion magic */
353                 RNA_id_pointer_create(ale->id, &id_ptr);
354                 if (RNA_path_resolve_property(&id_ptr, fcu->rna_path, &fcu_prop_ptr, &fcu_prop)) {
355                         /* determine the unit for this property */
356                         unit = RNA_SUBTYPE_UNIT(RNA_property_subtype(fcu_prop));
357                 }
358                 
359                 /* interpolation */
360                 col = uiLayoutColumn(layout, false);
361                 if (fcu->flag & FCURVE_DISCRETE_VALUES) {
362                         uiLayout *split = uiLayoutSplit(col, 0.33f, true);
363                         uiItemL(split, IFACE_("Interpolation:"), ICON_NONE);
364                         uiItemL(split, IFACE_("None for Enum/Boolean"), ICON_IPO_CONSTANT);
365                 }
366                 else {
367                         uiItemR(col, &bezt_ptr, "interpolation", 0, NULL, ICON_NONE);
368                 }
369                 
370                 /* easing type */
371                 if (bezt->ipo > BEZT_IPO_BEZ)
372                         uiItemR(col, &bezt_ptr, "easing", 0, NULL, 0);
373
374                 /* easing extra */
375                 switch (bezt->ipo) {
376                         case BEZT_IPO_BACK:
377                                 col = uiLayoutColumn(layout, 1);
378                                 uiItemR(col, &bezt_ptr, "back", 0, NULL, 0);
379                                 break;
380                         case BEZT_IPO_ELASTIC:
381                                 col = uiLayoutColumn(layout, 1);
382                                 uiItemR(col, &bezt_ptr, "amplitude", 0, NULL, 0);
383                                 uiItemR(col, &bezt_ptr, "period", 0, NULL, 0);
384                                 break;
385                         default:
386                                 break;
387                 }
388                 
389                 /* numerical coordinate editing 
390                  *  - we use the button-versions of the calls so that we can attach special update handlers
391                  *    and unit conversion magic that cannot be achieved using a purely RNA-approach
392                  */
393                 col = uiLayoutColumn(layout, true);
394                 /* keyframe itself */
395                 {
396                         uiItemL(col, IFACE_("Key:"), ICON_NONE);
397                         
398                         but = uiDefButR(block, UI_BTYPE_NUM, B_REDR, IFACE_("Frame:"), 0, 0, UI_UNIT_X, UI_UNIT_Y,
399                                         &bezt_ptr, "co", 0, 0, 0, -1, -1, NULL);
400                         UI_but_func_set(but, graphedit_activekey_update_cb, fcu, bezt);
401                         
402                         but = uiDefButR(block, UI_BTYPE_NUM, B_REDR, IFACE_("Value:"), 0, 0, UI_UNIT_X, UI_UNIT_Y,
403                                         &bezt_ptr, "co", 1, 0, 0, -1, -1, NULL);
404                         UI_but_func_set(but, graphedit_activekey_update_cb, fcu, bezt);
405                         UI_but_unit_type_set(but, unit);
406                 }
407                 
408                 /* previous handle - only if previous was Bezier interpolation */
409                 if ((prevbezt) && (prevbezt->ipo == BEZT_IPO_BEZ)) {
410                         uiItemL(col, IFACE_("Left Handle:"), ICON_NONE);
411                         
412                         but = uiDefButR(block, UI_BTYPE_NUM, B_REDR, "X:", 0, 0, UI_UNIT_X, UI_UNIT_Y,
413                                         &bezt_ptr, "handle_left", 0, 0, 0, -1, -1, NULL);
414                         UI_but_func_set(but, graphedit_activekey_left_handle_coord_cb, fcu, bezt);
415                         
416                         but = uiDefButR(block, UI_BTYPE_NUM, B_REDR, "Y:", 0, 0, UI_UNIT_X, UI_UNIT_Y,
417                                         &bezt_ptr, "handle_left", 1, 0, 0, -1, -1, NULL);
418                         UI_but_func_set(but, graphedit_activekey_left_handle_coord_cb, fcu, bezt);
419                         UI_but_unit_type_set(but, unit);
420                         
421                         /* XXX: with label? */
422                         but = uiDefButR(block, UI_BTYPE_MENU, B_REDR, NULL, 0, 0, UI_UNIT_X, UI_UNIT_Y,
423                                         &bezt_ptr, "handle_left_type", 0, 0, 0, -1, -1, "Type of left handle");
424                         UI_but_func_set(but, graphedit_activekey_handles_cb, fcu, bezt);
425                 }
426                 
427                 /* next handle - only if current is Bezier interpolation */
428                 if (bezt->ipo == BEZT_IPO_BEZ) {
429                         /* NOTE: special update callbacks are needed on the coords here due to T39911 */
430                         uiItemL(col, IFACE_("Right Handle:"), ICON_NONE);
431                         
432                         but = uiDefButR(block, UI_BTYPE_NUM, B_REDR, "X:", 0, 0, UI_UNIT_X, UI_UNIT_Y,
433                                         &bezt_ptr, "handle_right", 0, 0, 0, -1, -1, NULL);
434                         UI_but_func_set(but, graphedit_activekey_right_handle_coord_cb, fcu, bezt);
435                         
436                         but = uiDefButR(block, UI_BTYPE_NUM, B_REDR, "Y:", 0, 0, UI_UNIT_X, UI_UNIT_Y,
437                                         &bezt_ptr, "handle_right", 1, 0, 0, -1, -1, NULL);
438                         UI_but_func_set(but, graphedit_activekey_right_handle_coord_cb, fcu, bezt);
439                         UI_but_unit_type_set(but, unit);
440                         
441                         /* XXX: with label? */
442                         but = uiDefButR(block, UI_BTYPE_MENU, B_REDR, NULL, 0, 0, UI_UNIT_X, UI_UNIT_Y,
443                                         &bezt_ptr, "handle_right_type", 0, 0, 0, -1, -1, "Type of right handle");
444                         UI_but_func_set(but, graphedit_activekey_handles_cb, fcu, bezt);
445                 }
446         }
447         else {
448                 if ((fcu->bezt == NULL) && (fcu->modifiers.first)) {
449                         /* modifiers only - so no keyframes to be active */
450                         uiItemL(layout, IFACE_("F-Curve only has F-Modifiers"), ICON_NONE);
451                         uiItemL(layout, IFACE_("See Modifiers panel below"), ICON_INFO);
452                 }
453                 else if (fcu->fpt) {
454                         /* samples only */
455                         uiItemL(layout, IFACE_("F-Curve doesn't have any keyframes as it only contains sampled points"),
456                                 ICON_NONE);
457                 }
458                 else
459                         uiItemL(layout, IFACE_("No active keyframe on F-Curve"), ICON_NONE);
460         }
461         
462         MEM_freeN(ale);
463 }
464
465 /* ******************* drivers ******************************** */
466
467 #define B_IPO_DEPCHANGE     10
468
469 static void do_graph_region_driver_buttons(bContext *C, void *UNUSED(arg), int event)
470 {
471         Main *bmain = CTX_data_main(C);
472         Scene *scene = CTX_data_scene(C);
473         
474         switch (event) {
475                 case B_IPO_DEPCHANGE:
476                 {
477                         /* rebuild depsgraph for the new deps */
478                         DAG_relations_tag_update(bmain);
479                         break;
480                 }
481         }
482         
483         /* default for now */
484         WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene); // XXX could use better notifier
485 }
486
487 /* callback to remove the active driver */
488 static void driver_remove_cb(bContext *C, void *ale_v, void *UNUSED(arg))
489 {
490         bAnimListElem *ale = (bAnimListElem *)ale_v;
491         ID *id = ale->id;
492         FCurve *fcu = ale->data;
493         ReportList *reports = CTX_wm_reports(C);
494         
495         /* try to get F-Curve that driver lives on, and ID block which has this AnimData */
496         if (ELEM(NULL, id, fcu))
497                 return;
498         
499         /* call API method to remove this driver  */
500         ANIM_remove_driver(reports, id, fcu->rna_path, fcu->array_index, 0);
501 }
502
503 /* callback to add a target variable to the active driver */
504 static void driver_add_var_cb(bContext *UNUSED(C), void *driver_v, void *UNUSED(arg))
505 {
506         ChannelDriver *driver = (ChannelDriver *)driver_v;
507         
508         /* add a new variable */
509         driver_add_new_variable(driver);
510 }
511
512 /* callback to remove target variable from active driver */
513 static void driver_delete_var_cb(bContext *UNUSED(C), void *driver_v, void *dvar_v)
514 {
515         ChannelDriver *driver = (ChannelDriver *)driver_v;
516         DriverVar *dvar = (DriverVar *)dvar_v;
517         
518         /* remove the active variable */
519         driver_free_variable_ex(driver, dvar);
520 }
521
522 /* callback to report why a driver variable is invalid */
523 static void driver_dvar_invalid_name_query_cb(bContext *C, void *dvar_v, void *UNUSED(arg))
524 {
525         uiPopupMenu *pup = UI_popup_menu_begin(C, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Invalid Variable Name"), ICON_NONE);
526         uiLayout *layout = UI_popup_menu_layout(pup);
527         
528         DriverVar *dvar = (DriverVar *)dvar_v;
529         
530         if (dvar->flag & DVAR_FLAG_INVALID_EMPTY) {
531                 uiItemL(layout, "It cannot be left blank", ICON_ERROR);
532         }
533         if (dvar->flag & DVAR_FLAG_INVALID_START_NUM) {
534                 uiItemL(layout, "It cannot start with a number", ICON_ERROR);
535         }
536         if (dvar->flag & DVAR_FLAG_INVALID_START_CHAR) {
537                 uiItemL(layout, 
538                         "It cannot start with a special character,"
539                         " including '$', '@', '!', '~', '+', '-', '_', '.', or ' '",
540                         ICON_NONE);
541         }
542         if (dvar->flag & DVAR_FLAG_INVALID_HAS_SPACE) {
543                 uiItemL(layout, "It cannot contain spaces (e.g. 'a space')", ICON_ERROR);
544         }
545         if (dvar->flag & DVAR_FLAG_INVALID_HAS_DOT) {
546                 uiItemL(layout, "It cannot contain dots (e.g. 'a.dot')", ICON_ERROR);
547         }
548         if (dvar->flag & DVAR_FLAG_INVALID_HAS_SPECIAL) {
549                 uiItemL(layout, "It cannot contain special (non-alphabetical/numeric) characters", ICON_ERROR);
550         }
551         if (dvar->flag & DVAR_FLAG_INVALID_PY_KEYWORD) {
552                 uiItemL(layout, "It cannot be a reserved keyword in Python", ICON_INFO);
553         }
554         
555         UI_popup_menu_end(C, pup);
556 }
557
558 /* callback to reset the driver's flags */
559 static void driver_update_flags_cb(bContext *UNUSED(C), void *fcu_v, void *UNUSED(arg))
560 {
561         FCurve *fcu = (FCurve *)fcu_v;
562         ChannelDriver *driver = fcu->driver;
563         
564         /* clear invalid flags */
565         fcu->flag &= ~FCURVE_DISABLED;
566         driver->flag &= ~DRIVER_FLAG_INVALID;
567 }
568
569 /* drivers panel poll */
570 static int graph_panel_drivers_poll(const bContext *C, PanelType *UNUSED(pt))
571 {
572         SpaceIpo *sipo = CTX_wm_space_graph(C);
573
574         if (sipo->mode != SIPO_MODE_DRIVERS)
575                 return 0;
576
577         return graph_panel_context(C, NULL, NULL);
578 }
579
580 /* settings for 'single property' driver variable type */
581 static void graph_panel_driverVar__singleProp(uiLayout *layout, ID *id, DriverVar *dvar)
582 {
583         DriverTarget *dtar = &dvar->targets[0];
584         PointerRNA dtar_ptr;
585         uiLayout *row, *col;
586         
587         /* initialize RNA pointer to the target */
588         RNA_pointer_create(id, &RNA_DriverTarget, dtar, &dtar_ptr); 
589         
590         /* Target ID */
591         row = uiLayoutRow(layout, false);
592         uiLayoutSetRedAlert(row, ((dtar->flag & DTAR_FLAG_INVALID) && !dtar->id));
593         uiTemplateAnyID(row, &dtar_ptr, "id", "id_type", IFACE_("Prop:"));
594         
595         /* Target Property */
596         if (dtar->id) {
597                 PointerRNA root_ptr;
598                 
599                 /* get pointer for resolving the property selected */
600                 RNA_id_pointer_create(dtar->id, &root_ptr);
601                 
602                 /* rna path */
603                 col = uiLayoutColumn(layout, true);
604                 uiLayoutSetRedAlert(col, (dtar->flag & DTAR_FLAG_INVALID));
605                 uiTemplatePathBuilder(col, &dtar_ptr, "data_path", &root_ptr, IFACE_("Path"));
606         }
607 }
608
609 /* settings for 'rotation difference' driver variable type */
610 /* FIXME: 1) Must be same armature for both dtars, 2) Alignment issues... */
611 static void graph_panel_driverVar__rotDiff(uiLayout *layout, ID *id, DriverVar *dvar)
612 {
613         DriverTarget *dtar = &dvar->targets[0];
614         DriverTarget *dtar2 = &dvar->targets[1];
615         Object *ob1 = (Object *)dtar->id;
616         Object *ob2 = (Object *)dtar2->id;
617         PointerRNA dtar_ptr, dtar2_ptr;
618         uiLayout *col;
619         
620         /* initialize RNA pointer to the target */
621         RNA_pointer_create(id, &RNA_DriverTarget, dtar, &dtar_ptr); 
622         RNA_pointer_create(id, &RNA_DriverTarget, dtar2, &dtar2_ptr); 
623         
624         /* Bone 1 */
625         col = uiLayoutColumn(layout, true);
626         uiLayoutSetRedAlert(col, (dtar->flag & DTAR_FLAG_INVALID)); /* XXX: per field... */
627         uiItemR(col, &dtar_ptr, "id", 0, IFACE_("Bone 1"), ICON_NONE);
628         
629         if (dtar->id && GS(dtar->id->name) == ID_OB && ob1->pose) {
630                 PointerRNA tar_ptr;
631                 
632                 RNA_pointer_create(dtar->id, &RNA_Pose, ob1->pose, &tar_ptr);
633                 uiItemPointerR(col, &dtar_ptr, "bone_target", &tar_ptr, "bones", "", ICON_BONE_DATA);
634         }
635         
636         col = uiLayoutColumn(layout, true);
637         uiLayoutSetRedAlert(col, (dtar2->flag & DTAR_FLAG_INVALID)); /* XXX: per field... */
638         uiItemR(col, &dtar2_ptr, "id", 0, IFACE_("Bone 2"), ICON_NONE);
639                 
640         if (dtar2->id && GS(dtar2->id->name) == ID_OB && ob2->pose) {
641                 PointerRNA tar_ptr;
642                 
643                 RNA_pointer_create(dtar2->id, &RNA_Pose, ob2->pose, &tar_ptr);
644                 uiItemPointerR(col, &dtar2_ptr, "bone_target", &tar_ptr, "bones", "", ICON_BONE_DATA);
645         }
646 }
647
648 /* settings for 'location difference' driver variable type */
649 static void graph_panel_driverVar__locDiff(uiLayout *layout, ID *id, DriverVar *dvar)
650 {
651         DriverTarget *dtar  = &dvar->targets[0];
652         DriverTarget *dtar2 = &dvar->targets[1];
653         Object *ob1 = (Object *)dtar->id;
654         Object *ob2 = (Object *)dtar2->id;
655         PointerRNA dtar_ptr, dtar2_ptr;
656         uiLayout *col;
657         
658         /* initialize RNA pointer to the target */
659         RNA_pointer_create(id, &RNA_DriverTarget, dtar,  &dtar_ptr); 
660         RNA_pointer_create(id, &RNA_DriverTarget, dtar2, &dtar2_ptr); 
661         
662         /* Bone 1 */
663         col = uiLayoutColumn(layout, true);
664         uiLayoutSetRedAlert(col, (dtar->flag & DTAR_FLAG_INVALID)); /* XXX: per field... */
665         uiItemR(col, &dtar_ptr, "id", 0, IFACE_("Object 1"), ICON_NONE);
666         
667         if (dtar->id && GS(dtar->id->name) == ID_OB && ob1->pose) {
668                 PointerRNA tar_ptr;
669                 
670                 RNA_pointer_create(dtar->id, &RNA_Pose, ob1->pose, &tar_ptr);
671                 uiItemPointerR(col, &dtar_ptr, "bone_target", &tar_ptr, "bones", IFACE_("Bone"), ICON_BONE_DATA);
672         }
673         
674         uiLayoutSetRedAlert(col, false); /* we can clear it again now - it's only needed when creating the ID/Bone fields */
675         uiItemR(col, &dtar_ptr, "transform_space", 0, NULL, ICON_NONE);
676         
677         col = uiLayoutColumn(layout, true);
678         uiLayoutSetRedAlert(col, (dtar2->flag & DTAR_FLAG_INVALID)); /* XXX: per field... */
679         uiItemR(col, &dtar2_ptr, "id", 0, IFACE_("Object 2"), ICON_NONE);
680         
681         if (dtar2->id && GS(dtar2->id->name) == ID_OB && ob2->pose) {
682                 PointerRNA tar_ptr;
683                 
684                 RNA_pointer_create(dtar2->id, &RNA_Pose, ob2->pose, &tar_ptr);
685                 uiItemPointerR(col, &dtar2_ptr, "bone_target", &tar_ptr, "bones", IFACE_("Bone"), ICON_BONE_DATA);
686         }
687                 
688         uiLayoutSetRedAlert(col, false); /* we can clear it again now - it's only needed when creating the ID/Bone fields */
689         uiItemR(col, &dtar2_ptr, "transform_space", 0, NULL, ICON_NONE);
690 }
691
692 /* settings for 'transform channel' driver variable type */
693 static void graph_panel_driverVar__transChan(uiLayout *layout, ID *id, DriverVar *dvar)
694 {
695         DriverTarget *dtar = &dvar->targets[0];
696         Object *ob = (Object *)dtar->id;
697         PointerRNA dtar_ptr;
698         uiLayout *col, *sub;
699         
700         /* initialize RNA pointer to the target */
701         RNA_pointer_create(id, &RNA_DriverTarget, dtar, &dtar_ptr); 
702         
703         /* properties */
704         col = uiLayoutColumn(layout, true);
705         uiLayoutSetRedAlert(col, (dtar->flag & DTAR_FLAG_INVALID)); /* XXX: per field... */
706         uiItemR(col, &dtar_ptr, "id", 0, IFACE_("Object"), ICON_NONE);
707         
708         if (dtar->id && GS(dtar->id->name) == ID_OB && ob->pose) {
709                 PointerRNA tar_ptr;
710                 
711                 RNA_pointer_create(dtar->id, &RNA_Pose, ob->pose, &tar_ptr);
712                 uiItemPointerR(col, &dtar_ptr, "bone_target", &tar_ptr, "bones", IFACE_("Bone"), ICON_BONE_DATA);
713         }
714                 
715         sub = uiLayoutColumn(layout, true);
716         uiItemR(sub, &dtar_ptr, "transform_type", 0, NULL, ICON_NONE);
717         uiItemR(sub, &dtar_ptr, "transform_space", 0, IFACE_("Space"), ICON_NONE);
718 }
719
720 /* driver settings for active F-Curve (only for 'Drivers' mode) */
721 static void graph_panel_drivers(const bContext *C, Panel *pa)
722 {
723         bAnimListElem *ale;
724         FCurve *fcu;
725         ChannelDriver *driver;
726         DriverVar *dvar;
727         
728         PointerRNA driver_ptr;
729         uiLayout *col;
730         uiBlock *block;
731         uiBut *but;
732         
733         /* Get settings from context */
734         if (!graph_panel_context(C, &ale, &fcu))
735                 return;
736         driver = fcu->driver;
737         
738         /* set event handler for panel */
739         block = uiLayoutGetBlock(pa->layout); // xxx?
740         UI_block_func_handle_set(block, do_graph_region_driver_buttons, NULL);
741         
742         /* general actions - management */
743         col = uiLayoutColumn(pa->layout, false);
744         block = uiLayoutGetBlock(col);
745         but = uiDefIconTextBut(block, UI_BTYPE_BUT, B_IPO_DEPCHANGE, ICON_FILE_REFRESH, IFACE_("Update Dependencies"),
746                        0, 0, 10 * UI_UNIT_X, UI_UNIT_Y,
747                        NULL, 0.0, 0.0, 0, 0,
748                        TIP_("Force updates of dependencies"));
749         UI_but_func_set(but, driver_update_flags_cb, fcu, NULL);
750
751         but = uiDefIconTextBut(block, UI_BTYPE_BUT, B_IPO_DEPCHANGE, ICON_ZOOMOUT, IFACE_("Remove Driver"),
752                        0, 0, 10 * UI_UNIT_X, UI_UNIT_Y,
753                        NULL, 0.0, 0.0, 0, 0,
754                        TIP_("Remove this driver"));
755         UI_but_funcN_set(but, driver_remove_cb, MEM_dupallocN(ale), NULL);
756                 
757         /* driver-level settings - type, expressions, and errors */
758         RNA_pointer_create(ale->id, &RNA_Driver, driver, &driver_ptr);
759         
760         col = uiLayoutColumn(pa->layout, true);
761         block = uiLayoutGetBlock(col);
762         uiItemR(col, &driver_ptr, "type", 0, NULL, ICON_NONE);
763
764         /* show expression box if doing scripted drivers, and/or error messages when invalid drivers exist */
765         if (driver->type == DRIVER_TYPE_PYTHON) {
766                 bool bpy_data_expr_error = (strstr(driver->expression, "bpy.data.") != NULL);
767                 bool bpy_ctx_expr_error  = (strstr(driver->expression, "bpy.context.") != NULL);
768                 
769                 /* expression */
770                 uiItemR(col, &driver_ptr, "expression", 0, IFACE_("Expr"), ICON_NONE);
771                 
772                 /* errors? */
773                 if ((G.f & G_SCRIPT_AUTOEXEC) == 0) {
774                         uiItemL(col, IFACE_("ERROR: Python auto-execution disabled"), ICON_CANCEL);
775                 }
776                 else if (driver->flag & DRIVER_FLAG_INVALID) {
777                         uiItemL(col, IFACE_("ERROR: Invalid Python expression"), ICON_CANCEL);
778                 }
779                 
780                 /* Explicit bpy-references are evil. Warn about these to prevent errors */
781                 /* TODO: put these in a box? */
782                 if (bpy_data_expr_error || bpy_ctx_expr_error) {
783                         uiItemL(col, IFACE_("WARNING: Driver expression may not work correctly"), ICON_HELP);
784                         
785                         if (bpy_data_expr_error) {
786                                 uiItemL(col, IFACE_("TIP: Use variables instead of bpy.data paths (see below)"), ICON_ERROR);
787                         }
788                         if (bpy_ctx_expr_error) {
789                                 uiItemL(col, IFACE_("TIP: bpy.context is not safe for renderfarm usage"), ICON_ERROR);
790                         }
791                 }
792         }
793         else {
794                 /* errors? */
795                 if (driver->flag & DRIVER_FLAG_INVALID)
796                         uiItemL(col, IFACE_("ERROR: Invalid target channel(s)"), ICON_ERROR);
797                         
798                 /* Warnings about a lack of variables
799                  * NOTE: The lack of variables is generally a bad thing, since it indicates
800                  *       that the driver doesn't work at all. This particular scenario arises
801                  *       primarily when users mistakenly try to use drivers for procedural
802                  *       property animation
803                  */
804                 if (BLI_listbase_is_empty(&driver->variables)) {
805                         uiItemL(col, IFACE_("ERROR: Driver is useless without any inputs"), ICON_ERROR);
806                         
807                         if (!BLI_listbase_is_empty(&fcu->modifiers)) {
808                                 uiItemL(col, IFACE_("TIP: Use F-Curves for procedural animation instead"), ICON_INFO);
809                                 uiItemL(col, IFACE_("F-Modifiers can generate curves for those too"), ICON_INFO);
810                         }
811                 }
812         }
813                 
814         col = uiLayoutColumn(pa->layout, true);
815
816         if (driver->type == DRIVER_TYPE_PYTHON) {
817                 uiItemR(col, &driver_ptr, "use_self", 0, NULL, ICON_NONE);
818         }
819
820         /* debug setting */
821         uiItemR(col, &driver_ptr, "show_debug_info", 0, NULL, ICON_NONE);
822                 
823         /* value of driver */
824         if (driver->flag & DRIVER_FLAG_SHOWDEBUG) {
825                 uiLayout *row = uiLayoutRow(col, true);
826                 char valBuf[32];
827                         
828                 uiItemL(row, IFACE_("Driver Value:"), ICON_NONE);
829                         
830                 BLI_snprintf(valBuf, sizeof(valBuf), "%.3f", driver->curval);
831                 uiItemL(row, valBuf, ICON_NONE);
832         }
833         
834         /* add/copy/paste driver variables */
835         {
836                 uiLayout *row;
837                 
838                 /* add driver variable */
839                 row = uiLayoutRow(pa->layout, false);
840                 block = uiLayoutGetBlock(row);
841                 but = uiDefIconTextBut(block, UI_BTYPE_BUT, B_IPO_DEPCHANGE, ICON_ZOOMIN, IFACE_("Add Variable"),
842                                    0, 0, 10 * UI_UNIT_X, UI_UNIT_Y,
843                                    NULL, 0.0, 0.0, 0, 0,
844                                    TIP_("Driver variables ensure that all dependencies will be accounted for and that drivers will update correctly"));
845                 UI_but_func_set(but, driver_add_var_cb, driver, NULL);
846                 
847                 /* copy/paste (as sub-row) */
848                 row = uiLayoutRow(row, true);
849                 block = uiLayoutGetBlock(row);
850                 
851                 uiItemO(row, "", ICON_COPYDOWN, "GRAPH_OT_driver_variables_copy");
852                 uiItemO(row, "", ICON_PASTEDOWN, "GRAPH_OT_driver_variables_paste");
853         }
854         
855         /* loop over targets, drawing them */
856         for (dvar = driver->variables.first; dvar; dvar = dvar->next) {
857                 PointerRNA dvar_ptr;
858                 uiLayout *box, *row;
859                 uiLayout *subrow, *sub;
860                 
861                 /* sub-layout column for this variable's settings */
862                 col = uiLayoutColumn(pa->layout, true);
863                 
864                 /* 1) header panel */
865                 box = uiLayoutBox(col);
866                 RNA_pointer_create(ale->id, &RNA_DriverVariable, dvar, &dvar_ptr);
867                 
868                 row = uiLayoutRow(box, false);
869                 block = uiLayoutGetBlock(row);
870                 
871                 /* 1.1) variable type and name */
872                 subrow = uiLayoutRow(row, true);
873                 
874                 /* 1.1.1) variable type */
875                 sub = uiLayoutRow(subrow, true);                     /* HACK: special group just for the enum, otherwise we */
876                 uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT);  /*       we get ugly layout with text included too...  */
877                 
878                 uiItemR(sub, &dvar_ptr, "type", UI_ITEM_R_ICON_ONLY, "", ICON_NONE);
879                 
880                 /* 1.1.2) variable name */
881                 sub = uiLayoutRow(subrow, true);                       /* HACK: special group to counteract the effects of the previous */
882                 uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_EXPAND);  /*       enum, which now pushes everything too far right         */
883                 
884                 uiItemR(sub, &dvar_ptr, "name", 0, "", ICON_NONE);
885                 
886                 /* 1.2) invalid name? */
887                 UI_block_emboss_set(block, UI_EMBOSS_NONE);
888                 
889                 if (dvar->flag & DVAR_FLAG_INVALID_NAME) {
890                         but = uiDefIconBut(block, UI_BTYPE_BUT, B_IPO_DEPCHANGE, ICON_ERROR, 290, 0, UI_UNIT_X, UI_UNIT_Y,
891                                            NULL, 0.0, 0.0, 0.0, 0.0, IFACE_("Invalid variable name, click here for details"));
892                         UI_but_func_set(but, driver_dvar_invalid_name_query_cb, dvar, NULL); // XXX: reports?
893                 }
894                 
895                 /* 1.3) remove button */
896                 but = uiDefIconBut(block, UI_BTYPE_BUT, B_IPO_DEPCHANGE, ICON_X, 290, 0, UI_UNIT_X, UI_UNIT_Y,
897                                    NULL, 0.0, 0.0, 0.0, 0.0, IFACE_("Delete target variable"));
898                 UI_but_func_set(but, driver_delete_var_cb, driver, dvar);
899                 UI_block_emboss_set(block, UI_EMBOSS);
900                 
901                 
902                 /* 2) variable type settings */
903                 box = uiLayoutBox(col);
904                 /* controls to draw depends on the type of variable */
905                 switch (dvar->type) {
906                         case DVAR_TYPE_SINGLE_PROP:     /* single property */
907                                 graph_panel_driverVar__singleProp(box, ale->id, dvar);
908                                 break;
909                         case DVAR_TYPE_ROT_DIFF:     /* rotational difference */
910                                 graph_panel_driverVar__rotDiff(box, ale->id, dvar);
911                                 break;
912                         case DVAR_TYPE_LOC_DIFF:     /* location difference */
913                                 graph_panel_driverVar__locDiff(box, ale->id, dvar);
914                                 break;
915                         case DVAR_TYPE_TRANSFORM_CHAN:     /* transform channel */
916                                 graph_panel_driverVar__transChan(box, ale->id, dvar);
917                                 break;
918                 }
919                 
920                 /* 3) value of variable */
921                 if (driver->flag & DRIVER_FLAG_SHOWDEBUG) {
922                         char valBuf[32];
923                         
924                         box = uiLayoutBox(col);
925                         row = uiLayoutRow(box, true);
926                         uiItemL(row, IFACE_("Value:"), ICON_NONE);
927                         
928                         if ((dvar->type == DVAR_TYPE_ROT_DIFF) ||
929                             (dvar->type == DVAR_TYPE_TRANSFORM_CHAN &&
930                              dvar->targets[0].transChan >= DTAR_TRANSCHAN_ROTX &&
931                              dvar->targets[0].transChan < DTAR_TRANSCHAN_SCALEX))
932                         {
933                                 BLI_snprintf(valBuf, sizeof(valBuf), "%.3f (%4.1f°)", dvar->curval, RAD2DEGF(dvar->curval));
934                         }
935                         else {
936                                 BLI_snprintf(valBuf, sizeof(valBuf), "%.3f", dvar->curval);
937                         }
938                         
939                         uiItemL(row, valBuf, ICON_NONE);
940                 }
941         }
942         
943         /* cleanup */
944         MEM_freeN(ale);
945 }
946
947 /* ******************* F-Modifiers ******************************** */
948 /* All the drawing code is in editors/animation/fmodifier_ui.c */
949
950 #define B_FMODIFIER_REDRAW      20
951
952 static void do_graph_region_modifier_buttons(bContext *C, void *UNUSED(arg), int event)
953 {
954         switch (event) {
955                 case B_FMODIFIER_REDRAW: // XXX this should send depsgraph updates too
956                         WM_event_add_notifier(C, NC_ANIMATION, NULL); // XXX need a notifier specially for F-Modifiers
957                         break;
958         }
959 }
960
961 static void graph_panel_modifiers(const bContext *C, Panel *pa) 
962 {
963         bAnimListElem *ale;
964         FCurve *fcu;
965         FModifier *fcm;
966         uiLayout *col, *row;
967         uiBlock *block;
968         bool active;
969         
970         if (!graph_panel_context(C, &ale, &fcu))
971                 return;
972         
973         block = uiLayoutGetBlock(pa->layout);
974         UI_block_func_handle_set(block, do_graph_region_modifier_buttons, NULL);
975         
976         /* 'add modifier' button at top of panel */
977         {
978                 row = uiLayoutRow(pa->layout, false);
979                 
980                 /* this is an operator button which calls a 'add modifier' operator... 
981                  * a menu might be nicer but would be tricky as we need some custom filtering
982                  */
983                 uiItemMenuEnumO(row, (bContext *)C, "GRAPH_OT_fmodifier_add", "type", IFACE_("Add Modifier"), ICON_NONE);
984                 
985                 /* copy/paste (as sub-row) */
986                 row = uiLayoutRow(row, true);
987                 uiItemO(row, "", ICON_COPYDOWN, "GRAPH_OT_fmodifier_copy");
988                 uiItemO(row, "", ICON_PASTEDOWN, "GRAPH_OT_fmodifier_paste");
989         }
990         
991         active = !(fcu->flag & FCURVE_MOD_OFF);
992         /* draw each modifier */
993         for (fcm = fcu->modifiers.first; fcm; fcm = fcm->next) {
994                 col = uiLayoutColumn(pa->layout, true);
995                 uiLayoutSetActive(col, active);
996                 
997                 ANIM_uiTemplate_fmodifier_draw(col, ale->id, &fcu->modifiers, fcm);
998         }
999         
1000         MEM_freeN(ale);
1001 }
1002
1003 /* ******************* general ******************************** */
1004
1005 void graph_buttons_register(ARegionType *art)
1006 {
1007         PanelType *pt;
1008
1009         pt = MEM_callocN(sizeof(PanelType), "spacetype graph panel properties");
1010         strcpy(pt->idname, "GRAPH_PT_properties");
1011         strcpy(pt->label, N_("Active F-Curve"));
1012         strcpy(pt->category, "F-Curve");
1013         strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
1014         pt->draw = graph_panel_properties;
1015         pt->poll = graph_panel_poll;
1016         BLI_addtail(&art->paneltypes, pt);
1017         
1018         pt = MEM_callocN(sizeof(PanelType), "spacetype graph panel properties");
1019         strcpy(pt->idname, "GRAPH_PT_key_properties");
1020         strcpy(pt->label, N_("Active Keyframe"));
1021         strcpy(pt->category, "F-Curve");
1022         strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
1023         pt->draw = graph_panel_key_properties;
1024         pt->poll = graph_panel_poll;
1025         BLI_addtail(&art->paneltypes, pt);
1026
1027
1028         pt = MEM_callocN(sizeof(PanelType), "spacetype graph panel drivers");
1029         strcpy(pt->idname, "GRAPH_PT_drivers");
1030         strcpy(pt->label, N_("Drivers"));
1031         strcpy(pt->category, "Drivers");
1032         strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
1033         pt->draw = graph_panel_drivers;
1034         pt->poll = graph_panel_drivers_poll;
1035         BLI_addtail(&art->paneltypes, pt);
1036
1037         pt = MEM_callocN(sizeof(PanelType), "spacetype graph panel modifiers");
1038         strcpy(pt->idname, "GRAPH_PT_modifiers");
1039         strcpy(pt->label, N_("Modifiers"));
1040         strcpy(pt->category, "Modifiers");
1041         strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
1042         pt->draw = graph_panel_modifiers;
1043         pt->poll = graph_panel_poll;
1044         BLI_addtail(&art->paneltypes, pt);
1045         
1046         pt = MEM_callocN(sizeof(PanelType), "spacetype graph panel view");
1047         strcpy(pt->idname, "GRAPH_PT_view");
1048         strcpy(pt->label, N_("View Properties"));
1049         strcpy(pt->category, "View");
1050         strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
1051         pt->draw = graph_panel_view;
1052         BLI_addtail(&art->paneltypes, pt);
1053 }
1054
1055 static int graph_properties_toggle_exec(bContext *C, wmOperator *UNUSED(op))
1056 {
1057         ScrArea *sa = CTX_wm_area(C);
1058         ARegion *ar = graph_has_buttons_region(sa);
1059         
1060         if (ar)
1061                 ED_region_toggle_hidden(C, ar);
1062
1063         return OPERATOR_FINISHED;
1064 }
1065
1066 void GRAPH_OT_properties(wmOperatorType *ot)
1067 {
1068         ot->name = "Properties";
1069         ot->idname = "GRAPH_OT_properties";
1070         ot->description = "Toggle display properties panel";
1071         
1072         ot->exec = graph_properties_toggle_exec;
1073         ot->poll = ED_operator_graphedit_active;
1074
1075         /* flags */
1076         ot->flag = 0;
1077 }