1 /*
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>
11  *
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.
16  *
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.
21  *
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.
25  */
26 
27 #include <cstdlib>
28 #include <cmath>
29 #include <algorithm>
30 #include <string>
31 #include <list>
32 
33 #include <boost/smart_ptr/scoped_ptr.hpp>
34 
35 #include <gtkmm/separator.h>
36 
37 #include "pbd/error.h"
38 #include "pbd/convert.h"
39 #include "pbd/unwind.h"
40 
41 #include "ardour/profile.h"
42 
43 #include "gtkmm2ext/colors.h"
44 #include "gtkmm2ext/doi.h"
45 #include "gtkmm2ext/utils.h"
46 
47 #include "canvas/canvas.h"
48 #include "canvas/rectangle.h"
49 #include "canvas/debug.h"
50 #include "canvas/utils.h"
51 
52 #include "widgets/tooltips.h"
53 
54 #include "ardour_dialog.h"
55 #include "audio_time_axis.h"
56 #include "floating_text_entry.h"
57 #include "gui_thread.h"
58 #include "public_editor.h"
59 #include "time_axis_view.h"
60 #include "region_view.h"
61 #include "ghostregion.h"
62 #include "selection.h"
63 #include "keyboard.h"
64 #include "rgb_macros.h"
65 #include "utils.h"
66 #include "streamview.h"
67 #include "editor_drag.h"
68 #include "editor.h"
69 #include "ui_config.h"
70 
71 #include "pbd/i18n.h"
72 
73 using namespace std;
74 using namespace Gtk;
75 using namespace Gdk;
76 using namespace ARDOUR;
77 using namespace PBD;
78 using namespace Editing;
79 using namespace ArdourCanvas;
80 using namespace ArdourWidgets;
81 using Gtkmm2ext::Keyboard;
82 
83 #define TOP_LEVEL_WIDGET controls_ebox
84 
85 const double trim_handle_size = 6.0; /* pixels */
86 uint32_t TimeAxisView::button_height = 0;
87 uint32_t TimeAxisView::extra_height = 0;
88 int const TimeAxisView::_max_order = 512;
89 unsigned int TimeAxisView::name_width_px = 100;
90 PBD::Signal1<void,TimeAxisView*> TimeAxisView::CatchDeletion;
91 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::controls_meters_size_group = Glib::RefPtr<Gtk::SizeGroup>();
92 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::midi_scroomer_size_group = Glib::RefPtr<Gtk::SizeGroup>();
93 
94 void
setup_sizes()95 TimeAxisView::setup_sizes()
96 {
97 	name_width_px = ceilf (100.f * UIConfiguration::instance().get_ui_scale());
98 }
99 
TimeAxisView(ARDOUR::Session * sess,PublicEditor & ed,TimeAxisView * rent,Canvas &)100 TimeAxisView::TimeAxisView (ARDOUR::Session* sess, PublicEditor& ed, TimeAxisView* rent, Canvas& /*canvas*/)
101 	: controls_table (5, 4)
102 	, controls_button_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
103 	, _name_editing (false)
104 	, height (0)
105 	, display_menu (0)
106 	, parent (rent)
107 	, selection_group (0)
108 	, _ghost_group (0)
109 	, _hidden (true)
110 	, in_destructor (false)
111 	, _size_menu (0)
112 	, _canvas_display (0)
113 	, _y_position (0)
114 	, _editor (ed)
115 	, control_parent (0)
116 	, _order (0)
117 	, _effective_height (0)
118 	, _resize_drag_start (-1)
119 	, _did_resize (false)
120 	, _preresize_cursor (0)
121 	, _have_preresize_cursor (false)
122 	, _ebox_release_can_act (true)
123 {
124 	if (!controls_meters_size_group) {
125 		controls_meters_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
126 	}
127 	if (!midi_scroomer_size_group) {
128 		midi_scroomer_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
129 	}
130 	if (extra_height == 0) {
131 		compute_heights ();
132 	}
133 
134 	_canvas_display = new ArdourCanvas::Container (ed.get_trackview_group ());
135 	CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
136 	_canvas_display->hide(); // reveal as needed
137 
138 	_canvas_separator = new ArdourCanvas::Line(_canvas_display);
139 	CANVAS_DEBUG_NAME (_canvas_separator, "separator for TAV");
140 	_canvas_separator->set (ArdourCanvas::Duple(0.0, 0.0), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, 0.0));
141 	_canvas_separator->set_outline_color(Gtkmm2ext::rgba_to_color (0, 0, 0, 1.0));
142 	_canvas_separator->set_outline_width(1.0);
143 	_canvas_separator->hide();
144 
145 	selection_group = new ArdourCanvas::Container (_canvas_display);
146 	CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
147 	selection_group->set_data (X_("timeselection"), (void *) 1);
148 	selection_group->hide();
149 
150 	_ghost_group = new ArdourCanvas::Container (_canvas_display);
151 	CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
152 	_ghost_group->lower_to_bottom();
153 	_ghost_group->show();
154 
155 	name_label.set_name (X_("TrackNameEditor"));
156 	name_label.set_alignment (0.0, 0.5);
157 	name_label.set_width_chars (12);
158 	set_tooltip (name_label, _("Track/Bus name (double click to edit)"));
159 
160 	inactive_label.set_name (X_("TrackNameEditor"));
161 	inactive_label.set_alignment (0.0, 0.5);
162 	set_tooltip (inactive_label, _("This track is inactive. (right-click to activate)"));
163 
164 	{
165 		boost::scoped_ptr<Gtk::Entry> an_entry (new FocusEntry);
166 		an_entry->set_name (X_("TrackNameEditor"));
167 		Gtk::Requisition req = an_entry->size_request ();
168 
169 		name_label.set_size_request (-1, req.height);
170 		set_name_ellipsize_mode ();
171 	}
172 
173 	// set min. track-header width if fader is not visible
174 	name_label.set_size_request(name_width_px, -1);
175 
176 	name_label.show ();
177 	inactive_label.show ();
178 
179 	controls_table.set_row_spacings (2);
180 	controls_table.set_col_spacings (2);
181 	controls_table.set_border_width (2);
182 
183 	if (ARDOUR::Profile->get_mixbus() ) {
184 		controls_table.attach (name_label, 4, 5, 0, 1,  Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
185 	} else {
186 		controls_table.attach (name_label, 1, 2, 0, 1,  Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
187 	}
188 
189 	controls_table.show_all ();
190 	controls_table.set_no_show_all ();
191 
192 	inactive_table.set_no_show_all ();
193 	inactive_table.set_border_width (4);  //try to match the offset of the label on an "active" track
194 	inactive_table.attach (inactive_label, 1, 2, 0, 1,  Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
195 	controls_vbox.pack_start (inactive_table, false, false);
196 
197 	controls_vbox.pack_start (controls_table, false, false);
198 	controls_vbox.show ();
199 
200 	top_hbox.pack_start (controls_vbox, true, true);
201 	top_hbox.show ();
202 
203 	controls_ebox.add (time_axis_hbox);
204 	controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
205 				  Gdk::BUTTON_RELEASE_MASK|
206 				  Gdk::POINTER_MOTION_MASK|
207 				  Gdk::ENTER_NOTIFY_MASK|
208 				  Gdk::LEAVE_NOTIFY_MASK|
209 				  Gdk::SCROLL_MASK);
210 	controls_ebox.set_flags (CAN_FOCUS);
211 
212 	/* note that this handler connects *before* the default handler */
213 	controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
214 	controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
215 	controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
216 	controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
217 	controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
218 	controls_ebox.show ();
219 
220 	time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
221 	time_axis_frame.add(top_hbox);
222 	time_axis_frame.show();
223 
224 	HSeparator* separator = manage (new HSeparator());
225 	separator->set_name("TrackSeparator");
226 	separator->set_size_request(-1, 1);
227 	separator->show();
228 
229 	scroomer_placeholder.set_size_request (-1, -1);
230 	scroomer_placeholder.show();
231 	midi_scroomer_size_group->add_widget (scroomer_placeholder);
232 
233 	time_axis_vbox.pack_start (*separator, false, false);
234 	time_axis_vbox.pack_start (time_axis_frame, true, true);
235 	time_axis_vbox.show();
236 	time_axis_hbox.pack_start (time_axis_vbox, true, true);
237 	time_axis_hbox.show();
238 	top_hbox.pack_start (scroomer_placeholder, false, false); // OR pack_end to move after meters ?
239 
240 	UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
241 	UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &TimeAxisView::parameter_changed));
242 }
243 
~TimeAxisView()244 TimeAxisView::~TimeAxisView()
245 {
246 	in_destructor = true;
247 
248 	for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
249 		delete *i;
250 	}
251 
252 	for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_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;
256 
257 	}
258 
259 	for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
260 		delete (*i)->rect; (*i)->rect = 0;
261 		delete (*i)->start_trim; (*i)->start_trim = 0;
262 		delete (*i)->end_trim; (*i)->end_trim = 0;
263 	}
264 
265 	delete selection_group;
266 	selection_group = 0;
267 
268 	delete _canvas_display;
269 	_canvas_display = 0;
270 
271 	delete display_menu;
272 	display_menu = 0;
273 
274 	delete _size_menu;
275 }
276 
277 void
hide()278 TimeAxisView::hide ()
279 {
280 	if (_hidden) {
281 		return;
282 	}
283 
284 	_canvas_display->hide ();
285 	_canvas_separator->hide ();
286 
287 	if (control_parent) {
288 		control_parent->remove (TOP_LEVEL_WIDGET);
289 		control_parent = 0;
290 	}
291 
292 	_y_position = -1;
293 	_hidden = true;
294 
295 	/* now hide children */
296 
297 	for (Children::iterator i = children.begin(); i != children.end(); ++i) {
298 		(*i)->hide ();
299 	}
300 
301 	/* if its hidden, it cannot be selected */
302 	_editor.get_selection().remove (this);
303 	/* and neither can its regions */
304 	_editor.get_selection().remove_regions (this);
305 
306 	Hiding ();
307 }
308 
309 /** Display this TimeAxisView as the nth component of the parent box, at y.
310 *
311 * @param y y position.
312 * @param nth index for this TimeAxisView, increased if this view has children.
313 * @param parent parent component.
314 *
315 * @return height of this TimeAxisView.
316 */
317 guint32
show_at(double y,int & nth,VBox * parent)318 TimeAxisView::show_at (double y, int& nth, VBox *parent)
319 {
320 	if (control_parent) {
321 		control_parent->reorder_child (TOP_LEVEL_WIDGET, nth);
322 	} else {
323 		control_parent = parent;
324 		parent->pack_start (TOP_LEVEL_WIDGET, false, false);
325 		parent->reorder_child (TOP_LEVEL_WIDGET, nth);
326 	}
327 
328 	_order = nth;
329 
330 	if (_y_position != y) {
331 		_canvas_display->set_y_position (y);
332 		_y_position = y;
333 	}
334 
335 	_canvas_display->raise_to_top ();
336 	_canvas_display->show ();
337 
338 	_hidden = false;
339 
340 	_effective_height = current_height ();
341 
342 	/* now show relevant children */
343 
344 	for (Children::iterator i = children.begin(); i != children.end(); ++i) {
345 		if ((*i)->marked_for_display()) {
346 			++nth;
347 			_effective_height += (*i)->show_at (y + _effective_height, nth, parent);
348 		} else {
349 			(*i)->hide ();
350 		}
351 	}
352 
353 	for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
354 		(*i)->set_height ();
355 	}
356 
357 	/* put separator at the bottom of this time axis view */
358 
359 	_canvas_separator->set (ArdourCanvas::Duple(0, height), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, height));
360 	_canvas_separator->lower_to_bottom ();
361 	_canvas_separator->show ();
362 
363 	return _effective_height;
364 }
365 
366 bool
controls_ebox_scroll(GdkEventScroll * ev)367 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
368 {
369 	switch (ev->direction) {
370 	case GDK_SCROLL_UP:
371 		if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
372 			/* See Editor::_stepping_axis_view for notes on this hack */
373 			Editor& e = dynamic_cast<Editor&> (_editor);
374 			if (!e.stepping_axis_view ()) {
375 				e.set_stepping_axis_view (this);
376 			}
377 			e.stepping_axis_view()->step_height (false);
378 			return true;
379 		}
380 		break;
381 
382 	case GDK_SCROLL_DOWN:
383 		if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
384 			/* See Editor::_stepping_axis_view for notes on this hack */
385 			Editor& e = dynamic_cast<Editor&> (_editor);
386 			if (!e.stepping_axis_view ()) {
387 				e.set_stepping_axis_view (this);
388 			}
389 			e.stepping_axis_view()->step_height (true);
390 			return true;
391 		}
392 		break;
393 
394 	default:
395 		/* no handling for left/right, yet */
396 		break;
397 	}
398 
399 	/* Just forward to the normal canvas scroll method. The coordinate
400 	   systems are different but since the canvas is always larger than the
401 	   track headers, and aligned with the trackview area, this will work.
402 
403 	   In the not too distant future this layout is going away anyway and
404 	   headers will be on the canvas.
405 	*/
406 	return _editor.canvas_scroll_event (ev, false);
407 }
408 
409 bool
controls_ebox_button_press(GdkEventButton * event)410 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
411 {
412 	if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
413 		/* see if it is inside the name label */
414 		if (name_label.is_ancestor (controls_ebox)) {
415 			int nlx;
416 			int nly;
417 			controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
418 			Gtk::Allocation a = name_label.get_allocation ();
419 			if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
420 				begin_name_edit ();
421 				_ebox_release_can_act = false;
422 				return true;
423 			}
424 		}
425 
426 	}
427 
428 	if (event->button == 1 && event->type == GDK_2BUTTON_PRESS) {
429 		if (_effective_height < preset_height (HeightLargest)) {
430 			set_height_enum (HeightLargest);
431 		} else {
432 			set_height_enum (HeightNormal);
433 		}
434 	}
435 
436 	_ebox_release_can_act = true;
437 
438 	if (maybe_set_cursor (event->y) > 0) {
439 		_resize_drag_start = event->y_root;
440 	}
441 
442 	return true;
443 }
444 
445 void
idle_resize(int32_t h)446 TimeAxisView::idle_resize (int32_t h)
447 {
448 	set_height (std::max(0, h));
449 }
450 
451 
452 bool
controls_ebox_motion(GdkEventMotion * ev)453 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
454 {
455 	if (_resize_drag_start >= 0) {
456 
457 		/* (ab)use the DragManager to do autoscrolling - basically we
458 		 * are pretending that the drag is taking place over the canvas
459 		 * (which perhaps in the glorious future, when track headers
460 		 * and the canvas are unified, will actually be true.)
461 		 */
462 
463 		_editor.maybe_autoscroll (false, true, true);
464 
465 		/* now schedule the actual TAV resize */
466 		int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
467 		_editor.add_to_idle_resize (this, delta);
468 		_resize_drag_start = ev->y_root;
469 		_did_resize = true;
470 	} else {
471 		/* not dragging but ... */
472 		maybe_set_cursor (ev->y);
473 	}
474 
475 	gdk_event_request_motions(ev);
476 	return true;
477 }
478 
479 bool
controls_ebox_leave(GdkEventCrossing *)480 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
481 {
482 	if (_have_preresize_cursor) {
483 		gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
484 		_have_preresize_cursor = false;
485 	}
486 	return true;
487 }
488 
489 bool
maybe_set_cursor(int y)490 TimeAxisView::maybe_set_cursor (int y)
491 {
492 	/* XXX no Gtkmm Gdk::Window::get_cursor() */
493 	Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
494 
495 	if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
496 
497 		/* y-coordinate in lower 25% */
498 
499 		if (!_have_preresize_cursor) {
500 			_preresize_cursor = gdk_window_get_cursor (win->gobj());
501 			_have_preresize_cursor = true;
502 			win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
503 		}
504 
505 		return 1;
506 
507 	} else if (_have_preresize_cursor) {
508 		gdk_window_set_cursor (win->gobj(), _preresize_cursor);
509 		_have_preresize_cursor = false;
510 
511 		return -1;
512 	}
513 
514 	return 0;
515 }
516 
517 bool
controls_ebox_button_release(GdkEventButton * ev)518 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
519 {
520 	if (_resize_drag_start >= 0) {
521 		if (_have_preresize_cursor) {
522 			gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
523 			_preresize_cursor = 0;
524 			_have_preresize_cursor = false;
525 		}
526 		_editor.stop_canvas_autoscroll ();
527 		_resize_drag_start = -1;
528 		if (_did_resize) {
529 			_did_resize = false;
530 			// don't change selection
531 			return true;
532 		}
533 	}
534 
535 	if (!_ebox_release_can_act) {
536 		return true;
537 	}
538 
539 	switch (ev->button) {
540 	case 1:
541 		if (selectable()) {
542 			selection_click (ev);
543 		}
544 		break;
545 
546 	case 3:
547 		popup_display_menu (ev->time);
548 		break;
549 	}
550 
551 	return true;
552 }
553 
554 void
selection_click(GdkEventButton * ev)555 TimeAxisView::selection_click (GdkEventButton* ev)
556 {
557 	Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
558 	_editor.set_selected_track (*this, op, false);
559 }
560 
561 
562 /** Steps through the defined heights for this TrackView.
563  *  @param coarser true if stepping should decrease in size, otherwise false.
564  */
565 void
step_height(bool coarser)566 TimeAxisView::step_height (bool coarser)
567 {
568 	static const uint32_t step = 25;
569 
570 	if (coarser) {
571 
572 		if (height <= preset_height (HeightSmall)) {
573 			return;
574 		} else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
575 			set_height_enum (HeightSmall);
576 		} else {
577 			set_height (height - step);
578 		}
579 
580 	} else {
581 
582 		if (height <= preset_height(HeightSmall)) {
583 			set_height_enum (HeightNormal);
584 		} else {
585 			set_height (height + step);
586 		}
587 
588 	}
589 }
590 
591 void
set_height_enum(Height h,bool apply_to_selection)592 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
593 {
594 	if (apply_to_selection) {
595 		_editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
596 	} else {
597 		set_height (preset_height (h));
598 	}
599 }
600 
601 void
set_height(uint32_t h,TrackHeightMode m)602 TimeAxisView::set_height (uint32_t h, TrackHeightMode m)
603 {
604 	uint32_t lanes = 0;
605 	if (m == TotalHeight) {
606 		for (Children::iterator i = children.begin(); i != children.end(); ++i) {
607 			if (!(*i)->hidden()) {
608 				++lanes;
609 			}
610 		}
611 	}
612 	h /= (lanes + 1);
613 
614 	if (h < preset_height (HeightSmall)) {
615 		h = preset_height (HeightSmall);
616 	}
617 
618 	TOP_LEVEL_WIDGET.property_height_request () = h;
619 	height = h;
620 
621 	set_gui_property ("height", height);
622 
623 	for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
624 		(*i)->set_height ();
625 	}
626 
627 	if (selection_group->visible ()) {
628 		/* resize the selection rect */
629 		show_selection (_editor.get_selection().time);
630 	}
631 
632 	if (m != OnlySelf) {
633 		for (Children::iterator i = children.begin(); i != children.end(); ++i) {
634 			(*i)->set_height(h, OnlySelf);
635 		}
636 	}
637 
638 	_editor.override_visible_track_count ();
639 }
640 
641 void
begin_name_edit()642 TimeAxisView::begin_name_edit ()
643 {
644 	if (!can_edit_name()) {
645 		return;
646 	}
647 
648 	Gtk::Window* toplevel = (Gtk::Window*) control_parent->get_toplevel();
649 	FloatingTextEntry* fte = new FloatingTextEntry (toplevel, name ());
650 
651 	fte->set_name ("TrackNameEditor");
652 	fte->use_text.connect (sigc::mem_fun (*this, &TimeAxisView::end_name_edit));
653 
654 	/* We want to new toplevel window to overlay the name label, so
655 	 * translate the coordinates of the upper left corner of the name label
656 	 * into the coordinate space of the top level window.
657 	 */
658 
659 	int x, y;
660 	int wx, wy;
661 
662 	name_label.translate_coordinates (*toplevel, 0, 0, x, y);
663 	toplevel->get_window()->get_origin (wx, wy);
664 
665 	fte->move (wx + x, wy + y);
666 	fte->present ();
667 }
668 
669 void
end_name_edit(std::string str,int next_dir)670 TimeAxisView::end_name_edit (std::string str, int next_dir)
671 {
672 	if (!name_entry_changed (str)) {
673 		next_dir = 0;
674 	}
675 
676 	if (next_dir > 0) {
677 
678 		TrackViewList const & allviews = _editor.get_track_views ();
679 		TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
680 
681 		if (i != allviews.end()) {
682 
683 			do {
684 				if (++i == allviews.end()) {
685 					return;
686 				}
687 
688 				RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
689 
690 				if (rtav && rtav->is_track() && rtav->track()->rec_enable_control()->get_value()) {
691 					continue;
692 				}
693 
694 				if (!(*i)->hidden()) {
695 					break;
696 				}
697 
698 			} while (true);
699 		}
700 
701 		if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
702 			_editor.ensure_time_axis_view_is_visible (**i, false);
703 			(*i)->begin_name_edit ();
704 		}
705 
706 	} else if (next_dir < 0) {
707 
708 		TrackViewList const & allviews = _editor.get_track_views ();
709 		TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
710 
711 		if (i != allviews.begin()) {
712 			do {
713 				if (i == allviews.begin()) {
714 					return;
715 				}
716 
717 				--i;
718 
719 				RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
720 
721 				if (rtav && rtav->is_track() && rtav->track()->rec_enable_control()->get_value()) {
722 					continue;
723 				}
724 
725 				if (!(*i)->hidden()) {
726 					break;
727 				}
728 
729 			} while (true);
730 		}
731 
732 		if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
733 			_editor.ensure_time_axis_view_is_visible (**i, false);
734 			(*i)->begin_name_edit ();
735 		}
736 	}
737 }
738 
739 bool
name_entry_changed(string const &)740 TimeAxisView::name_entry_changed (string const&)
741 {
742 	return true;
743 }
744 
745 bool
can_edit_name() const746 TimeAxisView::can_edit_name () const
747 {
748 	return true;
749 }
750 
751 void
conditionally_add_to_selection()752 TimeAxisView::conditionally_add_to_selection ()
753 {
754 	if (!selectable()) {
755 		return;
756 	}
757 
758 	Selection& s (_editor.get_selection ());
759 
760 	if (!s.selected (this)) {
761 		_editor.set_selected_track (*this, Selection::Set);
762 	}
763 }
764 
765 void
popup_display_menu(guint32 when)766 TimeAxisView::popup_display_menu (guint32 when)
767 {
768 	conditionally_add_to_selection ();
769 
770 	build_display_menu ();
771 
772 	if (!display_menu->items().empty()) {
773 		display_menu->popup (1, when);
774 	}
775 }
776 
777 void
set_selected(bool yn)778 TimeAxisView::set_selected (bool yn)
779 {
780 	if (yn == selected()) {
781 		return;
782 	}
783 
784 	AxisView::set_selected (yn);
785 
786 	if (_selected) {
787 		time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
788 		time_axis_frame.set_name ("MixerStripSelectedFrame");
789 		controls_ebox.set_name (controls_base_selected_name);
790 		controls_vbox.set_name (controls_base_selected_name);
791 		time_axis_vbox.set_name (controls_base_selected_name);
792 	} else {
793 		time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
794 		time_axis_frame.set_name (controls_base_unselected_name);
795 		controls_ebox.set_name (controls_base_unselected_name);
796 		controls_vbox.set_name (controls_base_unselected_name);
797 		time_axis_vbox.set_name (controls_base_unselected_name);
798 
799 		hide_selection ();
800 	}
801 
802 	time_axis_frame.show();
803 }
804 
805 void
build_display_menu()806 TimeAxisView::build_display_menu ()
807 {
808 	using namespace Menu_Helpers;
809 
810 	if (_size_menu) {
811 		Gtkmm2ext::detach_menu (*_size_menu);
812 	}
813 
814 	delete display_menu;
815 
816 	display_menu = new Menu;
817 	display_menu->set_name ("ArdourContextMenu");
818 
819 	// Just let implementing classes define what goes into the manu
820 }
821 
822 void
set_samples_per_pixel(double fpp)823 TimeAxisView::set_samples_per_pixel (double fpp)
824 {
825 	for (Children::iterator i = children.begin(); i != children.end(); ++i) {
826 		(*i)->set_samples_per_pixel (fpp);
827 	}
828 }
829 
830 void
show_timestretch(samplepos_t start,samplepos_t end,int layers,int layer)831 TimeAxisView::show_timestretch (samplepos_t start, samplepos_t end, int layers, int layer)
832 {
833 	for (Children::iterator i = children.begin(); i != children.end(); ++i) {
834 		(*i)->show_timestretch (start, end, layers, layer);
835 	}
836 }
837 
838 void
hide_timestretch()839 TimeAxisView::hide_timestretch ()
840 {
841 	for (Children::iterator i = children.begin(); i != children.end(); ++i) {
842 		(*i)->hide_timestretch ();
843 	}
844 }
845 
846 void
show_selection(TimeSelection & ts)847 TimeAxisView::show_selection (TimeSelection& ts)
848 {
849 	double x1;
850 	double x2;
851 	double y2;
852 	SelectionRect *rect;
853 
854 	time_axis_frame.show();
855 
856 	for (Children::iterator i = children.begin(); i != children.end(); ++i) {
857 		if (!(*i)->selected () && !(*i)->propagate_time_selection ()) {
858 			continue;
859 		}
860 		(*i)->show_selection (ts);
861 	}
862 
863 	if (selection_group->visible ()) {
864 		while (!used_selection_rects.empty()) {
865 			free_selection_rects.push_front (used_selection_rects.front());
866 			used_selection_rects.pop_front();
867 			free_selection_rects.front()->rect->hide();
868 			free_selection_rects.front()->start_trim->hide();
869 			free_selection_rects.front()->end_trim->hide();
870 		}
871 		selection_group->hide();
872 	}
873 
874 	selection_group->show();
875 	selection_group->raise_to_top();
876 
877 	uint32_t gap = UIConfiguration::instance().get_vertical_region_gap ();
878 	float ui_scale = UIConfiguration::instance().get_ui_scale ();
879 	if (gap > 0 && ui_scale > 0) {
880 		gap = ceil (gap * ui_scale);
881 	}
882 
883 	for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
884 		samplepos_t start, end;
885 		samplecnt_t cnt;
886 
887 		start = (*i).start;
888 		end = (*i).end;
889 		cnt = end - start + 1;
890 
891 		rect = get_selection_rect ((*i).id);
892 
893 		x1 = _editor.sample_to_pixel (start);
894 		x2 = _editor.sample_to_pixel (start + cnt - 1);
895 		y2 = current_height() - 1;
896 
897 		if (dynamic_cast<AudioTimeAxisView*>(this)) {
898 			if (y2 > gap) {
899 				y2 -= gap;
900 			} else {
901 				y2 = 1;
902 			}
903 		}
904 
905 		rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
906 
907 		// trim boxes are at the top for selections
908 
909 		if (x2 > x1) {
910 			rect->start_trim->set (ArdourCanvas::Rect (x1, 0, x1 + trim_handle_size, y2));
911 			rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
912 
913 			rect->start_trim->show();
914 			rect->end_trim->show();
915 		} else {
916 			rect->start_trim->hide();
917 			rect->end_trim->hide();
918 		}
919 
920 		rect->rect->show ();
921 		used_selection_rects.push_back (rect);
922 	}
923 }
924 
925 void
reshow_selection(TimeSelection & ts)926 TimeAxisView::reshow_selection (TimeSelection& ts)
927 {
928 	show_selection (ts);
929 
930 	for (Children::iterator i = children.begin(); i != children.end(); ++i) {
931 		if (!(*i)->selected () && !(*i)->propagate_time_selection ()) {
932 			continue;
933 		}
934 		(*i)->show_selection (ts);
935 	}
936 }
937 
938 void
hide_selection()939 TimeAxisView::hide_selection ()
940 {
941 	if (selection_group->visible ()) {
942 		while (!used_selection_rects.empty()) {
943 			free_selection_rects.push_front (used_selection_rects.front());
944 			used_selection_rects.pop_front();
945 			free_selection_rects.front()->rect->hide();
946 			free_selection_rects.front()->start_trim->hide();
947 			free_selection_rects.front()->end_trim->hide();
948 		}
949 		selection_group->hide();
950 	}
951 
952 	for (Children::iterator i = children.begin(); i != children.end(); ++i) {
953 		(*i)->hide_selection ();
954 	}
955 }
956 
957 void
order_selection_trims(ArdourCanvas::Item * item,bool put_start_on_top)958 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
959 {
960 	/* find the selection rect this is for. we have the item corresponding to one
961 	   of the trim handles.
962 	 */
963 
964 	for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
965 		if ((*i)->start_trim == item || (*i)->end_trim == item) {
966 
967 			/* make one trim handle be "above" the other so that if they overlap,
968 			   the top one is the one last used.
969 			*/
970 
971 			(*i)->rect->raise_to_top ();
972 			(put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
973 			(put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
974 
975 			break;
976 		}
977 	}
978 }
979 
980 // retuned rect is pushed back into the used_selection_rects list
981 // in TimeAxisView::show_selection() which is the only caller.
982 SelectionRect *
get_selection_rect(uint32_t id)983 TimeAxisView::get_selection_rect (uint32_t id)
984 {
985 	SelectionRect *rect;
986 
987 	/* check to see if we already have a visible rect for this particular selection ID */
988 
989 	for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
990 		if ((*i)->id == id) {
991 			SelectionRect* ret = (*i);
992 			used_selection_rects.erase (i);
993 			return ret;
994 		}
995 	}
996 
997 	/* ditto for the free rect list */
998 
999 	for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1000 		if ((*i)->id == id) {
1001 			SelectionRect* ret = (*i);
1002 			free_selection_rects.erase (i);
1003 			return ret;
1004 		}
1005 	}
1006 
1007 	/* no existing matching rect, so go get a new one from the free list, or create one if there are none */
1008 
1009 	if (free_selection_rects.empty()) {
1010 
1011 		rect = new SelectionRect;
1012 
1013 		rect->rect = new ArdourCanvas::Rectangle (selection_group);
1014 		CANVAS_DEBUG_NAME (rect->rect, "selection rect");
1015 		rect->rect->set_outline (true);
1016 		rect->rect->set_outline_width (1.0);
1017 		rect->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1018 		rect->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1019 
1020 		rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
1021 		CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
1022 		rect->start_trim->set_outline (false);
1023 		rect->start_trim->set_fill (false);
1024 
1025 		rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
1026 		CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
1027 		rect->end_trim->set_outline (false);
1028 		rect->end_trim->set_fill (false);
1029 
1030 		free_selection_rects.push_front (rect);
1031 
1032 		rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
1033 		rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
1034 		rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
1035 	}
1036 
1037 	rect = free_selection_rects.front();
1038 	rect->id = id;
1039 	free_selection_rects.pop_front();
1040 	return rect;
1041 }
1042 
operator ()null_deleter1043 struct null_deleter { void operator()(void const *) const {} };
1044 
1045 bool
is_child(TimeAxisView * tav)1046 TimeAxisView::is_child (TimeAxisView* tav)
1047 {
1048 	return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1049 }
1050 
1051 void
add_child(boost::shared_ptr<TimeAxisView> child)1052 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1053 {
1054 	children.push_back (child);
1055 }
1056 
1057 void
remove_child(boost::shared_ptr<TimeAxisView> child)1058 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1059 {
1060 	Children::iterator i;
1061 
1062 	if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1063 		children.erase (i);
1064 	}
1065 }
1066 
1067 /** Get selectable things within a given range.
1068  *  @param start Start time in session samples.
1069  *  @param end End time in session samples.
1070  *  @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1071  *  @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1072  *  @param result Filled in with selectable things.
1073  */
1074 void
get_selectables(samplepos_t start,samplepos_t end,double top,double bot,list<Selectable * > & results,bool within)1075 TimeAxisView::get_selectables (samplepos_t start, samplepos_t end, double top, double bot, list<Selectable*>& results, bool within)
1076 {
1077 	for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1078 		if (!(*i)->hidden()) {
1079 			(*i)->get_selectables (start, end, top, bot, results, within);
1080 		}
1081 	}
1082 }
1083 
1084 void
set_selected_points(PointSelection & points)1085 TimeAxisView::set_selected_points (PointSelection& points)
1086 {
1087 	for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1088 		(*i)->set_selected_points (points);
1089 	}
1090 }
1091 
1092 void
get_inverted_selectables(Selection & sel,list<Selectable * > & results)1093 TimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& results)
1094 {
1095 	for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1096 		if (!(*i)->hidden()) {
1097 			(*i)->get_inverted_selectables (sel, results);
1098 		}
1099 	}
1100 }
1101 
1102 void
add_ghost(RegionView * rv)1103 TimeAxisView::add_ghost (RegionView* rv)
1104 {
1105 	GhostRegion* gr = rv->add_ghost (*this);
1106 
1107 	if (gr) {
1108 		ghosts.push_back(gr);
1109 	}
1110 }
1111 
1112 void
remove_ghost(RegionView * rv)1113 TimeAxisView::remove_ghost (RegionView* rv)
1114 {
1115 	rv->remove_ghost_in (*this);
1116 }
1117 
1118 void
erase_ghost(GhostRegion * gr)1119 TimeAxisView::erase_ghost (GhostRegion* gr)
1120 {
1121 	if (in_destructor) {
1122 		return;
1123 	}
1124 
1125 	for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1126 		if ((*i) == gr) {
1127 			ghosts.erase (i);
1128 			break;
1129 		}
1130 	}
1131 }
1132 
1133 bool
touched(double top,double bot)1134 TimeAxisView::touched (double top, double bot)
1135 {
1136 	/* remember: this is X Window - coordinate space starts in upper left and moves down.
1137 	  y_position is the "origin" or "top" of the track.
1138 	*/
1139 
1140 	double mybot = _y_position + current_height();
1141 
1142 	return ((_y_position <= bot && _y_position >= top) ||
1143 		((mybot <= bot) && (top < mybot)) ||
1144 		(mybot >= bot && _y_position < top));
1145 }
1146 
1147 void
set_parent(TimeAxisView & p)1148 TimeAxisView::set_parent (TimeAxisView& p)
1149 {
1150 	parent = &p;
1151 }
1152 
1153 void
reset_height()1154 TimeAxisView::reset_height ()
1155 {
1156 	set_height (height);
1157 
1158 	for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1159 		(*i)->set_height ((*i)->height);
1160 	}
1161 }
1162 
1163 void
compute_heights()1164 TimeAxisView::compute_heights ()
1165 {
1166 	// TODO this function should be re-evaluated when font-scaling changes (!)
1167 	Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1168 	Gtk::Table one_row_table (1, 1);
1169 	ArdourButton* test_button = manage (new ArdourButton);
1170 	const int border_width = 2;
1171 	const int frame_height = 2;
1172 	extra_height = (2 * border_width) + frame_height;
1173 
1174 	window.add (one_row_table);
1175 	test_button->set_name ("mute button");
1176 	test_button->set_text (S_("Mute|M"));
1177 	test_button->set_tweaks (ArdourButton::TrackHeader);
1178 
1179 	one_row_table.set_border_width (border_width);
1180 	one_row_table.set_row_spacings (2);
1181 	one_row_table.set_col_spacings (2);
1182 
1183 	one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1184 	one_row_table.show_all ();
1185 
1186 	Gtk::Requisition req(one_row_table.size_request ());
1187 	button_height = req.height;
1188 }
1189 
1190 void
color_handler()1191 TimeAxisView::color_handler ()
1192 {
1193 	for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1194 		(*i)->set_colors();
1195 	}
1196 
1197 	for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1198 
1199 		(*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1200 		(*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1201 
1202 		(*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1203 		(*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1204 
1205 		(*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1206 		(*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1207 	}
1208 
1209 	for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1210 
1211 		(*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1212 		(*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1213 
1214 		(*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1215 		(*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1216 
1217 		(*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1218 		(*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1219 	}
1220 }
1221 
1222 void
parameter_changed(string const & what_changed)1223 TimeAxisView::parameter_changed (string const & what_changed)
1224 {
1225 	if (what_changed == "vertical-region-gap") {
1226 		if (selected ()) {
1227 			show_selection (_editor.get_selection().time);
1228 		}
1229 	} else if (what_changed == "time-axis-name-ellipsize-mode") {
1230 		set_name_ellipsize_mode ();
1231 	}
1232 
1233 	if (view()) {
1234 		view()->parameter_changed (what_changed);
1235 	}
1236 }
1237 
1238 /** @return Pair: TimeAxisView, layer index.
1239  * TimeAxisView is non-0 if this object covers @param y, or one of its children
1240  * does. @param y is an offset from the top of the trackview area.
1241  *
1242  * If the covering object is a child axis, then the child is returned.
1243  * TimeAxisView is 0 otherwise.
1244  *
1245  * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1246  * and is in stacked or expanded * region display mode, otherwise 0.
1247  */
1248 std::pair<TimeAxisView*, double>
covers_y_position(double y) const1249 TimeAxisView::covers_y_position (double y) const
1250 {
1251 	if (hidden()) {
1252 		return std::make_pair ((TimeAxisView *) 0, 0);
1253 	}
1254 
1255 	if (_y_position <= y && y < (_y_position + height)) {
1256 
1257 		/* work out the layer index if appropriate */
1258 		double l = 0;
1259 		switch (layer_display ()) {
1260 		case Overlaid:
1261 			break;
1262 		case Stacked:
1263 			if (view ()) {
1264 				/* compute layer */
1265 				l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1266 				/* clamp to max layers to be on the safe side; sometimes the above calculation
1267 				   returns a too-high value */
1268 				if (l >= view()->layers ()) {
1269 					l = view()->layers() - 1;
1270 				}
1271 			}
1272 			break;
1273 		case Expanded:
1274 			if (view ()) {
1275 				int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1276 				l = n * 0.5 - 0.5;
1277 				if (l >= (view()->layers() - 0.5)) {
1278 					l = view()->layers() - 0.5;
1279 				}
1280 			}
1281 			break;
1282 		}
1283 
1284 		return std::make_pair (const_cast<TimeAxisView*>(this), l);
1285 	}
1286 
1287 	for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1288 
1289 		std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1290 		if (r.first) {
1291 			return r;
1292 		}
1293 	}
1294 
1295 	return std::make_pair ((TimeAxisView *) 0, 0);
1296 }
1297 
1298 bool
covered_by_y_range(double y0,double y1) const1299 TimeAxisView::covered_by_y_range (double y0, double y1) const
1300 {
1301 	if (hidden()) {
1302 		return false;
1303 	}
1304 
1305 	/* if either the top or bottom of the axisview is in the vertical
1306 	 * range, we cover it.
1307 	 */
1308 
1309 	if ((y0 < _y_position && y1 < _y_position) ||
1310 	    (y0 >= _y_position + height && y1 >= _y_position + height)) {
1311 		return false;
1312 	}
1313 
1314 	for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1315 		if ((*i)->covered_by_y_range (y0, y1)) {
1316 			return true;
1317 		}
1318 	}
1319 
1320 	return true;
1321 }
1322 
1323 uint32_t
preset_height(Height h)1324 TimeAxisView::preset_height (Height h)
1325 {
1326 	switch (h) {
1327 	case HeightLargest:
1328 		return (button_height * 2) + extra_height + 260;
1329 	case HeightLarger:
1330 		return (button_height * 2) + extra_height + 160;
1331 	case HeightLarge:
1332 		return (button_height * 2) + extra_height + 60;
1333 	case HeightNormal:
1334 		return (button_height * 2) + extra_height + 10;
1335 	case HeightSmall:
1336 		return button_height + extra_height;
1337 	}
1338 
1339 	abort(); /* NOTREACHED */
1340 	return 0;
1341 }
1342 
1343 /** @return Child time axis views that are not hidden */
1344 TimeAxisView::Children
get_child_list() const1345 TimeAxisView::get_child_list () const
1346 {
1347 	Children c;
1348 
1349 	for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1350 		if (!(*i)->hidden()) {
1351 			c.push_back(*i);
1352 		}
1353 	}
1354 
1355 	return c;
1356 }
1357 
1358 void
build_size_menu()1359 TimeAxisView::build_size_menu ()
1360 {
1361 	if (_size_menu) {
1362 		return;
1363 	}
1364 
1365 	using namespace Menu_Helpers;
1366 
1367 	_size_menu = new Menu;
1368 	_size_menu->set_name ("ArdourContextMenu");
1369 	MenuList& items = _size_menu->items();
1370 
1371 	items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1372 	items.push_back (MenuElem (_("Larger"),  sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1373 	items.push_back (MenuElem (_("Large"),   sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1374 	items.push_back (MenuElem (_("Normal"),  sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1375 	items.push_back (MenuElem (_("Small"),   sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1376 }
1377 
1378 void
reset_visual_state()1379 TimeAxisView::reset_visual_state ()
1380 {
1381 	/* this method is not required to trigger a global redraw */
1382 
1383 	uint32_t height;
1384 	if (get_gui_property ("height", height)) {
1385 		set_height (height);
1386 	} else {
1387 		set_height (preset_height (HeightNormal));
1388 	}
1389 }
1390 
1391 TrackViewList
filter_to_unique_playlists()1392 TrackViewList::filter_to_unique_playlists ()
1393 {
1394 	std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1395 	TrackViewList ts;
1396 
1397 	for (iterator i = begin(); i != end(); ++i) {
1398 		RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1399 		if (!rtav) {
1400 			/* not a route: include it anyway */
1401 			ts.push_back (*i);
1402 		} else {
1403 			boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1404 			if (t) {
1405 				if (playlists.insert (t->playlist()).second) {
1406 					/* playlist not seen yet */
1407 					ts.push_back (*i);
1408 				}
1409 			} else {
1410 				/* not a track: include it anyway */
1411 				ts.push_back (*i);
1412 			}
1413 		}
1414 	}
1415 	return ts;
1416 }
1417