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; // TODO adjust with font-scaling on style-change
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>();
82 TimeAxisView::TimeAxisView (ARDOUR::Session* sess, PublicEditor& ed, TimeAxisView* rent, Canvas& /*canvas*/)
84 , controls_table (3, 3)
85 , controls_button_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
86 , _name_editing (false)
93 , in_destructor (false)
101 , _effective_height (0)
102 , _resize_drag_start (-1)
103 , _did_resize (false)
104 , _preresize_cursor (0)
105 , _have_preresize_cursor (false)
106 , _ebox_release_can_act (true)
108 if (!controls_meters_size_group) {
109 controls_meters_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
111 if (!midi_scroomer_size_group) {
112 midi_scroomer_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
114 if (extra_height == 0) {
118 _canvas_display = new ArdourCanvas::Container (ed.get_trackview_group ());
119 CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
120 _canvas_display->hide(); // reveal as needed
122 _canvas_separator = new ArdourCanvas::Line(_canvas_display);
123 CANVAS_DEBUG_NAME (_canvas_separator, "separator for TAV");
124 _canvas_separator->set (ArdourCanvas::Duple(0.0, 0.0), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, 0.0));
125 _canvas_separator->set_outline_color(ArdourCanvas::rgba_to_color (0, 0, 0, 1.0));
126 _canvas_separator->set_outline_width(1.0);
127 _canvas_separator->hide();
129 selection_group = new ArdourCanvas::Container (_canvas_display);
130 CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
131 selection_group->set_data (X_("timeselection"), (void *) 1);
132 selection_group->hide();
134 _ghost_group = new ArdourCanvas::Container (_canvas_display);
135 CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
136 _ghost_group->lower_to_bottom();
137 _ghost_group->show();
139 name_label.set_name ("TrackLabel");
140 name_label.set_alignment (0.0, 0.5);
141 name_label.set_width_chars (12);
142 ARDOUR_UI::instance()->set_tip (name_label, _("Track/Bus name (double click to edit)"));
144 Gtk::Entry* an_entry = new Gtkmm2ext::FocusEntry;
145 an_entry->set_name ("EditorTrackNameDisplay");
146 Gtk::Requisition req;
147 an_entry->size_request (req);
148 name_label.set_size_request (-1, req.height);
149 name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
152 name_hbox.pack_end (name_label, true, true);
154 // set min. track-header width if fader is not visible
155 name_hbox.set_size_request(name_width_px, -1);
160 controls_table.set_row_spacings (2);
161 controls_table.set_col_spacings (2);
162 controls_table.set_border_width (2);
164 if (ARDOUR::Profile->get_mixbus() ) {
165 controls_table.attach (name_hbox, 4, 5, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
167 controls_table.attach (name_hbox, 1, 2, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
169 controls_table.show_all ();
170 controls_table.set_no_show_all ();
172 controls_vbox.pack_start (controls_table, false, false);
173 controls_vbox.show ();
175 top_hbox.pack_start (controls_vbox, true, true);
178 controls_ebox.add (time_axis_hbox);
179 controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
180 Gdk::BUTTON_RELEASE_MASK|
181 Gdk::POINTER_MOTION_MASK|
182 Gdk::ENTER_NOTIFY_MASK|
183 Gdk::LEAVE_NOTIFY_MASK|
185 controls_ebox.set_flags (CAN_FOCUS);
187 /* note that this handler connects *before* the default handler */
188 controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
189 controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
190 controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
191 controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
192 controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
193 controls_ebox.show ();
195 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
196 time_axis_frame.add(top_hbox);
197 time_axis_frame.show();
199 HSeparator* separator = manage (new HSeparator());
200 separator->set_name("TrackSeparator");
201 separator->set_size_request(-1, 1);
204 scroomer_placeholder.set_size_request (-1, -1);
205 scroomer_placeholder.show();
206 midi_scroomer_size_group->add_widget (scroomer_placeholder);
208 time_axis_vbox.pack_start (*separator, false, false);
209 time_axis_vbox.pack_start (time_axis_frame, true, true);
210 time_axis_vbox.show();
211 time_axis_hbox.pack_start (time_axis_vbox, true, true);
212 time_axis_hbox.show();
213 top_hbox.pack_start (scroomer_placeholder, false, false); // OR pack_end to move after meters ?
215 ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
217 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&TimeAxisView::erase_ghost, this, _1), gui_context());
220 TimeAxisView::~TimeAxisView()
222 CatchDeletion (this);
224 in_destructor = true;
226 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
230 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
232 delete (*i)->start_trim;
233 delete (*i)->end_trim;
237 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
239 delete (*i)->start_trim;
240 delete (*i)->end_trim;
243 delete selection_group;
246 delete _canvas_display;
256 TimeAxisView::hide ()
262 _canvas_display->hide ();
263 _canvas_separator->hide ();
265 if (control_parent) {
266 control_parent->remove (TOP_LEVEL_WIDGET);
273 /* now hide children */
275 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
279 /* if its hidden, it cannot be selected */
280 _editor.get_selection().remove (this);
281 /* and neither can its regions */
282 _editor.get_selection().remove_regions (this);
287 /** Display this TimeAxisView as the nth component of the parent box, at y.
289 * @param y y position.
290 * @param nth index for this TimeAxisView, increased if this view has children.
291 * @param parent parent component.
292 * @return height of this TimeAxisView.
295 TimeAxisView::show_at (double y, int& nth, VBox *parent)
297 if (control_parent) {
298 control_parent->reorder_child (TOP_LEVEL_WIDGET, nth);
300 control_parent = parent;
301 parent->pack_start (TOP_LEVEL_WIDGET, false, false);
302 parent->reorder_child (TOP_LEVEL_WIDGET, nth);
307 if (_y_position != y) {
308 _canvas_display->set_y_position (y);
312 _canvas_display->raise_to_top ();
313 _canvas_display->show ();
317 _effective_height = current_height ();
319 /* now show relevant children */
321 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
322 if ((*i)->marked_for_display()) {
324 _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
330 /* put separator at the bottom of this time axis view */
332 _canvas_separator->set (ArdourCanvas::Duple(0, height), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, height));
333 _canvas_separator->lower_to_bottom ();
334 _canvas_separator->show ();
336 return _effective_height;
340 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
342 switch (ev->direction) {
344 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
345 /* See Editor::_stepping_axis_view for notes on this hack */
346 Editor& e = dynamic_cast<Editor&> (_editor);
347 if (!e.stepping_axis_view ()) {
348 e.set_stepping_axis_view (this);
350 e.stepping_axis_view()->step_height (false);
355 case GDK_SCROLL_DOWN:
356 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
357 /* See Editor::_stepping_axis_view for notes on this hack */
358 Editor& e = dynamic_cast<Editor&> (_editor);
359 if (!e.stepping_axis_view ()) {
360 e.set_stepping_axis_view (this);
362 e.stepping_axis_view()->step_height (true);
368 /* no handling for left/right, yet */
372 /* Just forward to the normal canvas scroll method. The coordinate
373 systems are different but since the canvas is always larger than the
374 track headers, and aligned with the trackview area, this will work.
376 In the not too distant future this layout is going away anyway and
377 headers will be on the canvas.
379 return _editor.canvas_scroll_event (ev, false);
383 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
385 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
386 /* see if it is inside the name label */
387 if (name_label.is_ancestor (controls_ebox)) {
390 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
391 Gtk::Allocation a = name_label.get_allocation ();
392 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
394 _ebox_release_can_act = false;
401 _ebox_release_can_act = true;
403 if (maybe_set_cursor (event->y) > 0) {
404 _resize_drag_start = event->y_root;
411 TimeAxisView::idle_resize (int32_t h)
413 set_height (std::max(0, h));
418 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
420 if (_resize_drag_start >= 0) {
422 /* (ab)use the DragManager to do autoscrolling - basically we
423 * are pretending that the drag is taking place over the canvas
424 * (which perhaps in the glorious future, when track headers
425 * and the canvas are unified, will actually be true.)
428 _editor.maybe_autoscroll (false, true, true);
430 /* now schedule the actual TAV resize */
431 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
432 _editor.add_to_idle_resize (this, delta);
433 _resize_drag_start = ev->y_root;
436 /* not dragging but ... */
437 maybe_set_cursor (ev->y);
440 gdk_event_request_motions(ev);
445 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
447 if (_have_preresize_cursor) {
448 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
449 _have_preresize_cursor = false;
455 TimeAxisView::maybe_set_cursor (int y)
457 /* XXX no Gtkmm Gdk::Window::get_cursor() */
458 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
460 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
462 /* y-coordinate in lower 25% */
464 if (!_have_preresize_cursor) {
465 _preresize_cursor = gdk_window_get_cursor (win->gobj());
466 _have_preresize_cursor = true;
467 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
472 } else if (_have_preresize_cursor) {
473 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
474 _have_preresize_cursor = false;
483 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
485 if (_resize_drag_start >= 0) {
486 if (_have_preresize_cursor) {
487 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
488 _preresize_cursor = 0;
489 _have_preresize_cursor = false;
491 _editor.stop_canvas_autoscroll ();
492 _resize_drag_start = -1;
495 // don't change selection
500 if (!_ebox_release_can_act) {
504 switch (ev->button) {
506 selection_click (ev);
510 popup_display_menu (ev->time);
518 TimeAxisView::selection_click (GdkEventButton* ev)
520 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
521 _editor.set_selected_track (*this, op, false);
525 /** Steps through the defined heights for this TrackView.
526 * @param coarser true if stepping should decrease in size, otherwise false.
529 TimeAxisView::step_height (bool coarser)
531 static const uint32_t step = 25;
535 if (height <= preset_height (HeightSmall)) {
537 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
538 set_height_enum (HeightSmall);
540 set_height (height - step);
545 if (height <= preset_height(HeightSmall)) {
546 set_height_enum (HeightNormal);
548 set_height (height + step);
555 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
557 if (apply_to_selection) {
558 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
560 set_height (preset_height (h));
565 TimeAxisView::set_height (uint32_t h)
567 if (h < preset_height (HeightSmall)) {
568 h = preset_height (HeightSmall);
571 TOP_LEVEL_WIDGET.property_height_request () = h;
575 snprintf (buf, sizeof (buf), "%u", height);
576 set_gui_property ("height", buf);
578 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
582 if (selection_group->visible ()) {
583 /* resize the selection rect */
584 show_selection (_editor.get_selection().time);
587 _editor.override_visible_track_count ();
591 TimeAxisView::name_entry_key_press (GdkEventKey* ev)
593 /* steal escape, tabs from GTK */
595 switch (ev->keyval) {
597 case GDK_ISO_Left_Tab:
605 TimeAxisView::name_entry_key_release (GdkEventKey* ev)
607 TrackViewList::iterator i;
609 switch (ev->keyval) {
611 end_name_edit (RESPONSE_CANCEL);
614 /* Shift+Tab Keys Pressed. Note that for Shift+Tab, GDK actually
615 * generates a different ev->keyval, rather than setting
618 case GDK_ISO_Left_Tab:
619 end_name_edit (RESPONSE_APPLY);
623 end_name_edit (RESPONSE_ACCEPT);
633 TimeAxisView::name_entry_focus_out (GdkEventFocus*)
635 end_name_edit (RESPONSE_OK);
640 TimeAxisView::begin_name_edit ()
646 if (can_edit_name()) {
648 name_entry = manage (new Gtkmm2ext::FocusEntry);
650 name_entry->set_width_chars(8); // min width, entry expands
652 name_entry->set_name ("EditorTrackNameDisplay");
653 name_entry->signal_key_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_press), false);
654 name_entry->signal_key_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_release), false);
655 name_entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_focus_out));
656 name_entry->set_text (name_label.get_text());
657 name_entry->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisView::end_name_edit), RESPONSE_OK));
659 if (name_label.is_ancestor (name_hbox)) {
660 name_hbox.remove (name_label);
663 name_hbox.pack_end (*name_entry, true, true);
666 name_entry->select_region (0, -1);
667 name_entry->set_state (STATE_SELECTED);
668 name_entry->grab_focus ();
669 name_entry->start_editing (0);
674 TimeAxisView::end_name_edit (int response)
680 bool edit_next = false;
681 bool edit_prev = false;
684 case RESPONSE_CANCEL:
687 name_entry_changed ();
689 case RESPONSE_ACCEPT:
690 name_entry_changed ();
693 name_entry_changed ();
697 /* this will delete the name_entry. but it will also drop focus, which
698 * will cause another callback to this function, so set name_entry = 0
699 * first to ensure we don't double-remove etc. etc.
702 Gtk::Entry* tmp = name_entry;
704 name_hbox.remove (*tmp);
706 /* put the name label back */
708 name_hbox.pack_end (name_label);
713 TrackViewList const & allviews = _editor.get_track_views ();
714 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
716 if (i != allviews.end()) {
719 if (++i == allviews.end()) {
723 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
725 if (rtav && rtav->route()->record_enabled()) {
729 if (!(*i)->hidden()) {
736 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
737 _editor.ensure_time_axis_view_is_visible (**i, false);
738 (*i)->begin_name_edit ();
741 } else if (edit_prev) {
743 TrackViewList const & allviews = _editor.get_track_views ();
744 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
746 if (i != allviews.begin()) {
748 if (i == allviews.begin()) {
754 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
756 if (rtav && rtav->route()->record_enabled()) {
760 if (!(*i)->hidden()) {
767 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
768 _editor.ensure_time_axis_view_is_visible (**i, false);
769 (*i)->begin_name_edit ();
775 TimeAxisView::name_entry_changed ()
780 TimeAxisView::can_edit_name () const
786 TimeAxisView::conditionally_add_to_selection ()
788 Selection& s (_editor.get_selection ());
790 if (!s.selected (this)) {
791 _editor.set_selected_track (*this, Selection::Set);
796 TimeAxisView::popup_display_menu (guint32 when)
798 conditionally_add_to_selection ();
800 build_display_menu ();
801 display_menu->popup (1, when);
805 TimeAxisView::set_selected (bool yn)
807 if (can_edit_name() && name_entry && name_entry->get_visible()) {
808 end_name_edit (RESPONSE_CANCEL);
811 if (yn == _selected) {
815 Selectable::set_selected (yn);
818 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
819 time_axis_frame.set_name ("MixerStripSelectedFrame");
820 controls_ebox.set_name (controls_base_selected_name);
821 controls_vbox.set_name (controls_base_selected_name);
822 time_axis_vbox.set_name (controls_base_selected_name);
824 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
825 time_axis_frame.set_name (controls_base_unselected_name);
826 controls_ebox.set_name (controls_base_unselected_name);
827 controls_vbox.set_name (controls_base_unselected_name);
828 time_axis_vbox.set_name (controls_base_unselected_name);
832 /* children will be set for the yn=true case. but when deselecting
833 the editor only has a list of top-level trackviews, so we
834 have to do this here.
837 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
838 (*i)->set_selected (false);
842 time_axis_frame.show();
847 TimeAxisView::build_display_menu ()
849 using namespace Menu_Helpers;
853 display_menu = new Menu;
854 display_menu->set_name ("ArdourContextMenu");
856 // Just let implementing classes define what goes into the manu
860 TimeAxisView::set_samples_per_pixel (double fpp)
862 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
863 (*i)->set_samples_per_pixel (fpp);
868 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
870 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
871 (*i)->show_timestretch (start, end, layers, layer);
876 TimeAxisView::hide_timestretch ()
878 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
879 (*i)->hide_timestretch ();
884 TimeAxisView::show_selection (TimeSelection& ts)
889 SelectionRect *rect; time_axis_frame.show();
892 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
893 (*i)->show_selection (ts);
896 if (selection_group->visible ()) {
897 while (!used_selection_rects.empty()) {
898 free_selection_rects.push_front (used_selection_rects.front());
899 used_selection_rects.pop_front();
900 free_selection_rects.front()->rect->hide();
901 free_selection_rects.front()->start_trim->hide();
902 free_selection_rects.front()->end_trim->hide();
904 selection_group->hide();
907 selection_group->show();
908 selection_group->raise_to_top();
910 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
911 framepos_t start, end;
916 cnt = end - start + 1;
918 rect = get_selection_rect ((*i).id);
920 x1 = _editor.sample_to_pixel (start);
921 x2 = _editor.sample_to_pixel (start + cnt - 1);
922 y2 = current_height() - 1;
924 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
926 // trim boxes are at the top for selections
929 rect->start_trim->set (ArdourCanvas::Rect (x1, 0, x1 + trim_handle_size, y2));
930 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
932 rect->start_trim->show();
933 rect->end_trim->show();
935 rect->start_trim->hide();
936 rect->end_trim->hide();
940 used_selection_rects.push_back (rect);
945 TimeAxisView::reshow_selection (TimeSelection& ts)
949 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
950 (*i)->show_selection (ts);
955 TimeAxisView::hide_selection ()
957 if (selection_group->visible ()) {
958 while (!used_selection_rects.empty()) {
959 free_selection_rects.push_front (used_selection_rects.front());
960 used_selection_rects.pop_front();
961 free_selection_rects.front()->rect->hide();
962 free_selection_rects.front()->start_trim->hide();
963 free_selection_rects.front()->end_trim->hide();
965 selection_group->hide();
968 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
969 (*i)->hide_selection ();
974 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
976 /* find the selection rect this is for. we have the item corresponding to one
980 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
981 if ((*i)->start_trim == item || (*i)->end_trim == item) {
983 /* make one trim handle be "above" the other so that if they overlap,
984 the top one is the one last used.
987 (*i)->rect->raise_to_top ();
988 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
989 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
997 TimeAxisView::get_selection_rect (uint32_t id)
1001 /* check to see if we already have a visible rect for this particular selection ID */
1003 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1004 if ((*i)->id == id) {
1009 /* ditto for the free rect list */
1011 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1012 if ((*i)->id == id) {
1013 SelectionRect* ret = (*i);
1014 free_selection_rects.erase (i);
1019 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
1021 if (free_selection_rects.empty()) {
1023 rect = new SelectionRect;
1025 rect->rect = new ArdourCanvas::TimeRectangle (selection_group);
1026 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
1027 rect->rect->set_outline (false);
1028 rect->rect->set_fill_color (ARDOUR_UI::config()->color_mod ("selection rect", "selection rect"));
1030 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
1031 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
1032 rect->start_trim->set_outline (false);
1033 rect->start_trim->set_fill (false);
1035 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
1036 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
1037 rect->end_trim->set_outline (false);
1038 rect->end_trim->set_fill (false);
1040 free_selection_rects.push_front (rect);
1042 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
1043 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
1044 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
1047 rect = free_selection_rects.front();
1049 free_selection_rects.pop_front();
1053 struct null_deleter { void operator()(void const *) const {} };
1056 TimeAxisView::is_child (TimeAxisView* tav)
1058 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1062 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1064 children.push_back (child);
1068 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1070 Children::iterator i;
1072 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1077 /** Get selectable things within a given range.
1078 * @param start Start time in session frames.
1079 * @param end End time in session frames.
1080 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1081 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1082 * @param result Filled in with selectable things.
1085 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/)
1091 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1097 TimeAxisView::add_ghost (RegionView* rv)
1099 GhostRegion* gr = rv->add_ghost (*this);
1102 ghosts.push_back(gr);
1107 TimeAxisView::remove_ghost (RegionView* rv)
1109 rv->remove_ghost_in (*this);
1113 TimeAxisView::erase_ghost (GhostRegion* gr)
1115 if (in_destructor) {
1119 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1128 TimeAxisView::touched (double top, double bot)
1130 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1131 y_position is the "origin" or "top" of the track.
1134 double mybot = _y_position + current_height();
1136 return ((_y_position <= bot && _y_position >= top) ||
1137 ((mybot <= bot) && (top < mybot)) ||
1138 (mybot >= bot && _y_position < top));
1142 TimeAxisView::set_parent (TimeAxisView& p)
1148 TimeAxisView::reset_height ()
1150 set_height (height);
1152 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1153 (*i)->set_height ((*i)->height);
1158 TimeAxisView::compute_heights ()
1160 // TODO this function should be re-evaluated when font-scaling changes (!)
1161 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1162 Gtk::Table one_row_table (1, 1);
1163 ArdourButton* test_button = manage (new ArdourButton);
1164 const int border_width = 2;
1165 const int frame_height = 2;
1166 extra_height = (2 * border_width) + frame_height;
1168 window.add (one_row_table);
1169 test_button->set_name ("mute button");
1170 test_button->set_text (_("M"));
1171 test_button->set_tweaks (ArdourButton::TrackHeader);
1173 one_row_table.set_border_width (border_width);
1174 one_row_table.set_row_spacings (2);
1175 one_row_table.set_col_spacings (2);
1177 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1178 one_row_table.show_all ();
1180 Gtk::Requisition req(one_row_table.size_request ());
1181 button_height = req.height;
1185 TimeAxisView::color_handler ()
1187 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1191 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1193 (*i)->rect->set_fill_color (ARDOUR_UI::config()->color_mod ("selection rect", "selection rect"));
1194 (*i)->rect->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1196 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->color ("selection"));
1197 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1199 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->color ("selection"));
1200 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1203 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1205 (*i)->rect->set_fill_color (ARDOUR_UI::config()->color_mod ("selection rect", "selection rect"));
1206 (*i)->rect->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1208 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->color ("selection"));
1209 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1211 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->color ("selection"));
1212 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1216 /** @return Pair: TimeAxisView, layer index.
1217 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1218 * does. @param y is an offset from the top of the trackview area.
1220 * If the covering object is a child axis, then the child is returned.
1221 * TimeAxisView is 0 otherwise.
1223 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1224 * and is in stacked or expanded * region display mode, otherwise 0.
1226 std::pair<TimeAxisView*, double>
1227 TimeAxisView::covers_y_position (double y) const
1230 return std::make_pair ((TimeAxisView *) 0, 0);
1233 if (_y_position <= y && y < (_y_position + height)) {
1235 /* work out the layer index if appropriate */
1237 switch (layer_display ()) {
1243 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1244 /* clamp to max layers to be on the safe side; sometimes the above calculation
1245 returns a too-high value */
1246 if (l >= view()->layers ()) {
1247 l = view()->layers() - 1;
1253 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1255 if (l >= (view()->layers() - 0.5)) {
1256 l = view()->layers() - 0.5;
1262 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1265 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1267 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1273 return std::make_pair ((TimeAxisView *) 0, 0);
1277 TimeAxisView::covered_by_y_range (double y0, double y1) const
1283 /* if either the top or bottom of the axisview is in the vertical
1284 * range, we cover it.
1287 if ((y0 < _y_position && y1 < _y_position) ||
1288 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1292 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1293 if ((*i)->covered_by_y_range (y0, y1)) {
1302 TimeAxisView::preset_height (Height h)
1306 return (button_height * 2) + extra_height + 260;
1308 return (button_height * 2) + extra_height + 160;
1310 return (button_height * 2) + extra_height + 60;
1312 return (button_height * 2) + extra_height + 10;
1314 return button_height + extra_height;
1317 abort(); /* NOTREACHED */
1321 /** @return Child time axis views that are not hidden */
1322 TimeAxisView::Children
1323 TimeAxisView::get_child_list ()
1327 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1328 if (!(*i)->hidden()) {
1337 TimeAxisView::build_size_menu ()
1339 if (_size_menu && _size_menu->gobj ()) {
1345 using namespace Menu_Helpers;
1347 _size_menu = new Menu;
1348 _size_menu->set_name ("ArdourContextMenu");
1349 MenuList& items = _size_menu->items();
1351 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1352 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1353 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1354 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1355 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1359 TimeAxisView::reset_visual_state ()
1361 /* this method is not required to trigger a global redraw */
1363 string str = gui_property ("height");
1366 set_height (atoi (str));
1368 set_height (preset_height (HeightNormal));
1373 TrackViewList::filter_to_unique_playlists ()
1375 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1378 for (iterator i = begin(); i != end(); ++i) {
1379 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1381 /* not a route: include it anyway */
1384 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1386 if (playlists.insert (t->playlist()).second) {
1387 /* playlist not seen yet */
1391 /* not a track: include it anyway */