Logo Search packages:      
Sourcecode: blender version File versions

editaction.c

/**
 * $Id: editaction.c,v 1.37 2004/12/12 21:13:07 ton Exp $
 *
 * ***** BEGIN GPL/BL DUAL LICENSE BLOCK *****
 *
 * 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. The Blender
 * Foundation also sells licenses for use in proprietary software under
 * the Blender License.  See http://www.blender.org/BL/ for information
 * about this.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 * All rights reserved.
 *
 * The Original Code is: all of this file.
 *
 * Contributor(s): none yet.
 *
 * ***** END GPL/BL DUAL LICENSE BLOCK *****
 */

#include <string.h>
#include <math.h>

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef WIN32
#include "BLI_winstuff.h"
#endif

#include "MEM_guardedalloc.h"

#include "PIL_time.h"

#include "BLI_blenlib.h"
#include "BLI_arithb.h"

#include "DNA_action_types.h"
#include "DNA_armature_types.h"
#include "DNA_curve_types.h"
#include "DNA_ipo_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_userdef_types.h"
#include "DNA_constraint_types.h"
#include "DNA_key_types.h"
#include "DNA_mesh_types.h"
#include "DNA_lattice_types.h"

#include "BKE_armature.h"
#include "BKE_constraint.h"
#include "BKE_displist.h"
#include "BKE_curve.h"
#include "BKE_ipo.h"
#include "BKE_global.h"
#include "BKE_library.h"
#include "BKE_main.h"
#include "BKE_action.h"
#include "BKE_key.h"

#include "BIF_gl.h"
#include "BIF_mywindow.h"
#include "BIF_toolbox.h"
#include "BIF_screen.h"
#include "BIF_space.h"
#include "BIF_butspace.h"
#include "BIF_interface.h"
#include "BIF_editview.h"
#include "BIF_poseobject.h"
#include "BIF_editarmature.h"
#include "BIF_editaction.h"

#include "BSE_edit.h"
#include "BSE_drawipo.h"
#include "BSE_headerbuttons.h"
#include "BSE_editipo.h"
#include "BSE_editaction.h"
#include "BSE_trans_types.h"
#include "BSE_editaction_types.h"

#include "BDR_editobject.h"

#include "mydevice.h"
#include "blendef.h"
#include "nla.h"

static bPose      *g_posebuf=NULL;
extern int count_action_levels (bAction *act);

#define BEZSELECTED(bezt)   (((bezt)->f1 & 1) || ((bezt)->f2 & 1) || ((bezt)->f3 & 1))

/* Local Function prototypes */

static void insertactionkey(bAction *act, bActionChannel *achan, bPoseChannel *chan, int adrcode, short makecurve, float time);
static void flip_name (char *name);
static void mouse_actionchannels(bAction *act, short *mval, 
                                 short *mvalo, int selectmode);
static void mouse_action(int selectmode);
static void mouse_mesh_action(int selectmode, Key *key);
static bActionChannel *get_nearest_actionchannel_key (float *index, short *sel, bConstraintChannel **conchan);
static void delete_actionchannels(void);
static void select_poseelement_by_name (char *name, int select);
static void hilight_channel (bAction *act, bActionChannel *chan, short hilight);
static void set_action_key_time (bAction *act, bPoseChannel *chan, int adrcode, short makecurve, float time);
/* Implementation */

short showsliders = 0;
short ACTWIDTH = NAMEWIDTH;

static void select_poseelement_by_name (char *name, int select)
{
      /* Synchs selection of channels with selection of object elements in posemode */

      Object *ob;

      ob = G.obpose;
      if (!ob)
            return;

      switch (ob->type){
      case OB_ARMATURE:
            select_bone_by_name ((bArmature*)ob->data, name, select);
            break;
      default:
            break;
      }
}
#ifdef __NLA_BAKE
bAction* bake_action_with_client (bAction *act, Object *armob, float tolerance)
{
      bAction                 *result=NULL;
      bActionChannel *achan;
      float             actlen;
      int                     curframe;
      char              newname[64];
      bArmature         *arm;
      Bone              *bone;
      float             oldframe;
      bAction                 *temp;
      bPoseChannel *pchan;

      if (!act)
            return NULL;

      arm = get_armature(armob);

      
      if (G.obedit){
            error ("Actions can't be baked in Edit Mode");
            return NULL;
      }

      if (!arm){
            error ("Select an armature before baking");
            return NULL;
      }
      /* Get a new action */
      result = add_empty_action();

      /* Assign the new action a unique name */
      sprintf (newname, "%s.BAKED", act->id.name+2);
      rename_id(&result->id, newname);

      actlen = calc_action_end(act);

      oldframe = G.scene->r.cfra;

      temp = armob->action;
      armob->action = act;
      
      for (curframe=1; curframe<ceil(actlen+1); curframe++){

            /* Apply the old action */
            
            G.scene->r.cfra = curframe;

            /* Apply the object ipo */
            get_pose_from_action(&armob->pose, act, curframe);
            apply_pose_armature(arm, armob->pose, 1);
            clear_object_constraint_status(armob);
            where_is_armature_time(armob, curframe);
            
            /* For each channel: set quats and locs if channel is a bone */
            for (pchan=armob->pose->chanbase.first; pchan; pchan=pchan->next){

                  bone = get_named_bone(arm, pchan->name);
                  if (bone){
                        float tmp_quat[4], tmp_loc[3], tmp_size[3];
                        QUATCOPY(tmp_quat, pchan->quat);
                        VECCOPY(tmp_loc, pchan->loc);
                        VECCOPY(tmp_size, pchan->size);

                        Mat4ToQuat(pchan->obmat, pchan->quat);
                        Mat4ToSize(pchan->obmat, pchan->size);
                        VECCOPY(pchan->loc, pchan->obmat[3]);

                        /* Apply to keys */
                        set_action_key_time (result, pchan, AC_QUAT_X, 1, curframe);
                        set_action_key_time (result, pchan, AC_QUAT_Y, 1, curframe);
                        set_action_key_time (result, pchan, AC_QUAT_Z, 1, curframe);
                        set_action_key_time (result, pchan, AC_QUAT_W, 1, curframe);
                        set_action_key_time (result, pchan, AC_LOC_X, 1, curframe);
                        set_action_key_time (result, pchan, AC_LOC_Y, 1, curframe);
                        set_action_key_time (result, pchan, AC_LOC_Z, 1, curframe);
                        set_action_key_time (result, pchan, AC_SIZE_X, 1, curframe);
                        set_action_key_time (result, pchan, AC_SIZE_Y, 1, curframe);
                        set_action_key_time (result, pchan, AC_SIZE_Z, 1, curframe);

                        QUATCOPY(pchan->quat, tmp_quat);
                        VECCOPY(pchan->loc, tmp_loc);
                        VECCOPY(pchan->size, tmp_size);
                  }
            }
      }


      /* Make another pass to ensure all keyframes are set to linear interpolation mode */
      for (achan = result->chanbase.first; achan; achan=achan->next){
            IpoCurve* icu;
            for (icu = achan->ipo->curve.first; icu; icu=icu->next){
                  icu->ipo= IPO_LIN;
            }
      }

      notice ("Made a new action named \"%s\"", newname);
      G.scene->r.cfra = oldframe;
      armob->action = temp;
      return result;
}
#endif

void select_actionchannel_by_name (bAction *act, char *name, int select)
{
      bActionChannel *chan;

      if (!act)
            return;

      for (chan = act->chanbase.first; chan; chan=chan->next){
            if (!strcmp (chan->name, name)){
                  act->achan = chan;
                  if (select){
                        chan->flag |= ACHAN_SELECTED;
                        hilight_channel (act, chan, 1);
                  }
                  else{
                        chan->flag &= ~ACHAN_SELECTED;
                        hilight_channel (act, chan, 0);
                  }
                  return;
            }
      }
}

void remake_action_ipos(bAction *act)
{
      bActionChannel *chan;
      bConstraintChannel *conchan;
      IpoCurve          *icu;

      for (chan= act->chanbase.first; chan; chan=chan->next){
            if (chan->ipo){
                  for (icu = chan->ipo->curve.first; icu; icu=icu->next){
                        sort_time_ipocurve(icu);
                        testhandles_ipocurve(icu);
                  }
            }
            for (conchan=chan->constraintChannels.first; conchan; conchan=conchan->next){
                  if (conchan->ipo){
                        for (icu = conchan->ipo->curve.first; icu; icu=icu->next){
                              sort_time_ipocurve(icu);
                              testhandles_ipocurve(icu);
                        }
                  }
            }
      }
}

void remake_meshaction_ipos(Ipo *ipo)
{
      /* this puts the bezier triples in proper
       * order and makes sure the bezier handles
       * aren't too strange.
       */
      IpoCurve *icu;

      for (icu = ipo->curve.first; icu; icu=icu->next){
            sort_time_ipocurve(icu);
            testhandles_ipocurve(icu);
      }
}

static void meshkey_do_redraw(Key *key)
{
      remake_meshaction_ipos(key->ipo);
    do_all_ipos();
    do_spec_key(key);

      allspace(REMAKEIPO, 0);
      allqueue(REDRAWACTION, 0);
      allqueue(REDRAWIPO, 0);
      allqueue(REDRAWNLA, 0);

}

void duplicate_meshchannel_keys(Key *key)
{
      duplicate_ipo_keys(key->ipo);
      transform_meshchannel_keys ('g', key);
}


void duplicate_actionchannel_keys(void)
{
      bAction *act;
      bActionChannel *chan;
      bConstraintChannel *conchan;

      act=G.saction->action;
      if (!act)
            return;

      /* Find selected items */
      for (chan = act->chanbase.first; chan; chan=chan->next){
            duplicate_ipo_keys(chan->ipo);
            for (conchan=chan->constraintChannels.first; conchan; conchan=conchan->next)
                  duplicate_ipo_keys(conchan->ipo);
      }

      transform_actionchannel_keys ('g');
}

static bActionChannel *get_nearest_actionchannel_key (float *index, short *sel, bConstraintChannel **rchan){
      bAction *act;
      bActionChannel *chan;
      IpoCurve *icu;
      bActionChannel *firstchan=NULL;
      bConstraintChannel *conchan, *firstconchan=NULL;
      int   foundsel=0;
      float firstvert=-1, foundx=-1;
            int i;
      short mval[2];
      float ymin, ymax;
      rctf  rectf;
      *index=0;

      *rchan=NULL;
      act=G.saction->action; /* We presume that we are only called during a valid action */
      
      getmouseco_areawin (mval);

      mval[0]-=7;
      areamouseco_to_ipoco(G.v2d, mval, &rectf.xmin, &rectf.ymin);
      mval[0]+=14;
      areamouseco_to_ipoco(G.v2d, mval, &rectf.xmax, &rectf.ymax);

      ymax = count_action_levels(act) * (CHANNELHEIGHT + CHANNELSKIP);
      ymax += CHANNELHEIGHT/2;
      
      *sel=0;

      for (chan=act->chanbase.first; chan; chan=chan->next){

            /* Check action channel */
            ymin= ymax-(CHANNELHEIGHT+CHANNELSKIP);
            if (!((ymax < rectf.ymin) || (ymin > rectf.ymax))){
                  for (icu=chan->ipo->curve.first; icu; icu=icu->next){
                        for (i=0; i<icu->totvert; i++){
                              if (icu->bezt[i].vec[1][0] > rectf.xmin && icu->bezt[i].vec[1][0] <= rectf.xmax ){
                                    if (!firstchan){
                                          firstchan=chan;
                                          firstvert=icu->bezt[i].vec[1][0];
                                          *sel = icu->bezt[i].f2 & 1;   
                                    }
                                    
                                    if (icu->bezt[i].f2 & 1){ 
                                          if (!foundsel){
                                                foundsel=1;
                                                foundx = icu->bezt[i].vec[1][0];
                                          }
                                    }
                                    else if (foundsel && icu->bezt[i].vec[1][0] != foundx){
                                          *index=icu->bezt[i].vec[1][0];
                                          *sel = 0;
                                          return chan;
                                    }
                              }
                        }
                  }
            }
            ymax=ymin;
            
            /* Check constraint channels */
            for (conchan=chan->constraintChannels.first; conchan; conchan=conchan->next){
                  ymin=ymax-(CHANNELHEIGHT+CHANNELSKIP);
                  if (!((ymax < rectf.ymin) || (ymin > rectf.ymax))){
                        for (icu=conchan->ipo->curve.first; icu; icu=icu->next){
                              for (i=0; i<icu->totvert; i++){
                                    if (icu->bezt[i].vec[1][0] > rectf.xmin && icu->bezt[i].vec[1][0] <= rectf.xmax ){
                                          if (!firstchan){
                                                firstchan=chan;
                                                firstconchan=conchan;
                                                firstvert=icu->bezt[i].vec[1][0];
                                                *sel = icu->bezt[i].f2 & 1;   
                                          }
                                          
                                          if (icu->bezt[i].f2 & 1){ 
                                                if (!foundsel){
                                                      foundsel=1;
                                                      foundx = icu->bezt[i].vec[1][0];
                                                }
                                          }
                                          else if (foundsel && icu->bezt[i].vec[1][0] != foundx){
                                                *index=icu->bezt[i].vec[1][0];
                                                *sel = 0;
                                                *rchan = conchan;
                                                return chan;
                                          }
                                    }
                              }
                        }
                  }
                  ymax=ymin;
            }
      }     
      
      *rchan = firstconchan;
      *index=firstvert;
      return firstchan;
}

static IpoCurve *get_nearest_meshchannel_key (float *index, short *sel)
{
      /* This function tries to find the RVK key that is
       * closest to the user's mouse click
       */
    Key      *key;
    IpoCurve *icu; 
    IpoCurve *firsticu=NULL;
    int          foundsel=0;
    float    firstvert=-1, foundx=-1;
      int      i;
    short    mval[2];
    float    ymin, ymax, ybase;
    rctf     rectf;

    *index=0;

    key = get_action_mesh_key();
      
    /* lets get the mouse position and process it so 
     * we can start testing selections
     */
    getmouseco_areawin (mval);
    mval[0]-=7;
    areamouseco_to_ipoco(G.v2d, mval, &rectf.xmin, &rectf.ymin);
    mval[0]+=14;
    areamouseco_to_ipoco(G.v2d, mval, &rectf.xmax, &rectf.ymax);

    ybase = key->totkey * (CHANNELHEIGHT + CHANNELSKIP);
      ybase += CHANNELHEIGHT/2;
    *sel=0;

    /* lets loop through the IpoCurves trying to find the closest
     * bezier
     */
      if (!key->ipo) return NULL;
    for (icu = key->ipo->curve.first; icu ; icu = icu->next) {
        /* lets not deal with the "speed" Ipo
         */
        if (!icu->adrcode) continue;

        ymax = ybase    - (CHANNELHEIGHT+CHANNELSKIP)*(icu->adrcode-1);
        ymin=ymax-(CHANNELHEIGHT+CHANNELSKIP);

        /* Does this curve coorespond to the right
         * strip?
         */
        if (!((ymax < rectf.ymin) || (ymin > rectf.ymax))){
                  
            /* loop through the beziers in the curve
             */
            for (i=0; i<icu->totvert; i++){

                /* Is this bezier in the right area?
                 */
                if (icu->bezt[i].vec[1][0] > rectf.xmin && 
                    icu->bezt[i].vec[1][0] <= rectf.xmax ){

                    /* if no other curves have been picked ...
                     */
                    if (!firsticu){
                        /* mark this curve/bezier as the first
                         * selected
                         */
                        firsticu=icu;
                        firstvert=icu->bezt[i].vec[1][0];

                        /* sel = (is the bezier is already selected) ? 1 : 0;
                         */
                        *sel = icu->bezt[i].f2 & 1;   
                    }

                    /* if the bezier is selected ...
                     */
                    if (icu->bezt[i].f2 & 1){ 
                        /* if we haven't found a selected one yet ...
                         */
                        if (!foundsel){
                            /* record the found x value
                             */
                            foundsel=1;
                            foundx = icu->bezt[i].vec[1][0];
                        }
                    }

                    /* if the bezier is unselected and not at the x
                     * position of a previous found selected bezier ...
                     */
                    else if (foundsel && icu->bezt[i].vec[1][0] != foundx){
                        /* lets return this found curve/bezier
                         */
                        *index=icu->bezt[i].vec[1][0];
                        *sel = 0;
                        return icu;
                    }
                }
            }
        }
      }
      
    /* return what we've found
     */
    *index=firstvert;
    return firsticu;
}

static void mouse_action(int selectmode)
{
      bAction     *act;
      short sel;
      float selx;
      bActionChannel *chan;
      bConstraintChannel *conchan;
      short mval[2];

      act=G.saction->action;
      if (!act)
            return;

      getmouseco_areawin (mval);

      chan=get_nearest_actionchannel_key(&selx, &sel, &conchan);

      if (chan){
            if (selectmode == SELECT_REPLACE) {
                  if (sel == 0)
                        selectmode = SELECT_ADD;
                  else
                        selectmode = SELECT_SUBTRACT;
                  deselect_actionchannel_keys(act, 0);
                  deselect_actionchannels(act, 0);
                  act->achan = chan;
                  chan->flag |= ACHAN_SELECTED;
                  hilight_channel (act, chan, 1);
            }
            
            if (conchan)
                  select_ipo_key(conchan->ipo, selx, selectmode);
            else
                  select_ipo_key(chan->ipo, selx, selectmode);

            BIF_undo_push("Select Action");
            allqueue(REDRAWIPO, 0);
            allqueue(REDRAWVIEW3D, 0);
            allqueue(REDRAWACTION, 0);
            allqueue(REDRAWNLA, 0);

      }
}

static void mouse_mesh_action(int selectmode, Key *key)
{
      /* Handle a right mouse click selection in an
       * action window displaying RVK data
       */

    IpoCurve *icu;
    short  sel;
    float  selx;
    short  mval[2];

    /* going to assume that the only reason 
     * we got here is because it has been 
     * determined that we are a mesh with
     * the right properties (i.e., have key
     * data, etc)
     */

      /* get the click location, and the cooresponding
       * ipo curve and selection time value
       */
    getmouseco_areawin (mval);
    icu = get_nearest_meshchannel_key(&selx, &sel);

    if (icu){
        if (selectmode == SELECT_REPLACE) {
                  /* if we had planned to replace the
                   * selection, then we will first deselect
                   * all of the keys, and if the clicked on
                   * key had been unselected, we will select 
                   * it, otherwise, we are done.
                   */
            deselect_meshchannel_keys(key, 0);

            if (sel == 0)
                selectmode = SELECT_ADD;
            else
                        /* the key is selected so we should
                         * deselect -- but everything is now deselected
                         * so we are done.
                         */
                        return;
        }
            
            /* select the key using the given mode
             * and redraw as mush stuff as needed.
             */
            select_icu_key(icu, selx, selectmode);

            BIF_undo_push("Select Action key");
        allqueue(REDRAWIPO, 0);
        allqueue(REDRAWVIEW3D, 0);
        allqueue(REDRAWACTION, 0);
        allqueue(REDRAWNLA, 0);

    }
}

void borderselect_action(void)
{ 
      rcti rect;
      rctf rectf;
      int val, selectmode;          
      short mval[2];
      bActionChannel *chan;
      bConstraintChannel *conchan;
      bAction     *act;
      float ymin, ymax;

      act=G.saction->action;


      if (!act)
            return;

      if ( (val = get_border(&rect, 3)) ){
    if (val == LEFTMOUSE)
      selectmode = SELECT_ADD;
    else
      selectmode = SELECT_SUBTRACT;

            mval[0]= rect.xmin;
            mval[1]= rect.ymin+2;
            areamouseco_to_ipoco(G.v2d, mval, &rectf.xmin, &rectf.ymin);
            mval[0]= rect.xmax;
            mval[1]= rect.ymax-2;
            areamouseco_to_ipoco(G.v2d, mval, &rectf.xmax, &rectf.ymax);
            
            ymax= count_action_levels(act) * (CHANNELHEIGHT+CHANNELSKIP);
            ymax += CHANNELHEIGHT/2;
            
            for (chan=act->chanbase.first; chan; chan=chan->next){
                  
                  /* Check action */
                  ymin=ymax-(CHANNELHEIGHT+CHANNELSKIP);
                  if (!((ymax < rectf.ymin) || (ymin > rectf.ymax)))
          borderselect_ipo_key(chan->ipo, rectf.xmin, rectf.xmax,
                               selectmode);

                  ymax=ymin;

                  /* Check constraints */
                  for (conchan=chan->constraintChannels.first; conchan; conchan=conchan->next){
                        ymin=ymax-(CHANNELHEIGHT+CHANNELSKIP);
                        if (!((ymax < rectf.ymin) || (ymin > rectf.ymax)))
                              borderselect_ipo_key(conchan->ipo, rectf.xmin, rectf.xmax,
                               selectmode);
                        
                        ymax=ymin;
                  }
            }     
            BIF_undo_push("Border Select Action");
            allqueue(REDRAWNLA, 0);
            allqueue(REDRAWACTION, 0);
            allqueue(REDRAWIPO, 0);
      }
}

void borderselect_mesh(Key *key)
{ 
      rcti     rect;
      int      val, adrcodemax, adrcodemin;
      short  mval[2];
      float  xmin, xmax;
      int      (*select_function)(BezTriple *);
      IpoCurve *icu;

      if ( (val = get_border(&rect, 3)) ){
            /* set the selection function based on what
             * mouse button had been used in the border
             * select
             */
            if (val == LEFTMOUSE)
                  select_function = select_bezier_add;
            else
                  select_function = select_bezier_subtract;

            /* get the minimum and maximum adrcode numbers
             * for the IpoCurves (this is the number that
             * relates an IpoCurve to the keyblock it
             * controls).
             */
            mval[0]= rect.xmin;
            mval[1]= rect.ymin+2;
            adrcodemax = get_nearest_key_num(key, mval, &xmin);
            adrcodemax = (adrcodemax >= key->totkey) ? key->totkey : adrcodemax;

            mval[0]= rect.xmax;
            mval[1]= rect.ymax-2;
            adrcodemin = get_nearest_key_num(key, mval, &xmax);
            adrcodemin = (adrcodemin < 1) ? 1 : adrcodemin;

            /* Lets loop throug the IpoCurves and do borderselect
             * on the curves with adrcodes in our selected range.
             */
            for (icu = key->ipo->curve.first; icu ; icu = icu->next) {
                  /* lets not deal with the "speed" Ipo
                   */
                  if (!icu->adrcode) continue;
                  if ( (icu->adrcode >= adrcodemin) && 
                         (icu->adrcode <= adrcodemax) ) {
                        borderselect_icu_key(icu, xmin, xmax, select_function);
                  }
            }

            /* redraw stuff */
            BIF_undo_push("Border select Action Key");
            allqueue(REDRAWNLA, 0);
            allqueue(REDRAWACTION, 0);
            allqueue(REDRAWIPO, 0);
      }
}

/* used by ipo, outliner, buttons to find the active channel */
bActionChannel* get_hilighted_action_channel(bAction* action)
{
      bActionChannel *chan;

      if (!action)
            return NULL;

      for (chan=action->chanbase.first; chan; chan=chan->next){
            if (chan->flag & ACHAN_SELECTED && chan->flag & ACHAN_HILIGHTED)
                  return chan;
      }

      return NULL;

}

void set_exprap_action(int mode)
{
      if(G.saction->action && G.saction->action->id.lib) return;

      error ("Not yet implemented!");
}

void free_posebuf(void) 
{
      if (g_posebuf){
            clear_pose(g_posebuf);
            MEM_freeN (g_posebuf);
      }
      g_posebuf=NULL;
}

void copy_posebuf (void)
{
      Object *ob;

      free_posebuf();

      ob=G.obpose;
      if (!ob){
            error ("Copy buffer is empty");
            return;
      }

      filter_pose_keys();
      copy_pose(&g_posebuf, ob->pose, 0);

}

static void flip_name (char *name)
{

      char  prefix[128]={""}; /* The part before the facing */
      char  suffix[128]={""}; /* The part after the facing */
      char  replace[128]={""};      /* The replacement string */

      char  *index=NULL;
      /* Find the last period */

      strcpy (prefix, name);

      /* Check for an instance of .Right */
      if (!index){
            index = strstr (prefix, "Right");
            if (index){
                  *index=0;
                  strcpy (replace, "Left");
                  strcpy (suffix, index+6);
            }
      }

      /* Che ck for an instance of .RIGHT */
      if (!index){
            index = strstr (prefix, "RIGHT");
            if (index){
                  *index=0;
                  strcpy (replace, "LEFT");
                  strcpy (suffix, index+6);
            }
      }


      /* Check for an instance of .right */
      if (!index){
            index = strstr (prefix, "right");
            if (index){
                  *index=0;
                  strcpy (replace, "left");
                  strcpy (suffix, index+6);
            }
      }

      /* Check for an instance of .left */
      if (!index){
            index = strstr (prefix, "left");
            if (index){
                  *index=0;
                  strcpy (replace, "right");
                  strcpy (suffix, index+5);
            }
      }

      /* Check for an instance of .LEFT */
      if (!index){
            index = strstr (prefix, "LEFT");
            if (index){
                  *index=0;
                  strcpy (replace, "RIGHT");
                  strcpy (suffix, index+5);
            }
      }

      /* Check for an instance of .Left */
      if (!index){
            index = strstr (prefix, "Left");
            if (index){
                  *index=0;
                  strcpy (replace, "Right");
                  strcpy (suffix, index+5);
            }
      }

      /* check for an instance of .L */
      if (!index){
            index = strstr (prefix, ".L");
            if (index){
                  *index=0;
                  strcpy (replace, ".R");
                  strcpy (suffix, index+2);
            }
      }

      /* check for an instance of .l */
      if (!index){
            index = strstr (prefix, ".l");
            if (index){
                  *index=0;
                  strcpy (replace, ".r");
                  strcpy (suffix, index+2);
            }
      }

      /* Checl for an instance of .R */
      if (!index){
            index = strstr (prefix, ".R");
            if (index){
                  *index=0;
                  strcpy (replace, ".L");
                  strcpy (suffix, index+2);
            }
      }

      /* Checl for an instance of .r */
      if (!index){
            index = strstr (prefix, ".r");
            if (index){
                  *index=0;
                  strcpy (replace, ".l");
                  strcpy (suffix, index+2);
            }
      }

      sprintf (name, "%s%s%s", prefix, replace, suffix);
}

void paste_posebuf (int flip){
      Object *ob;
      bPoseChannel *temp, *chan;
      float eul[4];
      Base  *base;
      int         newchan = 0;

      ob=G.obpose;
      if (!ob)
            return;

      if (!g_posebuf){
            error ("Copy buffer is empty");
            return;
      };
      
      collect_pose_garbage(ob);

      /* Safely merge all of the channels in this pose into
      any existing pose */
      if (ob->pose){
            if (U.uiflag & USER_KEYINSERTACT){
                  /* Display "Avail, all" dialog */
            }
            for (chan=g_posebuf->chanbase.first; chan; chan=chan->next){
                  if (chan->flag & POSE_KEY){
                        temp = copy_pose_channel (chan);
                        if (flip){
                              flip_name (temp->name);
                              temp->loc[0]*=-1;

                              QuatToEul(temp->quat, eul);
                              eul[1]*=-1;
                              eul[2]*=-1;
                              EulToQuat(eul, temp->quat);
                        }

                        temp = set_pose_channel (ob->pose, temp);

                        if (U.uiflag & USER_KEYINSERTACT){
                              /* Set keys on pose */
                              if (chan->flag & POSE_ROT){
                                    set_action_key(ob->action, temp, AC_QUAT_X, newchan);
                                    set_action_key(ob->action, temp, AC_QUAT_Y, newchan);
                                    set_action_key(ob->action, temp, AC_QUAT_Z, newchan);
                                    set_action_key(ob->action, temp, AC_QUAT_W, newchan);
                              };
                              if (chan->flag & POSE_SIZE){
                                    set_action_key(ob->action, temp, AC_SIZE_X, newchan);
                                    set_action_key(ob->action, temp, AC_SIZE_Y, newchan);
                                    set_action_key(ob->action, temp, AC_SIZE_Z, newchan);
                              };
                              if (chan->flag & POSE_LOC){
                                    set_action_key(ob->action, temp, AC_LOC_X, newchan);
                                    set_action_key(ob->action, temp, AC_LOC_Y, newchan);
                                    set_action_key(ob->action, temp, AC_LOC_Z, newchan);
                              };                            
                        }
                  }
            }

            if (U.uiflag & USER_KEYINSERTACT){
                  remake_action_ipos(ob->action);
                  allqueue (REDRAWIPO, 0);
                  allqueue (REDRAWVIEW3D, 0);
                  allqueue (REDRAWACTION, 0);         
                  allqueue(REDRAWNLA, 0);
            }

            /* Update deformation children */
            if (G.obpose->type == OB_ARMATURE){
                  for (base= FIRSTBASE; base; base= base->next){
                        if (G.obpose==base->object->parent){
                              if (base->object->partype==PARSKEL)
                                    makeDispList(base->object);
                        }
                  }
            }
            BIF_undo_push("Paste Action Pose");
      }
}

void set_action_key (struct bAction *act, struct bPoseChannel *chan, int adrcode, short makecurve)
{
      set_action_key_time (act, chan, adrcode, makecurve, frame_to_float(CFRA));
}

static void set_action_key_time (bAction *act, bPoseChannel *chan, int adrcode, short makecurve, float time)
{
      bActionChannel    *achan;
      char  ipstr[256];

      if (!act)
            return;

      if (!chan)
            return;
      /* See if this action channel exists already */ 
      for (achan=act->chanbase.first; achan; achan=achan->next){
            if (!strcmp (chan->name, achan->name))
                  break;
      }

      if (!achan){
            if (!makecurve)
                  return;
            achan = MEM_callocN (sizeof(bActionChannel), "actionChannel");
            strcpy (achan->name, chan->name);
            BLI_addtail (&act->chanbase, achan);
      }

      /* Ensure the channel appears selected in the action window */
      /* ton: added flag hilighted, for display in ipowin. dunno what the difference is between select/hilite */
      achan->flag |= ACHAN_SELECTED|ACHAN_HILIGHTED;

      /* Ensure this action channel has a valid Ipo */
      if (!achan->ipo){
            sprintf (ipstr, "%s.%s", act->id.name+2, chan->name);
            ipstr[23]=0;
            achan->ipo= add_ipo(ipstr, ID_AC);  
      }

      insertactionkey(act, achan, chan, adrcode, makecurve, time);

}

static void insertactionkey(bAction *act, bActionChannel *achan, bPoseChannel *chan, int adrcode, short makecurve, float cfra)
{
      IpoCurve *icu;
      void *poin;
      float curval;
      int type;
      ID *id;
      
      if (!act){
            return;
      }
      if (act->id.lib){
            error ("Can't pose library actions");
            return;
      }
      act->achan=achan;
      act->pchan=chan;

      id=(ID*) act;

      /* First see if this curve exists */
      if (!makecurve){
            if (!achan->ipo)
                  return;

            for (icu = achan->ipo->curve.first; icu; icu=icu->next){
                  if (icu->adrcode == adrcode)
                        break;
            }
            if (!icu)
                  return;
      }

      
      icu = get_ipocurve (id, GS(id->name), adrcode, achan->ipo);

      if(icu) {
            poin= get_ipo_poin(id, icu, &type);
            if(poin) {
                  curval= read_ipo_poin(poin, type);
      //          cfra= frame_to_float(CFRA);
                  insert_vert_ipo(icu, cfra, curval);
            }
      }
      
}

bAction *add_empty_action(void)
{
      bAction *act;

      act= alloc_libblock(&G.main->action, ID_AC, "Action");
      act->id.flag |= LIB_FAKEUSER;
      act->id.us++;
      return act;
}

void transform_actionchannel_keys(char mode)
{
      bAction     *act;
      TransVert *tv;
      int /*sel=0,*/  i;
      bActionChannel    *chan;
      short mvals[2], mvalc[2], cent[2];
      float sval[2], cval[2], lastcval[2];
      short cancel=0;
      float fac=0.0F;
      int         loop=1;
      int         tvtot=0;
      float deltax, startx;
      float cenf[2];
      int         invert=0, firsttime=1;
      char  str[256];
      bConstraintChannel *conchan;

      act=G.saction->action;

      /* Ensure that partial selections result in beztriple selections */
      for (chan=act->chanbase.first; chan; chan=chan->next){
            tvtot+=fullselect_ipo_keys(chan->ipo);

            for (conchan=chan->constraintChannels.first; conchan; conchan=conchan->next)
                  tvtot+=fullselect_ipo_keys(conchan->ipo);
      }
      
      /* If nothing is selected, bail out */
      if (!tvtot)
            return;
      
      
      /* Build the transvert structure */
      tv = MEM_callocN (sizeof(TransVert) * tvtot, "transVert");
      tvtot=0;
      for (chan=act->chanbase.first; chan; chan=chan->next){
            /* Add the actionchannel */
            tvtot = add_trans_ipo_keys(chan->ipo, tv, tvtot);
            for (conchan=chan->constraintChannels.first; conchan; conchan=conchan->next)
                  tvtot = add_trans_ipo_keys(conchan->ipo, tv, tvtot);
      }

      /* Do the event loop */
      cent[0] = curarea->winx + (G.saction->v2d.hor.xmax)/2;
      cent[1] = curarea->winy + (G.saction->v2d.hor.ymax)/2;
      areamouseco_to_ipoco(G.v2d, cent, &cenf[0], &cenf[1]);

      getmouseco_areawin (mvals);
      areamouseco_to_ipoco(G.v2d, mvals, &sval[0], &sval[1]);

      startx=sval[0];
      while (loop) {
            /*          Get the input */
            /*          If we're cancelling, reset transformations */
            /*                Else calc new transformation */
            /*          Perform the transformations */
            while (qtest()) {
                  short val;
                  unsigned short event= extern_qread(&val);

                  if (val) {
                        switch (event) {
                        case LEFTMOUSE:
                        case SPACEKEY:
                        case RETKEY:
                              loop=0;
                              break;
                        case XKEY:
                              break;
                        case ESCKEY:
                        case RIGHTMOUSE:
                              cancel=1;
                              loop=0;
                              break;
                        default:
                              arrows_move_cursor(event);
                              break;
                        };
                  }
            }

            if (cancel) {
                  for (i=0; i<tvtot; i++) {
                        tv[i].loc[0]=tv[i].oldloc[0];
                        tv[i].loc[1]=tv[i].oldloc[1];
                  }
            } else {
                  getmouseco_areawin (mvalc);
                  areamouseco_to_ipoco(G.v2d, mvalc, &cval[0], &cval[1]);

                  if (!firsttime && lastcval[0]==cval[0] && lastcval[1]==cval[1]) {
                        PIL_sleep_ms(1);
                  } else {
                        for (i=0; i<tvtot; i++){
                              tv[i].loc[0]=tv[i].oldloc[0];

                              switch (mode){
                              case 'g':
                                    deltax = cval[0]-sval[0];
                                    fac= deltax;
                                    
                                    apply_keyb_grid(&fac, 0.0, 1.0, 0.1, U.flag & USER_AUTOGRABGRID);

                                    tv[i].loc[0]+=fac;
                                    break;
                              case 's':
                                    startx=mvals[0]-(ACTWIDTH/2+(curarea->winrct.xmax-curarea->winrct.xmin)/2);
                                    deltax=mvalc[0]-(ACTWIDTH/2+(curarea->winrct.xmax-curarea->winrct.xmin)/2);
                                    fac= fabs(deltax/startx);
                                    
                                    apply_keyb_grid(&fac, 0.0, 0.2, 0.1, U.flag & USER_AUTOSIZEGRID);
            
                                    if (invert){
                                          if (i % 03 == 0){
                                                memcpy (tv[i].loc, tv[i].oldloc, sizeof(tv[i+2].oldloc));
                                          }
                                          if (i % 03 == 2){
                                                memcpy (tv[i].loc, tv[i].oldloc, sizeof(tv[i-2].oldloc));
                                          }
      
                                          fac*=-1;
                                    }
                                    startx= (G.scene->r.cfra);
                              
                                    tv[i].loc[0]-= startx;
                                    tv[i].loc[0]*=fac;
                                    tv[i].loc[0]+= startx;
            
                                    break;
                              }
                        }
                  }
      
                  if (mode=='s'){
                        sprintf(str, "sizeX: %.3f", fac);
                        headerprint(str);
                  }
                  else if (mode=='g'){
                        sprintf(str, "deltaX: %.3f", fac);
                        headerprint(str);
                  }
      
                  if (G.saction->lock){
                        do_all_actions();
                        force_draw_all(0);
                  }
                  else {
                        force_draw(0);
                  }
            }
            
            lastcval[0]= cval[0];
            lastcval[1]= cval[1];
            firsttime= 0;
      }
      
      /*          Update the curve */
      /*          Depending on the lock status, draw necessary views */

      do_all_actions();
      remake_action_ipos(act);

      if(cancel==0) BIF_undo_push("Transform Action");
      allqueue (REDRAWVIEW3D, 0);
      allqueue (REDRAWACTION, 0);
      allqueue(REDRAWNLA, 0);
      allqueue (REDRAWIPO, 0);
      MEM_freeN (tv);
}

void transform_meshchannel_keys(char mode, Key *key)
{
      /* this is the function that determines what happens
       * to those little blocky rvk key things you have selected 
       * after you press a 'g' or an 's'. I'd love to say that
       * I have an intimate knowledge of all of what this function
       * is doing, but instead I'm just going to pretend.
       */
    TransVert *tv;
    int /*sel=0,*/  i;
    short   mvals[2], mvalc[2], cent[2];
    float   sval[2], cval[2], lastcval[2];
    short   cancel=0;
    float   fac=0.0F;
    int           loop=1;
    int           tvtot=0;
    float   deltax, startx;
    float   cenf[2];
    int           invert=0, firsttime=1;
    char    str[256];

      /* count all of the selected beziers, and
       * set all 3 control handles to selected
       */
    tvtot=fullselect_ipo_keys(key->ipo);
    
    /* If nothing is selected, bail out 
       */
    if (!tvtot)
        return;
      
      
    /* Build the transvert structure 
       */
    tv = MEM_callocN (sizeof(TransVert) * tvtot, "transVert");
    tvtot=0;

    tvtot = add_trans_ipo_keys(key->ipo, tv, tvtot);

    /* Do the event loop 
       */
    cent[0] = curarea->winx + (G.saction->v2d.hor.xmax)/2;
    cent[1] = curarea->winy + (G.saction->v2d.hor.ymax)/2;
    areamouseco_to_ipoco(G.v2d, cent, &cenf[0], &cenf[1]);

    getmouseco_areawin (mvals);
    areamouseco_to_ipoco(G.v2d, mvals, &sval[0], &sval[1]);

    startx=sval[0];
    while (loop) {
        /* Get the input
             * If we're cancelling, reset transformations
             * Else calc new transformation
             * Perform the transformations 
             */
        while (qtest()) {
            short val;
            unsigned short event= extern_qread(&val);

            if (val) {
                switch (event) {
                case LEFTMOUSE:
                case SPACEKEY:
                case RETKEY:
                    loop=0;
                    break;
                case XKEY:
                    break;
                case ESCKEY:
                case RIGHTMOUSE:
                    cancel=1;
                    loop=0;
                    break;
                default:
                    arrows_move_cursor(event);
                    break;
                };
            }
        }
        
        if (cancel) {
            for (i=0; i<tvtot; i++) {
                tv[i].loc[0]=tv[i].oldloc[0];
                tv[i].loc[1]=tv[i].oldloc[1];
            }
        } 
            else {
            getmouseco_areawin (mvalc);
            areamouseco_to_ipoco(G.v2d, mvalc, &cval[0], &cval[1]);
                  
            if (!firsttime && lastcval[0]==cval[0] && lastcval[1]==cval[1]) {
                PIL_sleep_ms(1);
            } else {
                for (i=0; i<tvtot; i++){
                    tv[i].loc[0]=tv[i].oldloc[0];

                    switch (mode){
                    case 'g':
                        deltax = cval[0]-sval[0];
                        fac= deltax;
                                    
                        apply_keyb_grid(&fac, 0.0, 1.0, 0.1, 
                                        U.flag & USER_AUTOGRABGRID);

                        tv[i].loc[0]+=fac;
                        break;
                    case 's':
                        startx=mvals[0]-(ACTWIDTH/2+(curarea->winrct.xmax
                                                     -curarea->winrct.xmin)/2);
                        deltax=mvalc[0]-(ACTWIDTH/2+(curarea->winrct.xmax
                                                     -curarea->winrct.xmin)/2);
                        fac= fabs(deltax/startx);
                                    
                        apply_keyb_grid(&fac, 0.0, 0.2, 0.1, 
                                        U.flag & USER_AUTOSIZEGRID);
            
                        if (invert){
                            if (i % 03 == 0){
                                memcpy (tv[i].loc, tv[i].oldloc, 
                                        sizeof(tv[i+2].oldloc));
                            }
                            if (i % 03 == 2){
                                memcpy (tv[i].loc, tv[i].oldloc, 
                                        sizeof(tv[i-2].oldloc));
                            }
                                          
                            fac*=-1;
                        }
                        startx= (G.scene->r.cfra);
                        
                        tv[i].loc[0]-= startx;
                        tv[i].loc[0]*=fac;
                        tv[i].loc[0]+= startx;
            
                        break;
                    }
                }
            }
                  /* Display a message showing the magnitude of
                   * the grab/scale we are performing
                   */
            if (mode=='s'){
                sprintf(str, "sizeX: %.3f", fac);
                headerprint(str);
            }
            else if (mode=='g'){
                sprintf(str, "deltaX: %.3f", fac);
                headerprint(str);
            }
      
            if (G.saction->lock){
                        /* doubt any of this code ever gets
                         * executed, but it might in the
                         * future
                         */
                         
                do_all_actions();
                allqueue (REDRAWVIEW3D, 0);
                allqueue (REDRAWACTION, 0);
                allqueue (REDRAWIPO, 0);
                allqueue(REDRAWNLA, 0);
                force_draw_all(0);
            }
            else {
                addqueue (curarea->win, REDRAWALL, 0);
                force_draw(0);
            }
        }
            
        lastcval[0]= cval[0];
        lastcval[1]= cval[1];
        firsttime= 0;
    }
      
      /* fix up the Ipocurves and redraw stuff
       */
    meshkey_do_redraw(key);
      BIF_undo_push("Transform Action Keys");

    MEM_freeN (tv);

      /* did you understand all of that? I pretty much understand
       * what it does, but the specifics seem a little weird and crufty.
       */
}


void deselect_actionchannel_keys (bAction *act, int test)
{
      bActionChannel    *chan;
      bConstraintChannel *conchan;
      int         sel=1;;

      if (!act)
            return;

      /* Determine if this is selection or deselection */
      
      if (test){
            for (chan=act->chanbase.first; chan; chan=chan->next){
                  /* Test the channel ipos */
                  if (is_ipo_key_selected(chan->ipo)){
                        sel = 0;
                        break;
                  }

                  /* Test the constraint ipos */
                  for (conchan=chan->constraintChannels.first; conchan; conchan=conchan->next){
                        if (is_ipo_key_selected(conchan->ipo)){
                              sel = 0;
                              break;
                        }
                  }

                  if (sel == 0)
                        break;
            }
      }
      else
            sel=0;
      
      /* Set the flags */
      for (chan=act->chanbase.first; chan; chan=chan->next){
            set_ipo_key_selection(chan->ipo, sel);
            for (conchan=chan->constraintChannels.first; conchan; conchan=conchan->next)
                  set_ipo_key_selection(conchan->ipo, sel);
      }
}

void deselect_meshchannel_keys (Key *key, int test)
{
      /* should deselect the rvk keys
       */
    int           sel=1;

    /* Determine if this is selection or deselection */
    if (test){
        if (is_ipo_key_selected(key->ipo)){
            sel = 0;
        }
    }
    else {
        sel=0;
    }
      
    /* Set the flags */
    set_ipo_key_selection(key->ipo, sel);
}

void deselect_actionchannels (bAction *act, int test)
{
      bActionChannel *chan;
      bConstraintChannel *conchan;
      int               sel=1;      

      if (!act)
            return;

      /* See if we should be selecting or deselecting */
      if (test){
            for (chan=act->chanbase.first; chan; chan=chan->next){
                  if (!sel)
                        break;

                  if (chan->flag & ACHAN_SELECTED){
                        sel=0;
                        break;
                  }
                  if (sel){
                        for (conchan=chan->constraintChannels.first; conchan; conchan=conchan->next){
                              if (conchan->flag & CONSTRAINT_CHANNEL_SELECT){
                                    sel=0;
                                    break;
                              }
                        }
                  }
            }
      }
      else
            sel=0;

      /* Now set the flags */
      for (chan=act->chanbase.first; chan; chan=chan->next){
            select_poseelement_by_name(chan->name, sel);

            if (sel)
                  chan->flag |= ACHAN_SELECTED;
            else
                  chan->flag &= ~ACHAN_SELECTED;

            for (conchan=chan->constraintChannels.first; conchan; conchan=conchan->next){
                  if (sel)
                        conchan->flag |= CONSTRAINT_CHANNEL_SELECT;
                  else
                        conchan->flag &= ~CONSTRAINT_CHANNEL_SELECT;
            }
      }

}

static void hilight_channel (bAction *act, bActionChannel *chan, short select)
{
      bActionChannel *curchan;

      if (!act)
            return;

      for (curchan=act->chanbase.first; curchan; curchan=curchan->next){
            if (curchan==chan && select)
                  curchan->flag |= ACHAN_HILIGHTED;
            else
                  curchan->flag &= ~ACHAN_HILIGHTED;
      }
}

/* select_mode = SELECT_REPLACE
 *             = SELECT_ADD
 *             = SELECT_SUBTRACT
 *             = SELECT_INVERT
 */

/* exported for outliner (ton) */
int select_channel(bAction *act, bActionChannel *chan,
                          int selectmode) {
      /* Select the channel based on the selection mode
       */
      int flag;

      switch (selectmode) {
      case SELECT_ADD:
            chan->flag |= ACHAN_SELECTED;
            break;
      case SELECT_SUBTRACT:
            chan->flag &= ~ACHAN_SELECTED;
            break;
      case SELECT_INVERT:
            chan->flag ^= ACHAN_SELECTED;
            break;
      }
      flag = (chan->flag & ACHAN_SELECTED) ? 1 : 0;

      hilight_channel(act, chan, flag);
      select_poseelement_by_name(chan->name, flag);

      return flag;
}

static int select_constraint_channel(bAction *act, 
                                     bConstraintChannel *conchan, 
                                     int selectmode) {
      /* Select the constraint channel based on the selection mode
       */
      int flag;

      switch (selectmode) {
      case SELECT_ADD:
            conchan->flag |= CONSTRAINT_CHANNEL_SELECT;
            break;
      case SELECT_SUBTRACT:
            conchan->flag &= ~CONSTRAINT_CHANNEL_SELECT;
            break;
      case SELECT_INVERT:
            conchan->flag ^= CONSTRAINT_CHANNEL_SELECT;
            break;
      }
      flag = (conchan->flag & CONSTRAINT_CHANNEL_SELECT) ? 1 : 0;

      return flag;
}


static void mouse_actionchannels(bAction *act, short *mval,
                                 short *mvalo, int selectmode) {
      /* Select action channels, based on mouse values.
       * If mvalo is NULL we assume it is a one click
       * action, other wise we treat it like it is a
       * border select with mval[0],mval[1] and
       * mvalo[0], mvalo[1] forming the corners of
       * a rectangle.
       */
      bActionChannel *chan;
      float click, x,y;
      int   clickmin, clickmax;
      int         wsize, sel;
      bConstraintChannel *conchan;

      if (!act)
            return;
  
      if (selectmode == SELECT_REPLACE) {
            deselect_actionchannels (act, 0);
            selectmode = SELECT_ADD;
      }

      /* wsize is the greatest possible height (in pixels) that would be
       * needed to draw all of the action channels and constraint
       * channels.
       */
      wsize =  count_action_levels(act)*(CHANNELHEIGHT+CHANNELSKIP);
      wsize += CHANNELHEIGHT/2;

    areamouseco_to_ipoco(G.v2d, mval, &x, &y);
    clickmin = (int) ((wsize - y) / (CHANNELHEIGHT+CHANNELSKIP));
      
      /* Only one click */
      if (mvalo == NULL) {
            clickmax = clickmin;
      }
      /* Two click values (i.e., border select */
      else {
            areamouseco_to_ipoco(G.v2d, mvalo, &x, &y);
            click = ((wsize - y) / (CHANNELHEIGHT+CHANNELSKIP));

            if ( ((int) click) < clickmin) {
                  clickmax = clickmin;
                  clickmin = (int) click;
            }
            else {
                  clickmax = (int) click;
            }
      }

      if (clickmax < 0) {
            return;
      }

      /* clickmin and clickmax now coorespond to indices into
       * the collection of channels and constraint channels.
       * What we need to do is apply the selection mode on all
       * channels and constraint channels between these indices.
       * This is done by traversing the channels and constraint
       * channels, for each item decrementing clickmin and clickmax.
       * When clickmin is less than zero we start selecting stuff,
       * until clickmax is less than zero or we run out of channels
       * and constraint channels.
       */

      for (chan = act->chanbase.first; chan; chan=chan->next){
            if (clickmax < 0) break;

            if ( clickmin <= 0) {
                  /* Select the channel with the given mode. If the
                   * channel is freshly selected then set it to the
                   * active channel for the action
                   */
                  sel = (chan->flag & ACHAN_SELECTED);
                  if ( select_channel(act, chan, selectmode) && !sel ) {
                        act->achan = chan;
                  }
            }
            --clickmin;
            --clickmax;

            /* Check for click in a constraint */
            for (conchan=chan->constraintChannels.first; 
                   conchan; conchan=conchan->next){
                  if (clickmax < 0) break;
                  if ( clickmin <= 0) {
                        select_constraint_channel(act, conchan, selectmode);
                  }
                  --clickmin;
                  --clickmax;
            }
      }

      allqueue (REDRAWIPO, 0);
      allqueue (REDRAWVIEW3D, 0);
      allqueue (REDRAWACTION, 0);
      allqueue (REDRAWNLA, 0);
}

void delete_meshchannel_keys(Key *key)
{
      if (!okee("Erase selected keys"))
            return;

      BIF_undo_push("Delete Action keys");
      delete_ipo_keys(key->ipo);

      meshkey_do_redraw(key);
}

void delete_actionchannel_keys(void)
{
      bAction *act;
      bActionChannel *chan;
      bConstraintChannel *conchan;

      act = G.saction->action;
      if (!act)
            return;

      if (!okee("Erase selected keys"))
            return;

      for (chan = act->chanbase.first; chan; chan=chan->next){

            /* Check action channel keys*/
            delete_ipo_keys(chan->ipo);

            /* Delete constraint channel keys */
            for (conchan=chan->constraintChannels.first; conchan; conchan=conchan->next)
                  delete_ipo_keys(conchan->ipo);
      }

      remake_action_ipos (act);
      BIF_undo_push("Delete Action keys");
      allspace(REMAKEIPO, 0);
      allqueue(REDRAWACTION, 0);
      allqueue(REDRAWIPO, 0);
      allqueue(REDRAWNLA, 0);

}
static void delete_actionchannels (void)
{
      bConstraintChannel *conchan=NULL, *nextconchan;
      bActionChannel *chan, *next;
      bAction     *act;
      int freechan;

      act=G.saction->action;

      if (!act)
            return;

      for (chan=act->chanbase.first; chan; chan=chan->next){
            if (chan->flag & ACHAN_SELECTED)
                  break;
            for (conchan=chan->constraintChannels.first; conchan; conchan=conchan->next)
            {
                  if (conchan->flag & CONSTRAINT_CHANNEL_SELECT){
                        chan=act->chanbase.last;
                        break;
                  }
            }
      }

      if (!chan && !conchan)
            return;

      if (!okee("Erase selected channels"))
            return;

      for (chan=act->chanbase.first; chan; chan=next){
            freechan = 0;
            next=chan->next;
            
            /* Remove action channels */
            if (chan->flag & ACHAN_SELECTED){
                  if (chan->ipo)
                        chan->ipo->id.us--;     /* Release the ipo */
                  freechan = 1;
            }
            
            /* Remove constraint channels */
            for (conchan=chan->constraintChannels.first; conchan; conchan=nextconchan){
                  nextconchan=conchan->next;
                  if (freechan || conchan->flag & CONSTRAINT_CHANNEL_SELECT){
                        if (conchan->ipo)
                              conchan->ipo->id.us--;
                        BLI_freelinkN(&chan->constraintChannels, conchan);
                  }
            }
            
            if (freechan)
                  BLI_freelinkN (&act->chanbase, chan);

      }

      BIF_undo_push("Delete Action channels");
      allqueue (REDRAWACTION, 0);
      allqueue(REDRAWNLA, 0);

}

void sethandles_meshchannel_keys(int code, Key *key)
{
    sethandles_ipo_keys(key->ipo, code);

      BIF_undo_push("Set handles Action keys");
      meshkey_do_redraw(key);
}

void sethandles_actionchannel_keys(int code)
{
      bAction *act;
      bActionChannel *chan;

      /* Get the selected action, exit if none are selected 
       */
      act = G.saction->action;
      if (!act)
            return;

      /* Loop through the channels and set the beziers
       * of the selected keys based on the integer code
       */
      for (chan = act->chanbase.first; chan; chan=chan->next){
            sethandles_ipo_keys(chan->ipo, code);
      }

      /* Clean up and redraw stuff
       */
      remake_action_ipos (act);
      BIF_undo_push("Set handles Action channel");
      allspace(REMAKEIPO, 0);
      allqueue(REDRAWACTION, 0);
      allqueue(REDRAWIPO, 0);
      allqueue(REDRAWNLA, 0);
}

void set_ipotype_actionchannels(int ipotype) {

      bAction *act; 
      bActionChannel *chan;
      short event;

      /* Get the selected action, exit if none are selected 
       */
      act = G.saction->action;
      if (!act)
            return;

      if (ipotype == SET_IPO_POPUP) {
            /* Present a popup menu asking the user what type
             * of IPO curve he/she/GreenBTH wants. ;)
             */
            event
                  =  pupmenu("Channel Ipo Type %t|"
                                 "Constant %x1|"
                                 "Linear %x2|"
                                 "Bezier %x3");
            if(event < 1) return;
            ipotype = event;
      }
      
      /* Loop through the channels and for the selected ones set
       * the type for each Ipo curve in the channel Ipo (based on
       * the value from the popup).
       */
      for (chan = act->chanbase.first; chan; chan=chan->next){
            if (chan->flag & ACHAN_SELECTED){
                  if (chan->ipo)
                        setipotype_ipo(chan->ipo, ipotype);
            }
      }

      /* Clean up and redraw stuff
       */
      remake_action_ipos (act);
      BIF_undo_push("Set Ipo type Action channel");
      allspace(REMAKEIPO, 0);
      allqueue(REDRAWACTION, 0);
      allqueue(REDRAWIPO, 0);
      allqueue(REDRAWNLA, 0);
}

void select_all_keys_frames(bAction *act, short *mval, 
                                          short *mvalo, int selectmode) {
      
      /* This function tries to select all action keys in
       * every channel for a given range of keyframes that
       * are within the mouse values mval and mvalo (usually
       * the result of a border select). If mvalo is passed as
       * NULL then the selection is treated as a one-click and
       * the function tries to select all keys within half a
       * frame of the click point.
       */
      
      rcti rect;
      rctf rectf;
      bActionChannel *chan;
      bConstraintChannel *conchan;

      if (!act)
            return;

      if (selectmode == SELECT_REPLACE) {
            deselect_actionchannel_keys(act, 0);
            selectmode = SELECT_ADD;
      }

      if (mvalo == NULL) {
            rect.xmin = rect.xmax = mval[0];
            rect.ymin = rect.ymax = mval[1];
      }
      else {
            if (mval[0] < mvalo[0] ) {
                  rect.xmin = mval[0];
                  rect.xmax = mvalo[0];
            }
            else {
                  rect.xmin = mvalo[0];
                  rect.xmax = mval[0];
            }
            if (mval[1] < mvalo[1] ) {
                  rect.ymin = mval[1];
                  rect.ymax = mvalo[1];
            }
            else {
                  rect.ymin = mvalo[1];
                  rect.ymax = mval[1];
            }
      }

      mval[0]= rect.xmin;
      mval[1]= rect.ymin+2;
      areamouseco_to_ipoco(G.v2d, mval, &rectf.xmin, &rectf.ymin);
      mval[0]= rect.xmax;
      mval[1]= rect.ymax-2;
      areamouseco_to_ipoco(G.v2d, mval, &rectf.xmax, &rectf.ymax);

      if (mvalo == NULL) {
            rectf.xmin = rectf.xmin - 0.5;
            rectf.xmax = rectf.xmax + 0.5;
      }
    
      for (chan=act->chanbase.first; chan; chan=chan->next){
            borderselect_ipo_key(chan->ipo, rectf.xmin, rectf.xmax,
                                           selectmode);
            for (conchan=chan->constraintChannels.first; conchan; 
                   conchan=conchan->next){
                  borderselect_ipo_key(conchan->ipo, rectf.xmin, rectf.xmax,
                                                 selectmode);
            }
      }     

      allqueue(REDRAWNLA, 0);
      allqueue(REDRAWACTION, 0);
      allqueue(REDRAWIPO, 0);
}


void select_all_keys_channels(bAction *act, short *mval, 
                              short *mvalo, int selectmode) {
      bActionChannel    *chan;
      float              click, x,y;
      int                clickmin, clickmax;
      int                wsize;
      bConstraintChannel *conchan;

      /* This function selects all the action keys that
       * are in the mouse selection range defined by
       * the ordered pairs mval and mvalo (usually
       * these 2 are obtained from a border select).
       * If mvalo is NULL, then the selection is
       * treated like a one-click action, and at most
       * one channel is selected.
       */

      /* If the action is null then abort
       */
      if (!act)
            return;

      if (selectmode == SELECT_REPLACE) {
            deselect_actionchannel_keys(act, 0);
            selectmode = SELECT_ADD;
      }

      /* wsize is the greatest possible height (in pixels) that would be
       * needed to draw all of the action channels and constraint
       * channels.
       */

      wsize =  count_action_levels(act)*(CHANNELHEIGHT+CHANNELSKIP);
      wsize += CHANNELHEIGHT/2;
      
    areamouseco_to_ipoco(G.v2d, mval, &x, &y);
    clickmin = (int) ((wsize - y) / (CHANNELHEIGHT+CHANNELSKIP));
      
      /* Only one click */
      if (mvalo == NULL) {
            clickmax = clickmin;
      }
      /* Two click values (i.e., border select) */
      else {

            areamouseco_to_ipoco(G.v2d, mvalo, &x, &y);
            click = ((wsize - y) / (CHANNELHEIGHT+CHANNELSKIP));
            
            if ( ((int) click) < clickmin) {
                  clickmax = clickmin;
                  clickmin = (int) click;
            }
            else {
                  clickmax = (int) click;
            }
      }

      if (clickmax < 0) {
            return;
      }

      for (chan = act->chanbase.first; chan; chan=chan->next){
            if (clickmax < 0) break;

            if ( clickmin <= 0) {
                  /* Select the channel with the given mode. If the
                   * channel is freshly selected then set it to the
                   * active channel for the action
                   */
                  select_ipo_bezier_keys(chan->ipo, selectmode);
            }
            --clickmin;
            --clickmax;

            /* Check for click in a constraint */
            for (conchan=chan->constraintChannels.first; 
                   conchan; conchan=conchan->next){
                  if (clickmax < 0) break;
                  if ( clickmin <= 0) {
                        select_ipo_bezier_keys(chan->ipo, selectmode);
                  }
                  --clickmin;
                  --clickmax;
            }
      }
  
      allqueue (REDRAWIPO, 0);
      allqueue (REDRAWVIEW3D, 0);
      allqueue (REDRAWACTION, 0);
      allqueue (REDRAWNLA, 0);
  
}

static void borderselect_function(void (*select_func)(bAction *act, 
                                                     short *mval, 
                                                     short *mvalo, 
                                                     int selectmode)) {
      /* This function executes an arbitrary selection
       * function as part of a border select. This
       * way the same function that is used for
       * right click selection points can generally
       * be used as the argument to this function
       */
      rcti rect;
      short mval[2], mvalo[2];
      bAction     *act;
      int val;          

      /* Get the selected action, exit if none are selected 
       */
      act=G.saction->action;
      if (!act)
            return;

      /* Let the user draw a border (or abort)
       */
      if ( (val=get_border (&rect, 3)) ) {
            mval[0]= rect.xmin;
            mval[1]= rect.ymin+2;
            mvalo[0]= rect.xmax;
            mvalo[1]= rect.ymax-2;

            /* if the left mouse was used, do an additive
             * selection with the user defined selection
             * function.
             */
            if (val == LEFTMOUSE)
                  select_func(act, mval, mvalo, SELECT_ADD);
            
            /* if the right mouse was used, do a subtractive
             * selection with the user defined selection
             * function.
             */
            else if (val == RIGHTMOUSE)
                  select_func(act, mval, mvalo, SELECT_SUBTRACT);
      }
      BIF_undo_push("Border select Action");
      
}

static void clever_keyblock_names(Key *key, short* mval){
    int        but=0, i, keynum;
    char       str[64];
      float      x;
      KeyBlock   *kb;
      /* get the keynum cooresponding to the y value
       * of the mouse pointer, return if this is
       * an invalid key number (and we don't deal
       * with the speed ipo).
       */

    keynum = get_nearest_key_num(key, mval, &x);
    if ( (keynum < 1) || (keynum >= key->totkey) )
        return;

      kb= key->block.first;
      for (i=0; i<keynum; ++i) kb = kb->next; 

      if (kb->name[0] == '\0') {
            sprintf(str, "Key %d", keynum);
      }
      else {
            strcpy(str, kb->name);
      }

      if ( (kb->slidermin >= kb->slidermax) ) {
            kb->slidermin = 0.0;
            kb->slidermax = 1.0;
      }

    add_numbut(but++, TEX, "KB: ", 0, 24, str, 
               "Does this really need a tool tip?");
      add_numbut(but++, NUM|FLO, "Slider Min:", 
                     -10000, kb->slidermax, &kb->slidermin, 0);
      add_numbut(but++, NUM|FLO, "Slider Max:", 
                     kb->slidermin, 10000, &kb->slidermax, 0);

    if (do_clever_numbuts(str, but, REDRAW)) {
            strcpy(kb->name, str);
        allqueue (REDRAWACTION, 0);
            allspace(REMAKEIPO, 0);
        allqueue (REDRAWIPO, 0);
      }

      
}

static void numbuts_action(void)
{
      /* now called from action window event loop, plus reacts on mouseclick */
      /* removed Hos grunts for that reason! :) (ton) */
    Key *key;
    short mval[2];

    if ( (key = get_action_mesh_key()) ) {
        getmouseco_areawin (mval);
            if (mval[0]<NAMEWIDTH) {
                  clever_keyblock_names(key, mval);
            }
    }
}

void winqreadactionspace(ScrArea *sa, void *spacedata, BWinEvent *evt)
{
      extern void do_actionbuts(unsigned short event); // drawaction.c
      SpaceAction *saction;
      bAction     *act;
      Key *key;
      float dx,dy;
      int doredraw= 0;
      int   cfra;
      short mval[2];
      unsigned short event= evt->event;
      short val= evt->val;
      short mousebut = L_MOUSE;

      if(curarea->win==0) return;

      saction= curarea->spacedata.first;
      if (!saction)
            return;

      act=saction->action;
      if(val) {
            
            if( uiDoBlocks(&curarea->uiblocks, event)!=UI_NOTHING ) event= 0;
            
            /* swap mouse buttons based on user preference */
            if (U.flag & USER_LMOUSESELECT) {
                  if (event == LEFTMOUSE) {
                        event = RIGHTMOUSE;
                        mousebut = L_MOUSE;
                  } else if (event == RIGHTMOUSE) {
                        event = LEFTMOUSE;
                        mousebut = R_MOUSE;
                  }
            }
            
            getmouseco_areawin(mval);

            key = get_action_mesh_key();

            switch(event) {
            case UI_BUT_EVENT:
                  do_actionbuts(val);     // window itself
                  break;
            
            case HOMEKEY:
                  do_action_buttons(B_ACTHOME); // header
                  break;

            case AKEY:
                  if (key) {
                        if (mval[0]<ACTWIDTH){
                              /* to do ??? */
                        }
                        else{
                              deselect_meshchannel_keys(key, 1);
                              allqueue (REDRAWACTION, 0);
                              allqueue(REDRAWNLA, 0);
                              allqueue (REDRAWIPO, 0);
                        }
                  }
                  else {
                        if (mval[0]<NAMEWIDTH){
                              deselect_actionchannels (act, 1);
                              allqueue (REDRAWVIEW3D, 0);
                              allqueue (REDRAWACTION, 0);
                              allqueue(REDRAWNLA, 0);
                              allqueue (REDRAWIPO, 0);
                        }
                        else if (mval[0]>ACTWIDTH){
                              deselect_actionchannel_keys (act, 1);
                              allqueue (REDRAWACTION, 0);
                              allqueue(REDRAWNLA, 0);
                              allqueue (REDRAWIPO, 0);
                        }
                  }
                  break;

            case BKEY:
                  if (key) {
                        if (mval[0]<ACTWIDTH){
                              /* to do?? */
                        }
                        else {
                              borderselect_mesh(key);
                        }
                  }
                  else {

                        /* If the border select is initiated in the
                         * part of the action window where the channel
                         * names reside, then select the channels
                         */
                        if (mval[0]<NAMEWIDTH){
                              borderselect_function(mouse_actionchannels);
                              BIF_undo_push("Select Action");
                        }
                        else if (mval[0]>ACTWIDTH){

                              /* If the border select is initiated in the
                               * vertical scrollbar, then (de)select all keys
                               * for the channels in the selection region
                               */
                              if (IN_2D_VERT_SCROLL(mval)) {
                                    borderselect_function(select_all_keys_channels);
                              }

                              /* If the border select is initiated in the
                               * horizontal scrollbar, then (de)select all keys
                               * for the keyframes in the selection region
                               */
                              else if (IN_2D_HORIZ_SCROLL(mval)) {
                                    borderselect_function(select_all_keys_frames);
                              }

                              /* Other wise, select the action keys
                               */
                              else {
                                    borderselect_action();
                              }
                        }
                  }
                  break;

            case CKEY:
                  /* scroll the window so the current
                   * frame is in the center.
                   */
                  center_currframe();
                  break;

            case DKEY:
                  if (key) {
                        if (G.qual & LR_SHIFTKEY && mval[0]>ACTWIDTH) {
                              duplicate_meshchannel_keys(key);
                        }
                  }
                  else if (act) {
                        if (G.qual & LR_SHIFTKEY && mval[0]>ACTWIDTH){
                              duplicate_actionchannel_keys();
                              remake_action_ipos(act);
                        }
                  }
                  break;

            case GKEY:
                  if (mval[0]>=ACTWIDTH) {
                        if (key) {
                              transform_meshchannel_keys('g', key);
                        }
                        else if (act) {
                              transform_actionchannel_keys ('g');
                        }
                  }
                  break;

            case HKEY:
                  if (key) {
                        if(G.qual & LR_SHIFTKEY) {
                              sethandles_meshchannel_keys(HD_AUTO, key);
                        }
                        else { 
                              sethandles_meshchannel_keys(HD_ALIGN, key);
                        }
                  }
                  else {
                        if(G.qual & LR_SHIFTKEY) {
                              sethandles_actionchannel_keys(HD_AUTO);
                        }
                        else { 
                              sethandles_actionchannel_keys(HD_ALIGN);
                        }
                  }
                  break;
            
            case NKEY:
                  if(G.qual==0) {
                        numbuts_action();
                        
                        /* no panel (yet). current numbuts are not easy to put in panel... */
                        //add_blockhandler(curarea, ACTION_HANDLER_PROPERTIES, UI_PNL_TO_MOUSE);
                        //scrarea_queue_winredraw(curarea);
                  }
                  break;
                  
            case SKEY: 
                  if (mval[0]>=ACTWIDTH) {
                        if (key) {
                              transform_meshchannel_keys('s', key);
                        }
                        else if (act) {
                              transform_actionchannel_keys ('s');
                        }
                  }
                  break;

                  /*** set the Ipo type  ***/
            case TKEY:
                  if (key) {
                        /* to do */
                  }
                  else {
                        set_ipotype_actionchannels(SET_IPO_POPUP);
                  }
                  break;

            case VKEY:
                  if (key) {
                        sethandles_meshchannel_keys(HD_VECT, key);
                        /* to do */
                  }
                  else {
                        sethandles_actionchannel_keys(HD_VECT);
                  }
                  break;

            case DELKEY:
            case XKEY:
                  if (key) {
                        delete_meshchannel_keys(key);
                  }
                  else {
                        if (mval[0]<NAMEWIDTH)
                              delete_actionchannels ();
                        else
                              delete_actionchannel_keys ();
                  }
                  break;
            /* LEFTMOUSE and RIGHTMOUSE event codes can be swapped above,
             * based on user preference USER_LMOUSESELECT
             */
            case LEFTMOUSE:
                  if(view2dmove(LEFTMOUSE)); // only checks for sliders
                  else if (mval[0]>ACTWIDTH){
                        do {
                              getmouseco_areawin(mval);
                              
                              areamouseco_to_ipoco(G.v2d, mval, &dx, &dy);
                              
                              cfra= (int)dx;
                              if(cfra< 1) cfra= 1;
                              
                              if( cfra!=CFRA ) {
                                    CFRA= cfra;
                                    update_for_newframe();
                                    force_draw_plus(SPACE_VIEW3D, 1);
                                    force_draw_plus(SPACE_IPO, 1);
                                    force_draw_plus(SPACE_BUTS, 1);
                              }
                              else PIL_sleep_ms(30);
                              
                        } while(get_mbut() & mousebut);
                  }
                  break;
            case RIGHTMOUSE:
                  /* Clicking in the channel area selects the
                   * channel or constraint channel
                   */
                  if (mval[0]<NAMEWIDTH) {
                        if(act) {
                              if(G.qual & LR_SHIFTKEY)
                                    mouse_actionchannels(act, mval, NULL,  SELECT_INVERT);
                              else
                                    mouse_actionchannels(act, mval, NULL,  SELECT_REPLACE);
                              
                              BIF_undo_push("Select Action");
                        }
                        else numbuts_action();
                  }
                  else if (mval[0]>ACTWIDTH) {
            
                        /* Clicking in the vertical scrollbar selects
                         * all of the keys for that channel at that height
                         */
                        if (IN_2D_VERT_SCROLL(mval)) {
                              if(G.qual & LR_SHIFTKEY)
                                    select_all_keys_channels(act, mval, NULL, 
                                                                         SELECT_INVERT);
                              else
                                    select_all_keys_channels(act, mval, NULL, 
                                                                         SELECT_REPLACE);
                        }
            
                        /* Clicking in the horizontal scrollbar selects
                         * all of the keys within 0.5 of the nearest integer
                         * frame
                         */
                        else if (IN_2D_HORIZ_SCROLL(mval)) {
                              if(G.qual & LR_SHIFTKEY)
                                    select_all_keys_frames(act, mval, NULL, 
                                                                     SELECT_INVERT);
                              else
                                    select_all_keys_frames(act, mval, NULL, 
                                                                     SELECT_REPLACE);
                              BIF_undo_push("Select all Action");
                        }
                        
                        /* Clicking in the main area of the action window
                         * selects keys
                         */
                        else {
                              if (key) {
                                    if(G.qual & LR_SHIFTKEY)
                                          mouse_mesh_action(SELECT_INVERT, key);
                                    else
                                          mouse_mesh_action(SELECT_REPLACE, key);
                              }
                              else {
                                    if(G.qual & LR_SHIFTKEY)
                                          mouse_action(SELECT_INVERT);
                                    else
                                          mouse_action(SELECT_REPLACE);
                              }
                        }
                  }
                  break;
            case PADPLUSKEY:
                  view2d_zoom(G.v2d, 0.1154, sa->winx, sa->winy);
                  test_view2d(G.v2d, sa->winx, sa->winy);
                  doredraw= 1;
                  break;
            case PADMINUS:
                  view2d_zoom(G.v2d, -0.15, sa->winx, sa->winy);
                  test_view2d(G.v2d, sa->winx, sa->winy);
                  doredraw= 1;
                  break;
            case MIDDLEMOUSE:
            case WHEELUPMOUSE:
            case WHEELDOWNMOUSE:
                  view2dmove(event);      /* in drawipo.c */
                  break;
            }
      }

      if(doredraw) addqueue(curarea->win, REDRAW, 1);
      
}

Key *get_action_mesh_key(void) {
      /* gets the key data from the currently selected
       * mesh/lattice. If a mesh is not selected, or does not have
       * key data, then we return NULL (currently only
       * returns key data for RVK type meshes). If there
       * is an action that is pinned, return null
       */
    Object *ob;
    Key    *key;

    ob = OBACT;
    if (!ob) return NULL;

      if (G.saction->pin) return NULL;

    if (ob->type==OB_MESH ) {
            key = ((Mesh *)ob->data)->key;
    }
      else if (ob->type==OB_LATTICE ) {
            key = ((Lattice *)ob->data)->key;
      }
      else return NULL;

      if (key) {
            if (key->type == KEY_RELATIVE)
                  return key;
      }

    return NULL;
}

int get_nearest_key_num(Key *key, short *mval, float *x) {
      /* returns the key num that cooresponds to the
       * y value of the mouse click. Does not check
       * if this is a valid keynum. Also gives the Ipo
       * x coordinate.
       */
    int num;
    float ybase, y;

    areamouseco_to_ipoco(G.v2d, mval, x, &y);

    ybase = key->totkey * (CHANNELHEIGHT + CHANNELSKIP);
    num = (int) ((ybase - y + CHANNELHEIGHT/2) / (CHANNELHEIGHT+CHANNELSKIP));

    return (num + 1);
}



Generated by  Doxygen 1.6.0   Back to index