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 in_destructor = true;
239 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
243 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
244 delete (*i)->rect; (*i)->rect=0;
245 delete (*i)->start_trim; (*i)->start_trim = 0;
246 delete (*i)->end_trim; (*i)->end_trim = 0;
250 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
251 delete (*i)->rect; (*i)->rect = 0;
252 delete (*i)->start_trim; (*i)->start_trim = 0;
253 delete (*i)->end_trim; (*i)->end_trim = 0;
256 delete selection_group;
259 delete _canvas_display;
269 TimeAxisView::hide ()
275 _canvas_display->hide ();
276 _canvas_separator->hide ();
278 if (control_parent) {
279 control_parent->remove (TOP_LEVEL_WIDGET);
286 /* now hide children */
288 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
292 /* if its hidden, it cannot be selected */
293 _editor.get_selection().remove (this);
294 /* and neither can its regions */
295 _editor.get_selection().remove_regions (this);
300 /** Display this TimeAxisView as the nth component of the parent box, at y.
302 * @param y y position.
303 * @param nth index for this TimeAxisView, increased if this view has children.
304 * @param parent parent component.
306 * @return height of this TimeAxisView.
309 TimeAxisView::show_at (double y, int& nth, VBox *parent)
311 if (control_parent) {
312 control_parent->reorder_child (TOP_LEVEL_WIDGET, nth);
314 control_parent = parent;
315 parent->pack_start (TOP_LEVEL_WIDGET, false, false);
316 parent->reorder_child (TOP_LEVEL_WIDGET, nth);
321 if (_y_position != y) {
322 _canvas_display->set_y_position (y);
326 _canvas_display->raise_to_top ();
327 _canvas_display->show ();
331 _effective_height = current_height ();
333 /* now show relevant children */
335 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
336 if ((*i)->marked_for_display()) {
338 _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
344 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
348 /* put separator at the bottom of this time axis view */
350 _canvas_separator->set (ArdourCanvas::Duple(0, height), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, height));
351 _canvas_separator->lower_to_bottom ();
352 _canvas_separator->show ();
354 return _effective_height;
358 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
360 switch (ev->direction) {
362 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
363 /* See Editor::_stepping_axis_view for notes on this hack */
364 Editor& e = dynamic_cast<Editor&> (_editor);
365 if (!e.stepping_axis_view ()) {
366 e.set_stepping_axis_view (this);
368 e.stepping_axis_view()->step_height (false);
373 case GDK_SCROLL_DOWN:
374 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
375 /* See Editor::_stepping_axis_view for notes on this hack */
376 Editor& e = dynamic_cast<Editor&> (_editor);
377 if (!e.stepping_axis_view ()) {
378 e.set_stepping_axis_view (this);
380 e.stepping_axis_view()->step_height (true);
386 /* no handling for left/right, yet */
390 /* Just forward to the normal canvas scroll method. The coordinate
391 systems are different but since the canvas is always larger than the
392 track headers, and aligned with the trackview area, this will work.
394 In the not too distant future this layout is going away anyway and
395 headers will be on the canvas.
397 return _editor.canvas_scroll_event (ev, false);
401 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
403 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
404 /* see if it is inside the name label */
405 if (name_label.is_ancestor (controls_ebox)) {
408 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
409 Gtk::Allocation a = name_label.get_allocation ();
410 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
412 _ebox_release_can_act = false;
419 _ebox_release_can_act = true;
421 if (maybe_set_cursor (event->y) > 0) {
422 _resize_drag_start = event->y_root;
429 TimeAxisView::idle_resize (int32_t h)
431 set_height (std::max(0, h));
436 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
438 if (_resize_drag_start >= 0) {
440 /* (ab)use the DragManager to do autoscrolling - basically we
441 * are pretending that the drag is taking place over the canvas
442 * (which perhaps in the glorious future, when track headers
443 * and the canvas are unified, will actually be true.)
446 _editor.maybe_autoscroll (false, true, true);
448 /* now schedule the actual TAV resize */
449 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
450 _editor.add_to_idle_resize (this, delta);
451 _resize_drag_start = ev->y_root;
454 /* not dragging but ... */
455 maybe_set_cursor (ev->y);
458 gdk_event_request_motions(ev);
463 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
465 if (_have_preresize_cursor) {
466 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
467 _have_preresize_cursor = false;
473 TimeAxisView::maybe_set_cursor (int y)
475 /* XXX no Gtkmm Gdk::Window::get_cursor() */
476 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
478 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
480 /* y-coordinate in lower 25% */
482 if (!_have_preresize_cursor) {
483 _preresize_cursor = gdk_window_get_cursor (win->gobj());
484 _have_preresize_cursor = true;
485 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
490 } else if (_have_preresize_cursor) {
491 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
492 _have_preresize_cursor = false;
501 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
503 if (_resize_drag_start >= 0) {
504 if (_have_preresize_cursor) {
505 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
506 _preresize_cursor = 0;
507 _have_preresize_cursor = false;
509 _editor.stop_canvas_autoscroll ();
510 _resize_drag_start = -1;
513 // don't change selection
518 if (!_ebox_release_can_act) {
522 switch (ev->button) {
525 selection_click (ev);
530 popup_display_menu (ev->time);
538 TimeAxisView::selection_click (GdkEventButton* ev)
540 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
541 _editor.set_selected_track (*this, op, false);
545 /** Steps through the defined heights for this TrackView.
546 * @param coarser true if stepping should decrease in size, otherwise false.
549 TimeAxisView::step_height (bool coarser)
551 static const uint32_t step = 25;
555 if (height <= preset_height (HeightSmall)) {
557 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
558 set_height_enum (HeightSmall);
560 set_height (height - step);
565 if (height <= preset_height(HeightSmall)) {
566 set_height_enum (HeightNormal);
568 set_height (height + step);
575 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
577 if (apply_to_selection) {
578 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
580 set_height (preset_height (h));
585 TimeAxisView::set_height (uint32_t h, TrackHeightMode m)
588 if (m == TotalHeight) {
589 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
590 if (!(*i)->hidden()) {
597 if (h < preset_height (HeightSmall)) {
598 h = preset_height (HeightSmall);
601 TOP_LEVEL_WIDGET.property_height_request () = h;
604 set_gui_property ("height", height);
606 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
610 if (selection_group->visible ()) {
611 /* resize the selection rect */
612 show_selection (_editor.get_selection().time);
616 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
617 (*i)->set_height(h, OnlySelf);
621 _editor.override_visible_track_count ();
625 TimeAxisView::begin_name_edit ()
627 if (!can_edit_name()) {
631 Gtk::Window* toplevel = (Gtk::Window*) control_parent->get_toplevel();
632 FloatingTextEntry* fte = new FloatingTextEntry (toplevel, name ());
634 fte->set_name ("TrackNameEditor");
635 fte->use_text.connect (sigc::mem_fun (*this, &TimeAxisView::end_name_edit));
637 /* We want to new toplevel window to overlay the name label, so
638 * translate the coordinates of the upper left corner of the name label
639 * into the coordinate space of the top level window.
645 name_label.translate_coordinates (*toplevel, 0, 0, x, y);
646 toplevel->get_window()->get_origin (wx, wy);
648 fte->move (wx + x, wy + y);
653 TimeAxisView::end_name_edit (std::string str, int next_dir)
655 if (!name_entry_changed (str)) {
661 TrackViewList const & allviews = _editor.get_track_views ();
662 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
664 if (i != allviews.end()) {
667 if (++i == allviews.end()) {
671 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
673 if (rtav && rtav->is_track() && rtav->track()->rec_enable_control()->get_value()) {
677 if (!(*i)->hidden()) {
684 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
685 _editor.ensure_time_axis_view_is_visible (**i, false);
686 (*i)->begin_name_edit ();
689 } else if (next_dir < 0) {
691 TrackViewList const & allviews = _editor.get_track_views ();
692 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
694 if (i != allviews.begin()) {
696 if (i == allviews.begin()) {
702 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
704 if (rtav && rtav->is_track() && rtav->track()->rec_enable_control()->get_value()) {
708 if (!(*i)->hidden()) {
715 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
716 _editor.ensure_time_axis_view_is_visible (**i, false);
717 (*i)->begin_name_edit ();
723 TimeAxisView::name_entry_changed (string const&)
729 TimeAxisView::can_edit_name () const
735 TimeAxisView::conditionally_add_to_selection ()
741 Selection& s (_editor.get_selection ());
743 if (!s.selected (this)) {
744 _editor.set_selected_track (*this, Selection::Set);
749 TimeAxisView::popup_display_menu (guint32 when)
751 conditionally_add_to_selection ();
753 build_display_menu ();
755 if (!display_menu->items().empty()) {
756 display_menu->popup (1, when);
761 TimeAxisView::set_selected (bool yn)
763 if (yn == selected()) {
767 AxisView::set_selected (yn);
770 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
771 time_axis_frame.set_name ("MixerStripSelectedFrame");
772 controls_ebox.set_name (controls_base_selected_name);
773 controls_vbox.set_name (controls_base_selected_name);
774 time_axis_vbox.set_name (controls_base_selected_name);
776 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
777 time_axis_frame.set_name (controls_base_unselected_name);
778 controls_ebox.set_name (controls_base_unselected_name);
779 controls_vbox.set_name (controls_base_unselected_name);
780 time_axis_vbox.set_name (controls_base_unselected_name);
785 time_axis_frame.show();
789 TimeAxisView::build_display_menu ()
791 using namespace Menu_Helpers;
795 display_menu = new Menu;
796 display_menu->set_name ("ArdourContextMenu");
798 // Just let implementing classes define what goes into the manu
802 TimeAxisView::set_samples_per_pixel (double fpp)
804 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
805 (*i)->set_samples_per_pixel (fpp);
810 TimeAxisView::show_timestretch (samplepos_t start, samplepos_t end, int layers, int layer)
812 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
813 (*i)->show_timestretch (start, end, layers, layer);
818 TimeAxisView::hide_timestretch ()
820 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
821 (*i)->hide_timestretch ();
826 TimeAxisView::show_selection (TimeSelection& ts)
833 time_axis_frame.show();
835 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
836 if (!(*i)->selected () && !(*i)->propagate_time_selection ()) {
839 (*i)->show_selection (ts);
842 if (selection_group->visible ()) {
843 while (!used_selection_rects.empty()) {
844 free_selection_rects.push_front (used_selection_rects.front());
845 used_selection_rects.pop_front();
846 free_selection_rects.front()->rect->hide();
847 free_selection_rects.front()->start_trim->hide();
848 free_selection_rects.front()->end_trim->hide();
850 selection_group->hide();
853 selection_group->show();
854 selection_group->raise_to_top();
856 uint32_t gap = UIConfiguration::instance().get_vertical_region_gap ();
857 float ui_scale = UIConfiguration::instance().get_ui_scale ();
858 if (gap > 0 && ui_scale > 0) {
859 gap = ceil (gap * ui_scale);
862 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
863 samplepos_t start, end;
868 cnt = end - start + 1;
870 rect = get_selection_rect ((*i).id);
872 x1 = _editor.sample_to_pixel (start);
873 x2 = _editor.sample_to_pixel (start + cnt - 1);
874 y2 = current_height() - 1;
876 if (dynamic_cast<AudioTimeAxisView*>(this)) {
884 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
886 // trim boxes are at the top for selections
889 rect->start_trim->set (ArdourCanvas::Rect (x1, 0, x1 + trim_handle_size, y2));
890 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
892 rect->start_trim->show();
893 rect->end_trim->show();
895 rect->start_trim->hide();
896 rect->end_trim->hide();
900 used_selection_rects.push_back (rect);
905 TimeAxisView::reshow_selection (TimeSelection& ts)
909 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
910 if (!(*i)->selected () && !(*i)->propagate_time_selection ()) {
913 (*i)->show_selection (ts);
918 TimeAxisView::hide_selection ()
920 if (selection_group->visible ()) {
921 while (!used_selection_rects.empty()) {
922 free_selection_rects.push_front (used_selection_rects.front());
923 used_selection_rects.pop_front();
924 free_selection_rects.front()->rect->hide();
925 free_selection_rects.front()->start_trim->hide();
926 free_selection_rects.front()->end_trim->hide();
928 selection_group->hide();
931 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
932 (*i)->hide_selection ();
937 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
939 /* find the selection rect this is for. we have the item corresponding to one
943 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
944 if ((*i)->start_trim == item || (*i)->end_trim == item) {
946 /* make one trim handle be "above" the other so that if they overlap,
947 the top one is the one last used.
950 (*i)->rect->raise_to_top ();
951 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
952 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
959 // retuned rect is pushed back into the used_selection_rects list
960 // in TimeAxisView::show_selection() which is the only caller.
962 TimeAxisView::get_selection_rect (uint32_t id)
966 /* check to see if we already have a visible rect for this particular selection ID */
968 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
969 if ((*i)->id == id) {
970 SelectionRect* ret = (*i);
971 used_selection_rects.erase (i);
976 /* ditto for the free rect list */
978 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
979 if ((*i)->id == id) {
980 SelectionRect* ret = (*i);
981 free_selection_rects.erase (i);
986 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
988 if (free_selection_rects.empty()) {
990 rect = new SelectionRect;
992 rect->rect = new ArdourCanvas::Rectangle (selection_group);
993 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
994 rect->rect->set_outline (false);
995 rect->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
997 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
998 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
999 rect->start_trim->set_outline (false);
1000 rect->start_trim->set_fill (false);
1002 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
1003 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
1004 rect->end_trim->set_outline (false);
1005 rect->end_trim->set_fill (false);
1007 free_selection_rects.push_front (rect);
1009 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
1010 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
1011 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
1014 rect = free_selection_rects.front();
1016 free_selection_rects.pop_front();
1020 struct null_deleter { void operator()(void const *) const {} };
1023 TimeAxisView::is_child (TimeAxisView* tav)
1025 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1029 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1031 children.push_back (child);
1035 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1037 Children::iterator i;
1039 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1044 /** Get selectable things within a given range.
1045 * @param start Start time in session samples.
1046 * @param end End time in session samples.
1047 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1048 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1049 * @param result Filled in with selectable things.
1052 TimeAxisView::get_selectables (samplepos_t start, samplepos_t end, double top, double bot, list<Selectable*>& results, bool within)
1054 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1055 if (!(*i)->hidden()) {
1056 (*i)->get_selectables (start, end, top, bot, results, within);
1062 TimeAxisView::set_selected_points (PointSelection& points)
1064 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1065 (*i)->set_selected_points (points);
1070 TimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& results)
1072 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1073 if (!(*i)->hidden()) {
1074 (*i)->get_inverted_selectables (sel, results);
1080 TimeAxisView::add_ghost (RegionView* rv)
1082 GhostRegion* gr = rv->add_ghost (*this);
1085 ghosts.push_back(gr);
1090 TimeAxisView::remove_ghost (RegionView* rv)
1092 rv->remove_ghost_in (*this);
1096 TimeAxisView::erase_ghost (GhostRegion* gr)
1098 if (in_destructor) {
1102 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1111 TimeAxisView::touched (double top, double bot)
1113 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1114 y_position is the "origin" or "top" of the track.
1117 double mybot = _y_position + current_height();
1119 return ((_y_position <= bot && _y_position >= top) ||
1120 ((mybot <= bot) && (top < mybot)) ||
1121 (mybot >= bot && _y_position < top));
1125 TimeAxisView::set_parent (TimeAxisView& p)
1131 TimeAxisView::reset_height ()
1133 set_height (height);
1135 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1136 (*i)->set_height ((*i)->height);
1141 TimeAxisView::compute_heights ()
1143 // TODO this function should be re-evaluated when font-scaling changes (!)
1144 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1145 Gtk::Table one_row_table (1, 1);
1146 ArdourButton* test_button = manage (new ArdourButton);
1147 const int border_width = 2;
1148 const int frame_height = 2;
1149 extra_height = (2 * border_width) + frame_height;
1151 window.add (one_row_table);
1152 test_button->set_name ("mute button");
1153 test_button->set_text (S_("Mute|M"));
1154 test_button->set_tweaks (ArdourButton::TrackHeader);
1156 one_row_table.set_border_width (border_width);
1157 one_row_table.set_row_spacings (2);
1158 one_row_table.set_col_spacings (2);
1160 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1161 one_row_table.show_all ();
1163 Gtk::Requisition req(one_row_table.size_request ());
1164 button_height = req.height;
1168 TimeAxisView::color_handler ()
1170 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1174 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1176 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1177 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1179 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1180 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1182 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1183 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1186 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1188 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1189 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1191 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1192 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1194 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1195 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1200 TimeAxisView::parameter_changed (string const & what_changed)
1202 if (what_changed == "vertical-region-gap") {
1204 show_selection (_editor.get_selection().time);
1209 view()->parameter_changed (what_changed);
1213 /** @return Pair: TimeAxisView, layer index.
1214 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1215 * does. @param y is an offset from the top of the trackview area.
1217 * If the covering object is a child axis, then the child is returned.
1218 * TimeAxisView is 0 otherwise.
1220 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1221 * and is in stacked or expanded * region display mode, otherwise 0.
1223 std::pair<TimeAxisView*, double>
1224 TimeAxisView::covers_y_position (double y) const
1227 return std::make_pair ((TimeAxisView *) 0, 0);
1230 if (_y_position <= y && y < (_y_position + height)) {
1232 /* work out the layer index if appropriate */
1234 switch (layer_display ()) {
1240 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1241 /* clamp to max layers to be on the safe side; sometimes the above calculation
1242 returns a too-high value */
1243 if (l >= view()->layers ()) {
1244 l = view()->layers() - 1;
1250 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1252 if (l >= (view()->layers() - 0.5)) {
1253 l = view()->layers() - 0.5;
1259 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1262 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1264 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1270 return std::make_pair ((TimeAxisView *) 0, 0);
1274 TimeAxisView::covered_by_y_range (double y0, double y1) const
1280 /* if either the top or bottom of the axisview is in the vertical
1281 * range, we cover it.
1284 if ((y0 < _y_position && y1 < _y_position) ||
1285 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1289 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1290 if ((*i)->covered_by_y_range (y0, y1)) {
1299 TimeAxisView::preset_height (Height h)
1303 return (button_height * 2) + extra_height + 260;
1305 return (button_height * 2) + extra_height + 160;
1307 return (button_height * 2) + extra_height + 60;
1309 return (button_height * 2) + extra_height + 10;
1311 return button_height + extra_height;
1314 abort(); /* NOTREACHED */
1318 /** @return Child time axis views that are not hidden */
1319 TimeAxisView::Children
1320 TimeAxisView::get_child_list () const
1324 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1325 if (!(*i)->hidden()) {
1334 TimeAxisView::build_size_menu ()
1336 if (_size_menu && _size_menu->gobj ()) {
1342 using namespace Menu_Helpers;
1344 _size_menu = new Menu;
1345 _size_menu->set_name ("ArdourContextMenu");
1346 MenuList& items = _size_menu->items();
1348 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1349 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1350 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1351 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1352 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1356 TimeAxisView::reset_visual_state ()
1358 /* this method is not required to trigger a global redraw */
1361 if (get_gui_property ("height", height)) {
1362 set_height (height);
1364 set_height (preset_height (HeightNormal));
1369 TrackViewList::filter_to_unique_playlists ()
1371 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1374 for (iterator i = begin(); i != end(); ++i) {
1375 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1377 /* not a route: include it anyway */
1380 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1382 if (playlists.insert (t->playlist()).second) {
1383 /* playlist not seen yet */
1387 /* not a track: include it anyway */