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