2 Copyright (C) 2000 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include "pbd/error.h"
28 #include "pbd/convert.h"
29 #include "pbd/stacktrace.h"
31 #include <gtkmm2ext/doi.h>
32 #include <gtkmm2ext/utils.h>
33 #include <gtkmm2ext/selector.h>
35 #include "canvas/canvas.h"
36 #include "canvas/rectangle.h"
37 #include "canvas/debug.h"
38 #include "canvas/utils.h"
39 #include "canvas/colors.h"
41 #include "ardour/profile.h"
43 #include "ardour_ui.h"
44 #include "ardour_dialog.h"
45 #include "global_signals.h"
46 #include "gui_thread.h"
47 #include "public_editor.h"
48 #include "time_axis_view.h"
49 #include "region_view.h"
50 #include "ghostregion.h"
51 #include "selection.h"
53 #include "rgb_macros.h"
55 #include "streamview.h"
56 #include "editor_drag.h"
64 using namespace ARDOUR;
65 using namespace ARDOUR_UI_UTILS;
67 using namespace Editing;
68 using namespace ArdourCanvas;
69 using Gtkmm2ext::Keyboard;
71 #define TOP_LEVEL_WIDGET controls_ebox
73 const double trim_handle_size = 6.0; /* pixels */
74 uint32_t TimeAxisView::button_height = 0;
75 uint32_t TimeAxisView::extra_height = 0;
76 int const TimeAxisView::_max_order = 512;
77 unsigned int TimeAxisView::name_width_px = 100;
78 PBD::Signal1<void,TimeAxisView*> TimeAxisView::CatchDeletion;
79 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::controls_meters_size_group = Glib::RefPtr<Gtk::SizeGroup>();
80 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::midi_scroomer_size_group = Glib::RefPtr<Gtk::SizeGroup>();
83 TimeAxisView::setup_sizes()
85 name_width_px = ceilf (100.f * ARDOUR_UI::ui_scale);
88 TimeAxisView::TimeAxisView (ARDOUR::Session* sess, PublicEditor& ed, TimeAxisView* rent, Canvas& /*canvas*/)
90 , controls_table (3, 3)
91 , controls_button_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
92 , _name_editing (false)
99 , in_destructor (false)
101 , _canvas_display (0)
107 , _effective_height (0)
108 , _resize_drag_start (-1)
109 , _did_resize (false)
110 , _preresize_cursor (0)
111 , _have_preresize_cursor (false)
112 , _ebox_release_can_act (true)
114 if (!controls_meters_size_group) {
115 controls_meters_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
117 if (!midi_scroomer_size_group) {
118 midi_scroomer_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
120 if (extra_height == 0) {
124 _canvas_display = new ArdourCanvas::Container (ed.get_trackview_group ());
125 CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
126 _canvas_display->hide(); // reveal as needed
128 _canvas_separator = new ArdourCanvas::Line(_canvas_display);
129 CANVAS_DEBUG_NAME (_canvas_separator, "separator for TAV");
130 _canvas_separator->set (ArdourCanvas::Duple(0.0, 0.0), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, 0.0));
131 _canvas_separator->set_outline_color(ArdourCanvas::rgba_to_color (0, 0, 0, 1.0));
132 _canvas_separator->set_outline_width(1.0);
133 _canvas_separator->hide();
135 selection_group = new ArdourCanvas::Container (_canvas_display);
136 CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
137 selection_group->set_data (X_("timeselection"), (void *) 1);
138 selection_group->hide();
140 _ghost_group = new ArdourCanvas::Container (_canvas_display);
141 CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
142 _ghost_group->lower_to_bottom();
143 _ghost_group->show();
145 name_label.set_name ("TrackLabel");
146 name_label.set_alignment (0.0, 0.5);
147 name_label.set_width_chars (12);
148 ARDOUR_UI::instance()->set_tip (name_label, _("Track/Bus name (double click to edit)"));
150 Gtk::Entry* an_entry = new Gtkmm2ext::FocusEntry;
151 an_entry->set_name ("EditorTrackNameDisplay");
152 Gtk::Requisition req;
153 an_entry->size_request (req);
154 name_label.set_size_request (-1, req.height);
155 name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
158 name_hbox.pack_end (name_label, true, true);
160 // set min. track-header width if fader is not visible
161 name_hbox.set_size_request(name_width_px, -1);
166 controls_table.set_row_spacings (2);
167 controls_table.set_col_spacings (2);
168 controls_table.set_border_width (2);
170 if (ARDOUR::Profile->get_mixbus() ) {
171 controls_table.attach (name_hbox, 4, 5, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
173 controls_table.attach (name_hbox, 1, 2, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
175 controls_table.show_all ();
176 controls_table.set_no_show_all ();
178 controls_vbox.pack_start (controls_table, false, false);
179 controls_vbox.show ();
181 top_hbox.pack_start (controls_vbox, true, true);
184 controls_ebox.add (time_axis_hbox);
185 controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
186 Gdk::BUTTON_RELEASE_MASK|
187 Gdk::POINTER_MOTION_MASK|
188 Gdk::ENTER_NOTIFY_MASK|
189 Gdk::LEAVE_NOTIFY_MASK|
191 controls_ebox.set_flags (CAN_FOCUS);
193 /* note that this handler connects *before* the default handler */
194 controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
195 controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
196 controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
197 controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
198 controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
199 controls_ebox.show ();
201 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
202 time_axis_frame.add(top_hbox);
203 time_axis_frame.show();
205 HSeparator* separator = manage (new HSeparator());
206 separator->set_name("TrackSeparator");
207 separator->set_size_request(-1, 1);
210 scroomer_placeholder.set_size_request (-1, -1);
211 scroomer_placeholder.show();
212 midi_scroomer_size_group->add_widget (scroomer_placeholder);
214 time_axis_vbox.pack_start (*separator, false, false);
215 time_axis_vbox.pack_start (time_axis_frame, true, true);
216 time_axis_vbox.show();
217 time_axis_hbox.pack_start (time_axis_vbox, true, true);
218 time_axis_hbox.show();
219 top_hbox.pack_start (scroomer_placeholder, false, false); // OR pack_end to move after meters ?
221 ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
223 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&TimeAxisView::erase_ghost, this, _1), gui_context());
226 TimeAxisView::~TimeAxisView()
228 CatchDeletion (this);
230 in_destructor = true;
232 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
236 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
237 delete (*i)->rect; (*i)->rect=0;
238 delete (*i)->start_trim; (*i)->start_trim = 0;
239 delete (*i)->end_trim; (*i)->end_trim = 0;
243 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_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;
249 delete selection_group;
252 delete _canvas_display;
262 TimeAxisView::hide ()
268 _canvas_display->hide ();
269 _canvas_separator->hide ();
271 if (control_parent) {
272 control_parent->remove (TOP_LEVEL_WIDGET);
279 /* now hide children */
281 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
285 /* if its hidden, it cannot be selected */
286 _editor.get_selection().remove (this);
287 /* and neither can its regions */
288 _editor.get_selection().remove_regions (this);
293 /** Display this TimeAxisView as the nth component of the parent box, at y.
295 * @param y y position.
296 * @param nth index for this TimeAxisView, increased if this view has children.
297 * @param parent parent component.
298 * @return height of this TimeAxisView.
301 TimeAxisView::show_at (double y, int& nth, VBox *parent)
303 if (control_parent) {
304 control_parent->reorder_child (TOP_LEVEL_WIDGET, nth);
306 control_parent = parent;
307 parent->pack_start (TOP_LEVEL_WIDGET, false, false);
308 parent->reorder_child (TOP_LEVEL_WIDGET, nth);
313 if (_y_position != y) {
314 _canvas_display->set_y_position (y);
318 _canvas_display->raise_to_top ();
319 _canvas_display->show ();
323 _effective_height = current_height ();
325 /* now show relevant children */
327 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
328 if ((*i)->marked_for_display()) {
330 _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
336 /* put separator at the bottom of this time axis view */
338 _canvas_separator->set (ArdourCanvas::Duple(0, height), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, height));
339 _canvas_separator->lower_to_bottom ();
340 _canvas_separator->show ();
342 return _effective_height;
346 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
348 switch (ev->direction) {
350 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
351 /* See Editor::_stepping_axis_view for notes on this hack */
352 Editor& e = dynamic_cast<Editor&> (_editor);
353 if (!e.stepping_axis_view ()) {
354 e.set_stepping_axis_view (this);
356 e.stepping_axis_view()->step_height (false);
361 case GDK_SCROLL_DOWN:
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 (true);
374 /* no handling for left/right, yet */
378 /* Just forward to the normal canvas scroll method. The coordinate
379 systems are different but since the canvas is always larger than the
380 track headers, and aligned with the trackview area, this will work.
382 In the not too distant future this layout is going away anyway and
383 headers will be on the canvas.
385 return _editor.canvas_scroll_event (ev, false);
389 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
391 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
392 /* see if it is inside the name label */
393 if (name_label.is_ancestor (controls_ebox)) {
396 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
397 Gtk::Allocation a = name_label.get_allocation ();
398 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
400 _ebox_release_can_act = false;
407 _ebox_release_can_act = true;
409 if (maybe_set_cursor (event->y) > 0) {
410 _resize_drag_start = event->y_root;
417 TimeAxisView::idle_resize (int32_t h)
419 set_height (std::max(0, h));
424 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
426 if (_resize_drag_start >= 0) {
428 /* (ab)use the DragManager to do autoscrolling - basically we
429 * are pretending that the drag is taking place over the canvas
430 * (which perhaps in the glorious future, when track headers
431 * and the canvas are unified, will actually be true.)
434 _editor.maybe_autoscroll (false, true, true);
436 /* now schedule the actual TAV resize */
437 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
438 _editor.add_to_idle_resize (this, delta);
439 _resize_drag_start = ev->y_root;
442 /* not dragging but ... */
443 maybe_set_cursor (ev->y);
446 gdk_event_request_motions(ev);
451 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
453 if (_have_preresize_cursor) {
454 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
455 _have_preresize_cursor = false;
461 TimeAxisView::maybe_set_cursor (int y)
463 /* XXX no Gtkmm Gdk::Window::get_cursor() */
464 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
466 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
468 /* y-coordinate in lower 25% */
470 if (!_have_preresize_cursor) {
471 _preresize_cursor = gdk_window_get_cursor (win->gobj());
472 _have_preresize_cursor = true;
473 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
478 } else if (_have_preresize_cursor) {
479 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
480 _have_preresize_cursor = false;
489 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
491 if (_resize_drag_start >= 0) {
492 if (_have_preresize_cursor) {
493 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
494 _preresize_cursor = 0;
495 _have_preresize_cursor = false;
497 _editor.stop_canvas_autoscroll ();
498 _resize_drag_start = -1;
501 // don't change selection
506 if (!_ebox_release_can_act) {
510 switch (ev->button) {
512 selection_click (ev);
516 popup_display_menu (ev->time);
524 TimeAxisView::selection_click (GdkEventButton* ev)
526 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
527 _editor.set_selected_track (*this, op, false);
531 /** Steps through the defined heights for this TrackView.
532 * @param coarser true if stepping should decrease in size, otherwise false.
535 TimeAxisView::step_height (bool coarser)
537 static const uint32_t step = 25;
541 if (height <= preset_height (HeightSmall)) {
543 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
544 set_height_enum (HeightSmall);
546 set_height (height - step);
551 if (height <= preset_height(HeightSmall)) {
552 set_height_enum (HeightNormal);
554 set_height (height + step);
561 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
563 if (apply_to_selection) {
564 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
566 set_height (preset_height (h));
571 TimeAxisView::set_height (uint32_t h, TrackHeightMode m)
574 if (m == TotalHeight) {
575 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
576 if ( !(*i)->hidden()) ++lanes;
581 if (h < preset_height (HeightSmall)) {
582 h = preset_height (HeightSmall);
585 TOP_LEVEL_WIDGET.property_height_request () = h;
589 snprintf (buf, sizeof (buf), "%u", height);
590 set_gui_property ("height", buf);
592 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
596 if (selection_group->visible ()) {
597 /* resize the selection rect */
598 show_selection (_editor.get_selection().time);
602 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
603 (*i)->set_height(h, OnlySelf);
607 _editor.override_visible_track_count ();
611 TimeAxisView::name_entry_key_press (GdkEventKey* ev)
613 /* steal escape, tabs from GTK */
615 switch (ev->keyval) {
617 case GDK_ISO_Left_Tab:
625 TimeAxisView::name_entry_key_release (GdkEventKey* ev)
627 TrackViewList::iterator i;
629 switch (ev->keyval) {
631 end_name_edit (RESPONSE_CANCEL);
634 /* Shift+Tab Keys Pressed. Note that for Shift+Tab, GDK actually
635 * generates a different ev->keyval, rather than setting
638 case GDK_ISO_Left_Tab:
639 end_name_edit (RESPONSE_APPLY);
643 end_name_edit (RESPONSE_ACCEPT);
653 TimeAxisView::name_entry_focus_out (GdkEventFocus*)
655 end_name_edit (RESPONSE_OK);
660 TimeAxisView::begin_name_edit ()
666 if (can_edit_name()) {
668 name_entry = manage (new Gtkmm2ext::FocusEntry);
670 name_entry->set_width_chars(8); // min width, entry expands
672 name_entry->set_name ("EditorTrackNameDisplay");
673 name_entry->signal_key_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_press), false);
674 name_entry->signal_key_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_release), false);
675 name_entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_focus_out));
676 name_entry->set_text (name_label.get_text());
677 name_entry->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisView::end_name_edit), RESPONSE_OK));
679 if (name_label.is_ancestor (name_hbox)) {
680 name_hbox.remove (name_label);
683 name_hbox.pack_end (*name_entry, true, true);
686 name_entry->select_region (0, -1);
687 name_entry->set_state (STATE_SELECTED);
688 name_entry->grab_focus ();
689 name_entry->start_editing (0);
694 TimeAxisView::end_name_edit (int response)
700 bool edit_next = false;
701 bool edit_prev = false;
704 case RESPONSE_CANCEL:
707 name_entry_changed ();
709 case RESPONSE_ACCEPT:
710 name_entry_changed ();
713 name_entry_changed ();
717 /* this will delete the name_entry. but it will also drop focus, which
718 * will cause another callback to this function, so set name_entry = 0
719 * first to ensure we don't double-remove etc. etc.
722 Gtk::Entry* tmp = name_entry;
724 name_hbox.remove (*tmp);
726 /* put the name label back */
728 name_hbox.pack_end (name_label);
733 TrackViewList const & allviews = _editor.get_track_views ();
734 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
736 if (i != allviews.end()) {
739 if (++i == allviews.end()) {
743 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
745 if (rtav && rtav->route()->record_enabled()) {
749 if (!(*i)->hidden()) {
756 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
757 _editor.ensure_time_axis_view_is_visible (**i, false);
758 (*i)->begin_name_edit ();
761 } else if (edit_prev) {
763 TrackViewList const & allviews = _editor.get_track_views ();
764 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
766 if (i != allviews.begin()) {
768 if (i == allviews.begin()) {
774 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
776 if (rtav && rtav->route()->record_enabled()) {
780 if (!(*i)->hidden()) {
787 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
788 _editor.ensure_time_axis_view_is_visible (**i, false);
789 (*i)->begin_name_edit ();
795 TimeAxisView::name_entry_changed ()
800 TimeAxisView::can_edit_name () const
806 TimeAxisView::conditionally_add_to_selection ()
808 Selection& s (_editor.get_selection ());
810 if (!s.selected (this)) {
811 _editor.set_selected_track (*this, Selection::Set);
816 TimeAxisView::popup_display_menu (guint32 when)
818 conditionally_add_to_selection ();
820 build_display_menu ();
821 display_menu->popup (1, when);
825 TimeAxisView::set_selected (bool yn)
827 if (can_edit_name() && name_entry && name_entry->get_visible()) {
828 end_name_edit (RESPONSE_CANCEL);
831 if (yn == _selected) {
835 Selectable::set_selected (yn);
838 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
839 time_axis_frame.set_name ("MixerStripSelectedFrame");
840 controls_ebox.set_name (controls_base_selected_name);
841 controls_vbox.set_name (controls_base_selected_name);
842 time_axis_vbox.set_name (controls_base_selected_name);
844 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
845 time_axis_frame.set_name (controls_base_unselected_name);
846 controls_ebox.set_name (controls_base_unselected_name);
847 controls_vbox.set_name (controls_base_unselected_name);
848 time_axis_vbox.set_name (controls_base_unselected_name);
852 /* children will be set for the yn=true case. but when deselecting
853 the editor only has a list of top-level trackviews, so we
854 have to do this here.
857 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
858 (*i)->set_selected (false);
862 time_axis_frame.show();
867 TimeAxisView::build_display_menu ()
869 using namespace Menu_Helpers;
873 display_menu = new Menu;
874 display_menu->set_name ("ArdourContextMenu");
876 // Just let implementing classes define what goes into the manu
880 TimeAxisView::set_samples_per_pixel (double fpp)
882 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
883 (*i)->set_samples_per_pixel (fpp);
888 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
890 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
891 (*i)->show_timestretch (start, end, layers, layer);
896 TimeAxisView::hide_timestretch ()
898 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
899 (*i)->hide_timestretch ();
904 TimeAxisView::show_selection (TimeSelection& ts)
909 SelectionRect *rect; time_axis_frame.show();
912 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
913 (*i)->show_selection (ts);
916 if (selection_group->visible ()) {
917 while (!used_selection_rects.empty()) {
918 free_selection_rects.push_front (used_selection_rects.front());
919 used_selection_rects.pop_front();
920 free_selection_rects.front()->rect->hide();
921 free_selection_rects.front()->start_trim->hide();
922 free_selection_rects.front()->end_trim->hide();
924 selection_group->hide();
927 selection_group->show();
928 selection_group->raise_to_top();
930 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
931 framepos_t start, end;
936 cnt = end - start + 1;
938 rect = get_selection_rect ((*i).id);
940 x1 = _editor.sample_to_pixel (start);
941 x2 = _editor.sample_to_pixel (start + cnt - 1);
942 y2 = current_height() - 1;
944 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
946 // trim boxes are at the top for selections
949 rect->start_trim->set (ArdourCanvas::Rect (x1, 0, x1 + trim_handle_size, y2));
950 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
952 rect->start_trim->show();
953 rect->end_trim->show();
955 rect->start_trim->hide();
956 rect->end_trim->hide();
960 used_selection_rects.push_back (rect);
965 TimeAxisView::reshow_selection (TimeSelection& ts)
969 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
970 (*i)->show_selection (ts);
975 TimeAxisView::hide_selection ()
977 if (selection_group->visible ()) {
978 while (!used_selection_rects.empty()) {
979 free_selection_rects.push_front (used_selection_rects.front());
980 used_selection_rects.pop_front();
981 free_selection_rects.front()->rect->hide();
982 free_selection_rects.front()->start_trim->hide();
983 free_selection_rects.front()->end_trim->hide();
985 selection_group->hide();
988 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
989 (*i)->hide_selection ();
994 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
996 /* find the selection rect this is for. we have the item corresponding to one
1000 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1001 if ((*i)->start_trim == item || (*i)->end_trim == item) {
1003 /* make one trim handle be "above" the other so that if they overlap,
1004 the top one is the one last used.
1007 (*i)->rect->raise_to_top ();
1008 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
1009 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
1017 TimeAxisView::get_selection_rect (uint32_t id)
1019 SelectionRect *rect;
1021 /* check to see if we already have a visible rect for this particular selection ID */
1023 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1024 if ((*i)->id == id) {
1029 /* ditto for the free rect list */
1031 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1032 if ((*i)->id == id) {
1033 SelectionRect* ret = (*i);
1034 free_selection_rects.erase (i);
1039 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
1041 if (free_selection_rects.empty()) {
1043 rect = new SelectionRect;
1045 rect->rect = new ArdourCanvas::Rectangle (selection_group);
1046 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
1047 rect->rect->set_outline (false);
1048 rect->rect->set_fill_color (ARDOUR_UI::config()->color_mod ("selection rect", "selection rect"));
1050 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
1051 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
1052 rect->start_trim->set_outline (false);
1053 rect->start_trim->set_fill (false);
1055 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
1056 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
1057 rect->end_trim->set_outline (false);
1058 rect->end_trim->set_fill (false);
1060 free_selection_rects.push_front (rect);
1062 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
1063 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
1064 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
1067 rect = free_selection_rects.front();
1069 free_selection_rects.pop_front();
1073 struct null_deleter { void operator()(void const *) const {} };
1076 TimeAxisView::is_child (TimeAxisView* tav)
1078 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1082 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1084 children.push_back (child);
1088 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1090 Children::iterator i;
1092 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1097 /** Get selectable things within a given range.
1098 * @param start Start time in session frames.
1099 * @param end End time in session frames.
1100 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1101 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1102 * @param result Filled in with selectable things.
1105 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/, bool /*within*/)
1111 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1117 TimeAxisView::add_ghost (RegionView* rv)
1119 GhostRegion* gr = rv->add_ghost (*this);
1122 ghosts.push_back(gr);
1127 TimeAxisView::remove_ghost (RegionView* rv)
1129 rv->remove_ghost_in (*this);
1133 TimeAxisView::erase_ghost (GhostRegion* gr)
1135 if (in_destructor) {
1139 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1148 TimeAxisView::touched (double top, double bot)
1150 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1151 y_position is the "origin" or "top" of the track.
1154 double mybot = _y_position + current_height();
1156 return ((_y_position <= bot && _y_position >= top) ||
1157 ((mybot <= bot) && (top < mybot)) ||
1158 (mybot >= bot && _y_position < top));
1162 TimeAxisView::set_parent (TimeAxisView& p)
1168 TimeAxisView::reset_height ()
1170 set_height (height);
1172 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1173 (*i)->set_height ((*i)->height);
1178 TimeAxisView::compute_heights ()
1180 // TODO this function should be re-evaluated when font-scaling changes (!)
1181 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1182 Gtk::Table one_row_table (1, 1);
1183 ArdourButton* test_button = manage (new ArdourButton);
1184 const int border_width = 2;
1185 const int frame_height = 2;
1186 extra_height = (2 * border_width) + frame_height;
1188 window.add (one_row_table);
1189 test_button->set_name ("mute button");
1190 test_button->set_text (S_("Mute|M"));
1191 test_button->set_tweaks (ArdourButton::TrackHeader);
1193 one_row_table.set_border_width (border_width);
1194 one_row_table.set_row_spacings (2);
1195 one_row_table.set_col_spacings (2);
1197 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1198 one_row_table.show_all ();
1200 Gtk::Requisition req(one_row_table.size_request ());
1201 button_height = req.height;
1205 TimeAxisView::color_handler ()
1207 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1211 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1213 (*i)->rect->set_fill_color (ARDOUR_UI::config()->color_mod ("selection rect", "selection rect"));
1214 (*i)->rect->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1216 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->color ("selection"));
1217 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1219 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->color ("selection"));
1220 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1223 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1225 (*i)->rect->set_fill_color (ARDOUR_UI::config()->color_mod ("selection rect", "selection rect"));
1226 (*i)->rect->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1228 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->color ("selection"));
1229 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1231 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->color ("selection"));
1232 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1236 /** @return Pair: TimeAxisView, layer index.
1237 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1238 * does. @param y is an offset from the top of the trackview area.
1240 * If the covering object is a child axis, then the child is returned.
1241 * TimeAxisView is 0 otherwise.
1243 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1244 * and is in stacked or expanded * region display mode, otherwise 0.
1246 std::pair<TimeAxisView*, double>
1247 TimeAxisView::covers_y_position (double y) const
1250 return std::make_pair ((TimeAxisView *) 0, 0);
1253 if (_y_position <= y && y < (_y_position + height)) {
1255 /* work out the layer index if appropriate */
1257 switch (layer_display ()) {
1263 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1264 /* clamp to max layers to be on the safe side; sometimes the above calculation
1265 returns a too-high value */
1266 if (l >= view()->layers ()) {
1267 l = view()->layers() - 1;
1273 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1275 if (l >= (view()->layers() - 0.5)) {
1276 l = view()->layers() - 0.5;
1282 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1285 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1287 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1293 return std::make_pair ((TimeAxisView *) 0, 0);
1297 TimeAxisView::covered_by_y_range (double y0, double y1) const
1303 /* if either the top or bottom of the axisview is in the vertical
1304 * range, we cover it.
1307 if ((y0 < _y_position && y1 < _y_position) ||
1308 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1312 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1313 if ((*i)->covered_by_y_range (y0, y1)) {
1322 TimeAxisView::preset_height (Height h)
1326 return (button_height * 2) + extra_height + 260;
1328 return (button_height * 2) + extra_height + 160;
1330 return (button_height * 2) + extra_height + 60;
1332 return (button_height * 2) + extra_height + 10;
1334 return button_height + extra_height;
1337 abort(); /* NOTREACHED */
1341 /** @return Child time axis views that are not hidden */
1342 TimeAxisView::Children
1343 TimeAxisView::get_child_list ()
1347 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1348 if (!(*i)->hidden()) {
1357 TimeAxisView::build_size_menu ()
1359 if (_size_menu && _size_menu->gobj ()) {
1365 using namespace Menu_Helpers;
1367 _size_menu = new Menu;
1368 _size_menu->set_name ("ArdourContextMenu");
1369 MenuList& items = _size_menu->items();
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)));
1379 TimeAxisView::reset_visual_state ()
1381 /* this method is not required to trigger a global redraw */
1383 string str = gui_property ("height");
1386 set_height (atoi (str));
1388 set_height (preset_height (HeightNormal));
1393 TrackViewList::filter_to_unique_playlists ()
1395 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1398 for (iterator i = begin(); i != end(); ++i) {
1399 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1401 /* not a route: include it anyway */
1404 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1406 if (playlists.insert (t->playlist()).second) {
1407 /* playlist not seen yet */
1411 /* not a track: include it anyway */