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 "ui_config.h"
55 #include "verbose_cursor.h"
60 using namespace ARDOUR;
61 using namespace ARDOUR_UI_UTILS;
65 using namespace Gtkmm2ext;
66 using namespace Editing;
69 Editor::initialize_canvas ()
71 _track_canvas_viewport = new ArdourCanvas::GtkCanvasViewport (horizontal_adjustment, vertical_adjustment);
72 _track_canvas = _track_canvas_viewport->canvas ();
74 _track_canvas->set_background_color (UIConfiguration::instance().color ("arrange base"));
76 /* scroll group for items that should not automatically scroll
77 * (e.g verbose cursor). It shares the canvas coordinate space.
79 no_scroll_group = new ArdourCanvas::Container (_track_canvas->root());
81 ArdourCanvas::ScrollGroup* hsg;
82 ArdourCanvas::ScrollGroup* hg;
83 ArdourCanvas::ScrollGroup* cg;
85 h_scroll_group = hg = new ArdourCanvas::ScrollGroup (_track_canvas->root(), ArdourCanvas::ScrollGroup::ScrollsHorizontally);
86 CANVAS_DEBUG_NAME (h_scroll_group, "canvas h scroll");
87 _track_canvas->add_scroller (*hg);
89 hv_scroll_group = hsg = new ArdourCanvas::ScrollGroup (_track_canvas->root(),
90 ArdourCanvas::ScrollGroup::ScrollSensitivity (ArdourCanvas::ScrollGroup::ScrollsVertically|
91 ArdourCanvas::ScrollGroup::ScrollsHorizontally));
92 CANVAS_DEBUG_NAME (hv_scroll_group, "canvas hv scroll");
93 _track_canvas->add_scroller (*hsg);
95 cursor_scroll_group = cg = new ArdourCanvas::ScrollGroup (_track_canvas->root(), ArdourCanvas::ScrollGroup::ScrollsHorizontally);
96 CANVAS_DEBUG_NAME (cursor_scroll_group, "canvas cursor scroll");
97 _track_canvas->add_scroller (*cg);
99 _verbose_cursor = new VerboseCursor (this);
101 /*a group to hold global rects like punch/loop indicators */
102 global_rect_group = new ArdourCanvas::Container (hv_scroll_group);
103 CANVAS_DEBUG_NAME (global_rect_group, "global rect group");
105 transport_loop_range_rect = new ArdourCanvas::Rectangle (global_rect_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, ArdourCanvas::COORD_MAX));
106 CANVAS_DEBUG_NAME (transport_loop_range_rect, "loop rect");
107 transport_loop_range_rect->hide();
109 transport_punch_range_rect = new ArdourCanvas::Rectangle (global_rect_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, ArdourCanvas::COORD_MAX));
110 CANVAS_DEBUG_NAME (transport_punch_range_rect, "punch rect");
111 transport_punch_range_rect->hide();
113 /*a group to hold time (measure) lines */
114 time_line_group = new ArdourCanvas::Container (h_scroll_group);
115 CANVAS_DEBUG_NAME (time_line_group, "time line group");
117 _trackview_group = new ArdourCanvas::Container (hv_scroll_group);
118 CANVAS_DEBUG_NAME (_trackview_group, "Canvas TrackViews");
120 // used as rubberband rect
121 rubberband_rect = new ArdourCanvas::Rectangle (hv_scroll_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, 0.0));
122 rubberband_rect->hide();
124 /* a group to hold stuff while it gets dragged around. Must be the
125 * uppermost (last) group with hv_scroll_group as a parent
127 _drag_motion_group = new ArdourCanvas::Container (hv_scroll_group);
128 CANVAS_DEBUG_NAME (_drag_motion_group, "Canvas Drag Motion");
130 /* TIME BAR CANVAS */
132 _time_markers_group = new ArdourCanvas::Container (h_scroll_group);
133 CANVAS_DEBUG_NAME (_time_markers_group, "time bars");
135 cd_marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, 0.0));
136 CANVAS_DEBUG_NAME (cd_marker_group, "cd marker group");
137 /* the vide is temporarily placed a the same location as the
138 cd_marker_group, but is moved later.
140 videotl_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple(0.0, 0.0));
141 CANVAS_DEBUG_NAME (videotl_group, "videotl group");
142 marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, timebar_height + 1.0));
143 CANVAS_DEBUG_NAME (marker_group, "marker group");
144 transport_marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 2.0) + 1.0));
145 CANVAS_DEBUG_NAME (transport_marker_group, "transport marker group");
146 range_marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 3.0) + 1.0));
147 CANVAS_DEBUG_NAME (range_marker_group, "range marker group");
148 tempo_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 4.0) + 1.0));
149 CANVAS_DEBUG_NAME (tempo_group, "tempo group");
150 meter_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 5.0) + 1.0));
151 CANVAS_DEBUG_NAME (meter_group, "meter group");
153 meter_bar = new ArdourCanvas::Rectangle (meter_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
154 CANVAS_DEBUG_NAME (meter_bar, "meter Bar");
155 meter_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
157 tempo_bar = new ArdourCanvas::Rectangle (tempo_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
158 CANVAS_DEBUG_NAME (tempo_bar, "Tempo Bar");
159 tempo_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
161 range_marker_bar = new ArdourCanvas::Rectangle (range_marker_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
162 CANVAS_DEBUG_NAME (range_marker_bar, "Range Marker Bar");
163 range_marker_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
165 transport_marker_bar = new ArdourCanvas::Rectangle (transport_marker_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
166 CANVAS_DEBUG_NAME (transport_marker_bar, "transport Marker Bar");
167 transport_marker_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
169 marker_bar = new ArdourCanvas::Rectangle (marker_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
170 CANVAS_DEBUG_NAME (marker_bar, "Marker Bar");
171 marker_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
173 cd_marker_bar = new ArdourCanvas::Rectangle (cd_marker_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
174 CANVAS_DEBUG_NAME (cd_marker_bar, "CD Marker Bar");
175 cd_marker_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
177 ARDOUR_UI::instance()->video_timeline = new VideoTimeLine(this, videotl_group, (timebar_height * videotl_bar_height));
179 cd_marker_bar_drag_rect = new ArdourCanvas::Rectangle (cd_marker_group, ArdourCanvas::Rect (0.0, 0.0, 100, timebar_height));
180 CANVAS_DEBUG_NAME (cd_marker_bar_drag_rect, "cd marker drag");
181 cd_marker_bar_drag_rect->set_outline (false);
182 cd_marker_bar_drag_rect->hide ();
184 range_bar_drag_rect = new ArdourCanvas::Rectangle (range_marker_group, ArdourCanvas::Rect (0.0, 0.0, 100, timebar_height));
185 CANVAS_DEBUG_NAME (range_bar_drag_rect, "range drag");
186 range_bar_drag_rect->set_outline (false);
187 range_bar_drag_rect->hide ();
189 transport_bar_drag_rect = new ArdourCanvas::Rectangle (transport_marker_group, ArdourCanvas::Rect (0.0, 0.0, 100, timebar_height));
190 CANVAS_DEBUG_NAME (transport_bar_drag_rect, "transport drag");
191 transport_bar_drag_rect->set_outline (false);
192 transport_bar_drag_rect->hide ();
194 transport_punchin_line = new ArdourCanvas::Line (hv_scroll_group);
195 transport_punchin_line->set_x0 (0);
196 transport_punchin_line->set_y0 (0);
197 transport_punchin_line->set_x1 (0);
198 transport_punchin_line->set_y1 (ArdourCanvas::COORD_MAX);
199 transport_punchin_line->hide ();
201 transport_punchout_line = new ArdourCanvas::Line (hv_scroll_group);
202 transport_punchout_line->set_x0 (0);
203 transport_punchout_line->set_y0 (0);
204 transport_punchout_line->set_x1 (0);
205 transport_punchout_line->set_y1 (ArdourCanvas::COORD_MAX);
206 transport_punchout_line->hide();
208 tempo_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_tempo_bar_event), tempo_bar));
209 meter_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_meter_bar_event), meter_bar));
210 marker_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_marker_bar_event), marker_bar));
211 cd_marker_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_cd_marker_bar_event), cd_marker_bar));
212 videotl_group->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_videotl_bar_event), videotl_group));
213 range_marker_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_range_marker_bar_event), range_marker_bar));
214 transport_marker_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_transport_marker_bar_event), transport_marker_bar));
216 playhead_cursor = new EditorCursor (*this, &Editor::canvas_playhead_cursor_event);
218 _canvas_drop_zone = new ArdourCanvas::Rectangle (hv_scroll_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, 0.0));
219 /* this thing is transparent */
220 _canvas_drop_zone->set_fill (false);
221 _canvas_drop_zone->set_outline (false);
222 _canvas_drop_zone->Event.connect (sigc::mem_fun (*this, &Editor::canvas_drop_zone_event));
224 /* these signals will initially be delivered to the canvas itself, but if they end up remaining unhandled, they are passed to Editor-level
228 _track_canvas->signal_scroll_event().connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_scroll_event), true));
229 _track_canvas->signal_motion_notify_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_motion_notify_event));
230 _track_canvas->signal_button_press_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_button_press_event));
231 _track_canvas->signal_button_release_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_button_release_event));
232 _track_canvas->signal_drag_motion().connect (sigc::mem_fun (*this, &Editor::track_canvas_drag_motion));
233 _track_canvas->signal_key_press_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_key_press));
234 _track_canvas->signal_key_release_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_key_release));
236 _track_canvas->set_name ("EditorMainCanvas");
237 _track_canvas->add_events (Gdk::POINTER_MOTION_HINT_MASK | Gdk::SCROLL_MASK | Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
238 _track_canvas->signal_leave_notify_event().connect (sigc::mem_fun(*this, &Editor::left_track_canvas), false);
239 _track_canvas->signal_enter_notify_event().connect (sigc::mem_fun(*this, &Editor::entered_track_canvas), false);
240 _track_canvas->set_flags (CAN_FOCUS);
242 /* set up drag-n-drop */
244 vector<TargetEntry> target_table;
246 // Drag-N-Drop from the region list can generate this target
247 target_table.push_back (TargetEntry ("regions"));
249 target_table.push_back (TargetEntry ("text/plain"));
250 target_table.push_back (TargetEntry ("text/uri-list"));
251 target_table.push_back (TargetEntry ("application/x-rootwin-drop"));
253 _track_canvas->drag_dest_set (target_table);
254 _track_canvas->signal_drag_data_received().connect (sigc::mem_fun(*this, &Editor::track_canvas_drag_data_received));
256 _track_canvas_viewport->signal_size_allocate().connect (sigc::mem_fun(*this, &Editor::track_canvas_viewport_allocate));
258 initialize_rulers ();
260 UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &Editor::color_handler));
266 Editor::track_canvas_viewport_allocate (Gtk::Allocation alloc)
268 _canvas_viewport_allocation = alloc;
269 track_canvas_viewport_size_allocated ();
273 Editor::track_canvas_viewport_size_allocated ()
275 bool height_changed = _visible_canvas_height != _canvas_viewport_allocation.get_height();
277 _visible_canvas_width = _canvas_viewport_allocation.get_width ();
278 _visible_canvas_height = _canvas_viewport_allocation.get_height ();
280 _canvas_drop_zone->set_y1 (_canvas_drop_zone->y0() + (_visible_canvas_height - 20.0));
284 if (height_changed) {
286 for (LocationMarkerMap::iterator i = location_markers.begin(); i != location_markers.end(); ++i) {
287 i->second->canvas_height_set (_visible_canvas_height);
290 vertical_adjustment.set_page_size (_visible_canvas_height);
291 if ((vertical_adjustment.get_value() + _visible_canvas_height) >= vertical_adjustment.get_upper()) {
293 We're increasing the size of the canvas while the bottom is visible.
294 We scroll down to keep in step with the controls layout.
296 vertical_adjustment.set_value (_full_canvas_height - _visible_canvas_height);
299 set_visible_track_count (_visible_track_count);
302 update_fixed_rulers();
303 redisplay_tempo (false);
304 _summary->set_overlays_dirty ();
308 Editor::reset_controls_layout_width ()
310 GtkRequisition req = { 0, 0 };
313 edit_controls_vbox.size_request (req);
316 if (_group_tabs->is_visible()) {
317 _group_tabs->size_request (req);
321 /* the controls layout has no horizontal scrolling, its visible
322 width is always equal to the total width of its contents.
325 controls_layout.property_width() = w;
326 controls_layout.property_width_request() = w;
330 Editor::reset_controls_layout_height (int32_t h)
332 /* ensure that the rect that represents the "bottom" of the canvas
333 * (the drag-n-drop zone) is, in fact, at the bottom.
336 _canvas_drop_zone->set_position (ArdourCanvas::Duple (0, h));
338 /* track controls layout must span the full height of "h" (all tracks)
339 * plus the bottom rect.
342 h += _canvas_drop_zone->height ();
344 /* set the height of the scrollable area (i.e. the sum of all contained widgets)
345 * for the controls layout. The size request is set elsewhere.
348 controls_layout.property_height() = h;
353 Editor::track_canvas_map_handler (GdkEventAny* /*ev*/)
355 if (!_cursor_stack.empty()) {
356 set_canvas_cursor (get_canvas_cursor());
358 PBD::error << "cursor stack is empty" << endmsg;
363 /** This is called when something is dropped onto the track canvas */
365 Editor::track_canvas_drag_data_received (const RefPtr<Gdk::DragContext>& context,
367 const SelectionData& data,
368 guint info, guint time)
370 if (data.get_target() == "regions") {
371 drop_regions (context, x, y, data, info, time);
373 drop_paths (context, x, y, data, info, time);
378 Editor::idle_drop_paths (vector<string> paths, framepos_t frame, double ypos, bool copy)
380 drop_paths_part_two (paths, frame, ypos, copy);
385 Editor::drop_paths_part_two (const vector<string>& paths, framepos_t frame, double ypos, bool copy)
387 RouteTimeAxisView* tv;
389 /* MIDI files must always be imported, because we consider them
390 * writable. So split paths into two vectors, and follow the import
391 * path on the MIDI part.
394 vector<string> midi_paths;
395 vector<string> audio_paths;
397 for (vector<string>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
398 if (SMFSource::safe_midi_file_extension (*i)) {
399 midi_paths.push_back (*i);
401 audio_paths.push_back (*i);
406 std::pair<TimeAxisView*, int> const tvp = trackview_by_y_position (ypos, false);
407 if (tvp.first == 0) {
409 /* drop onto canvas background: create new tracks */
412 InstrumentSelector is; // instantiation builds instrument-list and sets default.
413 do_import (midi_paths, Editing::ImportDistinctFiles, ImportAsTrack, SrcBest, frame, is.selected_instrument());
415 if (UIConfiguration::instance().get_only_copy_imported_files() || copy) {
416 do_import (audio_paths, Editing::ImportDistinctFiles, Editing::ImportAsTrack, SrcBest, frame);
418 do_embed (audio_paths, Editing::ImportDistinctFiles, ImportAsTrack, frame);
421 } else if ((tv = dynamic_cast<RouteTimeAxisView*> (tvp.first)) != 0) {
423 /* check that its a track, not a bus */
426 /* select the track, then embed/import */
429 do_import (midi_paths, Editing::ImportSerializeFiles, ImportToTrack, SrcBest, frame);
431 if (UIConfiguration::instance().get_only_copy_imported_files() || copy) {
432 do_import (audio_paths, Editing::ImportSerializeFiles, Editing::ImportToTrack, SrcBest, frame);
434 do_embed (audio_paths, Editing::ImportSerializeFiles, ImportToTrack, frame);
441 Editor::drop_paths (const RefPtr<Gdk::DragContext>& context,
443 const SelectionData& data,
444 guint info, guint time)
446 vector<string> paths;
451 if (convert_drop_to_paths (paths, context, x, y, data, info, time) == 0) {
453 /* D-n-D coordinates are window-relative, so convert to canvas coordinates
456 ev.type = GDK_BUTTON_RELEASE;
460 frame = window_event_sample (&ev, 0, &cy);
464 bool copy = ((context->get_actions() & (Gdk::ACTION_COPY | Gdk::ACTION_LINK | Gdk::ACTION_MOVE)) == Gdk::ACTION_COPY);
466 /* We are not allowed to call recursive main event loops from within
467 the main event loop with GTK/Quartz. Since import/embed wants
468 to push up a progress dialog, defer all this till we go idle.
470 Glib::signal_idle().connect (sigc::bind (sigc::mem_fun (*this, &Editor::idle_drop_paths), paths, frame, cy, copy));
472 drop_paths_part_two (paths, frame, cy, copy);
476 context->drag_finish (true, false, time);
479 /** @param allow_horiz true to allow horizontal autoscroll, otherwise false.
481 * @param allow_vert true to allow vertical autoscroll, otherwise false.
485 Editor::maybe_autoscroll (bool allow_horiz, bool allow_vert, bool from_headers)
487 Gtk::Window* toplevel = dynamic_cast<Gtk::Window*>(contents().get_toplevel());
493 if (!UIConfiguration::instance().get_autoscroll_editor () || autoscroll_active ()) {
497 /* define a rectangular boundary for scrolling. If the mouse moves
498 * outside of this area and/or continue to be outside of this area,
499 * then we will continuously auto-scroll the canvas in the appropriate
502 * the boundary is defined in coordinates relative to the toplevel
503 * window since that is what we're going to call ::get_pointer() on
504 * during autoscrolling to determine if we're still outside the
508 ArdourCanvas::Rect scrolling_boundary;
509 Gtk::Allocation alloc;
512 alloc = controls_layout.get_allocation ();
514 alloc = _track_canvas_viewport->get_allocation ();
516 /* reduce height by the height of the timebars, which happens
517 to correspond to the position of the hv_scroll_group.
520 alloc.set_height (alloc.get_height() - hv_scroll_group->position().y);
521 alloc.set_y (alloc.get_y() + hv_scroll_group->position().y);
523 /* now reduce it again so that we start autoscrolling before we
524 * move off the top or bottom of the canvas
527 alloc.set_height (alloc.get_height() - 20);
528 alloc.set_y (alloc.get_y() + 10);
530 /* the effective width of the autoscroll boundary so
531 that we start scrolling before we hit the edge.
533 this helps when the window is slammed up against the
534 right edge of the screen, making it hard to scroll
538 if (alloc.get_width() > 20) {
539 alloc.set_width (alloc.get_width() - 20);
540 alloc.set_x (alloc.get_x() + 10);
545 scrolling_boundary = ArdourCanvas::Rect (alloc.get_x(), alloc.get_y(), alloc.get_x() + alloc.get_width(), alloc.get_y() + alloc.get_height());
548 Gdk::ModifierType mask;
550 toplevel->get_window()->get_pointer (x, y, mask);
552 if ((allow_horiz && ((x < scrolling_boundary.x0 && leftmost_frame > 0) || x >= scrolling_boundary.x1)) ||
553 (allow_vert && ((y < scrolling_boundary.y0 && vertical_adjustment.get_value() > 0)|| y >= scrolling_boundary.y1))) {
554 start_canvas_autoscroll (allow_horiz, allow_vert, scrolling_boundary);
559 Editor::autoscroll_active () const
561 return autoscroll_connection.connected ();
565 Editor::autoscroll_canvas ()
568 Gdk::ModifierType mask;
569 frameoffset_t dx = 0;
570 bool no_stop = false;
571 Gtk::Window* toplevel = dynamic_cast<Gtk::Window*>(contents().get_toplevel());
577 toplevel->get_window()->get_pointer (x, y, mask);
580 bool vertical_motion = false;
582 if (autoscroll_horizontal_allowed) {
584 framepos_t new_frame = leftmost_frame;
588 if (x > autoscroll_boundary.x1) {
590 /* bring it back into view */
591 dx = x - autoscroll_boundary.x1;
592 dx += 10 + (2 * (autoscroll_cnt/2));
594 dx = pixel_to_sample (dx);
596 if (leftmost_frame < max_framepos - dx) {
597 new_frame = leftmost_frame + dx;
599 new_frame = max_framepos;
604 } else if (x < autoscroll_boundary.x0) {
606 dx = autoscroll_boundary.x0 - x;
607 dx += 10 + (2 * (autoscroll_cnt/2));
609 dx = pixel_to_sample (dx);
611 if (leftmost_frame >= dx) {
612 new_frame = leftmost_frame - dx;
620 if (new_frame != leftmost_frame) {
621 vc.time_origin = new_frame;
622 vc.add (VisualChange::TimeOrigin);
626 if (autoscroll_vertical_allowed) {
628 // const double vertical_pos = vertical_adjustment.get_value();
629 const int speed_factor = 10;
633 if (y < autoscroll_boundary.y0) {
635 /* scroll to make higher tracks visible */
637 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
638 scroll_up_one_track ();
639 vertical_motion = true;
643 } else if (y > autoscroll_boundary.y1) {
645 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
646 scroll_down_one_track ();
647 vertical_motion = true;
654 if (vc.pending || vertical_motion) {
656 /* change horizontal first */
662 /* now send a motion event to notify anyone who cares
663 that we have moved to a new location (because we scrolled)
668 ev.type = GDK_MOTION_NOTIFY;
669 ev.state = Gdk::BUTTON1_MASK;
671 /* the motion handler expects events in canvas coordinate space */
673 /* we asked for the mouse position above (::get_pointer()) via
674 * our own top level window (we being the Editor). Convert into
675 * coordinates within the canvas window.
681 toplevel->translate_coordinates (*_track_canvas, x, y, cx, cy);
683 /* clamp x and y to remain within the autoscroll boundary,
684 * which is defined in window coordinates
687 x = min (max ((ArdourCanvas::Coord) cx, autoscroll_boundary.x0), autoscroll_boundary.x1);
688 y = min (max ((ArdourCanvas::Coord) cy, autoscroll_boundary.y0), autoscroll_boundary.y1);
690 /* now convert from Editor window coordinates to canvas
694 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
699 motion_handler (0, (GdkEvent*) &ev, true);
701 } else if (no_stop) {
703 /* not changing visual state but pointer is outside the scrolling boundary
704 * so we still need to deliver a fake motion event
709 ev.type = GDK_MOTION_NOTIFY;
710 ev.state = Gdk::BUTTON1_MASK;
712 /* the motion handler expects events in canvas coordinate space */
714 /* first convert from Editor window coordinates to canvas
721 /* clamp x and y to remain within the visible area. except
722 * .. if horizontal scrolling is allowed, always allow us to
726 if (autoscroll_horizontal_allowed) {
727 x = min (max ((ArdourCanvas::Coord) x, 0.0), autoscroll_boundary.x1);
729 x = min (max ((ArdourCanvas::Coord) x, autoscroll_boundary.x0), autoscroll_boundary.x1);
731 y = min (max ((ArdourCanvas::Coord) y, autoscroll_boundary.y0), autoscroll_boundary.y1);
733 toplevel->translate_coordinates (*_track_canvas_viewport, x, y, cx, cy);
735 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
740 motion_handler (0, (GdkEvent*) &ev, true);
743 stop_canvas_autoscroll ();
749 return true; /* call me again */
753 Editor::start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary)
759 stop_canvas_autoscroll ();
761 autoscroll_horizontal_allowed = allow_horiz;
762 autoscroll_vertical_allowed = allow_vert;
763 autoscroll_boundary = boundary;
765 /* do the first scroll right now
768 autoscroll_canvas ();
770 /* scroll again at very very roughly 30FPS */
772 autoscroll_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &Editor::autoscroll_canvas), 30);
776 Editor::stop_canvas_autoscroll ()
778 autoscroll_connection.disconnect ();
782 Editor::EnterContext*
783 Editor::get_enter_context(ItemType type)
785 for (ssize_t i = _enter_stack.size() - 1; i >= 0; --i) {
786 if (_enter_stack[i].item_type == type) {
787 return &_enter_stack[i];
794 Editor::left_track_canvas (GdkEventCrossing */*ev*/)
797 within_track_canvas = false;
798 set_entered_track (0);
799 set_entered_regionview (0);
800 reset_canvas_action_sensitivity (false);
805 Editor::entered_track_canvas (GdkEventCrossing */*ev*/)
807 within_track_canvas = true;
808 reset_canvas_action_sensitivity (true);
813 Editor::ensure_time_axis_view_is_visible (TimeAxisView const & track, bool at_top)
815 if (track.hidden()) {
819 /* compute visible area of trackview group, as offsets from top of
823 double const current_view_min_y = vertical_adjustment.get_value();
824 double const current_view_max_y = current_view_min_y + vertical_adjustment.get_page_size();
826 double const track_min_y = track.y_position ();
827 double const track_max_y = track.y_position () + track.effective_height ();
830 (track_min_y >= current_view_min_y &&
831 track_max_y < current_view_max_y)) {
832 /* already visible, and caller did not ask to place it at the
833 * top of the track canvas
841 new_value = track_min_y;
843 if (track_min_y < current_view_min_y) {
844 // Track is above the current view
845 new_value = track_min_y;
846 } else if (track_max_y > current_view_max_y) {
847 // Track is below the current view
848 new_value = track.y_position () + track.effective_height() - vertical_adjustment.get_page_size();
850 new_value = track_min_y;
854 vertical_adjustment.set_value(new_value);
857 /** Called when the main vertical_adjustment has changed */
859 Editor::tie_vertical_scrolling ()
861 if (pending_visual_change.idle_handler_id < 0) {
862 _summary->set_overlays_dirty ();
867 Editor::set_horizontal_position (double p)
869 horizontal_adjustment.set_value (p);
871 leftmost_frame = (framepos_t) floor (p * samples_per_pixel);
873 update_fixed_rulers ();
874 redisplay_tempo (true);
876 if (pending_visual_change.idle_handler_id < 0) {
877 _summary->set_overlays_dirty ();
880 update_video_timeline();
884 Editor::color_handler()
886 ArdourCanvas::Color base = UIConfiguration::instance().color ("ruler base");
887 ArdourCanvas::Color text = UIConfiguration::instance().color ("ruler text");
888 timecode_ruler->set_fill_color (base);
889 timecode_ruler->set_outline_color (text);
890 minsec_ruler->set_fill_color (base);
891 minsec_ruler->set_outline_color (text);
892 samples_ruler->set_fill_color (base);
893 samples_ruler->set_outline_color (text);
894 bbt_ruler->set_fill_color (base);
895 bbt_ruler->set_outline_color (text);
897 playhead_cursor->set_color (UIConfiguration::instance().color ("play head"));
899 meter_bar->set_fill_color (UIConfiguration::instance().color_mod ("meter bar", "marker bar"));
900 meter_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
902 tempo_bar->set_fill_color (UIConfiguration::instance().color_mod ("tempo bar", "marker bar"));
903 tempo_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
905 marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("marker bar", "marker bar"));
906 marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
908 cd_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("cd marker bar", "marker bar"));
909 cd_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
911 range_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("range marker bar", "marker bar"));
912 range_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
914 transport_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("transport marker bar", "marker bar"));
915 transport_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
917 cd_marker_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
918 cd_marker_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
920 range_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
921 range_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
923 transport_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("transport drag rect"));
924 transport_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("transport drag rect"));
926 transport_loop_range_rect->set_fill_color (UIConfiguration::instance().color_mod ("transport loop rect", "loop rectangle"));
927 transport_loop_range_rect->set_outline_color (UIConfiguration::instance().color ("transport loop rect"));
929 transport_punch_range_rect->set_fill_color (UIConfiguration::instance().color ("transport punch rect"));
930 transport_punch_range_rect->set_outline_color (UIConfiguration::instance().color ("transport punch rect"));
932 transport_punchin_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
933 transport_punchout_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
935 rubberband_rect->set_outline_color (UIConfiguration::instance().color ("rubber band rect"));
936 rubberband_rect->set_fill_color (UIConfiguration::instance().color_mod ("rubber band rect", "selection rect"));
938 location_marker_color = UIConfiguration::instance().color ("location marker");
939 location_range_color = UIConfiguration::instance().color ("location range");
940 location_cd_marker_color = UIConfiguration::instance().color ("location cd marker");
941 location_loop_color = UIConfiguration::instance().color ("location loop");
942 location_punch_color = UIConfiguration::instance().color ("location punch");
944 refresh_location_display ();
946 /* redraw the whole thing */
947 _track_canvas->set_background_color (UIConfiguration::instance().color ("arrange base"));
948 _track_canvas->queue_draw ();
951 redisplay_tempo (true);
954 _session->tempo_map().apply_with_metrics (*this, &Editor::draw_metric_marks); // redraw metric markers
959 Editor::horizontal_position () const
961 return sample_to_pixel (leftmost_frame);
965 Editor::track_canvas_key_press (GdkEventKey*)
971 Editor::track_canvas_key_release (GdkEventKey*)
977 Editor::clamp_verbose_cursor_x (double x)
982 x = min (_visible_canvas_width - 200.0, x);
988 Editor::clamp_verbose_cursor_y (double y)
991 y = min (_visible_canvas_height - 50, y);
995 ArdourCanvas::GtkCanvasViewport*
996 Editor::get_track_canvas() const
998 return _track_canvas_viewport;
1002 Editor::get_canvas_cursor () const
1004 /* The top of the cursor stack is always the currently visible cursor. */
1005 return _cursor_stack.back();
1009 Editor::set_canvas_cursor (Gdk::Cursor* cursor)
1011 Glib::RefPtr<Gdk::Window> win = _track_canvas->get_window();
1013 if (win && !_cursors->is_invalid (cursor)) {
1014 /* glibmm 2.4 doesn't allow null cursor pointer because it uses
1015 a Gdk::Cursor& as the argument to Gdk::Window::set_cursor().
1016 But a null pointer just means "use parent window cursor",
1017 and so should be allowed. Gtkmm 3.x has fixed this API.
1019 For now, drop down and use C API
1021 gdk_window_set_cursor (win->gobj(), cursor ? cursor->gobj() : 0);
1026 Editor::push_canvas_cursor (Gdk::Cursor* cursor)
1028 if (!_cursors->is_invalid (cursor)) {
1029 _cursor_stack.push_back (cursor);
1030 set_canvas_cursor (cursor);
1032 return _cursor_stack.size() - 1;
1036 Editor::pop_canvas_cursor ()
1039 if (_cursor_stack.size() <= 1) {
1040 PBD::error << "attempt to pop default cursor" << endmsg;
1044 _cursor_stack.pop_back();
1045 if (_cursor_stack.back()) {
1046 /* Popped to an existing cursor, we're done. Otherwise, the
1047 context that created this cursor has been destroyed, so we need
1048 to skip to the next down the stack. */
1049 set_canvas_cursor (_cursor_stack.back());
1056 Editor::which_grabber_cursor () const
1058 Gdk::Cursor* c = _cursors->grabber;
1060 switch (_edit_point) {
1062 c = _cursors->grabber_edit_point;
1065 boost::shared_ptr<Movable> m = _movable.lock();
1066 if (m && m->locked()) {
1067 c = _cursors->speaker;
1076 Editor::which_trim_cursor (bool left) const
1078 if (!entered_regionview) {
1082 Trimmable::CanTrim ct = entered_regionview->region()->can_trim ();
1086 if (ct & Trimmable::FrontTrimEarlier) {
1087 return _cursors->left_side_trim;
1089 return _cursors->left_side_trim_right_only;
1092 if (ct & Trimmable::EndTrimLater) {
1093 return _cursors->right_side_trim;
1095 return _cursors->right_side_trim_left_only;
1101 Editor::which_mode_cursor () const
1103 Gdk::Cursor* mode_cursor = MouseCursors::invalid_cursor ();
1105 switch (mouse_mode) {
1107 mode_cursor = _cursors->selector;
1111 mode_cursor = _cursors->scissors;
1116 /* don't use mode cursor, pick a grabber cursor based on the item */
1120 mode_cursor = _cursors->midi_pencil;
1124 mode_cursor = _cursors->time_fx; // just use playhead
1128 mode_cursor = _cursors->speaker;
1132 /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
1133 if (get_smart_mode()) {
1136 get_pointer_position (x, y);
1138 if (x >= 0 && y >= 0) {
1140 vector<ArdourCanvas::Item const *> items;
1142 /* Note how we choose a specific scroll group to get
1143 * items from. This could be problematic.
1146 hv_scroll_group->add_items_at_point (ArdourCanvas::Duple (x,y), items);
1148 // first item will be the upper most
1150 if (!items.empty()) {
1151 const ArdourCanvas::Item* i = items.front();
1153 if (i && i->parent() && i->parent()->get_data (X_("timeselection"))) {
1154 pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y);
1155 if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
1156 mode_cursor = _cursors->up_down;
1167 Editor::which_track_cursor () const
1169 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
1171 switch (_join_object_range_state) {
1172 case JOIN_OBJECT_RANGE_NONE:
1173 case JOIN_OBJECT_RANGE_OBJECT:
1174 cursor = which_grabber_cursor ();
1176 case JOIN_OBJECT_RANGE_RANGE:
1177 cursor = _cursors->selector;
1185 Editor::which_canvas_cursor(ItemType type) const
1187 Gdk::Cursor* cursor = which_mode_cursor ();
1189 if (mouse_mode == MouseRange) {
1191 case StartSelectionTrimItem:
1192 cursor = _cursors->left_side_trim;
1194 case EndSelectionTrimItem:
1195 cursor = _cursors->right_side_trim;
1202 if ((mouse_mode == MouseObject || get_smart_mode ()) ||
1203 mouse_mode == MouseContent) {
1205 /* find correct cursor to use in object/smart mode */
1209 /* We don't choose a cursor for these items on top of a region view,
1210 because this would push a new context on the enter stack which
1211 means switching the region context for things like smart mode
1212 won't actualy change the cursor. */
1213 // case RegionViewNameHighlight:
1214 // case RegionViewName:
1217 case AutomationTrackItem:
1218 cursor = which_track_cursor ();
1220 case PlayheadCursorItem:
1221 switch (_edit_point) {
1223 cursor = _cursors->grabber_edit_point;
1226 cursor = _cursors->grabber;
1231 cursor = _cursors->selector;
1233 case ControlPointItem:
1234 cursor = _cursors->fader;
1237 cursor = _cursors->cross_hair;
1239 case AutomationLineItem:
1240 cursor = _cursors->cross_hair;
1242 case StartSelectionTrimItem:
1243 cursor = _cursors->left_side_trim;
1245 case EndSelectionTrimItem:
1246 cursor = _cursors->right_side_trim;
1249 cursor = _cursors->fade_in;
1251 case FadeInHandleItem:
1252 cursor = _cursors->fade_in;
1254 case FadeInTrimHandleItem:
1255 cursor = _cursors->fade_in;
1258 cursor = _cursors->fade_out;
1260 case FadeOutHandleItem:
1261 cursor = _cursors->fade_out;
1263 case FadeOutTrimHandleItem:
1264 cursor = _cursors->fade_out;
1266 case FeatureLineItem:
1267 cursor = _cursors->cross_hair;
1269 case LeftFrameHandle:
1270 if ( effective_mouse_mode() == MouseObject ) // (smart mode): if the user is in the btm half, show the trim cursor
1271 cursor = which_trim_cursor (true);
1273 cursor = _cursors->selector; // (smart mode): in the top half, just show the selection (range) cursor
1275 case RightFrameHandle:
1276 if ( effective_mouse_mode() == MouseObject ) //see above
1277 cursor = which_trim_cursor (false);
1279 cursor = _cursors->selector;
1281 case StartCrossFadeItem:
1282 cursor = _cursors->fade_in;
1284 case EndCrossFadeItem:
1285 cursor = _cursors->fade_out;
1287 case CrossfadeViewItem:
1288 cursor = _cursors->cross_hair;
1291 cursor = _cursors->grabber_note;
1296 } else if (mouse_mode == MouseDraw) {
1298 /* ControlPointItem is not really specific to region gain mode
1299 but it is the same cursor so don't worry about this for now.
1300 The result is that we'll see the fader cursor if we enter
1301 non-region-gain-line control points while in MouseDraw
1302 mode, even though we can't edit them in this mode.
1307 case ControlPointItem:
1308 cursor = _cursors->fader;
1311 cursor = _cursors->grabber_note;
1318 /* These items use the timebar cursor at all times */
1319 case TimecodeRulerItem:
1320 case MinsecRulerItem:
1322 case SamplesRulerItem:
1323 cursor = _cursors->timebar;
1326 /* These items use the grabber cursor at all times */
1327 case MeterMarkerItem:
1328 case TempoMarkerItem:
1333 case RangeMarkerBarItem:
1334 case CdMarkerBarItem:
1336 case TransportMarkerBarItem:
1338 cursor = which_grabber_cursor();
1349 Editor::choose_canvas_cursor_on_entry (ItemType type)
1351 if (_drags->active()) {
1355 Gdk::Cursor* cursor = which_canvas_cursor(type);
1357 if (!_cursors->is_invalid (cursor)) {
1358 // Push a new enter context
1359 const EnterContext ctx = { type, CursorContext::create(*this, cursor) };
1360 _enter_stack.push_back(ctx);
1365 Editor::update_all_enter_cursors ()
1367 for (std::vector<EnterContext>::iterator i = _enter_stack.begin(); i != _enter_stack.end(); ++i) {
1368 i->cursor_ctx->change(which_canvas_cursor(i->item_type));
1373 Editor::trackviews_height() const
1375 if (!_trackview_group) {
1379 return _visible_canvas_height - _trackview_group->canvas_origin().y;