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 ();
585 Editor::autoscroll_canvas ()
588 Gdk::ModifierType mask;
589 frameoffset_t dx = 0;
590 bool no_stop = false;
591 Gtk::Window* toplevel = dynamic_cast<Gtk::Window*>(contents().get_toplevel());
597 toplevel->get_window()->get_pointer (x, y, mask);
600 bool vertical_motion = false;
602 if (autoscroll_horizontal_allowed) {
604 framepos_t new_frame = leftmost_frame;
608 if (x > autoscroll_boundary.x1) {
610 /* bring it back into view */
611 dx = x - autoscroll_boundary.x1;
612 dx += 10 + (2 * (autoscroll_cnt/2));
614 dx = pixel_to_sample (dx);
616 if (leftmost_frame < max_framepos - dx) {
617 new_frame = leftmost_frame + dx;
619 new_frame = max_framepos;
624 } else if (x < autoscroll_boundary.x0) {
626 dx = autoscroll_boundary.x0 - x;
627 dx += 10 + (2 * (autoscroll_cnt/2));
629 dx = pixel_to_sample (dx);
631 if (leftmost_frame >= dx) {
632 new_frame = leftmost_frame - dx;
640 if (new_frame != leftmost_frame) {
641 vc.time_origin = new_frame;
642 vc.add (VisualChange::TimeOrigin);
646 if (autoscroll_vertical_allowed) {
648 // const double vertical_pos = vertical_adjustment.get_value();
649 const int speed_factor = 10;
653 if (y < autoscroll_boundary.y0) {
655 /* scroll to make higher tracks visible */
657 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
658 scroll_up_one_track ();
659 vertical_motion = true;
663 } else if (y > autoscroll_boundary.y1) {
665 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
666 scroll_down_one_track ();
667 vertical_motion = true;
674 if (vc.pending || vertical_motion) {
676 /* change horizontal first */
682 /* now send a motion event to notify anyone who cares
683 that we have moved to a new location (because we scrolled)
688 ev.type = GDK_MOTION_NOTIFY;
689 ev.state = Gdk::BUTTON1_MASK;
691 /* the motion handler expects events in canvas coordinate space */
693 /* we asked for the mouse position above (::get_pointer()) via
694 * our own top level window (we being the Editor). Convert into
695 * coordinates within the canvas window.
701 toplevel->translate_coordinates (*_track_canvas, x, y, cx, cy);
703 /* clamp x and y to remain within the autoscroll boundary,
704 * which is defined in window coordinates
707 x = min (max ((ArdourCanvas::Coord) cx, autoscroll_boundary.x0), autoscroll_boundary.x1);
708 y = min (max ((ArdourCanvas::Coord) cy, autoscroll_boundary.y0), autoscroll_boundary.y1);
710 /* now convert from Editor window coordinates to canvas
714 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
719 motion_handler (0, (GdkEvent*) &ev, true);
721 } else if (no_stop) {
723 /* not changing visual state but pointer is outside the scrolling boundary
724 * so we still need to deliver a fake motion event
729 ev.type = GDK_MOTION_NOTIFY;
730 ev.state = Gdk::BUTTON1_MASK;
732 /* the motion handler expects events in canvas coordinate space */
734 /* first convert from Editor window coordinates to canvas
741 /* clamp x and y to remain within the visible area. except
742 * .. if horizontal scrolling is allowed, always allow us to
746 if (autoscroll_horizontal_allowed) {
747 x = min (max ((ArdourCanvas::Coord) x, 0.0), autoscroll_boundary.x1);
749 x = min (max ((ArdourCanvas::Coord) x, autoscroll_boundary.x0), autoscroll_boundary.x1);
751 y = min (max ((ArdourCanvas::Coord) y, autoscroll_boundary.y0), autoscroll_boundary.y1);
753 toplevel->translate_coordinates (*_track_canvas_viewport, x, y, cx, cy);
755 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
760 motion_handler (0, (GdkEvent*) &ev, true);
763 stop_canvas_autoscroll ();
769 return true; /* call me again */
773 Editor::start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary)
779 stop_canvas_autoscroll ();
781 autoscroll_horizontal_allowed = allow_horiz;
782 autoscroll_vertical_allowed = allow_vert;
783 autoscroll_boundary = boundary;
785 /* do the first scroll right now
788 autoscroll_canvas ();
790 /* scroll again at very very roughly 30FPS */
792 autoscroll_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &Editor::autoscroll_canvas), 30);
796 Editor::stop_canvas_autoscroll ()
798 autoscroll_connection.disconnect ();
802 Editor::EnterContext*
803 Editor::get_enter_context(ItemType type)
805 for (ssize_t i = _enter_stack.size() - 1; i >= 0; --i) {
806 if (_enter_stack[i].item_type == type) {
807 return &_enter_stack[i];
814 Editor::left_track_canvas (GdkEventCrossing* ev)
816 const bool was_within = within_track_canvas;
818 within_track_canvas = false;
819 set_entered_track (0);
820 set_entered_regionview (0);
821 reset_canvas_action_sensitivity (false);
824 if (ev->detail == GDK_NOTIFY_NONLINEAR ||
825 ev->detail == GDK_NOTIFY_NONLINEAR_VIRTUAL) {
826 /* context menu or something similar */
827 sensitize_the_right_region_actions (false);
829 sensitize_the_right_region_actions (true);
837 Editor::entered_track_canvas (GdkEventCrossing* ev)
839 const bool was_within = within_track_canvas;
840 within_track_canvas = true;
841 reset_canvas_action_sensitivity (true);
844 if (ev->detail == GDK_NOTIFY_NONLINEAR ||
845 ev->detail == GDK_NOTIFY_NONLINEAR_VIRTUAL) {
846 /* context menu or something similar */
847 sensitize_the_right_region_actions (false);
849 sensitize_the_right_region_actions (true);
857 Editor::ensure_time_axis_view_is_visible (TimeAxisView const & track, bool at_top)
859 if (track.hidden()) {
863 /* compute visible area of trackview group, as offsets from top of
867 double const current_view_min_y = vertical_adjustment.get_value();
868 double const current_view_max_y = current_view_min_y + vertical_adjustment.get_page_size();
870 double const track_min_y = track.y_position ();
871 double const track_max_y = track.y_position () + track.effective_height ();
874 (track_min_y >= current_view_min_y &&
875 track_max_y < current_view_max_y)) {
876 /* already visible, and caller did not ask to place it at the
877 * top of the track canvas
885 new_value = track_min_y;
887 if (track_min_y < current_view_min_y) {
888 // Track is above the current view
889 new_value = track_min_y;
890 } else if (track_max_y > current_view_max_y) {
891 // Track is below the current view
892 new_value = track.y_position () + track.effective_height() - vertical_adjustment.get_page_size();
894 new_value = track_min_y;
898 vertical_adjustment.set_value(new_value);
901 /** Called when the main vertical_adjustment has changed */
903 Editor::tie_vertical_scrolling ()
905 if (pending_visual_change.idle_handler_id < 0) {
906 _summary->set_overlays_dirty ();
911 Editor::set_horizontal_position (double p)
913 horizontal_adjustment.set_value (p);
915 leftmost_frame = (framepos_t) floor (p * samples_per_pixel);
917 update_fixed_rulers ();
918 redisplay_tempo (true);
920 if (pending_visual_change.idle_handler_id < 0) {
921 _summary->set_overlays_dirty ();
924 update_video_timeline();
928 Editor::color_handler()
930 ArdourCanvas::Color base = UIConfiguration::instance().color ("ruler base");
931 ArdourCanvas::Color text = UIConfiguration::instance().color ("ruler text");
932 timecode_ruler->set_fill_color (base);
933 timecode_ruler->set_outline_color (text);
934 minsec_ruler->set_fill_color (base);
935 minsec_ruler->set_outline_color (text);
936 samples_ruler->set_fill_color (base);
937 samples_ruler->set_outline_color (text);
938 bbt_ruler->set_fill_color (base);
939 bbt_ruler->set_outline_color (text);
941 playhead_cursor->set_color (UIConfiguration::instance().color ("play head"));
943 meter_bar->set_fill_color (UIConfiguration::instance().color_mod ("meter bar", "marker bar"));
944 meter_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
946 tempo_bar->set_fill_color (UIConfiguration::instance().color_mod ("tempo bar", "marker bar"));
947 tempo_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
949 marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("marker bar", "marker bar"));
950 marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
952 cd_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("cd marker bar", "marker bar"));
953 cd_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
955 range_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("range marker bar", "marker bar"));
956 range_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
958 transport_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("transport marker bar", "marker bar"));
959 transport_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
961 cd_marker_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
962 cd_marker_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
964 range_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
965 range_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
967 transport_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("transport drag rect"));
968 transport_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("transport drag rect"));
970 transport_loop_range_rect->set_fill_color (UIConfiguration::instance().color_mod ("transport loop rect", "loop rectangle"));
971 transport_loop_range_rect->set_outline_color (UIConfiguration::instance().color ("transport loop rect"));
973 transport_punch_range_rect->set_fill_color (UIConfiguration::instance().color ("transport punch rect"));
974 transport_punch_range_rect->set_outline_color (UIConfiguration::instance().color ("transport punch rect"));
976 transport_punchin_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
977 transport_punchout_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
979 rubberband_rect->set_outline_color (UIConfiguration::instance().color ("rubber band rect"));
980 rubberband_rect->set_fill_color (UIConfiguration::instance().color_mod ("rubber band rect", "selection rect"));
982 location_marker_color = UIConfiguration::instance().color ("location marker");
983 location_range_color = UIConfiguration::instance().color ("location range");
984 location_cd_marker_color = UIConfiguration::instance().color ("location cd marker");
985 location_loop_color = UIConfiguration::instance().color ("location loop");
986 location_punch_color = UIConfiguration::instance().color ("location punch");
988 refresh_location_display ();
990 NoteBase::set_colors ();
992 /* redraw the whole thing */
993 _track_canvas->set_background_color (UIConfiguration::instance().color ("arrange base"));
994 _track_canvas->queue_draw ();
997 redisplay_tempo (true);
1000 _session->tempo_map().apply_with_metrics (*this, &Editor::draw_metric_marks); // redraw metric markers
1005 Editor::horizontal_position () const
1007 return sample_to_pixel (leftmost_frame);
1011 Editor::track_canvas_key_press (GdkEventKey*)
1017 Editor::track_canvas_key_release (GdkEventKey*)
1023 Editor::clamp_verbose_cursor_x (double x)
1028 x = min (_visible_canvas_width - 200.0, x);
1034 Editor::clamp_verbose_cursor_y (double y)
1037 y = min (_visible_canvas_height - 50, y);
1041 ArdourCanvas::GtkCanvasViewport*
1042 Editor::get_track_canvas() const
1044 return _track_canvas_viewport;
1048 Editor::get_canvas_cursor () const
1050 /* The top of the cursor stack is always the currently visible cursor. */
1051 return _cursor_stack.back();
1055 Editor::set_canvas_cursor (Gdk::Cursor* cursor)
1057 Glib::RefPtr<Gdk::Window> win = _track_canvas->get_window();
1059 if (win && !_cursors->is_invalid (cursor)) {
1060 /* glibmm 2.4 doesn't allow null cursor pointer because it uses
1061 a Gdk::Cursor& as the argument to Gdk::Window::set_cursor().
1062 But a null pointer just means "use parent window cursor",
1063 and so should be allowed. Gtkmm 3.x has fixed this API.
1065 For now, drop down and use C API
1067 gdk_window_set_cursor (win->gobj(), cursor ? cursor->gobj() : 0);
1072 Editor::push_canvas_cursor (Gdk::Cursor* cursor)
1074 if (!_cursors->is_invalid (cursor)) {
1075 _cursor_stack.push_back (cursor);
1076 set_canvas_cursor (cursor);
1078 return _cursor_stack.size() - 1;
1082 Editor::pop_canvas_cursor ()
1085 if (_cursor_stack.size() <= 1) {
1086 PBD::error << "attempt to pop default cursor" << endmsg;
1090 _cursor_stack.pop_back();
1091 if (_cursor_stack.back()) {
1092 /* Popped to an existing cursor, we're done. Otherwise, the
1093 context that created this cursor has been destroyed, so we need
1094 to skip to the next down the stack. */
1095 set_canvas_cursor (_cursor_stack.back());
1102 Editor::which_grabber_cursor () const
1104 Gdk::Cursor* c = _cursors->grabber;
1106 switch (_edit_point) {
1108 c = _cursors->grabber_edit_point;
1111 boost::shared_ptr<Movable> m = _movable.lock();
1112 if (m && m->locked()) {
1113 c = _cursors->speaker;
1122 Editor::which_trim_cursor (bool left) const
1124 if (!entered_regionview) {
1128 Trimmable::CanTrim ct = entered_regionview->region()->can_trim ();
1132 if (ct & Trimmable::FrontTrimEarlier) {
1133 return _cursors->left_side_trim;
1135 return _cursors->left_side_trim_right_only;
1138 if (ct & Trimmable::EndTrimLater) {
1139 return _cursors->right_side_trim;
1141 return _cursors->right_side_trim_left_only;
1147 Editor::which_mode_cursor () const
1149 Gdk::Cursor* mode_cursor = MouseCursors::invalid_cursor ();
1151 switch (mouse_mode) {
1153 mode_cursor = _cursors->selector;
1157 mode_cursor = _cursors->scissors;
1162 /* don't use mode cursor, pick a grabber cursor based on the item */
1166 mode_cursor = _cursors->midi_pencil;
1170 mode_cursor = _cursors->time_fx; // just use playhead
1174 mode_cursor = _cursors->speaker;
1178 /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
1179 if (get_smart_mode()) {
1182 get_pointer_position (x, y);
1184 if (x >= 0 && y >= 0) {
1186 vector<ArdourCanvas::Item const *> items;
1188 /* Note how we choose a specific scroll group to get
1189 * items from. This could be problematic.
1192 hv_scroll_group->add_items_at_point (ArdourCanvas::Duple (x,y), items);
1194 // first item will be the upper most
1196 if (!items.empty()) {
1197 const ArdourCanvas::Item* i = items.front();
1199 if (i && i->parent() && i->parent()->get_data (X_("timeselection"))) {
1200 pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y);
1201 if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
1202 mode_cursor = _cursors->up_down;
1213 Editor::which_track_cursor () const
1215 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
1217 switch (_join_object_range_state) {
1218 case JOIN_OBJECT_RANGE_NONE:
1219 case JOIN_OBJECT_RANGE_OBJECT:
1220 cursor = which_grabber_cursor ();
1222 case JOIN_OBJECT_RANGE_RANGE:
1223 cursor = _cursors->selector;
1231 Editor::which_canvas_cursor(ItemType type) const
1233 Gdk::Cursor* cursor = which_mode_cursor ();
1235 if (mouse_mode == MouseRange) {
1237 case StartSelectionTrimItem:
1238 cursor = _cursors->left_side_trim;
1240 case EndSelectionTrimItem:
1241 cursor = _cursors->right_side_trim;
1248 if ((mouse_mode == MouseObject || get_smart_mode ()) ||
1249 mouse_mode == MouseContent) {
1251 /* find correct cursor to use in object/smart mode */
1255 /* We don't choose a cursor for these items on top of a region view,
1256 because this would push a new context on the enter stack which
1257 means switching the region context for things like smart mode
1258 won't actualy change the cursor. */
1259 // case RegionViewNameHighlight:
1260 // case RegionViewName:
1263 case AutomationTrackItem:
1264 cursor = which_track_cursor ();
1266 case PlayheadCursorItem:
1267 switch (_edit_point) {
1269 cursor = _cursors->grabber_edit_point;
1272 cursor = _cursors->grabber;
1277 cursor = _cursors->selector;
1279 case ControlPointItem:
1280 cursor = _cursors->fader;
1283 cursor = _cursors->cross_hair;
1285 case AutomationLineItem:
1286 cursor = _cursors->cross_hair;
1288 case StartSelectionTrimItem:
1289 cursor = _cursors->left_side_trim;
1291 case EndSelectionTrimItem:
1292 cursor = _cursors->right_side_trim;
1295 cursor = _cursors->fade_in;
1297 case FadeInHandleItem:
1298 cursor = _cursors->fade_in;
1300 case FadeInTrimHandleItem:
1301 cursor = _cursors->fade_in;
1304 cursor = _cursors->fade_out;
1306 case FadeOutHandleItem:
1307 cursor = _cursors->fade_out;
1309 case FadeOutTrimHandleItem:
1310 cursor = _cursors->fade_out;
1312 case FeatureLineItem:
1313 cursor = _cursors->cross_hair;
1315 case LeftFrameHandle:
1316 if ( effective_mouse_mode() == MouseObject ) // (smart mode): if the user is in the btm half, show the trim cursor
1317 cursor = which_trim_cursor (true);
1319 cursor = _cursors->selector; // (smart mode): in the top half, just show the selection (range) cursor
1321 case RightFrameHandle:
1322 if ( effective_mouse_mode() == MouseObject ) //see above
1323 cursor = which_trim_cursor (false);
1325 cursor = _cursors->selector;
1327 case StartCrossFadeItem:
1328 cursor = _cursors->fade_in;
1330 case EndCrossFadeItem:
1331 cursor = _cursors->fade_out;
1333 case CrossfadeViewItem:
1334 cursor = _cursors->cross_hair;
1337 cursor = _cursors->grabber_note;
1342 } else if (mouse_mode == MouseDraw) {
1344 /* ControlPointItem is not really specific to region gain mode
1345 but it is the same cursor so don't worry about this for now.
1346 The result is that we'll see the fader cursor if we enter
1347 non-region-gain-line control points while in MouseDraw
1348 mode, even though we can't edit them in this mode.
1353 case ControlPointItem:
1354 cursor = _cursors->fader;
1357 cursor = _cursors->grabber_note;
1364 /* These items use the timebar cursor at all times */
1365 case TimecodeRulerItem:
1366 case MinsecRulerItem:
1368 case SamplesRulerItem:
1369 cursor = _cursors->timebar;
1372 /* These items use the grabber cursor at all times */
1373 case MeterMarkerItem:
1374 case TempoMarkerItem:
1379 case RangeMarkerBarItem:
1380 case CdMarkerBarItem:
1382 case TransportMarkerBarItem:
1384 cursor = which_grabber_cursor();
1395 Editor::choose_canvas_cursor_on_entry (ItemType type)
1397 if (_drags->active()) {
1401 Gdk::Cursor* cursor = which_canvas_cursor(type);
1403 if (!_cursors->is_invalid (cursor)) {
1404 // Push a new enter context
1405 const EnterContext ctx = { type, CursorContext::create(*this, cursor) };
1406 _enter_stack.push_back(ctx);
1411 Editor::update_all_enter_cursors ()
1413 for (std::vector<EnterContext>::iterator i = _enter_stack.begin(); i != _enter_stack.end(); ++i) {
1414 i->cursor_ctx->change(which_canvas_cursor(i->item_type));
1419 Editor::trackviews_height() const
1421 if (!_trackview_group) {
1425 return _visible_canvas_height - _trackview_group->canvas_origin().y;