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_mapped()) {
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;
642 } else if (y > autoscroll_boundary.y1) {
644 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
645 scroll_down_one_track ();
646 vertical_motion = true;
653 if (vc.pending || vertical_motion) {
655 /* change horizontal first */
661 /* now send a motion event to notify anyone who cares
662 that we have moved to a new location (because we scrolled)
667 ev.type = GDK_MOTION_NOTIFY;
668 ev.state = Gdk::BUTTON1_MASK;
670 /* the motion handler expects events in canvas coordinate space */
672 /* we asked for the mouse position above (::get_pointer()) via
673 * our own top level window (we being the Editor). Convert into
674 * coordinates within the canvas window.
680 toplevel->translate_coordinates (*_track_canvas, x, y, cx, cy);
682 /* clamp x and y to remain within the autoscroll boundary,
683 * which is defined in window coordinates
686 x = min (max ((ArdourCanvas::Coord) cx, autoscroll_boundary.x0), autoscroll_boundary.x1);
687 y = min (max ((ArdourCanvas::Coord) cy, autoscroll_boundary.y0), autoscroll_boundary.y1);
689 /* now convert from Editor window coordinates to canvas
693 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
698 motion_handler (0, (GdkEvent*) &ev, true);
700 } else if (no_stop) {
702 /* not changing visual state but pointer is outside the scrolling boundary
703 * so we still need to deliver a fake motion event
708 ev.type = GDK_MOTION_NOTIFY;
709 ev.state = Gdk::BUTTON1_MASK;
711 /* the motion handler expects events in canvas coordinate space */
713 /* first convert from Editor window coordinates to canvas
720 /* clamp x and y to remain within the visible area. except
721 * .. if horizontal scrolling is allowed, always allow us to
725 if (autoscroll_horizontal_allowed) {
726 x = min (max ((ArdourCanvas::Coord) x, 0.0), autoscroll_boundary.x1);
728 x = min (max ((ArdourCanvas::Coord) x, autoscroll_boundary.x0), autoscroll_boundary.x1);
730 y = min (max ((ArdourCanvas::Coord) y, autoscroll_boundary.y0), autoscroll_boundary.y1);
732 toplevel->translate_coordinates (*_track_canvas_viewport, x, y, cx, cy);
734 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
739 motion_handler (0, (GdkEvent*) &ev, true);
742 stop_canvas_autoscroll ();
748 return true; /* call me again */
752 Editor::start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary)
758 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 ();
781 Editor::EnterContext*
782 Editor::get_enter_context(ItemType type)
784 for (ssize_t i = _enter_stack.size() - 1; i >= 0; --i) {
785 if (_enter_stack[i].item_type == type) {
786 return &_enter_stack[i];
793 Editor::left_track_canvas (GdkEventCrossing */*ev*/)
796 within_track_canvas = false;
797 set_entered_track (0);
798 set_entered_regionview (0);
799 reset_canvas_action_sensitivity (false);
804 Editor::entered_track_canvas (GdkEventCrossing */*ev*/)
806 within_track_canvas = true;
807 reset_canvas_action_sensitivity (true);
812 Editor::ensure_time_axis_view_is_visible (TimeAxisView const & track, bool at_top)
814 if (track.hidden()) {
818 /* compute visible area of trackview group, as offsets from top of
822 double const current_view_min_y = vertical_adjustment.get_value();
823 double const current_view_max_y = current_view_min_y + vertical_adjustment.get_page_size();
825 double const track_min_y = track.y_position ();
826 double const track_max_y = track.y_position () + track.effective_height ();
829 (track_min_y >= current_view_min_y &&
830 track_max_y < current_view_max_y)) {
831 /* already visible, and caller did not ask to place it at the
832 * top of the track canvas
840 new_value = track_min_y;
842 if (track_min_y < current_view_min_y) {
843 // Track is above the current view
844 new_value = track_min_y;
845 } else if (track_max_y > current_view_max_y) {
846 // Track is below the current view
847 new_value = track.y_position () + track.effective_height() - vertical_adjustment.get_page_size();
849 new_value = track_min_y;
853 vertical_adjustment.set_value(new_value);
856 /** Called when the main vertical_adjustment has changed */
858 Editor::tie_vertical_scrolling ()
860 if (pending_visual_change.idle_handler_id < 0) {
861 _summary->set_overlays_dirty ();
866 Editor::set_horizontal_position (double p)
868 horizontal_adjustment.set_value (p);
870 leftmost_frame = (framepos_t) floor (p * samples_per_pixel);
872 update_fixed_rulers ();
873 redisplay_tempo (true);
875 if (pending_visual_change.idle_handler_id < 0) {
876 _summary->set_overlays_dirty ();
879 update_video_timeline();
883 Editor::color_handler()
885 ArdourCanvas::Color base = UIConfiguration::instance().color ("ruler base");
886 ArdourCanvas::Color text = UIConfiguration::instance().color ("ruler text");
887 timecode_ruler->set_fill_color (base);
888 timecode_ruler->set_outline_color (text);
889 minsec_ruler->set_fill_color (base);
890 minsec_ruler->set_outline_color (text);
891 samples_ruler->set_fill_color (base);
892 samples_ruler->set_outline_color (text);
893 bbt_ruler->set_fill_color (base);
894 bbt_ruler->set_outline_color (text);
896 playhead_cursor->set_color (UIConfiguration::instance().color ("play head"));
898 meter_bar->set_fill_color (UIConfiguration::instance().color_mod ("meter bar", "marker bar"));
899 meter_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
901 tempo_bar->set_fill_color (UIConfiguration::instance().color_mod ("tempo bar", "marker bar"));
902 tempo_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
904 marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("marker bar", "marker bar"));
905 marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
907 cd_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("cd marker bar", "marker bar"));
908 cd_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
910 range_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("range marker bar", "marker bar"));
911 range_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
913 transport_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("transport marker bar", "marker bar"));
914 transport_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
916 cd_marker_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
917 cd_marker_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
919 range_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
920 range_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
922 transport_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("transport drag rect"));
923 transport_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("transport drag rect"));
925 transport_loop_range_rect->set_fill_color (UIConfiguration::instance().color_mod ("transport loop rect", "loop rectangle"));
926 transport_loop_range_rect->set_outline_color (UIConfiguration::instance().color ("transport loop rect"));
928 transport_punch_range_rect->set_fill_color (UIConfiguration::instance().color ("transport punch rect"));
929 transport_punch_range_rect->set_outline_color (UIConfiguration::instance().color ("transport punch rect"));
931 transport_punchin_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
932 transport_punchout_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
934 rubberband_rect->set_outline_color (UIConfiguration::instance().color ("rubber band rect"));
935 rubberband_rect->set_fill_color (UIConfiguration::instance().color_mod ("rubber band rect", "selection rect"));
937 location_marker_color = UIConfiguration::instance().color ("location marker");
938 location_range_color = UIConfiguration::instance().color ("location range");
939 location_cd_marker_color = UIConfiguration::instance().color ("location cd marker");
940 location_loop_color = UIConfiguration::instance().color ("location loop");
941 location_punch_color = UIConfiguration::instance().color ("location punch");
943 refresh_location_display ();
945 /* redraw the whole thing */
946 _track_canvas->set_background_color (UIConfiguration::instance().color ("arrange base"));
947 _track_canvas->queue_draw ();
950 redisplay_tempo (true);
953 _session->tempo_map().apply_with_metrics (*this, &Editor::draw_metric_marks); // redraw metric markers
958 Editor::horizontal_position () const
960 return sample_to_pixel (leftmost_frame);
964 Editor::track_canvas_key_press (GdkEventKey*)
970 Editor::track_canvas_key_release (GdkEventKey*)
976 Editor::clamp_verbose_cursor_x (double x)
981 x = min (_visible_canvas_width - 200.0, x);
987 Editor::clamp_verbose_cursor_y (double y)
990 y = min (_visible_canvas_height - 50, y);
994 ArdourCanvas::GtkCanvasViewport*
995 Editor::get_track_canvas() const
997 return _track_canvas_viewport;
1001 Editor::get_canvas_cursor () const
1003 /* The top of the cursor stack is always the currently visible cursor. */
1004 return _cursor_stack.back();
1008 Editor::set_canvas_cursor (Gdk::Cursor* cursor)
1010 Glib::RefPtr<Gdk::Window> win = _track_canvas->get_window();
1012 if (win && !_cursors->is_invalid (cursor)) {
1013 /* glibmm 2.4 doesn't allow null cursor pointer because it uses
1014 a Gdk::Cursor& as the argument to Gdk::Window::set_cursor().
1015 But a null pointer just means "use parent window cursor",
1016 and so should be allowed. Gtkmm 3.x has fixed this API.
1018 For now, drop down and use C API
1020 gdk_window_set_cursor (win->gobj(), cursor ? cursor->gobj() : 0);
1025 Editor::push_canvas_cursor (Gdk::Cursor* cursor)
1027 if (!_cursors->is_invalid (cursor)) {
1028 _cursor_stack.push_back (cursor);
1029 set_canvas_cursor (cursor);
1031 return _cursor_stack.size() - 1;
1035 Editor::pop_canvas_cursor ()
1038 if (_cursor_stack.size() <= 1) {
1039 PBD::error << "attempt to pop default cursor" << endmsg;
1043 _cursor_stack.pop_back();
1044 if (_cursor_stack.back()) {
1045 /* Popped to an existing cursor, we're done. Otherwise, the
1046 context that created this cursor has been destroyed, so we need
1047 to skip to the next down the stack. */
1048 set_canvas_cursor (_cursor_stack.back());
1055 Editor::which_grabber_cursor () const
1057 Gdk::Cursor* c = _cursors->grabber;
1059 switch (_edit_point) {
1061 c = _cursors->grabber_edit_point;
1064 boost::shared_ptr<Movable> m = _movable.lock();
1065 if (m && m->locked()) {
1066 c = _cursors->speaker;
1075 Editor::which_trim_cursor (bool left) const
1077 if (!entered_regionview) {
1081 Trimmable::CanTrim ct = entered_regionview->region()->can_trim ();
1085 if (ct & Trimmable::FrontTrimEarlier) {
1086 return _cursors->left_side_trim;
1088 return _cursors->left_side_trim_right_only;
1091 if (ct & Trimmable::EndTrimLater) {
1092 return _cursors->right_side_trim;
1094 return _cursors->right_side_trim_left_only;
1100 Editor::which_mode_cursor () const
1102 Gdk::Cursor* mode_cursor = MouseCursors::invalid_cursor ();
1104 switch (mouse_mode) {
1106 mode_cursor = _cursors->selector;
1110 mode_cursor = _cursors->scissors;
1115 /* don't use mode cursor, pick a grabber cursor based on the item */
1119 mode_cursor = _cursors->midi_pencil;
1123 mode_cursor = _cursors->time_fx; // just use playhead
1127 mode_cursor = _cursors->speaker;
1131 /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
1132 if (get_smart_mode()) {
1135 get_pointer_position (x, y);
1137 if (x >= 0 && y >= 0) {
1139 vector<ArdourCanvas::Item const *> items;
1141 /* Note how we choose a specific scroll group to get
1142 * items from. This could be problematic.
1145 hv_scroll_group->add_items_at_point (ArdourCanvas::Duple (x,y), items);
1147 // first item will be the upper most
1149 if (!items.empty()) {
1150 const ArdourCanvas::Item* i = items.front();
1152 if (i && i->parent() && i->parent()->get_data (X_("timeselection"))) {
1153 pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y);
1154 if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
1155 mode_cursor = _cursors->up_down;
1166 Editor::which_track_cursor () const
1168 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
1170 switch (_join_object_range_state) {
1171 case JOIN_OBJECT_RANGE_NONE:
1172 case JOIN_OBJECT_RANGE_OBJECT:
1173 cursor = which_grabber_cursor ();
1175 case JOIN_OBJECT_RANGE_RANGE:
1176 cursor = _cursors->selector;
1184 Editor::which_canvas_cursor(ItemType type) const
1186 Gdk::Cursor* cursor = which_mode_cursor ();
1188 if (mouse_mode == MouseRange) {
1190 case StartSelectionTrimItem:
1191 cursor = _cursors->left_side_trim;
1193 case EndSelectionTrimItem:
1194 cursor = _cursors->right_side_trim;
1201 if ((mouse_mode == MouseObject || get_smart_mode ()) ||
1202 mouse_mode == MouseContent) {
1204 /* find correct cursor to use in object/smart mode */
1208 /* We don't choose a cursor for these items on top of a region view,
1209 because this would push a new context on the enter stack which
1210 means switching the region context for things like smart mode
1211 won't actualy change the cursor. */
1212 // case RegionViewNameHighlight:
1213 // case RegionViewName:
1216 case AutomationTrackItem:
1217 cursor = which_track_cursor ();
1219 case PlayheadCursorItem:
1220 switch (_edit_point) {
1222 cursor = _cursors->grabber_edit_point;
1225 cursor = _cursors->grabber;
1230 cursor = _cursors->selector;
1232 case ControlPointItem:
1233 cursor = _cursors->fader;
1236 cursor = _cursors->cross_hair;
1238 case AutomationLineItem:
1239 cursor = _cursors->cross_hair;
1241 case StartSelectionTrimItem:
1242 cursor = _cursors->left_side_trim;
1244 case EndSelectionTrimItem:
1245 cursor = _cursors->right_side_trim;
1248 cursor = _cursors->fade_in;
1250 case FadeInHandleItem:
1251 cursor = _cursors->fade_in;
1253 case FadeInTrimHandleItem:
1254 cursor = _cursors->fade_in;
1257 cursor = _cursors->fade_out;
1259 case FadeOutHandleItem:
1260 cursor = _cursors->fade_out;
1262 case FadeOutTrimHandleItem:
1263 cursor = _cursors->fade_out;
1265 case FeatureLineItem:
1266 cursor = _cursors->cross_hair;
1268 case LeftFrameHandle:
1269 if ( effective_mouse_mode() == MouseObject ) // (smart mode): if the user is in the btm half, show the trim cursor
1270 cursor = which_trim_cursor (true);
1272 cursor = _cursors->selector; // (smart mode): in the top half, just show the selection (range) cursor
1274 case RightFrameHandle:
1275 if ( effective_mouse_mode() == MouseObject ) //see above
1276 cursor = which_trim_cursor (false);
1278 cursor = _cursors->selector;
1280 case StartCrossFadeItem:
1281 cursor = _cursors->fade_in;
1283 case EndCrossFadeItem:
1284 cursor = _cursors->fade_out;
1286 case CrossfadeViewItem:
1287 cursor = _cursors->cross_hair;
1290 cursor = _cursors->grabber_note;
1295 } else if (mouse_mode == MouseDraw) {
1297 /* ControlPointItem is not really specific to region gain mode
1298 but it is the same cursor so don't worry about this for now.
1299 The result is that we'll see the fader cursor if we enter
1300 non-region-gain-line control points while in MouseDraw
1301 mode, even though we can't edit them in this mode.
1306 case ControlPointItem:
1307 cursor = _cursors->fader;
1310 cursor = _cursors->grabber_note;
1317 /* These items use the timebar cursor at all times */
1318 case TimecodeRulerItem:
1319 case MinsecRulerItem:
1321 case SamplesRulerItem:
1322 cursor = _cursors->timebar;
1325 /* These items use the grabber cursor at all times */
1326 case MeterMarkerItem:
1327 case TempoMarkerItem:
1332 case RangeMarkerBarItem:
1333 case CdMarkerBarItem:
1335 case TransportMarkerBarItem:
1337 cursor = which_grabber_cursor();
1348 Editor::choose_canvas_cursor_on_entry (ItemType type)
1350 if (_drags->active()) {
1354 Gdk::Cursor* cursor = which_canvas_cursor(type);
1356 if (!_cursors->is_invalid (cursor)) {
1357 // Push a new enter context
1358 const EnterContext ctx = { type, CursorContext::create(*this, cursor) };
1359 _enter_stack.push_back(ctx);
1364 Editor::update_all_enter_cursors ()
1366 for (std::vector<EnterContext>::iterator i = _enter_stack.begin(); i != _enter_stack.end(); ++i) {
1367 i->cursor_ctx->change(which_canvas_cursor(i->item_type));
1372 Editor::trackviews_height() const
1374 if (!_trackview_group) {
1378 return _visible_canvas_height - _trackview_group->canvas_origin().y;