2 * Copyright (C) 2005-2007 Doug McLain <doug@nostar.net>
3 * Copyright (C) 2005-2008 Nick Mainsbridge <mainsbridge@gmail.com>
4 * Copyright (C) 2005-2019 Paul Davis <paul@linuxaudiosystems.com>
5 * Copyright (C) 2005 Taybin Rutkin <taybin@taybin.com>
6 * Copyright (C) 2006-2014 David Robillard <d@drobilla.net>
7 * Copyright (C) 2006-2016 Tim Mayberry <mojofunk@gmail.com>
8 * Copyright (C) 2008-2012 Carl Hetherington <carl@carlh.net>
9 * Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
10 * Copyright (C) 2014-2015 Ben Loftis <ben@harrisonconsoles.com>
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License along
23 * with this program; if not, write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
33 #include <boost/smart_ptr/scoped_ptr.hpp>
35 #include <gtkmm/separator.h>
37 #include "pbd/error.h"
38 #include "pbd/convert.h"
39 #include "pbd/stacktrace.h"
40 #include "pbd/unwind.h"
42 #include "ardour/profile.h"
44 #include "gtkmm2ext/colors.h"
45 #include "gtkmm2ext/doi.h"
46 #include "gtkmm2ext/utils.h"
48 #include "canvas/canvas.h"
49 #include "canvas/rectangle.h"
50 #include "canvas/debug.h"
51 #include "canvas/utils.h"
53 #include "widgets/tooltips.h"
55 #include "ardour_dialog.h"
56 #include "audio_time_axis.h"
57 #include "floating_text_entry.h"
58 #include "gui_thread.h"
59 #include "public_editor.h"
60 #include "time_axis_view.h"
61 #include "region_view.h"
62 #include "ghostregion.h"
63 #include "selection.h"
65 #include "rgb_macros.h"
67 #include "streamview.h"
68 #include "editor_drag.h"
70 #include "ui_config.h"
77 using namespace ARDOUR;
79 using namespace Editing;
80 using namespace ArdourCanvas;
81 using namespace ArdourWidgets;
82 using Gtkmm2ext::Keyboard;
84 #define TOP_LEVEL_WIDGET controls_ebox
86 const double trim_handle_size = 6.0; /* pixels */
87 uint32_t TimeAxisView::button_height = 0;
88 uint32_t TimeAxisView::extra_height = 0;
89 int const TimeAxisView::_max_order = 512;
90 unsigned int TimeAxisView::name_width_px = 100;
91 PBD::Signal1<void,TimeAxisView*> TimeAxisView::CatchDeletion;
92 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::controls_meters_size_group = Glib::RefPtr<Gtk::SizeGroup>();
93 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::midi_scroomer_size_group = Glib::RefPtr<Gtk::SizeGroup>();
96 TimeAxisView::setup_sizes()
98 name_width_px = ceilf (100.f * UIConfiguration::instance().get_ui_scale());
101 TimeAxisView::TimeAxisView (ARDOUR::Session* sess, PublicEditor& ed, TimeAxisView* rent, Canvas& /*canvas*/)
102 : controls_table (5, 4)
103 , controls_button_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
104 , _name_editing (false)
108 , selection_group (0)
111 , in_destructor (false)
113 , _canvas_display (0)
118 , _effective_height (0)
119 , _resize_drag_start (-1)
120 , _did_resize (false)
121 , _preresize_cursor (0)
122 , _have_preresize_cursor (false)
123 , _ebox_release_can_act (true)
125 if (!controls_meters_size_group) {
126 controls_meters_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
128 if (!midi_scroomer_size_group) {
129 midi_scroomer_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
131 if (extra_height == 0) {
135 _canvas_display = new ArdourCanvas::Container (ed.get_trackview_group ());
136 CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
137 _canvas_display->hide(); // reveal as needed
139 _canvas_separator = new ArdourCanvas::Line(_canvas_display);
140 CANVAS_DEBUG_NAME (_canvas_separator, "separator for TAV");
141 _canvas_separator->set (ArdourCanvas::Duple(0.0, 0.0), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, 0.0));
142 _canvas_separator->set_outline_color(Gtkmm2ext::rgba_to_color (0, 0, 0, 1.0));
143 _canvas_separator->set_outline_width(1.0);
144 _canvas_separator->hide();
146 selection_group = new ArdourCanvas::Container (_canvas_display);
147 CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
148 selection_group->set_data (X_("timeselection"), (void *) 1);
149 selection_group->hide();
151 _ghost_group = new ArdourCanvas::Container (_canvas_display);
152 CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
153 _ghost_group->lower_to_bottom();
154 _ghost_group->show();
156 name_label.set_name (X_("TrackNameEditor"));
157 name_label.set_alignment (0.0, 0.5);
158 name_label.set_width_chars (12);
159 set_tooltip (name_label, _("Track/Bus name (double click to edit)"));
162 boost::scoped_ptr<Gtk::Entry> an_entry (new FocusEntry);
163 an_entry->set_name (X_("TrackNameEditor"));
164 Gtk::Requisition req = an_entry->size_request ();
166 name_label.set_size_request (-1, req.height);
167 name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
170 // set min. track-header width if fader is not visible
171 name_label.set_size_request(name_width_px, -1);
175 controls_table.set_row_spacings (2);
176 controls_table.set_col_spacings (2);
177 controls_table.set_border_width (2);
179 if (ARDOUR::Profile->get_mixbus() ) {
180 controls_table.attach (name_label, 4, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
182 controls_table.attach (name_label, 1, 2, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
185 controls_table.show_all ();
186 controls_table.set_no_show_all ();
188 controls_vbox.pack_start (controls_table, false, false);
189 controls_vbox.show ();
191 top_hbox.pack_start (controls_vbox, true, true);
194 controls_ebox.add (time_axis_hbox);
195 controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
196 Gdk::BUTTON_RELEASE_MASK|
197 Gdk::POINTER_MOTION_MASK|
198 Gdk::ENTER_NOTIFY_MASK|
199 Gdk::LEAVE_NOTIFY_MASK|
201 controls_ebox.set_flags (CAN_FOCUS);
203 /* note that this handler connects *before* the default handler */
204 controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
205 controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
206 controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
207 controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
208 controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
209 controls_ebox.show ();
211 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
212 time_axis_frame.add(top_hbox);
213 time_axis_frame.show();
215 HSeparator* separator = manage (new HSeparator());
216 separator->set_name("TrackSeparator");
217 separator->set_size_request(-1, 1);
220 scroomer_placeholder.set_size_request (-1, -1);
221 scroomer_placeholder.show();
222 midi_scroomer_size_group->add_widget (scroomer_placeholder);
224 time_axis_vbox.pack_start (*separator, false, false);
225 time_axis_vbox.pack_start (time_axis_frame, true, true);
226 time_axis_vbox.show();
227 time_axis_hbox.pack_start (time_axis_vbox, true, true);
228 time_axis_hbox.show();
229 top_hbox.pack_start (scroomer_placeholder, false, false); // OR pack_end to move after meters ?
231 UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
232 UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &TimeAxisView::parameter_changed));
235 TimeAxisView::~TimeAxisView()
237 CatchDeletion (this);
239 in_destructor = true;
241 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
245 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
246 delete (*i)->rect; (*i)->rect=0;
247 delete (*i)->start_trim; (*i)->start_trim = 0;
248 delete (*i)->end_trim; (*i)->end_trim = 0;
252 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
253 delete (*i)->rect; (*i)->rect = 0;
254 delete (*i)->start_trim; (*i)->start_trim = 0;
255 delete (*i)->end_trim; (*i)->end_trim = 0;
258 delete selection_group;
261 delete _canvas_display;
271 TimeAxisView::hide ()
277 _canvas_display->hide ();
278 _canvas_separator->hide ();
280 if (control_parent) {
281 control_parent->remove (TOP_LEVEL_WIDGET);
288 /* now hide children */
290 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
294 /* if its hidden, it cannot be selected */
295 _editor.get_selection().remove (this);
296 /* and neither can its regions */
297 _editor.get_selection().remove_regions (this);
302 /** Display this TimeAxisView as the nth component of the parent box, at y.
304 * @param y y position.
305 * @param nth index for this TimeAxisView, increased if this view has children.
306 * @param parent parent component.
308 * @return height of this TimeAxisView.
311 TimeAxisView::show_at (double y, int& nth, VBox *parent)
313 if (control_parent) {
314 control_parent->reorder_child (TOP_LEVEL_WIDGET, nth);
316 control_parent = parent;
317 parent->pack_start (TOP_LEVEL_WIDGET, false, false);
318 parent->reorder_child (TOP_LEVEL_WIDGET, nth);
323 if (_y_position != y) {
324 _canvas_display->set_y_position (y);
328 _canvas_display->raise_to_top ();
329 _canvas_display->show ();
333 _effective_height = current_height ();
335 /* now show relevant children */
337 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
338 if ((*i)->marked_for_display()) {
340 _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
346 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
350 /* put separator at the bottom of this time axis view */
352 _canvas_separator->set (ArdourCanvas::Duple(0, height), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, height));
353 _canvas_separator->lower_to_bottom ();
354 _canvas_separator->show ();
356 return _effective_height;
360 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
362 switch (ev->direction) {
364 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
365 /* See Editor::_stepping_axis_view for notes on this hack */
366 Editor& e = dynamic_cast<Editor&> (_editor);
367 if (!e.stepping_axis_view ()) {
368 e.set_stepping_axis_view (this);
370 e.stepping_axis_view()->step_height (false);
375 case GDK_SCROLL_DOWN:
376 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
377 /* See Editor::_stepping_axis_view for notes on this hack */
378 Editor& e = dynamic_cast<Editor&> (_editor);
379 if (!e.stepping_axis_view ()) {
380 e.set_stepping_axis_view (this);
382 e.stepping_axis_view()->step_height (true);
388 /* no handling for left/right, yet */
392 /* Just forward to the normal canvas scroll method. The coordinate
393 systems are different but since the canvas is always larger than the
394 track headers, and aligned with the trackview area, this will work.
396 In the not too distant future this layout is going away anyway and
397 headers will be on the canvas.
399 return _editor.canvas_scroll_event (ev, false);
403 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
405 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
406 /* see if it is inside the name label */
407 if (name_label.is_ancestor (controls_ebox)) {
410 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
411 Gtk::Allocation a = name_label.get_allocation ();
412 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
414 _ebox_release_can_act = false;
421 _ebox_release_can_act = true;
423 if (maybe_set_cursor (event->y) > 0) {
424 _resize_drag_start = event->y_root;
431 TimeAxisView::idle_resize (int32_t h)
433 set_height (std::max(0, h));
438 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
440 if (_resize_drag_start >= 0) {
442 /* (ab)use the DragManager to do autoscrolling - basically we
443 * are pretending that the drag is taking place over the canvas
444 * (which perhaps in the glorious future, when track headers
445 * and the canvas are unified, will actually be true.)
448 _editor.maybe_autoscroll (false, true, true);
450 /* now schedule the actual TAV resize */
451 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
452 _editor.add_to_idle_resize (this, delta);
453 _resize_drag_start = ev->y_root;
456 /* not dragging but ... */
457 maybe_set_cursor (ev->y);
460 gdk_event_request_motions(ev);
465 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
467 if (_have_preresize_cursor) {
468 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
469 _have_preresize_cursor = false;
475 TimeAxisView::maybe_set_cursor (int y)
477 /* XXX no Gtkmm Gdk::Window::get_cursor() */
478 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
480 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
482 /* y-coordinate in lower 25% */
484 if (!_have_preresize_cursor) {
485 _preresize_cursor = gdk_window_get_cursor (win->gobj());
486 _have_preresize_cursor = true;
487 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
492 } else if (_have_preresize_cursor) {
493 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
494 _have_preresize_cursor = false;
503 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
505 if (_resize_drag_start >= 0) {
506 if (_have_preresize_cursor) {
507 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
508 _preresize_cursor = 0;
509 _have_preresize_cursor = false;
511 _editor.stop_canvas_autoscroll ();
512 _resize_drag_start = -1;
515 // don't change selection
520 if (!_ebox_release_can_act) {
524 switch (ev->button) {
527 selection_click (ev);
532 popup_display_menu (ev->time);
540 TimeAxisView::selection_click (GdkEventButton* ev)
542 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
543 _editor.set_selected_track (*this, op, false);
547 /** Steps through the defined heights for this TrackView.
548 * @param coarser true if stepping should decrease in size, otherwise false.
551 TimeAxisView::step_height (bool coarser)
553 static const uint32_t step = 25;
557 if (height <= preset_height (HeightSmall)) {
559 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
560 set_height_enum (HeightSmall);
562 set_height (height - step);
567 if (height <= preset_height(HeightSmall)) {
568 set_height_enum (HeightNormal);
570 set_height (height + step);
577 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
579 if (apply_to_selection) {
580 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
582 set_height (preset_height (h));
587 TimeAxisView::set_height (uint32_t h, TrackHeightMode m)
590 if (m == TotalHeight) {
591 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
592 if (!(*i)->hidden()) {
599 if (h < preset_height (HeightSmall)) {
600 h = preset_height (HeightSmall);
603 TOP_LEVEL_WIDGET.property_height_request () = h;
606 set_gui_property ("height", height);
608 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
612 if (selection_group->visible ()) {
613 /* resize the selection rect */
614 show_selection (_editor.get_selection().time);
618 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
619 (*i)->set_height(h, OnlySelf);
623 _editor.override_visible_track_count ();
627 TimeAxisView::begin_name_edit ()
629 if (!can_edit_name()) {
633 Gtk::Window* toplevel = (Gtk::Window*) control_parent->get_toplevel();
634 FloatingTextEntry* fte = new FloatingTextEntry (toplevel, name ());
636 fte->set_name ("TrackNameEditor");
637 fte->use_text.connect (sigc::mem_fun (*this, &TimeAxisView::end_name_edit));
639 /* We want to new toplevel window to overlay the name label, so
640 * translate the coordinates of the upper left corner of the name label
641 * into the coordinate space of the top level window.
647 name_label.translate_coordinates (*toplevel, 0, 0, x, y);
648 toplevel->get_window()->get_origin (wx, wy);
650 fte->move (wx + x, wy + y);
655 TimeAxisView::end_name_edit (std::string str, int next_dir)
657 if (!name_entry_changed (str)) {
663 TrackViewList const & allviews = _editor.get_track_views ();
664 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
666 if (i != allviews.end()) {
669 if (++i == allviews.end()) {
673 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
675 if (rtav && rtav->is_track() && rtav->track()->rec_enable_control()->get_value()) {
679 if (!(*i)->hidden()) {
686 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
687 _editor.ensure_time_axis_view_is_visible (**i, false);
688 (*i)->begin_name_edit ();
691 } else if (next_dir < 0) {
693 TrackViewList const & allviews = _editor.get_track_views ();
694 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
696 if (i != allviews.begin()) {
698 if (i == allviews.begin()) {
704 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
706 if (rtav && rtav->is_track() && rtav->track()->rec_enable_control()->get_value()) {
710 if (!(*i)->hidden()) {
717 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
718 _editor.ensure_time_axis_view_is_visible (**i, false);
719 (*i)->begin_name_edit ();
725 TimeAxisView::name_entry_changed (string const&)
731 TimeAxisView::can_edit_name () const
737 TimeAxisView::conditionally_add_to_selection ()
743 Selection& s (_editor.get_selection ());
745 if (!s.selected (this)) {
746 _editor.set_selected_track (*this, Selection::Set);
751 TimeAxisView::popup_display_menu (guint32 when)
753 conditionally_add_to_selection ();
755 build_display_menu ();
757 if (!display_menu->items().empty()) {
758 display_menu->popup (1, when);
763 TimeAxisView::set_selected (bool yn)
765 if (yn == selected()) {
769 AxisView::set_selected (yn);
772 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
773 time_axis_frame.set_name ("MixerStripSelectedFrame");
774 controls_ebox.set_name (controls_base_selected_name);
775 controls_vbox.set_name (controls_base_selected_name);
776 time_axis_vbox.set_name (controls_base_selected_name);
778 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
779 time_axis_frame.set_name (controls_base_unselected_name);
780 controls_ebox.set_name (controls_base_unselected_name);
781 controls_vbox.set_name (controls_base_unselected_name);
782 time_axis_vbox.set_name (controls_base_unselected_name);
787 time_axis_frame.show();
791 TimeAxisView::build_display_menu ()
793 using namespace Menu_Helpers;
797 display_menu = new Menu;
798 display_menu->set_name ("ArdourContextMenu");
800 // Just let implementing classes define what goes into the manu
804 TimeAxisView::set_samples_per_pixel (double fpp)
806 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
807 (*i)->set_samples_per_pixel (fpp);
812 TimeAxisView::show_timestretch (samplepos_t start, samplepos_t end, int layers, int layer)
814 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
815 (*i)->show_timestretch (start, end, layers, layer);
820 TimeAxisView::hide_timestretch ()
822 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
823 (*i)->hide_timestretch ();
828 TimeAxisView::show_selection (TimeSelection& ts)
835 time_axis_frame.show();
837 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
838 if (!(*i)->selected () && !(*i)->propagate_time_selection ()) {
841 (*i)->show_selection (ts);
844 if (selection_group->visible ()) {
845 while (!used_selection_rects.empty()) {
846 free_selection_rects.push_front (used_selection_rects.front());
847 used_selection_rects.pop_front();
848 free_selection_rects.front()->rect->hide();
849 free_selection_rects.front()->start_trim->hide();
850 free_selection_rects.front()->end_trim->hide();
852 selection_group->hide();
855 selection_group->show();
856 selection_group->raise_to_top();
858 uint32_t gap = UIConfiguration::instance().get_vertical_region_gap ();
859 float ui_scale = UIConfiguration::instance().get_ui_scale ();
860 if (gap > 0 && ui_scale > 0) {
861 gap = ceil (gap * ui_scale);
864 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
865 samplepos_t start, end;
870 cnt = end - start + 1;
872 rect = get_selection_rect ((*i).id);
874 x1 = _editor.sample_to_pixel (start);
875 x2 = _editor.sample_to_pixel (start + cnt - 1);
876 y2 = current_height() - 1;
878 if (dynamic_cast<AudioTimeAxisView*>(this)) {
886 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
888 // trim boxes are at the top for selections
891 rect->start_trim->set (ArdourCanvas::Rect (x1, 0, x1 + trim_handle_size, y2));
892 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
894 rect->start_trim->show();
895 rect->end_trim->show();
897 rect->start_trim->hide();
898 rect->end_trim->hide();
902 used_selection_rects.push_back (rect);
907 TimeAxisView::reshow_selection (TimeSelection& ts)
911 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
912 if (!(*i)->selected () && !(*i)->propagate_time_selection ()) {
915 (*i)->show_selection (ts);
920 TimeAxisView::hide_selection ()
922 if (selection_group->visible ()) {
923 while (!used_selection_rects.empty()) {
924 free_selection_rects.push_front (used_selection_rects.front());
925 used_selection_rects.pop_front();
926 free_selection_rects.front()->rect->hide();
927 free_selection_rects.front()->start_trim->hide();
928 free_selection_rects.front()->end_trim->hide();
930 selection_group->hide();
933 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
934 (*i)->hide_selection ();
939 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
941 /* find the selection rect this is for. we have the item corresponding to one
945 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
946 if ((*i)->start_trim == item || (*i)->end_trim == item) {
948 /* make one trim handle be "above" the other so that if they overlap,
949 the top one is the one last used.
952 (*i)->rect->raise_to_top ();
953 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
954 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
961 // retuned rect is pushed back into the used_selection_rects list
962 // in TimeAxisView::show_selection() which is the only caller.
964 TimeAxisView::get_selection_rect (uint32_t id)
968 /* check to see if we already have a visible rect for this particular selection ID */
970 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
971 if ((*i)->id == id) {
972 SelectionRect* ret = (*i);
973 used_selection_rects.erase (i);
978 /* ditto for the free rect list */
980 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
981 if ((*i)->id == id) {
982 SelectionRect* ret = (*i);
983 free_selection_rects.erase (i);
988 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
990 if (free_selection_rects.empty()) {
992 rect = new SelectionRect;
994 rect->rect = new ArdourCanvas::Rectangle (selection_group);
995 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
996 rect->rect->set_outline (false);
997 rect->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
999 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
1000 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
1001 rect->start_trim->set_outline (false);
1002 rect->start_trim->set_fill (false);
1004 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
1005 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
1006 rect->end_trim->set_outline (false);
1007 rect->end_trim->set_fill (false);
1009 free_selection_rects.push_front (rect);
1011 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
1012 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
1013 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
1016 rect = free_selection_rects.front();
1018 free_selection_rects.pop_front();
1022 struct null_deleter { void operator()(void const *) const {} };
1025 TimeAxisView::is_child (TimeAxisView* tav)
1027 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1031 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1033 children.push_back (child);
1037 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1039 Children::iterator i;
1041 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1046 /** Get selectable things within a given range.
1047 * @param start Start time in session samples.
1048 * @param end End time in session samples.
1049 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1050 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1051 * @param result Filled in with selectable things.
1054 TimeAxisView::get_selectables (samplepos_t start, samplepos_t end, double top, double bot, list<Selectable*>& results, bool within)
1056 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1057 if (!(*i)->hidden()) {
1058 (*i)->get_selectables (start, end, top, bot, results, within);
1064 TimeAxisView::set_selected_points (PointSelection& points)
1066 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1067 (*i)->set_selected_points (points);
1072 TimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& results)
1074 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1075 if (!(*i)->hidden()) {
1076 (*i)->get_inverted_selectables (sel, results);
1082 TimeAxisView::add_ghost (RegionView* rv)
1084 GhostRegion* gr = rv->add_ghost (*this);
1087 ghosts.push_back(gr);
1092 TimeAxisView::remove_ghost (RegionView* rv)
1094 rv->remove_ghost_in (*this);
1098 TimeAxisView::erase_ghost (GhostRegion* gr)
1100 if (in_destructor) {
1104 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1113 TimeAxisView::touched (double top, double bot)
1115 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1116 y_position is the "origin" or "top" of the track.
1119 double mybot = _y_position + current_height();
1121 return ((_y_position <= bot && _y_position >= top) ||
1122 ((mybot <= bot) && (top < mybot)) ||
1123 (mybot >= bot && _y_position < top));
1127 TimeAxisView::set_parent (TimeAxisView& p)
1133 TimeAxisView::reset_height ()
1135 set_height (height);
1137 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1138 (*i)->set_height ((*i)->height);
1143 TimeAxisView::compute_heights ()
1145 // TODO this function should be re-evaluated when font-scaling changes (!)
1146 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1147 Gtk::Table one_row_table (1, 1);
1148 ArdourButton* test_button = manage (new ArdourButton);
1149 const int border_width = 2;
1150 const int frame_height = 2;
1151 extra_height = (2 * border_width) + frame_height;
1153 window.add (one_row_table);
1154 test_button->set_name ("mute button");
1155 test_button->set_text (S_("Mute|M"));
1156 test_button->set_tweaks (ArdourButton::TrackHeader);
1158 one_row_table.set_border_width (border_width);
1159 one_row_table.set_row_spacings (2);
1160 one_row_table.set_col_spacings (2);
1162 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1163 one_row_table.show_all ();
1165 Gtk::Requisition req(one_row_table.size_request ());
1166 button_height = req.height;
1170 TimeAxisView::color_handler ()
1172 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1176 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1178 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1179 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1181 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1182 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1184 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1185 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1188 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1190 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1191 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1193 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1194 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1196 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1197 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1202 TimeAxisView::parameter_changed (string const & what_changed)
1204 if (what_changed == "vertical-region-gap") {
1206 show_selection (_editor.get_selection().time);
1211 view()->parameter_changed (what_changed);
1215 /** @return Pair: TimeAxisView, layer index.
1216 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1217 * does. @param y is an offset from the top of the trackview area.
1219 * If the covering object is a child axis, then the child is returned.
1220 * TimeAxisView is 0 otherwise.
1222 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1223 * and is in stacked or expanded * region display mode, otherwise 0.
1225 std::pair<TimeAxisView*, double>
1226 TimeAxisView::covers_y_position (double y) const
1229 return std::make_pair ((TimeAxisView *) 0, 0);
1232 if (_y_position <= y && y < (_y_position + height)) {
1234 /* work out the layer index if appropriate */
1236 switch (layer_display ()) {
1242 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1243 /* clamp to max layers to be on the safe side; sometimes the above calculation
1244 returns a too-high value */
1245 if (l >= view()->layers ()) {
1246 l = view()->layers() - 1;
1252 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1254 if (l >= (view()->layers() - 0.5)) {
1255 l = view()->layers() - 0.5;
1261 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1264 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1266 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1272 return std::make_pair ((TimeAxisView *) 0, 0);
1276 TimeAxisView::covered_by_y_range (double y0, double y1) const
1282 /* if either the top or bottom of the axisview is in the vertical
1283 * range, we cover it.
1286 if ((y0 < _y_position && y1 < _y_position) ||
1287 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1291 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1292 if ((*i)->covered_by_y_range (y0, y1)) {
1301 TimeAxisView::preset_height (Height h)
1305 return (button_height * 2) + extra_height + 260;
1307 return (button_height * 2) + extra_height + 160;
1309 return (button_height * 2) + extra_height + 60;
1311 return (button_height * 2) + extra_height + 10;
1313 return button_height + extra_height;
1316 abort(); /* NOTREACHED */
1320 /** @return Child time axis views that are not hidden */
1321 TimeAxisView::Children
1322 TimeAxisView::get_child_list () const
1326 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1327 if (!(*i)->hidden()) {
1336 TimeAxisView::build_size_menu ()
1338 if (_size_menu && _size_menu->gobj ()) {
1344 using namespace Menu_Helpers;
1346 _size_menu = new Menu;
1347 _size_menu->set_name ("ArdourContextMenu");
1348 MenuList& items = _size_menu->items();
1350 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1351 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1352 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1353 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1354 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1358 TimeAxisView::reset_visual_state ()
1360 /* this method is not required to trigger a global redraw */
1363 if (get_gui_property ("height", height)) {
1364 set_height (height);
1366 set_height (preset_height (HeightNormal));
1371 TrackViewList::filter_to_unique_playlists ()
1373 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1376 for (iterator i = begin(); i != end(); ++i) {
1377 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1379 /* not a route: include it anyway */
1382 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1384 if (playlists.insert (t->playlist()).second) {
1385 /* playlist not seen yet */
1389 /* not a track: include it anyway */