/* * Copyright (C) 2023 Paul Davis * * 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 #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/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 "quantize_dialog.h" #include "rc_option_editor.h" #include "selection.h" #include "selection_memento.h" #include "transform_dialog.h" #include "transpose_dialog.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 EditingContext::DropDownKeys; Gtkmm2ext::Bindings* EditingContext::button_bindings = nullptr; std::vector EditingContext::grid_type_strings; std::vector 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 }; 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) { 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_playhead_button, _("Scroll automatically to keep playhead visible")); 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)); 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 pc (std::bind (&EditingContext::ui_parameter_changed, this, _1)); UIConfiguration::instance().map_parameters (pc); } EditingContext::~EditingContext() { 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); } } 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 (); } 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"), std::bind (sigc::mem_fun (*this, &EditingContext::automation_create_point_at_edit_point), true)); reg_sens (_automation_actions, "move-points-later", _("Create Automation P (at Playhead)"), sigc::mem_fun (*this, &EditingContext::automation_move_points_later)); reg_sens (_automation_actions, "move-points-earlier", _("Create Automation Point (at Playhead)"), sigc::mem_fun (*this, &EditingContext::automation_move_points_earlier)); reg_sens (_automation_actions, "raise-points", _("Create Automation Point (at Playhead)"), sigc::mem_fun (*this, &EditingContext::automation_raise_points)); reg_sens (_automation_actions, "lower-points", _("Create Automation Point (at Playhead)"), 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); } } 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))); 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 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::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), &MidiRegionView::clear_note_selection)); ActionManager::register_action (_midi_actions, X_("invert-selection"), _("Invert Note Selection"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiRegionView::invert_selection)); ActionManager::register_action (_midi_actions, X_("extend-selection"), _("Extend Note Selection"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiRegionView::extend_selection)); ActionManager::register_action (_midi_actions, X_("duplicate-selection"), _("Duplicate Note Selection"), sigc::bind (sigc::mem_fun (*this, &EditingContext::midi_action), &MidiRegionView::duplicate_selection)); /* 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_("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)); } 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(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()) { 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 */ } 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 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 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 ract; if (c == DRAW_CHAN_AUTO) { ract = draw_channel_actions[DRAW_CHAN_AUTO]; } else { ract = draw_channel_actions[std::max (std::min (c, 5), 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 grids ({ GridTypeBeat, GridTypeBeatDiv2, GridTypeBeatDiv4, GridTypeBeatDiv8, GridTypeBeatDiv16, GridTypeBeatDiv32, GridTypeNone}); std::vector draw_grid_type_strings; for (auto & g : grids) { Glib::RefPtr 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 preselected_velocities ({8,32,64,82,100,127, DRAW_VEL_AUTO}); std::vector draw_velocity_strings; for (auto & v : preselected_velocities) { Glib::RefPtr 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 possible_channels ({0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, DRAW_CHAN_AUTO}); std::vector draw_channel_strings; for (auto & c : possible_channels) { Glib::RefPtr 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; } return follow_playhead_action->get_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(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,timepos_t> MapEntry; std::set 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 (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::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::Notes selected; mrv.selection_as_notelist (selected, true); if (selected.empty()) { return 0; } std::vector::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 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 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 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 act; act = ActionManager::get_action ((_name + X_("Editing")).c_str(), X_("temporal-zoom-in")); zoom_in_button.set_related_action (act); act = ActionManager::get_action ((_name + X_("Editing")).c_str(), X_("temporal-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); } } 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); } 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(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 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 EditingContext::reg_sens (RefPtr group, char const * name, char const * label, sigc::slot slot) { RefPtr act = ActionManager::register_action (group, name, label, slot); assert(act); ActionManager::session_sensitive_actions.push_back (act); return act; } Glib::RefPtr EditingContext::toggle_reg_sens (RefPtr group, char const * name, char const * label, sigc::slot slot) { RefPtr act = ActionManager::register_toggle_action (group, name, label, slot); assert(act); ActionManager::session_sensitive_actions.push_back (act); return act; } Glib::RefPtr EditingContext::radio_reg_sens (RefPtr action_group, RadioAction::Group& radio_group, char const * name, char const * label, sigc::slot slot) { RefPtr 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_grid_type_as_beats (bool& success, timepos_t const & position) const { EC_LOCAL_TEMPO_SCOPE; success = true; int32_t const divisions = get_grid_beat_divisions (grid_type()); /* 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 (grid_type()) { 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(); } Temporal::Beats EditingContext::get_draw_length_as_beats (bool& success, timepos_t const & position) const { EC_LOCAL_TEMPO_SCOPE; success = true; GridType grid_to_use = draw_length() == DRAW_LEN_AUTO ? grid_type() : draw_length(); int32_t const divisions = get_grid_beat_divisions (grid_to_use); if (divisions != 0) { return Temporal::Beats::ticks (Temporal::Beats::PPQN / divisions); } success = false; return Temporal::Beats(); } void EditingContext::select_automation_line (GdkEventButton* event, ArdourCanvas::Item* item, ARDOUR::SelectionOperation op) { EC_LOCAL_TEMPO_SCOPE; AutomationLine* al = reinterpret_cast (item->get_data ("line")); std::list 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 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 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 canvas_window = const_cast(this)->get_canvas()->get_window(); if (!canvas_window) { return false; } Glib::RefPtr 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) { 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()) { 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& 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_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_msecs; 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_msecs; 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_msecs; 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_msecs; 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 () { 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 () { 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 () { 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 () { 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()); } }