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 /* set up drag-n-drop */
246 vector<TargetEntry> target_table;
248 // Drag-N-Drop from the region list can generate this target
249 target_table.push_back (TargetEntry ("regions"));
251 target_table.push_back (TargetEntry ("text/plain"));
252 target_table.push_back (TargetEntry ("text/uri-list"));
253 target_table.push_back (TargetEntry ("application/x-rootwin-drop"));
255 _track_canvas->drag_dest_set (target_table);
256 _track_canvas->signal_drag_data_received().connect (sigc::mem_fun(*this, &Editor::track_canvas_drag_data_received));
258 _track_canvas_viewport->signal_size_allocate().connect (sigc::mem_fun(*this, &Editor::track_canvas_viewport_allocate));
260 initialize_rulers ();
262 UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &Editor::color_handler));
268 Editor::track_canvas_viewport_allocate (Gtk::Allocation alloc)
270 _canvas_viewport_allocation = alloc;
271 track_canvas_viewport_size_allocated ();
275 Editor::track_canvas_viewport_size_allocated ()
277 bool height_changed = _visible_canvas_height != _canvas_viewport_allocation.get_height();
279 _visible_canvas_width = _canvas_viewport_allocation.get_width ();
280 _visible_canvas_height = _canvas_viewport_allocation.get_height ();
282 _canvas_drop_zone->set_y1 (_canvas_drop_zone->y0() + (_visible_canvas_height - 20.0));
286 if (height_changed) {
288 for (LocationMarkerMap::iterator i = location_markers.begin(); i != location_markers.end(); ++i) {
289 i->second->canvas_height_set (_visible_canvas_height);
292 vertical_adjustment.set_page_size (_visible_canvas_height);
293 if ((vertical_adjustment.get_value() + _visible_canvas_height) >= vertical_adjustment.get_upper()) {
295 We're increasing the size of the canvas while the bottom is visible.
296 We scroll down to keep in step with the controls layout.
298 vertical_adjustment.set_value (_full_canvas_height - _visible_canvas_height);
301 set_visible_track_count (_visible_track_count);
304 update_fixed_rulers();
305 redisplay_tempo (false);
306 _summary->set_overlays_dirty ();
310 Editor::reset_controls_layout_width ()
312 GtkRequisition req = { 0, 0 };
315 edit_controls_vbox.size_request (req);
318 if (_group_tabs->is_visible()) {
319 _group_tabs->size_request (req);
323 /* the controls layout has no horizontal scrolling, its visible
324 width is always equal to the total width of its contents.
327 controls_layout.property_width() = w;
328 controls_layout.property_width_request() = w;
332 Editor::reset_controls_layout_height (int32_t h)
334 /* ensure that the rect that represents the "bottom" of the canvas
335 * (the drag-n-drop zone) is, in fact, at the bottom.
338 _canvas_drop_zone->set_position (ArdourCanvas::Duple (0, h));
340 /* track controls layout must span the full height of "h" (all tracks)
341 * plus the bottom rect.
344 h += _canvas_drop_zone->height ();
346 /* set the height of the scrollable area (i.e. the sum of all contained widgets)
347 * for the controls layout. The size request is set elsewhere.
350 controls_layout.property_height() = h;
355 Editor::track_canvas_map_handler (GdkEventAny* /*ev*/)
357 if (!_cursor_stack.empty()) {
358 set_canvas_cursor (get_canvas_cursor());
360 PBD::error << "cursor stack is empty" << endmsg;
365 /** This is called when something is dropped onto the track canvas */
367 Editor::track_canvas_drag_data_received (const RefPtr<Gdk::DragContext>& context,
369 const SelectionData& data,
370 guint info, guint time)
372 if (data.get_target() == "regions") {
373 drop_regions (context, x, y, data, info, time);
375 drop_paths (context, x, y, data, info, time);
380 Editor::idle_drop_paths (vector<string> paths, framepos_t frame, double ypos, bool copy)
382 drop_paths_part_two (paths, frame, ypos, copy);
387 Editor::drop_paths_part_two (const vector<string>& paths, framepos_t frame, double ypos, bool copy)
389 RouteTimeAxisView* tv;
391 /* MIDI files must always be imported, because we consider them
392 * writable. So split paths into two vectors, and follow the import
393 * path on the MIDI part.
396 vector<string> midi_paths;
397 vector<string> audio_paths;
399 for (vector<string>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
400 if (SMFSource::safe_midi_file_extension (*i)) {
401 midi_paths.push_back (*i);
403 audio_paths.push_back (*i);
408 std::pair<TimeAxisView*, int> const tvp = trackview_by_y_position (ypos, false);
409 if (tvp.first == 0) {
411 /* drop onto canvas background: create new tracks */
414 InstrumentSelector is; // instantiation builds instrument-list and sets default.
415 do_import (midi_paths, Editing::ImportDistinctFiles, ImportAsTrack, SrcBest, SMFTrackName, SMFTempoIgnore, frame, is.selected_instrument());
417 if (UIConfiguration::instance().get_only_copy_imported_files() || copy) {
418 do_import (audio_paths, Editing::ImportDistinctFiles, Editing::ImportAsTrack,
419 SrcBest, SMFTrackName, SMFTempoIgnore, frame);
421 do_embed (audio_paths, Editing::ImportDistinctFiles, ImportAsTrack, frame);
424 } else if ((tv = dynamic_cast<RouteTimeAxisView*> (tvp.first)) != 0) {
426 /* check that its a track, not a bus */
429 /* select the track, then embed/import */
432 do_import (midi_paths, Editing::ImportSerializeFiles, ImportToTrack,
433 SrcBest, SMFTrackName, SMFTempoIgnore, frame);
435 if (UIConfiguration::instance().get_only_copy_imported_files() || copy) {
436 do_import (audio_paths, Editing::ImportSerializeFiles, Editing::ImportToTrack,
437 SrcBest, SMFTrackName, SMFTempoIgnore, frame);
439 do_embed (audio_paths, Editing::ImportSerializeFiles, ImportToTrack, frame);
446 Editor::drop_paths (const RefPtr<Gdk::DragContext>& context,
448 const SelectionData& data,
449 guint info, guint time)
451 vector<string> paths;
455 if (convert_drop_to_paths (paths, context, x, y, data, info, time) == 0) {
457 /* D-n-D coordinates are window-relative, so convert to canvas coordinates
460 ev.type = GDK_BUTTON_RELEASE;
464 MusicFrame when (window_event_sample (&ev, 0, &cy), 0);
467 bool copy = ((context->get_actions() & (Gdk::ACTION_COPY | Gdk::ACTION_LINK | Gdk::ACTION_MOVE)) == Gdk::ACTION_COPY);
469 /* We are not allowed to call recursive main event loops from within
470 the main event loop with GTK/Quartz. Since import/embed wants
471 to push up a progress dialog, defer all this till we go idle.
473 Glib::signal_idle().connect (sigc::bind (sigc::mem_fun (*this, &Editor::idle_drop_paths), paths, when.frame, cy, copy));
475 drop_paths_part_two (paths, when.frame, cy, copy);
479 context->drag_finish (true, false, time);
482 /** @param allow_horiz true to allow horizontal autoscroll, otherwise false.
484 * @param allow_vert true to allow vertical autoscroll, otherwise false.
488 Editor::maybe_autoscroll (bool allow_horiz, bool allow_vert, bool from_headers)
490 Gtk::Window* toplevel = dynamic_cast<Gtk::Window*>(contents().get_toplevel());
496 if (!UIConfiguration::instance().get_autoscroll_editor () || autoscroll_active ()) {
500 /* define a rectangular boundary for scrolling. If the mouse moves
501 * outside of this area and/or continue to be outside of this area,
502 * then we will continuously auto-scroll the canvas in the appropriate
505 * the boundary is defined in coordinates relative to the toplevel
506 * window since that is what we're going to call ::get_pointer() on
507 * during autoscrolling to determine if we're still outside the
511 ArdourCanvas::Rect scrolling_boundary;
512 Gtk::Allocation alloc;
515 alloc = controls_layout.get_allocation ();
519 controls_layout.get_parent()->translate_coordinates (*toplevel,
520 alloc.get_x(), alloc.get_y(),
523 scrolling_boundary = ArdourCanvas::Rect (wx, wy, wx + alloc.get_width(), wy + alloc.get_height());
527 alloc = _track_canvas_viewport->get_allocation ();
529 /* reduce height by the height of the timebars, which happens
530 to correspond to the position of the hv_scroll_group.
533 alloc.set_height (alloc.get_height() - hv_scroll_group->position().y);
534 alloc.set_y (alloc.get_y() + hv_scroll_group->position().y);
536 /* now reduce it again so that we start autoscrolling before we
537 * move off the top or bottom of the canvas
540 alloc.set_height (alloc.get_height() - 20);
541 alloc.set_y (alloc.get_y() + 10);
543 /* the effective width of the autoscroll boundary so
544 that we start scrolling before we hit the edge.
546 this helps when the window is slammed up against the
547 right edge of the screen, making it hard to scroll
551 if (alloc.get_width() > 20) {
552 alloc.set_width (alloc.get_width() - 20);
553 alloc.set_x (alloc.get_x() + 10);
558 _track_canvas_viewport->get_parent()->translate_coordinates (*toplevel,
559 alloc.get_x(), alloc.get_y(),
562 scrolling_boundary = ArdourCanvas::Rect (wx, wy, wx + alloc.get_width(), wy + alloc.get_height());
566 Gdk::ModifierType mask;
568 toplevel->get_window()->get_pointer (x, y, mask);
570 if ((allow_horiz && ((x < scrolling_boundary.x0 && leftmost_frame > 0) || x >= scrolling_boundary.x1)) ||
571 (allow_vert && ((y < scrolling_boundary.y0 && vertical_adjustment.get_value() > 0)|| y >= scrolling_boundary.y1))) {
572 start_canvas_autoscroll (allow_horiz, allow_vert, scrolling_boundary);
577 Editor::autoscroll_active () const
579 return autoscroll_connection.connected ();
583 Editor::autoscroll_canvas ()
586 Gdk::ModifierType mask;
587 frameoffset_t dx = 0;
588 bool no_stop = false;
589 Gtk::Window* toplevel = dynamic_cast<Gtk::Window*>(contents().get_toplevel());
595 toplevel->get_window()->get_pointer (x, y, mask);
598 bool vertical_motion = false;
600 if (autoscroll_horizontal_allowed) {
602 framepos_t new_frame = leftmost_frame;
606 if (x > autoscroll_boundary.x1) {
608 /* bring it back into view */
609 dx = x - autoscroll_boundary.x1;
610 dx += 10 + (2 * (autoscroll_cnt/2));
612 dx = pixel_to_sample (dx);
614 if (leftmost_frame < max_framepos - dx) {
615 new_frame = leftmost_frame + dx;
617 new_frame = max_framepos;
622 } else if (x < autoscroll_boundary.x0) {
624 dx = autoscroll_boundary.x0 - x;
625 dx += 10 + (2 * (autoscroll_cnt/2));
627 dx = pixel_to_sample (dx);
629 if (leftmost_frame >= dx) {
630 new_frame = leftmost_frame - dx;
638 if (new_frame != leftmost_frame) {
639 vc.time_origin = new_frame;
640 vc.add (VisualChange::TimeOrigin);
644 if (autoscroll_vertical_allowed) {
646 // const double vertical_pos = vertical_adjustment.get_value();
647 const int speed_factor = 10;
651 if (y < autoscroll_boundary.y0) {
653 /* scroll to make higher tracks visible */
655 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
656 scroll_up_one_track ();
657 vertical_motion = true;
661 } else if (y > autoscroll_boundary.y1) {
663 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
664 scroll_down_one_track ();
665 vertical_motion = true;
672 if (vc.pending || vertical_motion) {
674 /* change horizontal first */
680 /* now send a motion event to notify anyone who cares
681 that we have moved to a new location (because we scrolled)
686 ev.type = GDK_MOTION_NOTIFY;
687 ev.state = Gdk::BUTTON1_MASK;
689 /* the motion handler expects events in canvas coordinate space */
691 /* we asked for the mouse position above (::get_pointer()) via
692 * our own top level window (we being the Editor). Convert into
693 * coordinates within the canvas window.
699 toplevel->translate_coordinates (*_track_canvas, x, y, cx, cy);
701 /* clamp x and y to remain within the autoscroll boundary,
702 * which is defined in window coordinates
705 x = min (max ((ArdourCanvas::Coord) cx, autoscroll_boundary.x0), autoscroll_boundary.x1);
706 y = min (max ((ArdourCanvas::Coord) cy, autoscroll_boundary.y0), autoscroll_boundary.y1);
708 /* now convert from Editor window coordinates to canvas
712 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
717 motion_handler (0, (GdkEvent*) &ev, true);
719 } else if (no_stop) {
721 /* not changing visual state but pointer is outside the scrolling boundary
722 * so we still need to deliver a fake motion event
727 ev.type = GDK_MOTION_NOTIFY;
728 ev.state = Gdk::BUTTON1_MASK;
730 /* the motion handler expects events in canvas coordinate space */
732 /* first convert from Editor window coordinates to canvas
739 /* clamp x and y to remain within the visible area. except
740 * .. if horizontal scrolling is allowed, always allow us to
744 if (autoscroll_horizontal_allowed) {
745 x = min (max ((ArdourCanvas::Coord) x, 0.0), autoscroll_boundary.x1);
747 x = min (max ((ArdourCanvas::Coord) x, autoscroll_boundary.x0), autoscroll_boundary.x1);
749 y = min (max ((ArdourCanvas::Coord) y, autoscroll_boundary.y0), autoscroll_boundary.y1);
751 toplevel->translate_coordinates (*_track_canvas_viewport, x, y, cx, cy);
753 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
758 motion_handler (0, (GdkEvent*) &ev, true);
761 stop_canvas_autoscroll ();
767 return true; /* call me again */
771 Editor::start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary)
777 stop_canvas_autoscroll ();
779 autoscroll_horizontal_allowed = allow_horiz;
780 autoscroll_vertical_allowed = allow_vert;
781 autoscroll_boundary = boundary;
783 /* do the first scroll right now
786 autoscroll_canvas ();
788 /* scroll again at very very roughly 30FPS */
790 autoscroll_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &Editor::autoscroll_canvas), 30);
794 Editor::stop_canvas_autoscroll ()
796 autoscroll_connection.disconnect ();
800 Editor::EnterContext*
801 Editor::get_enter_context(ItemType type)
803 for (ssize_t i = _enter_stack.size() - 1; i >= 0; --i) {
804 if (_enter_stack[i].item_type == type) {
805 return &_enter_stack[i];
812 Editor::left_track_canvas (GdkEventCrossing* ev)
814 const bool was_within = within_track_canvas;
816 within_track_canvas = false;
817 set_entered_track (0);
818 set_entered_regionview (0);
819 reset_canvas_action_sensitivity (false);
822 if (ev->detail == GDK_NOTIFY_NONLINEAR ||
823 ev->detail == GDK_NOTIFY_NONLINEAR_VIRTUAL) {
824 /* context menu or something similar */
825 sensitize_the_right_region_actions (false);
827 sensitize_the_right_region_actions (true);
835 Editor::entered_track_canvas (GdkEventCrossing* ev)
837 const bool was_within = within_track_canvas;
838 within_track_canvas = true;
839 reset_canvas_action_sensitivity (true);
842 if (ev->detail == GDK_NOTIFY_NONLINEAR ||
843 ev->detail == GDK_NOTIFY_NONLINEAR_VIRTUAL) {
844 /* context menu or something similar */
845 sensitize_the_right_region_actions (false);
847 sensitize_the_right_region_actions (true);
855 Editor::ensure_time_axis_view_is_visible (TimeAxisView const & track, bool at_top)
857 if (track.hidden()) {
861 /* compute visible area of trackview group, as offsets from top of
865 double const current_view_min_y = vertical_adjustment.get_value();
866 double const current_view_max_y = current_view_min_y + vertical_adjustment.get_page_size();
868 double const track_min_y = track.y_position ();
869 double const track_max_y = track.y_position () + track.effective_height ();
872 (track_min_y >= current_view_min_y &&
873 track_max_y < current_view_max_y)) {
874 /* already visible, and caller did not ask to place it at the
875 * top of the track canvas
883 new_value = track_min_y;
885 if (track_min_y < current_view_min_y) {
886 // Track is above the current view
887 new_value = track_min_y;
888 } else if (track_max_y > current_view_max_y) {
889 // Track is below the current view
890 new_value = track.y_position () + track.effective_height() - vertical_adjustment.get_page_size();
892 new_value = track_min_y;
896 vertical_adjustment.set_value(new_value);
899 /** Called when the main vertical_adjustment has changed */
901 Editor::tie_vertical_scrolling ()
903 if (pending_visual_change.idle_handler_id < 0) {
904 _summary->set_overlays_dirty ();
909 Editor::set_horizontal_position (double p)
911 horizontal_adjustment.set_value (p);
913 leftmost_frame = (framepos_t) floor (p * samples_per_pixel);
915 update_fixed_rulers ();
916 redisplay_tempo (true);
918 if (pending_visual_change.idle_handler_id < 0) {
919 _summary->set_overlays_dirty ();
922 update_video_timeline();
926 Editor::color_handler()
928 ArdourCanvas::Color base = UIConfiguration::instance().color ("ruler base");
929 ArdourCanvas::Color text = UIConfiguration::instance().color ("ruler text");
930 timecode_ruler->set_fill_color (base);
931 timecode_ruler->set_outline_color (text);
932 minsec_ruler->set_fill_color (base);
933 minsec_ruler->set_outline_color (text);
934 samples_ruler->set_fill_color (base);
935 samples_ruler->set_outline_color (text);
936 bbt_ruler->set_fill_color (base);
937 bbt_ruler->set_outline_color (text);
939 playhead_cursor->set_color (UIConfiguration::instance().color ("play head"));
941 meter_bar->set_fill_color (UIConfiguration::instance().color_mod ("meter bar", "marker bar"));
942 meter_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
944 tempo_bar->set_fill_color (UIConfiguration::instance().color_mod ("tempo bar", "marker bar"));
945 tempo_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
947 marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("marker bar", "marker bar"));
948 marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
950 cd_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("cd marker bar", "marker bar"));
951 cd_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
953 range_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("range marker bar", "marker bar"));
954 range_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
956 transport_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("transport marker bar", "marker bar"));
957 transport_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
959 cd_marker_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
960 cd_marker_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
962 range_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
963 range_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
965 transport_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("transport drag rect"));
966 transport_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("transport drag rect"));
968 transport_loop_range_rect->set_fill_color (UIConfiguration::instance().color_mod ("transport loop rect", "loop rectangle"));
969 transport_loop_range_rect->set_outline_color (UIConfiguration::instance().color ("transport loop rect"));
971 transport_punch_range_rect->set_fill_color (UIConfiguration::instance().color ("transport punch rect"));
972 transport_punch_range_rect->set_outline_color (UIConfiguration::instance().color ("transport punch rect"));
974 transport_punchin_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
975 transport_punchout_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
977 rubberband_rect->set_outline_color (UIConfiguration::instance().color ("rubber band rect"));
978 rubberband_rect->set_fill_color (UIConfiguration::instance().color_mod ("rubber band rect", "selection rect"));
980 location_marker_color = UIConfiguration::instance().color ("location marker");
981 location_range_color = UIConfiguration::instance().color ("location range");
982 location_cd_marker_color = UIConfiguration::instance().color ("location cd marker");
983 location_loop_color = UIConfiguration::instance().color ("location loop");
984 location_punch_color = UIConfiguration::instance().color ("location punch");
986 refresh_location_display ();
988 NoteBase::set_colors ();
990 /* redraw the whole thing */
991 _track_canvas->set_background_color (UIConfiguration::instance().color ("arrange base"));
992 _track_canvas->queue_draw ();
995 redisplay_tempo (true);
998 _session->tempo_map().apply_with_metrics (*this, &Editor::draw_metric_marks); // redraw metric markers
1003 Editor::horizontal_position () const
1005 return sample_to_pixel (leftmost_frame);
1009 Editor::track_canvas_key_press (GdkEventKey*)
1015 Editor::track_canvas_key_release (GdkEventKey*)
1021 Editor::clamp_verbose_cursor_x (double x)
1026 x = min (_visible_canvas_width - 200.0, x);
1032 Editor::clamp_verbose_cursor_y (double y)
1035 y = min (_visible_canvas_height - 50, y);
1039 ArdourCanvas::GtkCanvasViewport*
1040 Editor::get_track_canvas() const
1042 return _track_canvas_viewport;
1046 Editor::get_canvas_cursor () const
1048 /* The top of the cursor stack is always the currently visible cursor. */
1049 return _cursor_stack.back();
1053 Editor::set_canvas_cursor (Gdk::Cursor* cursor)
1055 Glib::RefPtr<Gdk::Window> win = _track_canvas->get_window();
1057 if (win && !_cursors->is_invalid (cursor)) {
1058 /* glibmm 2.4 doesn't allow null cursor pointer because it uses
1059 a Gdk::Cursor& as the argument to Gdk::Window::set_cursor().
1060 But a null pointer just means "use parent window cursor",
1061 and so should be allowed. Gtkmm 3.x has fixed this API.
1063 For now, drop down and use C API
1065 gdk_window_set_cursor (win->gobj(), cursor ? cursor->gobj() : 0);
1070 Editor::push_canvas_cursor (Gdk::Cursor* cursor)
1072 if (!_cursors->is_invalid (cursor)) {
1073 _cursor_stack.push_back (cursor);
1074 set_canvas_cursor (cursor);
1076 return _cursor_stack.size() - 1;
1080 Editor::pop_canvas_cursor ()
1083 if (_cursor_stack.size() <= 1) {
1084 PBD::error << "attempt to pop default cursor" << endmsg;
1088 _cursor_stack.pop_back();
1089 if (_cursor_stack.back()) {
1090 /* Popped to an existing cursor, we're done. Otherwise, the
1091 context that created this cursor has been destroyed, so we need
1092 to skip to the next down the stack. */
1093 set_canvas_cursor (_cursor_stack.back());
1100 Editor::which_grabber_cursor () const
1102 Gdk::Cursor* c = _cursors->grabber;
1104 switch (_edit_point) {
1106 c = _cursors->grabber_edit_point;
1109 boost::shared_ptr<Movable> m = _movable.lock();
1110 if (m && m->locked()) {
1111 c = _cursors->speaker;
1120 Editor::which_trim_cursor (bool left) const
1122 if (!entered_regionview) {
1126 Trimmable::CanTrim ct = entered_regionview->region()->can_trim ();
1130 if (ct & Trimmable::FrontTrimEarlier) {
1131 return _cursors->left_side_trim;
1133 return _cursors->left_side_trim_right_only;
1136 if (ct & Trimmable::EndTrimLater) {
1137 return _cursors->right_side_trim;
1139 return _cursors->right_side_trim_left_only;
1145 Editor::which_mode_cursor () const
1147 Gdk::Cursor* mode_cursor = MouseCursors::invalid_cursor ();
1149 switch (mouse_mode) {
1151 mode_cursor = _cursors->selector;
1155 mode_cursor = _cursors->scissors;
1160 /* don't use mode cursor, pick a grabber cursor based on the item */
1164 mode_cursor = _cursors->midi_pencil;
1168 mode_cursor = _cursors->time_fx; // just use playhead
1172 mode_cursor = _cursors->speaker;
1176 /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
1177 if (get_smart_mode()) {
1180 get_pointer_position (x, y);
1182 if (x >= 0 && y >= 0) {
1184 vector<ArdourCanvas::Item const *> items;
1186 /* Note how we choose a specific scroll group to get
1187 * items from. This could be problematic.
1190 hv_scroll_group->add_items_at_point (ArdourCanvas::Duple (x,y), items);
1192 // first item will be the upper most
1194 if (!items.empty()) {
1195 const ArdourCanvas::Item* i = items.front();
1197 if (i && i->parent() && i->parent()->get_data (X_("timeselection"))) {
1198 pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y);
1199 if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
1200 mode_cursor = _cursors->up_down;
1211 Editor::which_track_cursor () const
1213 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
1215 switch (_join_object_range_state) {
1216 case JOIN_OBJECT_RANGE_NONE:
1217 case JOIN_OBJECT_RANGE_OBJECT:
1218 cursor = which_grabber_cursor ();
1220 case JOIN_OBJECT_RANGE_RANGE:
1221 cursor = _cursors->selector;
1229 Editor::which_canvas_cursor(ItemType type) const
1231 Gdk::Cursor* cursor = which_mode_cursor ();
1233 if (mouse_mode == MouseRange) {
1235 case StartSelectionTrimItem:
1236 cursor = _cursors->left_side_trim;
1238 case EndSelectionTrimItem:
1239 cursor = _cursors->right_side_trim;
1246 if ((mouse_mode == MouseObject || get_smart_mode ()) ||
1247 mouse_mode == MouseContent) {
1249 /* find correct cursor to use in object/smart mode */
1253 /* We don't choose a cursor for these items on top of a region view,
1254 because this would push a new context on the enter stack which
1255 means switching the region context for things like smart mode
1256 won't actualy change the cursor. */
1257 // case RegionViewNameHighlight:
1258 // case RegionViewName:
1261 case AutomationTrackItem:
1262 cursor = which_track_cursor ();
1264 case PlayheadCursorItem:
1265 switch (_edit_point) {
1267 cursor = _cursors->grabber_edit_point;
1270 cursor = _cursors->grabber;
1275 cursor = _cursors->selector;
1277 case ControlPointItem:
1278 cursor = _cursors->fader;
1281 cursor = _cursors->cross_hair;
1283 case AutomationLineItem:
1284 cursor = _cursors->cross_hair;
1286 case StartSelectionTrimItem:
1287 cursor = _cursors->left_side_trim;
1289 case EndSelectionTrimItem:
1290 cursor = _cursors->right_side_trim;
1293 cursor = _cursors->fade_in;
1295 case FadeInHandleItem:
1296 cursor = _cursors->fade_in;
1298 case FadeInTrimHandleItem:
1299 cursor = _cursors->fade_in;
1302 cursor = _cursors->fade_out;
1304 case FadeOutHandleItem:
1305 cursor = _cursors->fade_out;
1307 case FadeOutTrimHandleItem:
1308 cursor = _cursors->fade_out;
1310 case FeatureLineItem:
1311 cursor = _cursors->cross_hair;
1313 case LeftFrameHandle:
1314 if ( effective_mouse_mode() == MouseObject ) // (smart mode): if the user is in the btm half, show the trim cursor
1315 cursor = which_trim_cursor (true);
1317 cursor = _cursors->selector; // (smart mode): in the top half, just show the selection (range) cursor
1319 case RightFrameHandle:
1320 if ( effective_mouse_mode() == MouseObject ) //see above
1321 cursor = which_trim_cursor (false);
1323 cursor = _cursors->selector;
1325 case StartCrossFadeItem:
1326 cursor = _cursors->fade_in;
1328 case EndCrossFadeItem:
1329 cursor = _cursors->fade_out;
1331 case CrossfadeViewItem:
1332 cursor = _cursors->cross_hair;
1335 cursor = _cursors->grabber_note;
1340 } else if (mouse_mode == MouseDraw) {
1342 /* ControlPointItem is not really specific to region gain mode
1343 but it is the same cursor so don't worry about this for now.
1344 The result is that we'll see the fader cursor if we enter
1345 non-region-gain-line control points while in MouseDraw
1346 mode, even though we can't edit them in this mode.
1351 case ControlPointItem:
1352 cursor = _cursors->fader;
1355 cursor = _cursors->grabber_note;
1362 /* These items use the timebar cursor at all times */
1363 case TimecodeRulerItem:
1364 case MinsecRulerItem:
1366 case SamplesRulerItem:
1367 cursor = _cursors->timebar;
1370 /* These items use the grabber cursor at all times */
1371 case MeterMarkerItem:
1372 case TempoMarkerItem:
1377 case RangeMarkerBarItem:
1378 case CdMarkerBarItem:
1380 case TransportMarkerBarItem:
1382 cursor = which_grabber_cursor();
1393 Editor::choose_canvas_cursor_on_entry (ItemType type)
1395 if (_drags->active()) {
1399 Gdk::Cursor* cursor = which_canvas_cursor(type);
1401 if (!_cursors->is_invalid (cursor)) {
1402 // Push a new enter context
1403 const EnterContext ctx = { type, CursorContext::create(*this, cursor) };
1404 _enter_stack.push_back(ctx);
1409 Editor::update_all_enter_cursors ()
1411 for (std::vector<EnterContext>::iterator i = _enter_stack.begin(); i != _enter_stack.end(); ++i) {
1412 i->cursor_ctx->change(which_canvas_cursor(i->item_type));
1417 Editor::trackviews_height() const
1419 if (!_trackview_group) {
1423 return _visible_canvas_height - _trackview_group->canvas_origin().y;