text: /*
   * This program is free software; you can redistribute it and/or
   * modify it under the terms of the GNU General Public License
   * as published by the Free Software Foundation; either version 2
   * of the License, or (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software Foundation,
   * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
   *
   * The Original Code is Copyright (C) 2009 Blender Foundation.
   * All rights reserved.
   */
  
  /** \file
   * \ingroup spgraph
   */
  
  #include <float.h>
  #include <math.h>
  #include <stdio.h>
  #include <string.h>
  
  #include "DNA_anim_types.h"
  #include "DNA_object_types.h"
  #include "DNA_scene_types.h"
  
  #include "MEM_guardedalloc.h"
  
  #include "BLI_blenlib.h"
  #include "BLI_math.h"
  #include "BLI_utildefines.h"
  
  #include "BLT_translation.h"
  
  #include "BKE_anim_data.h"
  #include "BKE_context.h"
  #include "BKE_curve.h"
  #include "BKE_fcurve.h"
  #include "BKE_global.h"
  #include "BKE_main.h"
  #include "BKE_screen.h"
  #include "BKE_unit.h"
  
  #include "DEG_depsgraph.h"
  #include "DEG_depsgraph_build.h"
  
  #include "WM_api.h"
  #include "WM_types.h"
  
  #include "RNA_access.h"
  
  #include "ED_anim_api.h"
  #include "ED_keyframing.h"
  #include "ED_screen.h"
  #include "ED_undo.h"
  
  #include "UI_interface.h"
  #include "UI_resources.h"
  
  #include "graph_intern.h"  // own include
  
  /* ******************* graph editor space & buttons ************** */
  
  #define B_REDR 1
  
  /* -------------- */
  
  static int graph_panel_context(const bContext *C, bAnimListElem **ale, FCurve **fcu)
  {
    bAnimContext ac;
    bAnimListElem *elem = NULL;
  
    /* For now, only draw if we could init the anim-context info
     * (necessary for all animation-related tools)
     * to work correctly is able to be correctly retrieved.
     * There's no point showing empty panels?
     */
    if (ANIM_animdata_get_context(C, &ac) == 0) {
      return 0;
    }
  
    /* try to find 'active' F-Curve */
    elem = get_active_fcurve_channel(&ac);
    if (elem == NULL) {
      return 0;
    }
  
    if (fcu) {
      *fcu = (FCurve *)elem->data;
    }
    if (ale) {
      *ale = elem;
    }
    else {
      MEM_freeN(elem);
    }
  
    return 1;
  }
  
  static bool graph_panel_poll(const bContext *C, PanelType *UNUSED(pt))
  {
    return graph_panel_context(C, NULL, NULL);
  }
  
  /* -------------- */
  
  static void graph_panel_cursor_header(const bContext *C, Panel *panel)
  {
    bScreen *screen = CTX_wm_screen(C);
    SpaceGraph *sipo = CTX_wm_space_graph(C);
    Scene *scene = CTX_data_scene(C);
    PointerRNA spaceptr, sceneptr;
    uiLayout *col;
  
    /* get RNA pointers for use when creating the UI elements */
    RNA_id_pointer_create(&scene->id, &sceneptr);
    RNA_pointer_create(&screen->id, &RNA_SpaceGraphEditor, sipo, &spaceptr);
  
    /* 2D-Cursor */
    col = uiLayoutColumn(panel->layout, false);
    uiItemR(col, &spaceptr, "show_cursor", 0, "", ICON_NONE);
  }
  
  static void graph_panel_cursor(const bContext *C, Panel *panel)
  {
    bScreen *screen = CTX_wm_screen(C);
    SpaceGraph *sipo = CTX_wm_space_graph(C);
    Scene *scene = CTX_data_scene(C);
    PointerRNA spaceptr, sceneptr;
    uiLayout *layout = panel->layout;
    uiLayout *col, *sub;
  
    /* get RNA pointers for use when creating the UI elements */
    RNA_id_pointer_create(&scene->id, &sceneptr);
    RNA_pointer_create(&screen->id, &RNA_SpaceGraphEditor, sipo, &spaceptr);
  
    uiLayoutSetPropSep(layout, true);
    uiLayoutSetPropDecorate(layout, false);
  
    /* 2D-Cursor */
    col = uiLayoutColumn(layout, false);
    uiLayoutSetActive(col, RNA_boolean_get(&spaceptr, "show_cursor"));
  
    sub = uiLayoutColumn(col, true);
    if (sipo->mode == SIPO_MODE_DRIVERS) {
      uiItemR(sub, &spaceptr, "cursor_position_x", 0, IFACE_("Cursor X"), ICON_NONE);
    }
    else {
      uiItemR(sub, &sceneptr, "frame_current", 0, IFACE_("Cursor X"), ICON_NONE);
    }
  
    uiItemR(sub, &spaceptr, "cursor_position_y", 0, IFACE_("Y"), ICON_NONE);
  
    sub = uiLayoutColumn(col, true);
    uiItemO(sub, IFACE_("Cursor to Selection"), ICON_NONE, "GRAPH_OT_frame_jump");
  }
  
  /* ******************* active F-Curve ************** */
  
  static void graph_panel_properties(const bContext *C, Panel *panel)
  {
    bAnimListElem *ale;
    FCurve *fcu;
    PointerRNA fcu_ptr;
    uiLayout *layout = panel->layout;
    uiLayout *col;
    char name[256];
    int icon = 0;
  
    if (!graph_panel_context(C, &ale, &fcu)) {
      return;
    }
  
    /* F-Curve pointer */
    RNA_pointer_create(ale->id, &RNA_FCurve, fcu, &fcu_ptr);
  
    /* user-friendly 'name' for F-Curve */
    col = uiLayoutColumn(layout, false);
    if (ale->type == ANIMTYPE_FCURVE) {
      /* get user-friendly name for F-Curve */
      icon = getname_anim_fcurve(name, ale->id, fcu);
    }
    else {
      /* NLA Control Curve, etc. */
      const bAnimChannelType *acf = ANIM_channel_get_typeinfo(ale);
  
      /* get name */
      if (acf && acf->name) {
        acf->name(ale, name);
      }
      else {
        strcpy(name, IFACE_("<invalid>"));
        icon = ICON_ERROR;
      }
  
      /* icon */
      if (ale->type == ANIMTYPE_NLACURVE) {
        icon = ICON_NLA;
      }
    }
    uiItemL(col, name, icon);
  
    uiLayoutSetPropSep(layout, true);
    uiLayoutSetPropDecorate(layout, false);
  
    /* RNA-Path Editing - only really should be enabled when things aren't working */
    col = uiLayoutColumn(layout, false);
    uiLayoutSetEnabled(col, (fcu->flag & FCURVE_DISABLED) != 0);
    uiItemR(col, &fcu_ptr, "data_path", 0, "", ICON_RNA);
    uiItemR(col, &fcu_ptr, "array_index", 0, NULL, ICON_NONE);
  
    /* color settings */
    col = uiLayoutColumn(layout, true);
    uiItemR(col, &fcu_ptr, "color_mode", 0, "Display Color", ICON_NONE);
  
    if (fcu->color_mode == FCURVE_COLOR_CUSTOM) {
      uiItemR(col, &fcu_ptr, "color", 0, "Color", ICON_NONE);
    }
  
    /* smoothing setting */
    col = uiLayoutColumn(layout, true);
    uiItemR(col, &fcu_ptr, "auto_smoothing", 0, "Handle Smoothing", ICON_NONE);
  
    MEM_freeN(ale);
  }
  
  /* ******************* active Keyframe ************** */
  
  /* get 'active' keyframe for panel editing */
  static short get_active_fcurve_keyframe_edit(FCurve *fcu, BezTriple **bezt, BezTriple **prevbezt)
  {
    BezTriple *b;
    int i;
  
    /* zero the pointers */
    *bezt = *prevbezt = NULL;
  
    /* sanity checks */
    if ((fcu->bezt == NULL) || (fcu->totvert == 0)) {
      return 0;
    }
  
    /* find first selected keyframe for now, and call it the active one
     * - this is a reasonable assumption, given that whenever anyone
     *   wants to edit numerically, there is likely to only be 1 vert selected
     */
    for (i = 0, b = fcu->bezt; i < fcu->totvert; i++, b++) {
      if (BEZT_ISSEL_ANY(b)) {
        /* found
         * - 'previous' is either the one before, of the keyframe itself (which is still fine)
         *   XXX: we can just make this null instead if needed
         */
        *prevbezt = (i > 0) ? b - 1 : b;
        *bezt = b;
  
        return 1;
      }
    }
  
    /* not found */
    return 0;
  }
  
  /* update callback for active keyframe properties - base updates stuff */
  static void graphedit_activekey_update_cb(bContext *UNUSED(C),
                                            void *fcu_ptr,
                                            void *UNUSED(bezt_ptr))
  {
    FCurve *fcu = (FCurve *)fcu_ptr;
  
    /* make sure F-Curve and its handles are still valid after this editing */
    sort_time_fcurve(fcu);
    calchandles_fcurve(fcu);
  }
  
  /* update callback for active keyframe properties - handle-editing wrapper */
  static void graphedit_activekey_handles_cb(bContext *C, void *fcu_ptr, void *bezt_ptr)
  {
    BezTriple *bezt = (BezTriple *)bezt_ptr;
  
    /* since editing the handles, make sure they're set to types which are receptive to editing
     * see transform_conversions.c :: createTransGraphEditData(), last step in second loop
     */
    if (ELEM(bezt->h1, HD_AUTO, HD_AUTO_ANIM) && ELEM(bezt->h2, HD_AUTO, HD_AUTO_ANIM)) {
      /* by changing to aligned handles, these can now be moved... */
      bezt->h1 = HD_ALIGN;
      bezt->h2 = HD_ALIGN;
    }
    else {
      BKE_nurb_bezt_handle_test(bezt, SELECT, true, false);
    }
  
    /* now call standard updates */
    graphedit_activekey_update_cb(C, fcu_ptr, bezt_ptr);
  }
  
  /* update callback for editing coordinates of right handle in active keyframe properties
   * NOTE: we cannot just do graphedit_activekey_handles_cb() due to "order of computation"
   *       weirdness (see calchandleNurb_intern() and T39911)
   */
  static void graphedit_activekey_left_handle_coord_cb(bContext *C, void *fcu_ptr, void *bezt_ptr)
  {
    BezTriple *bezt = (BezTriple *)bezt_ptr;
  
    const char f1 = bezt->f1;
    const char f3 = bezt->f3;
  
    bezt->f1 |= SELECT;
    bezt->f3 &= ~SELECT;
  
    /* perform normal updates NOW */
    graphedit_activekey_handles_cb(C, fcu_ptr, bezt_ptr);
  
    /* restore selection state so that no-one notices this hack */
    bezt->f1 = f1;
    bezt->f3 = f3;
  }
  
  static void graphedit_activekey_right_handle_coord_cb(bContext *C, void *fcu_ptr, void *bezt_ptr)
  {
    BezTriple *bezt = (BezTriple *)bezt_ptr;
  
    /* original state of handle selection - to be restored after performing the recalculation */
    const char f1 = bezt->f1;
    const char f3 = bezt->f3;
  
    /* temporarily make it so that only the right handle is selected, so that updates go correctly
     * (i.e. it now acts as if we've just transforming the vert when it is selected by itself)
     */
    bezt->f1 &= ~SELECT;
    bezt->f3 |= SELECT;
  
    /* perform normal updates NOW */
    graphedit_activekey_handles_cb(C, fcu_ptr, bezt_ptr);
  
    /* restore selection state so that no-one notices this hack */
    bezt->f1 = f1;
    bezt->f3 = f3;
  }
  
  static void graph_panel_key_properties(const bContext *C, Panel *panel)
  {
    bAnimListElem *ale;
    FCurve *fcu;
    BezTriple *bezt, *prevbezt;
  
    uiLayout *layout = panel->layout;
    const ARegion *region = CTX_wm_region(C);
    /* Just a width big enough so buttons use entire layout width (will be clamped by it then). */
    const int but_max_width = region->winx;
    uiLayout *col;
    uiBlock *block;
  
    if (!graph_panel_context(C, &ale, &fcu)) {
      return;
    }
  
    block = uiLayoutGetBlock(layout);
    /* UI_block_func_handle_set(block, do_graph_region_buttons, NULL); */
    uiLayoutSetPropSep(layout, true);
    uiLayoutSetPropDecorate(layout, false);
  
    /* only show this info if there are keyframes to edit */
    if (get_active_fcurve_keyframe_edit(fcu, &bezt, &prevbezt)) {
      PointerRNA bezt_ptr, id_ptr, fcu_prop_ptr;
      PropertyRNA *fcu_prop = NULL;
      uiBut *but;
      int unit = B_UNIT_NONE;
  
      /* RNA pointer to keyframe, to allow editing */
      RNA_pointer_create(ale->id, &RNA_Keyframe, bezt, &bezt_ptr);
  
      /* get property that F-Curve affects, for some unit-conversion magic */
      RNA_id_pointer_create(ale->id, &id_ptr);
      if (RNA_path_resolve_property(&id_ptr, fcu->rna_path, &fcu_prop_ptr, &fcu_prop)) {
        /* determine the unit for this property */
        unit = RNA_SUBTYPE_UNIT(RNA_property_subtype(fcu_prop));
      }
  
      /* interpolation */
      col = uiLayoutColumn(layout, false);
      if (fcu->flag & FCURVE_DISCRETE_VALUES) {
        uiLayout *split = uiLayoutSplit(col, 0.33f, true);
        uiItemL(split, IFACE_("Interpolation:"), ICON_NONE);
        uiItemL(split, IFACE_("None for Enum/Boolean"), ICON_IPO_CONSTANT);
      }
      else {
        uiItemR(col, &bezt_ptr, "interpolation", 0, NULL, ICON_NONE);
      }
  
      /* easing type */
      if (bezt->ipo > BEZT_IPO_BEZ) {
        uiItemR(col, &bezt_ptr, "easing", 0, NULL, 0);
      }
  
      /* easing extra */
      switch (bezt->ipo) {
        case BEZT_IPO_BACK:
          col = uiLayoutColumn(layout, 1);
          uiItemR(col, &bezt_ptr, "back", 0, NULL, 0);
          break;
        case BEZT_IPO_ELASTIC:
          col = uiLayoutColumn(layout, 1);
          uiItemR(col, &bezt_ptr, "amplitude", 0, NULL, 0);
          uiItemR(col, &bezt_ptr, "period", 0, NULL, 0);
          break;
        default:
          break;
      }
  
      /* numerical coordinate editing
       * - we use the button-versions of the calls so that we can attach special update handlers
       *   and unit conversion magic that cannot be achieved using a purely RNA-approach
       */
      col = uiLayoutColumn(layout, true);
      /* keyframe itself */
      {
  
        uiItemL_respect_property_split(col, IFACE_("Key Value"), ICON_NONE);
        but = uiDefButR(block,
                        UI_BTYPE_NUM,
                        B_REDR,
                        "",
                        0,
                        0,
                        but_max_width,
                        UI_UNIT_Y,
                        &bezt_ptr,
                        "co",
                        1,
                        0,
                        0,
                        -1,
                        -1,
                        NULL);
        UI_but_func_set(but, graphedit_activekey_update_cb, fcu, bezt);
        UI_but_unit_type_set(but, unit);
  
        uiItemL_respect_property_split(col, IFACE_("Frame"), ICON_NONE);
        but = uiDefButR(block,
                        UI_BTYPE_NUM,
                        B_REDR,
                        "",
                        0,
                        0,
                        but_max_width,
                        UI_UNIT_Y,
                        &bezt_ptr,
                        "co",
                        0,
                        0,
                        0,
                        -1,
                        -1,
                        NULL);
        UI_but_func_set(but, graphedit_activekey_update_cb, fcu, bezt);
      }
  
      /* previous handle - only if previous was Bezier interpolation */
      if ((prevbezt) && (prevbezt->ipo == BEZT_IPO_BEZ)) {
  
        col = uiLayoutColumn(layout, true);
  
        uiItemL_respect_property_split(col, IFACE_("Left Handle X"), ICON_NONE);
        but = uiDefButR(block,
                        UI_BTYPE_NUM,
                        B_REDR,
                        "",
                        0,
                        0,
                        but_max_width,
                        UI_UNIT_Y,
                        &bezt_ptr,
                        "handle_left",
                        0,
                        0,
                        0,
                        -1,
                        -1,
                        NULL);
        UI_but_func_set(but, graphedit_activekey_left_handle_coord_cb, fcu, bezt);
  
        uiItemL_respect_property_split(col, IFACE_("Y"), ICON_NONE);
        but = uiDefButR(block,
                        UI_BTYPE_NUM,
                        B_REDR,
                        "",
                        0,
                        0,
                        but_max_width,
                        UI_UNIT_Y,
                        &bezt_ptr,
                        "handle_left",
                        1,
                        0,
                        0,
                        -1,
                        -1,
                        NULL);
        UI_but_func_set(but, graphedit_activekey_left_handle_coord_cb, fcu, bezt);
        UI_but_unit_type_set(but, unit);
  
        uiItemL_respect_property_split(col, IFACE_("Type"), ICON_NONE);
        but = uiDefButR(block,
                        UI_BTYPE_MENU,
                        B_REDR,
                        NULL,
                        0,
                        0,
                        but_max_width,
                        UI_UNIT_Y,
                        &bezt_ptr,
                        "handle_left_type",
                        0,
                        0,
                        0,
                        -1,
                        -1,
                        "Type of left handle");
        UI_but_func_set(but, graphedit_activekey_handles_cb, fcu, bezt);
      }
  
      /* next handle - only if current is Bezier interpolation */
      if (bezt->ipo == BEZT_IPO_BEZ) {
        /* NOTE: special update callbacks are needed on the coords here due to T39911 */
  
        col = uiLayoutColumn(layout, true);
        uiItemL_respect_property_split(col, IFACE_("Right Handle X"), ICON_NONE);
        but = uiDefButR(block,
                        UI_BTYPE_NUM,
                        B_REDR,
                        "",
                        0,
                        0,
                        but_max_width,
                        UI_UNIT_Y,
                        &bezt_ptr,
                        "handle_right",
                        0,
                        0,
                        0,
                        -1,
                        -1,
                        NULL);
        UI_but_func_set(but, graphedit_activekey_right_handle_coord_cb, fcu, bezt);
  
        uiItemL_respect_property_split(col, IFACE_("Y"), ICON_NONE);
        but = uiDefButR(block,
                        UI_BTYPE_NUM,
                        B_REDR,
                        "",
                        0,
                        0,
                        but_max_width,
                        UI_UNIT_Y,
                        &bezt_ptr,
                        "handle_right",
                        1,
                        0,
                        0,
                        -1,
                        -1,
                        NULL);
        UI_but_func_set(but, graphedit_activekey_right_handle_coord_cb, fcu, bezt);
        UI_but_unit_type_set(but, unit);
  
        uiItemL_respect_property_split(col, IFACE_("Type"), ICON_NONE);
        but = uiDefButR(block,
                        UI_BTYPE_MENU,
                        B_REDR,
                        NULL,
                        0,
                        0,
                        but_max_width,
                        UI_UNIT_Y,
                        &bezt_ptr,
                        "handle_right_type",
                        0,
                        0,
                        0,
                        -1,
                        -1,
                        "Type of right handle");
        UI_but_func_set(but, graphedit_activekey_handles_cb, fcu, bezt);
      }
    }
    else {
      if ((fcu->bezt == NULL) && (fcu->modifiers.first)) {
        /* modifiers only - so no keyframes to be active */
        uiItemL(layout, IFACE_("F-Curve only has F-Modifiers"), ICON_NONE);
        uiItemL(layout, IFACE_("See Modifiers panel below"), ICON_INFO);
      }
      else if (fcu->fpt) {
        /* samples only */
        uiItemL(layout,
                IFACE_("F-Curve doesn't have any keyframes as it only contains sampled points"),
                ICON_NONE);
      }
      else {
        uiItemL(layout, IFACE_("No active keyframe on F-Curve"), ICON_NONE);
      }
    }
  
    MEM_freeN(ale);
  }
  
  /* ******************* drivers ******************************** */
  
  #define B_IPO_DEPCHANGE 10
  
  static void do_graph_region_driver_buttons(bContext *C, void *id_v, int event)
  {
    Main *bmain = CTX_data_main(C);
    Scene *scene = CTX_data_scene(C);
  
    switch (event) {
      case B_IPO_DEPCHANGE: {
        /* Was not actually run ever (NULL always passed as arg to this callback).
         * If needed again, will need to check how to pass both fcurve and ID... :/ */
  #if 0
        /* force F-Curve & Driver to get re-evaluated (same as the old Update Dependencies) */
        FCurve *fcu = (FCurve *)fcu_v;
        ChannelDriver *driver = (fcu) ? fcu->driver : NULL;
  
        /* clear invalid flags */
        if (fcu) {
          fcu->flag &= ~FCURVE_DISABLED;
          driver->flag &= ~DRIVER_FLAG_INVALID;
        }
  #endif
        ID *id = id_v;
        AnimData *adt = BKE_animdata_from_id(id);
  
        /* rebuild depsgraph for the new deps, and ensure COW copies get flushed. */
        DEG_relations_tag_update(bmain);
        DEG_id_tag_update_ex(bmain, id, ID_RECALC_COPY_ON_WRITE);
        if (adt != NULL) {
          if (adt->action != NULL) {
            DEG_id_tag_update_ex(bmain, &adt->action->id, ID_RECALC_COPY_ON_WRITE);
          }
          if (adt->tmpact != NULL) {
            DEG_id_tag_update_ex(bmain, &adt->tmpact->id, ID_RECALC_COPY_ON_WRITE);
          }
        }
  
        break;
      }
    }
  
    /* default for now */
    WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);  // XXX could use better notifier
  }
  
  /* callback to add a target variable to the active driver */
  static void driver_add_var_cb(bContext *C, void *driver_v, void *UNUSED(arg))
  {
    ChannelDriver *driver = (ChannelDriver *)driver_v;
  
    /* add a new variable */
    driver_add_new_variable(driver);
    ED_undo_push(C, "Add Driver Variable");
  }
  
  /* callback to remove target variable from active driver */
  static void driver_delete_var_cb(bContext *C, void *driver_v, void *dvar_v)
  {
    ChannelDriver *driver = (ChannelDriver *)driver_v;
    DriverVar *dvar = (DriverVar *)dvar_v;
  
    /* remove the active variable */
    driver_free_variable_ex(driver, dvar);
    ED_undo_push(C, "Delete Driver Variable");
  }
  
  /* callback to report why a driver variable is invalid */
  static void driver_dvar_invalid_name_query_cb(bContext *C, void *dvar_v, void *UNUSED(arg))
  {
    uiPopupMenu *pup = UI_popup_menu_begin(
        C, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Invalid Variable Name"), ICON_NONE);
    uiLayout *layout = UI_popup_menu_layout(pup);
  
    DriverVar *dvar = (DriverVar *)dvar_v;
  
    if (dvar->flag & DVAR_FLAG_INVALID_EMPTY) {
      uiItemL(layout, "It cannot be left blank", ICON_ERROR);
    }
    if (dvar->flag & DVAR_FLAG_INVALID_START_NUM) {
      uiItemL(layout, "It cannot start with a number", ICON_ERROR);
    }
    if (dvar->flag & DVAR_FLAG_INVALID_START_CHAR) {
      uiItemL(layout,
              "It cannot start with a special character,"
              " including '$', '@', '!', '~', '+', '-', '_', '.', or ' '",
              ICON_NONE);
    }
    if (dvar->flag & DVAR_FLAG_INVALID_HAS_SPACE) {
      uiItemL(layout, "It cannot contain spaces (e.g. 'a space')", ICON_ERROR);
    }
    if (dvar->flag & DVAR_FLAG_INVALID_HAS_DOT) {
      uiItemL(layout, "It cannot contain dots (e.g. 'a.dot')", ICON_ERROR);
    }
    if (dvar->flag & DVAR_FLAG_INVALID_HAS_SPECIAL) {
      uiItemL(layout, "It cannot contain special (non-alphabetical/numeric) characters", ICON_ERROR);
    }
    if (dvar->flag & DVAR_FLAG_INVALID_PY_KEYWORD) {
      uiItemL(layout, "It cannot be a reserved keyword in Python", ICON_INFO);
    }
  
    UI_popup_menu_end(C, pup);
  }
  
  /* callback to reset the driver's flags */
  static void driver_update_flags_cb(bContext *UNUSED(C), void *fcu_v, void *UNUSED(arg))
  {
    FCurve *fcu = (FCurve *)fcu_v;
    ChannelDriver *driver = fcu->driver;
  
    /* clear invalid flags */
    fcu->flag &= ~FCURVE_DISABLED;
    driver->flag &= ~DRIVER_FLAG_INVALID;
  }
  
  /* drivers panel poll */
  static bool graph_panel_drivers_poll(const bContext *C, PanelType *UNUSED(pt))
  {
    SpaceGraph *sipo = CTX_wm_space_graph(C);
  
    if (sipo->mode != SIPO_MODE_DRIVERS) {
      return 0;
    }
  
    return graph_panel_context(C, NULL, NULL);
  }
  
  /* settings for 'single property' driver variable type */
  static void graph_panel_driverVar__singleProp(uiLayout *layout, ID *id, DriverVar *dvar)
  {
    DriverTarget *dtar = &dvar->targets[0];
    PointerRNA dtar_ptr;
    uiLayout *row, *col;
  
    /* initialize RNA pointer to the target */
    RNA_pointer_create(id, &RNA_DriverTarget, dtar, &dtar_ptr);
  
    /* Target ID */
    row = uiLayoutRow(layout, false);
    uiLayoutSetRedAlert(row, ((dtar->flag & DTAR_FLAG_INVALID) && !dtar->id));
    uiTemplateAnyID(row, &dtar_ptr, "id", "id_type", IFACE_("Prop:"));
  
    /* Target Property */
    if (dtar->id) {
      PointerRNA root_ptr;
  
      /* get pointer for resolving the property selected */
      RNA_id_pointer_create(dtar->id, &root_ptr);
  
      /* rna path */
      col = uiLayoutColumn(layout, true);
      uiLayoutSetRedAlert(col, (dtar->flag & DTAR_FLAG_INVALID));
      uiTemplatePathBuilder(col, &dtar_ptr, "data_path", &root_ptr, IFACE_("Path"));
    }
  }
  
  /* settings for 'rotation difference' driver variable type */
  /* FIXME: 1) Must be same armature for both dtars, 2) Alignment issues... */
  static void graph_panel_driverVar__rotDiff(uiLayout *layout, ID *id, DriverVar *dvar)
  {
    DriverTarget *dtar = &dvar->targets[0];
    DriverTarget *dtar2 = &dvar->targets[1];
    Object *ob1 = (Object *)dtar->id;
    Object *ob2 = (Object *)dtar2->id;
    PointerRNA dtar_ptr, dtar2_ptr;
    uiLayout *col;
  
    /* initialize RNA pointer to the target */
    RNA_pointer_create(id, &RNA_DriverTarget, dtar, &dtar_ptr);
    RNA_pointer_create(id, &RNA_DriverTarget, dtar2, &dtar2_ptr);
  
    /* Object 1 */
    col = uiLayoutColumn(layout, true);
    uiLayoutSetRedAlert(col, (dtar->flag & DTAR_FLAG_INVALID)); /* XXX: per field... */
    uiItemR(col, &dtar_ptr, "id", 0, IFACE_("Object 1"), ICON_NONE);
  
    if (dtar->id && GS(dtar->id->name) == ID_OB && ob1->pose) {
      PointerRNA tar_ptr;
  
      RNA_pointer_create(dtar->id, &RNA_Pose, ob1->pose, &tar_ptr);
      uiItemPointerR(col, &dtar_ptr, "bone_target", &tar_ptr, "bones", "", ICON_BONE_DATA);
    }
  
    /* Object 2 */
    col = uiLayoutColumn(layout, true);
    uiLayoutSetRedAlert(col, (dtar2->flag & DTAR_FLAG_INVALID)); /* XXX: per field... */
    uiItemR(col, &dtar2_ptr, "id", 0, IFACE_("Object 2"), ICON_NONE);
  
    if (dtar2->id && GS(dtar2->id->name) == ID_OB && ob2->pose) {
      PointerRNA tar_ptr;
  
      RNA_pointer_create(dtar2->id, &RNA_Pose, ob2->pose, &tar_ptr);
      uiItemPointerR(col, &dtar2_ptr, "bone_target", &tar_ptr, "bones", "", ICON_BONE_DATA);
    }
  }
  
  /* settings for 'location difference' driver variable type */
  static void graph_panel_driverVar__locDiff(uiLayout *layout, ID *id, DriverVar *dvar)
  {
    DriverTarget *dtar = &dvar->targets[0];
    DriverTarget *dtar2 = &dvar->targets[1];
    Object *ob1 = (Object *)dtar->id;
    Object *ob2 = (Object *)dtar2->id;
    PointerRNA dtar_ptr, dtar2_ptr;
    uiLayout *col;
  
    /* initialize RNA pointer to the target */
    RNA_pointer_create(id, &RNA_DriverTarget, dtar, &dtar_ptr);
    RNA_pointer_create(id, &RNA_DriverTarget, dtar2, &dtar2_ptr);
  
    /* Object 1 */
    col = uiLayoutColumn(layout, true);
    uiLayoutSetRedAlert(col, (dtar->flag & DTAR_FLAG_INVALID)); /* XXX: per field... */
    uiItemR(col, &dtar_ptr, "id", 0, IFACE_("Object 1"), ICON_NONE);
  
    if (dtar->id && GS(dtar->id->name) == ID_OB && ob1->pose) {
      PointerRNA tar_ptr;
  
      RNA_pointer_create(dtar->id, &RNA_Pose, ob1->pose, &tar_ptr);
      uiItemPointerR(
          col, &dtar_ptr, "bone_target", &tar_ptr, "bones", IFACE_("Bone"), ICON_BONE_DATA);
    }
  
    /* we can clear it again now - it's only needed when creating the ID/Bone fields */
    uiLayoutSetRedAlert(col, false);
  
    uiItemR(col, &dtar_ptr, "transform_space", 0, NULL, ICON_NONE);
  
    /* Object 2 */
    col = uiLayoutColumn(layout, true);
    uiLayoutSetRedAlert(col, (dtar2->flag & DTAR_FLAG_INVALID)); /* XXX: per field... */
    uiItemR(col, &dtar2_ptr, "id", 0, IFACE_("Object 2"), ICON_NONE);
  
    if (dtar2->id && GS(dtar2->id->name) == ID_OB && ob2->pose) {
      PointerRNA tar_ptr;
  
      RNA_pointer_create(dtar2->id, &RNA_Pose, ob2->pose, &tar_ptr);
      uiItemPointerR(
          col, &dtar2_ptr, "bone_target", &tar_ptr, "bones", IFACE_("Bone"), ICON_BONE_DATA);
    }
  
    /* we can clear it again now - it's only needed when creating the ID/Bone fields */
    uiLayoutSetRedAlert(col, false);
  
    uiItemR(col, &dtar2_ptr, "transform_space", 0, NULL, ICON_NONE);
  }
  
  /* settings for 'transform channel' driver variable type */
  static void graph_panel_driverVar__transChan(uiLayout *layout, ID *id, DriverVar *dvar)
  {
    DriverTarget *dtar = &dvar->targets[0];
    Object *ob = (Object *)dtar->id;
    PointerRNA dtar_ptr;
    uiLayout *col, *sub;
  
    /* initialize RNA pointer to the target */
    RNA_pointer_create(id, &RNA_DriverTarget, dtar, &dtar_ptr);
  
    /* properties */
    col = uiLayoutColumn(layout, true);
    uiLayoutSetRedAlert(col, (dtar->flag & DTAR_FLAG_INVALID)); /* XXX: per field... */
    uiItemR(col, &dtar_ptr, "id", 0, IFACE_("Object"), ICON_NONE);
  
    if (dtar->id && GS(dtar->id->name) == ID_OB && ob->pose) {
      PointerRNA tar_ptr;
  
      RNA_pointer_create(dtar->id, &RNA_Pose, ob->pose, &tar_ptr);
      uiItemPointerR(
          col, &dtar_ptr, "bone_target", &tar_ptr, "bones", IFACE_("Bone"), ICON_BONE_DATA);
    }
  
    sub = uiLayoutColumn(layout, true);
    uiItemR(sub, &dtar_ptr, "transform_type", 0, NULL, ICON_NONE);
  
    if (ELEM(dtar->transChan,
             DTAR_TRANSCHAN_ROTX,
             DTAR_TRANSCHAN_ROTY,
             DTAR_TRANSCHAN_ROTZ,
             DTAR_TRANSCHAN_ROTW)) {
      uiItemR(sub, &dtar_ptr, "rotation_mode", 0, IFACE_("Mode"), ICON_NONE);
    }
  
    uiItemR(sub, &dtar_ptr, "transform_space", 0, IFACE_("Space"), ICON_NONE);
  }
  
  /* ----------------------------------------------------------------- */
  
  /* property driven by the driver - duplicates Active FCurve, but useful for clarity */
  static void graph_draw_driven_property_panel(uiLayout *layout, ID *id, FCurve *fcu)
  {
    PointerRNA fcu_ptr;
    uiLayout *row;
    char name[256];
    int icon = 0;
  
    /* F-Curve pointer */
    RNA_pointer_create(id, &RNA_FCurve, fcu, &fcu_ptr);
  
    /* get user-friendly 'name' for F-Curve */
    icon = getname_anim_fcurve(name, id, fcu);
  
    /* panel layout... */
    row = uiLayoutRow(layout, true);
    uiLayoutSetAlignment(row, UI_LAYOUT_ALIGN_LEFT);
  
    /* -> user friendly 'name' for datablock that owns F-Curve */
    /* XXX: Actually, we may need the datablock icons only...
     * (e.g. right now will show bone for bone props). */
    uiItemL(row, id->name + 2, icon);
  
    /* -> user friendly 'name' for F-Curve/driver target */
    uiItemL(row, "", ICON_SMALL_TRI_RIGHT_VEC);
    uiItemL(row, name, ICON_RNA);
  }
  
  /* UI properties panel layout for driver settings - shared for Drivers Editor and for */
  static void graph_draw_driver_settings_panel(uiLayout *layout,
                                               ID *id,
                                               FCurve *fcu,
                                               const bool is_popover)
  {
    ChannelDriver *driver = fcu->driver;
    DriverVar *dvar;
  
    PointerRNA driver_ptr;
    uiLayout *col, *row, *row_outer;
    uiBlock *block;
    uiBut *but;
  
    /* set event handler for panel */
    block = uiLayoutGetBlock(layout);
    UI_block_func_handle_set(block, do_graph_region_driver_buttons, id);
  
    /* driver-level settings - type, expressions, and errors */
    RNA_pointer_create(id, &RNA_Driver, driver, &driver_ptr);
  
    col = uiLayoutColumn(layout, true);
    block = uiLayoutGetBlock(col);
    uiItemR(col, &driver_ptr, "type", 0, NULL, ICON_NONE);
  
    {
      char valBuf[32];
  
      /* value of driver */
      row = uiLayoutRow(col, true);
      uiItemL(row, IFACE_("Driver Value:"), ICON_NONE);
      BLI_snprintf(valBuf, sizeof(valBuf), "%.3f", driver->curval);
      uiItemL(row, valBuf, ICON_NONE);
    }
  
    uiItemS(layout);
    uiItemS(layout);
  
    /* show expression box if doing scripted drivers,
     * and/or error messages when invalid drivers exist */
    if (driver->type == DRIVER_TYPE_PYTHON) {
      bool bpy_data_expr_error = (strstr(driver->expression, "bpy.data.") != NULL);
      bool bpy_ctx_expr_error = (strstr(driver->expression, "bpy.context.") != NULL);
  
      /* expression */
      /* TODO: "Show syntax hints" button */
      col = uiLayoutColumn(layout, true);
      block = uiLayoutGetBlock(col);
  
      uiItemL(col, IFACE_("Expression:"), ICON_NONE);
      uiItemR(col, &driver_ptr, "expression", 0, "", ICON_NONE);
      uiItemR(col, &driver_ptr, "use_self", 0, NULL, ICON_NONE);
  
      /* errors? */
      col = uiLayoutColumn(layout, true);
      block = uiLayoutGetBlock(col);
  
      if (driver->flag & DRIVER_FLAG_INVALID) {
        uiItemL(col, TIP_("ERROR: Invalid Python expression"), ICON_CANCEL);
      }
      else if (!BKE_driver_has_simple_expression(driver)) {
        if ((G.f & G_FLAG_SCRIPT_AUTOEXEC) == 0) {
          /* TODO: Add button to enable? */
          uiItemL(col, TIP_("Python restricted for security"), ICON_ERROR);
        }
        else {
          uiItemL(col, TIP_("Slow Python expression"), ICON_INFO);
        }
      }
  
      /* Explicit bpy-references are evil. Warn about these to prevent errors */
      /* TODO: put these in a box? */
      if (bpy_data_expr_error || bpy_ctx_expr_error) {
        uiItemL(col, TIP_("WARNING: Driver expression may not work correctly"), ICON_HELP);
  
        if (bpy_data_expr_error) {
          uiItemL(col, TIP_("TIP: Use variables instead of bpy.data paths (see below)"), ICON_ERROR);
        }
        if (bpy_ctx_expr_error) {
          uiItemL(col, TIP_("TIP: bpy.context is not safe for renderfarm usage"), ICON_ERROR);
        }
      }
    }
    else {
      /* errors? */
      col = uiLayoutColumn(layout, true);
      block = uiLayoutGetBlock(col);
  
      if (driver->flag & DRIVER_FLAG_INVALID) {
        uiItemL(col, TIP_("ERROR: Invalid target channel(s)"), ICON_ERROR);
      }
  
      /* Warnings about a lack of variables
       * NOTE: The lack of variables is generally a bad thing, since it indicates
       *       that the driver doesn't work at all. This particular scenario arises
       *       primarily when users mistakenly try to use drivers for procedural
       *       property animation
       */
      if (BLI_listbase_is_empty(&driver->variables)) {
        uiItemL(col, TIP_("ERROR: Driver is useless without any inputs"), ICON_ERROR);
  
        if (!BLI_listbase_is_empty(&fcu->modifiers)) {
          uiItemL(col, TIP_("TIP: Use F-Curves for procedural animation instead"), ICON_INFO);
          uiItemL(col, TIP_("F-Modifiers can generate curves for those too"), ICON_INFO);
        }
      }
    }
  
    uiItemS(layout);
  
    /* add/copy/paste driver variables */
    row_outer = uiLayoutRow(layout, false);
  
    /* add driver variable - add blank */
    row = uiLayoutRow(row_outer, true);
    block = uiLayoutGetBlock(row);
    but = uiDefIconTextBut(
        block,
        UI_BTYPE_BUT,
        B_IPO_DEPCHANGE,
        ICON_ADD,
        IFACE_("Add Input Variable"),
        0,
        0,
        10 * UI_UNIT_X,
        UI_UNIT_Y,
        NULL,
        0.0,
        0.0,
        0,
        0,
        TIP_("Add a Driver Variable to keep track of an input used by the driver"));
    UI_but_func_set(but, driver_add_var_cb, driver, NULL);
  
    if (is_popover) {
      /* add driver variable - add using eyedropper */
      /* XXX: will this operator work like this? */
      uiItemO(row, "", ICON_EYEDROPPER, "UI_OT_eyedropper_driver");
    }
  
    /* copy/paste (as sub-row) */
    row = uiLayoutRow(row_outer, true);
    block = uiLayoutGetBlock(row);
  
    uiItemO(row, "", ICON_COPYDOWN, "GRAPH_OT_driver_variables_copy");
    uiItemO(row, "", ICON_PASTEDOWN, "GRAPH_OT_driver_variables_paste");
  
    /* loop over targets, drawing them */
    for (dvar = driver->variables.first; dvar; dvar = dvar->next) {
      PointerRNA dvar_ptr;
      uiLayout *box;
      uiLayout *subrow, *sub;
  
      /* sub-layout column for this variable's settings */
      col = uiLayoutColumn(layout, true);
  
      /* 1) header panel */
      box = uiLayoutBox(col);
      RNA_pointer_create(id, &RNA_DriverVariable, dvar, &dvar_ptr);
  
      row = uiLayoutRow(box, false);
      block = uiLayoutGetBlock(row);
  
      /* 1.1) variable type and name */
      subrow = uiLayoutRow(row, true);
  
      /* 1.1.1) variable type */
  
      /* HACK: special group just for the enum,
       * otherwise we get ugly layout with text included too... */
      sub = uiLayoutRow(subrow, true);
  
      uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT);
  
      uiItemR(sub, &dvar_ptr, "type", UI_ITEM_R_ICON_ONLY, "", ICON_NONE);
  
      /* 1.1.2) variable name */
  
      /* HACK: special group to counteract the effects of the previous enum,
       * which now pushes everything too far right */
      sub = uiLayoutRow(subrow, true);
  
      uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_EXPAND);
  
      uiItemR(sub, &dvar_ptr, "name", 0, "", ICON_NONE);
  
      /* 1.2) invalid name? */
      UI_block_emboss_set(block, UI_EMBOSS_NONE);
  
      if (dvar->flag & DVAR_FLAG_INVALID_NAME) {
        but = uiDefIconBut(block,
                           UI_BTYPE_BUT,
                           B_IPO_DEPCHANGE,
                           ICON_ERROR,
                           290,
                           0,
                           UI_UNIT_X,
                           UI_UNIT_Y,
                           NULL,
                           0.0,
                           0.0,
                           0.0,
                           0.0,
                           TIP_("Invalid variable name, click here for details"));
        UI_but_func_set(but, driver_dvar_invalid_name_query_cb, dvar, NULL);  // XXX: reports?
      }
  
      /* 1.3) remove button */
      but = uiDefIconBut(block,
                         UI_BTYPE_BUT,
                         B_IPO_DEPCHANGE,
                         ICON_X,
                         290,
                         0,
                         UI_UNIT_X,
                         UI_UNIT_Y,
                         NULL,
                         0.0,
                         0.0,
                         0.0,
                         0.0,
                         TIP_("Delete target variable"));
      UI_but_func_set(but, driver_delete_var_cb, driver, dvar);
      UI_block_emboss_set(block, UI_EMBOSS);
  
      /* 2) variable type settings */
      box = uiLayoutBox(col);
      /* controls to draw depends on the type of variable */
      switch (dvar->type) {
        case DVAR_TYPE_SINGLE_PROP: /* single property */
          graph_panel_driverVar__singleProp(box, id, dvar);
          break;
        case DVAR_TYPE_ROT_DIFF: /* rotational difference */
          graph_panel_driverVar__rotDiff(box, id, dvar);
          break;
        case DVAR_TYPE_LOC_DIFF: /* location difference */
          graph_panel_driverVar__locDiff(box, id, dvar);
          break;
        case DVAR_TYPE_TRANSFORM_CHAN: /* transform channel */
          graph_panel_driverVar__transChan(box, id, dvar);
          break;
      }
  
      /* 3) value of variable */
      {
        char valBuf[32];
  
        box = uiLayoutBox(col);
        row = uiLayoutRow(box, true);
        uiItemL(row, IFACE_("Value:"), ICON_NONE);
  
        if ((dvar->type == DVAR_TYPE_ROT_DIFF) ||
            (dvar->type == DVAR_TYPE_TRANSFORM_CHAN &&
             ELEM(dvar->targets[0].transChan,
                  DTAR_TRANSCHAN_ROTX,
                  DTAR_TRANSCHAN_ROTY,
                  DTAR_TRANSCHAN_ROTZ,
                  DTAR_TRANSCHAN_ROTW) &&
             dvar->targets[0].rotation_mode != DTAR_ROTMODE_QUATERNION)) {
          BLI_snprintf(
              valBuf, sizeof(valBuf), "%.3f (%4.1f°)", dvar->curval, RAD2DEGF(dvar->curval));
        }
        else {
          BLI_snprintf(valBuf, sizeof(valBuf), "%.3f", dvar->curval);
        }
  
        uiItemL(row, valBuf, ICON_NONE);
      }
    }
  
    uiItemS(layout);
    uiItemS(layout);
  
    /* XXX: This should become redundant. But sometimes the flushing fails,
     * so keep this around for a while longer as a "last resort" */
    row = uiLayoutRow(layout, true);
    block = uiLayoutGetBlock(row);
    but = uiDefIconTextBut(
        block,
        UI_BTYPE_BUT,
        B_IPO_DEPCHANGE,
        ICON_FILE_REFRESH,
        IFACE_("Update Dependencies"),
        0,
        0,
        10 * UI_UNIT_X,
        UI_UNIT_Y,
        NULL,
        0.0,
        0.0,
        0,
        0,
        TIP_("Force updates of dependencies - Only use this if drivers are not updating correctly"));
    UI_but_func_set(but, driver_update_flags_cb, fcu, NULL);
  }
  
  /* ----------------------------------------------------------------- */
  
  /* Panel to show property driven by the driver (in Drivers Editor) - duplicates Active FCurve,
   * but useful for clarity. */
  static void graph_panel_driven_property(const bContext *C, Panel *panel)
  {
    bAnimListElem *ale;
    FCurve *fcu;
  
    if (!graph_panel_context(C, &ale, &fcu)) {
      return;
    }
  
    graph_draw_driven_property_panel(panel->layout, ale->id, fcu);
  
    MEM_freeN(ale);
  }
  
  /* driver settings for active F-Curve
   * (only for 'Drivers' mode in Graph Editor, i.e. the full "Drivers Editor") */
  static void graph_panel_drivers(const bContext *C, Panel *panel)
  {
    bAnimListElem *ale;
    FCurve *fcu;
  
    /* Get settings from context */
    if (!graph_panel_context(C, &ale, &fcu)) {
      return;
    }
  
    graph_draw_driver_settings_panel(panel->layout, ale->id, fcu, false);
  
    /* cleanup */
    MEM_freeN(ale);
  }
  
  /* ----------------------------------------------------------------- */
  
  /* Poll to make this not show up in the graph editor,
   * as this is only to be used as a popup elsewhere. */
  static bool graph_panel_drivers_popover_poll(const bContext *C, PanelType *UNUSED(pt))
  {
    return ED_operator_graphedit_active((bContext *)C) == false;
  }
  
  /* popover panel for driver editing anywhere in ui */
  static void graph_panel_drivers_popover(const bContext *C, Panel *panel)
  {
    uiLayout *layout = panel->layout;
  
    PointerRNA ptr = {NULL};
    PropertyRNA *prop = NULL;
    int index = -1;
    uiBut *but = NULL;
  
    /* Get active property to show driver properties for */
    but = UI_context_active_but_prop_get((bContext *)C, &ptr, &prop, &index);
    if (but) {
      FCurve *fcu;
      bool driven, special;
  
      fcu = rna_get_fcurve_context_ui(
          (bContext *)C, &ptr, prop, index, NULL, NULL, &driven, &special);
  
      /* Hack: Force all buttons in this panel to be able to know the driver button
       * this panel is getting spawned from, so that things like the "Open Drivers Editor"
       * button will work.
       */
      uiLayoutSetContextFromBut(layout, but);
  
      /* Populate Panel - With a combination of the contents of the Driven and Driver panels */
      if (fcu && fcu->driver) {
        ID *id = ptr.owner_id;
  
        PointerRNA ptr_fcurve;
        RNA_pointer_create(id, &RNA_FCurve, fcu, &ptr_fcurve);
        uiLayoutSetContextPointer(layout, "active_editable_fcurve", &ptr_fcurve);
  
        /* Driven Property Settings */
        uiItemL(layout, IFACE_("Driven Property:"), ICON_NONE);
        graph_draw_driven_property_panel(panel->layout, id, fcu);
        /* TODO: All vs Single */
  
        uiItemS(layout);
        uiItemS(layout);
  
        /* Drivers Settings */
        uiItemL(layout, IFACE_("Driver Settings:"), ICON_NONE);
        graph_draw_driver_settings_panel(panel->layout, id, fcu, true);
      }
    }
  
    /* Show drivers editor is always visible */
    uiItemO(layout, IFACE_("Show in Drivers Editor"), ICON_DRIVER, "SCREEN_OT_drivers_editor_show");
  }
  
  /* ******************* F-Modifiers ******************************** */
  /* All the drawing code is in editors/animation/fmodifier_ui.c */
  
  #define B_FMODIFIER_REDRAW 20
  
  static void do_graph_region_modifier_buttons(bContext *C, void *UNUSED(arg), int event)
  {
    switch (event) {
      case B_FMODIFIER_REDRAW:  // XXX this should send depsgraph updates too
        WM_event_add_notifier(
            C, NC_ANIMATION, NULL);  // XXX need a notifier specially for F-Modifiers
        break;
    }
  }
  
  static void graph_panel_modifiers(const bContext *C, Panel *panel)
  {
    bAnimListElem *ale;
    FCurve *fcu;
    FModifier *fcm;
    uiLayout *col, *row;
    uiBlock *block;
    bool active;
  
    if (!graph_panel_context(C, &ale, &fcu)) {
      return;
    }
  
    block = uiLayoutGetBlock(panel->layout);
    UI_block_func_handle_set(block, do_graph_region_modifier_buttons, NULL);
  
    /* 'add modifier' button at top of panel */
    {
      row = uiLayoutRow(panel->layout, false);
  
      /* this is an operator button which calls a 'add modifier' operator...
       * a menu might be nicer but would be tricky as we need some custom filtering
       */
      uiItemMenuEnumO(
          row, (bContext *)C, "GRAPH_OT_fmodifier_add", "type", IFACE_("Add Modifier"), ICON_NONE);
  
      /* copy/paste (as sub-row) */
      row = uiLayoutRow(row, true);
      uiItemO(row, "", ICON_COPYDOWN, "GRAPH_OT_fmodifier_copy");
      uiItemO(row, "", ICON_PASTEDOWN, "GRAPH_OT_fmodifier_paste");
    }
  
    active = !(fcu->flag & FCURVE_MOD_OFF);
    /* draw each modifier */
    for (fcm = fcu->modifiers.first; fcm; fcm = fcm->next) {
      col = uiLayoutColumn(panel->layout, true);
      uiLayoutSetActive(col, active);
  
      ANIM_uiTemplate_fmodifier_draw(col, ale->fcurve_owner_id, &fcu->modifiers, fcm);
    }
  
    MEM_freeN(ale);
  }
  
  /* ******************* general ******************************** */
  
  void graph_buttons_register(ARegionType *art)
  {
    PanelType *pt;
  
    pt = MEM_callocN(sizeof(PanelType), "spacetype graph panel properties");
    strcpy(pt->idname, "GRAPH_PT_properties");
    strcpy(pt->label, N_("Active F-Curve"));
    strcpy(pt->category, "F-Curve");
    strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
    pt->draw = graph_panel_properties;
    pt->poll = graph_panel_poll;
    BLI_addtail(&art->paneltypes, pt);
  
    pt = MEM_callocN(sizeof(PanelType), "spacetype graph panel properties");
    strcpy(pt->idname, "GRAPH_PT_key_properties");
    strcpy(pt->label, N_("Active Keyframe"));
    strcpy(pt->category, "F-Curve");
    strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
    pt->draw = graph_panel_key_properties;
    pt->poll = graph_panel_poll;
    BLI_addtail(&art->paneltypes, pt);
  
    pt = MEM_callocN(sizeof(PanelType), "spacetype graph panel drivers driven");
    strcpy(pt->idname, "GRAPH_PT_driven_property");
    strcpy(pt->label, N_("Driven Property"));
    strcpy(pt->category, "Drivers");
    strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
    pt->draw = graph_panel_driven_property;
    pt->poll = graph_panel_drivers_poll;
    BLI_addtail(&art->paneltypes, pt);
  
    pt = MEM_callocN(sizeof(PanelType), "spacetype graph panel drivers");
    strcpy(pt->idname, "GRAPH_PT_drivers");
    strcpy(pt->label, N_("Driver"));
    strcpy(pt->category, "Drivers");
    strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
    pt->draw = graph_panel_drivers;
    pt->poll = graph_panel_drivers_poll;
    BLI_addtail(&art->paneltypes, pt);
  
    pt = MEM_callocN(sizeof(PanelType), "spacetype graph panel drivers pover");
    strcpy(pt->idname, "GRAPH_PT_drivers_popover");
    strcpy(pt->label, N_("Add/Edit Driver"));
    strcpy(pt->category, "Drivers");
    strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
    pt->draw = graph_panel_drivers_popover;
    pt->poll = graph_panel_drivers_popover_poll;
    BLI_addtail(&art->paneltypes, pt);
    /* This panel isn't used in this region.
     * Add explicitly to global list (so popovers work). */
    WM_paneltype_add(pt);
  
    pt = MEM_callocN(sizeof(PanelType), "spacetype graph panel modifiers");
    strcpy(pt->idname, "GRAPH_PT_modifiers");
    strcpy(pt->label, N_("Modifiers"));
    strcpy(pt->category, "Modifiers");
    strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
    pt->draw = graph_panel_modifiers;
    pt->poll = graph_panel_poll;
    BLI_addtail(&art->paneltypes, pt);
  
    pt = MEM_callocN(sizeof(PanelType), "spacetype graph panel view");
    strcpy(pt->idname, "GRAPH_PT_view");
    strcpy(pt->label, N_("Show Cursor"));
    strcpy(pt->category, "View");
    strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
    pt->draw = graph_panel_cursor;
    pt->draw_header = graph_panel_cursor_header;
    BLI_addtail(&art->paneltypes, pt);
  }
  