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"
30 #include "pbd/unwind.h"
32 #include <gtkmm2ext/doi.h>
33 #include <gtkmm2ext/utils.h>
34 #include <gtkmm2ext/selector.h>
36 #include "canvas/canvas.h"
37 #include "canvas/rectangle.h"
38 #include "canvas/debug.h"
39 #include "canvas/utils.h"
40 #include "canvas/colors.h"
42 #include "ardour/profile.h"
44 #include "ardour_ui.h"
45 #include "ardour_dialog.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)
105 , ending_name_edit (false)
108 , _effective_height (0)
109 , _resize_drag_start (-1)
110 , _did_resize (false)
111 , _preresize_cursor (0)
112 , _have_preresize_cursor (false)
113 , _ebox_release_can_act (true)
115 if (!controls_meters_size_group) {
116 controls_meters_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
118 if (!midi_scroomer_size_group) {
119 midi_scroomer_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
121 if (extra_height == 0) {
125 _canvas_display = new ArdourCanvas::Container (ed.get_trackview_group ());
126 CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
127 _canvas_display->hide(); // reveal as needed
129 _canvas_separator = new ArdourCanvas::Line(_canvas_display);
130 CANVAS_DEBUG_NAME (_canvas_separator, "separator for TAV");
131 _canvas_separator->set (ArdourCanvas::Duple(0.0, 0.0), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, 0.0));
132 _canvas_separator->set_outline_color(ArdourCanvas::rgba_to_color (0, 0, 0, 1.0));
133 _canvas_separator->set_outline_width(1.0);
134 _canvas_separator->hide();
136 selection_group = new ArdourCanvas::Container (_canvas_display);
137 CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
138 selection_group->set_data (X_("timeselection"), (void *) 1);
139 selection_group->hide();
141 _ghost_group = new ArdourCanvas::Container (_canvas_display);
142 CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
143 _ghost_group->lower_to_bottom();
144 _ghost_group->show();
146 name_label.set_name ("TrackLabel");
147 name_label.set_alignment (0.0, 0.5);
148 name_label.set_width_chars (12);
149 ARDOUR_UI::instance()->set_tip (name_label, _("Track/Bus name (double click to edit)"));
151 Gtk::Entry* an_entry = new Gtkmm2ext::FocusEntry;
152 an_entry->set_name ("EditorTrackNameDisplay");
153 Gtk::Requisition req;
154 an_entry->size_request (req);
155 name_label.set_size_request (-1, req.height);
156 name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
159 name_hbox.pack_end (name_label, true, true);
161 // set min. track-header width if fader is not visible
162 name_hbox.set_size_request(name_width_px, -1);
167 controls_table.set_row_spacings (2);
168 controls_table.set_col_spacings (2);
169 controls_table.set_border_width (2);
171 if (ARDOUR::Profile->get_mixbus() ) {
172 controls_table.attach (name_hbox, 4, 5, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
174 controls_table.attach (name_hbox, 1, 2, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
176 controls_table.show_all ();
177 controls_table.set_no_show_all ();
179 controls_vbox.pack_start (controls_table, false, false);
180 controls_vbox.show ();
182 top_hbox.pack_start (controls_vbox, true, true);
185 controls_ebox.add (time_axis_hbox);
186 controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
187 Gdk::BUTTON_RELEASE_MASK|
188 Gdk::POINTER_MOTION_MASK|
189 Gdk::ENTER_NOTIFY_MASK|
190 Gdk::LEAVE_NOTIFY_MASK|
192 controls_ebox.set_flags (CAN_FOCUS);
194 /* note that this handler connects *before* the default handler */
195 controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
196 controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
197 controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
198 controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
199 controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
200 controls_ebox.show ();
202 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
203 time_axis_frame.add(top_hbox);
204 time_axis_frame.show();
206 HSeparator* separator = manage (new HSeparator());
207 separator->set_name("TrackSeparator");
208 separator->set_size_request(-1, 1);
211 scroomer_placeholder.set_size_request (-1, -1);
212 scroomer_placeholder.show();
213 midi_scroomer_size_group->add_widget (scroomer_placeholder);
215 time_axis_vbox.pack_start (*separator, false, false);
216 time_axis_vbox.pack_start (time_axis_frame, true, true);
217 time_axis_vbox.show();
218 time_axis_hbox.pack_start (time_axis_vbox, true, true);
219 time_axis_hbox.show();
220 top_hbox.pack_start (scroomer_placeholder, false, false); // OR pack_end to move after meters ?
222 UIConfiguration::ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
224 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&TimeAxisView::erase_ghost, this, _1), gui_context());
227 TimeAxisView::~TimeAxisView()
229 CatchDeletion (this);
231 in_destructor = true;
233 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
237 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
238 delete (*i)->rect; (*i)->rect=0;
239 delete (*i)->start_trim; (*i)->start_trim = 0;
240 delete (*i)->end_trim; (*i)->end_trim = 0;
244 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
245 delete (*i)->rect; (*i)->rect = 0;
246 delete (*i)->start_trim; (*i)->start_trim = 0;
247 delete (*i)->end_trim; (*i)->end_trim = 0;
250 delete selection_group;
253 delete _canvas_display;
263 TimeAxisView::hide ()
269 _canvas_display->hide ();
270 _canvas_separator->hide ();
272 if (control_parent) {
273 control_parent->remove (TOP_LEVEL_WIDGET);
280 /* now hide children */
282 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
286 /* if its hidden, it cannot be selected */
287 _editor.get_selection().remove (this);
288 /* and neither can its regions */
289 _editor.get_selection().remove_regions (this);
294 /** Display this TimeAxisView as the nth component of the parent box, at y.
296 * @param y y position.
297 * @param nth index for this TimeAxisView, increased if this view has children.
298 * @param parent parent component.
299 * @return height of this TimeAxisView.
302 TimeAxisView::show_at (double y, int& nth, VBox *parent)
304 if (control_parent) {
305 control_parent->reorder_child (TOP_LEVEL_WIDGET, nth);
307 control_parent = parent;
308 parent->pack_start (TOP_LEVEL_WIDGET, false, false);
309 parent->reorder_child (TOP_LEVEL_WIDGET, nth);
314 if (_y_position != y) {
315 _canvas_display->set_y_position (y);
319 _canvas_display->raise_to_top ();
320 _canvas_display->show ();
324 _effective_height = current_height ();
326 /* now show relevant children */
328 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
329 if ((*i)->marked_for_display()) {
331 _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
337 /* put separator at the bottom of this time axis view */
339 _canvas_separator->set (ArdourCanvas::Duple(0, height), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, height));
340 _canvas_separator->lower_to_bottom ();
341 _canvas_separator->show ();
343 return _effective_height;
347 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
349 switch (ev->direction) {
351 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
352 /* See Editor::_stepping_axis_view for notes on this hack */
353 Editor& e = dynamic_cast<Editor&> (_editor);
354 if (!e.stepping_axis_view ()) {
355 e.set_stepping_axis_view (this);
357 e.stepping_axis_view()->step_height (false);
362 case GDK_SCROLL_DOWN:
363 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
364 /* See Editor::_stepping_axis_view for notes on this hack */
365 Editor& e = dynamic_cast<Editor&> (_editor);
366 if (!e.stepping_axis_view ()) {
367 e.set_stepping_axis_view (this);
369 e.stepping_axis_view()->step_height (true);
375 /* no handling for left/right, yet */
379 /* Just forward to the normal canvas scroll method. The coordinate
380 systems are different but since the canvas is always larger than the
381 track headers, and aligned with the trackview area, this will work.
383 In the not too distant future this layout is going away anyway and
384 headers will be on the canvas.
386 return _editor.canvas_scroll_event (ev, false);
390 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
392 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
393 /* see if it is inside the name label */
394 if (name_label.is_ancestor (controls_ebox)) {
397 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
398 Gtk::Allocation a = name_label.get_allocation ();
399 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
401 _ebox_release_can_act = false;
408 _ebox_release_can_act = true;
410 if (maybe_set_cursor (event->y) > 0) {
411 _resize_drag_start = event->y_root;
418 TimeAxisView::idle_resize (int32_t h)
420 set_height (std::max(0, h));
425 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
427 if (_resize_drag_start >= 0) {
429 /* (ab)use the DragManager to do autoscrolling - basically we
430 * are pretending that the drag is taking place over the canvas
431 * (which perhaps in the glorious future, when track headers
432 * and the canvas are unified, will actually be true.)
435 _editor.maybe_autoscroll (false, true, true);
437 /* now schedule the actual TAV resize */
438 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
439 _editor.add_to_idle_resize (this, delta);
440 _resize_drag_start = ev->y_root;
443 /* not dragging but ... */
444 maybe_set_cursor (ev->y);
447 gdk_event_request_motions(ev);
452 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
454 if (_have_preresize_cursor) {
455 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
456 _have_preresize_cursor = false;
462 TimeAxisView::maybe_set_cursor (int y)
464 /* XXX no Gtkmm Gdk::Window::get_cursor() */
465 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
467 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
469 /* y-coordinate in lower 25% */
471 if (!_have_preresize_cursor) {
472 _preresize_cursor = gdk_window_get_cursor (win->gobj());
473 _have_preresize_cursor = true;
474 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
479 } else if (_have_preresize_cursor) {
480 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
481 _have_preresize_cursor = false;
490 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
492 if (_resize_drag_start >= 0) {
493 if (_have_preresize_cursor) {
494 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
495 _preresize_cursor = 0;
496 _have_preresize_cursor = false;
498 _editor.stop_canvas_autoscroll ();
499 _resize_drag_start = -1;
502 // don't change selection
507 if (!_ebox_release_can_act) {
511 switch (ev->button) {
513 selection_click (ev);
517 popup_display_menu (ev->time);
525 TimeAxisView::selection_click (GdkEventButton* ev)
527 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
528 _editor.set_selected_track (*this, op, false);
532 /** Steps through the defined heights for this TrackView.
533 * @param coarser true if stepping should decrease in size, otherwise false.
536 TimeAxisView::step_height (bool coarser)
538 static const uint32_t step = 25;
542 if (height <= preset_height (HeightSmall)) {
544 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
545 set_height_enum (HeightSmall);
547 set_height (height - step);
552 if (height <= preset_height(HeightSmall)) {
553 set_height_enum (HeightNormal);
555 set_height (height + step);
562 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
564 if (apply_to_selection) {
565 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
567 set_height (preset_height (h));
572 TimeAxisView::set_height (uint32_t h, TrackHeightMode m)
575 if (m == TotalHeight) {
576 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
577 if ( !(*i)->hidden()) ++lanes;
582 if (h < preset_height (HeightSmall)) {
583 h = preset_height (HeightSmall);
586 TOP_LEVEL_WIDGET.property_height_request () = h;
590 snprintf (buf, sizeof (buf), "%u", height);
591 set_gui_property ("height", buf);
593 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
597 if (selection_group->visible ()) {
598 /* resize the selection rect */
599 show_selection (_editor.get_selection().time);
603 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
604 (*i)->set_height(h, OnlySelf);
608 _editor.override_visible_track_count ();
612 TimeAxisView::name_entry_key_press (GdkEventKey* ev)
614 /* steal escape, tabs from GTK */
616 switch (ev->keyval) {
618 case GDK_ISO_Left_Tab:
626 TimeAxisView::name_entry_key_release (GdkEventKey* ev)
628 TrackViewList::iterator i;
630 switch (ev->keyval) {
632 end_name_edit (RESPONSE_CANCEL);
635 /* Shift+Tab Keys Pressed. Note that for Shift+Tab, GDK actually
636 * generates a different ev->keyval, rather than setting
639 case GDK_ISO_Left_Tab:
640 end_name_edit (RESPONSE_APPLY);
644 end_name_edit (RESPONSE_ACCEPT);
654 TimeAxisView::name_entry_focus_out (GdkEventFocus*)
656 end_name_edit (RESPONSE_OK);
661 TimeAxisView::begin_name_edit ()
667 if (can_edit_name()) {
669 name_entry = manage (new Gtkmm2ext::FocusEntry);
671 name_entry->set_width_chars(8); // min width, entry expands
673 name_entry->set_name ("EditorTrackNameDisplay");
674 name_entry->signal_key_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_press), false);
675 name_entry->signal_key_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_release), false);
676 name_entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_focus_out));
677 name_entry->set_text (name_label.get_text());
678 name_entry->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisView::end_name_edit), RESPONSE_OK));
680 if (name_label.is_ancestor (name_hbox)) {
681 name_hbox.remove (name_label);
684 name_hbox.pack_end (*name_entry, true, true);
687 name_entry->select_region (0, -1);
688 name_entry->set_state (STATE_SELECTED);
689 name_entry->grab_focus ();
690 name_entry->start_editing (0);
695 TimeAxisView::end_name_edit (int response)
701 if (ending_name_edit) {
702 /* already doing this, and focus out or other event has caused
703 us to re-enter this code.
708 PBD::Unwinder<bool> uw (ending_name_edit, true);
710 bool edit_next = false;
711 bool edit_prev = false;
714 case RESPONSE_CANCEL:
717 name_entry_changed ();
719 case RESPONSE_ACCEPT:
720 name_entry_changed ();
723 name_entry_changed ();
727 /* this will delete the name_entry. but it will also drop focus, which
728 * will cause another callback to this function, so set name_entry = 0
729 * first to ensure we don't double-remove etc. etc.
732 Gtk::Entry* tmp = name_entry;
734 name_hbox.remove (*tmp);
736 /* put the name label back */
738 name_hbox.pack_end (name_label);
743 TrackViewList const & allviews = _editor.get_track_views ();
744 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
746 if (i != allviews.end()) {
749 if (++i == allviews.end()) {
753 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
755 if (rtav && rtav->route()->record_enabled()) {
759 if (!(*i)->hidden()) {
766 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
767 _editor.ensure_time_axis_view_is_visible (**i, false);
768 (*i)->begin_name_edit ();
771 } else if (edit_prev) {
773 TrackViewList const & allviews = _editor.get_track_views ();
774 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
776 if (i != allviews.begin()) {
778 if (i == allviews.begin()) {
784 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
786 if (rtav && rtav->route()->record_enabled()) {
790 if (!(*i)->hidden()) {
797 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
798 _editor.ensure_time_axis_view_is_visible (**i, false);
799 (*i)->begin_name_edit ();
805 TimeAxisView::name_entry_changed ()
810 TimeAxisView::can_edit_name () const
816 TimeAxisView::conditionally_add_to_selection ()
818 Selection& s (_editor.get_selection ());
820 if (!s.selected (this)) {
821 _editor.set_selected_track (*this, Selection::Set);
826 TimeAxisView::popup_display_menu (guint32 when)
828 conditionally_add_to_selection ();
830 build_display_menu ();
831 display_menu->popup (1, when);
835 TimeAxisView::set_selected (bool yn)
837 if (can_edit_name() && name_entry && name_entry->get_visible()) {
838 end_name_edit (RESPONSE_CANCEL);
841 if (yn == _selected) {
845 Selectable::set_selected (yn);
848 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
849 time_axis_frame.set_name ("MixerStripSelectedFrame");
850 controls_ebox.set_name (controls_base_selected_name);
851 controls_vbox.set_name (controls_base_selected_name);
852 time_axis_vbox.set_name (controls_base_selected_name);
854 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
855 time_axis_frame.set_name (controls_base_unselected_name);
856 controls_ebox.set_name (controls_base_unselected_name);
857 controls_vbox.set_name (controls_base_unselected_name);
858 time_axis_vbox.set_name (controls_base_unselected_name);
862 /* children will be set for the yn=true case. but when deselecting
863 the editor only has a list of top-level trackviews, so we
864 have to do this here.
867 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
868 (*i)->set_selected (false);
872 time_axis_frame.show();
877 TimeAxisView::build_display_menu ()
879 using namespace Menu_Helpers;
883 display_menu = new Menu;
884 display_menu->set_name ("ArdourContextMenu");
886 // Just let implementing classes define what goes into the manu
890 TimeAxisView::set_samples_per_pixel (double fpp)
892 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
893 (*i)->set_samples_per_pixel (fpp);
898 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
900 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
901 (*i)->show_timestretch (start, end, layers, layer);
906 TimeAxisView::hide_timestretch ()
908 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
909 (*i)->hide_timestretch ();
914 TimeAxisView::show_selection (TimeSelection& ts)
919 SelectionRect *rect; time_axis_frame.show();
922 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
923 (*i)->show_selection (ts);
926 if (selection_group->visible ()) {
927 while (!used_selection_rects.empty()) {
928 free_selection_rects.push_front (used_selection_rects.front());
929 used_selection_rects.pop_front();
930 free_selection_rects.front()->rect->hide();
931 free_selection_rects.front()->start_trim->hide();
932 free_selection_rects.front()->end_trim->hide();
934 selection_group->hide();
937 selection_group->show();
938 selection_group->raise_to_top();
940 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
941 framepos_t start, end;
946 cnt = end - start + 1;
948 rect = get_selection_rect ((*i).id);
950 x1 = _editor.sample_to_pixel (start);
951 x2 = _editor.sample_to_pixel (start + cnt - 1);
952 y2 = current_height() - 1;
954 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
956 // trim boxes are at the top for selections
959 rect->start_trim->set (ArdourCanvas::Rect (x1, 0, x1 + trim_handle_size, y2));
960 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
962 rect->start_trim->show();
963 rect->end_trim->show();
965 rect->start_trim->hide();
966 rect->end_trim->hide();
970 used_selection_rects.push_back (rect);
975 TimeAxisView::reshow_selection (TimeSelection& ts)
979 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
980 (*i)->show_selection (ts);
985 TimeAxisView::hide_selection ()
987 if (selection_group->visible ()) {
988 while (!used_selection_rects.empty()) {
989 free_selection_rects.push_front (used_selection_rects.front());
990 used_selection_rects.pop_front();
991 free_selection_rects.front()->rect->hide();
992 free_selection_rects.front()->start_trim->hide();
993 free_selection_rects.front()->end_trim->hide();
995 selection_group->hide();
998 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
999 (*i)->hide_selection ();
1004 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
1006 /* find the selection rect this is for. we have the item corresponding to one
1007 of the trim handles.
1010 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1011 if ((*i)->start_trim == item || (*i)->end_trim == item) {
1013 /* make one trim handle be "above" the other so that if they overlap,
1014 the top one is the one last used.
1017 (*i)->rect->raise_to_top ();
1018 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
1019 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
1027 TimeAxisView::get_selection_rect (uint32_t id)
1029 SelectionRect *rect;
1031 /* check to see if we already have a visible rect for this particular selection ID */
1033 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1034 if ((*i)->id == id) {
1039 /* ditto for the free rect list */
1041 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1042 if ((*i)->id == id) {
1043 SelectionRect* ret = (*i);
1044 free_selection_rects.erase (i);
1049 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
1051 if (free_selection_rects.empty()) {
1053 rect = new SelectionRect;
1055 rect->rect = new ArdourCanvas::Rectangle (selection_group);
1056 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
1057 rect->rect->set_outline (false);
1058 rect->rect->set_fill_color (ARDOUR_UI::config()->color_mod ("selection rect", "selection rect"));
1060 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
1061 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
1062 rect->start_trim->set_outline (false);
1063 rect->start_trim->set_fill (false);
1065 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
1066 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
1067 rect->end_trim->set_outline (false);
1068 rect->end_trim->set_fill (false);
1070 free_selection_rects.push_front (rect);
1072 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
1073 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
1074 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
1077 rect = free_selection_rects.front();
1079 free_selection_rects.pop_front();
1083 struct null_deleter { void operator()(void const *) const {} };
1086 TimeAxisView::is_child (TimeAxisView* tav)
1088 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1092 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1094 children.push_back (child);
1098 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1100 Children::iterator i;
1102 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1107 /** Get selectable things within a given range.
1108 * @param start Start time in session frames.
1109 * @param end End time in session frames.
1110 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1111 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1112 * @param result Filled in with selectable things.
1115 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/, bool /*within*/)
1121 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1127 TimeAxisView::add_ghost (RegionView* rv)
1129 GhostRegion* gr = rv->add_ghost (*this);
1132 ghosts.push_back(gr);
1137 TimeAxisView::remove_ghost (RegionView* rv)
1139 rv->remove_ghost_in (*this);
1143 TimeAxisView::erase_ghost (GhostRegion* gr)
1145 if (in_destructor) {
1149 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1158 TimeAxisView::touched (double top, double bot)
1160 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1161 y_position is the "origin" or "top" of the track.
1164 double mybot = _y_position + current_height();
1166 return ((_y_position <= bot && _y_position >= top) ||
1167 ((mybot <= bot) && (top < mybot)) ||
1168 (mybot >= bot && _y_position < top));
1172 TimeAxisView::set_parent (TimeAxisView& p)
1178 TimeAxisView::reset_height ()
1180 set_height (height);
1182 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1183 (*i)->set_height ((*i)->height);
1188 TimeAxisView::compute_heights ()
1190 // TODO this function should be re-evaluated when font-scaling changes (!)
1191 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1192 Gtk::Table one_row_table (1, 1);
1193 ArdourButton* test_button = manage (new ArdourButton);
1194 const int border_width = 2;
1195 const int frame_height = 2;
1196 extra_height = (2 * border_width) + frame_height;
1198 window.add (one_row_table);
1199 test_button->set_name ("mute button");
1200 test_button->set_text (S_("Mute|M"));
1201 test_button->set_tweaks (ArdourButton::TrackHeader);
1203 one_row_table.set_border_width (border_width);
1204 one_row_table.set_row_spacings (2);
1205 one_row_table.set_col_spacings (2);
1207 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1208 one_row_table.show_all ();
1210 Gtk::Requisition req(one_row_table.size_request ());
1211 button_height = req.height;
1215 TimeAxisView::color_handler ()
1217 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1221 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1223 (*i)->rect->set_fill_color (ARDOUR_UI::config()->color_mod ("selection rect", "selection rect"));
1224 (*i)->rect->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1226 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->color ("selection"));
1227 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1229 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->color ("selection"));
1230 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1233 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1235 (*i)->rect->set_fill_color (ARDOUR_UI::config()->color_mod ("selection rect", "selection rect"));
1236 (*i)->rect->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1238 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->color ("selection"));
1239 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1241 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->color ("selection"));
1242 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1246 /** @return Pair: TimeAxisView, layer index.
1247 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1248 * does. @param y is an offset from the top of the trackview area.
1250 * If the covering object is a child axis, then the child is returned.
1251 * TimeAxisView is 0 otherwise.
1253 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1254 * and is in stacked or expanded * region display mode, otherwise 0.
1256 std::pair<TimeAxisView*, double>
1257 TimeAxisView::covers_y_position (double y) const
1260 return std::make_pair ((TimeAxisView *) 0, 0);
1263 if (_y_position <= y && y < (_y_position + height)) {
1265 /* work out the layer index if appropriate */
1267 switch (layer_display ()) {
1273 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1274 /* clamp to max layers to be on the safe side; sometimes the above calculation
1275 returns a too-high value */
1276 if (l >= view()->layers ()) {
1277 l = view()->layers() - 1;
1283 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1285 if (l >= (view()->layers() - 0.5)) {
1286 l = view()->layers() - 0.5;
1292 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1295 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1297 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1303 return std::make_pair ((TimeAxisView *) 0, 0);
1307 TimeAxisView::covered_by_y_range (double y0, double y1) const
1313 /* if either the top or bottom of the axisview is in the vertical
1314 * range, we cover it.
1317 if ((y0 < _y_position && y1 < _y_position) ||
1318 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1322 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1323 if ((*i)->covered_by_y_range (y0, y1)) {
1332 TimeAxisView::preset_height (Height h)
1336 return (button_height * 2) + extra_height + 260;
1338 return (button_height * 2) + extra_height + 160;
1340 return (button_height * 2) + extra_height + 60;
1342 return (button_height * 2) + extra_height + 10;
1344 return button_height + extra_height;
1347 abort(); /* NOTREACHED */
1351 /** @return Child time axis views that are not hidden */
1352 TimeAxisView::Children
1353 TimeAxisView::get_child_list ()
1357 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1358 if (!(*i)->hidden()) {
1367 TimeAxisView::build_size_menu ()
1369 if (_size_menu && _size_menu->gobj ()) {
1375 using namespace Menu_Helpers;
1377 _size_menu = new Menu;
1378 _size_menu->set_name ("ArdourContextMenu");
1379 MenuList& items = _size_menu->items();
1381 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1382 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1383 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1384 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1385 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1389 TimeAxisView::reset_visual_state ()
1391 /* this method is not required to trigger a global redraw */
1393 string str = gui_property ("height");
1396 set_height (atoi (str));
1398 set_height (preset_height (HeightNormal));
1403 TrackViewList::filter_to_unique_playlists ()
1405 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1408 for (iterator i = begin(); i != end(); ++i) {
1409 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1411 /* not a route: include it anyway */
1414 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1416 if (playlists.insert (t->playlist()).second) {
1417 /* playlist not seen yet */
1421 /* not a track: include it anyway */