Animation Editors: Select Linked Operator
[blender.git] / source / blender / editors / space_graph / graph_select.c
index d6b7e80961788004129a0235c4872329b8d6f543..a81de25f265d8a4e6333f16b6ef859bdd592b733 100644 (file)
@@ -15,7 +15,7 @@
  *
  * 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.
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  *
  * The Original Code is Copyright (C) 2008 Blender Foundation
  *
 #include "BLI_math.h"
 
 #include "DNA_anim_types.h"
-#include "DNA_action_types.h"
-#include "DNA_armature_types.h"
-#include "DNA_camera_types.h"
-#include "DNA_curve_types.h"
 #include "DNA_object_types.h"
 #include "DNA_screen_types.h"
 #include "DNA_scene_types.h"
 #include "DNA_space_types.h"
-#include "DNA_constraint_types.h"
-#include "DNA_key_types.h"
-#include "DNA_lamp_types.h"
-#include "DNA_material_types.h"
-#include "DNA_userdef_types.h"
-#include "DNA_gpencil_types.h"
-#include "DNA_windowmanager_types.h"
 
 #include "RNA_access.h"
 #include "RNA_define.h"
 #include "UI_view2d.h"
 
 #include "ED_anim_api.h"
-#include "ED_keyframing.h"
-#include "ED_keyframes_draw.h"
 #include "ED_keyframes_edit.h"
 #include "ED_markers.h"
-#include "ED_screen.h"
-#include "ED_space_api.h"
 
 #include "WM_api.h"
 #include "WM_types.h"
@@ -110,8 +95,8 @@ static void deselect_graph_keys (bAnimContext *ac, short test, short sel)
        int filter;
        
        SpaceIpo *sipo= (SpaceIpo *)ac->sa->spacedata.first;
-       BeztEditData bed;
-       BeztEditFunc test_cb, sel_cb;
+       KeyframeEditData ked;
+       KeyframeEditFunc test_cb, sel_cb;
        
        /* determine type-based settings */
        filter= (ANIMFILTER_VISIBLE | ANIMFILTER_CURVEVISIBLE | ANIMFILTER_CURVESONLY);
@@ -120,13 +105,13 @@ static void deselect_graph_keys (bAnimContext *ac, short test, short sel)
        ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
        
        /* init BezTriple looping data */
-       memset(&bed, 0, sizeof(BeztEditData));
+       memset(&ked, 0, sizeof(KeyframeEditData));
        test_cb= ANIM_editkeyframes_ok(BEZT_OK_SELECTED);
        
        /* See if we should be selecting or deselecting */
        if (test) {
                for (ale= anim_data.first; ale; ale= ale->next) {
-                       if (ANIM_fcurve_keys_bezier_loop(&bed, ale->key_data, NULL, test_cb, NULL)) {
+                       if (ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, test_cb, NULL)) {
                                sel= SELECT_SUBTRACT;
                                break;
                        }
@@ -141,7 +126,7 @@ static void deselect_graph_keys (bAnimContext *ac, short test, short sel)
                FCurve *fcu= (FCurve *)ale->key_data;
                
                /* Keyframes First */
-               ANIM_fcurve_keys_bezier_loop(&bed, ale->key_data, NULL, sel_cb, NULL);
+               ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, sel_cb, NULL);
                
                /* only change selection of channel when the visibility of keyframes doesn't depend on this */
                if ((sipo->flag & SIPO_SELCUVERTSONLY) == 0) {
@@ -189,7 +174,7 @@ void GRAPH_OT_select_all_toggle (wmOperatorType *ot)
        /* identifiers */
        ot->name= "Select All";
        ot->idname= "GRAPH_OT_select_all_toggle";
-       ot->description= "Toggle selection of all keyframes.";
+       ot->description= "Toggle selection of all keyframes";
        
        /* api callbacks */
        ot->exec= graphkeys_deselectall_exec;
@@ -199,7 +184,7 @@ void GRAPH_OT_select_all_toggle (wmOperatorType *ot)
        ot->flag= OPTYPE_REGISTER/*|OPTYPE_UNDO*/;
        
        /* props */
-       RNA_def_boolean(ot->srna, "invert", 0, "Invert", "");
+       ot->prop= RNA_def_boolean(ot->srna, "invert", 0, "Invert", "");
 }
 
 /* ******************** Border Select Operator **************************** */
@@ -211,18 +196,19 @@ void GRAPH_OT_select_all_toggle (wmOperatorType *ot)
  */
 
 /* Borderselect only selects keyframes now, as overshooting handles often get caught too,
- * which means that they may be inadvertantly moved as well.
- * Also, for convenience, handles should get same status as keyframe (if it was within bounds)
+ * which means that they may be inadvertantly moved as well. However, incl_handles overrides
+ * this, and allow handles to be considered independently too.
+ * Also, for convenience, handles should get same status as keyframe (if it was within bounds).
  */
-static void borderselect_graphkeys (bAnimContext *ac, rcti rect, short mode, short selectmode)
+static void borderselect_graphkeys (bAnimContext *ac, rcti rect, short mode, short selectmode, short incl_handles)
 {
        ListBase anim_data = {NULL, NULL};
        bAnimListElem *ale;
        int filter;
        
        SpaceIpo *sipo= (SpaceIpo *)ac->sa->spacedata.first;
-       BeztEditData bed;
-       BeztEditFunc ok_cb, select_cb;
+       KeyframeEditData ked;
+       KeyframeEditFunc ok_cb, select_cb;
        View2D *v2d= &ac->ar->v2d;
        rctf rectf;
        
@@ -239,14 +225,21 @@ static void borderselect_graphkeys (bAnimContext *ac, rcti rect, short mode, sho
        ok_cb= ANIM_editkeyframes_ok(mode);
        
        /* init editing data */
-       memset(&bed, 0, sizeof(BeztEditData));
-       bed.data= &rectf;
+       memset(&ked, 0, sizeof(KeyframeEditData));
+       ked.data= &rectf;
+       
+       /* treat handles separately? */
+       if (incl_handles)
+               ked.iterflags |= KEYFRAME_ITER_INCL_HANDLES;
        
        /* loop over data, doing border select */
        for (ale= anim_data.first; ale; ale= ale->next) {
                AnimData *adt= ANIM_nla_mapping_get(ac, ale);
                FCurve *fcu= (FCurve *)ale->key_data;
                
+               /* apply unit corrections */
+               ANIM_unit_mapping_apply_fcurve(ac->scene, ale->id, ale->key_data, ANIM_UNITCONV_ONLYKEYS);
+               
                /* apply NLA mapping to all the keyframes, since it's easier than trying to
                 * guess when a callback might use something different
                 */
@@ -255,21 +248,21 @@ static void borderselect_graphkeys (bAnimContext *ac, rcti rect, short mode, sho
                
                /* set horizontal range (if applicable) 
                 * NOTE: these values are only used for x-range and y-range but not region 
-                *              (which uses bed.data, i.e. rectf)
+                *              (which uses ked.data, i.e. rectf)
                 */
                if (mode != BEZT_OK_VALUERANGE) {
-                       bed.f1= rectf.xmin;
-                       bed.f2= rectf.xmax;
+                       ked.f1= rectf.xmin;
+                       ked.f2= rectf.xmax;
                }
                else {
-                       bed.f1= rectf.ymin;
-                       bed.f2= rectf.ymax;
+                       ked.f1= rectf.ymin;
+                       ked.f2= rectf.ymax;
                }
                
                /* firstly, check if any keyframes will be hit by this */
-               if (ANIM_fcurve_keys_bezier_loop(&bed, fcu, NULL, ok_cb, NULL)) {
+               if (ANIM_fcurve_keyframes_loop(&ked, fcu, NULL, ok_cb, NULL)) {
                        /* select keyframes that are in the appropriate places */
-                       ANIM_fcurve_keys_bezier_loop(&bed, fcu, ok_cb, select_cb, NULL);
+                       ANIM_fcurve_keyframes_loop(&ked, fcu, ok_cb, select_cb, NULL);
                        
                        /* only change selection of channel when the visibility of keyframes doesn't depend on this */
                        if ((sipo->flag & SIPO_SELCUVERTSONLY) == 0) {
@@ -282,6 +275,9 @@ static void borderselect_graphkeys (bAnimContext *ac, rcti rect, short mode, sho
                /* un-apply NLA mapping from all the keyframes */
                if (adt)
                        ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 1);
+                       
+               /* unapply unit corrections */
+               ANIM_unit_mapping_apply_fcurve(ac->scene, ale->id, ale->key_data, ANIM_UNITCONV_RESTORE|ANIM_UNITCONV_ONLYKEYS);
        }
        
        /* cleanup */
@@ -295,16 +291,23 @@ static int graphkeys_borderselect_exec(bContext *C, wmOperator *op)
        bAnimContext ac;
        rcti rect;
        short mode=0, selectmode=0;
+       short incl_handles;
        
        /* get editor data */
        if (ANIM_animdata_get_context(C, &ac) == 0)
                return OPERATOR_CANCELLED;
-
+       
+       /* get select mode 
+        *      - 'gesture_mode' from the operator specifies how to select
+        *      - 'include_handles' from the operator specifies whether to include handles in the selection
+        */
        if (RNA_int_get(op->ptr, "gesture_mode")==GESTURE_MODAL_SELECT)
                selectmode= SELECT_ADD;
        else
                selectmode= SELECT_SUBTRACT;
-
+               
+       incl_handles = RNA_boolean_get(op->ptr, "include_handles");
+       
        /* get settings from operator */
        rect.xmin= RNA_int_get(op->ptr, "xmin");
        rect.ymin= RNA_int_get(op->ptr, "ymin");
@@ -327,7 +330,7 @@ static int graphkeys_borderselect_exec(bContext *C, wmOperator *op)
                mode= BEZT_OK_REGION;
        
        /* apply borderselect action */
-       borderselect_graphkeys(&ac, rect, mode, selectmode);
+       borderselect_graphkeys(&ac, rect, mode, selectmode, incl_handles);
        
        /* send notifier that keyframe selection has changed */
        WM_event_add_notifier(C, NC_ANIMATION|ND_KEYFRAME_SELECT, NULL);
@@ -340,7 +343,7 @@ void GRAPH_OT_select_border(wmOperatorType *ot)
        /* identifiers */
        ot->name= "Border Select";
        ot->idname= "GRAPH_OT_select_border";
-       ot->description= "Select all keyframes within the specified region.";
+       ot->description= "Select all keyframes within the specified region";
        
        /* api callbacks */
        ot->invoke= WM_border_select_invoke;
@@ -355,7 +358,8 @@ void GRAPH_OT_select_border(wmOperatorType *ot)
        /* rna */
        WM_operator_properties_gesture_border(ot, FALSE);
        
-       RNA_def_boolean(ot->srna, "axis_range", 0, "Axis Range", "");
+       ot->prop= RNA_def_boolean(ot->srna, "axis_range", 0, "Axis Range", "");
+       RNA_def_boolean(ot->srna, "include_handles", 0, "Include Handles", "Are handles tested individually against the selection criteria");
 }
 
 /* ******************** Column Select Operator **************************** */
@@ -384,8 +388,8 @@ static void markers_selectkeys_between (bAnimContext *ac)
        bAnimListElem *ale;
        int filter;
        
-       BeztEditFunc ok_cb, select_cb;
-       BeztEditData bed;
+       KeyframeEditFunc ok_cb, select_cb;
+       KeyframeEditData ked;
        float min, max;
        
        /* get extreme markers */
@@ -397,9 +401,9 @@ static void markers_selectkeys_between (bAnimContext *ac)
        ok_cb= ANIM_editkeyframes_ok(BEZT_OK_FRAMERANGE);
        select_cb= ANIM_editkeyframes_select(SELECT_ADD);
        
-       memset(&bed, 0, sizeof(BeztEditData));
-       bed.f1= min; 
-       bed.f2= max;
+       memset(&ked, 0, sizeof(KeyframeEditData));
+       ked.f1= min; 
+       ked.f2= max;
        
        /* filter data */
        filter= (ANIMFILTER_VISIBLE | ANIMFILTER_CURVEVISIBLE | ANIMFILTER_CURVESONLY);
@@ -411,11 +415,11 @@ static void markers_selectkeys_between (bAnimContext *ac)
                
                if (adt) {      
                        ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 1);
-                       ANIM_fcurve_keys_bezier_loop(&bed, ale->key_data, ok_cb, select_cb, NULL);
+                       ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL);
                        ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 1);
                }
                else {
-                       ANIM_fcurve_keys_bezier_loop(&bed, ale->key_data, ok_cb, select_cb, NULL);
+                       ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL);
                }
        }
        
@@ -433,11 +437,11 @@ static void columnselect_graph_keys (bAnimContext *ac, short mode)
        
        Scene *scene= ac->scene;
        CfraElem *ce;
-       BeztEditFunc select_cb, ok_cb;
-       BeztEditData bed;
+       KeyframeEditFunc select_cb, ok_cb;
+       KeyframeEditData ked;
        
        /* initialise keyframe editing data */
-       memset(&bed, 0, sizeof(BeztEditData));
+       memset(&ked, 0, sizeof(KeyframeEditData));
        
        /* build list of columns */
        switch (mode) {
@@ -446,7 +450,7 @@ static void columnselect_graph_keys (bAnimContext *ac, short mode)
                        ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
                        
                        for (ale= anim_data.first; ale; ale= ale->next)
-                               ANIM_fcurve_keys_bezier_loop(&bed, ale->key_data, NULL, bezt_to_cfraelem, NULL);
+                               ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, bezt_to_cfraelem, NULL);
                        
                        BLI_freelistN(&anim_data);
                        break;
@@ -454,13 +458,13 @@ static void columnselect_graph_keys (bAnimContext *ac, short mode)
                case GRAPHKEYS_COLUMNSEL_CFRA: /* current frame */
                        /* make a single CfraElem for storing this */
                        ce= MEM_callocN(sizeof(CfraElem), "cfraElem");
-                       BLI_addtail(&bed.list, ce);
+                       BLI_addtail(&ked.list, ce);
                        
                        ce->cfra= (float)CFRA;
                        break;
                        
                case GRAPHKEYS_COLUMNSEL_MARKERS_COLUMN: /* list of selected markers */
-                       ED_markers_make_cfra_list(ac->markers, &bed.list, 1);
+                       ED_markers_make_cfra_list(ac->markers, &ked.list, 1);
                        break;
                        
                default: /* invalid option */
@@ -480,23 +484,23 @@ static void columnselect_graph_keys (bAnimContext *ac, short mode)
        for (ale= anim_data.first; ale; ale= ale->next) {
                AnimData *adt= ANIM_nla_mapping_get(ac, ale);
                
-               /* loop over cfraelems (stored in the BeztEditData->list)
+               /* loop over cfraelems (stored in the KeyframeEditData->list)
                 *      - we need to do this here, as we can apply fewer NLA-mapping conversions
                 */
-               for (ce= bed.list.first; ce; ce= ce->next) {
+               for (ce= ked.list.first; ce; ce= ce->next) {
                        /* set frame for validation callback to refer to */
                        if (ale)
-                               bed.f1= BKE_nla_tweakedit_remap(adt, ce->cfra, NLATIME_CONVERT_UNMAP);
+                               ked.f1= BKE_nla_tweakedit_remap(adt, ce->cfra, NLATIME_CONVERT_UNMAP);
                        else
-                               bed.f1= ce->cfra;
+                               ked.f1= ce->cfra;
                        
                        /* select elements with frame number matching cfraelem */
-                       ANIM_fcurve_keys_bezier_loop(&bed, ale->key_data, ok_cb, select_cb, NULL);
+                       ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL);
                }
        }
        
        /* free elements */
-       BLI_freelistN(&bed.list);
+       BLI_freelistN(&ked.list);
        BLI_freelistN(&anim_data);
 }
 
@@ -530,7 +534,7 @@ void GRAPH_OT_select_column (wmOperatorType *ot)
        /* identifiers */
        ot->name= "Select All";
        ot->idname= "GRAPH_OT_select_column";
-       ot->description= "Select all keyframes on the specified frame(s).";
+       ot->description= "Select all keyframes on the specified frame(s)";
        
        /* api callbacks */
        ot->exec= graphkeys_columnselect_exec;
@@ -540,7 +544,174 @@ void GRAPH_OT_select_column (wmOperatorType *ot)
        ot->flag= OPTYPE_REGISTER/*|OPTYPE_UNDO*/;
        
        /* props */
-       RNA_def_enum(ot->srna, "mode", prop_column_select_types, 0, "Mode", "");
+       ot->prop= RNA_def_enum(ot->srna, "mode", prop_column_select_types, 0, "Mode", "");
+}
+
+/* ******************** Select Linked Operator *********************** */
+
+static int graphkeys_select_linked_exec (bContext *C, wmOperator *op)
+{
+       bAnimContext ac;
+       
+       ListBase anim_data= {NULL, NULL};
+       bAnimListElem *ale;
+       int filter;
+       
+       KeyframeEditFunc ok_cb = ANIM_editkeyframes_ok(BEZT_OK_SELECTED);
+       KeyframeEditFunc sel_cb = ANIM_editkeyframes_select(SELECT_ADD);
+       
+       /* get editor data */
+       if (ANIM_animdata_get_context(C, &ac) == 0)
+               return OPERATOR_CANCELLED;
+       
+       /* loop through all of the keys and select additional keyframes based on these */
+       filter= (ANIMFILTER_VISIBLE | ANIMFILTER_CURVEVISIBLE | ANIMFILTER_CURVESONLY);
+       ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
+       
+       for (ale= anim_data.first; ale; ale= ale->next) {
+               FCurve *fcu= (FCurve *)ale->key_data;
+               
+               /* check if anything selected? */
+               if (ANIM_fcurve_keyframes_loop(NULL, fcu, NULL, ok_cb, NULL)) {
+                       /* select every keyframe in this curve then */
+                       ANIM_fcurve_keyframes_loop(NULL, fcu, NULL, sel_cb, NULL);
+               }
+       }
+       
+       /* Cleanup */
+       BLI_freelistN(&anim_data);
+       
+       /* set notifier that keyframe selection has changed */
+       WM_event_add_notifier(C, NC_ANIMATION|ND_KEYFRAME_SELECT, NULL);
+       
+       return OPERATOR_FINISHED;
+}
+
+void GRAPH_OT_select_linked (wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Select Linked";
+       ot->idname= "GRAPH_OT_select_linked";
+       ot->description = "Select keyframes occurring the same F-Curves as selected ones";
+       
+       /* api callbacks */
+       ot->exec= graphkeys_select_linked_exec;
+       ot->poll= graphop_visible_keyframes_poll;
+       
+       /* flags */
+       ot->flag= OPTYPE_REGISTER/*|OPTYPE_UNDO*/;
+}
+
+/* ******************** Select More/Less Operators *********************** */
+
+/* Common code to perform selection */
+static void select_moreless_graph_keys (bAnimContext *ac, short mode)
+{
+       ListBase anim_data= {NULL, NULL};
+       bAnimListElem *ale;
+       int filter;
+       
+       KeyframeEditData ked;
+       KeyframeEditFunc build_cb;
+       
+       
+       /* init selmap building data */
+       build_cb= ANIM_editkeyframes_buildselmap(mode);
+       memset(&ked, 0, sizeof(KeyframeEditData)); 
+       
+       /* loop through all of the keys and select additional keyframes based on these */
+       filter= (ANIMFILTER_VISIBLE | ANIMFILTER_CURVEVISIBLE | ANIMFILTER_CURVESONLY);
+       ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
+       
+       for (ale= anim_data.first; ale; ale= ale->next) {
+               FCurve *fcu= (FCurve *)ale->key_data;
+               
+               /* only continue if F-Curve has keyframes */
+               if (fcu->bezt == NULL)
+                       continue;
+               
+               /* build up map of whether F-Curve's keyframes should be selected or not */
+               ked.data= MEM_callocN(fcu->totvert, "selmap graphEdit");
+               ANIM_fcurve_keyframes_loop(&ked, fcu, NULL, build_cb, NULL);
+               
+               /* based on this map, adjust the selection status of the keyframes */
+               ANIM_fcurve_keyframes_loop(&ked, fcu, NULL, bezt_selmap_flush, NULL);
+               
+               /* free the selmap used here */
+               MEM_freeN(ked.data);
+               ked.data= NULL;
+       }
+       
+       /* Cleanup */
+       BLI_freelistN(&anim_data);
+}
+
+/* ----------------- */
+
+static int graphkeys_select_more_exec (bContext *C, wmOperator *op)
+{
+       bAnimContext ac;
+       
+       /* get editor data */
+       if (ANIM_animdata_get_context(C, &ac) == 0)
+               return OPERATOR_CANCELLED;
+       
+       /* perform select changes */
+       select_moreless_graph_keys(&ac, SELMAP_MORE);
+       
+       /* set notifier that keyframe selection has changed */
+       WM_event_add_notifier(C, NC_ANIMATION|ND_KEYFRAME_SELECT, NULL);
+       
+       return OPERATOR_FINISHED;
+}
+
+void GRAPH_OT_select_more (wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Select More";
+       ot->idname= "GRAPH_OT_select_more";
+       ot->description = "Select keyframes beside already selected ones";
+       
+       /* api callbacks */
+       ot->exec= graphkeys_select_more_exec;
+       ot->poll= graphop_visible_keyframes_poll;
+       
+       /* flags */
+       ot->flag= OPTYPE_REGISTER/*|OPTYPE_UNDO*/;
+}
+
+/* ----------------- */
+
+static int graphkeys_select_less_exec (bContext *C, wmOperator *op)
+{
+       bAnimContext ac;
+       
+       /* get editor data */
+       if (ANIM_animdata_get_context(C, &ac) == 0)
+               return OPERATOR_CANCELLED;
+       
+       /* perform select changes */
+       select_moreless_graph_keys(&ac, SELMAP_LESS);
+       
+       /* set notifier that keyframe selection has changed */
+       WM_event_add_notifier(C, NC_ANIMATION|ND_KEYFRAME_SELECT, NULL);
+       
+       return OPERATOR_FINISHED;
+}
+
+void GRAPH_OT_select_less (wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Select Less";
+       ot->idname= "GRAPH_OT_select_less";
+       ot->description = "Deselect keyframes on ends of selection islands";
+       
+       /* api callbacks */
+       ot->exec= graphkeys_select_less_exec;
+       ot->poll= graphop_visible_keyframes_poll;
+       
+       /* flags */
+       ot->flag= OPTYPE_REGISTER/*|OPTYPE_UNDO*/;
 }
 
 /* ******************** Mouse-Click Select Operator *********************** */
@@ -564,17 +735,97 @@ static EnumPropertyItem prop_graphkeys_leftright_select_types[] = {
 
 /* ------------------- */
 
-enum {
-       NEAREST_HANDLE_LEFT     = 0,
+/* temp info for caching handle vertices close */
+typedef struct tNearestVertInfo {
+       struct tNearestVertInfo *next, *prev;
+       
+       FCurve *fcu;            /* F-Curve that keyframe comes from */
+       
+       BezTriple *bezt;        /* keyframe to consider */
+       FPoint *fpt;            /* sample point to consider */
+       
+       short hpoint;           /* the handle index that we hit (eHandleIndex) */
+       short sel;                      /* whether the handle is selected or not */
+       int dist;                       /* distance from mouse to vert */
+} tNearestVertInfo;
+
+/* Tags for the type of graph vert that we have */
+typedef enum eGraphVertIndex {
+       NEAREST_HANDLE_LEFT     = -1,
        NEAREST_HANDLE_KEY,
        NEAREST_HANDLE_RIGHT
-} eHandleIndex; 
-/* Find the vertex (either handle (0/2) or the keyframe (1)) that is nearest to the mouse cursor (in area coordinates)  
- * Selected verts get a disadvantage, to make it easier to select handles behind.
- * Returns eHandleIndex
- */
-static short findnearest_fcurve_vert (bAnimContext *ac, int mval[2], FCurve **fcurve, BezTriple **bezt)
+} eGraphVertIndex; 
+
+/* Tolerance for absolute radius (in pixels) of the vert from the cursor to use */
+// TODO: perhaps this should depend a bit on the size that the user set the vertices to be?
+#define GVERTSEL_TOL   10
+
+/* ....... */
+
+/* check if its ok to select a handle */
+// XXX also need to check for int-values only?
+static int fcurve_handle_sel_check(SpaceIpo *sipo, BezTriple *bezt)
+{
+       if (sipo->flag & SIPO_NOHANDLES) return 0;
+       if ((sipo->flag & SIPO_SELVHANDLESONLY) && BEZSELECTED(bezt)==0) return 0;
+       return 1;
+}
+
+/* check if the given vertex is within bounds or not */
+// TODO: should we return if we hit something?
+static void nearest_fcurve_vert_store (ListBase *matches, View2D *v2d, FCurve *fcu, BezTriple *bezt, FPoint *fpt, short hpoint, int mval[2])
+{
+       /* Keyframes or Samples? */
+       if (bezt) {
+               int screen_co[2], dist;
+               
+               /* convert from data-space to screen coordinates 
+                * NOTE: hpoint+1 gives us 0,1,2 respectively for each handle, 
+                *      needed to access the relevant vertex coordinates in the 3x3 
+                *      'vec' matrix
+                */
+               UI_view2d_to_region_no_clip(v2d, bezt->vec[hpoint+1][0], bezt->vec[hpoint+1][1], &screen_co[0], &screen_co[1]);
+               
+               /* check if distance from mouse cursor to vert in screen space is within tolerance */
+                       // XXX: inlined distance calculation, since we cannot do this on ints using the math lib...
+               //dist = len_v2v2(mval, screen_co);
+               dist = sqrt((mval[0] - screen_co[0])*(mval[0] - screen_co[0]) + 
+                                       (mval[1] - screen_co[1])*(mval[1] - screen_co[1]));
+               
+               if (dist <= GVERTSEL_TOL) {
+                       tNearestVertInfo *nvi = (tNearestVertInfo *)matches->last;
+                       short replace = 0;
+                       
+                       /* if there is already a point for the F-Curve, check if this point is closer than that was */
+                       if ((nvi) && (nvi->fcu == fcu)) {
+                               /* replace if we are closer, or if equal and that one wasn't selected but we are... */
+                               if ( (nvi->dist > dist) || ((nvi->sel == 0) && BEZSELECTED(bezt)) )
+                                       replace= 1;
+                       }
+                       /* add new if not replacing... */
+                       if (replace == 0)
+                               nvi = MEM_callocN(sizeof(tNearestVertInfo), "Nearest Graph Vert Info - Bezt");
+                       
+                       /* store values */
+                       nvi->fcu = fcu;
+                       nvi->bezt = bezt;
+                       nvi->hpoint = hpoint;
+                       nvi->dist = dist;
+                       
+                       nvi->sel= BEZSELECTED(bezt); // XXX... should this use the individual verts instead?
+                       
+                       /* add to list of matches if appropriate... */
+                       if (replace == 0)
+                               BLI_addtail(matches, nvi);
+               }
+       }
+       else if (fpt) {
+               // TODO...
+       }
+} 
+
+/* helper for find_nearest_fcurve_vert() - build the list of nearest matches */
+static void get_nearest_fcurve_verts_list (bAnimContext *ac, int mval[2], ListBase *matches)
 {
        ListBase anim_data = {NULL, NULL};
        bAnimListElem *ale;
@@ -582,12 +833,6 @@ static short findnearest_fcurve_vert (bAnimContext *ac, int mval[2], FCurve **fc
        
        SpaceIpo *sipo= (SpaceIpo *)ac->sa->spacedata.first;
        View2D *v2d= &ac->ar->v2d;
-       int hpoint=0, sco[3][2];
-       int dist= 100, temp, i;
-       
-       /* clear pointers first */
-       *fcurve= 0;
-       *bezt= 0;
        
        /* get curves to search through 
         *      - if the option to only show keyframes that belong to selected F-Curves is enabled,
@@ -602,95 +847,131 @@ static short findnearest_fcurve_vert (bAnimContext *ac, int mval[2], FCurve **fc
                FCurve *fcu= (FCurve *)ale->key_data;
                AnimData *adt= ANIM_nla_mapping_get(ac, ale);
                
-               /* try to progressively get closer to the right point... */
+               /* apply unit corrections */
+               ANIM_unit_mapping_apply_fcurve(ac->scene, ale->id, ale->key_data, 0);
+               
+               /* apply NLA mapping to all the keyframes */
+               if (adt)
+                       ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 1);
+               
                if (fcu->bezt) {
                        BezTriple *bezt1=fcu->bezt, *prevbezt=NULL;
-                       
-                       /* apply NLA mapping to all the keyframes */
-                       if (adt)
-                               ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 1);
+                       int i;
                        
                        for (i=0; i < fcu->totvert; i++, prevbezt=bezt1, bezt1++) {
-                               /* convert beztriple points to screen-space */
-                               UI_view2d_to_region_no_clip(v2d, bezt1->vec[0][0], bezt1->vec[0][1], &sco[0][0], &sco[0][1]);
-                               UI_view2d_to_region_no_clip(v2d, bezt1->vec[1][0], bezt1->vec[1][1], &sco[1][0], &sco[1][1]);
-                               UI_view2d_to_region_no_clip(v2d, bezt1->vec[2][0], bezt1->vec[2][1], &sco[2][0], &sco[2][1]);
-                               
-                               /* keyframe - do select? */
-                               temp= abs(mval[0] - sco[1][0]) + abs(mval[1] - sco[1][1]);
-                               
-                               if (bezt1->f2 & SELECT) 
-                                       temp += 5;
-                               
-                               if (temp < dist) { 
-                                       hpoint= NEAREST_HANDLE_KEY; 
-                                       *bezt= bezt1; 
-                                       dist= temp; 
-                                       *fcurve= fcu; 
-                               }
+                               /* keyframe */
+                               nearest_fcurve_vert_store(matches, v2d, fcu, bezt1, NULL, NEAREST_HANDLE_KEY, mval);
                                
                                /* handles - only do them if they're visible */
-                               // XXX also need to check for int-values only?
-                               if ((sipo->flag & SIPO_NOHANDLES)==0) {
+                               if (fcurve_handle_sel_check(sipo, bezt1) && (fcu->totvert > 1)) {
                                        /* first handle only visible if previous segment had handles */
                                        if ( (!prevbezt && (bezt1->ipo==BEZT_IPO_BEZ)) || (prevbezt && (prevbezt->ipo==BEZT_IPO_BEZ)) )
                                        {
-                                               temp= -3 + abs(mval[0] - sco[0][0]) + abs(mval[1] - sco[0][1]);
-                                               if (bezt1->f1 & SELECT) 
-                                                       temp += 5;
-                                                       
-                                               if (temp < dist) { 
-                                                       hpoint= NEAREST_HANDLE_LEFT; 
-                                                       *bezt= bezt1; 
-                                                       dist= temp; 
-                                                       *fcurve= fcu; 
-                                               }
+                                               nearest_fcurve_vert_store(matches, v2d, fcu, bezt1, NULL, NEAREST_HANDLE_LEFT, mval);
                                        }
                                        
                                        /* second handle only visible if this segment is bezier */
                                        if (bezt1->ipo == BEZT_IPO_BEZ) 
                                        {
-                                               temp= abs(mval[0] - sco[2][0]) + abs(mval[1] - sco[2][1]);
-                                               if (bezt1->f3 & SELECT) 
-                                                       temp += 5;
-                                               
-                                               if (temp < dist) { 
-                                                       hpoint= NEAREST_HANDLE_RIGHT; 
-                                                       *bezt=bezt1; 
-                                                       dist= temp; 
-                                                       *fcurve= fcu; 
-                                               }
+                                               nearest_fcurve_vert_store(matches, v2d, fcu, bezt1, NULL, NEAREST_HANDLE_RIGHT, mval);
                                        }
                                }
                        }
+               }
+               else if (fcu->fpt) {
+                       // TODO; do this for samples too
                        
-                       /* un-apply NLA mapping from all the keyframes */
-                       if (adt)
-                               ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 1);
                }
+               
+               /* un-apply NLA mapping from all the keyframes */
+               if (adt)
+                       ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 1);
+               
+               /* unapply unit corrections */
+               ANIM_unit_mapping_apply_fcurve(ac->scene, ale->id, ale->key_data, ANIM_UNITCONV_RESTORE);
        }
        
        /* free channels */
        BLI_freelistN(&anim_data);
+}
+
+/* helper for find_nearest_fcurve_vert() - get the best match to use */
+static tNearestVertInfo *get_best_nearest_fcurve_vert (bAnimContext *ac, ListBase *matches)
+{
+       tNearestVertInfo *nvi = NULL;
+       short found = 0;
        
-       /* return handle */
-       return hpoint;
+       /* abort if list is empty */
+       if (matches->first == NULL) 
+               return NULL;
+               
+       /* if list only has 1 item, remove it from the list and return */
+       if (matches->first == matches->last) {
+               /* need to remove from the list, otherwise it gets freed and then we can't return it */
+               nvi= matches->first;
+               BLI_remlink(matches, nvi);
+               
+               return nvi;
+       }
+       
+       /* try to find the first selected F-Curve vert, then take the one after it */
+       for (nvi = matches->first; nvi; nvi = nvi->next) {
+               /* which mode of search are we in: find first selected, or find vert? */
+               if (found) {
+                       /* just take this vert now that we've found the selected one 
+                        *      - we'll need to remove this from the list so that it can be returned to the original caller
+                        */
+                       BLI_remlink(matches, nvi);
+                       return nvi;
+               }
+               else {
+                       /* if vert is selected, we've got what we want... */
+                       if (nvi->sel)
+                               found= 1;
+               }
+       }
+       
+       /* if we're still here, this means that we failed to find anything appropriate in the first pass,
+        * so just take the first item now...
+        */
+       nvi = matches->first;
+       BLI_remlink(matches, nvi);
+       return nvi;
 }
+
+/* Find the nearest vertices (either a handle or the keyframe) that are nearest to the mouse cursor (in area coordinates) 
+ * NOTE: the match info found must still be freed 
+ */
+static tNearestVertInfo *find_nearest_fcurve_vert (bAnimContext *ac, int mval[2])
+{
+       ListBase matches = {NULL, NULL};
+       tNearestVertInfo *nvi;
+       
+       /* step 1: get the nearest verts */
+       get_nearest_fcurve_verts_list(ac, mval, &matches);
+       
+       /* step 2: find the best vert */
+       nvi= get_best_nearest_fcurve_vert(ac, &matches);
+       
+       BLI_freelistN(&matches);
+       
+       /* return the best vert found */
+       return nvi;
+}
+
+/* ------------------- */
+
 /* option 1) select keyframe directly under mouse */
 static void mouse_graph_keys (bAnimContext *ac, int mval[], short select_mode, short curves_only)
 {
        SpaceIpo *sipo= (SpaceIpo *)ac->sa->spacedata.first;
-       FCurve *fcu;
-       BezTriple *bezt;
-       short handle;
-       int filter;
+       tNearestVertInfo *nvi;
        
        /* find the beztriple that we're selecting, and the handle that was clicked on */
-       handle= findnearest_fcurve_vert(ac, mval, &fcu, &bezt);
+       nvi = find_nearest_fcurve_vert(ac, mval);
        
        /* check if anything to select */
-       if (fcu == NULL)        
+       if (nvi == NULL)        
                return;
        
        /* deselect all other curves? */
@@ -711,13 +992,15 @@ static void mouse_graph_keys (bAnimContext *ac, int mval[], short select_mode, s
        
        /* if points can be selected on this F-Curve */
        // TODO: what about those with no keyframes?
-       if ((curves_only == 0) && ((fcu->flag & FCURVE_PROTECTED)==0)) {
+       if ((curves_only == 0) && ((nvi->fcu->flag & FCURVE_PROTECTED)==0)) {
                /* only if there's keyframe */
-               if (bezt) {
+               if (nvi->bezt) {
+                       BezTriple *bezt= nvi->bezt;
+                       
                        /* depends on selection mode */
                        if (select_mode == SELECT_INVERT) {
                                /* keyframe - invert select of all */
-                               if (handle == NEAREST_HANDLE_KEY) {
+                               if (nvi->hpoint == NEAREST_HANDLE_KEY) {
                                        if (BEZSELECTED(bezt)) {
                                                BEZ_DESEL(bezt);
                                        }
@@ -727,7 +1010,7 @@ static void mouse_graph_keys (bAnimContext *ac, int mval[], short select_mode, s
                                }
                                
                                /* handles - toggle selection of relevant handle */
-                               else if (handle == NEAREST_HANDLE_LEFT) {
+                               else if (nvi->hpoint == NEAREST_HANDLE_LEFT) {
                                        /* toggle selection */
                                        bezt->f1 ^= SELECT;
                                }
@@ -738,45 +1021,51 @@ static void mouse_graph_keys (bAnimContext *ac, int mval[], short select_mode, s
                        }
                        else {
                                /* if the keyframe was clicked on, select all verts of given beztriple */
-                               if (handle == NEAREST_HANDLE_KEY) {
+                               if (nvi->hpoint == NEAREST_HANDLE_KEY) {
                                        BEZ_SEL(bezt);
                                }
                                /* otherwise, select the handle that applied */
-                               else if (handle == NEAREST_HANDLE_LEFT) 
+                               else if (nvi->hpoint == NEAREST_HANDLE_LEFT) 
                                        bezt->f1 |= SELECT;
                                else 
                                        bezt->f3 |= SELECT;
                        }
                }
+               else if (nvi->fpt) {
+                       // TODO: need to handle sample points
+               }
        }
        else {
-               BeztEditFunc select_cb;
-               BeztEditData bed;
+               KeyframeEditFunc select_cb;
+               KeyframeEditData ked;
                
                /* initialise keyframe editing data */
-               memset(&bed, 0, sizeof(BeztEditData));
+               memset(&ked, 0, sizeof(KeyframeEditData));
                
                /* set up BezTriple edit callbacks */
                select_cb= ANIM_editkeyframes_select(select_mode);
                
                /* select all keyframes */
-               ANIM_fcurve_keys_bezier_loop(&bed, fcu, NULL, select_cb, NULL);
+               ANIM_fcurve_keyframes_loop(&ked, nvi->fcu, NULL, select_cb, NULL);
        }
        
        /* only change selection of channel when the visibility of keyframes doesn't depend on this */
        if ((sipo->flag & SIPO_SELCUVERTSONLY) == 0) {
                /* select or deselect curve? */
                if (select_mode == SELECT_INVERT)
-                       fcu->flag ^= FCURVE_SELECTED;
+                       nvi->fcu->flag ^= FCURVE_SELECTED;
                else if (select_mode == SELECT_ADD)
-                       fcu->flag |= FCURVE_SELECTED;
+                       nvi->fcu->flag |= FCURVE_SELECTED;
                        
                /* set active F-Curve (NOTE: sync the filter flags with findnearest_fcurve_vert) */
-               if (fcu->flag & FCURVE_SELECTED) {
-                       filter= (ANIMFILTER_VISIBLE | ANIMFILTER_CURVEVISIBLE | ANIMFILTER_CURVESONLY);
-                       ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, fcu, ANIMTYPE_FCURVE);
+               if (nvi->fcu->flag & FCURVE_SELECTED) {
+                       int filter= (ANIMFILTER_VISIBLE | ANIMFILTER_CURVEVISIBLE | ANIMFILTER_CURVESONLY);
+                       ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, nvi->fcu, ANIMTYPE_FCURVE);
                }
        }
+       
+       /* free temp sample data for filtering */
+       MEM_freeN(nvi);
 }
 
 /* Option 2) Selects all the keyframes on either side of the current frame (depends on which side the mouse is on) */
@@ -787,8 +1076,8 @@ static void graphkeys_mselect_leftright (bAnimContext *ac, short leftright, shor
        int filter;
        
        SpaceIpo *sipo= (SpaceIpo *)ac->sa->spacedata.first;
-       BeztEditFunc ok_cb, select_cb;
-       BeztEditData bed;
+       KeyframeEditFunc ok_cb, select_cb;
+       KeyframeEditData ked;
        Scene *scene= ac->scene;
        
        /* if select mode is replace, deselect all keyframes (and channels) first */
@@ -811,14 +1100,14 @@ static void graphkeys_mselect_leftright (bAnimContext *ac, short leftright, shor
        ok_cb= ANIM_editkeyframes_ok(BEZT_OK_FRAMERANGE);
        select_cb= ANIM_editkeyframes_select(select_mode);
        
-       memset(&bed, 0, sizeof(BeztEditFunc));
+       memset(&ked, 0, sizeof(KeyframeEditFunc));
        if (leftright == GRAPHKEYS_LRSEL_LEFT) {
-               bed.f1 = MINAFRAMEF;
-               bed.f2 = (float)(CFRA + 0.1f);
+               ked.f1 = MINAFRAMEF;
+               ked.f2 = (float)(CFRA + 0.1f);
        } 
        else {
-               bed.f1 = (float)(CFRA - 0.1f);
-               bed.f2 = MAXFRAMEF;
+               ked.f1 = (float)(CFRA - 0.1f);
+               ked.f2 = MAXFRAMEF;
        }
        
        /* filter data */
@@ -831,11 +1120,11 @@ static void graphkeys_mselect_leftright (bAnimContext *ac, short leftright, shor
                
                if (adt) {
                        ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 1);
-                       ANIM_fcurve_keys_bezier_loop(&bed, ale->key_data, ok_cb, select_cb, NULL);
+                       ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL);
                        ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 1);
                }
                else
-                       ANIM_fcurve_keys_bezier_loop(&bed, ale->key_data, ok_cb, select_cb, NULL);
+                       ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL);
        }
        
        /* Cleanup */
@@ -850,19 +1139,24 @@ static void graphkeys_mselect_column (bAnimContext *ac, int mval[2], short selec
        int filter;
        
        SpaceIpo *sipo= (SpaceIpo *)ac->sa->spacedata.first;
-       BeztEditFunc select_cb, ok_cb;
-       BeztEditData bed;
-       FCurve *fcu;
-       BezTriple *bezt;
+       KeyframeEditFunc select_cb, ok_cb;
+       KeyframeEditData ked;
+       tNearestVertInfo *nvi;
        float selx = (float)ac->scene->r.cfra;
        
-       /* find the beztriple that occurs on this frame, and use his as the frame number we're using */
-       findnearest_fcurve_vert(ac, mval, &fcu, &bezt);
+       /* find the beztriple that we're selecting, and the handle that was clicked on */
+       nvi = find_nearest_fcurve_vert(ac, mval);
        
        /* check if anything to select */
-       if (ELEM(NULL, fcu, bezt))      
+       if (nvi == NULL)        
                return;
-       selx= bezt->vec[1][0];
+       
+       /* get frame number on which elements should be selected */
+       // TODO: should we restrict to integer frames only?
+       if (nvi->bezt)
+               selx= nvi->bezt->vec[1][0];
+       else if (nvi->fpt)
+               selx= nvi->fpt->vec[0];
        
        /* if select mode is replace, deselect all keyframes (and channels) first */
        if (select_mode==SELECT_REPLACE) {
@@ -881,7 +1175,7 @@ static void graphkeys_mselect_column (bAnimContext *ac, int mval[2], short selec
        }
        
        /* initialise keyframe editing data */
-       memset(&bed, 0, sizeof(BeztEditData));
+       memset(&ked, 0, sizeof(KeyframeEditData));
        
        /* set up BezTriple edit callbacks */
        select_cb= ANIM_editkeyframes_select(select_mode);
@@ -898,16 +1192,17 @@ static void graphkeys_mselect_column (bAnimContext *ac, int mval[2], short selec
                
                /* set frame for validation callback to refer to */
                if (adt)
-                       bed.f1= BKE_nla_tweakedit_remap(adt, selx, NLATIME_CONVERT_UNMAP);
+                       ked.f1= BKE_nla_tweakedit_remap(adt, selx, NLATIME_CONVERT_UNMAP);
                else
-                       bed.f1= selx;
+                       ked.f1= selx;
                
                /* select elements with frame number matching cfra */
-               ANIM_fcurve_keys_bezier_loop(&bed, ale->key_data, ok_cb, select_cb, NULL);
+               ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL);
        }
        
        /* free elements */
-       BLI_freelistN(&bed.list);
+       MEM_freeN(nvi);
+       BLI_freelistN(&ked.list);
        BLI_freelistN(&anim_data);
 }
  
@@ -980,7 +1275,7 @@ void GRAPH_OT_clickselect (wmOperatorType *ot)
        /* identifiers */
        ot->name= "Mouse Select Keys";
        ot->idname= "GRAPH_OT_clickselect";
-       ot->description= "Select keyframes by clicking on them.";
+       ot->description= "Select keyframes by clicking on them";
        
        /* api callbacks */
        ot->invoke= graphkeys_clickselect_invoke;