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);
919 Editor::color_handler()
921 Gtkmm2ext::Color base = UIConfiguration::instance().color ("ruler base");
922 Gtkmm2ext::Color text = UIConfiguration::instance().color ("ruler text");
923 timecode_ruler->set_fill_color (base);
924 timecode_ruler->set_outline_color (text);
925 minsec_ruler->set_fill_color (base);
926 minsec_ruler->set_outline_color (text);
927 samples_ruler->set_fill_color (base);
928 samples_ruler->set_outline_color (text);
929 bbt_ruler->set_fill_color (base);
930 bbt_ruler->set_outline_color (text);
932 playhead_cursor->set_color (UIConfiguration::instance().color ("play head"));
934 meter_bar->set_fill_color (UIConfiguration::instance().color_mod ("meter bar", "marker bar"));
935 meter_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
937 tempo_bar->set_fill_color (UIConfiguration::instance().color_mod ("tempo bar", "marker bar"));
938 tempo_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
940 marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("marker bar", "marker bar"));
941 marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
943 cd_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("cd marker bar", "marker bar"));
944 cd_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
946 range_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("range marker bar", "marker bar"));
947 range_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
949 transport_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("transport marker bar", "marker bar"));
950 transport_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
952 cd_marker_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
953 cd_marker_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
955 range_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
956 range_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
958 transport_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("transport drag rect"));
959 transport_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("transport drag rect"));
961 transport_loop_range_rect->set_fill_color (UIConfiguration::instance().color_mod ("transport loop rect", "loop rectangle"));
962 transport_loop_range_rect->set_outline_color (UIConfiguration::instance().color ("transport loop rect"));
964 transport_punch_range_rect->set_fill_color (UIConfiguration::instance().color ("transport punch rect"));
965 transport_punch_range_rect->set_outline_color (UIConfiguration::instance().color ("transport punch rect"));
967 transport_punchin_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
968 transport_punchout_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
970 rubberband_rect->set_outline_color (UIConfiguration::instance().color ("rubber band rect"));
971 rubberband_rect->set_fill_color (UIConfiguration::instance().color_mod ("rubber band rect", "selection rect"));
973 location_marker_color = UIConfiguration::instance().color ("location marker");
974 location_range_color = UIConfiguration::instance().color ("location range");
975 location_cd_marker_color = UIConfiguration::instance().color ("location cd marker");
976 location_loop_color = UIConfiguration::instance().color ("location loop");
977 location_punch_color = UIConfiguration::instance().color ("location punch");
979 refresh_location_display ();
981 NoteBase::set_colors ();
983 /* redraw the whole thing */
984 _track_canvas->set_background_color (UIConfiguration::instance().color ("arrange base"));
985 _track_canvas->queue_draw ();
988 redisplay_tempo (true);
991 _session->tempo_map().apply_with_metrics (*this, &Editor::draw_metric_marks); // redraw metric markers
996 Editor::horizontal_position () const
998 return sample_to_pixel (leftmost_frame);
1002 Editor::track_canvas_key_press (GdkEventKey*)
1008 Editor::track_canvas_key_release (GdkEventKey*)
1014 Editor::clamp_verbose_cursor_x (double x)
1019 x = min (_visible_canvas_width - 200.0, x);
1025 Editor::clamp_verbose_cursor_y (double y)
1028 y = min (_visible_canvas_height - 50, y);
1032 ArdourCanvas::GtkCanvasViewport*
1033 Editor::get_track_canvas() const
1035 return _track_canvas_viewport;
1039 Editor::get_canvas_cursor () const
1041 /* The top of the cursor stack is always the currently visible cursor. */
1042 return _cursor_stack.back();
1046 Editor::set_canvas_cursor (Gdk::Cursor* cursor)
1048 Glib::RefPtr<Gdk::Window> win = _track_canvas->get_window();
1050 if (win && !_cursors->is_invalid (cursor)) {
1051 /* glibmm 2.4 doesn't allow null cursor pointer because it uses
1052 a Gdk::Cursor& as the argument to Gdk::Window::set_cursor().
1053 But a null pointer just means "use parent window cursor",
1054 and so should be allowed. Gtkmm 3.x has fixed this API.
1056 For now, drop down and use C API
1058 gdk_window_set_cursor (win->gobj(), cursor ? cursor->gobj() : 0);
1063 Editor::push_canvas_cursor (Gdk::Cursor* cursor)
1065 if (!_cursors->is_invalid (cursor)) {
1066 _cursor_stack.push_back (cursor);
1067 set_canvas_cursor (cursor);
1069 return _cursor_stack.size() - 1;
1073 Editor::pop_canvas_cursor ()
1076 if (_cursor_stack.size() <= 1) {
1077 PBD::error << "attempt to pop default cursor" << endmsg;
1081 _cursor_stack.pop_back();
1082 if (_cursor_stack.back()) {
1083 /* Popped to an existing cursor, we're done. Otherwise, the
1084 context that created this cursor has been destroyed, so we need
1085 to skip to the next down the stack. */
1086 set_canvas_cursor (_cursor_stack.back());
1093 Editor::which_grabber_cursor () const
1095 Gdk::Cursor* c = _cursors->grabber;
1097 switch (_edit_point) {
1099 c = _cursors->grabber_edit_point;
1102 boost::shared_ptr<Movable> m = _movable.lock();
1103 if (m && m->locked()) {
1104 c = _cursors->speaker;
1113 Editor::which_trim_cursor (bool left) const
1115 if (!entered_regionview) {
1119 Trimmable::CanTrim ct = entered_regionview->region()->can_trim ();
1123 if (ct & Trimmable::FrontTrimEarlier) {
1124 return _cursors->left_side_trim;
1126 return _cursors->left_side_trim_right_only;
1129 if (ct & Trimmable::EndTrimLater) {
1130 return _cursors->right_side_trim;
1132 return _cursors->right_side_trim_left_only;
1138 Editor::which_mode_cursor () const
1140 Gdk::Cursor* mode_cursor = MouseCursors::invalid_cursor ();
1142 switch (mouse_mode) {
1144 mode_cursor = _cursors->selector;
1148 mode_cursor = _cursors->scissors;
1153 /* don't use mode cursor, pick a grabber cursor based on the item */
1157 mode_cursor = _cursors->midi_pencil;
1161 mode_cursor = _cursors->time_fx; // just use playhead
1165 mode_cursor = _cursors->speaker;
1169 /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
1170 if (get_smart_mode()) {
1173 get_pointer_position (x, y);
1175 if (x >= 0 && y >= 0) {
1177 vector<ArdourCanvas::Item const *> items;
1179 /* Note how we choose a specific scroll group to get
1180 * items from. This could be problematic.
1183 hv_scroll_group->add_items_at_point (ArdourCanvas::Duple (x,y), items);
1185 // first item will be the upper most
1187 if (!items.empty()) {
1188 const ArdourCanvas::Item* i = items.front();
1190 if (i && i->parent() && i->parent()->get_data (X_("timeselection"))) {
1191 pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y);
1192 if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
1193 mode_cursor = _cursors->up_down;
1204 Editor::which_track_cursor () const
1206 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
1208 switch (_join_object_range_state) {
1209 case JOIN_OBJECT_RANGE_NONE:
1210 case JOIN_OBJECT_RANGE_OBJECT:
1211 cursor = which_grabber_cursor ();
1213 case JOIN_OBJECT_RANGE_RANGE:
1214 cursor = _cursors->selector;
1222 Editor::which_canvas_cursor(ItemType type) const
1224 Gdk::Cursor* cursor = which_mode_cursor ();
1226 if (mouse_mode == MouseRange) {
1228 case StartSelectionTrimItem:
1229 cursor = _cursors->left_side_trim;
1231 case EndSelectionTrimItem:
1232 cursor = _cursors->right_side_trim;
1239 if ((mouse_mode == MouseObject || get_smart_mode ()) ||
1240 mouse_mode == MouseContent) {
1242 /* find correct cursor to use in object/smart mode */
1246 /* We don't choose a cursor for these items on top of a region view,
1247 because this would push a new context on the enter stack which
1248 means switching the region context for things like smart mode
1249 won't actualy change the cursor. */
1250 // case RegionViewNameHighlight:
1251 // case RegionViewName:
1254 case AutomationTrackItem:
1255 cursor = which_track_cursor ();
1257 case PlayheadCursorItem:
1258 switch (_edit_point) {
1260 cursor = _cursors->grabber_edit_point;
1263 cursor = _cursors->grabber;
1268 cursor = _cursors->selector;
1270 case ControlPointItem:
1271 cursor = _cursors->fader;
1274 cursor = _cursors->cross_hair;
1276 case AutomationLineItem:
1277 cursor = _cursors->cross_hair;
1279 case StartSelectionTrimItem:
1280 cursor = _cursors->left_side_trim;
1282 case EndSelectionTrimItem:
1283 cursor = _cursors->right_side_trim;
1286 cursor = _cursors->fade_in;
1288 case FadeInHandleItem:
1289 cursor = _cursors->fade_in;
1291 case FadeInTrimHandleItem:
1292 cursor = _cursors->fade_in;
1295 cursor = _cursors->fade_out;
1297 case FadeOutHandleItem:
1298 cursor = _cursors->fade_out;
1300 case FadeOutTrimHandleItem:
1301 cursor = _cursors->fade_out;
1303 case FeatureLineItem:
1304 cursor = _cursors->cross_hair;
1306 case LeftFrameHandle:
1307 if ( effective_mouse_mode() == MouseObject ) // (smart mode): if the user is in the btm half, show the trim cursor
1308 cursor = which_trim_cursor (true);
1310 cursor = _cursors->selector; // (smart mode): in the top half, just show the selection (range) cursor
1312 case RightFrameHandle:
1313 if ( effective_mouse_mode() == MouseObject ) //see above
1314 cursor = which_trim_cursor (false);
1316 cursor = _cursors->selector;
1318 case StartCrossFadeItem:
1319 cursor = _cursors->fade_in;
1321 case EndCrossFadeItem:
1322 cursor = _cursors->fade_out;
1324 case CrossfadeViewItem:
1325 cursor = _cursors->cross_hair;
1328 cursor = _cursors->grabber_note;
1333 } else if (mouse_mode == MouseDraw) {
1335 /* ControlPointItem is not really specific to region gain mode
1336 but it is the same cursor so don't worry about this for now.
1337 The result is that we'll see the fader cursor if we enter
1338 non-region-gain-line control points while in MouseDraw
1339 mode, even though we can't edit them in this mode.
1344 case ControlPointItem:
1345 cursor = _cursors->fader;
1348 cursor = _cursors->grabber_note;
1355 /* These items use the timebar cursor at all times */
1356 case TimecodeRulerItem:
1357 case MinsecRulerItem:
1359 case SamplesRulerItem:
1360 cursor = _cursors->timebar;
1363 /* These items use the grabber cursor at all times */
1364 case MeterMarkerItem:
1365 case TempoMarkerItem:
1370 case RangeMarkerBarItem:
1371 case CdMarkerBarItem:
1373 case TransportMarkerBarItem:
1375 cursor = which_grabber_cursor();
1386 Editor::choose_canvas_cursor_on_entry (ItemType type)
1388 if (_drags->active()) {
1392 Gdk::Cursor* cursor = which_canvas_cursor(type);
1394 if (!_cursors->is_invalid (cursor)) {
1395 // Push a new enter context
1396 const EnterContext ctx = { type, CursorContext::create(*this, cursor) };
1397 _enter_stack.push_back(ctx);
1402 Editor::update_all_enter_cursors ()
1404 for (std::vector<EnterContext>::iterator i = _enter_stack.begin(); i != _enter_stack.end(); ++i) {
1405 i->cursor_ctx->change(which_canvas_cursor(i->item_type));
1410 Editor::trackviews_height() const
1412 if (!_trackview_group) {
1416 return _visible_canvas_height - _trackview_group->canvas_origin().y;