/*
 * Copyright (C) 2023 Paul Davis <paul@linuxaudiosystems.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <iostream>

#include "pbd/error.h"
#include "pbd/stacktrace.h"
#include "pbd/unwind.h"

#include "ardour/legatize.h"
#include "ardour/midi_region.h"
#include "ardour/midi_source.h"
#include "ardour/midi_track.h"
#include "ardour/rc_configuration.h"
#include "ardour/transpose.h"
#include "ardour/quantize.h"
#include "ardour/strum.h"

#include "gtkmm2ext/bindings.h"

#include "widgets/tooltips.h"

#include "actions.h"
#include "ardour_ui.h"
#include "automation_line.h"
#include "control_point.h"
#include "edit_note_dialog.h"
#include "editing_context.h"
#include "editing_convert.h"
#include "editor_cursors.h"
#include "editor_drag.h"
#include "grid_lines.h"
#include "gui_thread.h"
#include "keyboard.h"
#include "midi_region_view.h"
#include "note_base.h"
#include "pianoroll_window.h"
#include "quantize_dialog.h"
#include "rc_option_editor.h"
#include "selection.h"
#include "selection_memento.h"
#include "transform_dialog.h"
#include "transpose_dialog.h"
#include "utils.h"
#include "verbose_cursor.h"

#include "pbd/i18n.h"

using namespace ARDOUR;
using namespace Editing;
using namespace Glib;
using namespace Gtk;
using namespace Gtkmm2ext;
using namespace PBD;
using namespace Temporal;
using namespace ArdourWidgets;
using std::string;

sigc::signal<void> EditingContext::DropDownKeys;
Gtkmm2ext::Bindings* EditingContext::button_bindings = nullptr;
std::vector<std::string> EditingContext::grid_type_strings;
std::vector<std::string> EditingContext::grid_type_short_labels;
MouseCursors* EditingContext::_cursors = nullptr;
bool EditingContext::need_shared_actions = true;

static const gchar *_grid_type_strings[] = {
	N_("No Grid"),
	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_("Timecode"),
	N_("MinSec"),
	N_("CD Frames"),
	0
};

static const gchar *_grid_type_labels[] = {
	N_("No Grid"),
	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 Note"),
	N_("1/6 Note"),
	N_("1/12 Note"),
	N_("1/24 Note"),
	N_("1/5 Note"),
	N_("1/10 Note"),
	N_("1/20 Note"),
	N_("1/7 Note"),
	N_("1/14 Note"),
	N_("1/28 Note"),
	N_("Timecode"),
	N_("MinSec"),
	N_("CD Frames"),
	0
};


static const gchar *_zoom_focus_strings[] = {
	N_("Left"),
	N_("Right"),
	N_("Center"),
	N_("Playhead"),
	N_("Mouse"),
	N_("Edit point"),
	0
};

/* These are used in comparisons with text loaded from a file, and are thus not
 * yet translatable.
 */

std::vector<std::string> EditingContext::_chord_name_list ({
		X_("maj"), X_("min"), X_("sus4"), X_("sus2"), X_("dim"), X_("aug"),
		X_("maj7"), X_("7"), X_("min7"), X_("min6"), X_("min7b5"), X_("dim7"), X_("sus2/7"), X_("sus4/7"), X_("maj7#5"),
		X_("dom9"), X_("9"), X_("min9"), X_("add9"), X_("min/9"), X_("maj6"), X_("min6"), X_("maj6/9"), X_("sus2/7"), X_("sus4/7"), X_("gtr5maj"), X_("gtr5min"),
	});

PBD::Signal<void()> EditingContext::ChordsChanged;

EditingContext::EditingContext (std::string const & name)
	: rubberband_rect (0)
	, old_mouse_mode (Editing::MouseObject)
	, _name (name)
	, within_track_canvas (false)
	, pre_internal_grid_type (GridTypeBeat)
	, pre_internal_snap_mode (SnapOff)
	, internal_grid_type (GridTypeBeat)
	, internal_snap_mode (SnapOff)
	, _timeline_origin (0.)
	, play_note_selection_button (ArdourButton::default_elements)
	, follow_playhead_button (_("Follow Playhead"), ArdourButton::Element (ArdourButton::Edge | ArdourButton::Body | ArdourButton::VectorIcon), true)
	, follow_edits_button (_("Follow Range"), ArdourButton::Element (ArdourButton::Edge | ArdourButton::Body | ArdourButton::VectorIcon), true)
	, visible_channel_label (S_("MIDI|Ch:"))
	, _drags (new DragManager (this))
	, _leftmost_sample (0)
	, _playhead_cursor (nullptr)
	, _snapped_cursor (nullptr)
	, selection (new Selection (this, true))
	, cut_buffer (new Selection (this, false))
	, _selection_memento (new SelectionMemento())
	, samples_per_pixel (2048)
	, bbt_ruler_scale (bbt_show_many)
	, bbt_bars (0)
	, bbt_bar_helper_on (0)
	, _track_canvas_width (0)
	, _visible_canvas_width (0)
	, _visible_canvas_height (0)
	, quantize_dialog (nullptr)
	, vertical_adjustment (0.0, 0.0, 10.0, 400.0)
	, horizontal_adjustment (0.0, 0.0, 1e16)
	, own_bindings (nullptr)
	, visual_change_queued (false)
	, autoscroll_horizontal_allowed (false)
	, autoscroll_vertical_allowed (false)
	, autoscroll_cnt (0)
	, _mouse_changed_selection (false)
	, entered_marker (nullptr)
	, entered_track (nullptr)
	, entered_regionview (nullptr)
	, clear_entered_track (false)
	, grid_lines (nullptr)
	, time_line_group (nullptr)
	, minsec_mark_interval (0)
	, minsec_mark_modulo (0)
	, minsec_nmarks (0)
	, temporary_zoom_focus_change (false)
	, _dragging_playhead (false)
	, pianoroll_window (nullptr)
{
	using namespace Gtk::Menu_Helpers;

	if (!button_bindings) {
		button_bindings = new Bindings ("editor-mouse");

		XMLNode* node = button_settings();
		if (node) {
			for (XMLNodeList::const_iterator i = node->children().begin(); i != node->children().end(); ++i) {
				button_bindings->load_operation (**i);
			}
		}
	}

	grid_box.set_no_show_all ();

	if (grid_type_strings.empty()) {
		grid_type_strings =  I18N (_grid_type_strings);
	}

	if (grid_type_short_labels.empty()) {
		grid_type_short_labels =  I18N (_grid_type_labels);
	}

	if (zoom_focus_strings.empty()) {
		zoom_focus_strings = I18N (_zoom_focus_strings);
	}

	zoom_focus_selector.set_name ("zoom button");

	snap_mode_button.set_text (_("Snap"));
	snap_mode_button.set_name ("mouse mode button");
	snap_mode_button.signal_button_press_event().connect (sigc::mem_fun (*this, &EditingContext::snap_mode_button_clicked), false);

	if (!_cursors) {
		_cursors = new MouseCursors;
		_cursors->set_cursor_set (UIConfiguration::instance().get_icon_set());
		std::cerr << "Set cursor set to " << UIConfiguration::instance().get_icon_set() << std::endl;
	}

	set_tooltip (draw_length_selector, _("Note Length to Draw (AUTO uses the current Grid setting)"));
	set_tooltip (draw_velocity_selector, _("Note Velocity to Draw (AUTO uses the nearest note's velocity)"));
	set_tooltip (draw_channel_selector, _("Note Channel to Draw (AUTO uses the nearest note's channel)"));
	set_tooltip (grid_type_selector, _("Grid Mode"));
	set_tooltip (snap_mode_button, _("Snap Mode\n\nRight-click to visit Snap preferences."));
	set_tooltip (zoom_focus_selector, _("Zoom Focus"));

	set_tooltip (play_note_selection_button, _("Play notes when selected"));
	set_tooltip (note_mode_button, _("Switch between sustained and percussive mode"));
	set_tooltip (follow_edits_button, _("Playhead follows Range tool clicks, and Range selections"));
	/* Leave tip for full zoom button to derived class */
	set_tooltip (visible_channel_selector, _("Select visible MIDI channel"));

	play_note_selection_button.signal_clicked.connect (sigc::mem_fun (*this, &EditingContext::play_note_selection_clicked));
	note_mode_button.signal_clicked.connect (sigc::mem_fun (*this, &EditingContext::note_mode_clicked));
	full_zoom_button.signal_clicked.connect (sigc::mem_fun (*this, &EditingContext::full_zoom_clicked));

	follow_playhead_button.set_icon (ArdourIcon::EditorFollowPlayhead);
	follow_edits_button.set_icon (ArdourIcon::EditorFollowEdits);

	zoom_in_button.set_name ("zoom button");
	zoom_in_button.set_icon (ArdourIcon::ZoomIn);

	zoom_out_button.set_name ("zoom button");
	zoom_out_button.set_icon (ArdourIcon::ZoomOut);

	full_zoom_button.set_name ("zoom button");
	full_zoom_button.set_icon (ArdourIcon::ZoomFull);

	follow_playhead_button.set_name ("transport option button");
	follow_edits_button.set_name ("transport option button");

	note_mode_button.set_icon (ArdourIcon::Drum);
#define PX_SCALE(px) std::max((float)px, rintf((float)px * UIConfiguration::instance().get_ui_scale()))
	note_mode_button.set_size_request (PX_SCALE(50), -1);
	note_mode_button.set_active_color (UIConfiguration::instance().color ("alert:yellow"));

	selection->PointsChanged.connect (sigc::mem_fun(*this, &EditingContext::point_selection_changed));
	selection->RegionsChanged.connect (sigc::mem_fun(*this, &EditingContext::region_selection_changed));

	for (int i = 0; i < 16; i++) {
		char buf[4];
		sprintf(buf, "%d", i+1);
		visible_channel_selector.add_menu_elem (MenuElem (buf, [this,i]() { set_visible_channel (i); }));
	}

	/* handle escape */

	ARDOUR_UI::instance()->Escape.connect (escape_connection, MISSING_INVALIDATOR, std::bind (&EditingContext::escape, this), gui_context());

	Config->ParameterChanged.connect (parameter_connections, MISSING_INVALIDATOR, std::bind (&EditingContext::parameter_changed, this, _1), gui_context());
	UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &EditingContext::ui_parameter_changed));

	std::function<void (string)> pc (std::bind (&EditingContext::ui_parameter_changed, this, _1));
	UIConfiguration::instance().map_parameters (pc);
}

EditingContext::~EditingContext()
{
	if (pending_visual_change.idle_handler_id >= 0) {
		g_source_remove (pending_visual_change.idle_handler_id);
	}
	if (_midi_actions) {
		ActionManager::drop_action_group (_midi_actions);
	}
	if (_common_actions) {
		ActionManager::drop_action_group (_common_actions);
	}
	if (editor_actions) {
		ActionManager::drop_action_group (editor_actions);
	}
	if (snap_actions) {
		ActionManager::drop_action_group (snap_actions);
	}
	if (length_actions) {
		ActionManager::drop_action_group (length_actions);
	}
	if (channel_actions) {
		ActionManager::drop_action_group (channel_actions);
	}
	if (velocity_actions) {
		ActionManager::drop_action_group (velocity_actions);
	}
	if (zoom_actions) {
		ActionManager::drop_action_group (zoom_actions);
	}
	if (_automation_actions) {
		ActionManager::drop_action_group (_automation_actions);
	}
	if (chord_actions) {
		ActionManager::drop_action_group (chord_actions);
	}

	delete pianoroll_window;
}

void
EditingContext::ui_parameter_changed (string parameter)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (parameter == "sound-midi-notes") {
		if (UIConfiguration::instance().get_sound_midi_notes()) {
			play_note_selection_button.set_active_state (Gtkmm2ext::ExplicitActive);
		} else {
			play_note_selection_button.set_active_state (Gtkmm2ext::Off);
		}
	}
}


void
EditingContext::parameter_changed (string parameter)
{
	EC_LOCAL_TEMPO_SCOPE;

}

void
EditingContext::set_session (ARDOUR::Session* s)
{
	EC_LOCAL_TEMPO_SCOPE;

	SessionHandlePtr::set_session (s);
	disable_automation_bindings ();
	delete pianoroll_window;
	pianoroll_window = nullptr;
}

void
EditingContext::set_selected_midi_region_view (MidiRegionView& mrv)
{
	EC_LOCAL_TEMPO_SCOPE;

	/* clear note selection in all currently selected MidiRegionViews */

	if (get_selection().regions.contains (&mrv) && get_selection().regions.size() == 1) {
		/* Nothing to do */
		return;
	}

	midi_action (&MidiRegionView::clear_note_selection);
	get_selection().set (&mrv);
}

void
EditingContext::register_automation_actions (Bindings* automation_bindings, std::string const & prefix)
{
	EC_LOCAL_TEMPO_SCOPE;

	_automation_actions = ActionManager::create_action_group (automation_bindings, prefix + X_("Automation"));

	reg_sens (_automation_actions, "create-point", _("Create Automation Point"), std::bind (sigc::mem_fun (*this, &EditingContext::automation_create_point_at_edit_point), false));
	reg_sens (_automation_actions, "create-point-with-guards", _("Create Automation Point (with Guard Points)"), std::bind (sigc::mem_fun (*this, &EditingContext::automation_create_point_at_edit_point), true));
	reg_sens (_automation_actions, "move-points-later", _("Move Selected Automation Points Later"), sigc::mem_fun (*this, &EditingContext::automation_move_points_later));
	reg_sens (_automation_actions, "move-points-earlier", _("Move Selected Automation Points Earlier"), sigc::mem_fun (*this, &EditingContext::automation_move_points_earlier));
	reg_sens (_automation_actions, "raise-points", _("Increase Value of Selected Automation Points"), sigc::mem_fun (*this, &EditingContext::automation_raise_points));
	reg_sens (_automation_actions, "lower-points", _("Decrease Value of Selected Automation Points"), sigc::mem_fun (*this, &EditingContext::automation_lower_points));
	reg_sens (_automation_actions, "begin-edit", _("Open value entry window for automation editing"), sigc::mem_fun (*this, &EditingContext::automation_begin_edit));
	reg_sens (_automation_actions, "end-edit", _("Close value entry window for automation editing"), sigc::mem_fun (*this, &EditingContext::automation_end_edit));

	disable_automation_bindings ();
}

void
EditingContext::enable_automation_bindings ()
{
	EC_LOCAL_TEMPO_SCOPE;

	if (_automation_actions) {
		ActionManager::set_sensitive (_automation_actions, true);
	}
}

void
EditingContext::disable_automation_bindings ()
{
	EC_LOCAL_TEMPO_SCOPE;

	if (_automation_actions) {
		ActionManager::set_sensitive (_automation_actions, false);
	}
}

void
EditingContext::set_action_defaults ()
{
	EC_LOCAL_TEMPO_SCOPE;

#ifndef LIVETRAX
	follow_playhead_action->set_active (false);
	follow_playhead_action->set_active (true);
#else
	follow_playhead_action->set_active (true);
	follow_playhead_action->set_active (false);
#endif

	stationary_playhead_action->set_active (true);
	stationary_playhead_action->set_active (false);

	mouse_mode_actions[Editing::MouseObject]->set_active (false);
	mouse_mode_actions[Editing::MouseObject]->set_active (true);
	zoom_focus_actions[Editing::ZoomFocusLeft]->set_active (false);
	zoom_focus_actions[Editing::ZoomFocusLeft]->set_active (true);

	if (snap_mode_actions[Editing::SnapMagnetic]) {
		snap_mode_actions[Editing::SnapMagnetic]->set_active (false);
		snap_mode_actions[Editing::SnapMagnetic]->set_active (true);
	}
	if (grid_actions[Editing::GridTypeBeat]) {
		grid_actions[Editing::GridTypeBeat]->set_active (false);
		grid_actions[Editing::GridTypeBeat]->set_active (true);
	}
	if (draw_length_actions.find (DRAW_LEN_AUTO) != draw_length_actions.end()) {
		draw_length_actions[DRAW_LEN_AUTO]->set_active (false);
		draw_length_actions[DRAW_LEN_AUTO]->set_active (true);
	}
	if (draw_velocity_actions.find (DRAW_VEL_AUTO) != draw_velocity_actions.end()) {
		draw_velocity_actions[DRAW_VEL_AUTO]->set_active (false);
		draw_velocity_actions[DRAW_VEL_AUTO]->set_active (true);
	}
	if (draw_channel_actions.find (DRAW_CHAN_AUTO) != draw_channel_actions.end()) {
		draw_channel_actions[DRAW_CHAN_AUTO]->set_active (false);
		draw_channel_actions[DRAW_CHAN_AUTO]->set_active (true);
	}
	/* default draw chord action is first triad */
	if (_no_chord_action) {
		_no_chord_action->set_active (false);
		_no_chord_action->set_active (true);
	}
}

void
EditingContext::register_common_actions (Bindings* common_bindings, std::string const & prefix)
{
	EC_LOCAL_TEMPO_SCOPE;

	_common_actions = ActionManager::create_action_group (common_bindings, prefix + X_("Editing"));

	reg_sens (_common_actions, "temporal-zoom-out", _("Zoom Out"), sigc::bind (sigc::mem_fun (*this, &EditingContext::temporal_zoom_step), true));
	reg_sens (_common_actions, "temporal-zoom-in", _("Zoom In"), sigc::bind (sigc::mem_fun (*this, &EditingContext::temporal_zoom_step), false));

	follow_playhead_action = toggle_reg_sens (_common_actions, "toggle-follow-playhead", _("Follow Playhead"), sigc::mem_fun (*this, &EditingContext::follow_playhead_chosen));
	stationary_playhead_action = toggle_reg_sens (_common_actions, "toggle-stationary-playhead", _("Stationary Playhead"), (mem_fun(*this, &EditingContext::stationary_playhead_chosen)));

	follow_playhead_action->set_tooltip (_("Scroll automatically to keep playhead visible"));

	undo_action = reg_sens (_common_actions, "undo", S_("Command|Undo"), sigc::bind (sigc::mem_fun (*this, &EditingContext::undo), 1U));
	redo_action = reg_sens (_common_actions, "redo", _("Redo"), sigc::bind (sigc::mem_fun (*this, &EditingContext::redo), 1U));
	alternate_redo_action = reg_sens (_common_actions, "alternate-redo", _("Redo"), sigc::bind (sigc::mem_fun (*this, &EditingContext::redo), 1U));
	alternate_alternate_redo_action = reg_sens (_common_actions, "alternate-alternate-redo", _("Redo"), sigc::bind (sigc::mem_fun (*this, &EditingContext::redo), 1U));

	reg_sens (_common_actions, "editor-delete", _("Delete"), sigc::mem_fun (*this, &EditingContext::delete_));
	reg_sens (_common_actions, "alternate-editor-delete", _("Delete"), sigc::mem_fun (*this, &EditingContext::delete_));

	reg_sens (_common_actions, "editor-cut", _("Cut"), sigc::mem_fun (*this, &EditingContext::cut));
	reg_sens (_common_actions, "editor-copy", _("Copy"), sigc::mem_fun (*this, &EditingContext::copy));
	reg_sens (_common_actions, "editor-paste", _("Paste"), sigc::mem_fun (*this, &EditingContext::keyboard_paste));

	RadioAction::Group note_mode_group;
	note_mode_actions[ARDOUR::Sustained] = ActionManager::register_radio_action (_common_actions, note_mode_group, "set-note-mode-sustained", _("Sustained"), sigc::bind (sigc::mem_fun (*this, &EditingContext::note_mode_chosen), ARDOUR::Sustained));
	note_mode_actions[ARDOUR::Percussive] = ActionManager::register_radio_action (_common_actions, note_mode_group, "set-note-mode-percussivee", _("Percussive"), sigc::bind (sigc::mem_fun (*this, &EditingContext::note_mode_chosen), ARDOUR::Percussive));

	RadioAction::Group mouse_mode_group;

	mouse_mode_actions[Editing::MouseObject] = ActionManager::register_radio_action (_common_actions, mouse_mode_group, "set-mouse-mode-object", _("Grab (Object Tool)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::mouse_mode_chosen), Editing::MouseObject));
	mouse_mode_actions[Editing::MouseRange] = ActionManager::register_radio_action (_common_actions, mouse_mode_group, "set-mouse-mode-range", _("Range Tool"), sigc::bind (sigc::mem_fun (*this, &EditingContext::mouse_mode_chosen), Editing::MouseRange));
	mouse_mode_actions[Editing::MouseDraw] = ActionManager::register_radio_action (_common_actions, mouse_mode_group, "set-mouse-mode-draw", _("Note Drawing Tool"), sigc::bind (sigc::mem_fun (*this, &EditingContext::mouse_mode_chosen), Editing::MouseDraw));
	mouse_mode_actions[Editing::MouseTimeFX] = ActionManager::register_radio_action (_common_actions, mouse_mode_group, "set-mouse-mode-timefx", _("Time FX Tool"), sigc::bind (sigc::mem_fun (*this, &EditingContext::mouse_mode_chosen), Editing::MouseTimeFX));
	mouse_mode_actions[Editing::MouseGrid] = ActionManager::register_radio_action (_common_actions, mouse_mode_group, "set-mouse-mode-grid", _("Grid Tool"), sigc::bind (sigc::mem_fun (*this, &EditingContext::mouse_mode_chosen), Editing::MouseGrid));
	mouse_mode_actions[Editing::MouseContent] = ActionManager::register_radio_action (_common_actions, mouse_mode_group, "set-mouse-mode-content", _("Internal Edit (Content Tool)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::mouse_mode_chosen), Editing::MouseContent));
	mouse_mode_actions[Editing::MouseCut] = ActionManager::register_radio_action (_common_actions, mouse_mode_group, "set-mouse-mode-cut", _("Cut Tool"), sigc::bind (sigc::mem_fun (*this, &EditingContext::mouse_mode_chosen), Editing::MouseCut));

	zoom_actions = ActionManager::create_action_group (common_bindings, prefix + X_("Zoom"));
	RadioAction::Group zoom_group;

	zoom_focus_actions[Editing::ZoomFocusLeft] = radio_reg_sens (zoom_actions, zoom_group, "zoom-focus-left", zoom_focus_strings[Editing::ZoomFocusLeft].c_str(), sigc::bind (sigc::mem_fun (*this, &EditingContext::zoom_focus_chosen), Editing::ZoomFocusLeft));
	zoom_focus_actions[Editing::ZoomFocusRight] = radio_reg_sens (zoom_actions, zoom_group, "zoom-focus-right", zoom_focus_strings[Editing::ZoomFocusRight].c_str(), sigc::bind (sigc::mem_fun (*this, &EditingContext::zoom_focus_chosen), Editing::ZoomFocusRight));
	zoom_focus_actions[Editing::ZoomFocusCenter] = radio_reg_sens (zoom_actions, zoom_group, "zoom-focus-center", zoom_focus_strings[Editing::ZoomFocusCenter].c_str(), sigc::bind (sigc::mem_fun (*this, &EditingContext::zoom_focus_chosen), Editing::ZoomFocusCenter));
	zoom_focus_actions[Editing::ZoomFocusPlayhead] = radio_reg_sens (zoom_actions, zoom_group, "zoom-focus-playhead", zoom_focus_strings[Editing::ZoomFocusPlayhead].c_str(), sigc::bind (sigc::mem_fun (*this, &EditingContext::zoom_focus_chosen), Editing::ZoomFocusPlayhead));
	zoom_focus_actions[Editing::ZoomFocusMouse] = radio_reg_sens (zoom_actions, zoom_group, "zoom-focus-mouse", zoom_focus_strings[Editing::ZoomFocusMouse].c_str(), sigc::bind (sigc::mem_fun (*this, &EditingContext::zoom_focus_chosen), Editing::ZoomFocusMouse));
	zoom_focus_actions[Editing::ZoomFocusEdit] = radio_reg_sens (zoom_actions, zoom_group, "zoom-focus-edit", zoom_focus_strings[Editing::ZoomFocusEdit].c_str(), sigc::bind (sigc::mem_fun (*this, &EditingContext::zoom_focus_chosen), Editing::ZoomFocusEdit));

	ActionManager::register_action (zoom_actions, X_("cycle-zoom-focus"), _("Next Zoom Focus"), sigc::mem_fun (*this, &EditingContext::cycle_zoom_focus));

	/* Grid stuff */

	ActionManager::register_action (_common_actions, X_("GridChoice"), _("Snap & Grid"));

	RadioAction::Group snap_mode_group;
	snap_mode_actions[Editing::SnapOff] = ActionManager::register_radio_action (_common_actions, snap_mode_group, X_("snap-off"), _("No Grid"), (sigc::bind (sigc::mem_fun(*this, &EditingContext::snap_mode_chosen), Editing::SnapOff)));
	snap_mode_actions[Editing::SnapNormal] =  ActionManager::register_radio_action (_common_actions, snap_mode_group, X_("snap-normal"), _("Grid"), (sigc::bind (sigc::mem_fun(*this, &EditingContext::snap_mode_chosen), Editing::SnapNormal)));  //deprecated
	snap_mode_actions[Editing::SnapMagnetic] = ActionManager::register_radio_action (_common_actions, snap_mode_group, X_("snap-magnetic"), _("Magnetic"), (sigc::bind (sigc::mem_fun(*this, &EditingContext::snap_mode_chosen), Editing::SnapMagnetic)));

	ActionManager::register_action (_common_actions, X_("cycle-snap-mode"), _("Toggle Snap"), sigc::mem_fun (*this, &EditingContext::cycle_snap_mode));
	ActionManager::register_action (_common_actions, X_("next-grid-choice"), _("Next Quantize Grid Choice"), sigc::mem_fun (*this, &EditingContext::next_grid_choice));
	ActionManager::register_action (_common_actions, X_("prev-grid-choice"), _("Previous Quantize Grid Choice"), sigc::mem_fun (*this, &EditingContext::prev_grid_choice));

	snap_actions = ActionManager::create_action_group (common_bindings, prefix + X_("Snap"));
	RadioAction::Group grid_choice_group;

	grid_actions[Editing::GridTypeBeatDiv32] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-thirtyseconds"),  grid_type_strings[(int)GridTypeBeatDiv32].c_str(), (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeBeatDiv32)));
	grid_actions[Editing::GridTypeBeatDiv28] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-twentyeighths"),  grid_type_strings[(int)GridTypeBeatDiv28].c_str(), (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeBeatDiv28)));
	grid_actions[Editing::GridTypeBeatDiv24] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-twentyfourths"),  grid_type_strings[(int)GridTypeBeatDiv24].c_str(), (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeBeatDiv24)));
	grid_actions[Editing::GridTypeBeatDiv20] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-twentieths"),     grid_type_strings[(int)GridTypeBeatDiv20].c_str(), (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeBeatDiv20)));
	grid_actions[Editing::GridTypeBeatDiv16] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-asixteenthbeat"), grid_type_strings[(int)GridTypeBeatDiv16].c_str(), (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeBeatDiv16)));
	grid_actions[Editing::GridTypeBeatDiv14] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-fourteenths"),    grid_type_strings[(int)GridTypeBeatDiv14].c_str(), (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeBeatDiv14)));
	grid_actions[Editing::GridTypeBeatDiv12] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-twelfths"),       grid_type_strings[(int)GridTypeBeatDiv12].c_str(), (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeBeatDiv12)));
	grid_actions[Editing::GridTypeBeatDiv10] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-tenths"),         grid_type_strings[(int)GridTypeBeatDiv10].c_str(), (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeBeatDiv10)));
	grid_actions[Editing::GridTypeBeatDiv8] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-eighths"),        grid_type_strings[(int)GridTypeBeatDiv8].c_str(),  (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeBeatDiv8)));
	grid_actions[Editing::GridTypeBeatDiv7] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-sevenths"),       grid_type_strings[(int)GridTypeBeatDiv7].c_str(),  (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeBeatDiv7)));
	grid_actions[Editing::GridTypeBeatDiv6] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-sixths"),         grid_type_strings[(int)GridTypeBeatDiv6].c_str(),  (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeBeatDiv6)));
	grid_actions[Editing::GridTypeBeatDiv5] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-fifths"),         grid_type_strings[(int)GridTypeBeatDiv5].c_str(),  (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeBeatDiv5)));
	grid_actions[Editing::GridTypeBeatDiv4] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-quarters"),       grid_type_strings[(int)GridTypeBeatDiv4].c_str(),  (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeBeatDiv4)));
	grid_actions[Editing::GridTypeBeatDiv3] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-thirds"),         grid_type_strings[(int)GridTypeBeatDiv3].c_str(),  (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeBeatDiv3)));
	grid_actions[Editing::GridTypeBeatDiv2] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-halves"),         grid_type_strings[(int)GridTypeBeatDiv2].c_str(),  (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeBeatDiv2)));

	grid_actions[Editing::GridTypeTimecode] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-timecode"),       grid_type_strings[(int)GridTypeTimecode].c_str(),      (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeTimecode)));
	grid_actions[Editing::GridTypeMinSec] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-minsec"),         grid_type_strings[(int)GridTypeMinSec].c_str(),    (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeMinSec)));
	grid_actions[Editing::GridTypeCDFrame] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-cdframe"),        grid_type_strings[(int)GridTypeCDFrame].c_str(), (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeCDFrame)));

	grid_actions[Editing::GridTypeBeat] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-beat"),           grid_type_strings[(int)GridTypeBeat].c_str(),      (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeBeat)));
	grid_actions[Editing::GridTypeBar] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-bar"),            grid_type_strings[(int)GridTypeBar].c_str(),       (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeBar)));

	grid_actions[Editing::GridTypeNone] = ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-none"),           grid_type_strings[(int)GridTypeNone].c_str(),      (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeNone)));


	grid_actions[Editing::GridTypeBeatDiv32]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv32]);
	grid_actions[Editing::GridTypeBeatDiv28]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv28]);
	grid_actions[Editing::GridTypeBeatDiv24]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv24]);
	grid_actions[Editing::GridTypeBeatDiv20]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv20]);
	grid_actions[Editing::GridTypeBeatDiv16]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv16]);
	grid_actions[Editing::GridTypeBeatDiv14]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv14]);
	grid_actions[Editing::GridTypeBeatDiv12]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv12]);
	grid_actions[Editing::GridTypeBeatDiv10]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv10]);
	grid_actions[Editing::GridTypeBeatDiv8]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv8]);
	grid_actions[Editing::GridTypeBeatDiv7]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv7]);
	grid_actions[Editing::GridTypeBeatDiv6]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv6]);
	grid_actions[Editing::GridTypeBeatDiv5]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv5]);
	grid_actions[Editing::GridTypeBeatDiv4]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv4]);
	grid_actions[Editing::GridTypeBeatDiv3]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv3]);
	grid_actions[Editing::GridTypeBeatDiv2]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv2]);

	grid_actions[Editing::GridTypeTimecode]->set_short_label (grid_type_short_labels[Editing::GridTypeTimecode]);
	grid_actions[Editing::GridTypeMinSec]->set_short_label (grid_type_short_labels[Editing::GridTypeMinSec]);
	grid_actions[Editing::GridTypeCDFrame]->set_short_label (grid_type_short_labels[Editing::GridTypeCDFrame]);
	grid_actions[Editing::GridTypeBeat]->set_short_label (grid_type_short_labels[Editing::GridTypeBeat]);
	grid_actions[Editing::GridTypeBar]->set_short_label (grid_type_short_labels[Editing::GridTypeBar]);
	grid_actions[Editing::GridTypeNone]->set_short_label (grid_type_short_labels[Editing::GridTypeNone]);
}

void
EditingContext::enable_midi_bindings ()
{
	/* enable MIDI editing actions, which in turns enables their bindings */
	ActionManager::set_sensitive (_midi_actions, true);
}

void
EditingContext::disable_midi_bindings ()
{
	/* disable MIDI editing actions, which in turns disables their bindings */
	ActionManager::set_sensitive (_midi_actions, false);
}

void
EditingContext::register_midi_actions (Bindings* midi_bindings, std::string const & prefix)
{
	EC_LOCAL_TEMPO_SCOPE;

	_midi_actions = ActionManager::create_action_group (midi_bindings, prefix + X_("Notes"));

	/* two versions to allow same action for Delete and Backspace */

	ActionManager::register_action (_midi_actions, X_("clear-selection"), _("Clear Note Selection"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::clear_selection));
	ActionManager::register_action (_midi_actions, X_("invert-selection"), _("Invert Note Selection"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::invert_selection));
	ActionManager::register_action (_midi_actions, X_("extend-selection"), _("Extend Note Selection"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::extend_selection));
	ActionManager::register_action (_midi_actions, X_("duplicate-selection"), _("Duplicate Note Selection"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::duplicate_selection));

	/* Intervals */

	ActionManager::register_action (_midi_actions, X_("add-interval-m2"), _("Add minor 2nd (1 semitone)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::add_semitone_interval), 1));
	ActionManager::register_action (_midi_actions, X_("add-interval-M2"), _("Add major 2nd (2 semitones)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::add_semitone_interval), 2));
	ActionManager::register_action (_midi_actions, X_("add-interval-m3"), _("Add minor 3rd (3 semitones)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::add_semitone_interval), 3));
	ActionManager::register_action (_midi_actions, X_("add-interval-M3"), _("Add major 3rd (4 semitones)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::add_semitone_interval), 4));
	ActionManager::register_action (_midi_actions, X_("add-interval-P4"), _("Add perfect 4th (5 semitones)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::add_semitone_interval), 5));
	ActionManager::register_action (_midi_actions, X_("add-interval-A4"), _("Add tritone (6 semitones)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::add_semitone_interval), 6));
	ActionManager::register_action (_midi_actions, X_("add-interval-P5"), _("Add perfect 5th (7 semitones)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::add_semitone_interval), 7));
	ActionManager::register_action (_midi_actions, X_("add-interval-m6"), _("Add minor 6th (8 semitones)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::add_semitone_interval), 8));
	ActionManager::register_action (_midi_actions, X_("add-interval-M6"), _("Add major 6th (9 semitones)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::add_semitone_interval), 9));
	ActionManager::register_action (_midi_actions, X_("add-interval-M7"), _("Add minor 7th (10 semitones)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::add_semitone_interval), 10));
	ActionManager::register_action (_midi_actions, X_("add-interval-m7"), _("Add major 7th (11 semitones)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::add_semitone_interval), 11));
	ActionManager::register_action (_midi_actions, X_("add-interval-P8"), _("Add octave (12 semitones)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::add_semitone_interval), 12));

	/* Lengthen */

	ActionManager::register_action (_midi_actions, X_("move-starts-earlier-fine"), _("Move Note Start Earlier (fine)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiRegionView::move_note_starts_earlier_fine));
	ActionManager::register_action (_midi_actions, X_("move-starts-earlier"), _("Move Note Start Earlier"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiRegionView::move_note_starts_earlier));
	ActionManager::register_action (_midi_actions, X_("move-ends-later-fine"), _("Move Note Ends Later (fine)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiRegionView::move_note_ends_later_fine));
	ActionManager::register_action (_midi_actions, X_("move-ends-later"), _("Move Note Ends Later"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiRegionView::move_note_ends_later));

	/* Shorten */

	ActionManager::register_action (_midi_actions, X_("move-starts-later-fine"), _("Move Note Start Later (fine)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiRegionView::move_note_starts_later_fine));
	ActionManager::register_action (_midi_actions, X_("move-starts-later"), _("Move Note Start Later"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiRegionView::move_note_starts_later));
	ActionManager::register_action (_midi_actions, X_("move-ends-earlier-fine"), _("Move Note Ends Earlier (fine)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiRegionView::move_note_ends_earlier_fine));
	ActionManager::register_action (_midi_actions, X_("move-ends-earlier"), _("Move Note Ends Earlier"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiRegionView::move_note_ends_earlier));


	/* Alt versions allow bindings for both Tab and ISO_Left_Tab, if desired */

	ActionManager::register_action (_midi_actions, X_("select-all"), _("Select All"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::select_all_notes));
	ActionManager::register_action (_midi_actions, X_("alt-select-all"), _("Select All (alternate)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::select_all_notes));
	ActionManager::register_action (_midi_actions, X_("select-next"), _("Select Next"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::select_next_note));
	ActionManager::register_action (_midi_actions, X_("alt-select-next"), _("Select Next (alternate)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::select_next_note));
	ActionManager::register_action (_midi_actions, X_("select-previous"), _("Select Previous"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::select_previous_note));
	ActionManager::register_action (_midi_actions, X_("alt-select-previous"), _("Select Previous (alternate)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::select_previous_note));
	ActionManager::register_action (_midi_actions, X_("add-select-next"), _("Add Next to Selection"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::add_select_next_note));
	ActionManager::register_action (_midi_actions, X_("alt-add-select-next"), _("Add Next to Selection (alternate)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::add_select_next_note));
	ActionManager::register_action (_midi_actions, X_("add-select-previous"), _("Add Previous to Selection"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::add_select_previous_note));
	ActionManager::register_action (_midi_actions, X_("alt-add-select-previous"), _("Add Previous to Selection (alternate)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::add_select_previous_note));

	ActionManager::register_action (_midi_actions, X_("increase-velocity"), _("Increase Velocity"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::increase_note_velocity));
	ActionManager::register_action (_midi_actions, X_("increase-velocity-fine"), _("Increase Velocity (fine)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::increase_note_velocity_fine));
	ActionManager::register_action (_midi_actions, X_("increase-velocity-smush"), _("Increase Velocity (allow mush)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::increase_note_velocity_smush));
	ActionManager::register_action (_midi_actions, X_("increase-velocity-together"), _("Increase Velocity (non-relative)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::increase_note_velocity_together));
	ActionManager::register_action (_midi_actions, X_("increase-velocity-fine-smush"), _("Increase Velocity (fine, allow mush)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::increase_note_velocity_fine_smush));
	ActionManager::register_action (_midi_actions, X_("increase-velocity-fine-together"), _("Increase Velocity (fine, non-relative)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::increase_note_velocity_fine_together));
	ActionManager::register_action (_midi_actions, X_("increase-velocity-smush-together"), _("Increase Velocity (maintain ratios, allow mush)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::increase_note_velocity_smush_together));
	ActionManager::register_action (_midi_actions, X_("increase-velocity-fine-smush-together"), _("Increase Velocity (fine, allow mush, non-relative)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::increase_note_velocity_fine_smush_together));

	ActionManager::register_action (_midi_actions, X_("decrease-velocity"), _("Decrease Velocity"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::decrease_note_velocity));
	ActionManager::register_action (_midi_actions, X_("decrease-velocity-fine"), _("Decrease Velocity (fine)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::decrease_note_velocity_fine));
	ActionManager::register_action (_midi_actions, X_("decrease-velocity-smush"), _("Decrease Velocity (allow mush)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::decrease_note_velocity_smush));
	ActionManager::register_action (_midi_actions, X_("decrease-velocity-together"), _("Decrease Velocity (non-relative)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::decrease_note_velocity_together));
	ActionManager::register_action (_midi_actions, X_("decrease-velocity-fine-smush"), _("Decrease Velocity (fine, allow mush)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::decrease_note_velocity_fine_smush));
	ActionManager::register_action (_midi_actions, X_("decrease-velocity-fine-together"), _("Decrease Velocity (fine, non-relative)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::decrease_note_velocity_fine_together));
	ActionManager::register_action (_midi_actions, X_("decrease-velocity-smush-together"), _("Decrease Velocity (maintain ratios, allow mush)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::decrease_note_velocity_smush_together));
	ActionManager::register_action (_midi_actions, X_("decrease-velocity-fine-smush-together"), _("Decrease Velocity (fine, allow mush, non-relative)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::decrease_note_velocity_fine_smush_together));

	ActionManager::register_action (_midi_actions, X_("duplicate-notes"), _("Duplicate Notes"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::duplicate_notes));
	ActionManager::register_action (_midi_actions, X_("multi-duplicate-notes"), _("Multi-Duplicate Notes"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::multi_duplicate_notes));

	ActionManager::register_action (_midi_actions, X_("transpose-up-octave"), _("Transpose Up (octave)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::transpose_up_octave));
	ActionManager::register_action (_midi_actions, X_("transpose-up-octave-smush"), _("Transpose Up (octave, allow mush)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::transpose_up_octave_smush));
	ActionManager::register_action (_midi_actions, X_("transpose-up-semitone"), _("Transpose Up (semitone)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::transpose_up_tone));
	ActionManager::register_action (_midi_actions, X_("transpose-up-semitone-smush"), _("Transpose Up (semitone, allow mush)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::transpose_up_octave_smush));

	ActionManager::register_action (_midi_actions, X_("transpose-down-octave"), _("Transpose Down (octave)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::transpose_down_octave));
	ActionManager::register_action (_midi_actions, X_("transpose-down-octave-smush"), _("Transpose Down (octave, allow mush)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::transpose_down_octave_smush));
	ActionManager::register_action (_midi_actions, X_("transpose-down-semitone"), _("Transpose Down (semitone)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::transpose_down_tone));
	ActionManager::register_action (_midi_actions, X_("transpose-down-semitone-smush"), _("Transpose Down (semitone, allow mush)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::transpose_down_octave_smush));

	ActionManager::register_action (_midi_actions, X_("nudge-later"), _("Nudge Notes Later (grid)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::nudge_notes_later));
	ActionManager::register_action (_midi_actions, X_("nudge-later-fine"), _("Nudge Notes Later (1/4 grid)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::nudge_notes_later_fine));
	ActionManager::register_action (_midi_actions, X_("nudge-earlier"), _("Nudge Notes Earlier (grid)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::nudge_notes_earlier));
	ActionManager::register_action (_midi_actions, X_("nudge-earlier-fine"), _("Nudge Notes Earlier (1/4 grid)"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::nudge_notes_earlier_fine));

	ActionManager::register_action (_midi_actions, X_("split-notes-grid"), _("Split Selected Notes on grid boundaries"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::split_notes_grid));
	ActionManager::register_action (_midi_actions, X_("split-notes-more"), _("Split Selected Notes into more pieces"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::split_notes_more));
	ActionManager::register_action (_midi_actions, X_("split-notes-less"), _("Split Selected Notes into less pieces"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::split_notes_less));
	ActionManager::register_action (_midi_actions, X_("join-notes"), _("Join Selected Notes"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::join_notes));

	ActionManager::register_action (_midi_actions, X_("strum-forward"), _("Strum Notes Forward"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::strum_notes_forward));
	ActionManager::register_action (_midi_actions, X_("strum-backward"), _("Strum Notes Backward"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::strum_notes_backward));

	ActionManager::register_action (_midi_actions, X_("edit-channels"), _("Edit Note Channels"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::channel_edit));
	ActionManager::register_action (_midi_actions, X_("edit-velocities"), _("Edit Note Velocities"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiView::velocity_edit));

	ActionManager::register_action (_midi_actions, X_("quantize-selected-notes"), _("Quantize Selected Notes"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action),  &MidiView::quantize_selected_notes));

	length_actions = ActionManager::create_action_group (midi_bindings, prefix + X_("DrawLength"));
	RadioAction::Group draw_length_group;

	draw_length_actions[Editing::GridTypeBeatDiv32] = ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-thirtyseconds"),  grid_type_strings[(int)GridTypeBeatDiv32].c_str(), sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_length_chosen), Editing::GridTypeBeatDiv32));
	draw_length_actions[Editing::GridTypeBeatDiv28] = ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-twentyeighths"),  grid_type_strings[(int)GridTypeBeatDiv28].c_str(), sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_length_chosen), Editing::GridTypeBeatDiv28));
	draw_length_actions[Editing::GridTypeBeatDiv24] = ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-twentyfourths"),  grid_type_strings[(int)GridTypeBeatDiv24].c_str(), sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_length_chosen), Editing::GridTypeBeatDiv24));
	draw_length_actions[Editing::GridTypeBeatDiv20] = ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-twentieths"),     grid_type_strings[(int)GridTypeBeatDiv20].c_str(), sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_length_chosen), Editing::GridTypeBeatDiv20));
	draw_length_actions[Editing::GridTypeBeatDiv16] = ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-asixteenthbeat"), grid_type_strings[(int)GridTypeBeatDiv16].c_str(), sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_length_chosen), Editing::GridTypeBeatDiv16));
	draw_length_actions[Editing::GridTypeBeatDiv14] = ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-fourteenths"),    grid_type_strings[(int)GridTypeBeatDiv14].c_str(), sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_length_chosen), Editing::GridTypeBeatDiv14));
	draw_length_actions[Editing::GridTypeBeatDiv12] = ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-twelfths"),       grid_type_strings[(int)GridTypeBeatDiv12].c_str(), sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_length_chosen), Editing::GridTypeBeatDiv12));
	draw_length_actions[Editing::GridTypeBeatDiv10] = ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-tenths"),         grid_type_strings[(int)GridTypeBeatDiv10].c_str(), sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_length_chosen), Editing::GridTypeBeatDiv10));
	draw_length_actions[Editing::GridTypeBeatDiv8]  = ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-eighths"),        grid_type_strings[(int)GridTypeBeatDiv8].c_str(),  sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_length_chosen), Editing::GridTypeBeatDiv8));
	draw_length_actions[Editing::GridTypeBeatDiv7]  = ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-sevenths"),       grid_type_strings[(int)GridTypeBeatDiv7].c_str(),  sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_length_chosen), Editing::GridTypeBeatDiv7));
	draw_length_actions[Editing::GridTypeBeatDiv6]  = ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-sixths"),         grid_type_strings[(int)GridTypeBeatDiv6].c_str(),  sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_length_chosen), Editing::GridTypeBeatDiv6));
	draw_length_actions[Editing::GridTypeBeatDiv5]  = ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-fifths"),         grid_type_strings[(int)GridTypeBeatDiv5].c_str(),  sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_length_chosen), Editing::GridTypeBeatDiv5));
	draw_length_actions[Editing::GridTypeBeatDiv4]  = ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-quarters"),       grid_type_strings[(int)GridTypeBeatDiv4].c_str(),  sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_length_chosen), Editing::GridTypeBeatDiv4));
	draw_length_actions[Editing::GridTypeBeatDiv3]  = ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-thirds"),         grid_type_strings[(int)GridTypeBeatDiv3].c_str(),  sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_length_chosen), Editing::GridTypeBeatDiv3));
	draw_length_actions[Editing::GridTypeBeatDiv2]  = ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-halves"),         grid_type_strings[(int)GridTypeBeatDiv2].c_str(),  sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_length_chosen), Editing::GridTypeBeatDiv2));
	draw_length_actions[Editing::GridTypeBeat]      = ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-beat"),           grid_type_strings[(int)GridTypeBeat].c_str(),      sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_length_chosen), Editing::GridTypeBeat));
	draw_length_actions[Editing::GridTypeBar]       = ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-bar"),            grid_type_strings[(int)GridTypeBar].c_str(),       sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_length_chosen), Editing::GridTypeBar));
	draw_length_actions[DRAW_LEN_AUTO]              = ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-auto"),           _("Auto"),                                         sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_length_chosen), DRAW_LEN_AUTO));

	draw_length_actions[Editing::GridTypeBeatDiv32]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv32]);
	draw_length_actions[Editing::GridTypeBeatDiv28]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv28]);
	draw_length_actions[Editing::GridTypeBeatDiv24]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv24]);
	draw_length_actions[Editing::GridTypeBeatDiv20]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv20]);
	draw_length_actions[Editing::GridTypeBeatDiv16]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv16]);
	draw_length_actions[Editing::GridTypeBeatDiv14]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv14]);
	draw_length_actions[Editing::GridTypeBeatDiv12]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv12]);
	draw_length_actions[Editing::GridTypeBeatDiv10]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv10]);
	draw_length_actions[Editing::GridTypeBeatDiv8]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv8]);
	draw_length_actions[Editing::GridTypeBeatDiv7]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv7]);
	draw_length_actions[Editing::GridTypeBeatDiv6]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv6]);
	draw_length_actions[Editing::GridTypeBeatDiv5]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv5]);
	draw_length_actions[Editing::GridTypeBeatDiv4]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv4]);
	draw_length_actions[Editing::GridTypeBeatDiv3]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv3]);
	draw_length_actions[Editing::GridTypeBeatDiv2]->set_short_label (grid_type_short_labels[Editing::GridTypeBeatDiv2]);
	draw_length_actions[Editing::DRAW_LEN_AUTO]->set_short_label (_("Auto"));

	velocity_actions = ActionManager::create_action_group (midi_bindings, prefix + X_("DrawVelocity"));
	RadioAction::Group draw_velocity_group;
	draw_velocity_actions[DRAW_VEL_AUTO] = ActionManager::register_radio_action (velocity_actions, draw_velocity_group, X_("draw-velocity-auto"),  _("Auto"), sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_velocity_chosen), DRAW_VEL_AUTO));
	for (int i = 1; i <= 127; i++) {
		char buf[64];
		snprintf(buf, sizeof (buf), X_("draw-velocity-%d"), i);
		char vel[64];
		sprintf(vel, _("Velocity %d"), i);
		draw_velocity_actions[i] = ActionManager::register_radio_action (velocity_actions, draw_velocity_group, buf, vel, sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_velocity_chosen), i));
		snprintf (buf,sizeof (buf), "%d", i);
		draw_velocity_actions[i]->set_short_label (buf);
	}

	channel_actions = ActionManager::create_action_group (midi_bindings, prefix + X_("DrawChannel"));
	RadioAction::Group draw_channel_group;
	draw_channel_actions[DRAW_CHAN_AUTO] = ActionManager::register_radio_action (channel_actions, draw_channel_group, X_("draw-channel-auto"),  _("Auto"), sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_channel_chosen), DRAW_CHAN_AUTO));
	for (int i = 0; i <= 15; i++) {
		char buf[64];
		snprintf(buf, sizeof (buf), X_("draw-channel-%d"), i+1);
		char ch[64];
		sprintf(ch, X_("Channel %d"), i+1);
		draw_channel_actions[i] = ActionManager::register_radio_action (channel_actions, draw_channel_group, buf, ch, sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_channel_chosen), i));
	}

	chord_actions = ActionManager::create_action_group (midi_bindings, prefix + X_("Chords"));
	RadioAction::Group draw_chord_group;

	draw_chord_actions.resize (30);

	_no_chord_action = ActionManager::register_radio_action (chord_actions, draw_chord_group, X_("no-draw-chord"), _("No Chord"), sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_chord_chosen), -1));

	for (int n = 0; n < 30; ++n) {
		char action_name[64];
		char action_desc[64];
		snprintf (action_name, sizeof (action_name), X_("draw-chord-%d"), n);
		snprintf (action_desc, sizeof (action_desc), X_("Draw Chord #%d"), n + 1);
		draw_chord_actions[n] = ActionManager::register_radio_action (chord_actions, draw_chord_group, action_name, action_desc,  sigc::bind (sigc::mem_fun (*this, &EditingContext::draw_chord_chosen), n));
	}

	ActionManager::set_sensitive (_midi_actions, false);
}

void
EditingContext::midi_action (void (MidiView::*method)())
{
	EC_LOCAL_TEMPO_SCOPE;

	MidiRegionSelection ms = get_selection().midi_regions();

	if (ms.empty()) {
		return;
	}

	if (ms.size() > 1) {

		auto views = filter_to_unique_midi_region_views (ms);

		for (auto & mrv : views) {
			(mrv->*method) ();
		}

	} else {

		MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(ms.front());

		if (mrv) {
			(mrv->*method)();
		}
	}
}

void
EditingContext::next_grid_choice ()
{
	EC_LOCAL_TEMPO_SCOPE;

	switch (grid_type()) {
	case Editing::GridTypeBeatDiv32:
		set_grid_type (Editing::GridTypeNone);
		break;
	case Editing::GridTypeBeatDiv16:
		set_grid_type (Editing::GridTypeBeatDiv32);
		break;
	case Editing::GridTypeBeatDiv8:
		set_grid_type (Editing::GridTypeBeatDiv16);
		break;
	case Editing::GridTypeBeatDiv4:
		set_grid_type (Editing::GridTypeBeatDiv8);
		break;
	case Editing::GridTypeBeatDiv2:
		set_grid_type (Editing::GridTypeBeatDiv4);
		break;
	case Editing::GridTypeBeat:
		set_grid_type (Editing::GridTypeBeatDiv2);
		break;
	case Editing::GridTypeBar:
		set_grid_type (Editing::GridTypeBeat);
		break;
	case Editing::GridTypeNone:
		set_grid_type (Editing::GridTypeBar);
		break;
	case Editing::GridTypeBeatDiv3:
	case Editing::GridTypeBeatDiv6:
	case Editing::GridTypeBeatDiv12:
	case Editing::GridTypeBeatDiv24:
	case Editing::GridTypeBeatDiv5:
	case Editing::GridTypeBeatDiv10:
	case Editing::GridTypeBeatDiv20:
	case Editing::GridTypeBeatDiv7:
	case Editing::GridTypeBeatDiv14:
	case Editing::GridTypeBeatDiv28:
	case Editing::GridTypeTimecode:
	case Editing::GridTypeMinSec:
	case Editing::GridTypeCDFrame:
		break;  //do nothing
	}
}

void
EditingContext::prev_grid_choice ()
{
	EC_LOCAL_TEMPO_SCOPE;

	switch (grid_type()) {
	case Editing::GridTypeBeatDiv32:
		set_grid_type (Editing::GridTypeBeatDiv16);
		break;
	case Editing::GridTypeBeatDiv16:
		set_grid_type (Editing::GridTypeBeatDiv8);
		break;
	case Editing::GridTypeBeatDiv8:
		set_grid_type (Editing::GridTypeBeatDiv4);
		break;
	case Editing::GridTypeBeatDiv4:
		set_grid_type (Editing::GridTypeBeatDiv2);
		break;
	case Editing::GridTypeBeatDiv2:
		set_grid_type (Editing::GridTypeBeat);
		break;
	case Editing::GridTypeBeat:
		set_grid_type (Editing::GridTypeBar);
		break;
	case Editing::GridTypeBar:
		set_grid_type (Editing::GridTypeNone);
		break;
	case Editing::GridTypeNone:
		set_grid_type (Editing::GridTypeBeatDiv32);
		break;
	case Editing::GridTypeBeatDiv3:
	case Editing::GridTypeBeatDiv6:
	case Editing::GridTypeBeatDiv12:
	case Editing::GridTypeBeatDiv24:
	case Editing::GridTypeBeatDiv5:
	case Editing::GridTypeBeatDiv10:
	case Editing::GridTypeBeatDiv20:
	case Editing::GridTypeBeatDiv7:
	case Editing::GridTypeBeatDiv14:
	case Editing::GridTypeBeatDiv28:
	case Editing::GridTypeTimecode:
	case Editing::GridTypeMinSec:
	case Editing::GridTypeCDFrame:
		break;  //do nothing
	}
}

void
EditingContext::grid_type_chosen (GridType gt)
{
	EC_LOCAL_TEMPO_SCOPE;

	/* this is driven by a toggle on a radio group, and so is invoked twice,
	   once for the item that became inactive and once for the one that became
	   active.
	*/

	auto ti = grid_actions.find (gt);
	assert (ti != grid_actions.end());

	if (!ti->second->get_active()) {
		return;
	}

	unsigned int grid_ind = (unsigned int) gt;

	if (internal_editing() && UIConfiguration::instance().get_grid_follows_internal()) {
		internal_grid_type = gt;
	} else {
		pre_internal_grid_type = gt;
	}

	grid_type_selector.set_active (grid_type_short_labels[grid_ind]);

	if (UIConfiguration::instance().get_show_grids_ruler() && !ARDOUR_UI::instance()->loading_session()) {
		show_rulers_for_grid ();
	}

	instant_save ();

	const bool grid_is_musical = grid_musical ();

	if (grid_is_musical) {
		compute_bbt_ruler_scale (_leftmost_sample, _leftmost_sample + current_page_samples());
		update_tempo_based_rulers ();
	} else if (current_mouse_mode () == Editing::MouseGrid) {
		mouse_mode_actions[Editing::MouseObject]->set_active (true);
	}

	mouse_mode_actions[Editing::MouseGrid]->set_sensitive (grid_is_musical);

	mark_region_boundary_cache_dirty ();

	redisplay_grid (false);

	SnapChanged (); /* EMIT SIGNAL */
}

Glib::RefPtr<Gtk::RadioAction>
EditingContext::draw_chord_action (int num)
{
	if (num < (int) draw_chord_actions.size()) {
		return draw_chord_actions[num];
	}
	return Glib::RefPtr<Gtk::RadioAction>();
}

void
EditingContext::change_chord_list (size_t n, std::string const & short_name)
{
	if (n >= _chord_name_list.size()) {
		return;
	}

	_chord_name_list[n] = short_name;
	ChordsChanged (); /* EMIT SIGNAL */
}

void
EditingContext::draw_chord_chosen (int num)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (num < 0) {
		_draw_chord_name = std::string();
		return;
	}

	/* this is driven by a toggle on a radio group, and so is invoked twice,
	   once for the item that became inactive and once for the one that became
	   active.
	*/

	RefPtr<RadioAction> ract;

	ract = draw_chord_actions[std::max (std::min (size_t (num), _chord_name_list.size()), size_t (0))];

	if (!ract->get_active()) {
		return;
	}

	if (num >= (int) _chord_name_list.size()) {
		return;
	}

	_draw_chord_name = _chord_name_list[num];

	instant_save ();
}

void
EditingContext::draw_length_chosen (GridType type)
{
	EC_LOCAL_TEMPO_SCOPE;

	/* this is driven by a toggle on a radio group, and so is invoked twice,
	   once for the item that became inactive and once for the one that became
	   active.
	*/

	RefPtr<RadioAction> ract = draw_length_actions[type];

	if (!ract->get_active()) {
		return;
	}

	if ((DRAW_LEN_AUTO != type) && !grid_type_is_musical (type) ) {  // is this is sensible sanity check ?
		set_draw_length (DRAW_LEN_AUTO);
		return;
	}

	if (DRAW_LEN_AUTO == (unsigned int) type) {
		draw_length_selector.set_active (_("Auto"));
	} else {
		draw_length_selector.set_active (grid_type_short_labels[(unsigned int) type]);
	}

	instant_save ();
}

void
EditingContext::draw_velocity_chosen (int v)
{
	EC_LOCAL_TEMPO_SCOPE;

	/* this is driven by a toggle on a radio group, and so is invoked twice,
	   once for the item that became inactive and once for the one that became
	   active.
	*/

	RefPtr<RadioAction> ract;

	if (v == DRAW_VEL_AUTO) {
		ract = draw_velocity_actions[DRAW_VEL_AUTO];
	} else {
		ract = draw_velocity_actions[std::max (std::min (v, 127), 0)];
	}

	if (!ract->get_active()) {
		return;
	}

	if (DRAW_VEL_AUTO == v) {
		draw_velocity_selector.set_active (_("Auto"));
	} else {
		char buf[64];
		snprintf (buf, sizeof (buf), "%d", v);
		draw_velocity_selector.set_text (buf);
	}

	instant_save ();
}

void
EditingContext::draw_channel_chosen (int c)
{
	EC_LOCAL_TEMPO_SCOPE;

	/* this is driven by a toggle on a radio group, and so is invoked twice,
	   once for the item that became inactive and once for the one that became
	   active.
	*/

	RefPtr<RadioAction> ract;

	if (c == DRAW_CHAN_AUTO) {
		ract = draw_channel_actions[DRAW_CHAN_AUTO];
	} else {
		ract = draw_channel_actions[std::max (std::min (c, 15), 0)];
	}

	if (!ract->get_active()) {
		return;
	}

	if (DRAW_CHAN_AUTO == c) {
		draw_channel_selector.set_active (_("Auto"));
	} else {
		char buf[64];
		snprintf (buf, sizeof (buf), "%d", c+1 );
		draw_channel_selector.set_active (buf);
	}

	instant_save ();
}

void
EditingContext::cycle_snap_mode ()
{
	EC_LOCAL_TEMPO_SCOPE;

	switch (snap_mode()) {
	case SnapOff:
	case SnapNormal:
		set_snap_mode (SnapMagnetic);
		break;
	case SnapMagnetic:
		set_snap_mode (SnapOff);
		break;
	}
}

void
EditingContext::snap_mode_chosen (SnapMode mode)
{
	EC_LOCAL_TEMPO_SCOPE;

	/* this is driven by a toggle on a radio group, and so is invoked twice,
	   once for the item that became inactive and once for the one that became
	   active.
	*/

	if (mode == SnapNormal) {
		mode = SnapMagnetic;
	}

	if (!snap_mode_actions[mode]->get_active()) {
		return;
	}

	if (internal_editing()) {
		internal_snap_mode = mode;
	} else {
		pre_internal_snap_mode = mode;
	}

	if (mode == SnapOff) {
		snap_mode_button.set_active_state (Gtkmm2ext::Off);
	} else {
		snap_mode_button.set_active_state (Gtkmm2ext::ExplicitActive);
	}

	instant_save ();
}

GridType
EditingContext::grid_type() const
{
	EC_LOCAL_TEMPO_SCOPE;

	for (auto const & [grid_type,action] : grid_actions) {
		if (action->get_active()) {
			return grid_type;
		}
	}

	return Editing::GridTypeNone;
}

GridType
EditingContext::draw_length() const
{
	EC_LOCAL_TEMPO_SCOPE;

	for (auto const & [len,action] : draw_length_actions) {
		if (action->get_active()) {
			return len;
		}
	}

	return GridTypeBeat;
}

int
EditingContext::draw_velocity() const
{
	EC_LOCAL_TEMPO_SCOPE;

	for (auto const & [vel,action] : draw_velocity_actions) {
		if (action->get_active()) {
			return vel;
		}
	}

	return 64;
}

int
EditingContext::draw_channel() const
{
	EC_LOCAL_TEMPO_SCOPE;

	for (auto const & [chn,action] : draw_channel_actions) {
		if (action->get_active()) {
			return chn;
		}
	}

	return 0;
}

bool
EditingContext::grid_musical() const
{
	EC_LOCAL_TEMPO_SCOPE;

	return grid_type_is_musical (grid_type());
}

bool
EditingContext::grid_type_is_musical(GridType gt) const
{
	EC_LOCAL_TEMPO_SCOPE;

	switch (gt) {
	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;
	case GridTypeNone:
	case GridTypeTimecode:
	case GridTypeMinSec:
	case GridTypeCDFrame:
		return false;
	}
	return false;
}

SnapMode
EditingContext::snap_mode() const
{
	EC_LOCAL_TEMPO_SCOPE;

	for (auto const & [mode,action] : snap_mode_actions) {
		if (action->get_active()) {
			return mode;
		}
	}

	return Editing::SnapOff;
}

void
EditingContext::set_draw_length (GridType gt)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (draw_length_actions.find (gt) != draw_length_actions.end() && draw_length_actions[gt]) {
		draw_length_actions[gt]->set_active (true);
	}
}

void
EditingContext::set_draw_velocity (int v)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (draw_velocity_actions.find (v) == draw_velocity_actions.end() || draw_velocity_actions[v]) {
		return;
	}

	draw_velocity_actions[v]->set_active (true);
}

void
EditingContext::set_draw_channel (int c)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (draw_channel_actions.find (c) == draw_channel_actions.end() || draw_channel_actions[c]) {
		return;
	}

	draw_channel_actions[c]->set_active (true);
}

void
EditingContext::set_grid_type (GridType gt)
{
	EC_LOCAL_TEMPO_SCOPE;

	grid_actions[gt]->set_active (true);
}

void
EditingContext::set_snap_mode (SnapMode mode)
{
	EC_LOCAL_TEMPO_SCOPE;

	snap_mode_actions[mode]->set_active (true);
}

void
EditingContext::build_grid_type_menu ()
{
	EC_LOCAL_TEMPO_SCOPE;

	using namespace Menu_Helpers;

	/* there's no Grid, but if Snap is engaged, the Snap preferences will be applied */
	grid_type_selector.append (grid_actions[GridTypeNone]);
	grid_type_selector.add_separator ();

	/* musical grid: bars, quarter-notes, etc */
	grid_type_selector.append (grid_actions[GridTypeBar]);
	grid_type_selector.append (grid_actions[GridTypeBeat]);
	grid_type_selector.append (grid_actions[GridTypeBeatDiv2]);
	grid_type_selector.append (grid_actions[GridTypeBeatDiv4]);
	grid_type_selector.append (grid_actions[GridTypeBeatDiv8]);
	grid_type_selector.append (grid_actions[GridTypeBeatDiv16]);
	grid_type_selector.append (grid_actions[GridTypeBeatDiv32]);

	/* triplet grid */
	grid_type_selector.add_separator ();;
	Gtk::Menu *triplet_menu = manage (new Menu);
	{
		grid_type_selector.append (*triplet_menu, grid_actions[GridTypeBeatDiv3]);
		grid_type_selector.append (*triplet_menu, grid_actions[GridTypeBeatDiv6]);
		grid_type_selector.append (*triplet_menu, grid_actions[GridTypeBeatDiv12]);
		grid_type_selector.append (*triplet_menu, grid_actions[GridTypeBeatDiv24]);
	}
	grid_type_selector.add_menu_elem (Menu_Helpers::MenuElem (_("Triplets"), *triplet_menu));

	/* quintuplet grid */
	Gtk::Menu *quintuplet_menu = manage (new Menu);
	{
		grid_type_selector.append (*quintuplet_menu, grid_actions[GridTypeBeatDiv5]);
		grid_type_selector.append (*quintuplet_menu, grid_actions[GridTypeBeatDiv10]);
		grid_type_selector.append (*quintuplet_menu, grid_actions[GridTypeBeatDiv20]);
	}

	grid_type_selector.add_menu_elem (Menu_Helpers::MenuElem (_("Quintuplets"), *quintuplet_menu));

	/* septuplet grid */
	Gtk::Menu *septuplet_menu = manage (new Menu);
	{
		grid_type_selector.append (*septuplet_menu, grid_actions[GridTypeBeatDiv7]);
		grid_type_selector.append (*septuplet_menu, grid_actions[GridTypeBeatDiv14]);
		grid_type_selector.append (*septuplet_menu, grid_actions[GridTypeBeatDiv28]);
	}
	grid_type_selector.add_menu_elem (Menu_Helpers::MenuElem (_("Septuplets"), *septuplet_menu));

	grid_type_selector.add_separator ();
	grid_type_selector.append (grid_actions[GridTypeTimecode]);
	grid_type_selector.append (grid_actions[GridTypeMinSec]);
	grid_type_selector.append (grid_actions[GridTypeCDFrame]);

	grid_type_selector.set_sizing_texts (grid_type_short_labels);
}

void
EditingContext::build_draw_midi_menus ()
{
	EC_LOCAL_TEMPO_SCOPE;

	using namespace Menu_Helpers;

	/* Note-Length when drawing */

	std::vector<GridType> grids ({
			GridTypeBeat,
			GridTypeBeatDiv2,
			GridTypeBeatDiv4,
			GridTypeBeatDiv8,
			GridTypeBeatDiv16,
			GridTypeBeatDiv32,
			GridTypeNone});
	std::vector<std::string> draw_grid_type_strings;

	for (auto & g : grids) {
		Glib::RefPtr<RadioAction> ract = draw_length_actions[g];
		draw_length_selector.append (ract);
		draw_grid_type_strings.push_back (ract->get_short_label());

	}

	draw_length_selector.set_sizing_texts (draw_grid_type_strings);

	/* Note-Velocity when drawing */

	std::vector<int> preselected_velocities ({8,32,64,82,100,127, DRAW_VEL_AUTO});
	std::vector<std::string> draw_velocity_strings;

	for (auto & v : preselected_velocities) {
		Glib::RefPtr<RadioAction> ract = draw_velocity_actions[v];
		assert (ract);
		draw_velocity_selector.append (ract);
		draw_velocity_strings.push_back (ract->get_short_label());
	}

	draw_velocity_selector.set_sizing_texts (draw_velocity_strings);

	std::vector<int> possible_channels ({0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, DRAW_CHAN_AUTO});
	std::vector<std::string> draw_channel_strings;

	for (auto & c : possible_channels) {
		Glib::RefPtr<RadioAction> ract = draw_channel_actions[c];
		assert (ract);
		draw_channel_selector.append (ract);
		draw_channel_strings.push_back (ract->get_short_label());
	}

	draw_channel_selector.set_sizing_texts (draw_channel_strings);

	draw_channel_chosen (draw_channel());
	draw_length_chosen (draw_length());
	draw_velocity_chosen (draw_velocity());
}

bool
EditingContext::drag_active () const
{
	EC_LOCAL_TEMPO_SCOPE;

	return _drags->active();
}

bool
EditingContext::preview_video_drag_active () const
{
	EC_LOCAL_TEMPO_SCOPE;

	return _drags->preview_video ();
}

Temporal::TimeDomain
EditingContext::time_domain () const
{
	EC_LOCAL_TEMPO_SCOPE;

	if (_session) {
		return _session->config.get_default_time_domain();
	}

	/* Probably never reached */

	if (snap_mode() == SnapOff) {
		return Temporal::AudioTime;
	}

	switch (grid_type()) {
		case GridTypeNone:
			/* fallthrough */
		case GridTypeMinSec:
			/* fallthrough */
		case GridTypeCDFrame:
			/* fallthrough */
		case GridTypeTimecode:
			/* fallthrough */
			return Temporal::AudioTime;
		default:
			break;
	}

	return Temporal::BeatTime;
}

void
EditingContext::toggle_stationary_playhead ()
{
	EC_LOCAL_TEMPO_SCOPE;

	stationary_playhead_action->set_active (!stationary_playhead_action->get_active ());
}

void
EditingContext::stationary_playhead_chosen ()
{
	EC_LOCAL_TEMPO_SCOPE;

	instant_save ();
}

void
EditingContext::set_stationary_playhead (bool yn)
{
	EC_LOCAL_TEMPO_SCOPE;

	stationary_playhead_action->set_active (yn);
}

bool
EditingContext::stationary_playhead () const
{
	EC_LOCAL_TEMPO_SCOPE;

	if (!stationary_playhead_action) {
		return false;
	}

	return stationary_playhead_action->get_active ();
}

void
EditingContext::toggle_follow_playhead ()
{
	EC_LOCAL_TEMPO_SCOPE;

	set_follow_playhead (!follow_playhead_action->get_active(), true);
}

void
EditingContext::follow_playhead_chosen ()
{
	EC_LOCAL_TEMPO_SCOPE;

	instant_save ();
}

/** @param yn true to follow playhead, otherwise false.
 *  @param catch_up true to reset the editor view to show the playhead (if yn == true), otherwise false.
 */
void
EditingContext::set_follow_playhead (bool yn, bool catch_up)
{
	EC_LOCAL_TEMPO_SCOPE;

	assert (follow_playhead_action);
	follow_playhead_action->set_active (yn);
	if (yn && catch_up) {
		/* catch up */
		reset_x_origin_to_follow_playhead ();
	}
}

bool
EditingContext::follow_playhead() const
{
	EC_LOCAL_TEMPO_SCOPE;

	if (!follow_playhead_action) {
		return false;
	}

	/* Prevent follow playhead during the drag to be nice to the user */
	return follow_playhead_action->get_active () && !_drags->active ();
}

double
EditingContext::time_to_pixel (timepos_t const & pos) const
{
	EC_LOCAL_TEMPO_SCOPE;

	return sample_to_pixel (pos.samples());
}

double
EditingContext::time_to_pixel_unrounded (timepos_t const & pos) const
{
	EC_LOCAL_TEMPO_SCOPE;

	return sample_to_pixel_unrounded (pos.samples());
}

double
EditingContext::time_delta_to_pixel (timepos_t const& start, timepos_t const& end) const
{
	EC_LOCAL_TEMPO_SCOPE;

	return sample_to_pixel (end.samples()) - sample_to_pixel (start.samples ());
}

double
EditingContext::duration_to_pixels (timecnt_t const & dur) const
{
	EC_LOCAL_TEMPO_SCOPE;

	return sample_to_pixel (dur.samples());
}

double
EditingContext::duration_to_pixels_unrounded (timecnt_t const & dur) const
{
	EC_LOCAL_TEMPO_SCOPE;

	return sample_to_pixel_unrounded (dur.samples());
}

/** Snap a position to the grid, if appropriate, taking into account current
 *  grid settings and also the state of any snap modifier keys that may be pressed.
 *  @param start Position to snap.
 *  @param event Event to get current key modifier information from, or 0.
 */
void
EditingContext::snap_to_with_modifier (timepos_t& start, GdkEvent const * event, Temporal::RoundMode direction, SnapPref pref, bool ensure_snap) const
{
	EC_LOCAL_TEMPO_SCOPE;

	if (!_session || !event) {
		return;
	}

	if (ArdourKeyboard::indicates_snap (event->button.state)) {
		if (snap_mode() == SnapOff) {
			snap_to_internal (start, direction, pref, ensure_snap);
		}

	} else {
		if (snap_mode() != SnapOff) {
			snap_to_internal (start, direction, pref);
		} else if (ArdourKeyboard::indicates_snap_delta (event->button.state)) {
			/* SnapOff, but we pressed the snap_delta modifier */
			snap_to_internal (start, direction, pref, ensure_snap);
		}
	}
}

void
EditingContext::snap_to (timepos_t& start, Temporal::RoundMode direction, SnapPref pref, bool ensure_snap) const
{
	EC_LOCAL_TEMPO_SCOPE;

	if (!_session || (snap_mode() == SnapOff && !ensure_snap)) {
		return;
	}

	snap_to_internal (start, direction, pref, ensure_snap);
}

timepos_t
EditingContext::snap_to_bbt (timepos_t const & presnap, Temporal::RoundMode direction, SnapPref gpref) const
{
	EC_LOCAL_TEMPO_SCOPE;

	return snap_to_bbt_via_grid (presnap, direction, gpref, grid_type());
}

timepos_t
EditingContext::snap_to_bbt_via_grid (timepos_t const & presnap, Temporal::RoundMode direction, SnapPref gpref, GridType grid_type) const
{
	EC_LOCAL_TEMPO_SCOPE;

	timepos_t ret(presnap);
	TempoMap::SharedPtr tmap (TempoMap::use());

	/* Snap to bar always uses bars, and ignores visual grid, so it may
	 * sometimes snap to bars that are not visually distinguishable.
	 *
	 * XXX this should probably work totally different: we should get the
	 * nearby grid and walk towards the next bar point.
	 */

	if (grid_type == GridTypeBar) {
		return timepos_t (tmap->quarters_at (presnap).round_to_subdivision (get_grid_beat_divisions(grid_type), direction));
	}

	if (gpref != SnapToGrid_Unscaled) { // use the visual grid lines which are limited by the zoom scale that the user selected

		/* Determine the most obvious divisor of a beat to use
		 * for the snap, based on the grid setting.
		 */

		int divisor;
		switch (grid_type) {
			case GridTypeBeatDiv3:
			case GridTypeBeatDiv6:
			case GridTypeBeatDiv12:
			case GridTypeBeatDiv24:
				divisor = 3;
				break;
			case GridTypeBeatDiv5:
			case GridTypeBeatDiv10:
			case GridTypeBeatDiv20:
				divisor = 5;
				break;
			case GridTypeBeatDiv7:
			case GridTypeBeatDiv14:
			case GridTypeBeatDiv28:
				divisor = 7;
				break;
			case GridTypeBeat:
				divisor = 1;
				break;
			case GridTypeNone:
				return ret;
			default:
				divisor = 2;
				break;
		};

		/* bbt_ruler_scale reflects the level of detail we will show
		 * for the visual grid. Adjust the "natural" divisor to reflect
		 * this level of detail, and snap to that.
		 *
		 * So, for example, if the grid is Div3, we use 3 divisions per
		 * beat, but if the visual grid is using bbt_show_sixteenths (a
		 * fairly high level of detail), we will snap to (2 * 3)
		 * divisions per beat. Etc.
		 */

		BBTRulerScale scale = bbt_ruler_scale;
		switch (scale) {
			case bbt_show_many:
			case bbt_show_64:
			case bbt_show_16:
			case bbt_show_4:
			case bbt_show_1:
				/* Round to Bar */
				ret = timepos_t (tmap->quarters_at (presnap).round_to_subdivision (-1, direction));
				break;
			case bbt_show_quarters:
				/* Round to Beat */
				ret = timepos_t (tmap->quarters_at (presnap).round_to_subdivision (1, direction));
				break;
			case bbt_show_eighths:
				ret = timepos_t (tmap->quarters_at (presnap).round_to_subdivision (1 * divisor, direction));
				break;
			case bbt_show_sixteenths:
				ret = timepos_t (tmap->quarters_at (presnap).round_to_subdivision (2 * divisor, direction));
				break;
			case bbt_show_thirtyseconds:
				ret = timepos_t (tmap->quarters_at (presnap).round_to_subdivision (4 * divisor, direction));
				break;
			case bbt_show_sixtyfourths:
				ret = timepos_t (tmap->quarters_at (presnap).round_to_subdivision (8 * divisor, direction));
				break;
			case bbt_show_onetwentyeighths:
				ret = timepos_t (tmap->quarters_at (presnap).round_to_subdivision (16 * divisor, direction));
				break;
		}
	} else {
		/* Just use the grid as specified, without paying attention to
		 * zoom level
		 */

		ret = timepos_t (tmap->quarters_at (presnap).round_to_subdivision (get_grid_beat_divisions (grid_type), direction));
	}

	return ret;
}

void
EditingContext::check_best_snap (timepos_t const & presnap, timepos_t &test, timepos_t &dist, timepos_t &best) const
{
	EC_LOCAL_TEMPO_SCOPE;

	timepos_t diff = timepos_t (presnap.distance (test).abs ());
	if (diff < dist) {
		dist = diff;
		best = test;
	}

	test = timepos_t::max (test.time_domain()); // reset this so it doesn't get accidentally reused
}

timepos_t
EditingContext::canvas_event_time (GdkEvent const * event, double* pcx, double* pcy) const
{
	EC_LOCAL_TEMPO_SCOPE;

	timepos_t pos (canvas_event_sample (event, pcx, pcy));

	if (time_domain() == Temporal::AudioTime) {
		return pos;
	}

	return timepos_t (pos.beats());
}

samplepos_t
EditingContext::canvas_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
{
	EC_LOCAL_TEMPO_SCOPE;

	double x;
	double y;

	/* event coordinates are already in canvas units */

	if (!gdk_event_get_coords (event, &x, &y)) {
		std::cerr << "!NO c COORDS for event type " << event->type << std::endl;
		return 0;
	}

	if (pcx) {
		*pcx = x;
	}

	if (pcy) {
		*pcy = y;
	}

	/* note that pixel_to_sample_from_event() never returns less than zero, so even if the pixel
	   position is negative (as can be the case with motion events in particular),
	   the sample location is always positive.
	*/

	return pixel_to_sample_from_event (x);
}

uint32_t
EditingContext::count_bars (Beats const & start, Beats const & end) const
{
	EC_LOCAL_TEMPO_SCOPE;

	TempoMapPoints bar_grid;
	TempoMap::SharedPtr tmap (TempoMap::use());
	bar_grid.reserve (4096);
	superclock_t s (tmap->superclock_at (start));
	superclock_t e (tmap->superclock_at (end));
	tmap->get_grid (bar_grid, s, e, 1);
	return bar_grid.size();
}

void
EditingContext::compute_bbt_ruler_scale (samplepos_t lower, samplepos_t upper)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (_session == 0) {
		return;
	}

	Temporal::BBT_Time lower_beat, upper_beat; // the beats at each end of the ruler
	Temporal::TempoMap::SharedPtr tmap (Temporal::TempoMap::use());
	Beats floor_lower_beat = std::max (Beats(), tmap->quarters_at_sample (lower)).round_down_to_beat ();

	if (floor_lower_beat < Temporal::Beats()) {
		floor_lower_beat = Temporal::Beats();
	}

	const samplepos_t beat_before_lower_pos = tmap->sample_at (floor_lower_beat);
	const samplepos_t beat_after_upper_pos = tmap->sample_at ((std::max (Beats(), tmap->quarters_at_sample  (upper)).round_down_to_beat()) + Beats (1, 0));

	lower_beat = Temporal::TempoMap::use()->bbt_at (timepos_t (beat_before_lower_pos));
	upper_beat = Temporal::TempoMap::use()->bbt_at (timepos_t (beat_after_upper_pos));
	uint32_t beats = 0;

	bbt_bar_helper_on = false;
	bbt_bars = 0;

	bbt_ruler_scale =  bbt_show_many;

	const Beats ceil_upper_beat = std::max (Beats(), tmap->quarters_at_sample (upper)).round_up_to_beat() + Beats (1, 0);

	if (ceil_upper_beat == floor_lower_beat) {
		return;
	}

	bbt_bars = count_bars (floor_lower_beat, ceil_upper_beat);

	double ruler_line_granularity = UIConfiguration::instance().get_ruler_granularity ();  //in pixels
	ruler_line_granularity = visible_canvas_width() / (ruler_line_granularity*5);  //fudge factor '5' probably related to (4+1 beats)/measure, I think

	beats = (ceil_upper_beat - floor_lower_beat).get_beats();
	double beat_density = ((beats + 1) * ((double) (upper - lower) / (double) (1 + beat_after_upper_pos - beat_before_lower_pos))) / (float)ruler_line_granularity;

	/* Only show the bar helper if there aren't many bars on the screen */
	if ((bbt_bars < 2) || (beats < 5)) {
		bbt_bar_helper_on = true;
	}

	if (beat_density > 2048) {
		bbt_ruler_scale = bbt_show_many;
	} else if (beat_density > 1024) {
		bbt_ruler_scale = bbt_show_64;
	} else if (beat_density > 256) {
		bbt_ruler_scale = bbt_show_16;
	} else if (beat_density > 64) {
		bbt_ruler_scale = bbt_show_4;
	} else if (beat_density > 16) {
		bbt_ruler_scale =  bbt_show_quarters;
	} else  if (beat_density > 2) {
		bbt_ruler_scale =  bbt_show_eighths;
	} else  if (beat_density > 1) {
		bbt_ruler_scale =  bbt_show_sixteenths;
	} else  if (beat_density > 0.5) {
		bbt_ruler_scale =  bbt_show_thirtyseconds;
	} else  if (beat_density > 0.25) {
		bbt_ruler_scale =  bbt_show_sixtyfourths;
	} else {
		bbt_ruler_scale =  bbt_show_onetwentyeighths;
	}

	/* Now that we know how fine a grid (Ruler) is allowable on this screen, limit it to the coarseness selected by the user */
	/* note: GridType and RulerScale are not the same enums, so it's not a simple mathematical operation */
	int suggested_scale = (int) bbt_ruler_scale;
	GridType gt (grid_type());
	int divs = get_grid_music_divisions(gt);
	if (gt == GridTypeBar) {
		suggested_scale = std::min(suggested_scale, (int) bbt_show_1);
	} else if (gt == GridTypeBeat) {
		suggested_scale = std::min(suggested_scale, (int) bbt_show_quarters);
	}  else if ( divs < 4 ) {
		suggested_scale = std::min(suggested_scale, (int) bbt_show_eighths);
	}  else if ( divs < 8 ) {
		suggested_scale = std::min(suggested_scale, (int) bbt_show_sixteenths);
	} else if ( divs < 16 ) {
		suggested_scale = std::min(suggested_scale, (int) bbt_show_thirtyseconds);
	} else if ( divs < 32 ) {
		suggested_scale = std::min(suggested_scale, (int) bbt_show_sixtyfourths);
	} else {
		suggested_scale = std::min(suggested_scale, (int) bbt_show_onetwentyeighths);
	}

	bbt_ruler_scale = (EditingContext::BBTRulerScale) suggested_scale;
}

Quantize*
EditingContext::get_quantize_op ()
{
	EC_LOCAL_TEMPO_SCOPE;

	if (!quantize_dialog) {
		quantize_dialog = new QuantizeDialog (*transient_parent (), *this);
	}

	quantize_dialog->present ();
	int r = quantize_dialog->run ();
	quantize_dialog->hide ();


	if (r != Gtk::RESPONSE_OK) {
		return nullptr;
	}

	return new Quantize (quantize_dialog->snap_start(),
	                     quantize_dialog->snap_end(),
	                     quantize_dialog->start_grid_size(),
	                     quantize_dialog->end_grid_size(),
	                     quantize_dialog->strength(),
	                     quantize_dialog->swing(),
	                     quantize_dialog->threshold());
}

Strum*
EditingContext::get_strum_op (bool forward, bool fine)
{
	EC_LOCAL_TEMPO_SCOPE;

	return new Strum (forward, fine);
}

timecnt_t
EditingContext::relative_distance (timepos_t const & origin, timecnt_t const & duration, Temporal::TimeDomain domain)
{
	EC_LOCAL_TEMPO_SCOPE;

	return Temporal::TempoMap::use()->convert_duration (duration, origin, domain);
}

/** Snap a time offset within our region using the current snap settings.
 *  @param x Time offset from this region's position.
 *  @param ensure_snap whether to ignore snap_mode (in the case of SnapOff) and magnetic snap.
 *  Used when inverting snap mode logic with key modifiers, or snap distance calculation.
 *  @return Snapped time offset from this region's position.
 */
timecnt_t
EditingContext::snap_relative_time_to_relative_time (timepos_t const & origin, timecnt_t const & x, bool ensure_snap) const
{
	EC_LOCAL_TEMPO_SCOPE;

	/* x is relative to origin, convert it to global absolute time */
	timepos_t const session_pos = origin + x;

	/* try a snap in either direction */
	timepos_t snapped = session_pos;
	snap_to (snapped, Temporal::RoundNearest, SnapToAny_Visual, ensure_snap);

	/* if we went off the beginning of the region, snap forwards */
	if (snapped < origin) {
		snapped = session_pos;
		snap_to (snapped, Temporal::RoundUpAlways, SnapToAny_Visual, ensure_snap);
	}

	/* back to relative */
	return origin.distance (snapped);
}

bool
EditingContext::typed_event (ArdourCanvas::Item* item, GdkEvent *event, ItemType type)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (!session () || session()->loading () || session()->deletion_in_progress ()) {
		return false;
	}

	bool ret = false;

	switch (event->type) {
	case GDK_BUTTON_PRESS:
	case GDK_2BUTTON_PRESS:
	case GDK_3BUTTON_PRESS:
		ret = button_press_handler (item, event, type);
		break;
	case GDK_BUTTON_RELEASE:
		ret = button_release_handler (item, event, type);
		break;
	case GDK_MOTION_NOTIFY:
		ret = motion_handler (item, event);
		break;

	case GDK_ENTER_NOTIFY:
		ret = enter_handler (item, event, type);
		break;

	case GDK_LEAVE_NOTIFY:
		ret = leave_handler (item, event, type);
		break;

	case GDK_KEY_PRESS:
		ret = key_press_handler (item, event, type);
		break;

	case GDK_KEY_RELEASE:
		ret = key_release_handler (item, event, type);
		break;

	default:
		break;
	}
	return ret;
}

void
EditingContext::popup_note_context_menu (ArdourCanvas::Item* item, GdkEvent* event)
{
	EC_LOCAL_TEMPO_SCOPE;

	using namespace Menu_Helpers;

	NoteBase* note = reinterpret_cast<NoteBase*>(item->get_data("notebase"));
	if (!note) {
		return;
	}

	/* We need to get the selection here and pass it to the operations, since
	   popping up the menu will cause a region leave event which clears
	   entered_regionview. */

	MidiView&       mrv = note->midi_view();
	const uint32_t sel_size = mrv.selection_size ();
	MidiViews mvs (midiviews_from_region_selection (region_selection ()));

	if (std::find (mvs.begin(), mvs.end(), &mrv) == mvs.end()) {
		mvs.push_back (&mrv);
	}

	MenuList& items = _note_context_menu.items();
	items.clear();

	if (sel_size > 0) {
		items.push_back (MenuElem(_("Delete"), sigc::mem_fun(mrv, &MidiView::delete_selection)));
	}

	items.push_back(MenuElem(_("Edit..."), sigc::bind(sigc::mem_fun(*this, &EditingContext::edit_notes), &mrv)));
	items.push_back(MenuElem(_("Transpose..."),  sigc::bind(sigc::mem_fun(*this, &EditingContext::transpose_regions), mvs)));
	items.push_back(MenuElem(_("Legatize"), sigc::bind(sigc::mem_fun(*this, &EditingContext::legatize_regions), mvs, false)));
	if (sel_size < 2) {
		items.back().set_sensitive (false);
	}
	items.push_back(MenuElem(_("Quantize..."), sigc::bind(sigc::mem_fun(*this, &EditingContext::quantize_regions), mvs)));
	items.push_back(MenuElem(_("Remove Overlap"), sigc::bind(sigc::mem_fun(*this, &EditingContext::legatize_regions), mvs, true)));
	if (sel_size < 2) {
		items.back().set_sensitive (false);
	}
	items.push_back(MenuElem(_("Transform..."), sigc::bind(sigc::mem_fun(*this, &EditingContext::transform_regions), mvs)));
	items.push_back (SeparatorElem());
	items.push_back(MenuElem(_("Strum Forward"), sigc::bind(sigc::mem_fun(*this, &EditingContext::strum_notes), mvs, true)));
	items.push_back(MenuElem(_("Strum Backward"), sigc::bind(sigc::mem_fun(*this, &EditingContext::strum_notes), mvs, false)));

	_note_context_menu.popup (event->button.button, event->button.time);
}

XMLNode*
EditingContext::button_settings () const
{
	EC_LOCAL_TEMPO_SCOPE;

	XMLNode* settings = ARDOUR_UI::instance()->editor_settings();
	XMLNode* node = find_named_node (*settings, X_("Buttons"));

	if (!node) {
		node = new XMLNode (X_("Buttons"));
	}

	return node;
}

EditingContext::MidiViews
EditingContext::filter_to_unique_midi_region_views (RegionSelection const & rs) const
{
	EC_LOCAL_TEMPO_SCOPE;

	return filter_to_unique_midi_region_views (midiviews_from_region_selection (rs));
}

EditingContext::MidiViews
EditingContext::filter_to_unique_midi_region_views (MidiViews const & mvs) const
{
	EC_LOCAL_TEMPO_SCOPE;

	typedef std::pair<std::shared_ptr<MidiSource>,timepos_t> MapEntry;
	std::set<MapEntry> single_region_set;

	MidiViews views;

	/* build a list of regions that are unique with respect to their source
	 * and start position. Note: this is non-exhaustive... if someone has a
	 * non-forked copy of a MIDI region and then suitably modifies it, this
	 * will still put both regions into the list of things to be acted
	 * upon.
	 *
	 * Solution: user should not select both regions, or should fork one of them.
	 */

	for (auto const & mv : mvs) {

		MapEntry entry = make_pair (mv->midi_region()->midi_source(), mv->midi_region()->start());

		if (single_region_set.insert (entry).second) {
			views.push_back (mv);
		}
	}

	return views;
}

EditingContext::MidiViews
EditingContext::midiviews_from_region_selection (RegionSelection const & rs) const
{
	EC_LOCAL_TEMPO_SCOPE;

	MidiViews views;

	for (auto & rv : rs) {
		MidiView* mrv = dynamic_cast<MidiView*> (rv);
		if (mrv) {
			views.push_back (mrv);
		}
	}

	return views;
}

void
EditingContext::quantize_region ()
{
	EC_LOCAL_TEMPO_SCOPE;

	if (_session) {
		quantize_regions (midiviews_from_region_selection (region_selection()));
	}
}

void
EditingContext::quantize_regions (const MidiViews& rs)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (rs.empty()) {
		return;
	}

	Quantize* quant = get_quantize_op ();

	if (!quant) {
		return;
	}

	if (!quant->empty()) {
		apply_midi_note_edit_op (*quant, rs);
	}

	delete quant;
}

void
EditingContext::legatize_region (bool shrink_only)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (_session) {
		legatize_regions (midiviews_from_region_selection (region_selection ()), shrink_only);
	}
}

void
EditingContext::legatize_regions (const MidiViews& rs, bool shrink_only)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (rs.empty()) {
		return;
	}

	Legatize legatize (shrink_only);
	apply_midi_note_edit_op (legatize, rs);
}

void
EditingContext::transform_region ()
{
	EC_LOCAL_TEMPO_SCOPE;

	if (_session) {
		transform_regions (midiviews_from_region_selection (region_selection ()));
	}
}

void
EditingContext::transform_regions (const MidiViews& rs)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (rs.empty()) {
		return;
	}

	TransformDialog td (*transient_parent ());

	td.present();
	const int r = td.run();
	td.hide();

	if (r == Gtk::RESPONSE_OK) {
		Transform transform(td.get());
		apply_midi_note_edit_op(transform, rs);
	}
}

void
EditingContext::add_semitone_interval (int semitones)
{
	EC_LOCAL_TEMPO_SCOPE;

	MidiViews mvs (midiviews_from_region_selection (region_selection ()));

	if (mvs.empty()) {
		return;
	}

	if (mvs.size() == 1) {
		mvs.front()->add_semitone_interval (semitones);
		return;
	}

	begin_reversible_command (string_compose (_("Add note interval of %1 semitones"), semitones));
	for (auto & mv : mvs) {
		mv->add_semitone_interval (semitones, true);
	}
	commit_reversible_command ();
}

void
EditingContext::transpose_region ()
{
	EC_LOCAL_TEMPO_SCOPE;

	if (_session) {
		transpose_regions (midiviews_from_region_selection (region_selection ()));
	}
}

void
EditingContext::transpose_regions (const MidiViews& rs)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (rs.empty()) {
		return;
	}

	TransposeDialog d (*transient_parent ());
	int const r = d.run ();

	if (r == RESPONSE_ACCEPT) {
		Transpose transpose(d.semitones ());
		apply_midi_note_edit_op (transpose, rs);
	}
}

void
EditingContext::strum_notes (const MidiViews& rs, bool forward)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (rs.empty()) {
		return;
	}

	Strum strum (forward, false);
	apply_midi_note_edit_op (strum, rs);
}

void
EditingContext::edit_notes (MidiView* mrv)
{
	EC_LOCAL_TEMPO_SCOPE;

	MidiView::Selection const & s = mrv->selection();

	if (s.empty ()) {
		return;
	}

	EditNoteDialog* d = new EditNoteDialog (*transient_parent (), mrv, s);
	d->show_all ();

	d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &EditingContext::note_edit_done), d));
}

void
EditingContext::note_edit_done (int r, EditNoteDialog* d)
{
	EC_LOCAL_TEMPO_SCOPE;

	d->done (r);
	delete d;
}

PBD::Command*
EditingContext::apply_midi_note_edit_op_to_region (MidiOperator& op, MidiView& mrv)
{
	EC_LOCAL_TEMPO_SCOPE;

	Evoral::Sequence<Temporal::Beats>::Notes selected;
	mrv.selection_as_notelist (selected, true);

	if (selected.empty()) {
		return nullptr;
	}

	std::vector<Evoral::Sequence<Temporal::Beats>::Notes> v;
	v.push_back (selected);

	timepos_t pos = mrv.midi_region()->source_position();

	return op (mrv.midi_region()->model(), pos.beats(), v);
}

void
EditingContext::apply_midi_note_edit_op (MidiOperator& op, const RegionSelection& rs)
{
	EC_LOCAL_TEMPO_SCOPE;

	apply_midi_note_edit_op (op, midiviews_from_region_selection (rs));
}

void
EditingContext::apply_midi_note_edit_op (MidiOperator& op, const MidiViews& rs)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (rs.empty()) {
		return;
	}

	bool in_command = false;

	std::vector<MidiView*> views = filter_to_unique_midi_region_views (rs);

	for (auto & mv : views) {

		Command* cmd = apply_midi_note_edit_op_to_region (op, *mv);
		if (cmd) {
			if (!in_command) {
				begin_reversible_command (op.name ());
				in_command = true;
			}
			(*cmd)();
			add_command (cmd);
			}
	}

	if (in_command) {
		commit_reversible_command ();
		_session->set_dirty ();
	}
}

double
EditingContext::horizontal_position () const
{
	EC_LOCAL_TEMPO_SCOPE;

	return horizontal_adjustment.get_value();
}

void
EditingContext::set_horizontal_position (double pixel)
{
	EC_LOCAL_TEMPO_SCOPE;

	pixel = std::max (0., pixel);

	_leftmost_sample = (samplepos_t) floor (pixel * samples_per_pixel);
	horizontal_adjustment.set_value (pixel);
}

Gdk::Cursor*
EditingContext::get_canvas_cursor () const
{
	EC_LOCAL_TEMPO_SCOPE;

	Glib::RefPtr<Gdk::Window> win = get_canvas_viewport()->get_window();

	if (win) {
		return _cursors->from_gdk_cursor (gdk_window_get_cursor (win->gobj()));
	}

	return nullptr;
}

void
EditingContext::set_canvas_cursor (Gdk::Cursor* cursor)
{
	EC_LOCAL_TEMPO_SCOPE;

	Glib::RefPtr<Gdk::Window> win = get_canvas()->get_window();

	if (win && !_cursors->is_invalid (cursor)) {
		/* glibmm 2.4 doesn't allow null cursor pointer because it uses
		   a Gdk::Cursor& as the argument to Gdk::Window::set_cursor().
		   But a null pointer just means "use parent window cursor",
		   and so should be allowed. Gtkmm 3.x has fixed this API.

		   For now, drop down and use C API
		*/
		gdk_window_set_cursor (win->gobj(), cursor ? cursor->gobj() : 0);
		gdk_flush ();
	}
}

void
EditingContext::pack_draw_box (bool with_channel)
{
	EC_LOCAL_TEMPO_SCOPE;

	/* Draw  - these MIDI tools are only visible when in Draw mode */
	draw_box.set_spacing (2);
	draw_box.pack_start (*manage (new Label (_("Len:"))), false, false);
	draw_box.pack_start (draw_length_selector, false, false, 4);
	if (with_channel) {
		draw_box.pack_start (*manage (new Label (S_("MIDI|Ch:"))), false, false);
		draw_box.pack_start (draw_channel_selector, false, false, 4);
	}
	draw_box.pack_start (*manage (new Label (_("Vel:"))), false, false);
	draw_box.pack_start (draw_velocity_selector, false, false, 4);

	draw_length_selector.set_name ("mouse mode button");
	draw_velocity_selector.set_name ("mouse mode button");
	draw_channel_selector.set_name ("mouse mode button");

	draw_velocity_selector.set_sizing_text (_("Auto"));
	draw_channel_selector.set_sizing_text (_("Auto"));

	draw_velocity_selector.disable_scrolling ();
	draw_velocity_selector.signal_scroll_event().connect (sigc::mem_fun(*this, &EditingContext::on_velocity_scroll_event), false);

	draw_box.show_all_children ();
	draw_box.set_no_show_all ();
}

void
EditingContext::pack_snap_box ()
{
	EC_LOCAL_TEMPO_SCOPE;

	snap_box.set_spacing (2);
	snap_box.pack_start (snap_mode_button, false, false);
	snap_box.pack_start (grid_type_selector, false, false);
}

void
EditingContext::bind_mouse_mode_buttons ()
{
	EC_LOCAL_TEMPO_SCOPE;

	RefPtr<Action> act;

	act = ActionManager::get_action ((_name + X_("Editing")).c_str(), X_("temporal-zoom-in"));
	act->set_tooltip (_("Zoom In"));
	zoom_in_button.set_related_action (act);

	act = ActionManager::get_action ((_name + X_("Editing")).c_str(), X_("temporal-zoom-out"));
	act->set_tooltip (_("Zoom Out"));
	zoom_out_button.set_related_action (act);

	follow_playhead_button.set_related_action (follow_playhead_action);

	act = ActionManager::get_action (X_("Transport"), X_("ToggleFollowEdits"));
	follow_edits_button.set_related_action (act);

	mouse_move_button.set_related_action (mouse_mode_actions[Editing::MouseObject]);
	mouse_move_button.set_icon (ArdourWidgets::ArdourIcon::ToolGrab);
	mouse_move_button.set_name ("mouse mode button");

	mouse_select_button.set_related_action (mouse_mode_actions[Editing::MouseRange]);
	mouse_select_button.set_icon (ArdourWidgets::ArdourIcon::ToolRange);
	mouse_select_button.set_name ("mouse mode button");

	mouse_draw_button.set_related_action (mouse_mode_actions[Editing::MouseDraw]);
	mouse_draw_button.set_icon (ArdourWidgets::ArdourIcon::ToolDraw);
	mouse_draw_button.set_name ("mouse mode button");

	mouse_timefx_button.set_related_action (mouse_mode_actions[Editing::MouseTimeFX]);
	mouse_timefx_button.set_icon (ArdourWidgets::ArdourIcon::ToolStretch);
	mouse_timefx_button.set_name ("mouse mode button");

	mouse_grid_button.set_related_action (mouse_mode_actions[Editing::MouseGrid]);
	mouse_grid_button.set_icon (ArdourWidgets::ArdourIcon::ToolGrid);
	mouse_grid_button.set_name ("mouse mode button");

	mouse_content_button.set_related_action (mouse_mode_actions[Editing::MouseContent]);
	mouse_content_button.set_icon (ArdourWidgets::ArdourIcon::ToolContent);
	mouse_content_button.set_name ("mouse mode button");

	mouse_cut_button.set_related_action (mouse_mode_actions[Editing::MouseCut]);
	mouse_cut_button.set_icon (ArdourWidgets::ArdourIcon::ToolCut);
	mouse_cut_button.set_name ("mouse mode button");

	set_tooltip (mouse_move_button, _("Grab Mode (select/move objects)"));
	set_tooltip (mouse_cut_button, _("Cut Mode (split regions)"));
	set_tooltip (mouse_select_button, _("Range Mode (select time ranges)"));
	set_tooltip (mouse_grid_button, _("Grid Mode (edit tempo-map, drag/drop music-time grid)"));
	set_tooltip (mouse_draw_button, _("Draw Mode (draw and edit gain/notes/automation)"));
	set_tooltip (mouse_timefx_button, _("Stretch Mode (time-stretch audio and midi regions, preserving pitch)"));
	set_tooltip (mouse_content_button, _("Internal Edit Mode (edit notes and automation points)"));
}

Editing::MouseMode
EditingContext::current_mouse_mode() const
{
	EC_LOCAL_TEMPO_SCOPE;

	for (auto & [mode,action] : mouse_mode_actions) {
		if (action->get_active()) {
			return mode;
		}
	}

	return MouseObject;
}

void
EditingContext::set_mouse_mode (MouseMode m, bool force)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (_drags->active ()) {
		return;
	}

	if (force && mouse_mode_actions[m]->get_active()) {
		mouse_mode_actions[m]->set_active (false);
	}

	mouse_mode_actions[m]->set_active (true);
}

bool
EditingContext::on_velocity_scroll_event (GdkEventScroll* ev)
{
	EC_LOCAL_TEMPO_SCOPE;

	int v = PBD::atoi (draw_velocity_selector.get_text ());
	switch (ev->direction) {
		case GDK_SCROLL_DOWN:
			v = std::min (127, v + 1);
			break;
		case GDK_SCROLL_UP:
			v = std::max (1, v - 1);
			break;
		default:
			return false;
	}
	set_draw_velocity (v);
	return true;
}

void
EditingContext::set_common_editing_state (XMLNode const & node)
{
	EC_LOCAL_TEMPO_SCOPE;

	double z;
	if (node.get_property ("zoom", z)) {
		/* older versions of ardour used floating point samples_per_pixel */
		reset_zoom (llrintf (z));
	} else {
		reset_zoom (samples_per_pixel);
	}

	GridType grid_type;
	if (!node.get_property ("grid-type", grid_type)) {
		grid_type = GridTypeNone;
	}
	set_grid_type (grid_type);

	SnapMode sm;
	if (!node.get_property ("snap-mode", sm)) {
		sm = SnapOff;
	}
	set_snap_mode (sm);

	node.get_property ("internal-grid-type", internal_grid_type);
	node.get_property ("internal-snap-mode", internal_snap_mode);
	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;
	if (node.get_property ("mouse-mode", mm_str)) {
		MouseMode m = str2mousemode(mm_str);
		set_mouse_mode (m, true);
	} else {
		set_mouse_mode (MouseObject, true);
	}

	samplepos_t lf_pos;
	if (node.get_property ("left-frame", lf_pos)) {
		if (lf_pos < 0) {
			lf_pos = 0;
		}
		reset_x_origin (lf_pos);
	}

	int v;
	if (node.get_property ("draw-velocity", v)) {
		if (draw_velocity_actions.find (v) != draw_velocity_actions.end()) {
			draw_velocity_actions[v]->set_active (true);
		}
	}

	if (node.get_property ("draw-channel", v)) {
		if (draw_channel_actions.find (v) != draw_channel_actions.end()) {
			draw_channel_actions[v]->set_active (true);
		}
	}
}

void
EditingContext::get_common_editing_state (XMLNode& node) const
{
	EC_LOCAL_TEMPO_SCOPE;

	node.set_property ("zoom", samples_per_pixel);
	node.set_property ("grid-type", grid_type());
	node.set_property ("snap-mode", snap_mode());
	node.set_property ("internal-grid-type", internal_grid_type);
	node.set_property ("internal-snap-mode", internal_snap_mode);
	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 ("left-frame", _leftmost_sample);
	node.set_property ("draw-velocity", draw_velocity ());
	node.set_property ("draw-channel", draw_channel ());
}

bool
EditingContext::snap_mode_button_clicked (GdkEventButton* ev)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (ev->button != 3) {
		cycle_snap_mode();
		return true;
	}

	RCOptionEditor* rc_option_editor = ARDOUR_UI::instance()->get_rc_option_editor();
	if (rc_option_editor) {
		ARDOUR_UI::instance()->show_tabbable (rc_option_editor);
		rc_option_editor->set_current_page (_("Editor/Snap"));
	}

	return true;
}

void
EditingContext::ensure_visual_change_idle_handler ()
{
	EC_LOCAL_TEMPO_SCOPE;

	if (pending_visual_change.idle_handler_id < 0) {
		/* see comment in add_to_idle_resize above. */
		pending_visual_change.idle_handler_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE + 10, _idle_visual_changer, this, NULL);
		pending_visual_change.being_handled = false;
	}
}

int
EditingContext::_idle_visual_changer (void* arg)
{
	return static_cast<EditingContext*>(arg)->idle_visual_changer ();
}

int
EditingContext::idle_visual_changer ()
{
	EC_LOCAL_TEMPO_SCOPE;

	pending_visual_change.idle_handler_id = -1;

	if (pending_visual_change.pending == 0) {
		return G_SOURCE_REMOVE;
	}

	/* set_horizontal_position() below (and maybe other calls) call
	   gtk_main_iteration(), so it's possible that a signal will be handled
	   half-way through this method.  If this signal wants an
	   idle_visual_changer we must schedule another one after this one, soa
	   mark the idle_handler_id as -1 here to allow that.  Also make a note
	   that we are doing the visual change, so that changes in response to
	   super-rapid-screen-update can be dropped if we are still processing
	   the last one.
	*/

	if (visual_change_queued) {
		return G_SOURCE_REMOVE;
	}

	pending_visual_change.being_handled = true;

	VisualChange vc = pending_visual_change;

	pending_visual_change.pending = (VisualChange::Type) 0;

	visual_changer (vc);

	pending_visual_change.being_handled = false;

	visual_change_queued = true;

	return G_SOURCE_REMOVE; /* this is always a one-shot call */
}


/** Queue up a change to the Editor viewport x origin.
 *  @param sample New x origin.
 */
void
EditingContext::reset_x_origin (samplepos_t sample)
{
	EC_LOCAL_TEMPO_SCOPE;

	pending_visual_change.add (VisualChange::TimeOrigin);
	pending_visual_change.time_origin = sample;
	ensure_visual_change_idle_handler ();
}

void
EditingContext::reset_y_origin (double y)
{
	EC_LOCAL_TEMPO_SCOPE;

	pending_visual_change.add (VisualChange::YOrigin);
	pending_visual_change.y_origin = y;
	ensure_visual_change_idle_handler ();
}

void
EditingContext::reset_zoom (samplecnt_t spp)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (_track_canvas_width <= 0) {
		return;
	}

	std::pair<timepos_t, timepos_t> ext = max_zoom_extent();
	samplecnt_t p = (ext.second.samples() - ext.first.samples())  / _track_canvas_width;

	if (spp == 0) {
		/* less samples than pixels */
		spp = 1;
	}

	if (p == 0) {
		/* Less samples to display than there are pixels, use spp = 1 */
		p = 1;
	}
	samplecnt_t max_extents_pp = max_extents_scale() * p;

	if (spp > max_extents_pp) {
		spp = max_extents_pp;
	}

	if (spp == samples_per_pixel) {
		return;
	}

	assert (spp != 0);

	pending_visual_change.add (VisualChange::ZoomLevel);
	pending_visual_change.samples_per_pixel = spp;
	ensure_visual_change_idle_handler ();
}

void
EditingContext::pre_render ()
{
	EC_LOCAL_TEMPO_SCOPE;

	visual_change_queued = false;

	if (pending_visual_change.pending != 0) {
		ensure_visual_change_idle_handler();
	}
}

/* Convenience functions to slightly reduce verbosity when registering actions */

RefPtr<Action>
EditingContext::reg_sens (RefPtr<ActionGroup> group, char const * name, char const * label, sigc::slot<void> slot)
{
	RefPtr<Action> act = ActionManager::register_action (group, name, label, slot);
	assert(act);
	ActionManager::session_sensitive_actions.push_back (act);
	return act;
}

Glib::RefPtr<ToggleAction>
EditingContext::toggle_reg_sens (RefPtr<ActionGroup> group, char const * name, char const * label, sigc::slot<void> slot)
{
	RefPtr<ToggleAction> act = ActionManager::register_toggle_action (group, name, label, slot);
	assert(act);
	ActionManager::session_sensitive_actions.push_back (act);
	return act;
}

Glib::RefPtr<Gtk::RadioAction>
EditingContext::radio_reg_sens (RefPtr<ActionGroup> action_group, RadioAction::Group& radio_group, char const * name, char const * label, sigc::slot<void> slot)
{
	RefPtr<RadioAction> act = ActionManager::register_radio_action (action_group, radio_group, name, label, slot);
	assert(act);
	ActionManager::session_sensitive_actions.push_back (act);
	return act;
}

void
EditingContext::update_undo_redo_actions (PBD::UndoHistory const & history)
{
	EC_LOCAL_TEMPO_SCOPE;

	string label;

	if (undo_action) {
		if (history.undo_depth() == 0) {
			label = S_("Command|Undo");
			undo_action->set_sensitive(false);
		} else {
			label = string_compose(S_("Command|Undo (%1)"), history.next_undo());
			undo_action->set_sensitive(true);
		}
		undo_action->property_label() = label;
	}

	if (redo_action) {
		if (history.redo_depth() == 0) {
			label = _("Redo");
			redo_action->set_sensitive (false);
		} else {
			label = string_compose(_("Redo (%1)"), history.next_redo());
			redo_action->set_sensitive (true);
		}
		redo_action->property_label() = label;
	}
}

int32_t
EditingContext::get_grid_beat_divisions (GridType gt) const
{
	EC_LOCAL_TEMPO_SCOPE;

	switch (gt) {
	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 GridTypeTimecode:   return 0;
	case GridTypeMinSec:     return 0;
	case GridTypeCDFrame:    return 0;
	default:                 return 0;
	}
	return 0;
}

/**
 * Return the musical grid divisions
 *
 * @param event_state the current keyboard modifier mask.
 * @return Music grid beat divisions
 */
int32_t
EditingContext::get_grid_music_divisions (Editing::GridType gt) const
{
	EC_LOCAL_TEMPO_SCOPE;

	return get_grid_beat_divisions (gt);
}

Temporal::Beats
EditingContext::get_draw_length_as_beats (bool& success, timepos_t const & position) const
{
	EC_LOCAL_TEMPO_SCOPE;
	return get_a_grid_type_as_beats (draw_length() == DRAW_LEN_AUTO ? grid_type() : draw_length(), success, position);
}

Temporal::Beats
EditingContext::get_grid_type_as_beats (bool& success, timepos_t const & position) const
{
	EC_LOCAL_TEMPO_SCOPE;
	return get_a_grid_type_as_beats (grid_type(), success, position);

}

Temporal::Beats
EditingContext::get_a_grid_type_as_beats (GridType gtype, bool& success, timepos_t const & position) const
{
	EC_LOCAL_TEMPO_SCOPE;

	success = true;

	int32_t const divisions = get_grid_beat_divisions (gtype);
	/* Beat (+1), and Bar (-1) are handled below */
	if (divisions > 1) {
		/* grid divisions are divisions of a 1/4 note */
		return Temporal::Beats::ticks(Temporal::Beats::PPQN / divisions);
	}

	TempoMap::SharedPtr tmap (TempoMap::use());

	switch (gtype) {
	case GridTypeBar:
		if (_session) {
			const Meter& m = tmap->meter_at (position);
			return Temporal::Beats::from_double ((4.0 * m.divisions_per_bar()) / m.note_value());
		}
		break;

	case GridTypeBeat:
		return Temporal::Beats::from_double (tmap->meter_at (position).note_value() / 4.0);

	case GridTypeBeatDiv2:
		return Temporal::Beats::from_double (tmap->meter_at (position).note_value() / 8.0);

	case GridTypeBeatDiv4:
		return Temporal::Beats::from_double (tmap->meter_at (position).note_value() / 16.0);

	case GridTypeBeatDiv8:
		return Temporal::Beats::from_double (tmap->meter_at (position).note_value() / 32.0);

	case GridTypeBeatDiv16:
		return Temporal::Beats::from_double (tmap->meter_at (position).note_value() / 64.0);

	case GridTypeBeatDiv32:
		return Temporal::Beats::from_double (tmap->meter_at (position).note_value() / 128.0);

	case GridTypeBeatDiv3:  //Triplet eighth
		return Temporal::Beats::from_double (tmap->meter_at (position).note_value() / 12.0);


	case GridTypeBeatDiv6:
		return Temporal::Beats::from_double (tmap->meter_at (position).note_value() / 24.0);

	case GridTypeBeatDiv12:
		return Temporal::Beats::from_double (tmap->meter_at (position).note_value() / 48.0);

	case GridTypeBeatDiv24:
		return Temporal::Beats::from_double (tmap->meter_at (position).note_value() / 96.0);

	case GridTypeBeatDiv5:  //Quintuplet //eighth
		return Temporal::Beats::from_double (tmap->meter_at (position).note_value() / 20.0);

	case GridTypeBeatDiv10:
		return Temporal::Beats::from_double (tmap->meter_at (position).note_value() / 40.0);

	case GridTypeBeatDiv20:
		return Temporal::Beats::from_double (tmap->meter_at (position).note_value() / 80.0);

	case GridTypeBeatDiv7:  //Septuplet eighth
		return Temporal::Beats::from_double (tmap->meter_at (position).note_value() / 28.0);

	case GridTypeBeatDiv14:
		return Temporal::Beats::from_double (tmap->meter_at (position).note_value() / 56.0);

	case GridTypeBeatDiv28:
		return Temporal::Beats::from_double (tmap->meter_at (position).note_value() / 112.0);

	default:
		success = false;
		break;
	}

	return Temporal::Beats();
}

void
EditingContext::select_automation_line (GdkEventButton* event, ArdourCanvas::Item* item, ARDOUR::SelectionOperation op)
{
	EC_LOCAL_TEMPO_SCOPE;

	AutomationLine* al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
	std::list<Selectable*> selectables;
	double mx = event->x;
	double my = event->y;
	bool press = (event->type == GDK_BUTTON_PRESS);

	al->grab_item().canvas_to_item (mx, my);

	uint32_t before, after;
	samplecnt_t const  where = (samplecnt_t) floor (canvas_to_timeline (mx) * samples_per_pixel);

	if (!al || !al->control_points_adjacent (where, before, after)) {
		return;
	}

	selectables.push_back (al->nth (before));
	selectables.push_back (al->nth (after));

	switch (op) {
	case SelectionSet:
		if (press) {
			selection->set (selectables);
			_mouse_changed_selection = true;
		}
		break;
	case SelectionAdd:
		if (press) {
			selection->add (selectables);
			_mouse_changed_selection = true;
		}
		break;
	case SelectionToggle:
		if (press) {
			selection->toggle (selectables);
			_mouse_changed_selection = true;
		}
		break;
	case SelectionExtend:
		/* XXX */
		break;
	case SelectionRemove:
		/* not relevant */
		break;
	}
}

/** Reset all selected points to the relevant default value */
void
EditingContext::reset_point_selection ()
{
	EC_LOCAL_TEMPO_SCOPE;

	for (PointSelection::iterator i = selection->points.begin(); i != selection->points.end(); ++i) {
		ARDOUR::AutomationList::iterator j = (*i)->model ();
		(*j)->value = (*i)->line().the_list()->descriptor ().normal;
	}
}

void
EditingContext::choose_canvas_cursor_on_entry (ItemType type)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (_drags->active()) {
		return;
	}

	Gdk::Cursor* cursor = which_canvas_cursor (type);

	if (!_cursors->is_invalid (cursor)) {
		// Push a new enter context
		set_canvas_cursor (cursor);
	}
}

void
EditingContext::play_note_selection_clicked ()
{
	EC_LOCAL_TEMPO_SCOPE;

	UIConfiguration::instance().set_sound_midi_notes (!UIConfiguration::instance().get_sound_midi_notes());
}

void
EditingContext::cycle_zoom_focus ()
{
	EC_LOCAL_TEMPO_SCOPE;

	switch (zoom_focus()) {
	case ZoomFocusLeft:
		set_zoom_focus (ZoomFocusRight);
		break;
	case ZoomFocusRight:
		set_zoom_focus (ZoomFocusCenter);
		break;
	case ZoomFocusCenter:
		set_zoom_focus (ZoomFocusPlayhead);
		break;
	case ZoomFocusPlayhead:
		set_zoom_focus (ZoomFocusMouse);
		break;
	case ZoomFocusMouse:
		set_zoom_focus (ZoomFocusEdit);
		break;
	case ZoomFocusEdit:
		set_zoom_focus (ZoomFocusLeft);
		break;
	}
}

void
EditingContext::temporal_zoom_step_mouse_focus_scale (bool zoom_out, double scale)
{
	EC_LOCAL_TEMPO_SCOPE;

	ZoomFocus old_zf (zoom_focus());
	PBD::Unwinder<bool> uw (temporary_zoom_focus_change, true);
	set_zoom_focus (Editing::ZoomFocusMouse);
	temporal_zoom_step_scale (zoom_out, scale);
	set_zoom_focus (old_zf);
}

void
EditingContext::temporal_zoom_step_mouse_focus (bool zoom_out)
{
	EC_LOCAL_TEMPO_SCOPE;

	temporal_zoom_step_mouse_focus_scale (zoom_out, 2.0);
}

void
EditingContext::temporal_zoom_step (bool zoom_out)
{
	EC_LOCAL_TEMPO_SCOPE;

	temporal_zoom_step_scale (zoom_out, 2.0);
}

void
EditingContext::temporal_zoom_step_scale (bool zoom_out, double scale)
{
	EC_LOCAL_TEMPO_SCOPE;

	ENSURE_GUI_THREAD (*this, &EditingContext::temporal_zoom_step, zoom_out, scale)

	samplecnt_t nspp = samples_per_pixel;

	if (zoom_out) {
		nspp *= scale;
		if (nspp == samples_per_pixel) {
			nspp *= 2.0;
		}
	} else {
		nspp /= scale;
		if (nspp == samples_per_pixel) {
			nspp /= 2.0;
		}
	}

	//zoom-behavior-tweaks
	//limit our maximum zoom to the session gui extents value
	std::pair<timepos_t, timepos_t> ext = max_zoom_extent();
	samplecnt_t session_extents_pp = (ext.second.samples() - ext.first.samples())  / _track_canvas_width;
	if (nspp > session_extents_pp) {
		nspp = session_extents_pp;
	}

	temporal_zoom (nspp);
}

void
EditingContext::temporal_zoom (samplecnt_t spp)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (!_session) {
		return;
	}

	samplepos_t current_page = current_page_samples();
	samplepos_t current_leftmost = _leftmost_sample;
	samplepos_t current_rightmost;
	samplepos_t current_center;
	samplepos_t new_page_size;
	samplepos_t half_page_size;
	samplepos_t leftmost_after_zoom = 0;
	samplepos_t where;
	bool in_track_canvas;
	bool use_mouse_sample = true;
	samplecnt_t nspp;
	double l;

	if (spp == samples_per_pixel) {
		return;
	}

	// Imposing an arbitrary limit to zoom out as too much zoom out produces
	// segfaults for lack of memory. If somebody decides this is not high enough I
	// believe it can be raisen to higher values but some limit must be in place.
	//
	// This constant represents 1 day @ 48kHz on a 1600 pixel wide display
	// all of which is used for the editor track displays. The whole day
	// would be 4147200000 samples, so 2592000 samples per pixel.

	nspp = std::min (spp, (samplecnt_t) 2592000);
	nspp = std::max ((samplecnt_t) 1, nspp);

	new_page_size = (samplepos_t) floor (_track_canvas_width * nspp);
	half_page_size = new_page_size / 2;

	Editing::ZoomFocus zf = effective_zoom_focus();

	switch (zf) {
	case ZoomFocusLeft:
		leftmost_after_zoom = current_leftmost;
		break;

	case ZoomFocusRight:
		current_rightmost = _leftmost_sample + current_page;
		if (current_rightmost < new_page_size) {
			leftmost_after_zoom = 0;
		} else {
			leftmost_after_zoom = current_rightmost - new_page_size;
		}
		break;

	case ZoomFocusCenter:
		current_center = current_leftmost + (current_page/2);
		if (current_center < half_page_size) {
			leftmost_after_zoom = 0;
		} else {
			leftmost_after_zoom = current_center - half_page_size;
		}
		break;

	case ZoomFocusPlayhead:
		/* centre playhead */
		l = _session->transport_sample() - (new_page_size * 0.5);

		if (l < 0) {
			leftmost_after_zoom = 0;
		} else if (l > max_samplepos) {
			leftmost_after_zoom = max_samplepos - new_page_size;
		} else {
			leftmost_after_zoom = (samplepos_t) l;
		}
		break;

	case ZoomFocusMouse:
		/* try to keep the mouse over the same point in the display */

		if (_drags->active()) {
			where = _drags->current_pointer_sample ();
		} else if (!mouse_sample (where, in_track_canvas)) {
			use_mouse_sample = false;
		}

		if (use_mouse_sample) {

			l = - ((new_page_size * ((where - current_leftmost)/(double)current_page)) - where);

			if (l < 0) {
				leftmost_after_zoom = 0;
			} else if (l > max_samplepos) {
				leftmost_after_zoom = max_samplepos - new_page_size;
			} else {
				leftmost_after_zoom = (samplepos_t) l;
			}
		} else {
			/* use playhead instead */
			where = _session->transport_sample();

			if (where < half_page_size) {
				leftmost_after_zoom = 0;
			} else {
				leftmost_after_zoom = where - half_page_size;
			}
		}
		break;

	case ZoomFocusEdit:
		/* try to keep the edit point in the same place */
		where = get_preferred_edit_position ().samples();
		{
			double l = - ((new_page_size * ((where - current_leftmost)/(double)current_page)) - where);

			if (l < 0) {
				leftmost_after_zoom = 0;
			} else if (l > max_samplepos) {
				leftmost_after_zoom = max_samplepos - new_page_size;
			} else {
				leftmost_after_zoom = (samplepos_t) l;
			}
		}
		break;

	}

	// leftmost_after_zoom = min (leftmost_after_zoom, _session->current_end_sample());

	reposition_and_zoom (leftmost_after_zoom, nspp);
}

void
EditingContext::calc_extra_zoom_edges (samplepos_t &start, samplepos_t &end)
{
	EC_LOCAL_TEMPO_SCOPE;

	/* this func helps make sure we leave a little space
	   at each end of the editor so that the zoom doesn't fit the region
	   precisely to the screen.
	*/

	GdkScreen* screen = gdk_screen_get_default ();
	const gint pixwidth = gdk_screen_get_width (screen);
	const gint mmwidth = gdk_screen_get_width_mm (screen);
	const double pix_per_mm = (double) pixwidth/ (double) mmwidth;
	const double one_centimeter_in_pixels = pix_per_mm * 10.0;

	const samplepos_t range = end - start;
	const samplecnt_t new_fpp = (samplecnt_t) ceil ((double) range / (double) _track_canvas_width);
	const samplepos_t extra_samples = (samplepos_t) floor (one_centimeter_in_pixels * new_fpp);

	if (start > extra_samples) {
		start -= extra_samples;
	} else {
		start = 0;
	}

	if (max_samplepos - extra_samples > end) {
		end += extra_samples;
	} else {
		end = max_samplepos;
	}
}


void
EditingContext::temporal_zoom_by_sample (samplepos_t start, samplepos_t end)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (!_session) return;

	if ((start == 0 && end == 0) || end < start) {
		return;
	}

	samplepos_t range = end - start;

	const samplecnt_t new_fpp = (samplecnt_t) ceil ((double) range / (double) _track_canvas_width);

	samplepos_t new_page = range;
	samplepos_t middle = (samplepos_t) floor ((double) start + ((double) range / 2.0f));
	samplepos_t new_leftmost = (samplepos_t) floor ((double) middle - ((double) new_page / 2.0f));

	if (new_leftmost > middle) {
		new_leftmost = 0;
	}

	if (new_leftmost < 0) {
		new_leftmost = 0;
	}

	reposition_and_zoom (new_leftmost, new_fpp);
}

void
EditingContext::temporal_zoom_to_sample (bool coarser, samplepos_t sample)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (!_session) {
		return;
	}

	samplecnt_t range_before = sample - _leftmost_sample;
	samplecnt_t new_spp;

	if (coarser) {
		if (samples_per_pixel <= 1) {
			new_spp = 2;
		} else {
			new_spp = samples_per_pixel + (samples_per_pixel/2);
		}
		range_before += range_before/2;
	} else {
		if (samples_per_pixel >= 1) {
			new_spp = samples_per_pixel - (samples_per_pixel/2);
		} else {
			/* could bail out here since we cannot zoom any finer,
			   but leave that to the equality test below
			*/
			new_spp = samples_per_pixel;
		}

		range_before -= range_before/2;
	}

	if (new_spp == samples_per_pixel)  {
		return;
	}

	/* zoom focus is automatically taken as @p sample when this
	   method is used.
	*/

	samplepos_t new_leftmost = sample - (samplepos_t)range_before;

	if (new_leftmost > sample) {
		new_leftmost = 0;
	}

	if (new_leftmost < 0) {
		new_leftmost = 0;
	}

	reposition_and_zoom (new_leftmost, new_spp);
}

bool
EditingContext::mouse_sample (samplepos_t& where, bool& in_track_canvas) const
{
	EC_LOCAL_TEMPO_SCOPE;

	/* gdk_window_get_pointer() has X11's XQueryPointer semantics in that it only
	 * pays attentions to subwindows. this means that menu windows are ignored, and
	 * if the pointer is in a menu, the return window from the call will be the
	 * the regular subwindow *under* the menu.
	 *
	 * this matters quite a lot if the pointer is moving around in a menu that overlaps
	 * the track canvas because we will believe that we are within the track canvas
	 * when we are not. therefore, we track enter/leave events for the track canvas
	 * and allow that to override the result of gdk_window_get_pointer().
	 */

	if (!within_track_canvas) {
		return false;
	}

	int x, y;
	Glib::RefPtr<Gdk::Window> canvas_window = const_cast<EditingContext*>(this)->get_canvas()->get_window();

	if (!canvas_window) {
		return false;
	}

	Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);

	if (!pointer_window) {
		return false;
	}

	if (pointer_window != canvas_window) {
		in_track_canvas = false;
		return false;
	}

	in_track_canvas = true;

	GdkEvent event;
	event.type = GDK_BUTTON_RELEASE;
	event.button.x = x;
	event.button.y = y;

	where = window_event_sample (&event, 0, 0);

	return true;
}

samplepos_t
EditingContext::window_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
{
	EC_LOCAL_TEMPO_SCOPE;

	ArdourCanvas::Duple d;

	if (!gdk_event_get_coords (event, &d.x, &d.y)) {
		return 0;
	}

	/* event coordinates are in window units, so convert to canvas
	 */

	d = get_canvas()->window_to_canvas (d);

	if (pcx) {
		*pcx = d.x;
	}

	if (pcy) {
		*pcy = d.y;
	}

	return pixel_to_sample (canvas_to_timeline (d.x));
}

Editing::ZoomFocus
EditingContext::zoom_focus () const
{
	EC_LOCAL_TEMPO_SCOPE;

	for (auto & [mode,action] : zoom_focus_actions) {
		if (action->get_active()) {
			return mode;
		}
	}

	return ZoomFocusLeft;
}

void
EditingContext::zoom_focus_chosen (ZoomFocus focus)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (temporary_zoom_focus_change) {
		/* we are just changing settings momentarily, no need to do anything */
		return;
	}

	/* this is driven by a toggle on a radio group, and so is invoked twice,
	   once for the item that became inactive and once for the one that became
	   active.
	*/

	if (!zoom_focus_actions[focus]->get_active()) {
		return;
	}

	zoom_focus_selector.set_active (zoom_focus_strings[focus]);
	set_zoom_focus (focus);
	instant_save ();
}

void
EditingContext::alt_delete_ ()
{
	EC_LOCAL_TEMPO_SCOPE;

	delete_ ();
}

/** Cut selected regions, automation points or a time range */
void
EditingContext::cut ()
{
	EC_LOCAL_TEMPO_SCOPE;

	cut_copy (Cut);
}

/** Copy selected regions, automation points or a time range */
void
EditingContext::copy ()
{
	EC_LOCAL_TEMPO_SCOPE;

	cut_copy (Copy);
}

void
EditingContext::load_shared_bindings ()
{
	EC_LOCAL_TEMPO_SCOPE;

	Bindings* m = Bindings::get_bindings (X_("MIDI"));
	Bindings* b = Bindings::get_bindings (X_("Editing"));
	Bindings* a = Bindings::get_bindings (X_("Automation"));

	if (need_shared_actions) {
		/* This registers 3 different groups of actions (midi, common,
		   automation), but with a null prefix name. We will use them to
		   make copies below, using our editor_name() to define unique
		   per-EditingContext actions.
		*/
		register_midi_actions (m, string());
		register_common_actions (b, string());
		register_automation_actions (a, string());
		need_shared_actions = false;
	}

	/* Copy each set of shared bindings but give them a new name, which will make them refer to actions
	 * named after this EditingContext (ie. unique to this EC)
	 */

	Bindings* midi_bindings = new Bindings (_name, *m);
	register_midi_actions (midi_bindings, _name);
	midi_bindings->associate ();

	Bindings* shared_bindings = new Bindings (_name, *b);
	register_common_actions (shared_bindings, _name);
	shared_bindings->associate ();

	Bindings* automation_bindings = new Bindings (_name, *a);
	register_automation_actions (automation_bindings, _name);
	automation_bindings->associate ();

	/* Attach bindings to the canvas for this editing context */

	bindings.push_back (automation_bindings);
	bindings.push_back (midi_bindings);
	bindings.push_back (shared_bindings);
}

void
EditingContext::drop_grid ()
{
	EC_LOCAL_TEMPO_SCOPE;

	hide_grid_lines ();
	grid_lines.reset ();
}

void
EditingContext::hide_grid_lines ()
{
	EC_LOCAL_TEMPO_SCOPE;

	if (grid_lines) {
		grid_lines->hide();
	}
}

void
EditingContext::maybe_draw_grid_lines (ArdourCanvas::Container* group)
{
	EC_LOCAL_TEMPO_SCOPE;

	if (!_session) {
		return;
	}

	if (!grid_lines) {
		grid_lines.reset (new GridLines (*this, group, ArdourCanvas::LineSet::Vertical));

	}

	grid_marks.clear();
	samplepos_t rightmost_sample = _leftmost_sample + current_page_samples();
	GridType gt (grid_type());

	if (grid_musical()) {
		metric_get_bbt (grid_marks, _leftmost_sample, rightmost_sample, 12);
	} else if (gt == GridTypeTimecode) {
		metric_get_timecode (grid_marks, _leftmost_sample, rightmost_sample, 12);
	} else if (gt == GridTypeCDFrame) {
		metric_get_minsec (grid_marks, _leftmost_sample, rightmost_sample, 12);
	} else if (gt == GridTypeMinSec) {
		metric_get_minsec (grid_marks, _leftmost_sample, rightmost_sample, 12);
	}

	grid_lines->draw (grid_marks);
	grid_lines->show();
}


void
EditingContext::update_grid ()
{
	EC_LOCAL_TEMPO_SCOPE;

	if (!_session) {
		return;
	}

	if (grid_type() == GridTypeNone) {
		hide_grid_lines ();
	} else {
		maybe_draw_grid_lines (time_line_group);
	}
}

Location*
EditingContext::transport_loop_location()
{
	EC_LOCAL_TEMPO_SCOPE;

	if (_session) {
		return _session->locations()->auto_loop_location();
	} else {
		return 0;
	}
}

bool
EditingContext::allow_trim_cursors () const
{
	EC_LOCAL_TEMPO_SCOPE;

	auto mouse_mode = current_mouse_mode();
	return mouse_mode == MouseContent || mouse_mode == MouseTimeFX || mouse_mode == MouseDraw;
}

/** Queue a change for the Editor viewport x origin to follow the playhead */
void
EditingContext::reset_x_origin_to_follow_playhead ()
{
	EC_LOCAL_TEMPO_SCOPE;

	assert (_session);

	samplepos_t const sample = _playhead_cursor->current_sample ();

	if (sample > _leftmost_sample && sample <= _leftmost_sample + current_page_samples()) {
		return;
	}

	if (_session->transport_speed() < 0) {

		if (sample > (current_page_samples() / 2)) {
			center_screen (sample-(current_page_samples()/2));
		} else {
			center_screen (current_page_samples()/2);
		}

	} else {

		samplepos_t l = 0;

		if (sample < _leftmost_sample) {
			/* moving left */
			if (_session->transport_rolling()) {
				/* rolling; end up with the playhead at the right of the page */
				l = sample - current_page_samples ();
			} else {
				/* not rolling: end up with the playhead 1/4 of the way along the page */
				l = sample - current_page_samples() / 4;
			}
		} else {
			/* moving right */
			if (_session->transport_rolling()) {
				/* rolling: end up with the playhead on the left of the page */
				l = sample;
			} else {
				/* not rolling: end up with the playhead 3/4 of the way along the page */
				l = sample - 3 * current_page_samples() / 4;
			}
		}

		if (l < 0) {
			l = 0;
		}

		center_screen_internal (l + (current_page_samples() / 2), current_page_samples ());
	}
}

void
EditingContext::center_screen (samplepos_t sample)
{
	EC_LOCAL_TEMPO_SCOPE;

	samplecnt_t const page = _visible_canvas_width * samples_per_pixel;

	/* if we're off the page, then scroll.
	 */

	if (sample < _leftmost_sample || sample >= _leftmost_sample + page) {
		center_screen_internal (sample, page);
	}
}

void
EditingContext::center_screen_internal (samplepos_t sample, float page)
{
	EC_LOCAL_TEMPO_SCOPE;

	page /= 2;

	if (sample > page) {
		sample -= (samplepos_t) page;
	} else {
		sample = 0;
	}

	reset_x_origin (sample);
}

void
EditingContext::sample_to_clock_parts (samplepos_t sample,
                                       samplepos_t sample_rate,
                                       long*       hrs_p,
                                       long*       mins_p,
                                       long*       secs_p,
                                       long*       millisecs_p)
{
	samplepos_t left;
	long hrs;
	long mins;
	long secs;
	long millisecs;

	left = sample;
	hrs = left / (sample_rate * 60 * 60 * 1000);
	left -= hrs * sample_rate * 60 * 60 * 1000;
	mins = left / (sample_rate * 60 * 1000);
	left -= mins * sample_rate * 60 * 1000;
	secs = left / (sample_rate * 1000);
	left -= secs * sample_rate * 1000;
	millisecs = left / sample_rate;

	*millisecs_p = millisecs;
	*secs_p = secs;
	*mins_p = mins;
	*hrs_p = hrs;

	return;
}



void
EditingContext::metric_get_minsec (std::vector<ArdourCanvas::Ruler::Mark>& marks, int64_t lower, int64_t upper, gint /*maxchars*/)
{
	EC_LOCAL_TEMPO_SCOPE;

	samplepos_t pos;
	samplepos_t spacer;
	long hrs, mins, secs, millisecs;
	gchar buf[16];
	gint n;
	ArdourCanvas::Ruler::Mark mark;

	if (_session == 0) {
		return;
	}

	/* to prevent 'flashing' */
	if (lower > (spacer = (samplepos_t) (128 * get_current_zoom ()))) {
		lower = lower - spacer;
	} else {
		lower = 0;
	}

	if (minsec_mark_interval == 0) {  //we got here too early; divide-by-zero imminent
		return;
	}

	pos = (((1000 * (samplepos_t) floor(lower)) + (minsec_mark_interval/2))/minsec_mark_interval) * minsec_mark_interval;

	switch (minsec_ruler_scale) {

	case minsec_show_msecs:
		for (n = 0; n < minsec_nmarks && n < upper; pos += minsec_mark_interval, ++n) {
			sample_to_clock_parts (pos, _session->sample_rate(), &hrs, &mins, &secs, &millisecs);
			if (millisecs % minsec_mark_modulo == 0) {
				if (millisecs == 0) {
					mark.style = ArdourCanvas::Ruler::Mark::Major;
				} else {
					mark.style = ArdourCanvas::Ruler::Mark::Minor;
				}
				snprintf (buf, sizeof(buf), "%02ld:%02ld:%02ld.%03ld", hrs, mins, secs, millisecs);
			} else {
				buf[0] = '\0';
				mark.style = ArdourCanvas::Ruler::Mark::Micro;
			}
			mark.label = buf;
			mark.position = pos/1000.0;
			marks.push_back (mark);
		}
		break;

	case minsec_show_csecs:
		for (n = 0; n < minsec_nmarks && n < upper; pos += minsec_mark_interval, ++n) {
			sample_to_clock_parts (pos, _session->sample_rate(), &hrs, &mins, &secs, &millisecs);
			if ((millisecs * 10) % minsec_mark_modulo == 0) {
				if (millisecs == 0) {
					mark.style = ArdourCanvas::Ruler::Mark::Major;
				} else {
					mark.style = ArdourCanvas::Ruler::Mark::Minor;
				}
				snprintf (buf, sizeof(buf), "%02ld:%02ld:%02ld.%03ld", hrs, mins, secs, millisecs);
			} else {
				buf[0] = '\0';
				mark.style = ArdourCanvas::Ruler::Mark::Micro;
			}
			mark.label = buf;
			mark.position = pos/1000.0;
			marks.push_back (mark);
		}
		break;

	case minsec_show_dsecs:
		for (n = 0; n < minsec_nmarks && n < upper; pos += minsec_mark_interval, ++n) {
			sample_to_clock_parts (pos, _session->sample_rate(), &hrs, &mins, &secs, &millisecs);
			if ((millisecs * 100) % minsec_mark_modulo == 0) {
				if (millisecs == 0) {
					mark.style = ArdourCanvas::Ruler::Mark::Major;
				} else {
					mark.style = ArdourCanvas::Ruler::Mark::Minor;
				}
				snprintf (buf, sizeof(buf), "%02ld:%02ld:%02ld.%03ld", hrs, mins, secs, millisecs);
			} else {
				buf[0] = '\0';
				mark.style = ArdourCanvas::Ruler::Mark::Micro;
			}
			mark.label = buf;
			mark.position = pos/1000.0;
			marks.push_back (mark);
		}
		break;

	case minsec_show_seconds:
		for (n = 0; n < minsec_nmarks; pos += minsec_mark_interval, ++n) {
			sample_to_clock_parts (pos, _session->sample_rate(), &hrs, &mins, &secs, &millisecs);
			if (secs % minsec_mark_modulo == 0) {
				if (secs == 0) {
					mark.style = ArdourCanvas::Ruler::Mark::Major;
				} else {
					mark.style = ArdourCanvas::Ruler::Mark::Minor;
				}
				snprintf (buf, sizeof(buf), "%02ld:%02ld:%02ld", hrs, mins, secs);
			} else {
				buf[0] = '\0';
				mark.style = ArdourCanvas::Ruler::Mark::Micro;
			}
			mark.label = buf;
			mark.position = pos/1000.0;
			marks.push_back (mark);
		}
		break;

	case minsec_show_minutes:
		for (n = 0; n < minsec_nmarks; pos += minsec_mark_interval, ++n) {
			sample_to_clock_parts (pos, _session->sample_rate(), &hrs, &mins, &secs, &millisecs);
			if (mins % minsec_mark_modulo == 0) {
				if (mins == 0) {
					mark.style = ArdourCanvas::Ruler::Mark::Major;
				} else {
					mark.style = ArdourCanvas::Ruler::Mark::Minor;
				}
				snprintf (buf, sizeof(buf), "%02ld:%02ld:%02ld", hrs, mins, secs);
			} else {
				buf[0] = '\0';
				mark.style = ArdourCanvas::Ruler::Mark::Micro;
			}
			mark.label = buf;
			mark.position = pos/1000.0;
			marks.push_back (mark);
		}
		break;

	case minsec_show_hours:
		 for (n = 0; n < minsec_nmarks; pos += minsec_mark_interval, ++n) {
			sample_to_clock_parts (pos, _session->sample_rate(), &hrs, &mins, &secs, &millisecs);
			if (hrs % minsec_mark_modulo == 0) {
				mark.style = ArdourCanvas::Ruler::Mark::Major;
				snprintf (buf, sizeof(buf), "%02ld:%02ld", hrs, mins);
			} else {
				buf[0] = '\0';
				mark.style = ArdourCanvas::Ruler::Mark::Micro;
			}
			mark.label = buf;
			mark.position = pos/1000.0;
			marks.push_back (mark);
		 }
		 break;

	case minsec_show_many_hours:
		for (n = 0; n < minsec_nmarks;) {
			sample_to_clock_parts (pos, _session->sample_rate(), &hrs, &mins, &secs, &millisecs);
			if (hrs % minsec_mark_modulo == 0) {
				mark.style = ArdourCanvas::Ruler::Mark::Major;
				snprintf (buf, sizeof(buf), "%02ld:00", hrs);
				mark.label = buf;
				mark.position = pos/1000.0;
				marks.push_back (mark);
				++n;
			}
			pos += minsec_mark_interval;
		}
		break;
	}
}

void
EditingContext::set_minsec_ruler_scale (samplepos_t lower, samplepos_t upper)
{
	EC_LOCAL_TEMPO_SCOPE;

	samplepos_t fr = _session->sample_rate() * 1000;
	samplepos_t spacer;

	if (_session == 0) {
		return;
	}


	/* to prevent 'flashing' */
	if (lower > (spacer = (samplepos_t)(128 * get_current_zoom ()))) {
		lower -= spacer;
	} else {
		lower = 0;
	}
	upper += spacer;
	samplecnt_t const range = (upper - lower) * 1000;

	if (range <= (fr / 10)) { /* 0-0.1 second */
		minsec_mark_interval = fr / 1000; /* show 1/1000 seconds */
		minsec_ruler_scale = minsec_show_msecs;
		minsec_mark_modulo = 10;
		minsec_nmarks = 2 + (range / minsec_mark_interval);
	} else if (range <= (fr / 2)) { /* 0-0.5 second */
		minsec_mark_interval = fr / 100;  /* show 1/100 seconds */
		minsec_ruler_scale = minsec_show_csecs;
		minsec_mark_modulo = 100;
		minsec_nmarks = 2 + (range / minsec_mark_interval);
	} else if (range <= fr) { /* 0-1 second */
		minsec_mark_interval = fr / 10;  /* show 1/10 seconds */
		minsec_ruler_scale = minsec_show_dsecs;
		minsec_mark_modulo = 200;
		minsec_nmarks = 2 + (range / minsec_mark_interval);
	} else if (range <= 2 * fr) { /* 1-2 seconds */
		minsec_mark_interval = fr / 10; /* show 1/10 seconds */
		minsec_ruler_scale = minsec_show_dsecs;
		minsec_mark_modulo = 500;
		minsec_nmarks = 2 + (range / minsec_mark_interval);
	} else if (range <= 8 * fr) { /* 2-5 seconds */
		minsec_mark_interval =  fr / 5; /* show 2 seconds */
		minsec_ruler_scale = minsec_show_dsecs;
		minsec_mark_modulo = 1000;
		minsec_nmarks = 2 + (range / minsec_mark_interval);
	} else if (range <= 16 * fr) { /* 8-16 seconds */
		minsec_mark_interval =  fr; /* show 1 seconds */
		minsec_ruler_scale = minsec_show_seconds;
		minsec_mark_modulo = 2;
		minsec_nmarks = 2 + (range / minsec_mark_interval);
	} else if (range <= 30 * fr) { /* 10-30 seconds */
		minsec_mark_interval =  fr; /* show 1 seconds */
		minsec_ruler_scale = minsec_show_seconds;
		minsec_mark_modulo = 5;
		minsec_nmarks = 2 + (range / minsec_mark_interval);
	} else if (range <= 60 * fr) { /* 30-60 seconds */
		minsec_mark_interval = fr; /* show 1 seconds */
		minsec_ruler_scale = minsec_show_seconds;
		minsec_mark_modulo = 5;
		minsec_nmarks = 2 + (range / minsec_mark_interval);
	} else if (range <= 2 * 60 * fr) { /* 1-2 minutes */
		minsec_mark_interval = 5 * fr; /* show 5 seconds */
		minsec_ruler_scale = minsec_show_seconds;
		minsec_mark_modulo = 3;
		minsec_nmarks = 2 + (range / minsec_mark_interval);
	} else if (range <= 4 * 60 * fr) { /* 4 minutes */
		minsec_mark_interval = 5 * fr; /* show 10 seconds */
		minsec_ruler_scale = minsec_show_seconds;
		minsec_mark_modulo = 30;
		minsec_nmarks = 2 + (range / minsec_mark_interval);
	} else if (range <= 10 * 60 * fr) { /* 10 minutes */
		minsec_mark_interval = 30 * fr; /* show 30 seconds */
		minsec_ruler_scale = minsec_show_seconds;
		minsec_mark_modulo = 120;
		minsec_nmarks = 2 + (range / minsec_mark_interval);
	} else if (range <= 30 * 60 * fr) { /* 10-30 minutes */
		minsec_mark_interval =  60 * fr; /* show 1 minute */
		minsec_ruler_scale = minsec_show_minutes;
		minsec_mark_modulo = 5;
		minsec_nmarks = 2 + (range / minsec_mark_interval);
	} else if (range <= 60 * 60 * fr) { /* 30 minutes - 1hr */
		minsec_mark_interval = 2 * 60 * fr; /* show 2 minutes */
		minsec_ruler_scale = minsec_show_minutes;
		minsec_mark_modulo = 10;
		minsec_nmarks = 2 + (range / minsec_mark_interval);
	} else if (range <= 4 * 60 * 60 * fr) { /* 1 - 4 hrs*/
		minsec_mark_interval = 5 * 60 * fr; /* show 10 minutes */
		minsec_ruler_scale = minsec_show_minutes;
		minsec_mark_modulo = 30;
		minsec_nmarks = 2 + (range / minsec_mark_interval);
	} else if (range <= 8 * 60 * 60 * fr) { /* 4 - 8 hrs*/
		minsec_mark_interval = 20 * 60 * fr; /* show 20 minutes */
		minsec_ruler_scale = minsec_show_minutes;
		minsec_mark_modulo = 60;
		minsec_nmarks = 2 + (range / minsec_mark_interval);
	} else if (range <= 16 * 60 * 60 * fr) { /* 16-24 hrs*/
		minsec_mark_interval =  60 * 60 * fr; /* show 60 minutes */
		minsec_ruler_scale = minsec_show_hours;
		minsec_mark_modulo = 2;
		minsec_nmarks = 2 + (range / minsec_mark_interval);
	} else {

		const samplecnt_t hours_in_range = range / (60 * 60 * fr);
		const int text_width_rough_guess = 70; /* pixels, very very approximate guess at how wide the tick mark text is */

		/* Normally we do not need to know anything about the width of the canvas
		   to set the ruler scale, because the caller has already determined
		   the width and set lower + upper arguments to this function to match that.

		   But in this case, where the range defined by lower and upper can vary
		   substantially (anything from 24hrs+ to several billion years)
		   trying to decide which tick marks to show does require us to know
		   about the available width.
		*/

		minsec_nmarks = get_canvas()->width() / text_width_rough_guess;
		minsec_mark_modulo = std::max ((samplecnt_t) 1, 1 + (hours_in_range / minsec_nmarks));
		minsec_mark_interval = minsec_mark_modulo * (60 * 60 * fr);
		minsec_ruler_scale = minsec_show_many_hours;
	}
}

void
EditingContext::scroll_left_step ()
{
	EC_LOCAL_TEMPO_SCOPE;

	samplepos_t xdelta = (current_page_samples() / 8);

	if (_leftmost_sample > xdelta) {
		reset_x_origin (_leftmost_sample - xdelta);
	} else {
		reset_x_origin (0);
	}
}


void
EditingContext::scroll_right_step ()
{
	EC_LOCAL_TEMPO_SCOPE;

	samplepos_t xdelta = (current_page_samples() / 8);

	if (max_samplepos - xdelta > _leftmost_sample) {
		reset_x_origin (_leftmost_sample + xdelta);
	} else {
		reset_x_origin (max_samplepos - current_page_samples());
	}
}

void
EditingContext::scroll_left_half_page ()
{
	EC_LOCAL_TEMPO_SCOPE;

	samplepos_t xdelta = (current_page_samples() / 2);
	if (_leftmost_sample > xdelta) {
		reset_x_origin (_leftmost_sample - xdelta);
	} else {
		reset_x_origin (0);
	}
}

void
EditingContext::scroll_right_half_page ()
{
	EC_LOCAL_TEMPO_SCOPE;

	samplepos_t xdelta = (current_page_samples() / 2);
	if (max_samplepos - xdelta > _leftmost_sample) {
		reset_x_origin (_leftmost_sample + xdelta);
	} else {
		reset_x_origin (max_samplepos - current_page_samples());
	}
}

Gtk::Menu*
EditingContext::get_single_region_context_menu ()
{
	return nullptr;
}

void
EditingContext::region_selection_changed ()
{
	EC_LOCAL_TEMPO_SCOPE;

	if (!pianoroll_window || selection->regions.empty()) {
		return;
	}

	pianoroll_window->remove_regions ();

	std::vector<MidiRegionView*> midi_region_views;
	std::set<Temporal::Beats> positions;

	for (auto & rv : selection->regions) {
		MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
		if (mrv) {
			midi_region_views.push_back (mrv);
			positions.insert (mrv->region()->position().beats());
		}
	}

	if (midi_region_views.empty()) {
		return;
	}

	if (positions.size() > 1) {
		/* multiple positions for the regions, so just pick one of the
		   positions, and the regions there
		*/

		Temporal::Beats first_position (*positions.begin());

		for (auto i = midi_region_views.begin(); i != midi_region_views.end(); ) {
			if ((*i)->region()->position().beats() != first_position) {
				i = midi_region_views.erase (i);
			} else {
				i++;
			}
		}
	}

	bool have_set = false;

	std::shared_ptr<ARDOUR::MidiTrack> kt;
	std::shared_ptr<ARDOUR::MidiRegion> kr;

	for (auto & mrv : midi_region_views) {
		TimeAxisView& tav (mrv->get_time_axis_view());
		RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&tav);
		if (!rtav) { /* how */
			continue;
		}

		std::shared_ptr<MidiTrack> track = std::dynamic_pointer_cast<MidiTrack> (rtav->stripable());
		if (!track) {
			continue;
		}

		std::shared_ptr<MidiRegion> midi_region = std::dynamic_pointer_cast<MidiRegion> (mrv->region());
		if (!midi_region) {
			continue;
		}

		if (!have_set) {
			kt = track;
			kr = midi_region;
			have_set = true;
		} else {
			pianoroll_window->add_region (track, midi_region);
		}
	}

	pianoroll_window->set_region (kt, kr);
}

void
EditingContext::pianoroll_edit ()
{
	EC_LOCAL_TEMPO_SCOPE;

	if (!pianoroll_window) {
		pianoroll_window = new PianorollWindow (_("Pianoroll Window"), *_session);
	}

	pianoroll_window->set_show_source (false);

	region_selection_changed ();

	pianoroll_window->show_all ();
	pianoroll_window->present ();
}
