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