new_grid: Rewrite of Snap and Grid. (squashed commit)
[ardour.git] / gtk2_ardour / editor.cc
index 9ce693184a966782ba1bf43824806ef6ce551dd8..ba9f9d1a7416de9311ca7cdc91a690b1bbc9778e 100644 (file)
 #include "selection.h"
 #include "simple_progress_dialog.h"
 #include "sfdb_ui.h"
-#include "tempo_lines.h"
+#include "grid_lines.h"
 #include "time_axis_view.h"
 #include "time_info_box.h"
 #include "timers.h"
@@ -160,44 +160,28 @@ using Gtkmm2ext::Keyboard;
 
 double Editor::timebar_height = 15.0;
 
-static const gchar *_snap_type_strings[] = {
-       N_("CD Samples"),
-       N_("TC Frames"),
-       N_("TC Seconds"),
-       N_("TC Minutes"),
-       N_("Seconds"),
-       N_("Minutes"),
-       N_("Beats/128"),
-       N_("Beats/64"),
-       N_("Beats/32"),
-       N_("Beats/28"),
-       N_("Beats/24"),
-       N_("Beats/20"),
-       N_("Beats/16"),
-       N_("Beats/14"),
-       N_("Beats/12"),
-       N_("Beats/10"),
-       N_("Beats/8"),
-       N_("Beats/7"),
-       N_("Beats/6"),
-       N_("Beats/5"),
-       N_("Beats/4"),
-       N_("Beats/3"),
-       N_("Beats/2"),
-       N_("Beats"),
-       N_("Bars"),
-       N_("Marks"),
-       N_("Region starts"),
-       N_("Region ends"),
-       N_("Region syncs"),
-       N_("Region bounds"),
-       0
-};
-
-static const gchar *_snap_mode_strings[] = {
+static const gchar *_grid_type_strings[] = {
        N_("No Grid"),
-       N_("Grid"),
-       N_("Magnetic"),
+       N_("Bar"),
+       N_("1/4 Note"),
+       N_("1/8 Note"),
+       N_("1/16 Note"),
+       N_("1/32 Note"),
+       N_("1/64 Note"),
+       N_("1/128 Note"),
+       N_("1/3 (8th triplet)"), // or "1/12" ?
+       N_("1/6 (16th triplet)"),
+       N_("1/12 (32nd triplet)"),
+       N_("1/24 (64th triplet)"),
+       N_("1/5 (8th quintuplet)"),
+       N_("1/10 (16th quintuplet)"),
+       N_("1/20 (32nd quintuplet)"),
+       N_("1/7 (8th septuplet)"),
+       N_("1/14 (16th septuplet)"),
+       N_("1/28 (32nd septuplet)"),
+       N_("Smpte"),
+       N_("MinSec"),
+       N_("Samples"),
        0
 };
 
@@ -252,9 +236,9 @@ Editor::Editor ()
        , samples_per_pixel (2048)
        , zoom_focus (ZoomFocusPlayhead)
        , mouse_mode (MouseObject)
-       , pre_internal_snap_type (SnapToBeat)
+       , pre_internal_grid_type (GridTypeBeat)
        , pre_internal_snap_mode (SnapOff)
-       , internal_snap_type (SnapToBeat)
+       , internal_grid_type (GridTypeBeat)
        , internal_snap_mode (SnapOff)
        , _join_object_range_state (JOIN_OBJECT_RANGE_NONE)
        , _notebook_shrunk (false)
@@ -337,6 +321,7 @@ Editor::Editor ()
        , videotl_group (0)
        , snapped_cursor (0)
        , playhead_cursor (0)
+       , _region_boundary_cache_dirty (true)
        , edit_packer (4, 4, true)
        , vertical_adjustment (0.0, 0.0, 10.0, 400.0)
        , horizontal_adjustment (0.0, 0.0, 1e16)
@@ -366,7 +351,7 @@ Editor::Editor ()
        , scrub_reverse_distance (0)
        , have_pending_keyboard_selection (false)
        , pending_keyboard_selection_start (0)
-       , _snap_type (SnapToBeat)
+       , _grid_type (GridTypeBeat)
        , _snap_mode (SnapOff)
        , ignore_gui_changes (false)
        , _drags (new DragManager (this))
@@ -374,11 +359,10 @@ Editor::Editor ()
          /* , last_event_time { 0, 0 } */ /* this initialization style requires C++11 */
        , _dragging_playhead (false)
        , _dragging_edit_point (false)
-       , _show_measures (true)
        , _follow_playhead (true)
        , _stationary_playhead (false)
        , _maximised (false)
-       , tempo_lines (0)
+       , grid_lines (0)
        , global_rect_group (0)
        , time_line_group (0)
        , tempo_marker_menu (0)
@@ -472,8 +456,7 @@ Editor::Editor ()
        selection_op_history.clear();
        before.clear();
 
-       snap_type_strings =  I18N (_snap_type_strings);
-       snap_mode_strings =  I18N (_snap_mode_strings);
+       grid_type_strings =  I18N (_grid_type_strings);
        zoom_focus_strings = I18N (_zoom_focus_strings);
        edit_mode_strings = I18N (_edit_mode_strings);
        edit_point_strings = I18N (_edit_point_strings);
@@ -485,8 +468,7 @@ Editor::Editor ()
        build_edit_mode_menu();
        build_zoom_focus_menu();
        build_track_count_menu();
-       build_snap_mode_menu();
-       build_snap_type_menu();
+       build_grid_type_menu();
        build_edit_point_menu();
 
        location_marker_color = UIConfiguration::instance().color ("location marker");
@@ -855,6 +837,8 @@ Editor::Editor ()
 
        setup_fade_images ();
 
+       set_grid_to (GridTypeNone);
+
        instant_save ();
 }
 
@@ -908,7 +892,7 @@ Editor::button_settings () const
 bool
 Editor::get_smart_mode () const
 {
-       return ((current_mouse_mode() == Editing::MouseObject) && smart_mode_action->get_active());
+       return ((current_mouse_mode() == MouseObject) && smart_mode_action->get_active());
 }
 
 void
@@ -1429,18 +1413,6 @@ Editor::set_session (Session *t)
                sigc::mem_fun (*this, &Editor::super_rapid_screen_update)
                );
 
-       switch (_snap_type) {
-       case SnapToRegionStart:
-       case SnapToRegionEnd:
-       case SnapToRegionSync:
-       case SnapToRegionBoundary:
-               build_region_boundary_cache ();
-               break;
-
-       default:
-               break;
-       }
-
        /* register for undo history */
        _session->register_with_memento_command_factory(id(), this);
        _session->register_with_memento_command_factory(_selection_memento->id(), _selection_memento);
@@ -2138,43 +2110,73 @@ Editor::add_bus_context_items (Menu_Helpers::MenuList& edit_items)
        edit_items.push_back (MenuElem (_("Nudge"), *nudge_menu));
 }
 
-SnapType
-Editor::snap_type() const
+GridType
+Editor::grid_type() const
 {
-       return _snap_type;
+       return _grid_type;
 }
 
 bool
-Editor::snap_musical() const
-{
-       switch (_snap_type) {
-       case SnapToBeatDiv128:
-       case SnapToBeatDiv64:
-       case SnapToBeatDiv32:
-       case SnapToBeatDiv28:
-       case SnapToBeatDiv24:
-       case SnapToBeatDiv20:
-       case SnapToBeatDiv16:
-       case SnapToBeatDiv14:
-       case SnapToBeatDiv12:
-       case SnapToBeatDiv10:
-       case SnapToBeatDiv8:
-       case SnapToBeatDiv7:
-       case SnapToBeatDiv6:
-       case SnapToBeatDiv5:
-       case SnapToBeatDiv4:
-       case SnapToBeatDiv3:
-       case SnapToBeatDiv2:
-       case SnapToBeat:
-       case SnapToBar:
+Editor::grid_musical() const
+{
+       switch (_grid_type) {
+       case GridTypeBeatDiv32:
+       case GridTypeBeatDiv28:
+       case GridTypeBeatDiv24:
+       case GridTypeBeatDiv20:
+       case GridTypeBeatDiv16:
+       case GridTypeBeatDiv14:
+       case GridTypeBeatDiv12:
+       case GridTypeBeatDiv10:
+       case GridTypeBeatDiv8:
+       case GridTypeBeatDiv7:
+       case GridTypeBeatDiv6:
+       case GridTypeBeatDiv5:
+       case GridTypeBeatDiv4:
+       case GridTypeBeatDiv3:
+       case GridTypeBeatDiv2:
+       case GridTypeBeat:
+       case GridTypeBar:
                return true;
-       default:
-               break;
+       case GridTypeNone:
+       case GridTypeSmpte:
+       case GridTypeMinSec:
+       case GridTypeSamples:
+               return false;
        }
-
        return false;
 }
 
+bool
+Editor::grid_nonmusical() const
+{
+       switch (_grid_type) {
+       case GridTypeSmpte:
+       case GridTypeMinSec:
+       case GridTypeSamples:
+               return true;
+       case GridTypeBeatDiv32:
+       case GridTypeBeatDiv28:
+       case GridTypeBeatDiv24:
+       case GridTypeBeatDiv20:
+       case GridTypeBeatDiv16:
+       case GridTypeBeatDiv14:
+       case GridTypeBeatDiv12:
+       case GridTypeBeatDiv10:
+       case GridTypeBeatDiv8:
+       case GridTypeBeatDiv7:
+       case GridTypeBeatDiv6:
+       case GridTypeBeatDiv5:
+       case GridTypeBeatDiv4:
+       case GridTypeBeatDiv3:
+       case GridTypeBeatDiv2:
+       case GridTypeBeat:
+       case GridTypeBar:
+       case GridTypeNone:
+               return false;
+       }
+       return false;
+}
 SnapMode
 Editor::snap_mode() const
 {
@@ -2182,67 +2184,79 @@ Editor::snap_mode() const
 }
 
 void
-Editor::set_snap_to (SnapType st)
+Editor::set_grid_to (GridType gt)
 {
-       unsigned int snap_ind = (unsigned int)st;
+       if (_grid_type == gt) {  //already set
+               return;
+       }
+
+       unsigned int grid_ind = (unsigned int)gt;
 
        if (internal_editing()) {
-               internal_snap_type = st;
+               internal_grid_type = gt;
        } else {
-               pre_internal_snap_type = st;
+               pre_internal_grid_type = gt;
        }
 
-       _snap_type = st;
+       _grid_type = gt;
 
-       if (snap_ind > snap_type_strings.size() - 1) {
-               snap_ind = 0;
-               _snap_type = (SnapType)snap_ind;
+       if (grid_ind > grid_type_strings.size() - 1) {
+               grid_ind = 0;
+               _grid_type = (GridType)grid_ind;
        }
 
-       string str = snap_type_strings[snap_ind];
+       string str = grid_type_strings[grid_ind];
 
-       if (str != snap_type_selector.get_text()) {
-               snap_type_selector.set_text (str);
+       if (str != grid_type_selector.get_text()) {
+               grid_type_selector.set_text (str);
+       }
+
+       //show appropriate rulers for this grid setting.  (ToDo:  perhaps make this optional)
+       //Currently this is 'required' because the RULER calculates the grid_marks which will be used by grid_lines 
+       if ( grid_musical() ) {
+               ruler_tempo_action->set_active(true);
+               ruler_meter_action->set_active(true);
+
+               ruler_bbt_action->set_active(true);
+               ruler_timecode_action->set_active(false);
+               ruler_minsec_action->set_active(false);
+               ruler_samples_action->set_active(false);
+       } else if (_grid_type == GridTypeSmpte ) {
+               ruler_tempo_action->set_active(false);
+               ruler_meter_action->set_active(false);
+
+               ruler_bbt_action->set_active(false);
+               ruler_timecode_action->set_active(true);
+               ruler_minsec_action->set_active(false);
+               ruler_samples_action->set_active(false);
+       } else if (_grid_type == GridTypeMinSec ) {
+               ruler_tempo_action->set_active(false);
+               ruler_meter_action->set_active(false);
+
+               ruler_bbt_action->set_active(false);
+               ruler_timecode_action->set_active(false);
+               ruler_minsec_action->set_active(true);
+               ruler_samples_action->set_active(false);
+       } else if (_grid_type == GridTypeSamples ) {
+               ruler_tempo_action->set_active(false);
+               ruler_meter_action->set_active(false);
+
+               ruler_bbt_action->set_active(false);
+               ruler_timecode_action->set_active(false);
+               ruler_minsec_action->set_active(false);
+               ruler_samples_action->set_active(true);
        }
 
        instant_save ();
 
-       switch (_snap_type) {
-       case SnapToBeatDiv128:
-       case SnapToBeatDiv64:
-       case SnapToBeatDiv32:
-       case SnapToBeatDiv28:
-       case SnapToBeatDiv24:
-       case SnapToBeatDiv20:
-       case SnapToBeatDiv16:
-       case SnapToBeatDiv14:
-       case SnapToBeatDiv12:
-       case SnapToBeatDiv10:
-       case SnapToBeatDiv8:
-       case SnapToBeatDiv7:
-       case SnapToBeatDiv6:
-       case SnapToBeatDiv5:
-       case SnapToBeatDiv4:
-       case SnapToBeatDiv3:
-       case SnapToBeatDiv2: {
+       if ( grid_musical() ) {
                compute_bbt_ruler_scale (_leftmost_sample, _leftmost_sample + current_page_samples());
                update_tempo_based_rulers ();
-               break;
        }
 
-       case SnapToRegionStart:
-       case SnapToRegionEnd:
-       case SnapToRegionSync:
-       case SnapToRegionBoundary:
-               build_region_boundary_cache ();
-               break;
-
-       default:
-               /* relax */
-               break;
-       }
+       mark_region_boundary_cache_dirty ();
 
-       redisplay_tempo (false);
+       redisplay_grid (false);
 
        SnapChanged (); /* EMIT SIGNAL */
 }
@@ -2250,8 +2264,6 @@ Editor::set_snap_to (SnapType st)
 void
 Editor::set_snap_mode (SnapMode mode)
 {
-       string str = snap_mode_strings[(int)mode];
-
        if (internal_editing()) {
                internal_snap_mode = mode;
        } else {
@@ -2259,9 +2271,11 @@ Editor::set_snap_mode (SnapMode mode)
        }
 
        _snap_mode = mode;
-
-       if (str != snap_mode_selector.get_text ()) {
-               snap_mode_selector.set_text (str);
+       
+       if (_snap_mode == SnapOff ) {
+               snap_mode_button.set_active_state (Gtkmm2ext::Off);
+       } else {
+               snap_mode_button.set_active_state (Gtkmm2ext::ExplicitActive);
        }
 
        instant_save ();
@@ -2292,7 +2306,6 @@ Editor::set_edit_point_preference (EditPoint ep, bool force)
 
        switch (_edit_point) {
        case EditAtPlayhead:
-//ToDo:  hide or show mouse_cursor
                action = "edit-at-playhead";
                break;
        case EditAtSelectedMarker:
@@ -2360,11 +2373,11 @@ Editor::set_state (const XMLNode& node, int version)
                set_visible_track_count (cnt);
        }
 
-       SnapType snap_type;
-       if (!node.get_property ("snap-to", snap_type)) {
-               snap_type = _snap_type;
+       GridType grid_type;
+       if (!node.get_property ("grid-type", grid_type)) {
+               grid_type = _grid_type;
        }
-       set_snap_to (snap_type);
+       set_grid_to (grid_type);
 
        SnapMode sm;
        if (node.get_property ("snap-mode", sm)) {
@@ -2378,9 +2391,9 @@ Editor::set_state (const XMLNode& node, int version)
                set_snap_mode (_snap_mode);
        }
 
-       node.get_property ("internal-snap-to", internal_snap_type);
+       node.get_property ("internal-grid-type", internal_grid_type);
        node.get_property ("internal-snap-mode", internal_snap_mode);
-       node.get_property ("pre-internal-snap-to", pre_internal_snap_type);
+       node.get_property ("pre-internal-grid-type", pre_internal_grid_type);
        node.get_property ("pre-internal-snap-mode", pre_internal_snap_mode);
 
        std::string mm_str;
@@ -2421,8 +2434,6 @@ Editor::set_state (const XMLNode& node, int version)
                set_edit_point_preference (_edit_point);
        }
 
-       node.get_property ("show-measures", _show_measures);
-
        if (node.get_property ("follow-playhead", yn)) {
                set_follow_playhead (yn);
        }
@@ -2508,15 +2519,6 @@ Editor::set_state (const XMLNode& node, int version)
                 */
                RefPtr<Action> act;
 
-               act = ActionManager::get_action (X_("Editor"), X_("ToggleMeasureVisibility"));
-               if (act) {
-                       yn = _show_measures;
-                       RefPtr<ToggleAction> tact = RefPtr<ToggleAction>::cast_dynamic(act);
-                       /* do it twice to force the change */
-                       tact->set_active (!yn);
-                       tact->set_active (yn);
-               }
-
                act = ActionManager::get_action (X_("Editor"), X_("toggle-follow-playhead"));
                yn = _follow_playhead;
                if (act) {
@@ -2557,11 +2559,11 @@ Editor::get_state ()
        node->set_property ("zoom-focus", zoom_focus);
 
        node->set_property ("zoom", samples_per_pixel);
-       node->set_property ("snap-to", _snap_type);
+       node->set_property ("grid-type", _grid_type);
        node->set_property ("snap-mode", _snap_mode);
-       node->set_property ("internal-snap-to", internal_snap_type);
+       node->set_property ("internal-grid-type", internal_grid_type);
        node->set_property ("internal-snap-mode", internal_snap_mode);
-       node->set_property ("pre-internal-snap-to", pre_internal_snap_type);
+       node->set_property ("pre-internal-grid-type", pre_internal_grid_type);
        node->set_property ("pre-internal-snap-mode", pre_internal_snap_mode);
        node->set_property ("edit-point", _edit_point);
        node->set_property ("visible-track-count", _visible_track_count);
@@ -2570,7 +2572,6 @@ Editor::get_state ()
        node->set_property ("left-frame", _leftmost_sample);
        node->set_property ("y-origin", vertical_adjustment.get_value ());
 
-       node->set_property ("show-measures", _show_measures);
        node->set_property ("maximised", _maximised);
        node->set_property ("follow-playhead", _follow_playhead);
        node->set_property ("stationary-playhead", _stationary_playhead);
@@ -2658,7 +2659,7 @@ Editor::set_snapped_cursor_position (samplepos_t pos)
  *  @param event Event to get current key modifier information from, or 0.
  */
 void
-Editor::snap_to_with_modifier (MusicSample& start, GdkEvent const * event, RoundMode direction, bool for_mark)
+Editor::snap_to_with_modifier (MusicSample& start, GdkEvent const * event, RoundMode direction, SnapPref pref, bool for_mark)
 {
        if (!_session || !event) {
                return;
@@ -2666,16 +2667,16 @@ Editor::snap_to_with_modifier (MusicSample& start, GdkEvent const * event, Round
 
        if (ArdourKeyboard::indicates_snap (event->button.state)) {
                if (_snap_mode == SnapOff) {
-                       snap_to_internal (start, direction, for_mark);
+                       snap_to_internal (start, direction, pref, for_mark);
                } else {
                        start.set (start.sample, 0);
                }
        } else {
                if (_snap_mode != SnapOff) {
-                       snap_to_internal (start, direction, for_mark);
+                       snap_to_internal (start, direction, pref, for_mark);
                } else if (ArdourKeyboard::indicates_snap_delta (event->button.state)) {
                        /* SnapOff, but we pressed the snap_delta modifier */
-                       snap_to_internal (start, direction, for_mark);
+                       snap_to_internal (start, direction, pref, for_mark);
                } else {
                        start.set (start.sample, 0);
                }
@@ -2683,249 +2684,142 @@ Editor::snap_to_with_modifier (MusicSample& start, GdkEvent const * event, Round
 }
 
 void
-Editor::snap_to (MusicSample& start, RoundMode direction, bool for_mark, bool ensure_snap)
+Editor::snap_to (MusicSample& start, RoundMode direction, SnapPref pref, bool for_mark, bool ensure_snap)
 {
        if (!_session || (_snap_mode == SnapOff && !ensure_snap)) {
                start.set (start.sample, 0);
                return;
        }
 
-       snap_to_internal (start, direction, for_mark, ensure_snap);
+       snap_to_internal (start, direction, pref, for_mark, ensure_snap);
 }
 
 void
-Editor::timecode_snap_to_internal (MusicSample& pos, RoundMode direction, bool /*for_mark*/)
+check_best_snap ( samplepos_t presnap, samplepos_t &test, samplepos_t &dist, samplepos_t &best  )
 {
-       samplepos_t start = pos.sample;
-       const samplepos_t one_timecode_second = (samplepos_t)(rint(_session->timecode_frames_per_second()) * _session->samples_per_timecode_frame());
-       samplepos_t one_timecode_minute = (samplepos_t)(rint(_session->timecode_frames_per_second()) * _session->samples_per_timecode_frame() * 60);
-
-       switch (_snap_type) {
-       case SnapToTimecodeFrame:
-               if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
-                   fmod((double)start, (double)_session->samples_per_timecode_frame()) == 0) {
-                       /* start is already on a whole timecode frame, do nothing */
-               } else if (((direction == 0) && (fmod((double)start, (double)_session->samples_per_timecode_frame()) > (_session->samples_per_timecode_frame() / 2))) || (direction > 0)) {
-                       start = (samplepos_t) (ceil ((double) start / _session->samples_per_timecode_frame()) * _session->samples_per_timecode_frame());
-               } else {
-                       start = (samplepos_t) (floor ((double) start / _session->samples_per_timecode_frame()) *  _session->samples_per_timecode_frame());
-               }
-               break;
-
-       case SnapToTimecodeSeconds:
-               if (_session->config.get_timecode_offset_negative()) {
-                       start += _session->config.get_timecode_offset ();
-               } else {
-                       start -= _session->config.get_timecode_offset ();
-               }
-               if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
-                   (start % one_timecode_second == 0)) {
-                       /* start is already on a whole second, do nothing */
-               } else if (((direction == 0) && (start % one_timecode_second > one_timecode_second / 2)) || direction > 0) {
-                       start = (samplepos_t) ceil ((double) start / one_timecode_second) * one_timecode_second;
-               } else {
-                       start = (samplepos_t) floor ((double) start / one_timecode_second) * one_timecode_second;
-               }
-
-               if (_session->config.get_timecode_offset_negative()) {
-                       start -= _session->config.get_timecode_offset ();
-               } else {
-                       start += _session->config.get_timecode_offset ();
-               }
-               break;
-
-       case SnapToTimecodeMinutes:
-               if (_session->config.get_timecode_offset_negative()) {
-                       start += _session->config.get_timecode_offset ();
-               } else {
-                       start -= _session->config.get_timecode_offset ();
-               }
-               if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
-                   (start % one_timecode_minute == 0)) {
-                       /* start is already on a whole minute, do nothing */
-               } else if (((direction == 0) && (start % one_timecode_minute > one_timecode_minute / 2)) || direction > 0) {
-                       start = (samplepos_t) ceil ((double) start / one_timecode_minute) * one_timecode_minute;
-               } else {
-                       start = (samplepos_t) floor ((double) start / one_timecode_minute) * one_timecode_minute;
-               }
-               if (_session->config.get_timecode_offset_negative()) {
-                       start -= _session->config.get_timecode_offset ();
-               } else {
-                       start += _session->config.get_timecode_offset ();
-               }
-               break;
-       default:
-               fatal << "Editor::smpte_snap_to_internal() called with non-timecode snap type!" << endmsg;
-               abort(); /*NOTREACHED*/
+       samplepos_t diff = abs( test - presnap );
+       if ( diff < dist ) {
+               dist = diff;
+               best = test;
        }
 
-       pos.set (start, 0);
+       test = max_samplepos; //reset this so it doesn't get accidentally reused
 }
 
-void
-Editor::snap_to_internal (MusicSample& start, RoundMode direction, bool for_mark, bool ensure_snap)
+samplepos_t
+Editor::snap_to_grid (vector<ArdourCanvas::Ruler::Mark> marks, samplepos_t presnap, RoundMode direction)
 {
-       const samplepos_t one_second = _session->sample_rate();
-       const samplepos_t one_minute = _session->sample_rate() * 60;
-       samplepos_t presnap = start.sample;
        samplepos_t before;
        samplepos_t after;
+       samplepos_t test;
 
-       int snap_threshold_s = pixel_to_sample(UIConfiguration::instance().get_snap_threshold());
-
-       switch (_snap_type) {
-       case SnapToTimecodeFrame:
-       case SnapToTimecodeSeconds:
-       case SnapToTimecodeMinutes:
-               return timecode_snap_to_internal (start, direction, for_mark);
-
-       case SnapToCDFrame:
-               if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
-                   start.sample % (one_second/75) == 0) {
-                       /* start is already on a whole CD sample, do nothing */
-               } else if (((direction == 0) && (start.sample % (one_second/75) > (one_second/75) / 2)) || (direction > 0)) {
-                       start.sample = (samplepos_t) ceil ((double) start.sample / (one_second / 75)) * (one_second / 75);
-               } else {
-                       start.sample = (samplepos_t) floor ((double) start.sample / (one_second / 75)) * (one_second / 75);
-               }
-
-               start.set (start.sample, 0);
+       before = after = max_samplepos;
 
-               break;
+       //get marks to either side of presnap
+       vector<ArdourCanvas::Ruler::Mark>::const_iterator m = marks.begin();
+       while ( m != marks.end() && (m->position < presnap) ) {
+               ++m;
+       }
 
-       case SnapToSeconds:
-               if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
-                   start.sample % one_second == 0) {
-                       /* start is already on a whole second, do nothing */
-               } else if (((direction == 0) && (start.sample % one_second > one_second / 2)) || (direction > 0)) {
-                       start.sample = (samplepos_t) ceil ((double) start.sample / one_second) * one_second;
-               } else {
-                       start.sample = (samplepos_t) floor ((double) start.sample / one_second) * one_second;
-               }
+       if (m == marks.end ()) {
+               /* ran out of marks */
+               before = grid_marks.back().position;
+       }
 
-               start.set (start.sample, 0);
+       after = m->position;
 
-               break;
+       if (m != marks.begin ()) {
+               --m;
+               before = m->position;
+       }
 
-       case SnapToMinutes:
-               if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
-                   start.sample % one_minute == 0) {
-                       /* start is already on a whole minute, do nothing */
-               } else if (((direction == 0) && (start.sample % one_minute > one_minute / 2)) || (direction > 0)) {
-                       start.sample = (samplepos_t) ceil ((double) start.sample / one_minute) * one_minute;
-               } else {
-                       start.sample = (samplepos_t) floor ((double) start.sample / one_minute) * one_minute;
+       if (before == max_samplepos && after == max_samplepos) {
+               /* No smpte to snap to, so just don't snap */
+               return presnap;
+       } else if (before == max_samplepos) {
+               test = after;
+       } else if (after == max_samplepos) {
+               test = before;
+       } else  {
+               if ((direction == RoundUpMaybe || direction == RoundUpAlways))
+                       test = after;
+               else if ((direction == RoundDownMaybe || direction == RoundDownAlways))
+                       test = before;
+               else if (direction ==  0 ) {
+                       if ((presnap - before) < (after - presnap)) {
+                               test = before;
+                       } else {
+                               test = after;
+                       }
                }
+       }
 
-               start.set (start.sample, 0);
+       return test;
+}
 
-               break;
+samplepos_t
+Editor::marker_snap_to_internal (samplepos_t presnap, RoundMode direction)
+{
+       samplepos_t before;
+       samplepos_t after;
+       samplepos_t test;
+
+       _session->locations()->marks_either_side (presnap, before, after);
+
+       if (before == max_samplepos && after == max_samplepos) {
+               /* No marks to snap to, so just don't snap */
+               return presnap;
+       } else if (before == max_samplepos) {
+               test = after;
+       } else if (after == max_samplepos) {
+               test = before;
+       } else  {
+               if ((direction == RoundUpMaybe || direction == RoundUpAlways)) {
+                       test = after;
+               } else if ((direction == RoundDownMaybe || direction == RoundDownAlways)) {
+                       test = before;
+               } else if (direction ==  0 ) {
+                       if ((presnap - before) < (after - presnap)) {
+                               test = before;
+                       } else {
+                               test = after;
+                       }
+               }
+       }
 
-       case SnapToBar:
-               start = _session->tempo_map().round_to_bar (start.sample, direction);
-               break;
+       return test;
+}
 
-       case SnapToBeat:
-               start = _session->tempo_map().round_to_beat (start.sample, direction);
-               break;
+void
+Editor::snap_to_internal (MusicSample& start, RoundMode direction, SnapPref pref, bool for_mark, bool ensure_snap)
+{
+       const samplepos_t presnap = start.sample;
 
-       case SnapToBeatDiv128:
-               start = _session->tempo_map().round_to_quarter_note_subdivision (start.sample, 128, direction);
-               break;
-       case SnapToBeatDiv64:
-               start = _session->tempo_map().round_to_quarter_note_subdivision (start.sample, 64, direction);
-               break;
-       case SnapToBeatDiv32:
-               start = _session->tempo_map().round_to_quarter_note_subdivision (start.sample, 32, direction);
-               break;
-       case SnapToBeatDiv28:
-               start = _session->tempo_map().round_to_quarter_note_subdivision (start.sample, 28, direction);
-               break;
-       case SnapToBeatDiv24:
-               start = _session->tempo_map().round_to_quarter_note_subdivision (start.sample, 24, direction);
-               break;
-       case SnapToBeatDiv20:
-               start = _session->tempo_map().round_to_quarter_note_subdivision (start.sample, 20, direction);
-               break;
-       case SnapToBeatDiv16:
-               start = _session->tempo_map().round_to_quarter_note_subdivision (start.sample, 16, direction);
-               break;
-       case SnapToBeatDiv14:
-               start = _session->tempo_map().round_to_quarter_note_subdivision (start.sample, 14, direction);
-               break;
-       case SnapToBeatDiv12:
-               start = _session->tempo_map().round_to_quarter_note_subdivision (start.sample, 12, direction);
-               break;
-       case SnapToBeatDiv10:
-               start = _session->tempo_map().round_to_quarter_note_subdivision (start.sample, 10, direction);
-               break;
-       case SnapToBeatDiv8:
-               start = _session->tempo_map().round_to_quarter_note_subdivision (start.sample, 8, direction);
-               break;
-       case SnapToBeatDiv7:
-               start = _session->tempo_map().round_to_quarter_note_subdivision (start.sample, 7, direction);
-               break;
-       case SnapToBeatDiv6:
-               start = _session->tempo_map().round_to_quarter_note_subdivision (start.sample, 6, direction);
-               break;
-       case SnapToBeatDiv5:
-               start = _session->tempo_map().round_to_quarter_note_subdivision (start.sample, 5, direction);
-               break;
-       case SnapToBeatDiv4:
-               start = _session->tempo_map().round_to_quarter_note_subdivision (start.sample, 4, direction);
-               break;
-       case SnapToBeatDiv3:
-               start = _session->tempo_map().round_to_quarter_note_subdivision (start.sample, 3, direction);
-               break;
-       case SnapToBeatDiv2:
-               start = _session->tempo_map().round_to_quarter_note_subdivision (start.sample, 2, direction);
-               break;
+       samplepos_t test = max_samplepos;  //for each snap, we'll use this value
+       samplepos_t dist = max_samplepos;  //this records the distance of the best snap result we've found so far
+       samplepos_t best = max_samplepos;  //this records the best snap-result we've found so far
 
-       case SnapToMark:
+       //check snap-to-marker
+       if ( UIConfiguration::instance().get_snap_to_marks() ) {
                if (for_mark) {
                        return;
                }
 
-               _session->locations()->marks_either_side (start.sample, before, after);
-
-               if (before == max_samplepos && after == max_samplepos) {
-                       /* No marks to snap to, so just don't snap */
-                       return;
-               } else if (before == max_samplepos) {
-                       start.sample = after;
-               } else if (after == max_samplepos) {
-                       start.sample = before;
-               } else if (before != max_samplepos && after != max_samplepos) {
-                       if ((direction == RoundUpMaybe || direction == RoundUpAlways))
-                               start.sample = after;
-                       else if ((direction == RoundDownMaybe || direction == RoundDownAlways))
-                               start.sample = before;
-                       else if (direction ==  0 ) {
-                               if ((start.sample - before) < (after - start.sample)) {
-                                       start.sample = before;
-                               } else {
-                                       start.sample = after;
-                               }
-                       }
-               }
-
-               start.set (start.sample, 0);
-
-               break;
+               test = marker_snap_to_internal ( presnap, direction );
+               check_best_snap(presnap, test, dist, best);
+       }
 
-       case SnapToRegionStart:
-       case SnapToRegionEnd:
-       case SnapToRegionSync:
-       case SnapToRegionBoundary:
+       //check snap-to-region-{start/end/sync}
+       if ( UIConfiguration::instance().get_snap_to_region_start() || UIConfiguration::instance().get_snap_to_region_end() || UIConfiguration::instance().get_snap_to_region_sync() ) {
                if (!region_boundary_cache.empty()) {
 
                        vector<samplepos_t>::iterator prev = region_boundary_cache.end ();
                        vector<samplepos_t>::iterator next = region_boundary_cache.end ();
 
                        if (direction > 0) {
-                               next = std::upper_bound (region_boundary_cache.begin(), region_boundary_cache.end(), start.sample);
+                               next = std::upper_bound (region_boundary_cache.begin(), region_boundary_cache.end(), presnap);
                        } else {
-                               next = std::lower_bound (region_boundary_cache.begin(), region_boundary_cache.end(), start.sample);
+                               next = std::lower_bound (region_boundary_cache.begin(), region_boundary_cache.end(), presnap);
                        }
 
                        if (next != region_boundary_cache.begin ()) {
@@ -2936,43 +2830,45 @@ Editor::snap_to_internal (MusicSample& start, RoundMode direction, bool for_mark
                        samplepos_t const p = (prev == region_boundary_cache.end()) ? region_boundary_cache.front () : *prev;
                        samplepos_t const n = (next == region_boundary_cache.end()) ? region_boundary_cache.back () : *next;
 
-                       if (start.sample > (p + n) / 2) {
-                               start.sample = n;
+                       if (presnap > (p + n) / 2) {
+                               test = n;
                        } else {
-                               start.sample = p;
+                               test = p;
                        }
                }
 
-               start.set (start.sample, 0);
-
-               break;
+               check_best_snap(presnap, test, dist, best);
        }
 
-       switch (_snap_mode) {
-       case SnapNormal:
-               return;
+       //check Grid
+       if (UIConfiguration::instance().get_snap_to_grid() && (_grid_type != GridTypeNone) ) {
 
-       case SnapMagnetic:
-
-               if (ensure_snap) {
-                       return;
-               }
-
-               if (presnap > start.sample) {
-                       if (presnap > (start.sample + snap_threshold_s)) {
-                               start.set (presnap, 0);
-                       }
+               //if SnapToGrid is selected, the user wants to prioritize the music grid
+               //in this case we should reset the best distance, so Grid will prevail
+               dist = max_samplepos;
 
-               } else if (presnap < start.sample) {
-                       if (presnap < (start.sample - snap_threshold_s)) {
-                               start.set (presnap, 0);
-                       }
-               }
+               test = snap_to_grid (grid_marks, presnap, direction);
+               check_best_snap(presnap, test, dist, best);
+       }
 
-       default:
-               /* handled at entry */
+       //now check "magnetic" state: is the grid within reasonable on-screen distance to trigger a snap?
+       //this also helps to avoid snapping to somewhere the user can't see.  ( i.e.:  I clicked on a region and it disappeared!! )
+       //ToDo:  perhaps this should only occur if EditPointMouse?
+       int snap_threshold_s = pixel_to_sample(UIConfiguration::instance().get_snap_threshold());
+       if (ensure_snap) {
+               start.set (best, 0);
                return;
+       } else if (presnap > best) {
+               if (presnap > (best+ snap_threshold_s)) {
+                       best = presnap;
+               }
+       } else if (presnap < best) {
+               if (presnap < (best - snap_threshold_s)) {
+                        best = presnap;
+               }
        }
+
+       start.set (best, 0);
 }
 
 
@@ -3010,8 +2906,8 @@ Editor::setup_toolbar ()
                mouse_mode_size_group->add_widget (visible_tracks_selector);
        }
 
-       mouse_mode_size_group->add_widget (snap_type_selector);
-       mouse_mode_size_group->add_widget (snap_mode_selector);
+       mouse_mode_size_group->add_widget (grid_type_selector);
+       mouse_mode_size_group->add_widget (snap_mode_button);
 
        mouse_mode_size_group->add_widget (edit_point_selector);
        mouse_mode_size_group->add_widget (edit_mode_selector);
@@ -3129,14 +3025,14 @@ Editor::setup_toolbar ()
        snap_box.set_spacing (2);
        snap_box.set_border_width (2);
 
-       snap_type_selector.set_name ("mouse mode button");
+       grid_type_selector.set_name ("mouse mode button");
 
-       snap_mode_selector.set_name ("mouse mode button");
+       snap_mode_button.set_name ("mouse mode button");
 
        edit_point_selector.set_name ("mouse mode button");
 
-       snap_box.pack_start (snap_mode_selector, false, false);
-       snap_box.pack_start (snap_type_selector, false, false);
+       snap_box.pack_start (snap_mode_button, false, false);
+       snap_box.pack_start (grid_type_selector, false, false);
 
        /* Edit Point*/
        HBox *ep_box = manage (new HBox);
@@ -3219,55 +3115,58 @@ Editor::build_edit_mode_menu ()
 }
 
 void
-Editor::build_snap_mode_menu ()
+Editor::build_grid_type_menu ()
 {
        using namespace Menu_Helpers;
 
-       snap_mode_selector.AddMenuElem (MenuElem ( snap_mode_strings[(int)SnapOff], sigc::bind (sigc::mem_fun(*this, &Editor::snap_mode_selection_done), (SnapMode) SnapOff)));
-       snap_mode_selector.AddMenuElem (MenuElem ( snap_mode_strings[(int)SnapNormal], sigc::bind (sigc::mem_fun(*this, &Editor::snap_mode_selection_done), (SnapMode) SnapNormal)));
-       snap_mode_selector.AddMenuElem (MenuElem ( snap_mode_strings[(int)SnapMagnetic], sigc::bind (sigc::mem_fun(*this, &Editor::snap_mode_selection_done), (SnapMode) SnapMagnetic)));
+       //main grid: bars, quarter-notes, etc
+       grid_type_selector.AddMenuElem (MenuElem ( grid_type_strings[(int)GridTypeNone],      sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeNone)));
+       grid_type_selector.AddMenuElem (MenuElem ( grid_type_strings[(int)GridTypeBar],       sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeBar)));
+       grid_type_selector.AddMenuElem (MenuElem ( grid_type_strings[(int)GridTypeBeat],      sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeBeat)));
+       grid_type_selector.AddMenuElem (MenuElem ( grid_type_strings[(int)GridTypeBeatDiv2],  sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeBeatDiv2)));
+       grid_type_selector.AddMenuElem (MenuElem ( grid_type_strings[(int)GridTypeBeatDiv4],  sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeBeatDiv4)));
+       grid_type_selector.AddMenuElem (MenuElem ( grid_type_strings[(int)GridTypeBeatDiv8],  sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeBeatDiv8)));
+       grid_type_selector.AddMenuElem (MenuElem ( grid_type_strings[(int)GridTypeBeatDiv16], sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeBeatDiv16)));
+       grid_type_selector.AddMenuElem (MenuElem ( grid_type_strings[(int)GridTypeBeatDiv32], sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeBeatDiv32)));
+
+       //triplet grid
+       grid_type_selector.AddMenuElem(SeparatorElem());
+       Gtk::Menu *_triplet_menu = manage (new Menu);
+       MenuList& triplet_items (_triplet_menu->items());
+       {
+               triplet_items.push_back( MenuElem( grid_type_strings[(int)GridTypeBeatDiv3],  sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeBeatDiv3) ));
+               triplet_items.push_back( MenuElem( grid_type_strings[(int)GridTypeBeatDiv6],  sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeBeatDiv6) ));
+               triplet_items.push_back( MenuElem( grid_type_strings[(int)GridTypeBeatDiv12], sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeBeatDiv12) ));
+               triplet_items.push_back( MenuElem( grid_type_strings[(int)GridTypeBeatDiv24], sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeBeatDiv24) ));
+       }
+       grid_type_selector.AddMenuElem (Menu_Helpers::MenuElem (_("Triplets"), *_triplet_menu));
 
-       set_size_request_to_display_given_text (snap_mode_selector, snap_mode_strings, COMBO_TRIANGLE_WIDTH, 2);
-}
+       //quintuplet grid
+       Gtk::Menu *_quintuplet_menu = manage (new Menu);
+       MenuList& quintuplet_items (_quintuplet_menu->items());
+       {
+               quintuplet_items.push_back( MenuElem( grid_type_strings[(int)GridTypeBeatDiv5],  sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeBeatDiv5) ));
+               quintuplet_items.push_back( MenuElem( grid_type_strings[(int)GridTypeBeatDiv10], sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeBeatDiv10) ));
+               quintuplet_items.push_back( MenuElem( grid_type_strings[(int)GridTypeBeatDiv20], sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeBeatDiv20) ));
+       }
+       grid_type_selector.AddMenuElem (Menu_Helpers::MenuElem (_("Quintuplets"), *_quintuplet_menu));
 
-void
-Editor::build_snap_type_menu ()
-{
-       using namespace Menu_Helpers;
+       //septuplet grid
+       Gtk::Menu *_septuplet_menu = manage (new Menu);
+       MenuList& septuplet_items (_septuplet_menu->items());
+       {
+               septuplet_items.push_back( MenuElem( grid_type_strings[(int)GridTypeBeatDiv7],  sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeBeatDiv7) ));
+               septuplet_items.push_back( MenuElem( grid_type_strings[(int)GridTypeBeatDiv14], sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeBeatDiv14) ));
+               septuplet_items.push_back( MenuElem( grid_type_strings[(int)GridTypeBeatDiv28], sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeBeatDiv28) ));
+       }
+       grid_type_selector.AddMenuElem (Menu_Helpers::MenuElem (_("Septuplets"), *_septuplet_menu));
 
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToCDFrame], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToCDFrame)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToTimecodeFrame], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToTimecodeFrame)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToTimecodeSeconds], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToTimecodeSeconds)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToTimecodeMinutes], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToTimecodeMinutes)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToSeconds], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToSeconds)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToMinutes], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToMinutes)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBeatDiv128], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBeatDiv128)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBeatDiv64], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBeatDiv64)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBeatDiv32], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBeatDiv32)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBeatDiv28], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBeatDiv28)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBeatDiv24], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBeatDiv24)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBeatDiv20], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBeatDiv20)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBeatDiv16], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBeatDiv16)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBeatDiv14], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBeatDiv14)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBeatDiv12], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBeatDiv12)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBeatDiv10], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBeatDiv10)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBeatDiv8], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBeatDiv8)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBeatDiv7], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBeatDiv7)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBeatDiv6], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBeatDiv6)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBeatDiv5], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBeatDiv5)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBeatDiv4], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBeatDiv4)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBeatDiv3], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBeatDiv3)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBeatDiv2], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBeatDiv2)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBeat], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBeat)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToBar], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToBar)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToMark], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToMark)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToRegionStart], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToRegionStart)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToRegionEnd], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToRegionEnd)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToRegionSync], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToRegionSync)));
-       snap_type_selector.AddMenuElem (MenuElem ( snap_type_strings[(int)SnapToRegionBoundary], sigc::bind (sigc::mem_fun(*this, &Editor::snap_type_selection_done), (SnapType) SnapToRegionBoundary)));
-
-       set_size_request_to_display_given_text (snap_type_selector, snap_type_strings, COMBO_TRIANGLE_WIDTH, 2);
+       grid_type_selector.AddMenuElem(SeparatorElem());
+       grid_type_selector.AddMenuElem (MenuElem ( grid_type_strings[(int)GridTypeSmpte], sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeSmpte)));
+       grid_type_selector.AddMenuElem (MenuElem ( grid_type_strings[(int)GridTypeMinSec], sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeMinSec)));
+       grid_type_selector.AddMenuElem (MenuElem ( grid_type_strings[(int)GridTypeSamples], sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeSamples)));
 
+       set_size_request_to_display_given_text (grid_type_selector, "No Grid", COMBO_TRIANGLE_WIDTH, 2);
 }
 
 void
@@ -3292,8 +3191,8 @@ Editor::setup_tooltips ()
        set_tooltip (tav_expand_button, _("Expand Tracks"));
        set_tooltip (tav_shrink_button, _("Shrink Tracks"));
        set_tooltip (visible_tracks_selector, _("Number of visible tracks"));
-       set_tooltip (snap_type_selector, _("Snap/Grid Units"));
-       set_tooltip (snap_mode_selector, _("Snap/Grid Mode"));
+       set_tooltip (grid_type_selector, _("Grid Mode"));
+       set_tooltip (snap_mode_button, _("Snap Mode\n\nRight-click to visit Snap preferences."));
        set_tooltip (edit_point_selector, _("Edit Point"));
        set_tooltip (edit_mode_selector, _("Edit Mode"));
        set_tooltip (nudge_clock, _("Nudge Clock\n(controls distance used to nudge regions and selections)"));
@@ -3644,7 +3543,7 @@ Editor::duplicate_range (bool with_dialog)
                times = adjustment.get_value();
        }
 
-       if ((current_mouse_mode() == Editing::MouseRange)) {
+       if ((current_mouse_mode() == MouseRange)) {
                if (selection->time.length()) {
                        duplicate_selection (times);
                }
@@ -3688,9 +3587,9 @@ Editor::edit_mode_selection_done ( EditMode m )
 }
 
 void
-Editor::snap_type_selection_done (SnapType snaptype)
+Editor::grid_type_selection_done (GridType gridtype)
 {
-       RefPtr<RadioAction> ract = snap_type_action (snaptype);
+       RefPtr<RadioAction> ract = grid_type_action (gridtype);
        if (ract) {
                ract->set_active ();
        }
@@ -3949,22 +3848,18 @@ Editor::cycle_zoom_focus ()
 }
 
 void
-Editor::set_show_measures (bool yn)
+Editor::update_grid ()
 {
-       if (_show_measures != yn) {
-               hide_measures ();
-
-               if ((_show_measures = yn) == true) {
-                       if (tempo_lines) {
-                               tempo_lines->show();
-                       }
-
-                       std::vector<TempoMap::BBTPoint> grid;
+       if ( grid_musical() ) {
+               std::vector<TempoMap::BBTPoint> grid;
+               if (bbt_ruler_scale != bbt_show_many) {
                        compute_current_bbt_points (grid, _leftmost_sample, _leftmost_sample + current_page_samples());
-                       draw_measures (grid);
                }
-
-               instant_save ();
+               maybe_draw_grid_lines ();
+       } else if ( grid_nonmusical() ) {
+               maybe_draw_grid_lines ();
+       } else {
+               hide_grid_lines ();
        }
 }
 
@@ -4044,25 +3939,28 @@ Editor::get_paste_offset (samplepos_t pos, unsigned paste_count, samplecnt_t dur
 unsigned
 Editor::get_grid_beat_divisions(samplepos_t position)
 {
-       switch (_snap_type) {
-       case SnapToBeatDiv128: return 128;
-       case SnapToBeatDiv64:  return 64;
-       case SnapToBeatDiv32:  return 32;
-       case SnapToBeatDiv28:  return 28;
-       case SnapToBeatDiv24:  return 24;
-       case SnapToBeatDiv20:  return 20;
-       case SnapToBeatDiv16:  return 16;
-       case SnapToBeatDiv14:  return 14;
-       case SnapToBeatDiv12:  return 12;
-       case SnapToBeatDiv10:  return 10;
-       case SnapToBeatDiv8:   return 8;
-       case SnapToBeatDiv7:   return 7;
-       case SnapToBeatDiv6:   return 6;
-       case SnapToBeatDiv5:   return 5;
-       case SnapToBeatDiv4:   return 4;
-       case SnapToBeatDiv3:   return 3;
-       case SnapToBeatDiv2:   return 2;
-       default:               return 0;
+       switch (_grid_type) {
+       case GridTypeBeatDiv32:  return 32;
+       case GridTypeBeatDiv28:  return 28;
+       case GridTypeBeatDiv24:  return 24;
+       case GridTypeBeatDiv20:  return 20;
+       case GridTypeBeatDiv16:  return 16;
+       case GridTypeBeatDiv14:  return 14;
+       case GridTypeBeatDiv12:  return 12;
+       case GridTypeBeatDiv10:  return 10;
+       case GridTypeBeatDiv8:   return 8;
+       case GridTypeBeatDiv7:   return 7;
+       case GridTypeBeatDiv6:   return 6;
+       case GridTypeBeatDiv5:   return 5;
+       case GridTypeBeatDiv4:   return 4;
+       case GridTypeBeatDiv3:   return 3;
+       case GridTypeBeatDiv2:   return 2;
+
+       case GridTypeNone:       return 0;
+       case GridTypeSmpte:      return 0;
+       case GridTypeMinSec:     return 0;
+       case GridTypeSamples:    return 0;
+       default:                 return 0;
        }
        return 0;
 }
@@ -4075,35 +3973,37 @@ Editor::get_grid_beat_divisions(samplepos_t position)
 int32_t
 Editor::get_grid_music_divisions (uint32_t event_state)
 {
-       if (snap_mode() == Editing::SnapOff && !ArdourKeyboard::indicates_snap (event_state)) {
+       if (snap_mode() == SnapOff && !ArdourKeyboard::indicates_snap (event_state)) {
                return 0;
        }
 
-       if (snap_mode() != Editing::SnapOff && ArdourKeyboard::indicates_snap (event_state)) {
+       if (snap_mode() != SnapOff && ArdourKeyboard::indicates_snap (event_state)) {
                return 0;
        }
 
-       switch (_snap_type) {
-       case SnapToBeatDiv128: return 128;
-       case SnapToBeatDiv64:  return 64;
-       case SnapToBeatDiv32:  return 32;
-       case SnapToBeatDiv28:  return 28;
-       case SnapToBeatDiv24:  return 24;
-       case SnapToBeatDiv20:  return 20;
-       case SnapToBeatDiv16:  return 16;
-       case SnapToBeatDiv14:  return 14;
-       case SnapToBeatDiv12:  return 12;
-       case SnapToBeatDiv10:  return 10;
-       case SnapToBeatDiv8:   return 8;
-       case SnapToBeatDiv7:   return 7;
-       case SnapToBeatDiv6:   return 6;
-       case SnapToBeatDiv5:   return 5;
-       case SnapToBeatDiv4:   return 4;
-       case SnapToBeatDiv3:   return 3;
-       case SnapToBeatDiv2:   return 2;
-       case SnapToBeat:       return 1;
-       case SnapToBar :       return -1;
-       default:               return 0;
+       switch (_grid_type) {
+       case GridTypeBeatDiv32:  return 32;
+       case GridTypeBeatDiv28:  return 28;
+       case GridTypeBeatDiv24:  return 24;
+       case GridTypeBeatDiv20:  return 20;
+       case GridTypeBeatDiv16:  return 16;
+       case GridTypeBeatDiv14:  return 14;
+       case GridTypeBeatDiv12:  return 12;
+       case GridTypeBeatDiv10:  return 10;
+       case GridTypeBeatDiv8:   return 8;
+       case GridTypeBeatDiv7:   return 7;
+       case GridTypeBeatDiv6:   return 6;
+       case GridTypeBeatDiv5:   return 5;
+       case GridTypeBeatDiv4:   return 4;
+       case GridTypeBeatDiv3:   return 3;
+       case GridTypeBeatDiv2:   return 2;
+       case GridTypeBeat:       return 1;
+       case GridTypeBar :       return -1;
+
+       case GridTypeNone:       return 0;
+       case GridTypeSmpte:      return 0;
+       case GridTypeMinSec:     return 0;
+       case GridTypeSamples:    return 0;
        }
        return 0;
 }
@@ -4118,10 +4018,10 @@ Editor::get_grid_type_as_beats (bool& success, samplepos_t position)
                return Temporal::Beats(1.0 / (double)get_grid_beat_divisions(position));
        }
 
-       switch (_snap_type) {
-       case SnapToBeat:
+       switch (_grid_type) {
+       case GridTypeBeat:
                return Temporal::Beats(4.0 / _session->tempo_map().meter_at_sample (position).note_divisor());
-       case SnapToBar:
+       case GridTypeBar:
                if (_session) {
                        const Meter& m = _session->tempo_map().meter_at_sample (position);
                        return Temporal::Beats((4.0 * m.divisions_per_bar()) / m.note_divisor());
@@ -4534,10 +4434,6 @@ Editor::set_samples_per_pixel (samplecnt_t spp)
 void
 Editor::on_samples_per_pixel_changed ()
 {
-       if (tempo_lines) {
-               tempo_lines->tempo_map_changed(_session->tempo_map().music_origin());
-       }
-
        bool const showing_time_selection = selection->time.length() > 0;
 
        if (showing_time_selection && selection->time.start () != selection->time.end_sample ()) {
@@ -4691,7 +4587,7 @@ Editor::visual_changer (const VisualChange& vc)
        // If we are only scrolling vertically there is no need to update these
        if (vc.pending != VisualChange::YOrigin) {
                update_fixed_rulers ();
-               redisplay_tempo (true);
+               redisplay_grid (true);
 
                /* video frames & position need to be updated for zoom, horiz-scroll
                 * and (explicitly) VisualChange::VideoTimeline.
@@ -5278,12 +5174,16 @@ Editor::region_view_added (RegionView * rv)
        }
 
        _summary->set_background_dirty ();
+
+       mark_region_boundary_cache_dirty ();
 }
 
 void
 Editor::region_view_removed ()
 {
        _summary->set_background_dirty ();
+
+       mark_region_boundary_cache_dirty ();
 }
 
 AxisView*
@@ -5853,7 +5753,7 @@ Editor::super_rapid_screen_update ()
        } else {
                _last_update_time = now;
        }
-       
+
        //snapped cursor stuff ( the snapped_cursor shows where an operation is going to occur )
        bool ignored;
        MusicSample where (sample, 0);
@@ -5875,7 +5775,7 @@ Editor::super_rapid_screen_update ()
        } else { //mouse is out of the editing canvas.  hide the snapped_cursor
                snapped_cursor->hide ();
        }
-       
+
        /* There are a few reasons why we might not update the playhead / viewport stuff:
         *
         * 1.  we don't update things when there's a pending locate request, otherwise
@@ -5989,11 +5889,11 @@ Editor::session_going_away ()
 
        /* clear tempo/meter rulers */
        remove_metric_marks ();
-       hide_measures ();
        clear_marker_display ();
 
-       delete tempo_lines;
-       tempo_lines = 0;
+       hide_grid_lines ();
+       delete grid_lines;
+       grid_lines = 0;
 
        stop_step_editing ();