2 Copyright (C) 2005 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 #include "gtk2ardour-config.h"
24 #include "gtkmm2ext/utils.h"
26 #include "ardour/profile.h"
27 #include "ardour/rc_configuration.h"
28 #include "ardour/smf_source.h"
30 #include "pbd/error.h"
32 #include "canvas/canvas.h"
33 #include "canvas/rectangle.h"
34 #include "canvas/pixbuf.h"
35 #include "canvas/scroll_group.h"
36 #include "canvas/text.h"
37 #include "canvas/debug.h"
39 #include "ardour_ui.h"
40 #include "automation_time_axis.h"
43 #include "rgb_macros.h"
45 #include "audio_time_axis.h"
46 #include "editor_drag.h"
47 #include "region_view.h"
48 #include "editor_group_tabs.h"
49 #include "editor_summary.h"
50 #include "video_timeline.h"
52 #include "editor_cursors.h"
53 #include "mouse_cursors.h"
54 #include "note_base.h"
55 #include "ui_config.h"
56 #include "verbose_cursor.h"
61 using namespace ARDOUR;
62 using namespace ARDOUR_UI_UTILS;
66 using namespace Gtkmm2ext;
67 using namespace Editing;
70 Editor::initialize_canvas ()
72 _track_canvas_viewport = new ArdourCanvas::GtkCanvasViewport (horizontal_adjustment, vertical_adjustment);
73 _track_canvas = _track_canvas_viewport->canvas ();
75 _track_canvas->set_background_color (UIConfiguration::instance().color ("arrange base"));
76 _track_canvas->use_nsglview ();
78 /* scroll group for items that should not automatically scroll
79 * (e.g verbose cursor). It shares the canvas coordinate space.
81 no_scroll_group = new ArdourCanvas::Container (_track_canvas->root());
83 ArdourCanvas::ScrollGroup* hsg;
84 ArdourCanvas::ScrollGroup* hg;
85 ArdourCanvas::ScrollGroup* cg;
87 h_scroll_group = hg = new ArdourCanvas::ScrollGroup (_track_canvas->root(), ArdourCanvas::ScrollGroup::ScrollsHorizontally);
88 CANVAS_DEBUG_NAME (h_scroll_group, "canvas h scroll");
89 _track_canvas->add_scroller (*hg);
91 hv_scroll_group = hsg = new ArdourCanvas::ScrollGroup (_track_canvas->root(),
92 ArdourCanvas::ScrollGroup::ScrollSensitivity (ArdourCanvas::ScrollGroup::ScrollsVertically|
93 ArdourCanvas::ScrollGroup::ScrollsHorizontally));
94 CANVAS_DEBUG_NAME (hv_scroll_group, "canvas hv scroll");
95 _track_canvas->add_scroller (*hsg);
97 cursor_scroll_group = cg = new ArdourCanvas::ScrollGroup (_track_canvas->root(), ArdourCanvas::ScrollGroup::ScrollsHorizontally);
98 CANVAS_DEBUG_NAME (cursor_scroll_group, "canvas cursor scroll");
99 _track_canvas->add_scroller (*cg);
101 _verbose_cursor = new VerboseCursor (this);
103 /*a group to hold global rects like punch/loop indicators */
104 global_rect_group = new ArdourCanvas::Container (hv_scroll_group);
105 CANVAS_DEBUG_NAME (global_rect_group, "global rect group");
107 transport_loop_range_rect = new ArdourCanvas::Rectangle (global_rect_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, ArdourCanvas::COORD_MAX));
108 CANVAS_DEBUG_NAME (transport_loop_range_rect, "loop rect");
109 transport_loop_range_rect->hide();
111 transport_punch_range_rect = new ArdourCanvas::Rectangle (global_rect_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, ArdourCanvas::COORD_MAX));
112 CANVAS_DEBUG_NAME (transport_punch_range_rect, "punch rect");
113 transport_punch_range_rect->hide();
115 /*a group to hold time (measure) lines */
116 time_line_group = new ArdourCanvas::Container (h_scroll_group);
117 CANVAS_DEBUG_NAME (time_line_group, "time line group");
119 _trackview_group = new ArdourCanvas::Container (hv_scroll_group);
120 CANVAS_DEBUG_NAME (_trackview_group, "Canvas TrackViews");
122 // used as rubberband rect
123 rubberband_rect = new ArdourCanvas::Rectangle (hv_scroll_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, 0.0));
124 rubberband_rect->hide();
126 /* a group to hold stuff while it gets dragged around. Must be the
127 * uppermost (last) group with hv_scroll_group as a parent
129 _drag_motion_group = new ArdourCanvas::Container (hv_scroll_group);
130 CANVAS_DEBUG_NAME (_drag_motion_group, "Canvas Drag Motion");
132 /* TIME BAR CANVAS */
134 _time_markers_group = new ArdourCanvas::Container (h_scroll_group);
135 CANVAS_DEBUG_NAME (_time_markers_group, "time bars");
137 cd_marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, 0.0));
138 CANVAS_DEBUG_NAME (cd_marker_group, "cd marker group");
139 /* the vide is temporarily placed a the same location as the
140 cd_marker_group, but is moved later.
142 videotl_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple(0.0, 0.0));
143 CANVAS_DEBUG_NAME (videotl_group, "videotl group");
144 marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, timebar_height + 1.0));
145 CANVAS_DEBUG_NAME (marker_group, "marker group");
146 transport_marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 2.0) + 1.0));
147 CANVAS_DEBUG_NAME (transport_marker_group, "transport marker group");
148 range_marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 3.0) + 1.0));
149 CANVAS_DEBUG_NAME (range_marker_group, "range marker group");
150 tempo_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 4.0) + 1.0));
151 CANVAS_DEBUG_NAME (tempo_group, "tempo group");
152 meter_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 5.0) + 1.0));
153 CANVAS_DEBUG_NAME (meter_group, "meter group");
155 meter_bar = new ArdourCanvas::Rectangle (meter_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
156 CANVAS_DEBUG_NAME (meter_bar, "meter Bar");
157 meter_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
159 tempo_bar = new ArdourCanvas::Rectangle (tempo_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
160 CANVAS_DEBUG_NAME (tempo_bar, "Tempo Bar");
161 tempo_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
163 range_marker_bar = new ArdourCanvas::Rectangle (range_marker_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
164 CANVAS_DEBUG_NAME (range_marker_bar, "Range Marker Bar");
165 range_marker_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
167 transport_marker_bar = new ArdourCanvas::Rectangle (transport_marker_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
168 CANVAS_DEBUG_NAME (transport_marker_bar, "transport Marker Bar");
169 transport_marker_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
171 marker_bar = new ArdourCanvas::Rectangle (marker_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
172 CANVAS_DEBUG_NAME (marker_bar, "Marker Bar");
173 marker_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
175 cd_marker_bar = new ArdourCanvas::Rectangle (cd_marker_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
176 CANVAS_DEBUG_NAME (cd_marker_bar, "CD Marker Bar");
177 cd_marker_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
179 ARDOUR_UI::instance()->video_timeline = new VideoTimeLine(this, videotl_group, (timebar_height * videotl_bar_height));
181 cd_marker_bar_drag_rect = new ArdourCanvas::Rectangle (cd_marker_group, ArdourCanvas::Rect (0.0, 0.0, 100, timebar_height));
182 CANVAS_DEBUG_NAME (cd_marker_bar_drag_rect, "cd marker drag");
183 cd_marker_bar_drag_rect->set_outline (false);
184 cd_marker_bar_drag_rect->hide ();
186 range_bar_drag_rect = new ArdourCanvas::Rectangle (range_marker_group, ArdourCanvas::Rect (0.0, 0.0, 100, timebar_height));
187 CANVAS_DEBUG_NAME (range_bar_drag_rect, "range drag");
188 range_bar_drag_rect->set_outline (false);
189 range_bar_drag_rect->hide ();
191 transport_bar_drag_rect = new ArdourCanvas::Rectangle (transport_marker_group, ArdourCanvas::Rect (0.0, 0.0, 100, timebar_height));
192 CANVAS_DEBUG_NAME (transport_bar_drag_rect, "transport drag");
193 transport_bar_drag_rect->set_outline (false);
194 transport_bar_drag_rect->hide ();
196 transport_punchin_line = new ArdourCanvas::Line (hv_scroll_group);
197 transport_punchin_line->set_x0 (0);
198 transport_punchin_line->set_y0 (0);
199 transport_punchin_line->set_x1 (0);
200 transport_punchin_line->set_y1 (ArdourCanvas::COORD_MAX);
201 transport_punchin_line->hide ();
203 transport_punchout_line = new ArdourCanvas::Line (hv_scroll_group);
204 transport_punchout_line->set_x0 (0);
205 transport_punchout_line->set_y0 (0);
206 transport_punchout_line->set_x1 (0);
207 transport_punchout_line->set_y1 (ArdourCanvas::COORD_MAX);
208 transport_punchout_line->hide();
210 tempo_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_tempo_bar_event), tempo_bar));
211 meter_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_meter_bar_event), meter_bar));
212 marker_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_marker_bar_event), marker_bar));
213 cd_marker_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_cd_marker_bar_event), cd_marker_bar));
214 videotl_group->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_videotl_bar_event), videotl_group));
215 range_marker_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_range_marker_bar_event), range_marker_bar));
216 transport_marker_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_transport_marker_bar_event), transport_marker_bar));
218 playhead_cursor = new EditorCursor (*this, &Editor::canvas_playhead_cursor_event);
220 snapped_cursor = new EditorCursor (*this);
222 _canvas_drop_zone = new ArdourCanvas::Rectangle (hv_scroll_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, 0.0));
223 /* this thing is transparent */
224 _canvas_drop_zone->set_fill (false);
225 _canvas_drop_zone->set_outline (false);
226 _canvas_drop_zone->Event.connect (sigc::mem_fun (*this, &Editor::canvas_drop_zone_event));
228 /* these signals will initially be delivered to the canvas itself, but if they end up remaining unhandled, they are passed to Editor-level
232 _track_canvas->signal_scroll_event().connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_scroll_event), true));
233 _track_canvas->signal_motion_notify_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_motion_notify_event));
234 _track_canvas->signal_button_press_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_button_press_event));
235 _track_canvas->signal_button_release_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_button_release_event));
236 _track_canvas->signal_drag_motion().connect (sigc::mem_fun (*this, &Editor::track_canvas_drag_motion));
237 _track_canvas->signal_key_press_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_key_press));
238 _track_canvas->signal_key_release_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_key_release));
240 _track_canvas->set_name ("EditorMainCanvas");
241 _track_canvas->add_events (Gdk::POINTER_MOTION_HINT_MASK | Gdk::SCROLL_MASK | Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
242 _track_canvas->signal_leave_notify_event().connect (sigc::mem_fun(*this, &Editor::left_track_canvas), false);
243 _track_canvas->signal_enter_notify_event().connect (sigc::mem_fun(*this, &Editor::entered_track_canvas), false);
244 _track_canvas->set_flags (CAN_FOCUS);
246 _track_canvas->PreRender.connect (sigc::mem_fun(*this, &Editor::pre_render));
248 /* set up drag-n-drop */
250 vector<TargetEntry> target_table;
252 // Drag-N-Drop from the region list can generate this target
253 target_table.push_back (TargetEntry ("regions"));
255 target_table.push_back (TargetEntry ("text/plain"));
256 target_table.push_back (TargetEntry ("text/uri-list"));
257 target_table.push_back (TargetEntry ("application/x-rootwin-drop"));
259 _track_canvas->drag_dest_set (target_table);
260 _track_canvas->signal_drag_data_received().connect (sigc::mem_fun(*this, &Editor::track_canvas_drag_data_received));
262 _track_canvas_viewport->signal_size_allocate().connect (sigc::mem_fun(*this, &Editor::track_canvas_viewport_allocate));
264 initialize_rulers ();
266 UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &Editor::color_handler));
272 Editor::track_canvas_viewport_allocate (Gtk::Allocation alloc)
274 _canvas_viewport_allocation = alloc;
275 track_canvas_viewport_size_allocated ();
279 Editor::track_canvas_viewport_size_allocated ()
281 bool height_changed = _visible_canvas_height != _canvas_viewport_allocation.get_height();
283 _visible_canvas_width = _canvas_viewport_allocation.get_width ();
284 _visible_canvas_height = _canvas_viewport_allocation.get_height ();
286 _canvas_drop_zone->set_y1 (_canvas_drop_zone->y0() + (_visible_canvas_height - 20.0));
290 if (height_changed) {
292 for (LocationMarkerMap::iterator i = location_markers.begin(); i != location_markers.end(); ++i) {
293 i->second->canvas_height_set (_visible_canvas_height);
296 vertical_adjustment.set_page_size (_visible_canvas_height);
297 if ((vertical_adjustment.get_value() + _visible_canvas_height) >= vertical_adjustment.get_upper()) {
299 We're increasing the size of the canvas while the bottom is visible.
300 We scroll down to keep in step with the controls layout.
302 vertical_adjustment.set_value (_full_canvas_height - _visible_canvas_height);
305 set_visible_track_count (_visible_track_count);
308 update_fixed_rulers();
309 redisplay_grid (false);
310 _summary->set_overlays_dirty ();
314 Editor::reset_controls_layout_width ()
316 GtkRequisition req = { 0, 0 };
319 edit_controls_vbox.size_request (req);
322 if (_group_tabs->is_visible()) {
323 _group_tabs->size_request (req);
327 /* the controls layout has no horizontal scrolling, its visible
328 width is always equal to the total width of its contents.
331 controls_layout.property_width() = w;
332 controls_layout.property_width_request() = w;
336 Editor::reset_controls_layout_height (int32_t h)
338 /* ensure that the rect that represents the "bottom" of the canvas
339 * (the drag-n-drop zone) is, in fact, at the bottom.
342 _canvas_drop_zone->set_position (ArdourCanvas::Duple (0, h));
344 /* track controls layout must span the full height of "h" (all tracks)
345 * plus the bottom rect.
348 h += _canvas_drop_zone->height ();
350 /* set the height of the scrollable area (i.e. the sum of all contained widgets)
351 * for the controls layout. The size request is set elsewhere.
354 controls_layout.property_height() = h;
359 Editor::track_canvas_map_handler (GdkEventAny* /*ev*/)
361 if (!_cursor_stack.empty()) {
362 set_canvas_cursor (get_canvas_cursor());
364 PBD::error << "cursor stack is empty" << endmsg;
369 /** This is called when something is dropped onto the track canvas */
371 Editor::track_canvas_drag_data_received (const RefPtr<Gdk::DragContext>& context,
373 const SelectionData& data,
374 guint info, guint time)
376 if (!ARDOUR_UI_UTILS::engine_is_running ()) {
379 if (data.get_target() == "regions") {
380 drop_regions (context, x, y, data, info, time);
382 drop_paths (context, x, y, data, info, time);
387 Editor::idle_drop_paths (vector<string> paths, samplepos_t sample, double ypos, bool copy)
389 drop_paths_part_two (paths, sample, ypos, copy);
394 Editor::drop_paths_part_two (const vector<string>& paths, samplepos_t sample, double ypos, bool copy)
396 RouteTimeAxisView* tv;
398 /* MIDI files must always be imported, because we consider them
399 * writable. So split paths into two vectors, and follow the import
400 * path on the MIDI part.
403 vector<string> midi_paths;
404 vector<string> audio_paths;
406 for (vector<string>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
407 if (SMFSource::safe_midi_file_extension (*i)) {
408 midi_paths.push_back (*i);
410 audio_paths.push_back (*i);
415 std::pair<TimeAxisView*, int> const tvp = trackview_by_y_position (ypos, false);
416 if (tvp.first == 0) {
418 /* drop onto canvas background: create new tracks */
421 InstrumentSelector is; // instantiation builds instrument-list and sets default.
422 do_import (midi_paths, Editing::ImportDistinctFiles, ImportAsTrack, SrcBest, SMFTrackName, SMFTempoIgnore, sample, is.selected_instrument());
424 if (UIConfiguration::instance().get_only_copy_imported_files() || copy) {
425 do_import (audio_paths, Editing::ImportDistinctFiles, Editing::ImportAsTrack,
426 SrcBest, SMFTrackName, SMFTempoIgnore, sample);
428 do_embed (audio_paths, Editing::ImportDistinctFiles, ImportAsTrack, sample);
431 } else if ((tv = dynamic_cast<RouteTimeAxisView*> (tvp.first)) != 0) {
433 /* check that its a track, not a bus */
436 /* select the track, then embed/import */
439 do_import (midi_paths, Editing::ImportSerializeFiles, ImportToTrack,
440 SrcBest, SMFTrackName, SMFTempoIgnore, sample);
442 if (UIConfiguration::instance().get_only_copy_imported_files() || copy) {
443 do_import (audio_paths, Editing::ImportSerializeFiles, Editing::ImportToTrack,
444 SrcBest, SMFTrackName, SMFTempoIgnore, sample);
446 do_embed (audio_paths, Editing::ImportSerializeFiles, ImportToTrack, sample);
453 Editor::drop_paths (const RefPtr<Gdk::DragContext>& context,
455 const SelectionData& data,
456 guint info, guint time)
458 vector<string> paths;
462 if (convert_drop_to_paths (paths, context, x, y, data, info, time) == 0) {
464 /* D-n-D coordinates are window-relative, so convert to canvas coordinates
467 ev.type = GDK_BUTTON_RELEASE;
471 MusicSample when (window_event_sample (&ev, 0, &cy), 0);
474 bool copy = ((context->get_actions() & (Gdk::ACTION_COPY | Gdk::ACTION_LINK | Gdk::ACTION_MOVE)) == Gdk::ACTION_COPY);
476 /* We are not allowed to call recursive main event loops from within
477 the main event loop with GTK/Quartz. Since import/embed wants
478 to push up a progress dialog, defer all this till we go idle.
480 Glib::signal_idle().connect (sigc::bind (sigc::mem_fun (*this, &Editor::idle_drop_paths), paths, when.sample, cy, copy));
482 drop_paths_part_two (paths, when.sample, cy, copy);
486 context->drag_finish (true, false, time);
489 /** @param allow_horiz true to allow horizontal autoscroll, otherwise false.
491 * @param allow_vert true to allow vertical autoscroll, otherwise false.
495 Editor::maybe_autoscroll (bool allow_horiz, bool allow_vert, bool from_headers)
497 Gtk::Window* toplevel = dynamic_cast<Gtk::Window*>(contents().get_toplevel());
503 if (!UIConfiguration::instance().get_autoscroll_editor () || autoscroll_active ()) {
507 /* define a rectangular boundary for scrolling. If the mouse moves
508 * outside of this area and/or continue to be outside of this area,
509 * then we will continuously auto-scroll the canvas in the appropriate
512 * the boundary is defined in coordinates relative to the toplevel
513 * window since that is what we're going to call ::get_pointer() on
514 * during autoscrolling to determine if we're still outside the
518 ArdourCanvas::Rect scrolling_boundary;
519 Gtk::Allocation alloc;
522 alloc = controls_layout.get_allocation ();
526 controls_layout.get_parent()->translate_coordinates (*toplevel,
527 alloc.get_x(), alloc.get_y(),
530 scrolling_boundary = ArdourCanvas::Rect (wx, wy, wx + alloc.get_width(), wy + alloc.get_height());
534 alloc = _track_canvas_viewport->get_allocation ();
536 /* reduce height by the height of the timebars, which happens
537 to correspond to the position of the hv_scroll_group.
540 alloc.set_height (alloc.get_height() - hv_scroll_group->position().y);
541 alloc.set_y (alloc.get_y() + hv_scroll_group->position().y);
543 /* now reduce it again so that we start autoscrolling before we
544 * move off the top or bottom of the canvas
547 alloc.set_height (alloc.get_height() - 20);
548 alloc.set_y (alloc.get_y() + 10);
550 /* the effective width of the autoscroll boundary so
551 that we start scrolling before we hit the edge.
553 this helps when the window is slammed up against the
554 right edge of the screen, making it hard to scroll
558 if (alloc.get_width() > 20) {
559 alloc.set_width (alloc.get_width() - 20);
560 alloc.set_x (alloc.get_x() + 10);
565 _track_canvas_viewport->get_parent()->translate_coordinates (*toplevel,
566 alloc.get_x(), alloc.get_y(),
569 scrolling_boundary = ArdourCanvas::Rect (wx, wy, wx + alloc.get_width(), wy + alloc.get_height());
573 Gdk::ModifierType mask;
575 toplevel->get_window()->get_pointer (x, y, mask);
577 if ((allow_horiz && ((x < scrolling_boundary.x0 && _leftmost_sample > 0) || x >= scrolling_boundary.x1)) ||
578 (allow_vert && ((y < scrolling_boundary.y0 && vertical_adjustment.get_value() > 0)|| y >= scrolling_boundary.y1))) {
579 start_canvas_autoscroll (allow_horiz, allow_vert, scrolling_boundary);
584 Editor::autoscroll_active () const
586 return autoscroll_connection.connected ();
589 std::pair <samplepos_t,samplepos_t>
590 Editor::session_gui_extents (bool use_extra) const
593 return std::pair <samplepos_t,samplepos_t>(max_samplepos,0);
596 samplecnt_t session_extent_start = _session->current_start_sample();
597 samplecnt_t session_extent_end = _session->current_end_sample();
599 /* calculate the extents of all regions in every playlist
600 * NOTE: we should listen to playlists, and cache these values so we don't calculate them every time.
603 boost::shared_ptr<RouteList> rl = _session->get_routes();
604 for (RouteList::iterator r = rl->begin(); r != rl->end(); ++r) {
605 boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*r);
607 boost::shared_ptr<Playlist> pl = tr->playlist();
608 if (pl && !pl->all_regions_empty()) {
609 pair<samplepos_t, samplepos_t> e;
610 e = pl->get_extent();
611 if (e.first < session_extent_start) {
612 session_extent_start = e.first;
614 if (e.second > session_extent_end) {
615 session_extent_end = e.second;
622 /* ToDo: also incorporate automation regions (in case the session has no audio/midi but is just used for automating plugins or the like) */
624 /* add additional time to the ui extents (user-defined in config) */
626 samplecnt_t const extra = UIConfiguration::instance().get_extra_ui_extents_time() * 60 * _session->nominal_sample_rate();
627 session_extent_end += extra;
628 session_extent_start -= extra;
632 if (session_extent_end > max_samplepos) {
633 session_extent_end = max_samplepos;
635 if (session_extent_start < 0) {
636 session_extent_start = 0;
639 std::pair <samplepos_t,samplepos_t> ret (session_extent_start, session_extent_end);
644 Editor::autoscroll_canvas ()
647 Gdk::ModifierType mask;
648 sampleoffset_t dx = 0;
649 bool no_stop = false;
650 Gtk::Window* toplevel = dynamic_cast<Gtk::Window*>(contents().get_toplevel());
656 toplevel->get_window()->get_pointer (x, y, mask);
659 bool vertical_motion = false;
661 if (autoscroll_horizontal_allowed) {
663 samplepos_t new_sample = _leftmost_sample;
667 if (x > autoscroll_boundary.x1) {
669 /* bring it back into view */
670 dx = x - autoscroll_boundary.x1;
671 dx += 10 + (2 * (autoscroll_cnt/2));
673 dx = pixel_to_sample (dx);
675 dx *= UIConfiguration::instance().get_draggable_playhead_speed();
677 if (_leftmost_sample < max_samplepos - dx) {
678 new_sample = _leftmost_sample + dx;
680 new_sample = max_samplepos;
685 } else if (x < autoscroll_boundary.x0) {
687 dx = autoscroll_boundary.x0 - x;
688 dx += 10 + (2 * (autoscroll_cnt/2));
690 dx = pixel_to_sample (dx);
692 dx *= UIConfiguration::instance().get_draggable_playhead_speed();
694 if (_leftmost_sample >= dx) {
695 new_sample = _leftmost_sample - dx;
703 if (new_sample != _leftmost_sample) {
704 vc.time_origin = new_sample;
705 vc.add (VisualChange::TimeOrigin);
709 if (autoscroll_vertical_allowed) {
711 // const double vertical_pos = vertical_adjustment.get_value();
712 const int speed_factor = 10;
716 if (y < autoscroll_boundary.y0) {
718 /* scroll to make higher tracks visible */
720 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
721 scroll_up_one_track ();
722 vertical_motion = true;
726 } else if (y > autoscroll_boundary.y1) {
728 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
729 scroll_down_one_track ();
730 vertical_motion = true;
737 if (vc.pending || vertical_motion) {
739 /* change horizontal first */
745 /* now send a motion event to notify anyone who cares
746 that we have moved to a new location (because we scrolled)
751 ev.type = GDK_MOTION_NOTIFY;
752 ev.state = Gdk::BUTTON1_MASK;
754 /* the motion handler expects events in canvas coordinate space */
756 /* we asked for the mouse position above (::get_pointer()) via
757 * our own top level window (we being the Editor). Convert into
758 * coordinates within the canvas window.
764 toplevel->translate_coordinates (*_track_canvas, x, y, cx, cy);
766 /* clamp x and y to remain within the autoscroll boundary,
767 * which is defined in window coordinates
770 x = min (max ((ArdourCanvas::Coord) cx, autoscroll_boundary.x0), autoscroll_boundary.x1);
771 y = min (max ((ArdourCanvas::Coord) cy, autoscroll_boundary.y0), autoscroll_boundary.y1);
773 /* now convert from Editor window coordinates to canvas
777 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
782 motion_handler (0, (GdkEvent*) &ev, true);
784 } else if (no_stop) {
786 /* not changing visual state but pointer is outside the scrolling boundary
787 * so we still need to deliver a fake motion event
792 ev.type = GDK_MOTION_NOTIFY;
793 ev.state = Gdk::BUTTON1_MASK;
795 /* the motion handler expects events in canvas coordinate space */
797 /* first convert from Editor window coordinates to canvas
804 /* clamp x and y to remain within the visible area. except
805 * .. if horizontal scrolling is allowed, always allow us to
809 if (autoscroll_horizontal_allowed) {
810 x = min (max ((ArdourCanvas::Coord) x, 0.0), autoscroll_boundary.x1);
812 x = min (max ((ArdourCanvas::Coord) x, autoscroll_boundary.x0), autoscroll_boundary.x1);
814 y = min (max ((ArdourCanvas::Coord) y, autoscroll_boundary.y0), autoscroll_boundary.y1);
816 toplevel->translate_coordinates (*_track_canvas_viewport, x, y, cx, cy);
818 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
823 motion_handler (0, (GdkEvent*) &ev, true);
826 stop_canvas_autoscroll ();
832 return true; /* call me again */
836 Editor::start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary)
842 stop_canvas_autoscroll ();
844 autoscroll_horizontal_allowed = allow_horiz;
845 autoscroll_vertical_allowed = allow_vert;
846 autoscroll_boundary = boundary;
848 /* do the first scroll right now
851 autoscroll_canvas ();
853 /* scroll again at very very roughly 30FPS */
855 autoscroll_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &Editor::autoscroll_canvas), 30);
859 Editor::stop_canvas_autoscroll ()
861 autoscroll_connection.disconnect ();
865 Editor::EnterContext*
866 Editor::get_enter_context(ItemType type)
868 for (ssize_t i = _enter_stack.size() - 1; i >= 0; --i) {
869 if (_enter_stack[i].item_type == type) {
870 return &_enter_stack[i];
877 Editor::left_track_canvas (GdkEventCrossing* ev)
879 const bool was_within = within_track_canvas;
881 within_track_canvas = false;
882 set_entered_track (0);
883 set_entered_regionview (0);
884 reset_canvas_action_sensitivity (false);
887 if (ev->detail == GDK_NOTIFY_NONLINEAR ||
888 ev->detail == GDK_NOTIFY_NONLINEAR_VIRTUAL) {
889 /* context menu or something similar */
890 sensitize_the_right_region_actions (false);
892 sensitize_the_right_region_actions (true);
900 Editor::entered_track_canvas (GdkEventCrossing* ev)
902 const bool was_within = within_track_canvas;
903 within_track_canvas = true;
904 reset_canvas_action_sensitivity (true);
907 if (ev->detail == GDK_NOTIFY_NONLINEAR ||
908 ev->detail == GDK_NOTIFY_NONLINEAR_VIRTUAL) {
909 /* context menu or something similar */
910 sensitize_the_right_region_actions (false);
912 sensitize_the_right_region_actions (true);
920 Editor::ensure_time_axis_view_is_visible (TimeAxisView const & track, bool at_top)
922 if (track.hidden()) {
926 /* compute visible area of trackview group, as offsets from top of
930 double const current_view_min_y = vertical_adjustment.get_value();
931 double const current_view_max_y = current_view_min_y + vertical_adjustment.get_page_size();
933 double const track_min_y = track.y_position ();
934 double const track_max_y = track.y_position () + track.effective_height ();
937 (track_min_y >= current_view_min_y &&
938 track_max_y < current_view_max_y)) {
939 /* already visible, and caller did not ask to place it at the
940 * top of the track canvas
948 new_value = track_min_y;
950 if (track_min_y < current_view_min_y) {
951 // Track is above the current view
952 new_value = track_min_y;
953 } else if (track_max_y > current_view_max_y) {
954 // Track is below the current view
955 new_value = track.y_position () + track.effective_height() - vertical_adjustment.get_page_size();
957 new_value = track_min_y;
961 vertical_adjustment.set_value(new_value);
964 /** Called when the main vertical_adjustment has changed */
966 Editor::tie_vertical_scrolling ()
968 if (pending_visual_change.idle_handler_id < 0) {
969 _summary->set_overlays_dirty ();
974 Editor::set_horizontal_position (double p)
976 horizontal_adjustment.set_value (p);
978 _leftmost_sample = (samplepos_t) floor (p * samples_per_pixel);
982 Editor::color_handler()
984 Gtkmm2ext::Color base = UIConfiguration::instance().color ("ruler base");
985 Gtkmm2ext::Color text = UIConfiguration::instance().color ("ruler text");
986 timecode_ruler->set_fill_color (base);
987 timecode_ruler->set_outline_color (text);
988 minsec_ruler->set_fill_color (base);
989 minsec_ruler->set_outline_color (text);
990 samples_ruler->set_fill_color (base);
991 samples_ruler->set_outline_color (text);
992 bbt_ruler->set_fill_color (base);
993 bbt_ruler->set_outline_color (text);
995 playhead_cursor->set_color (UIConfiguration::instance().color ("play head"));
997 meter_bar->set_fill_color (UIConfiguration::instance().color_mod ("meter bar", "marker bar"));
998 meter_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1000 tempo_bar->set_fill_color (UIConfiguration::instance().color_mod ("tempo bar", "marker bar"));
1001 tempo_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1003 marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("marker bar", "marker bar"));
1004 marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1006 cd_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("cd marker bar", "marker bar"));
1007 cd_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1009 range_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("range marker bar", "marker bar"));
1010 range_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1012 transport_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("transport marker bar", "marker bar"));
1013 transport_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1015 cd_marker_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
1016 cd_marker_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
1018 range_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
1019 range_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
1021 transport_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("transport drag rect"));
1022 transport_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("transport drag rect"));
1024 transport_loop_range_rect->set_fill_color (UIConfiguration::instance().color_mod ("transport loop rect", "loop rectangle"));
1025 transport_loop_range_rect->set_outline_color (UIConfiguration::instance().color ("transport loop rect"));
1027 transport_punch_range_rect->set_fill_color (UIConfiguration::instance().color ("transport punch rect"));
1028 transport_punch_range_rect->set_outline_color (UIConfiguration::instance().color ("transport punch rect"));
1030 transport_punchin_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
1031 transport_punchout_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
1033 rubberband_rect->set_outline_color (UIConfiguration::instance().color ("rubber band rect"));
1034 rubberband_rect->set_fill_color (UIConfiguration::instance().color_mod ("rubber band rect", "selection rect"));
1036 location_marker_color = UIConfiguration::instance().color ("location marker");
1037 location_range_color = UIConfiguration::instance().color ("location range");
1038 location_cd_marker_color = UIConfiguration::instance().color ("location cd marker");
1039 location_loop_color = UIConfiguration::instance().color ("location loop");
1040 location_punch_color = UIConfiguration::instance().color ("location punch");
1042 refresh_location_display ();
1044 NoteBase::set_colors ();
1046 /* redraw the whole thing */
1047 _track_canvas->set_background_color (UIConfiguration::instance().color ("arrange base"));
1048 _track_canvas->queue_draw ();
1051 redisplay_grid (true);
1054 _session->tempo_map().apply_with_metrics (*this, &Editor::draw_metric_marks); // redraw metric markers
1059 Editor::horizontal_position () const
1061 return sample_to_pixel (_leftmost_sample);
1065 Editor::track_canvas_key_press (GdkEventKey*)
1071 Editor::track_canvas_key_release (GdkEventKey*)
1077 Editor::clamp_verbose_cursor_x (double x)
1082 x = min (_visible_canvas_width - 200.0, x);
1088 Editor::clamp_verbose_cursor_y (double y)
1091 y = min (_visible_canvas_height - 50, y);
1095 ArdourCanvas::GtkCanvasViewport*
1096 Editor::get_track_canvas() const
1098 return _track_canvas_viewport;
1102 Editor::get_canvas_cursor () const
1104 /* The top of the cursor stack is always the currently visible cursor. */
1105 return _cursor_stack.back();
1109 Editor::set_canvas_cursor (Gdk::Cursor* cursor)
1111 Glib::RefPtr<Gdk::Window> win = _track_canvas->get_window();
1113 if (win && !_cursors->is_invalid (cursor)) {
1114 /* glibmm 2.4 doesn't allow null cursor pointer because it uses
1115 a Gdk::Cursor& as the argument to Gdk::Window::set_cursor().
1116 But a null pointer just means "use parent window cursor",
1117 and so should be allowed. Gtkmm 3.x has fixed this API.
1119 For now, drop down and use C API
1121 gdk_window_set_cursor (win->gobj(), cursor ? cursor->gobj() : 0);
1126 Editor::push_canvas_cursor (Gdk::Cursor* cursor)
1128 if (!_cursors->is_invalid (cursor)) {
1129 _cursor_stack.push_back (cursor);
1130 set_canvas_cursor (cursor);
1132 return _cursor_stack.size() - 1;
1136 Editor::pop_canvas_cursor ()
1139 if (_cursor_stack.size() <= 1) {
1140 PBD::error << "attempt to pop default cursor" << endmsg;
1144 _cursor_stack.pop_back();
1145 if (_cursor_stack.back()) {
1146 /* Popped to an existing cursor, we're done. Otherwise, the
1147 context that created this cursor has been destroyed, so we need
1148 to skip to the next down the stack. */
1149 set_canvas_cursor (_cursor_stack.back());
1156 Editor::which_trim_cursor (bool left) const
1158 if (!entered_regionview) {
1162 Trimmable::CanTrim ct = entered_regionview->region()->can_trim ();
1166 if (ct & Trimmable::FrontTrimEarlier) {
1167 return _cursors->left_side_trim;
1169 return _cursors->left_side_trim_right_only;
1172 if (ct & Trimmable::EndTrimLater) {
1173 return _cursors->right_side_trim;
1175 return _cursors->right_side_trim_left_only;
1181 Editor::which_mode_cursor () const
1183 Gdk::Cursor* mode_cursor = MouseCursors::invalid_cursor ();
1185 switch (mouse_mode) {
1187 mode_cursor = _cursors->selector;
1191 mode_cursor = _cursors->scissors;
1196 /* don't use mode cursor, pick a grabber cursor based on the item */
1200 mode_cursor = _cursors->midi_pencil;
1204 mode_cursor = _cursors->time_fx; // just use playhead
1208 mode_cursor = _cursors->speaker;
1212 /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
1213 if (get_smart_mode()) {
1216 get_pointer_position (x, y);
1218 if (x >= 0 && y >= 0) {
1220 vector<ArdourCanvas::Item const *> items;
1222 /* Note how we choose a specific scroll group to get
1223 * items from. This could be problematic.
1226 hv_scroll_group->add_items_at_point (ArdourCanvas::Duple (x,y), items);
1228 // first item will be the upper most
1230 if (!items.empty()) {
1231 const ArdourCanvas::Item* i = items.front();
1233 if (i && i->parent() && i->parent()->get_data (X_("timeselection"))) {
1234 pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y);
1235 if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
1236 mode_cursor = _cursors->up_down;
1247 Editor::which_track_cursor () const
1249 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
1251 switch (_join_object_range_state) {
1252 case JOIN_OBJECT_RANGE_NONE:
1253 case JOIN_OBJECT_RANGE_OBJECT:
1254 cursor = _cursors->grabber;
1256 case JOIN_OBJECT_RANGE_RANGE:
1257 cursor = _cursors->selector;
1265 Editor::which_canvas_cursor(ItemType type) const
1267 Gdk::Cursor* cursor = which_mode_cursor ();
1269 if (mouse_mode == MouseRange) {
1271 case StartSelectionTrimItem:
1272 cursor = _cursors->left_side_trim;
1274 case EndSelectionTrimItem:
1275 cursor = _cursors->right_side_trim;
1282 if ((mouse_mode == MouseObject || get_smart_mode ()) ||
1283 mouse_mode == MouseContent) {
1285 /* find correct cursor to use in object/smart mode */
1289 /* We don't choose a cursor for these items on top of a region view,
1290 because this would push a new context on the enter stack which
1291 means switching the region context for things like smart mode
1292 won't actualy change the cursor. */
1293 // case RegionViewNameHighlight:
1294 // case RegionViewName:
1297 case AutomationTrackItem:
1298 cursor = which_track_cursor ();
1300 case PlayheadCursorItem:
1301 cursor = _cursors->grabber;
1304 cursor = _cursors->selector;
1306 case ControlPointItem:
1307 cursor = _cursors->fader;
1310 cursor = _cursors->cross_hair;
1312 case AutomationLineItem:
1313 cursor = _cursors->cross_hair;
1315 case StartSelectionTrimItem:
1316 cursor = _cursors->left_side_trim;
1318 case EndSelectionTrimItem:
1319 cursor = _cursors->right_side_trim;
1322 cursor = _cursors->fade_in;
1324 case FadeInHandleItem:
1325 cursor = _cursors->fade_in;
1327 case FadeInTrimHandleItem:
1328 cursor = _cursors->fade_in;
1331 cursor = _cursors->fade_out;
1333 case FadeOutHandleItem:
1334 cursor = _cursors->fade_out;
1336 case FadeOutTrimHandleItem:
1337 cursor = _cursors->fade_out;
1339 case FeatureLineItem:
1340 cursor = _cursors->cross_hair;
1342 case LeftFrameHandle:
1343 if (effective_mouse_mode() == MouseObject) // (smart mode): if the user is in the btm half, show the trim cursor
1344 cursor = which_trim_cursor (true);
1346 cursor = _cursors->selector; // (smart mode): in the top half, just show the selection (range) cursor
1348 case RightFrameHandle:
1349 if (effective_mouse_mode() == MouseObject) // see above
1350 cursor = which_trim_cursor (false);
1352 cursor = _cursors->selector;
1354 case StartCrossFadeItem:
1355 cursor = _cursors->fade_in;
1357 case EndCrossFadeItem:
1358 cursor = _cursors->fade_out;
1360 case CrossfadeViewItem:
1361 cursor = _cursors->cross_hair;
1364 cursor = _cursors->grabber_note;
1369 } else if (mouse_mode == MouseDraw) {
1371 /* ControlPointItem is not really specific to region gain mode
1372 but it is the same cursor so don't worry about this for now.
1373 The result is that we'll see the fader cursor if we enter
1374 non-region-gain-line control points while in MouseDraw
1375 mode, even though we can't edit them in this mode.
1380 case ControlPointItem:
1381 cursor = _cursors->fader;
1384 cursor = _cursors->grabber_note;
1391 /* These items use the timebar cursor at all times */
1392 case TimecodeRulerItem:
1393 case MinsecRulerItem:
1395 case SamplesRulerItem:
1396 cursor = _cursors->timebar;
1399 /* These items use the grabber cursor at all times */
1400 case MeterMarkerItem:
1401 case TempoMarkerItem:
1406 case RangeMarkerBarItem:
1407 case CdMarkerBarItem:
1409 case TransportMarkerBarItem:
1411 cursor = _cursors->grabber;
1422 Editor::choose_canvas_cursor_on_entry (ItemType type)
1424 if (_drags->active()) {
1428 Gdk::Cursor* cursor = which_canvas_cursor(type);
1430 if (!_cursors->is_invalid (cursor)) {
1431 // Push a new enter context
1432 const EnterContext ctx = { type, CursorContext::create(*this, cursor) };
1433 _enter_stack.push_back(ctx);
1438 Editor::update_all_enter_cursors ()
1440 for (std::vector<EnterContext>::iterator i = _enter_stack.begin(); i != _enter_stack.end(); ++i) {
1441 i->cursor_ctx->change(which_canvas_cursor(i->item_type));
1446 Editor::trackviews_height() const
1448 if (!_trackview_group) {
1452 return _visible_canvas_height - _trackview_group->canvas_origin().y;