2 * Copyright (C) 2005-2017 Paul Davis <paul@linuxaudiosystems.com>
3 * Copyright (C) 2006-2015 David Robillard <d@drobilla.net>
4 * Copyright (C) 2007-2017 Tim Mayberry <mojofunk@gmail.com>
5 * Copyright (C) 2007 Doug McLain <doug@nostar.net>
6 * Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
7 * Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
8 * Copyright (C) 2014-2019 Ben Loftis <ben@harrisonconsoles.com>
9 * Copyright (C) 2015-2017 Nick Mainsbridge <mainsbridge@gmail.com>
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License along
22 * with this program; if not, write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27 #include "gtk2ardour-config.h"
30 #include "gtkmm2ext/utils.h"
32 #include "ardour/profile.h"
33 #include "ardour/rc_configuration.h"
34 #include "ardour/smf_source.h"
36 #include "pbd/error.h"
38 #include "canvas/canvas.h"
39 #include "canvas/rectangle.h"
40 #include "canvas/pixbuf.h"
41 #include "canvas/scroll_group.h"
42 #include "canvas/text.h"
43 #include "canvas/debug.h"
45 #include "ardour_ui.h"
46 #include "automation_time_axis.h"
49 #include "rgb_macros.h"
51 #include "audio_time_axis.h"
52 #include "editor_drag.h"
53 #include "region_view.h"
54 #include "editor_group_tabs.h"
55 #include "editor_summary.h"
56 #include "video_timeline.h"
58 #include "editor_cursors.h"
59 #include "mouse_cursors.h"
60 #include "note_base.h"
61 #include "ui_config.h"
62 #include "verbose_cursor.h"
67 using namespace ARDOUR;
68 using namespace ARDOUR_UI_UTILS;
72 using namespace Gtkmm2ext;
73 using namespace Editing;
76 Editor::initialize_canvas ()
78 _track_canvas_viewport = new ArdourCanvas::GtkCanvasViewport (horizontal_adjustment, vertical_adjustment);
79 _track_canvas = _track_canvas_viewport->canvas ();
81 _track_canvas->set_background_color (UIConfiguration::instance().color ("arrange base"));
82 _track_canvas->use_nsglview ();
84 /* scroll group for items that should not automatically scroll
85 * (e.g verbose cursor). It shares the canvas coordinate space.
87 no_scroll_group = new ArdourCanvas::Container (_track_canvas->root());
89 ArdourCanvas::ScrollGroup* hsg;
90 ArdourCanvas::ScrollGroup* hg;
91 ArdourCanvas::ScrollGroup* cg;
93 h_scroll_group = hg = new ArdourCanvas::ScrollGroup (_track_canvas->root(), ArdourCanvas::ScrollGroup::ScrollsHorizontally);
94 CANVAS_DEBUG_NAME (h_scroll_group, "canvas h scroll");
95 _track_canvas->add_scroller (*hg);
97 hv_scroll_group = hsg = new ArdourCanvas::ScrollGroup (_track_canvas->root(),
98 ArdourCanvas::ScrollGroup::ScrollSensitivity (ArdourCanvas::ScrollGroup::ScrollsVertically|
99 ArdourCanvas::ScrollGroup::ScrollsHorizontally));
100 CANVAS_DEBUG_NAME (hv_scroll_group, "canvas hv scroll");
101 _track_canvas->add_scroller (*hsg);
103 cursor_scroll_group = cg = new ArdourCanvas::ScrollGroup (_track_canvas->root(), ArdourCanvas::ScrollGroup::ScrollsHorizontally);
104 CANVAS_DEBUG_NAME (cursor_scroll_group, "canvas cursor scroll");
105 _track_canvas->add_scroller (*cg);
107 _verbose_cursor = new VerboseCursor (this);
109 /*a group to hold global rects like punch/loop indicators */
110 global_rect_group = new ArdourCanvas::Container (hv_scroll_group);
111 CANVAS_DEBUG_NAME (global_rect_group, "global rect group");
113 transport_loop_range_rect = new ArdourCanvas::Rectangle (global_rect_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, ArdourCanvas::COORD_MAX));
114 CANVAS_DEBUG_NAME (transport_loop_range_rect, "loop rect");
115 transport_loop_range_rect->hide();
117 transport_punch_range_rect = new ArdourCanvas::Rectangle (global_rect_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, ArdourCanvas::COORD_MAX));
118 CANVAS_DEBUG_NAME (transport_punch_range_rect, "punch rect");
119 transport_punch_range_rect->hide();
121 /*a group to hold time (measure) lines */
122 time_line_group = new ArdourCanvas::Container (h_scroll_group);
123 CANVAS_DEBUG_NAME (time_line_group, "time line group");
125 _trackview_group = new ArdourCanvas::Container (hv_scroll_group);
126 CANVAS_DEBUG_NAME (_trackview_group, "Canvas TrackViews");
128 // used as rubberband rect
129 rubberband_rect = new ArdourCanvas::Rectangle (hv_scroll_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, 0.0));
130 rubberband_rect->hide();
132 /* a group to hold stuff while it gets dragged around. Must be the
133 * uppermost (last) group with hv_scroll_group as a parent
135 _drag_motion_group = new ArdourCanvas::Container (hv_scroll_group);
136 CANVAS_DEBUG_NAME (_drag_motion_group, "Canvas Drag Motion");
138 /* TIME BAR CANVAS */
140 _time_markers_group = new ArdourCanvas::Container (h_scroll_group);
141 CANVAS_DEBUG_NAME (_time_markers_group, "time bars");
143 cd_marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, 0.0));
144 CANVAS_DEBUG_NAME (cd_marker_group, "cd marker group");
145 /* the vide is temporarily placed a the same location as the
146 cd_marker_group, but is moved later.
148 videotl_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple(0.0, 0.0));
149 CANVAS_DEBUG_NAME (videotl_group, "videotl group");
150 marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, timebar_height + 1.0));
151 CANVAS_DEBUG_NAME (marker_group, "marker group");
152 transport_marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 2.0) + 1.0));
153 CANVAS_DEBUG_NAME (transport_marker_group, "transport marker group");
154 range_marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 3.0) + 1.0));
155 CANVAS_DEBUG_NAME (range_marker_group, "range marker group");
156 tempo_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 4.0) + 1.0));
157 CANVAS_DEBUG_NAME (tempo_group, "tempo group");
158 meter_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 5.0) + 1.0));
159 CANVAS_DEBUG_NAME (meter_group, "meter group");
161 meter_bar = new ArdourCanvas::Rectangle (meter_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
162 CANVAS_DEBUG_NAME (meter_bar, "meter Bar");
163 meter_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
165 tempo_bar = new ArdourCanvas::Rectangle (tempo_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
166 CANVAS_DEBUG_NAME (tempo_bar, "Tempo Bar");
167 tempo_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
169 range_marker_bar = new ArdourCanvas::Rectangle (range_marker_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
170 CANVAS_DEBUG_NAME (range_marker_bar, "Range Marker Bar");
171 range_marker_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
173 transport_marker_bar = new ArdourCanvas::Rectangle (transport_marker_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
174 CANVAS_DEBUG_NAME (transport_marker_bar, "transport Marker Bar");
175 transport_marker_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
177 marker_bar = new ArdourCanvas::Rectangle (marker_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
178 CANVAS_DEBUG_NAME (marker_bar, "Marker Bar");
179 marker_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
181 cd_marker_bar = new ArdourCanvas::Rectangle (cd_marker_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
182 CANVAS_DEBUG_NAME (cd_marker_bar, "CD Marker Bar");
183 cd_marker_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
185 ARDOUR_UI::instance()->video_timeline = new VideoTimeLine(this, videotl_group, (timebar_height * videotl_bar_height));
187 cd_marker_bar_drag_rect = new ArdourCanvas::Rectangle (cd_marker_group, ArdourCanvas::Rect (0.0, 0.0, 100, timebar_height));
188 CANVAS_DEBUG_NAME (cd_marker_bar_drag_rect, "cd marker drag");
189 cd_marker_bar_drag_rect->set_outline (false);
190 cd_marker_bar_drag_rect->hide ();
192 range_bar_drag_rect = new ArdourCanvas::Rectangle (range_marker_group, ArdourCanvas::Rect (0.0, 0.0, 100, timebar_height));
193 CANVAS_DEBUG_NAME (range_bar_drag_rect, "range drag");
194 range_bar_drag_rect->set_outline (false);
195 range_bar_drag_rect->hide ();
197 transport_bar_drag_rect = new ArdourCanvas::Rectangle (transport_marker_group, ArdourCanvas::Rect (0.0, 0.0, 100, timebar_height));
198 CANVAS_DEBUG_NAME (transport_bar_drag_rect, "transport drag");
199 transport_bar_drag_rect->set_outline (false);
200 transport_bar_drag_rect->hide ();
202 transport_punchin_line = new ArdourCanvas::Line (hv_scroll_group);
203 transport_punchin_line->set_x0 (0);
204 transport_punchin_line->set_y0 (0);
205 transport_punchin_line->set_x1 (0);
206 transport_punchin_line->set_y1 (ArdourCanvas::COORD_MAX);
207 transport_punchin_line->hide ();
209 transport_punchout_line = new ArdourCanvas::Line (hv_scroll_group);
210 transport_punchout_line->set_x0 (0);
211 transport_punchout_line->set_y0 (0);
212 transport_punchout_line->set_x1 (0);
213 transport_punchout_line->set_y1 (ArdourCanvas::COORD_MAX);
214 transport_punchout_line->hide();
216 tempo_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_tempo_bar_event), tempo_bar));
217 meter_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_meter_bar_event), meter_bar));
218 marker_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_marker_bar_event), marker_bar));
219 cd_marker_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_cd_marker_bar_event), cd_marker_bar));
220 videotl_group->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_videotl_bar_event), videotl_group));
221 range_marker_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_range_marker_bar_event), range_marker_bar));
222 transport_marker_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_transport_marker_bar_event), transport_marker_bar));
224 playhead_cursor = new EditorCursor (*this, &Editor::canvas_playhead_cursor_event);
226 snapped_cursor = new EditorCursor (*this);
228 _canvas_drop_zone = new ArdourCanvas::Rectangle (hv_scroll_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, 0.0));
229 /* this thing is transparent */
230 _canvas_drop_zone->set_fill (false);
231 _canvas_drop_zone->set_outline (false);
232 _canvas_drop_zone->Event.connect (sigc::mem_fun (*this, &Editor::canvas_drop_zone_event));
234 /* these signals will initially be delivered to the canvas itself, but if they end up remaining unhandled, they are passed to Editor-level
238 _track_canvas->signal_scroll_event().connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_scroll_event), true));
239 _track_canvas->signal_motion_notify_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_motion_notify_event));
240 _track_canvas->signal_button_press_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_button_press_event));
241 _track_canvas->signal_button_release_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_button_release_event));
242 _track_canvas->signal_drag_motion().connect (sigc::mem_fun (*this, &Editor::track_canvas_drag_motion));
243 _track_canvas->signal_key_press_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_key_press));
244 _track_canvas->signal_key_release_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_key_release));
246 _track_canvas->set_name ("EditorMainCanvas");
247 _track_canvas->add_events (Gdk::POINTER_MOTION_HINT_MASK | Gdk::SCROLL_MASK | Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
248 _track_canvas->signal_leave_notify_event().connect (sigc::mem_fun(*this, &Editor::left_track_canvas), false);
249 _track_canvas->signal_enter_notify_event().connect (sigc::mem_fun(*this, &Editor::entered_track_canvas), false);
250 _track_canvas->set_flags (CAN_FOCUS);
252 _track_canvas->PreRender.connect (sigc::mem_fun(*this, &Editor::pre_render));
254 /* set up drag-n-drop */
256 vector<TargetEntry> target_table;
258 target_table.push_back (TargetEntry ("regions")); // DnD from the region list will generate this target
259 target_table.push_back (TargetEntry ("sources")); // DnD from the source list will generate this target
260 target_table.push_back (TargetEntry ("text/uri-list"));
261 target_table.push_back (TargetEntry ("text/plain"));
262 target_table.push_back (TargetEntry ("application/x-rootwin-drop"));
264 _track_canvas->drag_dest_set (target_table);
265 _track_canvas->signal_drag_data_received().connect (sigc::mem_fun(*this, &Editor::track_canvas_drag_data_received));
267 _track_canvas_viewport->signal_size_allocate().connect (sigc::mem_fun(*this, &Editor::track_canvas_viewport_allocate));
269 initialize_rulers ();
271 UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &Editor::color_handler));
277 Editor::track_canvas_viewport_allocate (Gtk::Allocation alloc)
279 _canvas_viewport_allocation = alloc;
280 track_canvas_viewport_size_allocated ();
284 Editor::track_canvas_viewport_size_allocated ()
286 bool height_changed = _visible_canvas_height != _canvas_viewport_allocation.get_height();
288 _visible_canvas_width = _canvas_viewport_allocation.get_width ();
289 _visible_canvas_height = _canvas_viewport_allocation.get_height ();
291 _canvas_drop_zone->set_y1 (_canvas_drop_zone->y0() + (_visible_canvas_height - 20.0));
295 if (height_changed) {
297 for (LocationMarkerMap::iterator i = location_markers.begin(); i != location_markers.end(); ++i) {
298 i->second->canvas_height_set (_visible_canvas_height);
301 vertical_adjustment.set_page_size (_visible_canvas_height);
302 if ((vertical_adjustment.get_value() + _visible_canvas_height) >= vertical_adjustment.get_upper()) {
304 We're increasing the size of the canvas while the bottom is visible.
305 We scroll down to keep in step with the controls layout.
307 vertical_adjustment.set_value (_full_canvas_height - _visible_canvas_height);
310 set_visible_track_count (_visible_track_count);
313 update_fixed_rulers();
314 redisplay_grid (false);
315 _summary->set_overlays_dirty ();
319 Editor::reset_controls_layout_width ()
321 GtkRequisition req = { 0, 0 };
324 edit_controls_vbox.size_request (req);
327 if (_group_tabs->is_visible()) {
328 _group_tabs->size_request (req);
332 /* the controls layout has no horizontal scrolling, its visible
333 width is always equal to the total width of its contents.
336 controls_layout.property_width() = w;
337 controls_layout.property_width_request() = w;
341 Editor::reset_controls_layout_height (int32_t h)
343 /* ensure that the rect that represents the "bottom" of the canvas
344 * (the drag-n-drop zone) is, in fact, at the bottom.
347 _canvas_drop_zone->set_position (ArdourCanvas::Duple (0, h));
349 /* track controls layout must span the full height of "h" (all tracks)
350 * plus the bottom rect.
353 h += _canvas_drop_zone->height ();
355 /* set the height of the scrollable area (i.e. the sum of all contained widgets)
356 * for the controls layout. The size request is set elsewhere.
359 controls_layout.property_height() = h;
364 Editor::track_canvas_map_handler (GdkEventAny* /*ev*/)
366 if (!_cursor_stack.empty()) {
367 set_canvas_cursor (get_canvas_cursor());
369 PBD::error << "cursor stack is empty" << endmsg;
374 /** This is called when something is dropped onto the track canvas */
376 Editor::track_canvas_drag_data_received (const RefPtr<Gdk::DragContext>& context,
378 const SelectionData& data,
379 guint info, guint time)
381 if (!ARDOUR_UI_UTILS::engine_is_running ()) {
384 if (data.get_target() == X_("regions")) {
385 drop_regions (context, x, y, data, info, time, true);
386 } else if (data.get_target() == X_("sources")) {
387 drop_regions (context, x, y, data, info, time, false);
389 drop_paths (context, x, y, data, info, time);
394 Editor::idle_drop_paths (vector<string> paths, samplepos_t sample, double ypos, bool copy)
396 drop_paths_part_two (paths, sample, ypos, copy);
401 Editor::drop_paths_part_two (const vector<string>& paths, samplepos_t sample, double ypos, bool copy)
403 RouteTimeAxisView* tv;
405 /* MIDI files must always be imported, because we consider them
406 * writable. So split paths into two vectors, and follow the import
407 * path on the MIDI part.
410 vector<string> midi_paths;
411 vector<string> audio_paths;
413 for (vector<string>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
414 if (SMFSource::safe_midi_file_extension (*i)) {
415 midi_paths.push_back (*i);
417 audio_paths.push_back (*i);
422 std::pair<TimeAxisView*, int> const tvp = trackview_by_y_position (ypos, false);
423 if (tvp.first == 0) {
425 /* drop onto canvas background: create new tracks */
428 InstrumentSelector is; // instantiation builds instrument-list and sets default.
429 do_import (midi_paths, Editing::ImportDistinctFiles, ImportAsTrack, SrcBest, SMFTrackName, SMFTempoIgnore, sample, is.selected_instrument());
431 if (UIConfiguration::instance().get_only_copy_imported_files() || copy) {
432 do_import (audio_paths, Editing::ImportDistinctFiles, Editing::ImportAsTrack,
433 SrcBest, SMFTrackName, SMFTempoIgnore, sample);
435 do_embed (audio_paths, Editing::ImportDistinctFiles, ImportAsTrack, sample);
438 } else if ((tv = dynamic_cast<RouteTimeAxisView*> (tvp.first)) != 0) {
440 /* check that its a track, not a bus */
443 /* select the track, then embed/import */
446 do_import (midi_paths, Editing::ImportSerializeFiles, ImportToTrack,
447 SrcBest, SMFTrackName, SMFTempoIgnore, sample);
449 if (UIConfiguration::instance().get_only_copy_imported_files() || copy) {
450 do_import (audio_paths, Editing::ImportSerializeFiles, Editing::ImportToTrack,
451 SrcBest, SMFTrackName, SMFTempoIgnore, sample);
453 do_embed (audio_paths, Editing::ImportSerializeFiles, ImportToTrack, sample);
460 Editor::drop_paths (const RefPtr<Gdk::DragContext>& context,
462 const SelectionData& data,
463 guint info, guint time)
465 vector<string> paths;
469 if (convert_drop_to_paths (paths, context, x, y, data, info, time) == 0) {
471 /* D-n-D coordinates are window-relative, so convert to canvas coordinates
474 ev.type = GDK_BUTTON_RELEASE;
478 MusicSample when (window_event_sample (&ev, 0, &cy), 0);
481 bool copy = ((context->get_actions() & (Gdk::ACTION_COPY | Gdk::ACTION_LINK | Gdk::ACTION_MOVE)) == Gdk::ACTION_COPY);
483 /* We are not allowed to call recursive main event loops from within
484 the main event loop with GTK/Quartz. Since import/embed wants
485 to push up a progress dialog, defer all this till we go idle.
487 Glib::signal_idle().connect (sigc::bind (sigc::mem_fun (*this, &Editor::idle_drop_paths), paths, when.sample, cy, copy));
489 drop_paths_part_two (paths, when.sample, cy, copy);
493 context->drag_finish (true, false, time);
496 /** @param allow_horiz true to allow horizontal autoscroll, otherwise false.
498 * @param allow_vert true to allow vertical autoscroll, otherwise false.
502 Editor::maybe_autoscroll (bool allow_horiz, bool allow_vert, bool from_headers)
504 Gtk::Window* toplevel = dynamic_cast<Gtk::Window*>(contents().get_toplevel());
510 if (!UIConfiguration::instance().get_autoscroll_editor () || autoscroll_active ()) {
514 /* define a rectangular boundary for scrolling. If the mouse moves
515 * outside of this area and/or continue to be outside of this area,
516 * then we will continuously auto-scroll the canvas in the appropriate
519 * the boundary is defined in coordinates relative to the toplevel
520 * window since that is what we're going to call ::get_pointer() on
521 * during autoscrolling to determine if we're still outside the
525 ArdourCanvas::Rect scrolling_boundary;
526 Gtk::Allocation alloc;
529 alloc = controls_layout.get_allocation ();
533 controls_layout.get_parent()->translate_coordinates (*toplevel,
534 alloc.get_x(), alloc.get_y(),
537 scrolling_boundary = ArdourCanvas::Rect (wx, wy, wx + alloc.get_width(), wy + alloc.get_height());
541 alloc = _track_canvas_viewport->get_allocation ();
543 /* reduce height by the height of the timebars, which happens
544 to correspond to the position of the hv_scroll_group.
547 alloc.set_height (alloc.get_height() - hv_scroll_group->position().y);
548 alloc.set_y (alloc.get_y() + hv_scroll_group->position().y);
550 /* now reduce it again so that we start autoscrolling before we
551 * move off the top or bottom of the canvas
554 alloc.set_height (alloc.get_height() - 20);
555 alloc.set_y (alloc.get_y() + 10);
557 /* the effective width of the autoscroll boundary so
558 that we start scrolling before we hit the edge.
560 this helps when the window is slammed up against the
561 right edge of the screen, making it hard to scroll
565 if (alloc.get_width() > 20) {
566 alloc.set_width (alloc.get_width() - 20);
567 alloc.set_x (alloc.get_x() + 10);
572 _track_canvas_viewport->get_parent()->translate_coordinates (*toplevel,
573 alloc.get_x(), alloc.get_y(),
576 scrolling_boundary = ArdourCanvas::Rect (wx, wy, wx + alloc.get_width(), wy + alloc.get_height());
580 Gdk::ModifierType mask;
582 toplevel->get_window()->get_pointer (x, y, mask);
584 if ((allow_horiz && ((x < scrolling_boundary.x0 && _leftmost_sample > 0) || x >= scrolling_boundary.x1)) ||
585 (allow_vert && ((y < scrolling_boundary.y0 && vertical_adjustment.get_value() > 0)|| y >= scrolling_boundary.y1))) {
586 start_canvas_autoscroll (allow_horiz, allow_vert, scrolling_boundary);
591 Editor::drag_active () const
593 return _drags->active();
597 Editor::preview_video_drag_active () const
599 return _drags->preview_video ();
603 Editor::autoscroll_active () const
605 return autoscroll_connection.connected ();
608 std::pair <samplepos_t,samplepos_t>
609 Editor::session_gui_extents (bool use_extra) const
612 return std::pair <samplepos_t,samplepos_t>(max_samplepos,0);
615 samplecnt_t session_extent_start = _session->current_start_sample();
616 samplecnt_t session_extent_end = _session->current_end_sample();
618 /* calculate the extents of all regions in every playlist
619 * NOTE: we should listen to playlists, and cache these values so we don't calculate them every time.
622 boost::shared_ptr<RouteList> rl = _session->get_routes();
623 for (RouteList::iterator r = rl->begin(); r != rl->end(); ++r) {
624 boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*r);
626 boost::shared_ptr<Playlist> pl = tr->playlist();
627 if (pl && !pl->all_regions_empty()) {
628 pair<samplepos_t, samplepos_t> e;
629 e = pl->get_extent();
630 if (e.first < session_extent_start) {
631 session_extent_start = e.first;
633 if (e.second > session_extent_end) {
634 session_extent_end = e.second;
641 /* ToDo: also incorporate automation regions (in case the session has no audio/midi but is just used for automating plugins or the like) */
643 /* add additional time to the ui extents (user-defined in config) */
645 samplecnt_t const extra = UIConfiguration::instance().get_extra_ui_extents_time() * 60 * _session->nominal_sample_rate();
646 session_extent_end += extra;
647 session_extent_start -= extra;
651 if (session_extent_end > max_samplepos) {
652 session_extent_end = max_samplepos;
654 if (session_extent_start < 0) {
655 session_extent_start = 0;
658 std::pair <samplepos_t,samplepos_t> ret (session_extent_start, session_extent_end);
663 Editor::autoscroll_canvas ()
666 Gdk::ModifierType mask;
667 sampleoffset_t dx = 0;
668 bool no_stop = false;
669 Gtk::Window* toplevel = dynamic_cast<Gtk::Window*>(contents().get_toplevel());
675 toplevel->get_window()->get_pointer (x, y, mask);
678 bool vertical_motion = false;
680 if (autoscroll_horizontal_allowed) {
682 samplepos_t new_sample = _leftmost_sample;
686 if (x > autoscroll_boundary.x1) {
688 /* bring it back into view */
689 dx = x - autoscroll_boundary.x1;
690 dx += 10 + (2 * (autoscroll_cnt/2));
692 dx = pixel_to_sample (dx);
694 dx *= UIConfiguration::instance().get_draggable_playhead_speed();
696 if (_leftmost_sample < max_samplepos - dx) {
697 new_sample = _leftmost_sample + dx;
699 new_sample = max_samplepos;
704 } else if (x < autoscroll_boundary.x0) {
706 dx = autoscroll_boundary.x0 - x;
707 dx += 10 + (2 * (autoscroll_cnt/2));
709 dx = pixel_to_sample (dx);
711 dx *= UIConfiguration::instance().get_draggable_playhead_speed();
713 if (_leftmost_sample >= dx) {
714 new_sample = _leftmost_sample - dx;
722 if (new_sample != _leftmost_sample) {
723 vc.time_origin = new_sample;
724 vc.add (VisualChange::TimeOrigin);
728 if (autoscroll_vertical_allowed) {
730 // const double vertical_pos = vertical_adjustment.get_value();
731 const int speed_factor = 10;
735 if (y < autoscroll_boundary.y0) {
737 /* scroll to make higher tracks visible */
739 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
740 scroll_up_one_track ();
741 vertical_motion = true;
745 } else if (y > autoscroll_boundary.y1) {
747 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
748 scroll_down_one_track ();
749 vertical_motion = true;
756 if (vc.pending || vertical_motion) {
758 /* change horizontal first */
764 /* now send a motion event to notify anyone who cares
765 that we have moved to a new location (because we scrolled)
770 ev.type = GDK_MOTION_NOTIFY;
771 ev.state = Gdk::BUTTON1_MASK;
773 /* the motion handler expects events in canvas coordinate space */
775 /* we asked for the mouse position above (::get_pointer()) via
776 * our own top level window (we being the Editor). Convert into
777 * coordinates within the canvas window.
783 toplevel->translate_coordinates (*_track_canvas, x, y, cx, cy);
785 /* clamp x and y to remain within the autoscroll boundary,
786 * which is defined in window coordinates
789 x = min (max ((ArdourCanvas::Coord) cx, autoscroll_boundary.x0), autoscroll_boundary.x1);
790 y = min (max ((ArdourCanvas::Coord) cy, autoscroll_boundary.y0), autoscroll_boundary.y1);
792 /* now convert from Editor window coordinates to canvas
796 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
801 motion_handler (0, (GdkEvent*) &ev, true);
803 } else if (no_stop) {
805 /* not changing visual state but pointer is outside the scrolling boundary
806 * so we still need to deliver a fake motion event
811 ev.type = GDK_MOTION_NOTIFY;
812 ev.state = Gdk::BUTTON1_MASK;
814 /* the motion handler expects events in canvas coordinate space */
816 /* first convert from Editor window coordinates to canvas
823 /* clamp x and y to remain within the visible area. except
824 * .. if horizontal scrolling is allowed, always allow us to
828 if (autoscroll_horizontal_allowed) {
829 x = min (max ((ArdourCanvas::Coord) x, 0.0), autoscroll_boundary.x1);
831 x = min (max ((ArdourCanvas::Coord) x, autoscroll_boundary.x0), autoscroll_boundary.x1);
833 y = min (max ((ArdourCanvas::Coord) y, autoscroll_boundary.y0), autoscroll_boundary.y1);
835 toplevel->translate_coordinates (*_track_canvas_viewport, x, y, cx, cy);
837 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
842 motion_handler (0, (GdkEvent*) &ev, true);
845 stop_canvas_autoscroll ();
851 return true; /* call me again */
855 Editor::start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary)
861 stop_canvas_autoscroll ();
863 autoscroll_horizontal_allowed = allow_horiz;
864 autoscroll_vertical_allowed = allow_vert;
865 autoscroll_boundary = boundary;
867 /* do the first scroll right now
870 autoscroll_canvas ();
872 /* scroll again at very very roughly 30FPS */
874 autoscroll_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &Editor::autoscroll_canvas), 30);
878 Editor::stop_canvas_autoscroll ()
880 autoscroll_connection.disconnect ();
884 Editor::EnterContext*
885 Editor::get_enter_context(ItemType type)
887 for (ssize_t i = _enter_stack.size() - 1; i >= 0; --i) {
888 if (_enter_stack[i].item_type == type) {
889 return &_enter_stack[i];
896 Editor::left_track_canvas (GdkEventCrossing* ev)
898 const bool was_within = within_track_canvas;
900 within_track_canvas = false;
901 set_entered_track (0);
902 set_entered_regionview (0);
903 reset_canvas_action_sensitivity (false);
906 if (ev->detail == GDK_NOTIFY_NONLINEAR ||
907 ev->detail == GDK_NOTIFY_NONLINEAR_VIRTUAL) {
908 /* context menu or something similar */
909 sensitize_the_right_region_actions (false);
911 sensitize_the_right_region_actions (true);
919 Editor::entered_track_canvas (GdkEventCrossing* ev)
921 const bool was_within = within_track_canvas;
922 within_track_canvas = true;
923 reset_canvas_action_sensitivity (true);
926 if (ev->detail == GDK_NOTIFY_NONLINEAR ||
927 ev->detail == GDK_NOTIFY_NONLINEAR_VIRTUAL) {
928 /* context menu or something similar */
929 sensitize_the_right_region_actions (false);
931 sensitize_the_right_region_actions (true);
939 Editor::ensure_time_axis_view_is_visible (TimeAxisView const & track, bool at_top)
941 if (track.hidden()) {
945 /* compute visible area of trackview group, as offsets from top of
949 double const current_view_min_y = vertical_adjustment.get_value();
950 double const current_view_max_y = current_view_min_y + vertical_adjustment.get_page_size();
952 double const track_min_y = track.y_position ();
953 double const track_max_y = track.y_position () + track.effective_height ();
956 (track_min_y >= current_view_min_y &&
957 track_max_y < current_view_max_y)) {
958 /* already visible, and caller did not ask to place it at the
959 * top of the track canvas
967 new_value = track_min_y;
969 if (track_min_y < current_view_min_y) {
970 // Track is above the current view
971 new_value = track_min_y;
972 } else if (track_max_y > current_view_max_y) {
973 // Track is below the current view
974 new_value = track.y_position () + track.effective_height() - vertical_adjustment.get_page_size();
976 new_value = track_min_y;
980 vertical_adjustment.set_value(new_value);
983 /** Called when the main vertical_adjustment has changed */
985 Editor::tie_vertical_scrolling ()
987 if (pending_visual_change.idle_handler_id < 0) {
988 _summary->set_overlays_dirty ();
993 Editor::set_horizontal_position (double p)
995 horizontal_adjustment.set_value (p);
997 _leftmost_sample = (samplepos_t) floor (p * samples_per_pixel);
1001 Editor::color_handler()
1003 Gtkmm2ext::Color base = UIConfiguration::instance().color ("ruler base");
1004 Gtkmm2ext::Color text = UIConfiguration::instance().color ("ruler text");
1005 timecode_ruler->set_fill_color (base);
1006 timecode_ruler->set_outline_color (text);
1007 minsec_ruler->set_fill_color (base);
1008 minsec_ruler->set_outline_color (text);
1009 samples_ruler->set_fill_color (base);
1010 samples_ruler->set_outline_color (text);
1011 bbt_ruler->set_fill_color (base);
1012 bbt_ruler->set_outline_color (text);
1014 playhead_cursor->set_color (UIConfiguration::instance().color ("play head"));
1016 meter_bar->set_fill_color (UIConfiguration::instance().color_mod ("meter bar", "marker bar"));
1017 meter_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1019 tempo_bar->set_fill_color (UIConfiguration::instance().color_mod ("tempo bar", "marker bar"));
1020 tempo_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1022 marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("marker bar", "marker bar"));
1023 marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1025 cd_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("cd marker bar", "marker bar"));
1026 cd_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1028 range_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("range marker bar", "marker bar"));
1029 range_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1031 transport_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("transport marker bar", "marker bar"));
1032 transport_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1034 cd_marker_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
1035 cd_marker_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
1037 range_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
1038 range_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
1040 transport_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("transport drag rect"));
1041 transport_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("transport drag rect"));
1043 transport_loop_range_rect->set_fill_color (UIConfiguration::instance().color_mod ("transport loop rect", "loop rectangle"));
1044 transport_loop_range_rect->set_outline_color (UIConfiguration::instance().color ("transport loop rect"));
1046 transport_punch_range_rect->set_fill_color (UIConfiguration::instance().color ("transport punch rect"));
1047 transport_punch_range_rect->set_outline_color (UIConfiguration::instance().color ("transport punch rect"));
1049 transport_punchin_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
1050 transport_punchout_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
1052 rubberband_rect->set_outline_color (UIConfiguration::instance().color ("rubber band rect"));
1053 rubberband_rect->set_fill_color (UIConfiguration::instance().color_mod ("rubber band rect", "selection rect"));
1055 location_marker_color = UIConfiguration::instance().color ("location marker");
1056 location_range_color = UIConfiguration::instance().color ("location range");
1057 location_cd_marker_color = UIConfiguration::instance().color ("location cd marker");
1058 location_loop_color = UIConfiguration::instance().color ("location loop");
1059 location_punch_color = UIConfiguration::instance().color ("location punch");
1061 refresh_location_display ();
1063 NoteBase::set_colors ();
1065 /* redraw the whole thing */
1066 _track_canvas->set_background_color (UIConfiguration::instance().color ("arrange base"));
1067 _track_canvas->queue_draw ();
1070 redisplay_grid (true);
1073 _session->tempo_map().apply_with_metrics (*this, &Editor::draw_metric_marks); // redraw metric markers
1078 Editor::horizontal_position () const
1080 return sample_to_pixel (_leftmost_sample);
1084 Editor::track_canvas_key_press (GdkEventKey*)
1090 Editor::track_canvas_key_release (GdkEventKey*)
1096 Editor::clamp_verbose_cursor_x (double x)
1101 x = min (_visible_canvas_width - 200.0, x);
1107 Editor::clamp_verbose_cursor_y (double y)
1110 y = min (_visible_canvas_height - 50, y);
1114 ArdourCanvas::GtkCanvasViewport*
1115 Editor::get_track_canvas() const
1117 return _track_canvas_viewport;
1121 Editor::get_canvas_cursor () const
1123 /* The top of the cursor stack is always the currently visible cursor. */
1124 return _cursor_stack.back();
1128 Editor::set_canvas_cursor (Gdk::Cursor* cursor)
1130 Glib::RefPtr<Gdk::Window> win = _track_canvas->get_window();
1132 if (win && !_cursors->is_invalid (cursor)) {
1133 /* glibmm 2.4 doesn't allow null cursor pointer because it uses
1134 a Gdk::Cursor& as the argument to Gdk::Window::set_cursor().
1135 But a null pointer just means "use parent window cursor",
1136 and so should be allowed. Gtkmm 3.x has fixed this API.
1138 For now, drop down and use C API
1140 gdk_window_set_cursor (win->gobj(), cursor ? cursor->gobj() : 0);
1145 Editor::push_canvas_cursor (Gdk::Cursor* cursor)
1147 if (!_cursors->is_invalid (cursor)) {
1148 _cursor_stack.push_back (cursor);
1149 set_canvas_cursor (cursor);
1151 return _cursor_stack.size() - 1;
1155 Editor::pop_canvas_cursor ()
1158 if (_cursor_stack.size() <= 1) {
1159 PBD::error << "attempt to pop default cursor" << endmsg;
1163 _cursor_stack.pop_back();
1164 if (_cursor_stack.back()) {
1165 /* Popped to an existing cursor, we're done. Otherwise, the
1166 context that created this cursor has been destroyed, so we need
1167 to skip to the next down the stack. */
1168 set_canvas_cursor (_cursor_stack.back());
1175 Editor::which_trim_cursor (bool left) const
1177 if (!entered_regionview) {
1181 Trimmable::CanTrim ct = entered_regionview->region()->can_trim ();
1184 if (ct & Trimmable::FrontTrimEarlier) {
1185 return _cursors->left_side_trim;
1187 return _cursors->left_side_trim_right_only;
1190 if (ct & Trimmable::EndTrimLater) {
1191 return _cursors->right_side_trim;
1193 return _cursors->right_side_trim_left_only;
1199 Editor::which_mode_cursor () const
1201 Gdk::Cursor* mode_cursor = MouseCursors::invalid_cursor ();
1203 switch (mouse_mode) {
1205 mode_cursor = _cursors->selector;
1209 mode_cursor = _cursors->scissors;
1214 /* don't use mode cursor, pick a grabber cursor based on the item */
1218 mode_cursor = _cursors->midi_pencil;
1222 mode_cursor = _cursors->time_fx; // just use playhead
1226 mode_cursor = _cursors->speaker;
1230 /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
1231 if (get_smart_mode()) {
1234 get_pointer_position (x, y);
1236 if (x >= 0 && y >= 0) {
1238 vector<ArdourCanvas::Item const *> items;
1240 /* Note how we choose a specific scroll group to get
1241 * items from. This could be problematic.
1244 hv_scroll_group->add_items_at_point (ArdourCanvas::Duple (x,y), items);
1246 // first item will be the upper most
1248 if (!items.empty()) {
1249 const ArdourCanvas::Item* i = items.front();
1251 if (i && i->parent() && i->parent()->get_data (X_("timeselection"))) {
1252 pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y);
1253 if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
1254 mode_cursor = _cursors->up_down;
1265 Editor::which_track_cursor () const
1267 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
1269 switch (_join_object_range_state) {
1270 case JOIN_OBJECT_RANGE_NONE:
1271 case JOIN_OBJECT_RANGE_OBJECT:
1272 cursor = _cursors->grabber;
1274 case JOIN_OBJECT_RANGE_RANGE:
1275 cursor = _cursors->selector;
1283 Editor::which_canvas_cursor(ItemType type) const
1285 Gdk::Cursor* cursor = which_mode_cursor ();
1287 if (mouse_mode == MouseRange) {
1289 case StartSelectionTrimItem:
1290 cursor = _cursors->left_side_trim;
1292 case EndSelectionTrimItem:
1293 cursor = _cursors->right_side_trim;
1300 if ((mouse_mode == MouseObject || get_smart_mode ()) ||
1301 mouse_mode == MouseContent) {
1303 /* find correct cursor to use in object/smart mode */
1306 /* We don't choose a cursor for these items on top of a region view,
1307 because this would push a new context on the enter stack which
1308 means switching the region context for things like smart mode
1309 won't actualy change the cursor. */
1312 case AutomationTrackItem:
1313 cursor = which_track_cursor ();
1315 case PlayheadCursorItem:
1316 cursor = _cursors->grabber;
1319 cursor = _cursors->selector;
1321 case ControlPointItem:
1322 cursor = _cursors->fader;
1325 cursor = _cursors->cross_hair;
1327 case AutomationLineItem:
1328 cursor = _cursors->cross_hair;
1330 case StartSelectionTrimItem:
1331 cursor = _cursors->left_side_trim;
1333 case EndSelectionTrimItem:
1334 cursor = _cursors->right_side_trim;
1337 cursor = _cursors->fade_in;
1339 case FadeInHandleItem:
1340 cursor = _cursors->fade_in;
1342 case FadeInTrimHandleItem:
1343 cursor = _cursors->fade_in;
1346 cursor = _cursors->fade_out;
1348 case FadeOutHandleItem:
1349 cursor = _cursors->fade_out;
1351 case FadeOutTrimHandleItem:
1352 cursor = _cursors->fade_out;
1354 case FeatureLineItem:
1355 cursor = _cursors->cross_hair;
1357 case LeftFrameHandle:
1358 if (effective_mouse_mode() == MouseObject) // (smart mode): if the user is in the btm half, show the trim cursor
1359 cursor = which_trim_cursor (true);
1361 cursor = _cursors->selector; // (smart mode): in the top half, just show the selection (range) cursor
1363 case RightFrameHandle:
1364 if (effective_mouse_mode() == MouseObject) // see above
1365 cursor = which_trim_cursor (false);
1367 cursor = _cursors->selector;
1369 case RegionViewName:
1370 case RegionViewNameHighlight:
1371 /* the trim bar is used for trimming, but we have to determine if we are on the left or right side of the region */
1372 cursor = MouseCursors::invalid_cursor ();
1373 if (entered_regionview) {
1375 bool in_track_canvas;
1376 if (mouse_sample (where, in_track_canvas)) {
1377 samplepos_t start = entered_regionview->region()->first_sample();
1378 samplepos_t end = entered_regionview->region()->last_sample();
1379 cursor = which_trim_cursor ((where - start) < (end - where));
1383 case StartCrossFadeItem:
1384 cursor = _cursors->fade_in;
1386 case EndCrossFadeItem:
1387 cursor = _cursors->fade_out;
1389 case CrossfadeViewItem:
1390 cursor = _cursors->cross_hair;
1393 cursor = _cursors->grabber_note;
1398 } else if (mouse_mode == MouseDraw) {
1400 /* ControlPointItem is not really specific to region gain mode
1401 but it is the same cursor so don't worry about this for now.
1402 The result is that we'll see the fader cursor if we enter
1403 non-region-gain-line control points while in MouseDraw
1404 mode, even though we can't edit them in this mode.
1409 case ControlPointItem:
1410 cursor = _cursors->fader;
1413 cursor = _cursors->grabber_note;
1420 /* These items use the timebar cursor at all times */
1421 case TimecodeRulerItem:
1422 case MinsecRulerItem:
1424 case SamplesRulerItem:
1425 cursor = _cursors->timebar;
1428 /* These items use the grabber cursor at all times */
1429 case MeterMarkerItem:
1430 case TempoMarkerItem:
1435 case RangeMarkerBarItem:
1436 case CdMarkerBarItem:
1438 case TransportMarkerBarItem:
1440 cursor = _cursors->grabber;
1451 Editor::choose_canvas_cursor_on_entry (ItemType type)
1453 if (_drags->active()) {
1457 Gdk::Cursor* cursor = which_canvas_cursor(type);
1459 if (!_cursors->is_invalid (cursor)) {
1460 // Push a new enter context
1461 const EnterContext ctx = { type, CursorContext::create(*this, cursor) };
1462 _enter_stack.push_back(ctx);
1467 Editor::update_all_enter_cursors ()
1469 for (std::vector<EnterContext>::iterator i = _enter_stack.begin(); i != _enter_stack.end(); ++i) {
1470 i->cursor_ctx->change(which_canvas_cursor(i->item_type));
1475 Editor::trackviews_height() const
1477 if (!_trackview_group) {
1481 return _visible_canvas_height - _trackview_group->canvas_origin().y;