#include "control_protocol/control_protocol.h"
-#include "actions.h"
#include "actions.h"
#include "analysis_window.h"
#include "audio_clock.h"
#include "gui_thread.h"
#include "keyboard.h"
#include "marker.h"
+#include "midi_region_view.h"
#include "midi_time_axis.h"
#include "mixer_strip.h"
#include "mixer_ui.h"
#include "mouse_cursors.h"
+#include "note_base.h"
#include "playlist_selector.h"
#include "public_editor.h"
#include "region_layering_order_editor.h"
#include "sfdb_ui.h"
#include "tempo_lines.h"
#include "time_axis_view.h"
+#include "timers.h"
#include "utils.h"
#include "verbose_cursor.h"
using PBD::atoi;
using Gtkmm2ext::Keyboard;
-const double Editor::timebar_height = 15.0;
+double Editor::timebar_height = 15.0;
static const gchar *_snap_type_strings[] = {
N_("CD Frames"),
Editor::Editor ()
: _join_object_range_state (JOIN_OBJECT_RANGE_NONE)
+ , _mouse_changed_selection (false)
/* time display buttons */
, minsec_label (_("Mins:Secs"))
, bbt_label (_("Bars:Beats"))
, _tools_tearoff (0)
, _toolbar_viewport (*manage (new Gtk::Adjustment (0, 0, 1e10)), *manage (new Gtk::Adjustment (0, 0, 1e10)))
+ , selection_op_cmd_depth (0)
+ , selection_op_history_it (0)
/* nudge */
selection = new Selection (this);
cut_buffer = new Selection (this);
+ _selection_memento = new SelectionMemento ();
+ selection_op_history.clear();
+ before.clear();
clicked_regionview = 0;
clicked_axisview = 0;
clicked_routeview = 0;
clicked_control_point = 0;
last_update_frame = 0;
- pre_press_cursor = 0;
+ last_paste_pos = 0;
+ paste_count = 0;
_drags = new DragManager (this);
lock_dialog = 0;
ruler_dialog = 0;
sfbrowser = 0;
- location_marker_color = ARDOUR_UI::config()->get_canvasvar_LocationMarker();
- location_range_color = ARDOUR_UI::config()->get_canvasvar_LocationRange();
- location_cd_marker_color = ARDOUR_UI::config()->get_canvasvar_LocationCDMarker();
- location_loop_color = ARDOUR_UI::config()->get_canvasvar_LocationLoop();
- location_punch_color = ARDOUR_UI::config()->get_canvasvar_LocationPunch();
+ location_marker_color = ARDOUR_UI::config()->color ("location marker");
+ location_range_color = ARDOUR_UI::config()->color ("location range");
+ location_cd_marker_color = ARDOUR_UI::config()->color ("location cd marker");
+ location_loop_color = ARDOUR_UI::config()->color ("location loop");
+ location_punch_color = ARDOUR_UI::config()->color ("location punch");
zoom_focus = ZoomFocusLeft;
_edit_point = EditAtMouse;
- _internal_editing = false;
- current_canvas_cursor = 0;
- _visible_track_count = 16;
+ _visible_track_count = -1;
samples_per_pixel = 2048; /* too early to use reset_zoom () */
+ timebar_height = std::max(12., ceil (15. * ARDOUR_UI::config()->get_font_scale() / 102400.));
+ TimeAxisView::setup_sizes ();
+ Marker::setup_sizes (timebar_height);
+
_scroll_callbacks = 0;
bbt_label.set_name ("EditorRulerLabel");
initialize_canvas ();
+ CairoWidget::set_focus_handler (sigc::mem_fun (*this, &Editor::reset_focus));
+
_summary = new EditorSummary (this);
selection->TimeChanged.connect (sigc::mem_fun(*this, &Editor::time_selection_changed));
_cursors->set_cursor_set (ARDOUR_UI::config()->get_icon_set());
cerr << "Set cursor set to " << ARDOUR_UI::config()->get_icon_set() << endl;
+ /* Push default cursor to ever-present bottom of cursor stack. */
+ push_canvas_cursor(_cursors->grabber);
+
ArdourCanvas::GtkCanvas* time_pad = manage (new ArdourCanvas::GtkCanvas ());
ArdourCanvas::Line* pad_line_1 = new ArdourCanvas::Line (time_pad->root());
_snapshots = new EditorSnapshots (this);
_locations = new EditorLocations (this);
+ /* these are static location signals */
+
+ Location::start_changed.connect (*this, invalidator (*this), boost::bind (&Editor::location_changed, this, _1), gui_context());
+ Location::end_changed.connect (*this, invalidator (*this), boost::bind (&Editor::location_changed, this, _1), gui_context());
+ Location::changed.connect (*this, invalidator (*this), boost::bind (&Editor::location_changed, this, _1), gui_context());
+
add_notebook_page (_("Regions"), _regions->widget ());
add_notebook_page (_("Tracks & Busses"), _routes->widget ());
add_notebook_page (_("Snapshots"), _snapshots->widget ());
_snap_mode = SnapOff;
set_snap_mode (_snap_mode);
set_mouse_mode (MouseObject, true);
- pre_internal_mouse_mode = MouseObject;
pre_internal_snap_type = _snap_type;
pre_internal_snap_mode = _snap_mode;
internal_snap_type = _snap_type;
delete _route_groups;
delete _track_canvas_viewport;
delete _drags;
+ delete nudge_clock;
}
XMLNode*
entered_regionview = rv;
if (entered_regionview != 0) {
- entered_regionview->entered (internal_editing ());
+ entered_regionview->entered ();
}
if (!_all_region_actions_sensitized && _last_region_menu_was_main) {
_dragging_playhead = true;
}
- if ((fraction < 0.0f) && (*_control_scroll_target < (framepos_t) fabs(step))) {
+ if ((fraction < 0.0f) && (*_control_scroll_target <= (framepos_t) fabs(step))) {
*_control_scroll_target = 0;
} else if ((fraction > 0.0f) && (max_framepos - *_control_scroll_target < step)) {
*_control_scroll_target = max_framepos - (current_page_samples()*2); // allow room for slop in where the PH is on the screen
} else {
- *_control_scroll_target += (framepos_t) floor (step);
+ *_control_scroll_target += (framepos_t) trunc (step);
}
/* move visuals, we'll catch up with it later */
case GDK_KEY_RELEASE:
gettimeofday (&last_event_time, 0);
break;
+
+ case GDK_LEAVE_NOTIFY:
+ switch (ev->crossing.detail) {
+ case GDK_NOTIFY_UNKNOWN:
+ case GDK_NOTIFY_INFERIOR:
+ case GDK_NOTIFY_ANCESTOR:
+ break;
+ case GDK_NOTIFY_VIRTUAL:
+ case GDK_NOTIFY_NONLINEAR:
+ case GDK_NOTIFY_NONLINEAR_VIRTUAL:
+ /* leaving window, so reset focus, thus ending any and
+ all text entry operations.
+ */
+ reset_focus();
+ break;
+ }
+ break;
+
default:
break;
}
+
return false;
}
timersub (&now, &last_event_time, &delta);
- if (delta.tv_sec > ARDOUR_UI::config()->get_lock_gui_after_seconds()) {
+ if (delta.tv_sec > (time_t) ARDOUR_UI::config()->get_lock_gui_after_seconds()) {
lock ();
/* don't call again. Returning false will effectively
disconnect us from the timer callback.
_session->locations()->added.connect (_session_connections, invalidator (*this), boost::bind (&Editor::add_new_location, this, _1), gui_context());
_session->locations()->removed.connect (_session_connections, invalidator (*this), boost::bind (&Editor::location_gone, this, _1), gui_context());
_session->locations()->changed.connect (_session_connections, invalidator (*this), boost::bind (&Editor::refresh_location_display, this), gui_context());
- _session->locations()->StateChanged.connect (_session_connections, invalidator (*this), boost::bind (&Editor::refresh_location_display, this), gui_context());
_session->history().Changed.connect (_session_connections, invalidator (*this), boost::bind (&Editor::history_changed, this), gui_context());
playhead_cursor->show ();
(static_cast<TimeAxisView*>(*i))->set_samples_per_pixel (samples_per_pixel);
}
- super_rapid_screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
+ super_rapid_screen_update_connection = Timers::super_rapid_connect (
sigc::mem_fun (*this, &Editor::super_rapid_screen_update)
);
/* register for undo history */
_session->register_with_memento_command_factory(id(), this);
+ _session->register_with_memento_command_factory(_selection_memento->id(), _selection_memento);
ActionManager::ui_manager->signal_pre_activate().connect (sigc::mem_fun (*this, &Editor::action_pre_activated));
Editor::popup_xfade_in_context_menu (int button, int32_t time, ArdourCanvas::Item* item, ItemType /*item_type*/)
{
using namespace Menu_Helpers;
- AudioRegionView* arv = static_cast<AudioRegionView*> (item->get_data ("regionview"));
- assert(arv);
+ AudioRegionView* arv = dynamic_cast<AudioRegionView*> ((RegionView*)item->get_data ("regionview"));
+ if (!arv) {
+ return;
+ }
MenuList& items (xfade_in_context_menu.items());
items.clear ();
Editor::popup_xfade_out_context_menu (int button, int32_t time, ArdourCanvas::Item* item, ItemType /*item_type*/)
{
using namespace Menu_Helpers;
- AudioRegionView* arv = static_cast<AudioRegionView*> (item->get_data ("regionview"));
- assert(arv);
+ AudioRegionView* arv = dynamic_cast<AudioRegionView*> ((RegionView*)item->get_data ("regionview"));
+ if (!arv) {
+ return;
+ }
MenuList& items (xfade_out_context_menu.items());
items.clear ();
);
edit_items.push_back (SeparatorElem());
- edit_items.push_back (MenuElem (_("Convert to Region In-Place"), mem_fun(*this, &Editor::separate_region_from_selection)));
+ edit_items.push_back (MenuElem (_("Separate"), mem_fun(*this, &Editor::separate_region_from_selection)));
edit_items.push_back (MenuElem (_("Convert to Region in Region List"), sigc::mem_fun(*this, &Editor::new_region_from_selection)));
edit_items.push_back (SeparatorElem());
edit_items.push_back (MenuElem (_("Select All in Range"), sigc::mem_fun(*this, &Editor::select_all_selectables_using_time_selection)));
edit_items.push_back (SeparatorElem());
- edit_items.push_back (MenuElem (_("Set Loop from Range"), sigc::bind (sigc::mem_fun(*this, &Editor::set_loop_from_selection), false)));
- edit_items.push_back (MenuElem (_("Set Punch from Range"), sigc::mem_fun(*this, &Editor::set_punch_from_selection)));
+ edit_items.push_back (MenuElem (_("Set Loop from Selection"), sigc::bind (sigc::mem_fun(*this, &Editor::set_loop_from_selection), false)));
+ edit_items.push_back (MenuElem (_("Set Punch from Selection"), sigc::mem_fun(*this, &Editor::set_punch_from_selection)));
+ edit_items.push_back (MenuElem (_("Set Session Start/End from Selection"), sigc::mem_fun(*this, &Editor::set_session_extents_from_selection)));
edit_items.push_back (SeparatorElem());
edit_items.push_back (MenuElem (_("Add Range Markers"), sigc::mem_fun (*this, &Editor::add_location_from_selection)));
{
unsigned int snap_ind = (unsigned int)st;
+ if (internal_editing()) {
+ internal_snap_type = st;
+ } else {
+ pre_internal_snap_type = st;
+ }
+
_snap_type = st;
if (snap_ind > snap_type_strings.size() - 1) {
break;
}
+ redisplay_tempo (false);
+
SnapChanged (); /* EMIT SIGNAL */
}
{
string str = snap_mode_strings[(int)mode];
- if (_internal_editing) {
+ if (internal_editing()) {
internal_snap_mode = mode;
} else {
pre_internal_snap_mode = mode;
bool changed = (_edit_point != ep);
_edit_point = ep;
- string str = edit_point_strings[(int)ep];
-
if (Profile->get_mixbus())
if (ep == EditAtSelectedMarker)
ep = EditAtPlayhead;
-
+
+ string str = edit_point_strings[(int)ep];
if (str != edit_point_selector.get_text ()) {
edit_point_selector.set_text (str);
}
- reset_canvas_cursor ();
+ update_all_enter_cursors();
if (!force && !changed) {
return;
if (_session && (prop = node.property ("playhead"))) {
framepos_t pos;
sscanf (prop->value().c_str(), "%" PRIi64, &pos);
- playhead_cursor->set_position (pos);
+ if (pos >= 0) {
+ playhead_cursor->set_position (pos);
+ } else {
+ warning << _("Playhead position stored with a negative value - ignored (use zero instead)") << endmsg;
+ playhead_cursor->set_position (0);
+ }
} else {
playhead_cursor->set_position (0);
}
pre_internal_snap_type = (SnapType) string_2_enum (prop->value(), pre_internal_snap_type);
}
-
if ((prop = node.property ("pre-internal-snap-mode"))) {
pre_internal_snap_mode = (SnapMode) string_2_enum (prop->value(), pre_internal_snap_mode);
}
reset_y_origin (atof (prop->value ()));
}
- if ((prop = node.property ("internal-edit"))) {
- bool yn = string_is_affirmative (prop->value());
- RefPtr<Action> act = ActionManager::get_action (X_("MouseMode"), X_("toggle-internal-edit"));
- if (act) {
- RefPtr<ToggleAction> tact = RefPtr<ToggleAction>::cast_dynamic(act);
- tact->set_active (!yn);
- tact->set_active (yn);
- }
- }
-
if ((prop = node.property ("join-object-range"))) {
RefPtr<Action> act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object-range"));
bool yn = string_is_affirmative (prop->value());
node->add_property ("stationary-playhead", _stationary_playhead ? "yes" : "no");
node->add_property ("region-list-sort-type", enum_2_string (_regions->sort_type ()));
node->add_property ("mouse-mode", enum2str(mouse_mode));
- node->add_property ("internal-edit", _internal_editing ? "yes" : "no");
node->add_property ("join-object-range", smart_mode_action->get_active () ? "yes" : "no");
Glib::RefPtr<Action> act = ActionManager::get_action (X_("Editor"), X_("show-editor-mixer"));
* @param event Event to get current key modifier information from, or 0.
*/
void
-Editor::snap_to_with_modifier (framepos_t& start, GdkEvent const * event, int32_t direction, bool for_mark)
+Editor::snap_to_with_modifier (framepos_t& start, GdkEvent const * event, RoundMode direction, bool for_mark)
{
if (!_session || !event) {
return;
}
void
-Editor::snap_to (framepos_t& start, int32_t direction, bool for_mark)
+Editor::snap_to (framepos_t& start, RoundMode direction, bool for_mark)
{
if (!_session || _snap_mode == SnapOff) {
return;
}
void
-Editor::timecode_snap_to_internal (framepos_t& start, int32_t direction, bool /*for_mark*/)
+Editor::timecode_snap_to_internal (framepos_t& start, RoundMode direction, bool /*for_mark*/)
{
const framepos_t one_timecode_second = (framepos_t)(rint(_session->timecode_frames_per_second()) * _session->frames_per_timecode_frame());
framepos_t one_timecode_minute = (framepos_t)(rint(_session->timecode_frames_per_second()) * _session->frames_per_timecode_frame() * 60);
switch (_snap_type) {
case SnapToTimecodeFrame:
- if (((direction == 0) && (fmod((double)start, (double)_session->frames_per_timecode_frame()) > (_session->frames_per_timecode_frame() / 2))) || (direction > 0)) {
+ if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
+ fmod((double)start, (double)_session->frames_per_timecode_frame()) == 0) {
+ /* start is already on a whole timecode frame, do nothing */
+ } else if (((direction == 0) && (fmod((double)start, (double)_session->frames_per_timecode_frame()) > (_session->frames_per_timecode_frame() / 2))) || (direction > 0)) {
start = (framepos_t) (ceil ((double) start / _session->frames_per_timecode_frame()) * _session->frames_per_timecode_frame());
} else {
start = (framepos_t) (floor ((double) start / _session->frames_per_timecode_frame()) * _session->frames_per_timecode_frame());
} else {
start -= _session->config.get_timecode_offset ();
}
- if (((direction == 0) && (start % one_timecode_second > one_timecode_second / 2)) || direction > 0) {
+ if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
+ (start % one_timecode_second == 0)) {
+ /* start is already on a whole second, do nothing */
+ } else if (((direction == 0) && (start % one_timecode_second > one_timecode_second / 2)) || direction > 0) {
start = (framepos_t) ceil ((double) start / one_timecode_second) * one_timecode_second;
} else {
start = (framepos_t) floor ((double) start / one_timecode_second) * one_timecode_second;
} else {
start -= _session->config.get_timecode_offset ();
}
- if (((direction == 0) && (start % one_timecode_minute > one_timecode_minute / 2)) || direction > 0) {
+ if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
+ (start % one_timecode_minute == 0)) {
+ /* start is already on a whole minute, do nothing */
+ } else if (((direction == 0) && (start % one_timecode_minute > one_timecode_minute / 2)) || direction > 0) {
start = (framepos_t) ceil ((double) start / one_timecode_minute) * one_timecode_minute;
} else {
start = (framepos_t) floor ((double) start / one_timecode_minute) * one_timecode_minute;
break;
default:
fatal << "Editor::smpte_snap_to_internal() called with non-timecode snap type!" << endmsg;
- /*NOTREACHED*/
+ abort(); /*NOTREACHED*/
}
}
void
-Editor::snap_to_internal (framepos_t& start, int32_t direction, bool for_mark)
+Editor::snap_to_internal (framepos_t& start, RoundMode direction, bool for_mark)
{
const framepos_t one_second = _session->frame_rate();
const framepos_t one_minute = _session->frame_rate() * 60;
return timecode_snap_to_internal (start, direction, for_mark);
case SnapToCDFrame:
- if (((direction == 0) && (start % (one_second/75) > (one_second/75) / 2)) || (direction > 0)) {
+ if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
+ start % (one_second/75) == 0) {
+ /* start is already on a whole CD frame, do nothing */
+ } else if (((direction == 0) && (start % (one_second/75) > (one_second/75) / 2)) || (direction > 0)) {
start = (framepos_t) ceil ((double) start / (one_second / 75)) * (one_second / 75);
} else {
start = (framepos_t) floor ((double) start / (one_second / 75)) * (one_second / 75);
break;
case SnapToSeconds:
- if (((direction == 0) && (start % one_second > one_second / 2)) || (direction > 0)) {
+ if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
+ start % one_second == 0) {
+ /* start is already on a whole second, do nothing */
+ } else if (((direction == 0) && (start % one_second > one_second / 2)) || (direction > 0)) {
start = (framepos_t) ceil ((double) start / one_second) * one_second;
} else {
start = (framepos_t) floor ((double) start / one_second) * one_second;
break;
case SnapToMinutes:
- if (((direction == 0) && (start % one_minute > one_minute / 2)) || (direction > 0)) {
+ if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
+ start % one_minute == 0) {
+ /* start is already on a whole minute, do nothing */
+ } else if (((direction == 0) && (start % one_minute > one_minute / 2)) || (direction > 0)) {
start = (framepos_t) ceil ((double) start / one_minute) * one_minute;
} else {
start = (framepos_t) floor ((double) start / one_minute) * one_minute;
VBox* mouse_mode_vbox = manage (new VBox);
Alignment* mouse_mode_align = manage (new Alignment);
- Glib::RefPtr<SizeGroup> mouse_mode_size_group = SizeGroup::create (SIZE_GROUP_BOTH);
- //mouse_mode_size_group->add_widget (smart_mode_button);
+ Glib::RefPtr<SizeGroup> mouse_mode_size_group = SizeGroup::create (SIZE_GROUP_VERTICAL);
+ mouse_mode_size_group->add_widget (smart_mode_button);
mouse_mode_size_group->add_widget (mouse_move_button);
mouse_mode_size_group->add_widget (mouse_cut_button);
mouse_mode_size_group->add_widget (mouse_select_button);
- mouse_mode_size_group->add_widget (mouse_zoom_button);
- mouse_mode_size_group->add_widget (mouse_gain_button);
mouse_mode_size_group->add_widget (mouse_timefx_button);
mouse_mode_size_group->add_widget (mouse_audition_button);
mouse_mode_size_group->add_widget (mouse_draw_button);
- mouse_mode_size_group->add_widget (internal_edit_button);
+ mouse_mode_size_group->add_widget (mouse_content_button);
+
+ mouse_mode_size_group->add_widget (zoom_in_button);
+ mouse_mode_size_group->add_widget (zoom_out_button);
+ mouse_mode_size_group->add_widget (zoom_preset_selector);
+ mouse_mode_size_group->add_widget (zoom_out_full_button);
+ mouse_mode_size_group->add_widget (zoom_focus_selector);
+
+ mouse_mode_size_group->add_widget (tav_shrink_button);
+ mouse_mode_size_group->add_widget (tav_expand_button);
+ mouse_mode_size_group->add_widget (visible_tracks_selector);
+
+ mouse_mode_size_group->add_widget (snap_type_selector);
+ mouse_mode_size_group->add_widget (snap_mode_selector);
+
+ mouse_mode_size_group->add_widget (edit_point_selector);
+ mouse_mode_size_group->add_widget (edit_mode_selector);
+
+ mouse_mode_size_group->add_widget (*nudge_clock);
+ mouse_mode_size_group->add_widget (nudge_forward_button);
+ mouse_mode_size_group->add_widget (nudge_backward_button);
- if (!ARDOUR::Profile->get_small_screen()) {
- /* make them just a bit bigger */
- mouse_move_button.set_size_request (24, 30);
- } else {
- /* make them just a bit taller */
- mouse_move_button.set_size_request (-1, 30);
- }
mouse_mode_hbox->set_spacing (2);
if (!ARDOUR::Profile->get_trx()) {
if (!ARDOUR::Profile->get_mixbus()) {
mouse_mode_hbox->pack_start (mouse_cut_button, false, false);
- mouse_mode_hbox->pack_start (mouse_zoom_button, false, false);
}
if (!ARDOUR::Profile->get_trx()) {
- mouse_mode_hbox->pack_start (mouse_gain_button, false, false);
mouse_mode_hbox->pack_start (mouse_timefx_button, false, false);
mouse_mode_hbox->pack_start (mouse_audition_button, false, false);
mouse_mode_hbox->pack_start (mouse_draw_button, false, false);
- mouse_mode_hbox->pack_start (internal_edit_button, false, false, 4);
+ mouse_mode_hbox->pack_start (mouse_content_button, false, false);
}
mouse_mode_vbox->pack_start (*mouse_mode_hbox);
}
tav_expand_button.set_name ("zoom button");
- tav_expand_button.set_size_request (-1, 20);
tav_expand_button.set_image(::get_icon ("tav_exp"));
act = ActionManager::get_action (X_("Editor"), X_("expand-tracks"));
tav_expand_button.set_related_action (act);
tav_shrink_button.set_name ("zoom button");
- tav_shrink_button.set_size_request (-1, 20);
tav_shrink_button.set_image(::get_icon ("tav_shrink"));
act = ActionManager::get_action (X_("Editor"), X_("shrink-tracks"));
tav_shrink_button.set_related_action (act);
void
Editor::setup_tooltips ()
{
- ARDOUR_UI::instance()->set_tip (smart_mode_button, _("Smart Mode (add Range functions to Object mode)"));
- ARDOUR_UI::instance()->set_tip (mouse_move_button, _("Object Mode (select/move Objects)"));
- ARDOUR_UI::instance()->set_tip (mouse_cut_button, _("Cut Mode (split Regions)"));
- ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Range Mode (select/move Ranges)"));
- ARDOUR_UI::instance()->set_tip (mouse_draw_button, _("Draw/Edit MIDI Notes"));
- ARDOUR_UI::instance()->set_tip (mouse_gain_button, _("Draw Region Gain"));
- ARDOUR_UI::instance()->set_tip (mouse_zoom_button, _("Select Zoom Range"));
- ARDOUR_UI::instance()->set_tip (mouse_timefx_button, _("Stretch/Shrink Regions and MIDI Notes"));
- ARDOUR_UI::instance()->set_tip (mouse_audition_button, _("Listen to Specific Regions"));
- ARDOUR_UI::instance()->set_tip (internal_edit_button, _("Note Level Editing"));
+ ARDOUR_UI::instance()->set_tip (smart_mode_button, _("Smart Mode (add Range functions to Grab mode)"));
+ ARDOUR_UI::instance()->set_tip (mouse_move_button, _("Grab Mode (select/move objects)"));
+ ARDOUR_UI::instance()->set_tip (mouse_cut_button, _("Cut Mode (split regions)"));
+ ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Range Mode (select time ranges)"));
+ ARDOUR_UI::instance()->set_tip (mouse_draw_button, _("Draw Mode (draw and edit gain/notes/automation)"));
+ ARDOUR_UI::instance()->set_tip (mouse_timefx_button, _("Stretch Mode (time-stretch audio and midi regions, preserving pitch)"));
+ ARDOUR_UI::instance()->set_tip (mouse_audition_button, _("Audition Mode (listen to regions)"));
+ ARDOUR_UI::instance()->set_tip (mouse_content_button, _("Internal Edit Mode (edit notes and gain curves inside regions)"));
ARDOUR_UI::instance()->set_tip (*_group_tabs, _("Groups: click to (de)activate\nContext-click for other operations"));
ARDOUR_UI::instance()->set_tip (nudge_forward_button, _("Nudge Region/Selection Later"));
ARDOUR_UI::instance()->set_tip (nudge_backward_button, _("Nudge Region/Selection Earlier"));
/* UNDO/REDO */
+void
+Editor::begin_selection_op_history ()
+{
+ selection_op_cmd_depth = 0;
+ selection_op_history_it = 0;
+ selection_op_history.clear();
+ selection_undo_action->set_sensitive (false);
+ selection_redo_action->set_sensitive (false);
+ selection_op_history.push_front (&_selection_memento->get_state ());
+}
+
+void
+Editor::begin_reversible_selection_op (string name)
+{
+ if (_session) {
+ //cerr << name << endl;
+ /* begin/commit pairs can be nested */
+ selection_op_cmd_depth++;
+ }
+}
+
+void
+Editor::commit_reversible_selection_op ()
+{
+ if (_session) {
+ if (selection_op_cmd_depth == 1) {
+
+ if (selection_op_history_it > 0 && selection_op_history_it < selection_op_history.size()) {
+ /* the user has undone some selection ops and then made a new one */
+ list<XMLNode *>::iterator it = selection_op_history.begin();
+ advance (it, selection_op_history_it);
+ selection_op_history.erase (selection_op_history.begin(), it);
+ }
+
+ selection_op_history.push_front (&_selection_memento->get_state ());
+ selection_op_history_it = 0;
+
+ selection_undo_action->set_sensitive (true);
+ selection_redo_action->set_sensitive (false);
+ }
+
+ if (selection_op_cmd_depth > 0) {
+ selection_op_cmd_depth--;
+ }
+ }
+}
+
+void
+Editor::undo_selection_op ()
+{
+ if (_session) {
+ selection_op_history_it++;
+ uint32_t n = 0;
+ for (std::list<XMLNode *>::iterator i = selection_op_history.begin(); i != selection_op_history.end(); ++i) {
+ if (n == selection_op_history_it) {
+ _selection_memento->set_state (*(*i), Stateful::current_state_version);
+ selection_redo_action->set_sensitive (true);
+ }
+ ++n;
+ }
+ /* is there an earlier entry? */
+ if ((selection_op_history_it + 1) >= selection_op_history.size()) {
+ selection_undo_action->set_sensitive (false);
+ }
+ }
+}
+
+void
+Editor::redo_selection_op ()
+{
+ if (_session) {
+ if (selection_op_history_it > 0) {
+ selection_op_history_it--;
+ }
+ uint32_t n = 0;
+ for (std::list<XMLNode *>::iterator i = selection_op_history.begin(); i != selection_op_history.end(); ++i) {
+ if (n == selection_op_history_it) {
+ _selection_memento->set_state (*(*i), Stateful::current_state_version);
+ selection_undo_action->set_sensitive (true);
+ }
+ ++n;
+ }
+
+ if (selection_op_history_it == 0) {
+ selection_redo_action->set_sensitive (false);
+ }
+ }
+}
+
void
Editor::begin_reversible_command (string name)
{
if (_session) {
+ before.push_back (&_selection_memento->get_state ());
_session->begin_reversible_command (name);
}
}
Editor::begin_reversible_command (GQuark q)
{
if (_session) {
+ before.push_back (&_selection_memento->get_state ());
_session->begin_reversible_command (q);
}
}
Editor::commit_reversible_command ()
{
if (_session) {
+ if (before.size() == 1) {
+ _session->add_command (new MementoCommand<SelectionMemento>(*(_selection_memento), before.front(), &_selection_memento->get_state ()));
+ undo_action->set_sensitive(true);
+ begin_selection_op_history ();
+ }
+
+ if (before.empty()) {
+ cerr << "Please call begin_reversible_command() before commit_reversible_command()." << endl;
+ } else {
+ before.pop_back();
+ }
+
_session->commit_reversible_command ();
}
}
visible_tracks_selector.AddMenuElem (MenuElem (X_("24"), sigc::bind (sigc::mem_fun(*this, &Editor::set_visible_track_count), 24)));
visible_tracks_selector.AddMenuElem (MenuElem (X_("32"), sigc::bind (sigc::mem_fun(*this, &Editor::set_visible_track_count), 32)));
visible_tracks_selector.AddMenuElem (MenuElem (X_("64"), sigc::bind (sigc::mem_fun(*this, &Editor::set_visible_track_count), 64)));
- visible_tracks_selector.AddMenuElem (MenuElem (_("Selected"), sigc::mem_fun(*this, &Editor::fit_selected_tracks)));
+ visible_tracks_selector.AddMenuElem (MenuElem (_("Selection"), sigc::mem_fun(*this, &Editor::fit_selection)));
visible_tracks_selector.AddMenuElem (MenuElem (_("All"), sigc::bind (sigc::mem_fun(*this, &Editor::set_visible_track_count), 0)));
} else {
visible_tracks_selector.AddMenuElem (MenuElem (_("Fit 1 track"), sigc::bind (sigc::mem_fun(*this, &Editor::set_visible_track_count), 1)));
visible_tracks_selector.AddMenuElem (MenuElem (_("Fit 32 tracks"), sigc::bind (sigc::mem_fun(*this, &Editor::set_visible_track_count), 32)));
visible_tracks_selector.AddMenuElem (MenuElem (_("Fit 48 tracks"), sigc::bind (sigc::mem_fun(*this, &Editor::set_visible_track_count), 48)));
visible_tracks_selector.AddMenuElem (MenuElem (_("Fit All tracks"), sigc::bind (sigc::mem_fun(*this, &Editor::set_visible_track_count), 0)));
- visible_tracks_selector.AddMenuElem (MenuElem (_("Fit Selected tracks"), sigc::mem_fun(*this, &Editor::fit_selected_tracks)));
+ visible_tracks_selector.AddMenuElem (MenuElem (_("Fit Selection"), sigc::mem_fun(*this, &Editor::fit_selection)));
zoom_preset_selector.AddMenuElem (MenuElem (_("Zoom to 10 ms"), sigc::bind (sigc::mem_fun(*this, &Editor::set_zoom_preset), 10)));
zoom_preset_selector.AddMenuElem (MenuElem (_("Zoom to 100 ms"), sigc::bind (sigc::mem_fun(*this, &Editor::set_zoom_preset), 100)));
int h;
string str;
+ DisplaySuspender ds;
if (_visible_track_count > 0) {
h = trackviews_height() / _visible_track_count;
void
Editor::override_visible_track_count ()
{
- _visible_track_count = -_visible_track_count;
+ _visible_track_count = -1;
visible_tracks_selector.set_text ( _("*") );
}
return *_playlist_selector;
}
-Evoral::MusicalTime
+framecnt_t
+Editor::get_paste_offset (framepos_t pos, unsigned paste_count, framecnt_t duration)
+{
+ if (paste_count == 0) {
+ /* don't bother calculating an offset that will be zero anyway */
+ return 0;
+ }
+
+ /* calculate basic unsnapped multi-paste offset */
+ framecnt_t offset = paste_count * duration;
+
+ /* snap offset so pos + offset is aligned to the grid */
+ framepos_t offset_pos = pos + offset;
+ snap_to(offset_pos, RoundUpMaybe);
+ offset = offset_pos - pos;
+
+ return offset;
+}
+
+unsigned
+Editor::get_grid_beat_divisions(framepos_t position)
+{
+ switch (_snap_type) {
+ case SnapToBeatDiv128: return 128;
+ case SnapToBeatDiv64: return 64;
+ case SnapToBeatDiv32: return 32;
+ case SnapToBeatDiv28: return 28;
+ case SnapToBeatDiv24: return 24;
+ case SnapToBeatDiv20: return 20;
+ case SnapToBeatDiv16: return 16;
+ case SnapToBeatDiv14: return 14;
+ case SnapToBeatDiv12: return 12;
+ case SnapToBeatDiv10: return 10;
+ case SnapToBeatDiv8: return 8;
+ case SnapToBeatDiv7: return 7;
+ case SnapToBeatDiv6: return 6;
+ case SnapToBeatDiv5: return 5;
+ case SnapToBeatDiv4: return 4;
+ case SnapToBeatDiv3: return 3;
+ case SnapToBeatDiv2: return 2;
+ default: return 0;
+ }
+ return 0;
+}
+
+Evoral::Beats
Editor::get_grid_type_as_beats (bool& success, framepos_t position)
{
success = true;
+ const unsigned divisions = get_grid_beat_divisions(position);
+ if (divisions) {
+ return Evoral::Beats(1.0 / (double)get_grid_beat_divisions(position));
+ }
+
switch (_snap_type) {
case SnapToBeat:
- return 1.0;
- break;
-
- case SnapToBeatDiv128:
- return 1.0/128.0;
- break;
- case SnapToBeatDiv64:
- return 1.0/64.0;
- break;
- case SnapToBeatDiv32:
- return 1.0/32.0;
- break;
- case SnapToBeatDiv28:
- return 1.0/28.0;
- break;
- case SnapToBeatDiv24:
- return 1.0/24.0;
- break;
- case SnapToBeatDiv20:
- return 1.0/20.0;
- break;
- case SnapToBeatDiv16:
- return 1.0/16.0;
- break;
- case SnapToBeatDiv14:
- return 1.0/14.0;
- break;
- case SnapToBeatDiv12:
- return 1.0/12.0;
- break;
- case SnapToBeatDiv10:
- return 1.0/10.0;
- break;
- case SnapToBeatDiv8:
- return 1.0/8.0;
- break;
- case SnapToBeatDiv7:
- return 1.0/7.0;
- break;
- case SnapToBeatDiv6:
- return 1.0/6.0;
- break;
- case SnapToBeatDiv5:
- return 1.0/5.0;
- break;
- case SnapToBeatDiv4:
- return 1.0/4.0;
- break;
- case SnapToBeatDiv3:
- return 1.0/3.0;
- break;
- case SnapToBeatDiv2:
- return 1.0/2.0;
- break;
-
+ return Evoral::Beats(1.0);
case SnapToBar:
if (_session) {
- return _session->tempo_map().meter_at (position).divisions_per_bar();
+ return Evoral::Beats(_session->tempo_map().meter_at (position).divisions_per_bar());
}
break;
-
- case SnapToCDFrame:
- case SnapToTimecodeFrame:
- case SnapToTimecodeSeconds:
- case SnapToTimecodeMinutes:
- case SnapToSeconds:
- case SnapToMinutes:
- case SnapToRegionStart:
- case SnapToRegionEnd:
- case SnapToRegionSync:
- case SnapToRegionBoundary:
default:
success = false;
break;
}
- return 0.0;
+ return Evoral::Beats();
}
framecnt_t
void
Editor::update_tearoff_visibility()
{
- bool visible = Config->get_keep_tearoffs();
+ bool visible = ARDOUR_UI::config()->get_keep_tearoffs();
_mouse_mode_tearoff->set_visible (visible);
_tools_tearoff->set_visible (visible);
if (_zoom_tearoff) {
}
}
+void
+Editor::reattach_all_tearoffs ()
+{
+ if (_mouse_mode_tearoff) _mouse_mode_tearoff->put_it_back ();
+ if (_tools_tearoff) _tools_tearoff->put_it_back ();
+ if (_zoom_tearoff) _zoom_tearoff->put_it_back ();
+}
+
void
Editor::maximise_editing_space ()
{
void
Editor::clear_playlists (TimeAxisView* v)
{
- begin_reversible_command (_("clear playlists"));
+ begin_reversible_command (_("clear playlists"));
vector<boost::shared_ptr<ARDOUR::Playlist> > playlists;
_session->playlists->get (playlists);
mapover_tracks (sigc::mem_fun (*this, &Editor::mapped_clear_playlist), v, ARDOUR::Properties::select.property_id);
// return key_press_focus_accelerator_handler (*this, ev);
}
+double
+Editor::get_y_origin () const
+{
+ return vertical_adjustment.get_value ();
+}
+
/** Queue up a change to the viewport x origin.
* @param frame New x origin.
*/
redo_visual_stack.push_back (current_visual_state (vs ? vs->gui_state != 0 : false));
- use_visual_state (*vs);
+ if (vs) {
+ use_visual_state (*vs);
+ }
}
void
VisualState* vs = redo_visual_stack.back();
redo_visual_stack.pop_back();
- undo_visual_stack.push_back (current_visual_state (vs ? vs->gui_state != 0 : false));
+ // can 'vs' really be 0? Is there a place that puts NULL pointers onto the stack?
+ // why do we check here?
+ undo_visual_stack.push_back (current_visual_state (vs ? (vs->gui_state != 0) : false));
- use_visual_state (*vs);
+ if (vs) {
+ use_visual_state (*vs);
+ }
}
void
Editor::ensure_visual_change_idle_handler ()
{
if (pending_visual_change.idle_handler_id < 0) {
- pending_visual_change.idle_handler_id = g_idle_add (_idle_visual_changer, this);
+ // 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;
}
}
}
framepos_t
-Editor::get_preferred_edit_position (bool ignore_playhead, bool from_context_menu)
+Editor::get_preferred_edit_position (bool ignore_playhead, bool from_context_menu, bool from_outside_canvas)
{
bool ignored;
framepos_t where = 0;
if(Profile->get_mixbus())
if (ep == EditAtSelectedMarker)
ep=EditAtPlayhead;
-
- if (from_context_menu && (ep == EditAtMouse)) {
+
+ if (from_outside_canvas && (ep == EditAtMouse)) {
+ ep = EditAtPlayhead;
+ } else if (from_context_menu && (ep == EditAtMouse)) {
return canvas_event_sample (&context_click_event, 0, 0);
}
switch (ep) {
case EditAtPlayhead:
- where = _session->audible_frame();
+ if (_dragging_playhead) {
+ if (!mouse_frame (where, ignored)) {
+ /* XXX not right but what can we do ? */
+ return 0;
+ }
+ } else
+ where = _session->audible_frame();
DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("GPEP: use playhead @ %1\n", where));
break;
_session->set_auto_punch_location (loc);
XMLNode &after = _session->locations()->get_state();
_session->add_command (new MementoCommand<Locations>(*(_session->locations()), &before, &after));
- }
- else {
+ } else {
XMLNode &before = tpl->get_state();
tpl->set_hidden (false, this);
tpl->set (start, end);
return regions;
}
+void
+Editor::get_regionviews_by_id (PBD::ID const id, RegionSelection & regions) const
+{
+ for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
+ RouteTimeAxisView* rtav;
+
+ if ((rtav = dynamic_cast<RouteTimeAxisView*> (*i)) != 0) {
+ boost::shared_ptr<Playlist> pl;
+ std::vector<boost::shared_ptr<Region> > results;
+ boost::shared_ptr<Track> tr;
+
+ if ((tr = rtav->track()) == 0) {
+ /* bus */
+ continue;
+ }
+
+ if ((pl = (tr->playlist())) != 0) {
+ boost::shared_ptr<Region> r = pl->region_by_id (id);
+ if (r) {
+ RegionView* rv = rtav->view()->find_view (r);
+ if (rv) {
+ regions.push_back (rv);
+ }
+ }
+ }
+ }
+ }
+}
+
+void
+Editor::get_per_region_note_selection (list<pair<PBD::ID, set<boost::shared_ptr<Evoral::Note<Evoral::Beats> > > > > &selection) const
+{
+
+ for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
+ MidiTimeAxisView* mtav;
+
+ if ((mtav = dynamic_cast<MidiTimeAxisView*> (*i)) != 0) {
+
+ mtav->get_per_region_note_selection (selection);
+ }
+ }
+
+}
+
void
Editor::get_regions_corresponding_to (boost::shared_ptr<Region> region, vector<RegionView*>& regions, bool src_comparison)
{
_routes->redisplay ();
delete dialog;
+
+ if (_session->undo_depth() == 0) {
+ undo_action->set_sensitive(false);
+ }
+ redo_action->set_sensitive(false);
+ begin_selection_op_history ();
+
_have_idled = true;
}
Editor::add_to_idle_resize (TimeAxisView* view, int32_t h)
{
if (resize_idle_id < 0) {
- resize_idle_id = g_idle_add (_idle_resize, this);
+ /* https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#G-PRIORITY-HIGH-IDLE:CAPS
+ * GTK+ uses G_PRIORITY_HIGH_IDLE + 10 for resizing operations, and G_PRIORITY_HIGH_IDLE + 20 for redrawing operations.
+ * (This is done to ensure that any pending resizes are processed before any pending redraws, so that widgets are not redrawn twice unnecessarily.)
+ */
+ resize_idle_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE + 10, _idle_resize, this, NULL);
_pending_resize_amount = 0;
}
}
void
-Editor::region_view_added (RegionView *)
+Editor::region_view_added (RegionView * rv)
{
+ for (list<PBD::ID>::iterator pr = selection->regions.pending.begin (); pr != selection->regions.pending.end (); ++pr) {
+ if (rv->region ()->id () == (*pr)) {
+ selection->add (rv);
+ selection->regions.pending.erase (pr);
+ break;
+ }
+ }
+
+ MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
+ if (mrv) {
+ list<pair<PBD::ID const, list<boost::shared_ptr<Evoral::Note<Evoral::Beats> > > > >::iterator rnote;
+ for (rnote = selection->pending_midi_note_selection.begin(); rnote != selection->pending_midi_note_selection.end(); ++rnote) {
+ if (rv->region()->id () == (*rnote).first) {
+ mrv->select_notes ((*rnote).second);
+ selection->pending_midi_note_selection.erase(rnote);
+ break;
+ }
+ }
+ }
+
_summary->set_background_dirty ();
}
rtv->effective_gain_display ();
- if (internal_editing()) {
- rtv->enter_internal_edit_mode ();
- } else {
- rtv->leave_internal_edit_mode ();
- }
-
rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &Editor::region_view_added));
rtv->view()->RegionViewRemoved.connect (sigc::mem_fun (*this, &Editor::region_view_removed));
}
_control_point_context_menu.popup (event->button.button, event->button.time);
}
+void
+Editor::popup_note_context_menu (ArdourCanvas::Item* item, GdkEvent* event)
+{
+ using namespace Menu_Helpers;
+
+ NoteBase* note = reinterpret_cast<NoteBase*>(item->get_data("notebase"));
+ if (!note) {
+ return;
+ }
+
+ /* We need to get the selection here and pass it to the operations, since
+ popping up the menu will cause a region leave event which clears
+ entered_regionview. */
+
+ MidiRegionView& mrv = note->region_view();
+ const RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ MenuList& items = _note_context_menu.items();
+ items.clear();
+
+ items.push_back(MenuElem(_("Delete"),
+ sigc::mem_fun(mrv, &MidiRegionView::delete_selection)));
+ items.push_back(MenuElem(_("Edit..."),
+ sigc::bind(sigc::mem_fun(*this, &Editor::edit_notes), &mrv)));
+ items.push_back(MenuElem(_("Legatize"),
+ sigc::bind(sigc::mem_fun(*this, &Editor::legatize_regions), rs, false)));
+ items.push_back(MenuElem(_("Quantize..."),
+ sigc::bind(sigc::mem_fun(*this, &Editor::quantize_regions), rs)));
+ items.push_back(MenuElem(_("Remove Overlap"),
+ sigc::bind(sigc::mem_fun(*this, &Editor::legatize_regions), rs, true)));
+ items.push_back(MenuElem(_("Transform..."),
+ sigc::bind(sigc::mem_fun(*this, &Editor::transform_regions), rs)));
+
+ _note_context_menu.popup (event->button.button, event->button.time);
+}
+
void
Editor::zoom_vertical_modifier_released()
{
{
if (parameter == "icon-set") {
while (!_cursor_stack.empty()) {
- _cursor_stack.pop();
+ _cursor_stack.pop_back();
}
_cursors->set_cursor_set (ARDOUR_UI::config()->get_icon_set());
+ _cursor_stack.push_back(_cursors->grabber);
} else if (parameter == "draggable-playhead") {
if (_verbose_cursor) {
playhead_cursor->set_sensitive (ARDOUR_UI::config()->get_draggable_playhead());