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 snapped_cursor = new EditorCursor (*this);
222 _canvas_drop_zone = new ArdourCanvas::Rectangle (hv_scroll_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, 0.0));
223 /* this thing is transparent */
224 _canvas_drop_zone->set_fill (false);
225 _canvas_drop_zone->set_outline (false);
226 _canvas_drop_zone->Event.connect (sigc::mem_fun (*this, &Editor::canvas_drop_zone_event));
228 /* these signals will initially be delivered to the canvas itself, but if they end up remaining unhandled, they are passed to Editor-level
232 _track_canvas->signal_scroll_event().connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_scroll_event), true));
233 _track_canvas->signal_motion_notify_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_motion_notify_event));
234 _track_canvas->signal_button_press_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_button_press_event));
235 _track_canvas->signal_button_release_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_button_release_event));
236 _track_canvas->signal_drag_motion().connect (sigc::mem_fun (*this, &Editor::track_canvas_drag_motion));
237 _track_canvas->signal_key_press_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_key_press));
238 _track_canvas->signal_key_release_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_key_release));
240 _track_canvas->set_name ("EditorMainCanvas");
241 _track_canvas->add_events (Gdk::POINTER_MOTION_HINT_MASK | Gdk::SCROLL_MASK | Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
242 _track_canvas->signal_leave_notify_event().connect (sigc::mem_fun(*this, &Editor::left_track_canvas), false);
243 _track_canvas->signal_enter_notify_event().connect (sigc::mem_fun(*this, &Editor::entered_track_canvas), false);
244 _track_canvas->set_flags (CAN_FOCUS);
246 _track_canvas->PreRender.connect (sigc::mem_fun(*this, &Editor::pre_render));
248 /* set up drag-n-drop */
250 vector<TargetEntry> target_table;
253 // Drag-N-Drop from the region list can generate this target
254 target_table.push_back (TargetEntry ("regions"));
257 target_table.push_back (TargetEntry ("regions")); // DnD from the region list will generate this target
258 target_table.push_back (TargetEntry ("sources")); // DnD from the source list will generate this target
259 target_table.push_back (TargetEntry ("text/plain"));
260 >>>>>>> Source list: Fix drag-n-drop.
261 target_table.push_back (TargetEntry ("text/uri-list"));
262 target_table.push_back (TargetEntry ("text/plain"));
263 target_table.push_back (TargetEntry ("application/x-rootwin-drop"));
265 _track_canvas->drag_dest_set (target_table);
266 _track_canvas->signal_drag_data_received().connect (sigc::mem_fun(*this, &Editor::track_canvas_drag_data_received));
268 _track_canvas_viewport->signal_size_allocate().connect (sigc::mem_fun(*this, &Editor::track_canvas_viewport_allocate));
270 initialize_rulers ();
272 UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &Editor::color_handler));
278 Editor::track_canvas_viewport_allocate (Gtk::Allocation alloc)
280 _canvas_viewport_allocation = alloc;
281 track_canvas_viewport_size_allocated ();
285 Editor::track_canvas_viewport_size_allocated ()
287 bool height_changed = _visible_canvas_height != _canvas_viewport_allocation.get_height();
289 _visible_canvas_width = _canvas_viewport_allocation.get_width ();
290 _visible_canvas_height = _canvas_viewport_allocation.get_height ();
292 _canvas_drop_zone->set_y1 (_canvas_drop_zone->y0() + (_visible_canvas_height - 20.0));
296 if (height_changed) {
298 for (LocationMarkerMap::iterator i = location_markers.begin(); i != location_markers.end(); ++i) {
299 i->second->canvas_height_set (_visible_canvas_height);
302 vertical_adjustment.set_page_size (_visible_canvas_height);
303 if ((vertical_adjustment.get_value() + _visible_canvas_height) >= vertical_adjustment.get_upper()) {
305 We're increasing the size of the canvas while the bottom is visible.
306 We scroll down to keep in step with the controls layout.
308 vertical_adjustment.set_value (_full_canvas_height - _visible_canvas_height);
311 set_visible_track_count (_visible_track_count);
314 update_fixed_rulers();
315 redisplay_grid (false);
316 _summary->set_overlays_dirty ();
320 Editor::reset_controls_layout_width ()
322 GtkRequisition req = { 0, 0 };
325 edit_controls_vbox.size_request (req);
328 if (_group_tabs->is_visible()) {
329 _group_tabs->size_request (req);
333 /* the controls layout has no horizontal scrolling, its visible
334 width is always equal to the total width of its contents.
337 controls_layout.property_width() = w;
338 controls_layout.property_width_request() = w;
342 Editor::reset_controls_layout_height (int32_t h)
344 /* ensure that the rect that represents the "bottom" of the canvas
345 * (the drag-n-drop zone) is, in fact, at the bottom.
348 _canvas_drop_zone->set_position (ArdourCanvas::Duple (0, h));
350 /* track controls layout must span the full height of "h" (all tracks)
351 * plus the bottom rect.
354 h += _canvas_drop_zone->height ();
356 /* set the height of the scrollable area (i.e. the sum of all contained widgets)
357 * for the controls layout. The size request is set elsewhere.
360 controls_layout.property_height() = h;
365 Editor::track_canvas_map_handler (GdkEventAny* /*ev*/)
367 if (!_cursor_stack.empty()) {
368 set_canvas_cursor (get_canvas_cursor());
370 PBD::error << "cursor stack is empty" << endmsg;
375 /** This is called when something is dropped onto the track canvas */
377 Editor::track_canvas_drag_data_received (const RefPtr<Gdk::DragContext>& context,
379 const SelectionData& data,
380 guint info, guint time)
382 if (!ARDOUR_UI_UTILS::engine_is_running ()) {
385 if (data.get_target() == X_("regions")) {
386 drop_regions (context, x, y, data, info, time, true);
387 } else if (data.get_target() == X_("sources")) {
388 drop_regions (context, x, y, data, info, time, false);
390 drop_paths (context, x, y, data, info, time);
395 Editor::idle_drop_paths (vector<string> paths, samplepos_t sample, double ypos, bool copy)
397 drop_paths_part_two (paths, sample, ypos, copy);
402 Editor::drop_paths_part_two (const vector<string>& paths, samplepos_t sample, double ypos, bool copy)
404 RouteTimeAxisView* tv;
406 /* MIDI files must always be imported, because we consider them
407 * writable. So split paths into two vectors, and follow the import
408 * path on the MIDI part.
411 vector<string> midi_paths;
412 vector<string> audio_paths;
414 for (vector<string>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
415 if (SMFSource::safe_midi_file_extension (*i)) {
416 midi_paths.push_back (*i);
418 audio_paths.push_back (*i);
423 std::pair<TimeAxisView*, int> const tvp = trackview_by_y_position (ypos, false);
424 if (tvp.first == 0) {
426 /* drop onto canvas background: create new tracks */
429 InstrumentSelector is; // instantiation builds instrument-list and sets default.
430 do_import (midi_paths, Editing::ImportDistinctFiles, ImportAsTrack, SrcBest, SMFTrackName, SMFTempoIgnore, sample, is.selected_instrument());
432 if (UIConfiguration::instance().get_only_copy_imported_files() || copy) {
433 do_import (audio_paths, Editing::ImportDistinctFiles, Editing::ImportAsTrack,
434 SrcBest, SMFTrackName, SMFTempoIgnore, sample);
436 do_embed (audio_paths, Editing::ImportDistinctFiles, ImportAsTrack, sample);
439 } else if ((tv = dynamic_cast<RouteTimeAxisView*> (tvp.first)) != 0) {
441 /* check that its a track, not a bus */
444 /* select the track, then embed/import */
447 do_import (midi_paths, Editing::ImportSerializeFiles, ImportToTrack,
448 SrcBest, SMFTrackName, SMFTempoIgnore, sample);
450 if (UIConfiguration::instance().get_only_copy_imported_files() || copy) {
451 do_import (audio_paths, Editing::ImportSerializeFiles, Editing::ImportToTrack,
452 SrcBest, SMFTrackName, SMFTempoIgnore, sample);
454 do_embed (audio_paths, Editing::ImportSerializeFiles, ImportToTrack, sample);
461 Editor::drop_paths (const RefPtr<Gdk::DragContext>& context,
463 const SelectionData& data,
464 guint info, guint time)
466 vector<string> paths;
470 if (convert_drop_to_paths (paths, context, x, y, data, info, time) == 0) {
472 /* D-n-D coordinates are window-relative, so convert to canvas coordinates
475 ev.type = GDK_BUTTON_RELEASE;
479 MusicSample when (window_event_sample (&ev, 0, &cy), 0);
482 bool copy = ((context->get_actions() & (Gdk::ACTION_COPY | Gdk::ACTION_LINK | Gdk::ACTION_MOVE)) == Gdk::ACTION_COPY);
484 /* We are not allowed to call recursive main event loops from within
485 the main event loop with GTK/Quartz. Since import/embed wants
486 to push up a progress dialog, defer all this till we go idle.
488 Glib::signal_idle().connect (sigc::bind (sigc::mem_fun (*this, &Editor::idle_drop_paths), paths, when.sample, cy, copy));
490 drop_paths_part_two (paths, when.sample, cy, copy);
494 context->drag_finish (true, false, time);
497 /** @param allow_horiz true to allow horizontal autoscroll, otherwise false.
499 * @param allow_vert true to allow vertical autoscroll, otherwise false.
503 Editor::maybe_autoscroll (bool allow_horiz, bool allow_vert, bool from_headers)
505 Gtk::Window* toplevel = dynamic_cast<Gtk::Window*>(contents().get_toplevel());
511 if (!UIConfiguration::instance().get_autoscroll_editor () || autoscroll_active ()) {
515 /* define a rectangular boundary for scrolling. If the mouse moves
516 * outside of this area and/or continue to be outside of this area,
517 * then we will continuously auto-scroll the canvas in the appropriate
520 * the boundary is defined in coordinates relative to the toplevel
521 * window since that is what we're going to call ::get_pointer() on
522 * during autoscrolling to determine if we're still outside the
526 ArdourCanvas::Rect scrolling_boundary;
527 Gtk::Allocation alloc;
530 alloc = controls_layout.get_allocation ();
534 controls_layout.get_parent()->translate_coordinates (*toplevel,
535 alloc.get_x(), alloc.get_y(),
538 scrolling_boundary = ArdourCanvas::Rect (wx, wy, wx + alloc.get_width(), wy + alloc.get_height());
542 alloc = _track_canvas_viewport->get_allocation ();
544 /* reduce height by the height of the timebars, which happens
545 to correspond to the position of the hv_scroll_group.
548 alloc.set_height (alloc.get_height() - hv_scroll_group->position().y);
549 alloc.set_y (alloc.get_y() + hv_scroll_group->position().y);
551 /* now reduce it again so that we start autoscrolling before we
552 * move off the top or bottom of the canvas
555 alloc.set_height (alloc.get_height() - 20);
556 alloc.set_y (alloc.get_y() + 10);
558 /* the effective width of the autoscroll boundary so
559 that we start scrolling before we hit the edge.
561 this helps when the window is slammed up against the
562 right edge of the screen, making it hard to scroll
566 if (alloc.get_width() > 20) {
567 alloc.set_width (alloc.get_width() - 20);
568 alloc.set_x (alloc.get_x() + 10);
573 _track_canvas_viewport->get_parent()->translate_coordinates (*toplevel,
574 alloc.get_x(), alloc.get_y(),
577 scrolling_boundary = ArdourCanvas::Rect (wx, wy, wx + alloc.get_width(), wy + alloc.get_height());
581 Gdk::ModifierType mask;
583 toplevel->get_window()->get_pointer (x, y, mask);
585 if ((allow_horiz && ((x < scrolling_boundary.x0 && _leftmost_sample > 0) || x >= scrolling_boundary.x1)) ||
586 (allow_vert && ((y < scrolling_boundary.y0 && vertical_adjustment.get_value() > 0)|| y >= scrolling_boundary.y1))) {
587 start_canvas_autoscroll (allow_horiz, allow_vert, scrolling_boundary);
592 Editor::autoscroll_active () const
594 return autoscroll_connection.connected ();
597 std::pair <samplepos_t,samplepos_t>
598 Editor::session_gui_extents (bool use_extra) const
601 return std::pair <samplepos_t,samplepos_t>(max_samplepos,0);
604 samplecnt_t session_extent_start = _session->current_start_sample();
605 samplecnt_t session_extent_end = _session->current_end_sample();
607 /* calculate the extents of all regions in every playlist
608 * NOTE: we should listen to playlists, and cache these values so we don't calculate them every time.
611 boost::shared_ptr<RouteList> rl = _session->get_routes();
612 for (RouteList::iterator r = rl->begin(); r != rl->end(); ++r) {
613 boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*r);
615 boost::shared_ptr<Playlist> pl = tr->playlist();
616 if (pl && !pl->all_regions_empty()) {
617 pair<samplepos_t, samplepos_t> e;
618 e = pl->get_extent();
619 if (e.first < session_extent_start) {
620 session_extent_start = e.first;
622 if (e.second > session_extent_end) {
623 session_extent_end = e.second;
630 /* ToDo: also incorporate automation regions (in case the session has no audio/midi but is just used for automating plugins or the like) */
632 /* add additional time to the ui extents (user-defined in config) */
634 samplecnt_t const extra = UIConfiguration::instance().get_extra_ui_extents_time() * 60 * _session->nominal_sample_rate();
635 session_extent_end += extra;
636 session_extent_start -= extra;
640 if (session_extent_end > max_samplepos) {
641 session_extent_end = max_samplepos;
643 if (session_extent_start < 0) {
644 session_extent_start = 0;
647 std::pair <samplepos_t,samplepos_t> ret (session_extent_start, session_extent_end);
652 Editor::autoscroll_canvas ()
655 Gdk::ModifierType mask;
656 sampleoffset_t dx = 0;
657 bool no_stop = false;
658 Gtk::Window* toplevel = dynamic_cast<Gtk::Window*>(contents().get_toplevel());
664 toplevel->get_window()->get_pointer (x, y, mask);
667 bool vertical_motion = false;
669 if (autoscroll_horizontal_allowed) {
671 samplepos_t new_sample = _leftmost_sample;
675 if (x > autoscroll_boundary.x1) {
677 /* bring it back into view */
678 dx = x - autoscroll_boundary.x1;
679 dx += 10 + (2 * (autoscroll_cnt/2));
681 dx = pixel_to_sample (dx);
683 dx *= UIConfiguration::instance().get_draggable_playhead_speed();
685 if (_leftmost_sample < max_samplepos - dx) {
686 new_sample = _leftmost_sample + dx;
688 new_sample = max_samplepos;
693 } else if (x < autoscroll_boundary.x0) {
695 dx = autoscroll_boundary.x0 - x;
696 dx += 10 + (2 * (autoscroll_cnt/2));
698 dx = pixel_to_sample (dx);
700 dx *= UIConfiguration::instance().get_draggable_playhead_speed();
702 if (_leftmost_sample >= dx) {
703 new_sample = _leftmost_sample - dx;
711 if (new_sample != _leftmost_sample) {
712 vc.time_origin = new_sample;
713 vc.add (VisualChange::TimeOrigin);
717 if (autoscroll_vertical_allowed) {
719 // const double vertical_pos = vertical_adjustment.get_value();
720 const int speed_factor = 10;
724 if (y < autoscroll_boundary.y0) {
726 /* scroll to make higher tracks visible */
728 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
729 scroll_up_one_track ();
730 vertical_motion = true;
734 } else if (y > autoscroll_boundary.y1) {
736 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
737 scroll_down_one_track ();
738 vertical_motion = true;
745 if (vc.pending || vertical_motion) {
747 /* change horizontal first */
753 /* now send a motion event to notify anyone who cares
754 that we have moved to a new location (because we scrolled)
759 ev.type = GDK_MOTION_NOTIFY;
760 ev.state = Gdk::BUTTON1_MASK;
762 /* the motion handler expects events in canvas coordinate space */
764 /* we asked for the mouse position above (::get_pointer()) via
765 * our own top level window (we being the Editor). Convert into
766 * coordinates within the canvas window.
772 toplevel->translate_coordinates (*_track_canvas, x, y, cx, cy);
774 /* clamp x and y to remain within the autoscroll boundary,
775 * which is defined in window coordinates
778 x = min (max ((ArdourCanvas::Coord) cx, autoscroll_boundary.x0), autoscroll_boundary.x1);
779 y = min (max ((ArdourCanvas::Coord) cy, autoscroll_boundary.y0), autoscroll_boundary.y1);
781 /* now convert from Editor window coordinates to canvas
785 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
790 motion_handler (0, (GdkEvent*) &ev, true);
792 } else if (no_stop) {
794 /* not changing visual state but pointer is outside the scrolling boundary
795 * so we still need to deliver a fake motion event
800 ev.type = GDK_MOTION_NOTIFY;
801 ev.state = Gdk::BUTTON1_MASK;
803 /* the motion handler expects events in canvas coordinate space */
805 /* first convert from Editor window coordinates to canvas
812 /* clamp x and y to remain within the visible area. except
813 * .. if horizontal scrolling is allowed, always allow us to
817 if (autoscroll_horizontal_allowed) {
818 x = min (max ((ArdourCanvas::Coord) x, 0.0), autoscroll_boundary.x1);
820 x = min (max ((ArdourCanvas::Coord) x, autoscroll_boundary.x0), autoscroll_boundary.x1);
822 y = min (max ((ArdourCanvas::Coord) y, autoscroll_boundary.y0), autoscroll_boundary.y1);
824 toplevel->translate_coordinates (*_track_canvas_viewport, x, y, cx, cy);
826 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
831 motion_handler (0, (GdkEvent*) &ev, true);
834 stop_canvas_autoscroll ();
840 return true; /* call me again */
844 Editor::start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary)
850 stop_canvas_autoscroll ();
852 autoscroll_horizontal_allowed = allow_horiz;
853 autoscroll_vertical_allowed = allow_vert;
854 autoscroll_boundary = boundary;
856 /* do the first scroll right now
859 autoscroll_canvas ();
861 /* scroll again at very very roughly 30FPS */
863 autoscroll_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &Editor::autoscroll_canvas), 30);
867 Editor::stop_canvas_autoscroll ()
869 autoscroll_connection.disconnect ();
873 Editor::EnterContext*
874 Editor::get_enter_context(ItemType type)
876 for (ssize_t i = _enter_stack.size() - 1; i >= 0; --i) {
877 if (_enter_stack[i].item_type == type) {
878 return &_enter_stack[i];
885 Editor::left_track_canvas (GdkEventCrossing* ev)
887 const bool was_within = within_track_canvas;
889 within_track_canvas = false;
890 set_entered_track (0);
891 set_entered_regionview (0);
892 reset_canvas_action_sensitivity (false);
895 if (ev->detail == GDK_NOTIFY_NONLINEAR ||
896 ev->detail == GDK_NOTIFY_NONLINEAR_VIRTUAL) {
897 /* context menu or something similar */
898 sensitize_the_right_region_actions (false);
900 sensitize_the_right_region_actions (true);
908 Editor::entered_track_canvas (GdkEventCrossing* ev)
910 const bool was_within = within_track_canvas;
911 within_track_canvas = true;
912 reset_canvas_action_sensitivity (true);
915 if (ev->detail == GDK_NOTIFY_NONLINEAR ||
916 ev->detail == GDK_NOTIFY_NONLINEAR_VIRTUAL) {
917 /* context menu or something similar */
918 sensitize_the_right_region_actions (false);
920 sensitize_the_right_region_actions (true);
928 Editor::ensure_time_axis_view_is_visible (TimeAxisView const & track, bool at_top)
930 if (track.hidden()) {
934 /* compute visible area of trackview group, as offsets from top of
938 double const current_view_min_y = vertical_adjustment.get_value();
939 double const current_view_max_y = current_view_min_y + vertical_adjustment.get_page_size();
941 double const track_min_y = track.y_position ();
942 double const track_max_y = track.y_position () + track.effective_height ();
945 (track_min_y >= current_view_min_y &&
946 track_max_y < current_view_max_y)) {
947 /* already visible, and caller did not ask to place it at the
948 * top of the track canvas
956 new_value = track_min_y;
958 if (track_min_y < current_view_min_y) {
959 // Track is above the current view
960 new_value = track_min_y;
961 } else if (track_max_y > current_view_max_y) {
962 // Track is below the current view
963 new_value = track.y_position () + track.effective_height() - vertical_adjustment.get_page_size();
965 new_value = track_min_y;
969 vertical_adjustment.set_value(new_value);
972 /** Called when the main vertical_adjustment has changed */
974 Editor::tie_vertical_scrolling ()
976 if (pending_visual_change.idle_handler_id < 0) {
977 _summary->set_overlays_dirty ();
982 Editor::set_horizontal_position (double p)
984 horizontal_adjustment.set_value (p);
986 _leftmost_sample = (samplepos_t) floor (p * samples_per_pixel);
990 Editor::color_handler()
992 Gtkmm2ext::Color base = UIConfiguration::instance().color ("ruler base");
993 Gtkmm2ext::Color text = UIConfiguration::instance().color ("ruler text");
994 timecode_ruler->set_fill_color (base);
995 timecode_ruler->set_outline_color (text);
996 minsec_ruler->set_fill_color (base);
997 minsec_ruler->set_outline_color (text);
998 samples_ruler->set_fill_color (base);
999 samples_ruler->set_outline_color (text);
1000 bbt_ruler->set_fill_color (base);
1001 bbt_ruler->set_outline_color (text);
1003 playhead_cursor->set_color (UIConfiguration::instance().color ("play head"));
1005 meter_bar->set_fill_color (UIConfiguration::instance().color_mod ("meter bar", "marker bar"));
1006 meter_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1008 tempo_bar->set_fill_color (UIConfiguration::instance().color_mod ("tempo bar", "marker bar"));
1009 tempo_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1011 marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("marker bar", "marker bar"));
1012 marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1014 cd_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("cd marker bar", "marker bar"));
1015 cd_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1017 range_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("range marker bar", "marker bar"));
1018 range_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1020 transport_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("transport marker bar", "marker bar"));
1021 transport_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1023 cd_marker_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
1024 cd_marker_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
1026 range_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
1027 range_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
1029 transport_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("transport drag rect"));
1030 transport_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("transport drag rect"));
1032 transport_loop_range_rect->set_fill_color (UIConfiguration::instance().color_mod ("transport loop rect", "loop rectangle"));
1033 transport_loop_range_rect->set_outline_color (UIConfiguration::instance().color ("transport loop rect"));
1035 transport_punch_range_rect->set_fill_color (UIConfiguration::instance().color ("transport punch rect"));
1036 transport_punch_range_rect->set_outline_color (UIConfiguration::instance().color ("transport punch rect"));
1038 transport_punchin_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
1039 transport_punchout_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
1041 rubberband_rect->set_outline_color (UIConfiguration::instance().color ("rubber band rect"));
1042 rubberband_rect->set_fill_color (UIConfiguration::instance().color_mod ("rubber band rect", "selection rect"));
1044 location_marker_color = UIConfiguration::instance().color ("location marker");
1045 location_range_color = UIConfiguration::instance().color ("location range");
1046 location_cd_marker_color = UIConfiguration::instance().color ("location cd marker");
1047 location_loop_color = UIConfiguration::instance().color ("location loop");
1048 location_punch_color = UIConfiguration::instance().color ("location punch");
1050 refresh_location_display ();
1052 NoteBase::set_colors ();
1054 /* redraw the whole thing */
1055 _track_canvas->set_background_color (UIConfiguration::instance().color ("arrange base"));
1056 _track_canvas->queue_draw ();
1059 redisplay_grid (true);
1062 _session->tempo_map().apply_with_metrics (*this, &Editor::draw_metric_marks); // redraw metric markers
1067 Editor::horizontal_position () const
1069 return sample_to_pixel (_leftmost_sample);
1073 Editor::track_canvas_key_press (GdkEventKey*)
1079 Editor::track_canvas_key_release (GdkEventKey*)
1085 Editor::clamp_verbose_cursor_x (double x)
1090 x = min (_visible_canvas_width - 200.0, x);
1096 Editor::clamp_verbose_cursor_y (double y)
1099 y = min (_visible_canvas_height - 50, y);
1103 ArdourCanvas::GtkCanvasViewport*
1104 Editor::get_track_canvas() const
1106 return _track_canvas_viewport;
1110 Editor::get_canvas_cursor () const
1112 /* The top of the cursor stack is always the currently visible cursor. */
1113 return _cursor_stack.back();
1117 Editor::set_canvas_cursor (Gdk::Cursor* cursor)
1119 Glib::RefPtr<Gdk::Window> win = _track_canvas->get_window();
1121 if (win && !_cursors->is_invalid (cursor)) {
1122 /* glibmm 2.4 doesn't allow null cursor pointer because it uses
1123 a Gdk::Cursor& as the argument to Gdk::Window::set_cursor().
1124 But a null pointer just means "use parent window cursor",
1125 and so should be allowed. Gtkmm 3.x has fixed this API.
1127 For now, drop down and use C API
1129 gdk_window_set_cursor (win->gobj(), cursor ? cursor->gobj() : 0);
1134 Editor::push_canvas_cursor (Gdk::Cursor* cursor)
1136 if (!_cursors->is_invalid (cursor)) {
1137 _cursor_stack.push_back (cursor);
1138 set_canvas_cursor (cursor);
1140 return _cursor_stack.size() - 1;
1144 Editor::pop_canvas_cursor ()
1147 if (_cursor_stack.size() <= 1) {
1148 PBD::error << "attempt to pop default cursor" << endmsg;
1152 _cursor_stack.pop_back();
1153 if (_cursor_stack.back()) {
1154 /* Popped to an existing cursor, we're done. Otherwise, the
1155 context that created this cursor has been destroyed, so we need
1156 to skip to the next down the stack. */
1157 set_canvas_cursor (_cursor_stack.back());
1164 Editor::which_trim_cursor (bool left) const
1166 if (!entered_regionview) {
1170 Trimmable::CanTrim ct = entered_regionview->region()->can_trim ();
1174 if (ct & Trimmable::FrontTrimEarlier) {
1175 return _cursors->left_side_trim;
1177 return _cursors->left_side_trim_right_only;
1180 if (ct & Trimmable::EndTrimLater) {
1181 return _cursors->right_side_trim;
1183 return _cursors->right_side_trim_left_only;
1189 Editor::which_mode_cursor () const
1191 Gdk::Cursor* mode_cursor = MouseCursors::invalid_cursor ();
1193 switch (mouse_mode) {
1195 mode_cursor = _cursors->selector;
1199 mode_cursor = _cursors->scissors;
1204 /* don't use mode cursor, pick a grabber cursor based on the item */
1208 mode_cursor = _cursors->midi_pencil;
1212 mode_cursor = _cursors->time_fx; // just use playhead
1216 mode_cursor = _cursors->speaker;
1220 /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
1221 if (get_smart_mode()) {
1224 get_pointer_position (x, y);
1226 if (x >= 0 && y >= 0) {
1228 vector<ArdourCanvas::Item const *> items;
1230 /* Note how we choose a specific scroll group to get
1231 * items from. This could be problematic.
1234 hv_scroll_group->add_items_at_point (ArdourCanvas::Duple (x,y), items);
1236 // first item will be the upper most
1238 if (!items.empty()) {
1239 const ArdourCanvas::Item* i = items.front();
1241 if (i && i->parent() && i->parent()->get_data (X_("timeselection"))) {
1242 pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y);
1243 if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
1244 mode_cursor = _cursors->up_down;
1255 Editor::which_track_cursor () const
1257 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
1259 switch (_join_object_range_state) {
1260 case JOIN_OBJECT_RANGE_NONE:
1261 case JOIN_OBJECT_RANGE_OBJECT:
1262 cursor = _cursors->grabber;
1264 case JOIN_OBJECT_RANGE_RANGE:
1265 cursor = _cursors->selector;
1273 Editor::which_canvas_cursor(ItemType type) const
1275 Gdk::Cursor* cursor = which_mode_cursor ();
1277 if (mouse_mode == MouseRange) {
1279 case StartSelectionTrimItem:
1280 cursor = _cursors->left_side_trim;
1282 case EndSelectionTrimItem:
1283 cursor = _cursors->right_side_trim;
1290 if ((mouse_mode == MouseObject || get_smart_mode ()) ||
1291 mouse_mode == MouseContent) {
1293 /* find correct cursor to use in object/smart mode */
1297 /* We don't choose a cursor for these items on top of a region view,
1298 because this would push a new context on the enter stack which
1299 means switching the region context for things like smart mode
1300 won't actualy change the cursor. */
1301 // case RegionViewNameHighlight:
1302 // case RegionViewName:
1305 case AutomationTrackItem:
1306 cursor = which_track_cursor ();
1308 case PlayheadCursorItem:
1309 cursor = _cursors->grabber;
1312 cursor = _cursors->selector;
1314 case ControlPointItem:
1315 cursor = _cursors->fader;
1318 cursor = _cursors->cross_hair;
1320 case AutomationLineItem:
1321 cursor = _cursors->cross_hair;
1323 case StartSelectionTrimItem:
1324 cursor = _cursors->left_side_trim;
1326 case EndSelectionTrimItem:
1327 cursor = _cursors->right_side_trim;
1330 cursor = _cursors->fade_in;
1332 case FadeInHandleItem:
1333 cursor = _cursors->fade_in;
1335 case FadeInTrimHandleItem:
1336 cursor = _cursors->fade_in;
1339 cursor = _cursors->fade_out;
1341 case FadeOutHandleItem:
1342 cursor = _cursors->fade_out;
1344 case FadeOutTrimHandleItem:
1345 cursor = _cursors->fade_out;
1347 case FeatureLineItem:
1348 cursor = _cursors->cross_hair;
1350 case LeftFrameHandle:
1351 if (effective_mouse_mode() == MouseObject) // (smart mode): if the user is in the btm half, show the trim cursor
1352 cursor = which_trim_cursor (true);
1354 cursor = _cursors->selector; // (smart mode): in the top half, just show the selection (range) cursor
1356 case RightFrameHandle:
1357 if (effective_mouse_mode() == MouseObject) // see above
1358 cursor = which_trim_cursor (false);
1360 cursor = _cursors->selector;
1362 case StartCrossFadeItem:
1363 cursor = _cursors->fade_in;
1365 case EndCrossFadeItem:
1366 cursor = _cursors->fade_out;
1368 case CrossfadeViewItem:
1369 cursor = _cursors->cross_hair;
1372 cursor = _cursors->grabber_note;
1377 } else if (mouse_mode == MouseDraw) {
1379 /* ControlPointItem is not really specific to region gain mode
1380 but it is the same cursor so don't worry about this for now.
1381 The result is that we'll see the fader cursor if we enter
1382 non-region-gain-line control points while in MouseDraw
1383 mode, even though we can't edit them in this mode.
1388 case ControlPointItem:
1389 cursor = _cursors->fader;
1392 cursor = _cursors->grabber_note;
1399 /* These items use the timebar cursor at all times */
1400 case TimecodeRulerItem:
1401 case MinsecRulerItem:
1403 case SamplesRulerItem:
1404 cursor = _cursors->timebar;
1407 /* These items use the grabber cursor at all times */
1408 case MeterMarkerItem:
1409 case TempoMarkerItem:
1414 case RangeMarkerBarItem:
1415 case CdMarkerBarItem:
1417 case TransportMarkerBarItem:
1419 cursor = _cursors->grabber;
1430 Editor::choose_canvas_cursor_on_entry (ItemType type)
1432 if (_drags->active()) {
1436 Gdk::Cursor* cursor = which_canvas_cursor(type);
1438 if (!_cursors->is_invalid (cursor)) {
1439 // Push a new enter context
1440 const EnterContext ctx = { type, CursorContext::create(*this, cursor) };
1441 _enter_stack.push_back(ctx);
1446 Editor::update_all_enter_cursors ()
1448 for (std::vector<EnterContext>::iterator i = _enter_stack.begin(); i != _enter_stack.end(); ++i) {
1449 i->cursor_ctx->change(which_canvas_cursor(i->item_type));
1454 Editor::trackviews_height() const
1456 if (!_trackview_group) {
1460 return _visible_canvas_height - _trackview_group->canvas_origin().y;