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 _canvas_drop_zone = new ArdourCanvas::Rectangle (hv_scroll_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, 0.0));
221 /* this thing is transparent */
222 _canvas_drop_zone->set_fill (false);
223 _canvas_drop_zone->set_outline (false);
224 _canvas_drop_zone->Event.connect (sigc::mem_fun (*this, &Editor::canvas_drop_zone_event));
226 /* these signals will initially be delivered to the canvas itself, but if they end up remaining unhandled, they are passed to Editor-level
230 _track_canvas->signal_scroll_event().connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_scroll_event), true));
231 _track_canvas->signal_motion_notify_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_motion_notify_event));
232 _track_canvas->signal_button_press_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_button_press_event));
233 _track_canvas->signal_button_release_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_button_release_event));
234 _track_canvas->signal_drag_motion().connect (sigc::mem_fun (*this, &Editor::track_canvas_drag_motion));
235 _track_canvas->signal_key_press_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_key_press));
236 _track_canvas->signal_key_release_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_key_release));
238 _track_canvas->set_name ("EditorMainCanvas");
239 _track_canvas->add_events (Gdk::POINTER_MOTION_HINT_MASK | Gdk::SCROLL_MASK | Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
240 _track_canvas->signal_leave_notify_event().connect (sigc::mem_fun(*this, &Editor::left_track_canvas), false);
241 _track_canvas->signal_enter_notify_event().connect (sigc::mem_fun(*this, &Editor::entered_track_canvas), false);
242 _track_canvas->set_flags (CAN_FOCUS);
244 _track_canvas->PreRender.connect (sigc::mem_fun(*this, &Editor::pre_render));
246 /* set up drag-n-drop */
248 vector<TargetEntry> target_table;
250 // Drag-N-Drop from the region list can generate this target
251 target_table.push_back (TargetEntry ("regions"));
253 target_table.push_back (TargetEntry ("text/plain"));
254 target_table.push_back (TargetEntry ("text/uri-list"));
255 target_table.push_back (TargetEntry ("application/x-rootwin-drop"));
257 _track_canvas->drag_dest_set (target_table);
258 _track_canvas->signal_drag_data_received().connect (sigc::mem_fun(*this, &Editor::track_canvas_drag_data_received));
260 _track_canvas_viewport->signal_size_allocate().connect (sigc::mem_fun(*this, &Editor::track_canvas_viewport_allocate));
262 initialize_rulers ();
264 UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &Editor::color_handler));
270 Editor::track_canvas_viewport_allocate (Gtk::Allocation alloc)
272 _canvas_viewport_allocation = alloc;
273 track_canvas_viewport_size_allocated ();
277 Editor::track_canvas_viewport_size_allocated ()
279 bool height_changed = _visible_canvas_height != _canvas_viewport_allocation.get_height();
281 _visible_canvas_width = _canvas_viewport_allocation.get_width ();
282 _visible_canvas_height = _canvas_viewport_allocation.get_height ();
284 _canvas_drop_zone->set_y1 (_canvas_drop_zone->y0() + (_visible_canvas_height - 20.0));
288 if (height_changed) {
290 for (LocationMarkerMap::iterator i = location_markers.begin(); i != location_markers.end(); ++i) {
291 i->second->canvas_height_set (_visible_canvas_height);
294 vertical_adjustment.set_page_size (_visible_canvas_height);
295 if ((vertical_adjustment.get_value() + _visible_canvas_height) >= vertical_adjustment.get_upper()) {
297 We're increasing the size of the canvas while the bottom is visible.
298 We scroll down to keep in step with the controls layout.
300 vertical_adjustment.set_value (_full_canvas_height - _visible_canvas_height);
303 set_visible_track_count (_visible_track_count);
306 update_fixed_rulers();
307 redisplay_tempo (false);
308 _summary->set_overlays_dirty ();
312 Editor::reset_controls_layout_width ()
314 GtkRequisition req = { 0, 0 };
317 edit_controls_vbox.size_request (req);
320 if (_group_tabs->is_visible()) {
321 _group_tabs->size_request (req);
325 /* the controls layout has no horizontal scrolling, its visible
326 width is always equal to the total width of its contents.
329 controls_layout.property_width() = w;
330 controls_layout.property_width_request() = w;
334 Editor::reset_controls_layout_height (int32_t h)
336 /* ensure that the rect that represents the "bottom" of the canvas
337 * (the drag-n-drop zone) is, in fact, at the bottom.
340 _canvas_drop_zone->set_position (ArdourCanvas::Duple (0, h));
342 /* track controls layout must span the full height of "h" (all tracks)
343 * plus the bottom rect.
346 h += _canvas_drop_zone->height ();
348 /* set the height of the scrollable area (i.e. the sum of all contained widgets)
349 * for the controls layout. The size request is set elsewhere.
352 controls_layout.property_height() = h;
357 Editor::track_canvas_map_handler (GdkEventAny* /*ev*/)
359 if (!_cursor_stack.empty()) {
360 set_canvas_cursor (get_canvas_cursor());
362 PBD::error << "cursor stack is empty" << endmsg;
367 /** This is called when something is dropped onto the track canvas */
369 Editor::track_canvas_drag_data_received (const RefPtr<Gdk::DragContext>& context,
371 const SelectionData& data,
372 guint info, guint time)
374 if (data.get_target() == "regions") {
375 drop_regions (context, x, y, data, info, time);
377 drop_paths (context, x, y, data, info, time);
382 Editor::idle_drop_paths (vector<string> paths, framepos_t frame, double ypos, bool copy)
384 drop_paths_part_two (paths, frame, ypos, copy);
389 Editor::drop_paths_part_two (const vector<string>& paths, framepos_t frame, double ypos, bool copy)
391 RouteTimeAxisView* tv;
393 /* MIDI files must always be imported, because we consider them
394 * writable. So split paths into two vectors, and follow the import
395 * path on the MIDI part.
398 vector<string> midi_paths;
399 vector<string> audio_paths;
401 for (vector<string>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
402 if (SMFSource::safe_midi_file_extension (*i)) {
403 midi_paths.push_back (*i);
405 audio_paths.push_back (*i);
410 std::pair<TimeAxisView*, int> const tvp = trackview_by_y_position (ypos, false);
411 if (tvp.first == 0) {
413 /* drop onto canvas background: create new tracks */
416 InstrumentSelector is; // instantiation builds instrument-list and sets default.
417 do_import (midi_paths, Editing::ImportDistinctFiles, ImportAsTrack, SrcBest, SMFTrackName, SMFTempoIgnore, frame, is.selected_instrument());
419 if (UIConfiguration::instance().get_only_copy_imported_files() || copy) {
420 do_import (audio_paths, Editing::ImportDistinctFiles, Editing::ImportAsTrack,
421 SrcBest, SMFTrackName, SMFTempoIgnore, frame);
423 do_embed (audio_paths, Editing::ImportDistinctFiles, ImportAsTrack, frame);
426 } else if ((tv = dynamic_cast<RouteTimeAxisView*> (tvp.first)) != 0) {
428 /* check that its a track, not a bus */
431 /* select the track, then embed/import */
434 do_import (midi_paths, Editing::ImportSerializeFiles, ImportToTrack,
435 SrcBest, SMFTrackName, SMFTempoIgnore, frame);
437 if (UIConfiguration::instance().get_only_copy_imported_files() || copy) {
438 do_import (audio_paths, Editing::ImportSerializeFiles, Editing::ImportToTrack,
439 SrcBest, SMFTrackName, SMFTempoIgnore, frame);
441 do_embed (audio_paths, Editing::ImportSerializeFiles, ImportToTrack, frame);
448 Editor::drop_paths (const RefPtr<Gdk::DragContext>& context,
450 const SelectionData& data,
451 guint info, guint time)
453 vector<string> paths;
457 if (convert_drop_to_paths (paths, context, x, y, data, info, time) == 0) {
459 /* D-n-D coordinates are window-relative, so convert to canvas coordinates
462 ev.type = GDK_BUTTON_RELEASE;
466 MusicFrame when (window_event_sample (&ev, 0, &cy), 0);
469 bool copy = ((context->get_actions() & (Gdk::ACTION_COPY | Gdk::ACTION_LINK | Gdk::ACTION_MOVE)) == Gdk::ACTION_COPY);
471 /* We are not allowed to call recursive main event loops from within
472 the main event loop with GTK/Quartz. Since import/embed wants
473 to push up a progress dialog, defer all this till we go idle.
475 Glib::signal_idle().connect (sigc::bind (sigc::mem_fun (*this, &Editor::idle_drop_paths), paths, when.frame, cy, copy));
477 drop_paths_part_two (paths, when.frame, cy, copy);
481 context->drag_finish (true, false, time);
484 /** @param allow_horiz true to allow horizontal autoscroll, otherwise false.
486 * @param allow_vert true to allow vertical autoscroll, otherwise false.
490 Editor::maybe_autoscroll (bool allow_horiz, bool allow_vert, bool from_headers)
492 Gtk::Window* toplevel = dynamic_cast<Gtk::Window*>(contents().get_toplevel());
498 if (!UIConfiguration::instance().get_autoscroll_editor () || autoscroll_active ()) {
502 /* define a rectangular boundary for scrolling. If the mouse moves
503 * outside of this area and/or continue to be outside of this area,
504 * then we will continuously auto-scroll the canvas in the appropriate
507 * the boundary is defined in coordinates relative to the toplevel
508 * window since that is what we're going to call ::get_pointer() on
509 * during autoscrolling to determine if we're still outside the
513 ArdourCanvas::Rect scrolling_boundary;
514 Gtk::Allocation alloc;
517 alloc = controls_layout.get_allocation ();
521 controls_layout.get_parent()->translate_coordinates (*toplevel,
522 alloc.get_x(), alloc.get_y(),
525 scrolling_boundary = ArdourCanvas::Rect (wx, wy, wx + alloc.get_width(), wy + alloc.get_height());
529 alloc = _track_canvas_viewport->get_allocation ();
531 /* reduce height by the height of the timebars, which happens
532 to correspond to the position of the hv_scroll_group.
535 alloc.set_height (alloc.get_height() - hv_scroll_group->position().y);
536 alloc.set_y (alloc.get_y() + hv_scroll_group->position().y);
538 /* now reduce it again so that we start autoscrolling before we
539 * move off the top or bottom of the canvas
542 alloc.set_height (alloc.get_height() - 20);
543 alloc.set_y (alloc.get_y() + 10);
545 /* the effective width of the autoscroll boundary so
546 that we start scrolling before we hit the edge.
548 this helps when the window is slammed up against the
549 right edge of the screen, making it hard to scroll
553 if (alloc.get_width() > 20) {
554 alloc.set_width (alloc.get_width() - 20);
555 alloc.set_x (alloc.get_x() + 10);
560 _track_canvas_viewport->get_parent()->translate_coordinates (*toplevel,
561 alloc.get_x(), alloc.get_y(),
564 scrolling_boundary = ArdourCanvas::Rect (wx, wy, wx + alloc.get_width(), wy + alloc.get_height());
568 Gdk::ModifierType mask;
570 toplevel->get_window()->get_pointer (x, y, mask);
572 if ((allow_horiz && ((x < scrolling_boundary.x0 && leftmost_frame > 0) || x >= scrolling_boundary.x1)) ||
573 (allow_vert && ((y < scrolling_boundary.y0 && vertical_adjustment.get_value() > 0)|| y >= scrolling_boundary.y1))) {
574 start_canvas_autoscroll (allow_horiz, allow_vert, scrolling_boundary);
579 Editor::autoscroll_active () const
581 return autoscroll_connection.connected ();
584 std::pair <framepos_t,framepos_t>
585 Editor::session_gui_extents ( bool use_extra ) const
588 return std::pair <framepos_t,framepos_t>(max_framepos,0);
591 framecnt_t session_extent_start = _session->current_start_frame();
592 framecnt_t session_extent_end = _session->current_end_frame();
594 //calculate the extents of all regions in every playlist
595 //NOTE: we should listen to playlists, and cache these values so we don't calculate them every time.
597 boost::shared_ptr<RouteList> rl = _session->get_routes();
598 for (RouteList::iterator r = rl->begin(); r != rl->end(); ++r) {
599 boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*r);
601 boost::shared_ptr<Playlist> pl = tr->playlist();
602 if ( pl && !pl->all_regions_empty() ) {
603 pair<framepos_t, framepos_t> e;
604 e = pl->get_extent();
605 if (e.first < session_extent_start) {
606 session_extent_start = e.first;
608 if (e.second > session_extent_end) {
609 session_extent_end = e.second;
616 //ToDo: also incorporate automation regions (in case the session has no audio/midi but is just used for automating plugins or the like)
618 //add additional time to the ui extents ( user-defined in config )
620 framecnt_t const extra = UIConfiguration::instance().get_extra_ui_extents_time() * 60 * _session->nominal_frame_rate();
621 session_extent_end += extra;
622 session_extent_start -= extra;
626 if (session_extent_end > max_framepos) {
627 session_extent_end = max_framepos;
629 if (session_extent_start < 0) {
630 session_extent_start = 0;
633 std::pair <framepos_t,framepos_t> ret (session_extent_start, session_extent_end);
638 Editor::autoscroll_canvas ()
641 Gdk::ModifierType mask;
642 frameoffset_t dx = 0;
643 bool no_stop = false;
644 Gtk::Window* toplevel = dynamic_cast<Gtk::Window*>(contents().get_toplevel());
650 toplevel->get_window()->get_pointer (x, y, mask);
653 bool vertical_motion = false;
655 if (autoscroll_horizontal_allowed) {
657 framepos_t new_frame = leftmost_frame;
661 if (x > autoscroll_boundary.x1) {
663 /* bring it back into view */
664 dx = x - autoscroll_boundary.x1;
665 dx += 10 + (2 * (autoscroll_cnt/2));
667 dx = pixel_to_sample (dx);
669 dx *= UIConfiguration::instance().get_draggable_playhead_speed();
671 if (leftmost_frame < max_framepos - dx) {
672 new_frame = leftmost_frame + dx;
674 new_frame = max_framepos;
679 } else if (x < autoscroll_boundary.x0) {
681 dx = autoscroll_boundary.x0 - x;
682 dx += 10 + (2 * (autoscroll_cnt/2));
684 dx = pixel_to_sample (dx);
686 dx *= UIConfiguration::instance().get_draggable_playhead_speed();
688 if (leftmost_frame >= dx) {
689 new_frame = leftmost_frame - dx;
697 if (new_frame != leftmost_frame) {
698 vc.time_origin = new_frame;
699 vc.add (VisualChange::TimeOrigin);
703 if (autoscroll_vertical_allowed) {
705 // const double vertical_pos = vertical_adjustment.get_value();
706 const int speed_factor = 10;
710 if (y < autoscroll_boundary.y0) {
712 /* scroll to make higher tracks visible */
714 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
715 scroll_up_one_track ();
716 vertical_motion = true;
720 } else if (y > autoscroll_boundary.y1) {
722 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
723 scroll_down_one_track ();
724 vertical_motion = true;
731 if (vc.pending || vertical_motion) {
733 /* change horizontal first */
739 /* now send a motion event to notify anyone who cares
740 that we have moved to a new location (because we scrolled)
745 ev.type = GDK_MOTION_NOTIFY;
746 ev.state = Gdk::BUTTON1_MASK;
748 /* the motion handler expects events in canvas coordinate space */
750 /* we asked for the mouse position above (::get_pointer()) via
751 * our own top level window (we being the Editor). Convert into
752 * coordinates within the canvas window.
758 toplevel->translate_coordinates (*_track_canvas, x, y, cx, cy);
760 /* clamp x and y to remain within the autoscroll boundary,
761 * which is defined in window coordinates
764 x = min (max ((ArdourCanvas::Coord) cx, autoscroll_boundary.x0), autoscroll_boundary.x1);
765 y = min (max ((ArdourCanvas::Coord) cy, autoscroll_boundary.y0), autoscroll_boundary.y1);
767 /* now convert from Editor window coordinates to canvas
771 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
776 motion_handler (0, (GdkEvent*) &ev, true);
778 } else if (no_stop) {
780 /* not changing visual state but pointer is outside the scrolling boundary
781 * so we still need to deliver a fake motion event
786 ev.type = GDK_MOTION_NOTIFY;
787 ev.state = Gdk::BUTTON1_MASK;
789 /* the motion handler expects events in canvas coordinate space */
791 /* first convert from Editor window coordinates to canvas
798 /* clamp x and y to remain within the visible area. except
799 * .. if horizontal scrolling is allowed, always allow us to
803 if (autoscroll_horizontal_allowed) {
804 x = min (max ((ArdourCanvas::Coord) x, 0.0), autoscroll_boundary.x1);
806 x = min (max ((ArdourCanvas::Coord) x, autoscroll_boundary.x0), autoscroll_boundary.x1);
808 y = min (max ((ArdourCanvas::Coord) y, autoscroll_boundary.y0), autoscroll_boundary.y1);
810 toplevel->translate_coordinates (*_track_canvas_viewport, x, y, cx, cy);
812 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
817 motion_handler (0, (GdkEvent*) &ev, true);
820 stop_canvas_autoscroll ();
826 return true; /* call me again */
830 Editor::start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary)
836 stop_canvas_autoscroll ();
838 autoscroll_horizontal_allowed = allow_horiz;
839 autoscroll_vertical_allowed = allow_vert;
840 autoscroll_boundary = boundary;
842 /* do the first scroll right now
845 autoscroll_canvas ();
847 /* scroll again at very very roughly 30FPS */
849 autoscroll_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &Editor::autoscroll_canvas), 30);
853 Editor::stop_canvas_autoscroll ()
855 autoscroll_connection.disconnect ();
859 Editor::EnterContext*
860 Editor::get_enter_context(ItemType type)
862 for (ssize_t i = _enter_stack.size() - 1; i >= 0; --i) {
863 if (_enter_stack[i].item_type == type) {
864 return &_enter_stack[i];
871 Editor::left_track_canvas (GdkEventCrossing* ev)
873 const bool was_within = within_track_canvas;
875 within_track_canvas = false;
876 set_entered_track (0);
877 set_entered_regionview (0);
878 reset_canvas_action_sensitivity (false);
881 if (ev->detail == GDK_NOTIFY_NONLINEAR ||
882 ev->detail == GDK_NOTIFY_NONLINEAR_VIRTUAL) {
883 /* context menu or something similar */
884 sensitize_the_right_region_actions (false);
886 sensitize_the_right_region_actions (true);
894 Editor::entered_track_canvas (GdkEventCrossing* ev)
896 const bool was_within = within_track_canvas;
897 within_track_canvas = true;
898 reset_canvas_action_sensitivity (true);
901 if (ev->detail == GDK_NOTIFY_NONLINEAR ||
902 ev->detail == GDK_NOTIFY_NONLINEAR_VIRTUAL) {
903 /* context menu or something similar */
904 sensitize_the_right_region_actions (false);
906 sensitize_the_right_region_actions (true);
914 Editor::ensure_time_axis_view_is_visible (TimeAxisView const & track, bool at_top)
916 if (track.hidden()) {
920 /* compute visible area of trackview group, as offsets from top of
924 double const current_view_min_y = vertical_adjustment.get_value();
925 double const current_view_max_y = current_view_min_y + vertical_adjustment.get_page_size();
927 double const track_min_y = track.y_position ();
928 double const track_max_y = track.y_position () + track.effective_height ();
931 (track_min_y >= current_view_min_y &&
932 track_max_y < current_view_max_y)) {
933 /* already visible, and caller did not ask to place it at the
934 * top of the track canvas
942 new_value = track_min_y;
944 if (track_min_y < current_view_min_y) {
945 // Track is above the current view
946 new_value = track_min_y;
947 } else if (track_max_y > current_view_max_y) {
948 // Track is below the current view
949 new_value = track.y_position () + track.effective_height() - vertical_adjustment.get_page_size();
951 new_value = track_min_y;
955 vertical_adjustment.set_value(new_value);
958 /** Called when the main vertical_adjustment has changed */
960 Editor::tie_vertical_scrolling ()
962 if (pending_visual_change.idle_handler_id < 0) {
963 _summary->set_overlays_dirty ();
968 Editor::set_horizontal_position (double p)
970 horizontal_adjustment.set_value (p);
972 leftmost_frame = (framepos_t) floor (p * samples_per_pixel);
976 Editor::color_handler()
978 Gtkmm2ext::Color base = UIConfiguration::instance().color ("ruler base");
979 Gtkmm2ext::Color text = UIConfiguration::instance().color ("ruler text");
980 timecode_ruler->set_fill_color (base);
981 timecode_ruler->set_outline_color (text);
982 minsec_ruler->set_fill_color (base);
983 minsec_ruler->set_outline_color (text);
984 samples_ruler->set_fill_color (base);
985 samples_ruler->set_outline_color (text);
986 bbt_ruler->set_fill_color (base);
987 bbt_ruler->set_outline_color (text);
989 playhead_cursor->set_color (UIConfiguration::instance().color ("play head"));
991 meter_bar->set_fill_color (UIConfiguration::instance().color_mod ("meter bar", "marker bar"));
992 meter_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
994 tempo_bar->set_fill_color (UIConfiguration::instance().color_mod ("tempo bar", "marker bar"));
995 tempo_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
997 marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("marker bar", "marker bar"));
998 marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1000 cd_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("cd marker bar", "marker bar"));
1001 cd_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1003 range_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("range marker bar", "marker bar"));
1004 range_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1006 transport_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("transport marker bar", "marker bar"));
1007 transport_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1009 cd_marker_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
1010 cd_marker_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
1012 range_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
1013 range_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
1015 transport_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("transport drag rect"));
1016 transport_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("transport drag rect"));
1018 transport_loop_range_rect->set_fill_color (UIConfiguration::instance().color_mod ("transport loop rect", "loop rectangle"));
1019 transport_loop_range_rect->set_outline_color (UIConfiguration::instance().color ("transport loop rect"));
1021 transport_punch_range_rect->set_fill_color (UIConfiguration::instance().color ("transport punch rect"));
1022 transport_punch_range_rect->set_outline_color (UIConfiguration::instance().color ("transport punch rect"));
1024 transport_punchin_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
1025 transport_punchout_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
1027 rubberband_rect->set_outline_color (UIConfiguration::instance().color ("rubber band rect"));
1028 rubberband_rect->set_fill_color (UIConfiguration::instance().color_mod ("rubber band rect", "selection rect"));
1030 location_marker_color = UIConfiguration::instance().color ("location marker");
1031 location_range_color = UIConfiguration::instance().color ("location range");
1032 location_cd_marker_color = UIConfiguration::instance().color ("location cd marker");
1033 location_loop_color = UIConfiguration::instance().color ("location loop");
1034 location_punch_color = UIConfiguration::instance().color ("location punch");
1036 refresh_location_display ();
1038 NoteBase::set_colors ();
1040 /* redraw the whole thing */
1041 _track_canvas->set_background_color (UIConfiguration::instance().color ("arrange base"));
1042 _track_canvas->queue_draw ();
1045 redisplay_tempo (true);
1048 _session->tempo_map().apply_with_metrics (*this, &Editor::draw_metric_marks); // redraw metric markers
1053 Editor::horizontal_position () const
1055 return sample_to_pixel (leftmost_frame);
1059 Editor::track_canvas_key_press (GdkEventKey*)
1065 Editor::track_canvas_key_release (GdkEventKey*)
1071 Editor::clamp_verbose_cursor_x (double x)
1076 x = min (_visible_canvas_width - 200.0, x);
1082 Editor::clamp_verbose_cursor_y (double y)
1085 y = min (_visible_canvas_height - 50, y);
1089 ArdourCanvas::GtkCanvasViewport*
1090 Editor::get_track_canvas() const
1092 return _track_canvas_viewport;
1096 Editor::get_canvas_cursor () const
1098 /* The top of the cursor stack is always the currently visible cursor. */
1099 return _cursor_stack.back();
1103 Editor::set_canvas_cursor (Gdk::Cursor* cursor)
1105 Glib::RefPtr<Gdk::Window> win = _track_canvas->get_window();
1107 if (win && !_cursors->is_invalid (cursor)) {
1108 /* glibmm 2.4 doesn't allow null cursor pointer because it uses
1109 a Gdk::Cursor& as the argument to Gdk::Window::set_cursor().
1110 But a null pointer just means "use parent window cursor",
1111 and so should be allowed. Gtkmm 3.x has fixed this API.
1113 For now, drop down and use C API
1115 gdk_window_set_cursor (win->gobj(), cursor ? cursor->gobj() : 0);
1120 Editor::push_canvas_cursor (Gdk::Cursor* cursor)
1122 if (!_cursors->is_invalid (cursor)) {
1123 _cursor_stack.push_back (cursor);
1124 set_canvas_cursor (cursor);
1126 return _cursor_stack.size() - 1;
1130 Editor::pop_canvas_cursor ()
1133 if (_cursor_stack.size() <= 1) {
1134 PBD::error << "attempt to pop default cursor" << endmsg;
1138 _cursor_stack.pop_back();
1139 if (_cursor_stack.back()) {
1140 /* Popped to an existing cursor, we're done. Otherwise, the
1141 context that created this cursor has been destroyed, so we need
1142 to skip to the next down the stack. */
1143 set_canvas_cursor (_cursor_stack.back());
1150 Editor::which_grabber_cursor () const
1152 Gdk::Cursor* c = _cursors->grabber;
1154 switch (_edit_point) {
1156 c = _cursors->grabber_edit_point;
1159 boost::shared_ptr<Movable> m = _movable.lock();
1160 if (m && m->locked()) {
1161 c = _cursors->speaker;
1170 Editor::which_trim_cursor (bool left) const
1172 if (!entered_regionview) {
1176 Trimmable::CanTrim ct = entered_regionview->region()->can_trim ();
1180 if (ct & Trimmable::FrontTrimEarlier) {
1181 return _cursors->left_side_trim;
1183 return _cursors->left_side_trim_right_only;
1186 if (ct & Trimmable::EndTrimLater) {
1187 return _cursors->right_side_trim;
1189 return _cursors->right_side_trim_left_only;
1195 Editor::which_mode_cursor () const
1197 Gdk::Cursor* mode_cursor = MouseCursors::invalid_cursor ();
1199 switch (mouse_mode) {
1201 mode_cursor = _cursors->selector;
1205 mode_cursor = _cursors->scissors;
1210 /* don't use mode cursor, pick a grabber cursor based on the item */
1214 mode_cursor = _cursors->midi_pencil;
1218 mode_cursor = _cursors->time_fx; // just use playhead
1222 mode_cursor = _cursors->speaker;
1226 /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
1227 if (get_smart_mode()) {
1230 get_pointer_position (x, y);
1232 if (x >= 0 && y >= 0) {
1234 vector<ArdourCanvas::Item const *> items;
1236 /* Note how we choose a specific scroll group to get
1237 * items from. This could be problematic.
1240 hv_scroll_group->add_items_at_point (ArdourCanvas::Duple (x,y), items);
1242 // first item will be the upper most
1244 if (!items.empty()) {
1245 const ArdourCanvas::Item* i = items.front();
1247 if (i && i->parent() && i->parent()->get_data (X_("timeselection"))) {
1248 pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y);
1249 if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
1250 mode_cursor = _cursors->up_down;
1261 Editor::which_track_cursor () const
1263 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
1265 switch (_join_object_range_state) {
1266 case JOIN_OBJECT_RANGE_NONE:
1267 case JOIN_OBJECT_RANGE_OBJECT:
1268 cursor = which_grabber_cursor ();
1270 case JOIN_OBJECT_RANGE_RANGE:
1271 cursor = _cursors->selector;
1279 Editor::which_canvas_cursor(ItemType type) const
1281 Gdk::Cursor* cursor = which_mode_cursor ();
1283 if (mouse_mode == MouseRange) {
1285 case StartSelectionTrimItem:
1286 cursor = _cursors->left_side_trim;
1288 case EndSelectionTrimItem:
1289 cursor = _cursors->right_side_trim;
1296 if ((mouse_mode == MouseObject || get_smart_mode ()) ||
1297 mouse_mode == MouseContent) {
1299 /* find correct cursor to use in object/smart mode */
1303 /* We don't choose a cursor for these items on top of a region view,
1304 because this would push a new context on the enter stack which
1305 means switching the region context for things like smart mode
1306 won't actualy change the cursor. */
1307 // case RegionViewNameHighlight:
1308 // case RegionViewName:
1311 case AutomationTrackItem:
1312 cursor = which_track_cursor ();
1314 case PlayheadCursorItem:
1315 switch (_edit_point) {
1317 cursor = _cursors->grabber_edit_point;
1320 cursor = _cursors->grabber;
1325 cursor = _cursors->selector;
1327 case ControlPointItem:
1328 cursor = _cursors->fader;
1331 cursor = _cursors->cross_hair;
1333 case AutomationLineItem:
1334 cursor = _cursors->cross_hair;
1336 case StartSelectionTrimItem:
1337 cursor = _cursors->left_side_trim;
1339 case EndSelectionTrimItem:
1340 cursor = _cursors->right_side_trim;
1343 cursor = _cursors->fade_in;
1345 case FadeInHandleItem:
1346 cursor = _cursors->fade_in;
1348 case FadeInTrimHandleItem:
1349 cursor = _cursors->fade_in;
1352 cursor = _cursors->fade_out;
1354 case FadeOutHandleItem:
1355 cursor = _cursors->fade_out;
1357 case FadeOutTrimHandleItem:
1358 cursor = _cursors->fade_out;
1360 case FeatureLineItem:
1361 cursor = _cursors->cross_hair;
1363 case LeftFrameHandle:
1364 if ( effective_mouse_mode() == MouseObject ) // (smart mode): if the user is in the btm half, show the trim cursor
1365 cursor = which_trim_cursor (true);
1367 cursor = _cursors->selector; // (smart mode): in the top half, just show the selection (range) cursor
1369 case RightFrameHandle:
1370 if ( effective_mouse_mode() == MouseObject ) //see above
1371 cursor = which_trim_cursor (false);
1373 cursor = _cursors->selector;
1375 case StartCrossFadeItem:
1376 cursor = _cursors->fade_in;
1378 case EndCrossFadeItem:
1379 cursor = _cursors->fade_out;
1381 case CrossfadeViewItem:
1382 cursor = _cursors->cross_hair;
1385 cursor = _cursors->grabber_note;
1390 } else if (mouse_mode == MouseDraw) {
1392 /* ControlPointItem is not really specific to region gain mode
1393 but it is the same cursor so don't worry about this for now.
1394 The result is that we'll see the fader cursor if we enter
1395 non-region-gain-line control points while in MouseDraw
1396 mode, even though we can't edit them in this mode.
1401 case ControlPointItem:
1402 cursor = _cursors->fader;
1405 cursor = _cursors->grabber_note;
1412 /* These items use the timebar cursor at all times */
1413 case TimecodeRulerItem:
1414 case MinsecRulerItem:
1416 case SamplesRulerItem:
1417 cursor = _cursors->timebar;
1420 /* These items use the grabber cursor at all times */
1421 case MeterMarkerItem:
1422 case TempoMarkerItem:
1427 case RangeMarkerBarItem:
1428 case CdMarkerBarItem:
1430 case TransportMarkerBarItem:
1432 cursor = which_grabber_cursor();
1443 Editor::choose_canvas_cursor_on_entry (ItemType type)
1445 if (_drags->active()) {
1449 Gdk::Cursor* cursor = which_canvas_cursor(type);
1451 if (!_cursors->is_invalid (cursor)) {
1452 // Push a new enter context
1453 const EnterContext ctx = { type, CursorContext::create(*this, cursor) };
1454 _enter_stack.push_back(ctx);
1459 Editor::update_all_enter_cursors ()
1461 for (std::vector<EnterContext>::iterator i = _enter_stack.begin(); i != _enter_stack.end(); ++i) {
1462 i->cursor_ctx->change(which_canvas_cursor(i->item_type));
1467 Editor::trackviews_height() const
1469 if (!_trackview_group) {
1473 return _visible_canvas_height - _trackview_group->canvas_origin().y;