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;
252 // Drag-N-Drop from the region list can generate this target
253 target_table.push_back (TargetEntry ("regions"));
255 target_table.push_back (TargetEntry ("text/plain"));
256 target_table.push_back (TargetEntry ("text/uri-list"));
257 target_table.push_back (TargetEntry ("application/x-rootwin-drop"));
259 _track_canvas->drag_dest_set (target_table);
260 _track_canvas->signal_drag_data_received().connect (sigc::mem_fun(*this, &Editor::track_canvas_drag_data_received));
262 _track_canvas_viewport->signal_size_allocate().connect (sigc::mem_fun(*this, &Editor::track_canvas_viewport_allocate));
264 initialize_rulers ();
266 UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &Editor::color_handler));
272 Editor::track_canvas_viewport_allocate (Gtk::Allocation alloc)
274 _canvas_viewport_allocation = alloc;
275 track_canvas_viewport_size_allocated ();
279 Editor::track_canvas_viewport_size_allocated ()
281 bool height_changed = _visible_canvas_height != _canvas_viewport_allocation.get_height();
283 _visible_canvas_width = _canvas_viewport_allocation.get_width ();
284 _visible_canvas_height = _canvas_viewport_allocation.get_height ();
286 _canvas_drop_zone->set_y1 (_canvas_drop_zone->y0() + (_visible_canvas_height - 20.0));
290 if (height_changed) {
292 for (LocationMarkerMap::iterator i = location_markers.begin(); i != location_markers.end(); ++i) {
293 i->second->canvas_height_set (_visible_canvas_height);
296 vertical_adjustment.set_page_size (_visible_canvas_height);
297 if ((vertical_adjustment.get_value() + _visible_canvas_height) >= vertical_adjustment.get_upper()) {
299 We're increasing the size of the canvas while the bottom is visible.
300 We scroll down to keep in step with the controls layout.
302 vertical_adjustment.set_value (_full_canvas_height - _visible_canvas_height);
305 set_visible_track_count (_visible_track_count);
308 update_fixed_rulers();
309 redisplay_grid (false);
310 _summary->set_overlays_dirty ();
314 Editor::reset_controls_layout_width ()
316 GtkRequisition req = { 0, 0 };
319 edit_controls_vbox.size_request (req);
322 if (_group_tabs->is_visible()) {
323 _group_tabs->size_request (req);
327 /* the controls layout has no horizontal scrolling, its visible
328 width is always equal to the total width of its contents.
331 controls_layout.property_width() = w;
332 controls_layout.property_width_request() = w;
336 Editor::reset_controls_layout_height (int32_t h)
338 /* ensure that the rect that represents the "bottom" of the canvas
339 * (the drag-n-drop zone) is, in fact, at the bottom.
342 _canvas_drop_zone->set_position (ArdourCanvas::Duple (0, h));
344 /* track controls layout must span the full height of "h" (all tracks)
345 * plus the bottom rect.
348 h += _canvas_drop_zone->height ();
350 /* set the height of the scrollable area (i.e. the sum of all contained widgets)
351 * for the controls layout. The size request is set elsewhere.
354 controls_layout.property_height() = h;
359 Editor::track_canvas_map_handler (GdkEventAny* /*ev*/)
361 if (!_cursor_stack.empty()) {
362 set_canvas_cursor (get_canvas_cursor());
364 PBD::error << "cursor stack is empty" << endmsg;
369 /** This is called when something is dropped onto the track canvas */
371 Editor::track_canvas_drag_data_received (const RefPtr<Gdk::DragContext>& context,
373 const SelectionData& data,
374 guint info, guint time)
376 if (data.get_target() == "regions") {
377 drop_regions (context, x, y, data, info, time);
379 drop_paths (context, x, y, data, info, time);
384 Editor::idle_drop_paths (vector<string> paths, samplepos_t sample, double ypos, bool copy)
386 drop_paths_part_two (paths, sample, ypos, copy);
391 Editor::drop_paths_part_two (const vector<string>& paths, samplepos_t sample, double ypos, bool copy)
393 RouteTimeAxisView* tv;
395 /* MIDI files must always be imported, because we consider them
396 * writable. So split paths into two vectors, and follow the import
397 * path on the MIDI part.
400 vector<string> midi_paths;
401 vector<string> audio_paths;
403 for (vector<string>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
404 if (SMFSource::safe_midi_file_extension (*i)) {
405 midi_paths.push_back (*i);
407 audio_paths.push_back (*i);
412 std::pair<TimeAxisView*, int> const tvp = trackview_by_y_position (ypos, false);
413 if (tvp.first == 0) {
415 /* drop onto canvas background: create new tracks */
418 InstrumentSelector is; // instantiation builds instrument-list and sets default.
419 do_import (midi_paths, Editing::ImportDistinctFiles, ImportAsTrack, SrcBest, SMFTrackName, SMFTempoIgnore, sample, is.selected_instrument());
421 if (UIConfiguration::instance().get_only_copy_imported_files() || copy) {
422 do_import (audio_paths, Editing::ImportDistinctFiles, Editing::ImportAsTrack,
423 SrcBest, SMFTrackName, SMFTempoIgnore, sample);
425 do_embed (audio_paths, Editing::ImportDistinctFiles, ImportAsTrack, sample);
428 } else if ((tv = dynamic_cast<RouteTimeAxisView*> (tvp.first)) != 0) {
430 /* check that its a track, not a bus */
433 /* select the track, then embed/import */
436 do_import (midi_paths, Editing::ImportSerializeFiles, ImportToTrack,
437 SrcBest, SMFTrackName, SMFTempoIgnore, sample);
439 if (UIConfiguration::instance().get_only_copy_imported_files() || copy) {
440 do_import (audio_paths, Editing::ImportSerializeFiles, Editing::ImportToTrack,
441 SrcBest, SMFTrackName, SMFTempoIgnore, sample);
443 do_embed (audio_paths, Editing::ImportSerializeFiles, ImportToTrack, sample);
450 Editor::drop_paths (const RefPtr<Gdk::DragContext>& context,
452 const SelectionData& data,
453 guint info, guint time)
455 vector<string> paths;
459 if (convert_drop_to_paths (paths, context, x, y, data, info, time) == 0) {
461 /* D-n-D coordinates are window-relative, so convert to canvas coordinates
464 ev.type = GDK_BUTTON_RELEASE;
468 MusicSample when (window_event_sample (&ev, 0, &cy), 0);
471 bool copy = ((context->get_actions() & (Gdk::ACTION_COPY | Gdk::ACTION_LINK | Gdk::ACTION_MOVE)) == Gdk::ACTION_COPY);
473 /* We are not allowed to call recursive main event loops from within
474 the main event loop with GTK/Quartz. Since import/embed wants
475 to push up a progress dialog, defer all this till we go idle.
477 Glib::signal_idle().connect (sigc::bind (sigc::mem_fun (*this, &Editor::idle_drop_paths), paths, when.sample, cy, copy));
479 drop_paths_part_two (paths, when.sample, cy, copy);
483 context->drag_finish (true, false, time);
486 /** @param allow_horiz true to allow horizontal autoscroll, otherwise false.
488 * @param allow_vert true to allow vertical autoscroll, otherwise false.
492 Editor::maybe_autoscroll (bool allow_horiz, bool allow_vert, bool from_headers)
494 Gtk::Window* toplevel = dynamic_cast<Gtk::Window*>(contents().get_toplevel());
500 if (!UIConfiguration::instance().get_autoscroll_editor () || autoscroll_active ()) {
504 /* define a rectangular boundary for scrolling. If the mouse moves
505 * outside of this area and/or continue to be outside of this area,
506 * then we will continuously auto-scroll the canvas in the appropriate
509 * the boundary is defined in coordinates relative to the toplevel
510 * window since that is what we're going to call ::get_pointer() on
511 * during autoscrolling to determine if we're still outside the
515 ArdourCanvas::Rect scrolling_boundary;
516 Gtk::Allocation alloc;
519 alloc = controls_layout.get_allocation ();
523 controls_layout.get_parent()->translate_coordinates (*toplevel,
524 alloc.get_x(), alloc.get_y(),
527 scrolling_boundary = ArdourCanvas::Rect (wx, wy, wx + alloc.get_width(), wy + alloc.get_height());
531 alloc = _track_canvas_viewport->get_allocation ();
533 /* reduce height by the height of the timebars, which happens
534 to correspond to the position of the hv_scroll_group.
537 alloc.set_height (alloc.get_height() - hv_scroll_group->position().y);
538 alloc.set_y (alloc.get_y() + hv_scroll_group->position().y);
540 /* now reduce it again so that we start autoscrolling before we
541 * move off the top or bottom of the canvas
544 alloc.set_height (alloc.get_height() - 20);
545 alloc.set_y (alloc.get_y() + 10);
547 /* the effective width of the autoscroll boundary so
548 that we start scrolling before we hit the edge.
550 this helps when the window is slammed up against the
551 right edge of the screen, making it hard to scroll
555 if (alloc.get_width() > 20) {
556 alloc.set_width (alloc.get_width() - 20);
557 alloc.set_x (alloc.get_x() + 10);
562 _track_canvas_viewport->get_parent()->translate_coordinates (*toplevel,
563 alloc.get_x(), alloc.get_y(),
566 scrolling_boundary = ArdourCanvas::Rect (wx, wy, wx + alloc.get_width(), wy + alloc.get_height());
570 Gdk::ModifierType mask;
572 toplevel->get_window()->get_pointer (x, y, mask);
574 if ((allow_horiz && ((x < scrolling_boundary.x0 && _leftmost_sample > 0) || x >= scrolling_boundary.x1)) ||
575 (allow_vert && ((y < scrolling_boundary.y0 && vertical_adjustment.get_value() > 0)|| y >= scrolling_boundary.y1))) {
576 start_canvas_autoscroll (allow_horiz, allow_vert, scrolling_boundary);
581 Editor::autoscroll_active () const
583 return autoscroll_connection.connected ();
586 std::pair <samplepos_t,samplepos_t>
587 Editor::session_gui_extents ( bool use_extra ) const
590 return std::pair <samplepos_t,samplepos_t>(max_samplepos,0);
593 samplecnt_t session_extent_start = _session->current_start_sample();
594 samplecnt_t session_extent_end = _session->current_end_sample();
596 //calculate the extents of all regions in every playlist
597 //NOTE: we should listen to playlists, and cache these values so we don't calculate them every time.
599 boost::shared_ptr<RouteList> rl = _session->get_routes();
600 for (RouteList::iterator r = rl->begin(); r != rl->end(); ++r) {
601 boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*r);
603 boost::shared_ptr<Playlist> pl = tr->playlist();
604 if ( pl && !pl->all_regions_empty() ) {
605 pair<samplepos_t, samplepos_t> e;
606 e = pl->get_extent();
607 if (e.first < session_extent_start) {
608 session_extent_start = e.first;
610 if (e.second > session_extent_end) {
611 session_extent_end = e.second;
618 //ToDo: also incorporate automation regions (in case the session has no audio/midi but is just used for automating plugins or the like)
620 //add additional time to the ui extents ( user-defined in config )
622 samplecnt_t const extra = UIConfiguration::instance().get_extra_ui_extents_time() * 60 * _session->nominal_sample_rate();
623 session_extent_end += extra;
624 session_extent_start -= extra;
628 if (session_extent_end > max_samplepos) {
629 session_extent_end = max_samplepos;
631 if (session_extent_start < 0) {
632 session_extent_start = 0;
635 std::pair <samplepos_t,samplepos_t> ret (session_extent_start, session_extent_end);
640 Editor::autoscroll_canvas ()
643 Gdk::ModifierType mask;
644 sampleoffset_t dx = 0;
645 bool no_stop = false;
646 Gtk::Window* toplevel = dynamic_cast<Gtk::Window*>(contents().get_toplevel());
652 toplevel->get_window()->get_pointer (x, y, mask);
655 bool vertical_motion = false;
657 if (autoscroll_horizontal_allowed) {
659 samplepos_t new_sample = _leftmost_sample;
663 if (x > autoscroll_boundary.x1) {
665 /* bring it back into view */
666 dx = x - autoscroll_boundary.x1;
667 dx += 10 + (2 * (autoscroll_cnt/2));
669 dx = pixel_to_sample (dx);
671 dx *= UIConfiguration::instance().get_draggable_playhead_speed();
673 if (_leftmost_sample < max_samplepos - dx) {
674 new_sample = _leftmost_sample + dx;
676 new_sample = max_samplepos;
681 } else if (x < autoscroll_boundary.x0) {
683 dx = autoscroll_boundary.x0 - x;
684 dx += 10 + (2 * (autoscroll_cnt/2));
686 dx = pixel_to_sample (dx);
688 dx *= UIConfiguration::instance().get_draggable_playhead_speed();
690 if (_leftmost_sample >= dx) {
691 new_sample = _leftmost_sample - dx;
699 if (new_sample != _leftmost_sample) {
700 vc.time_origin = new_sample;
701 vc.add (VisualChange::TimeOrigin);
705 if (autoscroll_vertical_allowed) {
707 // const double vertical_pos = vertical_adjustment.get_value();
708 const int speed_factor = 10;
712 if (y < autoscroll_boundary.y0) {
714 /* scroll to make higher tracks visible */
716 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
717 scroll_up_one_track ();
718 vertical_motion = true;
722 } else if (y > autoscroll_boundary.y1) {
724 if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
725 scroll_down_one_track ();
726 vertical_motion = true;
733 if (vc.pending || vertical_motion) {
735 /* change horizontal first */
741 /* now send a motion event to notify anyone who cares
742 that we have moved to a new location (because we scrolled)
747 ev.type = GDK_MOTION_NOTIFY;
748 ev.state = Gdk::BUTTON1_MASK;
750 /* the motion handler expects events in canvas coordinate space */
752 /* we asked for the mouse position above (::get_pointer()) via
753 * our own top level window (we being the Editor). Convert into
754 * coordinates within the canvas window.
760 toplevel->translate_coordinates (*_track_canvas, x, y, cx, cy);
762 /* clamp x and y to remain within the autoscroll boundary,
763 * which is defined in window coordinates
766 x = min (max ((ArdourCanvas::Coord) cx, autoscroll_boundary.x0), autoscroll_boundary.x1);
767 y = min (max ((ArdourCanvas::Coord) cy, autoscroll_boundary.y0), autoscroll_boundary.y1);
769 /* now convert from Editor window coordinates to canvas
773 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
778 motion_handler (0, (GdkEvent*) &ev, true);
780 } else if (no_stop) {
782 /* not changing visual state but pointer is outside the scrolling boundary
783 * so we still need to deliver a fake motion event
788 ev.type = GDK_MOTION_NOTIFY;
789 ev.state = Gdk::BUTTON1_MASK;
791 /* the motion handler expects events in canvas coordinate space */
793 /* first convert from Editor window coordinates to canvas
800 /* clamp x and y to remain within the visible area. except
801 * .. if horizontal scrolling is allowed, always allow us to
805 if (autoscroll_horizontal_allowed) {
806 x = min (max ((ArdourCanvas::Coord) x, 0.0), autoscroll_boundary.x1);
808 x = min (max ((ArdourCanvas::Coord) x, autoscroll_boundary.x0), autoscroll_boundary.x1);
810 y = min (max ((ArdourCanvas::Coord) y, autoscroll_boundary.y0), autoscroll_boundary.y1);
812 toplevel->translate_coordinates (*_track_canvas_viewport, x, y, cx, cy);
814 ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy));
819 motion_handler (0, (GdkEvent*) &ev, true);
822 stop_canvas_autoscroll ();
828 return true; /* call me again */
832 Editor::start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary)
838 stop_canvas_autoscroll ();
840 autoscroll_horizontal_allowed = allow_horiz;
841 autoscroll_vertical_allowed = allow_vert;
842 autoscroll_boundary = boundary;
844 /* do the first scroll right now
847 autoscroll_canvas ();
849 /* scroll again at very very roughly 30FPS */
851 autoscroll_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &Editor::autoscroll_canvas), 30);
855 Editor::stop_canvas_autoscroll ()
857 autoscroll_connection.disconnect ();
861 Editor::EnterContext*
862 Editor::get_enter_context(ItemType type)
864 for (ssize_t i = _enter_stack.size() - 1; i >= 0; --i) {
865 if (_enter_stack[i].item_type == type) {
866 return &_enter_stack[i];
873 Editor::left_track_canvas (GdkEventCrossing* ev)
875 const bool was_within = within_track_canvas;
877 within_track_canvas = false;
878 set_entered_track (0);
879 set_entered_regionview (0);
880 reset_canvas_action_sensitivity (false);
883 if (ev->detail == GDK_NOTIFY_NONLINEAR ||
884 ev->detail == GDK_NOTIFY_NONLINEAR_VIRTUAL) {
885 /* context menu or something similar */
886 sensitize_the_right_region_actions (false);
888 sensitize_the_right_region_actions (true);
896 Editor::entered_track_canvas (GdkEventCrossing* ev)
898 const bool was_within = within_track_canvas;
899 within_track_canvas = true;
900 reset_canvas_action_sensitivity (true);
903 if (ev->detail == GDK_NOTIFY_NONLINEAR ||
904 ev->detail == GDK_NOTIFY_NONLINEAR_VIRTUAL) {
905 /* context menu or something similar */
906 sensitize_the_right_region_actions (false);
908 sensitize_the_right_region_actions (true);
916 Editor::ensure_time_axis_view_is_visible (TimeAxisView const & track, bool at_top)
918 if (track.hidden()) {
922 /* compute visible area of trackview group, as offsets from top of
926 double const current_view_min_y = vertical_adjustment.get_value();
927 double const current_view_max_y = current_view_min_y + vertical_adjustment.get_page_size();
929 double const track_min_y = track.y_position ();
930 double const track_max_y = track.y_position () + track.effective_height ();
933 (track_min_y >= current_view_min_y &&
934 track_max_y < current_view_max_y)) {
935 /* already visible, and caller did not ask to place it at the
936 * top of the track canvas
944 new_value = track_min_y;
946 if (track_min_y < current_view_min_y) {
947 // Track is above the current view
948 new_value = track_min_y;
949 } else if (track_max_y > current_view_max_y) {
950 // Track is below the current view
951 new_value = track.y_position () + track.effective_height() - vertical_adjustment.get_page_size();
953 new_value = track_min_y;
957 vertical_adjustment.set_value(new_value);
960 /** Called when the main vertical_adjustment has changed */
962 Editor::tie_vertical_scrolling ()
964 if (pending_visual_change.idle_handler_id < 0) {
965 _summary->set_overlays_dirty ();
970 Editor::set_horizontal_position (double p)
972 horizontal_adjustment.set_value (p);
974 _leftmost_sample = (samplepos_t) floor (p * samples_per_pixel);
978 Editor::color_handler()
980 Gtkmm2ext::Color base = UIConfiguration::instance().color ("ruler base");
981 Gtkmm2ext::Color text = UIConfiguration::instance().color ("ruler text");
982 timecode_ruler->set_fill_color (base);
983 timecode_ruler->set_outline_color (text);
984 minsec_ruler->set_fill_color (base);
985 minsec_ruler->set_outline_color (text);
986 samples_ruler->set_fill_color (base);
987 samples_ruler->set_outline_color (text);
988 bbt_ruler->set_fill_color (base);
989 bbt_ruler->set_outline_color (text);
991 playhead_cursor->set_color (UIConfiguration::instance().color ("play head"));
993 meter_bar->set_fill_color (UIConfiguration::instance().color_mod ("meter bar", "marker bar"));
994 meter_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
996 tempo_bar->set_fill_color (UIConfiguration::instance().color_mod ("tempo bar", "marker bar"));
997 tempo_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
999 marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("marker bar", "marker bar"));
1000 marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1002 cd_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("cd marker bar", "marker bar"));
1003 cd_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1005 range_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("range marker bar", "marker bar"));
1006 range_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1008 transport_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("transport marker bar", "marker bar"));
1009 transport_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
1011 cd_marker_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
1012 cd_marker_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
1014 range_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect"));
1015 range_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect"));
1017 transport_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("transport drag rect"));
1018 transport_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("transport drag rect"));
1020 transport_loop_range_rect->set_fill_color (UIConfiguration::instance().color_mod ("transport loop rect", "loop rectangle"));
1021 transport_loop_range_rect->set_outline_color (UIConfiguration::instance().color ("transport loop rect"));
1023 transport_punch_range_rect->set_fill_color (UIConfiguration::instance().color ("transport punch rect"));
1024 transport_punch_range_rect->set_outline_color (UIConfiguration::instance().color ("transport punch rect"));
1026 transport_punchin_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
1027 transport_punchout_line->set_outline_color (UIConfiguration::instance().color ("punch line"));
1029 rubberband_rect->set_outline_color (UIConfiguration::instance().color ("rubber band rect"));
1030 rubberband_rect->set_fill_color (UIConfiguration::instance().color_mod ("rubber band rect", "selection rect"));
1032 location_marker_color = UIConfiguration::instance().color ("location marker");
1033 location_range_color = UIConfiguration::instance().color ("location range");
1034 location_cd_marker_color = UIConfiguration::instance().color ("location cd marker");
1035 location_loop_color = UIConfiguration::instance().color ("location loop");
1036 location_punch_color = UIConfiguration::instance().color ("location punch");
1038 refresh_location_display ();
1040 NoteBase::set_colors ();
1042 /* redraw the whole thing */
1043 _track_canvas->set_background_color (UIConfiguration::instance().color ("arrange base"));
1044 _track_canvas->queue_draw ();
1047 redisplay_grid (true);
1050 _session->tempo_map().apply_with_metrics (*this, &Editor::draw_metric_marks); // redraw metric markers
1055 Editor::horizontal_position () const
1057 return sample_to_pixel (_leftmost_sample);
1061 Editor::track_canvas_key_press (GdkEventKey*)
1067 Editor::track_canvas_key_release (GdkEventKey*)
1073 Editor::clamp_verbose_cursor_x (double x)
1078 x = min (_visible_canvas_width - 200.0, x);
1084 Editor::clamp_verbose_cursor_y (double y)
1087 y = min (_visible_canvas_height - 50, y);
1091 ArdourCanvas::GtkCanvasViewport*
1092 Editor::get_track_canvas() const
1094 return _track_canvas_viewport;
1098 Editor::get_canvas_cursor () const
1100 /* The top of the cursor stack is always the currently visible cursor. */
1101 return _cursor_stack.back();
1105 Editor::set_canvas_cursor (Gdk::Cursor* cursor)
1107 Glib::RefPtr<Gdk::Window> win = _track_canvas->get_window();
1109 if (win && !_cursors->is_invalid (cursor)) {
1110 /* glibmm 2.4 doesn't allow null cursor pointer because it uses
1111 a Gdk::Cursor& as the argument to Gdk::Window::set_cursor().
1112 But a null pointer just means "use parent window cursor",
1113 and so should be allowed. Gtkmm 3.x has fixed this API.
1115 For now, drop down and use C API
1117 gdk_window_set_cursor (win->gobj(), cursor ? cursor->gobj() : 0);
1122 Editor::push_canvas_cursor (Gdk::Cursor* cursor)
1124 if (!_cursors->is_invalid (cursor)) {
1125 _cursor_stack.push_back (cursor);
1126 set_canvas_cursor (cursor);
1128 return _cursor_stack.size() - 1;
1132 Editor::pop_canvas_cursor ()
1135 if (_cursor_stack.size() <= 1) {
1136 PBD::error << "attempt to pop default cursor" << endmsg;
1140 _cursor_stack.pop_back();
1141 if (_cursor_stack.back()) {
1142 /* Popped to an existing cursor, we're done. Otherwise, the
1143 context that created this cursor has been destroyed, so we need
1144 to skip to the next down the stack. */
1145 set_canvas_cursor (_cursor_stack.back());
1152 Editor::which_trim_cursor (bool left) const
1154 if (!entered_regionview) {
1158 Trimmable::CanTrim ct = entered_regionview->region()->can_trim ();
1162 if (ct & Trimmable::FrontTrimEarlier) {
1163 return _cursors->left_side_trim;
1165 return _cursors->left_side_trim_right_only;
1168 if (ct & Trimmable::EndTrimLater) {
1169 return _cursors->right_side_trim;
1171 return _cursors->right_side_trim_left_only;
1177 Editor::which_mode_cursor () const
1179 Gdk::Cursor* mode_cursor = MouseCursors::invalid_cursor ();
1181 switch (mouse_mode) {
1183 mode_cursor = _cursors->selector;
1187 mode_cursor = _cursors->scissors;
1192 /* don't use mode cursor, pick a grabber cursor based on the item */
1196 mode_cursor = _cursors->midi_pencil;
1200 mode_cursor = _cursors->time_fx; // just use playhead
1204 mode_cursor = _cursors->speaker;
1208 /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
1209 if (get_smart_mode()) {
1212 get_pointer_position (x, y);
1214 if (x >= 0 && y >= 0) {
1216 vector<ArdourCanvas::Item const *> items;
1218 /* Note how we choose a specific scroll group to get
1219 * items from. This could be problematic.
1222 hv_scroll_group->add_items_at_point (ArdourCanvas::Duple (x,y), items);
1224 // first item will be the upper most
1226 if (!items.empty()) {
1227 const ArdourCanvas::Item* i = items.front();
1229 if (i && i->parent() && i->parent()->get_data (X_("timeselection"))) {
1230 pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y);
1231 if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
1232 mode_cursor = _cursors->up_down;
1243 Editor::which_track_cursor () const
1245 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
1247 switch (_join_object_range_state) {
1248 case JOIN_OBJECT_RANGE_NONE:
1249 case JOIN_OBJECT_RANGE_OBJECT:
1250 cursor = _cursors->grabber;
1252 case JOIN_OBJECT_RANGE_RANGE:
1253 cursor = _cursors->selector;
1261 Editor::which_canvas_cursor(ItemType type) const
1263 Gdk::Cursor* cursor = which_mode_cursor ();
1265 if (mouse_mode == MouseRange) {
1267 case StartSelectionTrimItem:
1268 cursor = _cursors->left_side_trim;
1270 case EndSelectionTrimItem:
1271 cursor = _cursors->right_side_trim;
1278 if ((mouse_mode == MouseObject || get_smart_mode ()) ||
1279 mouse_mode == MouseContent) {
1281 /* find correct cursor to use in object/smart mode */
1285 /* We don't choose a cursor for these items on top of a region view,
1286 because this would push a new context on the enter stack which
1287 means switching the region context for things like smart mode
1288 won't actualy change the cursor. */
1289 // case RegionViewNameHighlight:
1290 // case RegionViewName:
1293 case AutomationTrackItem:
1294 cursor = which_track_cursor ();
1296 case PlayheadCursorItem:
1297 cursor = _cursors->grabber;
1300 cursor = _cursors->selector;
1302 case ControlPointItem:
1303 cursor = _cursors->fader;
1306 cursor = _cursors->cross_hair;
1308 case AutomationLineItem:
1309 cursor = _cursors->cross_hair;
1311 case StartSelectionTrimItem:
1312 cursor = _cursors->left_side_trim;
1314 case EndSelectionTrimItem:
1315 cursor = _cursors->right_side_trim;
1318 cursor = _cursors->fade_in;
1320 case FadeInHandleItem:
1321 cursor = _cursors->fade_in;
1323 case FadeInTrimHandleItem:
1324 cursor = _cursors->fade_in;
1327 cursor = _cursors->fade_out;
1329 case FadeOutHandleItem:
1330 cursor = _cursors->fade_out;
1332 case FadeOutTrimHandleItem:
1333 cursor = _cursors->fade_out;
1335 case FeatureLineItem:
1336 cursor = _cursors->cross_hair;
1338 case LeftFrameHandle:
1339 if ( effective_mouse_mode() == MouseObject ) // (smart mode): if the user is in the btm half, show the trim cursor
1340 cursor = which_trim_cursor (true);
1342 cursor = _cursors->selector; // (smart mode): in the top half, just show the selection (range) cursor
1344 case RightFrameHandle:
1345 if ( effective_mouse_mode() == MouseObject ) //see above
1346 cursor = which_trim_cursor (false);
1348 cursor = _cursors->selector;
1350 case StartCrossFadeItem:
1351 cursor = _cursors->fade_in;
1353 case EndCrossFadeItem:
1354 cursor = _cursors->fade_out;
1356 case CrossfadeViewItem:
1357 cursor = _cursors->cross_hair;
1360 cursor = _cursors->grabber_note;
1365 } else if (mouse_mode == MouseDraw) {
1367 /* ControlPointItem is not really specific to region gain mode
1368 but it is the same cursor so don't worry about this for now.
1369 The result is that we'll see the fader cursor if we enter
1370 non-region-gain-line control points while in MouseDraw
1371 mode, even though we can't edit them in this mode.
1376 case ControlPointItem:
1377 cursor = _cursors->fader;
1380 cursor = _cursors->grabber_note;
1387 /* These items use the timebar cursor at all times */
1388 case TimecodeRulerItem:
1389 case MinsecRulerItem:
1391 case SamplesRulerItem:
1392 cursor = _cursors->timebar;
1395 /* These items use the grabber cursor at all times */
1396 case MeterMarkerItem:
1397 case TempoMarkerItem:
1402 case RangeMarkerBarItem:
1403 case CdMarkerBarItem:
1405 case TransportMarkerBarItem:
1407 cursor = _cursors->grabber;
1418 Editor::choose_canvas_cursor_on_entry (ItemType type)
1420 if (_drags->active()) {
1424 Gdk::Cursor* cursor = which_canvas_cursor(type);
1426 if (!_cursors->is_invalid (cursor)) {
1427 // Push a new enter context
1428 const EnterContext ctx = { type, CursorContext::create(*this, cursor) };
1429 _enter_stack.push_back(ctx);
1434 Editor::update_all_enter_cursors ()
1436 for (std::vector<EnterContext>::iterator i = _enter_stack.begin(); i != _enter_stack.end(); ++i) {
1437 i->cursor_ctx->change(which_canvas_cursor(i->item_type));
1442 Editor::trackviews_height() const
1444 if (!_trackview_group) {
1448 return _visible_canvas_height - _trackview_group->canvas_origin().y;