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 () const
587 framecnt_t session_extent_start = _session->current_start_frame();
588 framecnt_t session_extent_end = _session->current_end_frame();
590 //calculate the extents of all regions in every playlist
591 //NOTE: we should listen to playlists, and cache these values so we don't calculate them every time.
593 boost::shared_ptr<RouteList> rl = _session->get_routes();
594 for (RouteList::iterator r = rl->begin(); r != rl->end(); ++r) {
595 boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*r);
597 boost::shared_ptr<Playlist> pl = tr->playlist();
598 if ( pl && !pl->all_regions_empty() ) {
599 pair<framepos_t, framepos_t> e;
600 e = pl->get_extent();
601 if (e.first < session_extent_start) {
602 session_extent_start = e.first;
604 if (e.second > session_extent_end) {
605 session_extent_end = e.second;
612 //ToDo: also incorporate automation regions (in case the session has no audio/midi but is just used for automating plugins or the like)
614 //if all else fails, give us 2 minutes
615 framecnt_t const min_length = _session->nominal_frame_rate()*60*2;
616 if ( session_extent_end < min_length )
617 session_extent_end = min_length;
619 std::pair <framepos_t,framepos_t> ret (session_extent_start, session_extent_end);
624 Editor::autoscroll_canvas ()
627 Gdk::ModifierType mask;
628 frameoffset_t dx = 0;
629 bool no_stop = false;
630 Gtk::Window* toplevel = dynamic_cast<Gtk::Window*>(contents().get_toplevel());
636 toplevel->get_window()->get_pointer (x, y, mask);
639 bool vertical_motion = false;
641 if (autoscroll_horizontal_allowed) {
643 framepos_t new_frame = leftmost_frame;
647 if (x > autoscroll_boundary.x1) {
649 /* bring it back into view */
650 dx = x - autoscroll_boundary.x1;
651 dx += 10 + (2 * (autoscroll_cnt/2));
653 dx = pixel_to_sample (dx);
655 dx /= 10; //ToDo: make a config variable for scroll speed zoom-behavior-tweaks
657 if (leftmost_frame < max_framepos - dx) {
658 new_frame = leftmost_frame + dx;
660 new_frame = max_framepos;
665 } else if (x < autoscroll_boundary.x0) {
667 dx = autoscroll_boundary.x0 - x;
668 dx += 10 + (2 * (autoscroll_cnt/2));
670 dx = pixel_to_sample (dx);
672 dx /= 10; //ToDo: make a config variable for scroll speed zoom-behavior-tweaks
674 if (leftmost_frame >= dx) {
675 new_frame = leftmost_frame - dx;
683 if (new_frame != leftmost_frame) {
684 vc.time_origin = new_frame;
685 vc.add (VisualChange::TimeOrigin);
689 if (autoscroll_vertical_allowed) {
691 // const double vertical_pos = vertical_adjustment.get_value();
692 const int speed_factor = 10;
696 if (y < autoscroll_boundary.y0) {
698 /* scroll to make higher tracks visible */
700 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
701 scroll_up_one_track ();
702 vertical_motion = true;
706 } else if (y > autoscroll_boundary.y1) {
708 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
709 scroll_down_one_track ();
710 vertical_motion = true;
717 if (vc.pending || vertical_motion) {
719 /* change horizontal first */
725 /* now send a motion event to notify anyone who cares
726 that we have moved to a new location (because we scrolled)
731 ev.type = GDK_MOTION_NOTIFY;
732 ev.state = Gdk::BUTTON1_MASK;
734 /* the motion handler expects events in canvas coordinate space */
736 /* we asked for the mouse position above (::get_pointer()) via
737 * our own top level window (we being the Editor). Convert into
738 * coordinates within the canvas window.
744 toplevel->translate_coordinates (*_track_canvas, x, y, cx, cy);
746 /* clamp x and y to remain within the autoscroll boundary,
747 * which is defined in window coordinates
750 x = min (max ((ArdourCanvas::Coord) cx, autoscroll_boundary.x0), autoscroll_boundary.x1);
751 y = min (max ((ArdourCanvas::Coord) cy, autoscroll_boundary.y0), autoscroll_boundary.y1);
753 /* now convert from Editor window coordinates to canvas
757 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
762 motion_handler (0, (GdkEvent*) &ev, true);
764 } else if (no_stop) {
766 /* not changing visual state but pointer is outside the scrolling boundary
767 * so we still need to deliver a fake motion event
772 ev.type = GDK_MOTION_NOTIFY;
773 ev.state = Gdk::BUTTON1_MASK;
775 /* the motion handler expects events in canvas coordinate space */
777 /* first convert from Editor window coordinates to canvas
784 /* clamp x and y to remain within the visible area. except
785 * .. if horizontal scrolling is allowed, always allow us to
789 if (autoscroll_horizontal_allowed) {
790 x = min (max ((ArdourCanvas::Coord) x, 0.0), autoscroll_boundary.x1);
792 x = min (max ((ArdourCanvas::Coord) x, autoscroll_boundary.x0), autoscroll_boundary.x1);
794 y = min (max ((ArdourCanvas::Coord) y, autoscroll_boundary.y0), autoscroll_boundary.y1);
796 toplevel->translate_coordinates (*_track_canvas_viewport, x, y, cx, cy);
798 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
803 motion_handler (0, (GdkEvent*) &ev, true);
806 stop_canvas_autoscroll ();
812 return true; /* call me again */
816 Editor::start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary)
822 stop_canvas_autoscroll ();
824 autoscroll_horizontal_allowed = allow_horiz;
825 autoscroll_vertical_allowed = allow_vert;
826 autoscroll_boundary = boundary;
828 /* do the first scroll right now
831 autoscroll_canvas ();
833 /* scroll again at very very roughly 30FPS */
835 autoscroll_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &Editor::autoscroll_canvas), 30);
839 Editor::stop_canvas_autoscroll ()
841 autoscroll_connection.disconnect ();
845 Editor::EnterContext*
846 Editor::get_enter_context(ItemType type)
848 for (ssize_t i = _enter_stack.size() - 1; i >= 0; --i) {
849 if (_enter_stack[i].item_type == type) {
850 return &_enter_stack[i];
857 Editor::left_track_canvas (GdkEventCrossing* ev)
859 const bool was_within = within_track_canvas;
861 within_track_canvas = false;
862 set_entered_track (0);
863 set_entered_regionview (0);
864 reset_canvas_action_sensitivity (false);
867 if (ev->detail == GDK_NOTIFY_NONLINEAR ||
868 ev->detail == GDK_NOTIFY_NONLINEAR_VIRTUAL) {
869 /* context menu or something similar */
870 sensitize_the_right_region_actions (false);
872 sensitize_the_right_region_actions (true);
880 Editor::entered_track_canvas (GdkEventCrossing* ev)
882 const bool was_within = within_track_canvas;
883 within_track_canvas = true;
884 reset_canvas_action_sensitivity (true);
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::ensure_time_axis_view_is_visible (TimeAxisView const & track, bool at_top)
902 if (track.hidden()) {
906 /* compute visible area of trackview group, as offsets from top of
910 double const current_view_min_y = vertical_adjustment.get_value();
911 double const current_view_max_y = current_view_min_y + vertical_adjustment.get_page_size();
913 double const track_min_y = track.y_position ();
914 double const track_max_y = track.y_position () + track.effective_height ();
917 (track_min_y >= current_view_min_y &&
918 track_max_y < current_view_max_y)) {
919 /* already visible, and caller did not ask to place it at the
920 * top of the track canvas
928 new_value = track_min_y;
930 if (track_min_y < current_view_min_y) {
931 // Track is above the current view
932 new_value = track_min_y;
933 } else if (track_max_y > current_view_max_y) {
934 // Track is below the current view
935 new_value = track.y_position () + track.effective_height() - vertical_adjustment.get_page_size();
937 new_value = track_min_y;
941 vertical_adjustment.set_value(new_value);
944 /** Called when the main vertical_adjustment has changed */
946 Editor::tie_vertical_scrolling ()
948 if (pending_visual_change.idle_handler_id < 0) {
949 _summary->set_overlays_dirty ();
954 Editor::set_horizontal_position (double p)
956 horizontal_adjustment.set_value (p);
958 leftmost_frame = (framepos_t) floor (p * samples_per_pixel);
962 Editor::color_handler()
964 Gtkmm2ext::Color base = UIConfiguration::instance().color ("ruler base");
965 Gtkmm2ext::Color text = UIConfiguration::instance().color ("ruler text");
966 timecode_ruler->set_fill_color (base);
967 timecode_ruler->set_outline_color (text);
968 minsec_ruler->set_fill_color (base);
969 minsec_ruler->set_outline_color (text);
970 samples_ruler->set_fill_color (base);
971 samples_ruler->set_outline_color (text);
972 bbt_ruler->set_fill_color (base);
973 bbt_ruler->set_outline_color (text);
975 playhead_cursor->set_color (UIConfiguration::instance().color ("play head"));
977 meter_bar->set_fill_color (UIConfiguration::instance().color_mod ("meter bar", "marker bar"));
978 meter_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
980 tempo_bar->set_fill_color (UIConfiguration::instance().color_mod ("tempo bar", "marker bar"));
981 tempo_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
983 marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("marker bar", "marker bar"));
984 marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
986 cd_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("cd marker bar", "marker bar"));
987 cd_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
989 range_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("range marker bar", "marker bar"));
990 range_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
992 transport_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("transport marker bar", "marker bar"));
993 transport_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
995 cd_marker_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
996 cd_marker_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
998 range_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
999 range_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
1001 transport_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("transport drag rect"));
1002 transport_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("transport drag rect"));
1004 transport_loop_range_rect->set_fill_color (UIConfiguration::instance().color_mod ("transport loop rect", "loop rectangle"));
1005 transport_loop_range_rect->set_outline_color (UIConfiguration::instance().color ("transport loop rect"));
1007 transport_punch_range_rect->set_fill_color (UIConfiguration::instance().color ("transport punch rect"));
1008 transport_punch_range_rect->set_outline_color (UIConfiguration::instance().color ("transport punch rect"));
1010 transport_punchin_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
1011 transport_punchout_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
1013 rubberband_rect->set_outline_color (UIConfiguration::instance().color ("rubber band rect"));
1014 rubberband_rect->set_fill_color (UIConfiguration::instance().color_mod ("rubber band rect", "selection rect"));
1016 location_marker_color = UIConfiguration::instance().color ("location marker");
1017 location_range_color = UIConfiguration::instance().color ("location range");
1018 location_cd_marker_color = UIConfiguration::instance().color ("location cd marker");
1019 location_loop_color = UIConfiguration::instance().color ("location loop");
1020 location_punch_color = UIConfiguration::instance().color ("location punch");
1022 refresh_location_display ();
1024 NoteBase::set_colors ();
1026 /* redraw the whole thing */
1027 _track_canvas->set_background_color (UIConfiguration::instance().color ("arrange base"));
1028 _track_canvas->queue_draw ();
1031 redisplay_tempo (true);
1034 _session->tempo_map().apply_with_metrics (*this, &Editor::draw_metric_marks); // redraw metric markers
1039 Editor::horizontal_position () const
1041 return sample_to_pixel (leftmost_frame);
1045 Editor::track_canvas_key_press (GdkEventKey*)
1051 Editor::track_canvas_key_release (GdkEventKey*)
1057 Editor::clamp_verbose_cursor_x (double x)
1062 x = min (_visible_canvas_width - 200.0, x);
1068 Editor::clamp_verbose_cursor_y (double y)
1071 y = min (_visible_canvas_height - 50, y);
1075 ArdourCanvas::GtkCanvasViewport*
1076 Editor::get_track_canvas() const
1078 return _track_canvas_viewport;
1082 Editor::get_canvas_cursor () const
1084 /* The top of the cursor stack is always the currently visible cursor. */
1085 return _cursor_stack.back();
1089 Editor::set_canvas_cursor (Gdk::Cursor* cursor)
1091 Glib::RefPtr<Gdk::Window> win = _track_canvas->get_window();
1093 if (win && !_cursors->is_invalid (cursor)) {
1094 /* glibmm 2.4 doesn't allow null cursor pointer because it uses
1095 a Gdk::Cursor& as the argument to Gdk::Window::set_cursor().
1096 But a null pointer just means "use parent window cursor",
1097 and so should be allowed. Gtkmm 3.x has fixed this API.
1099 For now, drop down and use C API
1101 gdk_window_set_cursor (win->gobj(), cursor ? cursor->gobj() : 0);
1106 Editor::push_canvas_cursor (Gdk::Cursor* cursor)
1108 if (!_cursors->is_invalid (cursor)) {
1109 _cursor_stack.push_back (cursor);
1110 set_canvas_cursor (cursor);
1112 return _cursor_stack.size() - 1;
1116 Editor::pop_canvas_cursor ()
1119 if (_cursor_stack.size() <= 1) {
1120 PBD::error << "attempt to pop default cursor" << endmsg;
1124 _cursor_stack.pop_back();
1125 if (_cursor_stack.back()) {
1126 /* Popped to an existing cursor, we're done. Otherwise, the
1127 context that created this cursor has been destroyed, so we need
1128 to skip to the next down the stack. */
1129 set_canvas_cursor (_cursor_stack.back());
1136 Editor::which_grabber_cursor () const
1138 Gdk::Cursor* c = _cursors->grabber;
1140 switch (_edit_point) {
1142 c = _cursors->grabber_edit_point;
1145 boost::shared_ptr<Movable> m = _movable.lock();
1146 if (m && m->locked()) {
1147 c = _cursors->speaker;
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 = which_grabber_cursor ();
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 switch (_edit_point) {
1303 cursor = _cursors->grabber_edit_point;
1306 cursor = _cursors->grabber;
1311 cursor = _cursors->selector;
1313 case ControlPointItem:
1314 cursor = _cursors->fader;
1317 cursor = _cursors->cross_hair;
1319 case AutomationLineItem:
1320 cursor = _cursors->cross_hair;
1322 case StartSelectionTrimItem:
1323 cursor = _cursors->left_side_trim;
1325 case EndSelectionTrimItem:
1326 cursor = _cursors->right_side_trim;
1329 cursor = _cursors->fade_in;
1331 case FadeInHandleItem:
1332 cursor = _cursors->fade_in;
1334 case FadeInTrimHandleItem:
1335 cursor = _cursors->fade_in;
1338 cursor = _cursors->fade_out;
1340 case FadeOutHandleItem:
1341 cursor = _cursors->fade_out;
1343 case FadeOutTrimHandleItem:
1344 cursor = _cursors->fade_out;
1346 case FeatureLineItem:
1347 cursor = _cursors->cross_hair;
1349 case LeftFrameHandle:
1350 if ( effective_mouse_mode() == MouseObject ) // (smart mode): if the user is in the btm half, show the trim cursor
1351 cursor = which_trim_cursor (true);
1353 cursor = _cursors->selector; // (smart mode): in the top half, just show the selection (range) cursor
1355 case RightFrameHandle:
1356 if ( effective_mouse_mode() == MouseObject ) //see above
1357 cursor = which_trim_cursor (false);
1359 cursor = _cursors->selector;
1361 case StartCrossFadeItem:
1362 cursor = _cursors->fade_in;
1364 case EndCrossFadeItem:
1365 cursor = _cursors->fade_out;
1367 case CrossfadeViewItem:
1368 cursor = _cursors->cross_hair;
1371 cursor = _cursors->grabber_note;
1376 } else if (mouse_mode == MouseDraw) {
1378 /* ControlPointItem is not really specific to region gain mode
1379 but it is the same cursor so don't worry about this for now.
1380 The result is that we'll see the fader cursor if we enter
1381 non-region-gain-line control points while in MouseDraw
1382 mode, even though we can't edit them in this mode.
1387 case ControlPointItem:
1388 cursor = _cursors->fader;
1391 cursor = _cursors->grabber_note;
1398 /* These items use the timebar cursor at all times */
1399 case TimecodeRulerItem:
1400 case MinsecRulerItem:
1402 case SamplesRulerItem:
1403 cursor = _cursors->timebar;
1406 /* These items use the grabber cursor at all times */
1407 case MeterMarkerItem:
1408 case TempoMarkerItem:
1413 case RangeMarkerBarItem:
1414 case CdMarkerBarItem:
1416 case TransportMarkerBarItem:
1418 cursor = which_grabber_cursor();
1429 Editor::choose_canvas_cursor_on_entry (ItemType type)
1431 if (_drags->active()) {
1435 Gdk::Cursor* cursor = which_canvas_cursor(type);
1437 if (!_cursors->is_invalid (cursor)) {
1438 // Push a new enter context
1439 const EnterContext ctx = { type, CursorContext::create(*this, cursor) };
1440 _enter_stack.push_back(ctx);
1445 Editor::update_all_enter_cursors ()
1447 for (std::vector<EnterContext>::iterator i = _enter_stack.begin(); i != _enter_stack.end(); ++i) {
1448 i->cursor_ctx->change(which_canvas_cursor(i->item_type));
1453 Editor::trackviews_height() const
1455 if (!_trackview_group) {
1459 return _visible_canvas_height - _trackview_group->canvas_origin().y;