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_dialog.h"
45 #include "gui_thread.h"
46 #include "public_editor.h"
47 #include "time_axis_view.h"
48 #include "region_view.h"
49 #include "ghostregion.h"
50 #include "selection.h"
52 #include "rgb_macros.h"
54 #include "streamview.h"
55 #include "editor_drag.h"
58 #include "ui_config.h"
65 using namespace ARDOUR;
66 using namespace ARDOUR_UI_UTILS;
68 using namespace Editing;
69 using namespace ArdourCanvas;
70 using Gtkmm2ext::Keyboard;
72 #define TOP_LEVEL_WIDGET controls_ebox
74 const double trim_handle_size = 6.0; /* pixels */
75 uint32_t TimeAxisView::button_height = 0;
76 uint32_t TimeAxisView::extra_height = 0;
77 int const TimeAxisView::_max_order = 512;
78 unsigned int TimeAxisView::name_width_px = 100;
79 PBD::Signal1<void,TimeAxisView*> TimeAxisView::CatchDeletion;
80 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::controls_meters_size_group = Glib::RefPtr<Gtk::SizeGroup>();
81 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::midi_scroomer_size_group = Glib::RefPtr<Gtk::SizeGroup>();
84 TimeAxisView::setup_sizes()
86 name_width_px = ceilf (100.f * UIConfiguration::instance().get_ui_scale());
89 TimeAxisView::TimeAxisView (ARDOUR::Session* sess, PublicEditor& ed, TimeAxisView* rent, Canvas& /*canvas*/)
91 , controls_table (5, 4)
92 , controls_button_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
93 , _name_editing (false)
100 , in_destructor (false)
102 , _canvas_display (0)
106 , ending_name_edit (false)
107 , by_popup_menu (false)
110 , _effective_height (0)
111 , _resize_drag_start (-1)
112 , _did_resize (false)
113 , _preresize_cursor (0)
114 , _have_preresize_cursor (false)
115 , _ebox_release_can_act (true)
117 if (!controls_meters_size_group) {
118 controls_meters_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
120 if (!midi_scroomer_size_group) {
121 midi_scroomer_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
123 if (extra_height == 0) {
127 _canvas_display = new ArdourCanvas::Container (ed.get_trackview_group ());
128 CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
129 _canvas_display->hide(); // reveal as needed
131 _canvas_separator = new ArdourCanvas::Line(_canvas_display);
132 CANVAS_DEBUG_NAME (_canvas_separator, "separator for TAV");
133 _canvas_separator->set (ArdourCanvas::Duple(0.0, 0.0), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, 0.0));
134 _canvas_separator->set_outline_color(ArdourCanvas::rgba_to_color (0, 0, 0, 1.0));
135 _canvas_separator->set_outline_width(1.0);
136 _canvas_separator->hide();
138 selection_group = new ArdourCanvas::Container (_canvas_display);
139 CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
140 selection_group->set_data (X_("timeselection"), (void *) 1);
141 selection_group->hide();
143 _ghost_group = new ArdourCanvas::Container (_canvas_display);
144 CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
145 _ghost_group->lower_to_bottom();
146 _ghost_group->show();
148 name_label.set_name ("TrackLabel");
149 name_label.set_alignment (0.0, 0.5);
150 name_label.set_width_chars (12);
151 set_tooltip (name_label, _("Track/Bus name (double click to edit)"));
153 Gtk::Entry* an_entry = new Gtkmm2ext::FocusEntry;
154 an_entry->set_name ("EditorTrackNameDisplay");
155 Gtk::Requisition req;
156 an_entry->size_request (req);
157 name_label.set_size_request (-1, req.height);
158 name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
161 name_hbox.pack_end (name_label, true, true);
163 // set min. track-header width if fader is not visible
164 name_hbox.set_size_request(name_width_px, -1);
169 controls_table.set_row_spacings (2);
170 controls_table.set_col_spacings (2);
171 controls_table.set_border_width (2);
173 if (ARDOUR::Profile->get_mixbus() ) {
174 controls_table.attach (name_hbox, 4, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
176 controls_table.attach (name_hbox, 1, 2, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
178 controls_table.show_all ();
179 controls_table.set_no_show_all ();
181 controls_vbox.pack_start (controls_table, false, false);
182 controls_vbox.show ();
184 top_hbox.pack_start (controls_vbox, true, true);
187 controls_ebox.add (time_axis_hbox);
188 controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
189 Gdk::BUTTON_RELEASE_MASK|
190 Gdk::POINTER_MOTION_MASK|
191 Gdk::ENTER_NOTIFY_MASK|
192 Gdk::LEAVE_NOTIFY_MASK|
194 controls_ebox.set_flags (CAN_FOCUS);
196 /* note that this handler connects *before* the default handler */
197 controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
198 controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
199 controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
200 controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
201 controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
202 controls_ebox.show ();
204 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
205 time_axis_frame.add(top_hbox);
206 time_axis_frame.show();
208 HSeparator* separator = manage (new HSeparator());
209 separator->set_name("TrackSeparator");
210 separator->set_size_request(-1, 1);
213 scroomer_placeholder.set_size_request (-1, -1);
214 scroomer_placeholder.show();
215 midi_scroomer_size_group->add_widget (scroomer_placeholder);
217 time_axis_vbox.pack_start (*separator, false, false);
218 time_axis_vbox.pack_start (time_axis_frame, true, true);
219 time_axis_vbox.show();
220 time_axis_hbox.pack_start (time_axis_vbox, true, true);
221 time_axis_hbox.show();
222 top_hbox.pack_start (scroomer_placeholder, false, false); // OR pack_end to move after meters ?
224 UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
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) {
514 selection_click (ev);
519 popup_display_menu (ev->time);
527 TimeAxisView::selection_click (GdkEventButton* ev)
529 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
530 _editor.set_selected_track (*this, op, false);
534 /** Steps through the defined heights for this TrackView.
535 * @param coarser true if stepping should decrease in size, otherwise false.
538 TimeAxisView::step_height (bool coarser)
540 static const uint32_t step = 25;
544 if (height <= preset_height (HeightSmall)) {
546 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
547 set_height_enum (HeightSmall);
549 set_height (height - step);
554 if (height <= preset_height(HeightSmall)) {
555 set_height_enum (HeightNormal);
557 set_height (height + step);
564 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
566 if (apply_to_selection) {
567 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
569 set_height (preset_height (h));
574 TimeAxisView::set_height (uint32_t h, TrackHeightMode m)
577 if (m == TotalHeight) {
578 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
579 if ( !(*i)->hidden()) ++lanes;
584 if (h < preset_height (HeightSmall)) {
585 h = preset_height (HeightSmall);
588 TOP_LEVEL_WIDGET.property_height_request () = h;
592 snprintf (buf, sizeof (buf), "%u", height);
593 set_gui_property ("height", buf);
595 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
599 if (selection_group->visible ()) {
600 /* resize the selection rect */
601 show_selection (_editor.get_selection().time);
605 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
606 (*i)->set_height(h, OnlySelf);
610 _editor.override_visible_track_count ();
614 TimeAxisView::name_entry_key_press (GdkEventKey* ev)
616 /* steal escape, tabs from GTK */
618 switch (ev->keyval) {
620 case GDK_ISO_Left_Tab:
628 TimeAxisView::name_entry_key_release (GdkEventKey* ev)
630 TrackViewList::iterator i;
632 switch (ev->keyval) {
634 end_name_edit (RESPONSE_CANCEL);
637 case GDK_ISO_Left_Tab:
638 /* Shift+Tab Keys Pressed. Note that for Shift+Tab, GDK actually
639 * generates a different ev->keyval, rather than setting
642 end_name_edit (RESPONSE_APPLY);
646 end_name_edit (RESPONSE_ACCEPT);
656 TimeAxisView::name_entry_focus_out (GdkEventFocus*)
659 by_popup_menu = false;
662 end_name_edit (RESPONSE_OK);
667 TimeAxisView::name_entry_populate_popup (Gtk::Menu *)
669 by_popup_menu = true;
673 TimeAxisView::begin_name_edit ()
679 if (can_edit_name()) {
681 name_entry = manage (new Gtkmm2ext::FocusEntry);
683 name_entry->set_width_chars(8); // min width, entry expands
685 name_entry->set_name ("EditorTrackNameDisplay");
686 name_entry->signal_key_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_press), false);
687 name_entry->signal_key_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_release), false);
688 name_entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_focus_out));
689 name_entry->set_text (name_label.get_text());
690 name_entry->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisView::end_name_edit), RESPONSE_OK));
691 name_entry->signal_populate_popup().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_populate_popup));
693 if (name_label.is_ancestor (name_hbox)) {
694 name_hbox.remove (name_label);
697 name_hbox.pack_end (*name_entry, true, true);
700 name_entry->select_region (0, -1);
701 name_entry->set_state (STATE_SELECTED);
702 name_entry->grab_focus ();
703 name_entry->start_editing (0);
708 TimeAxisView::end_name_edit (int response)
714 if (ending_name_edit) {
715 /* already doing this, and focus out or other event has caused
716 us to re-enter this code.
721 PBD::Unwinder<bool> uw (ending_name_edit, true);
723 bool edit_next = false;
724 bool edit_prev = false;
727 case RESPONSE_CANCEL:
730 name_entry_changed ();
732 case RESPONSE_ACCEPT:
733 name_entry_changed ();
736 name_entry_changed ();
740 /* this will delete the name_entry. but it will also drop focus, which
741 * will cause another callback to this function, so set name_entry = 0
742 * first to ensure we don't double-remove etc. etc.
745 Gtk::Entry* tmp = name_entry;
747 name_hbox.remove (*tmp);
749 /* put the name label back */
751 name_hbox.pack_end (name_label);
756 TrackViewList const & allviews = _editor.get_track_views ();
757 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
759 if (i != allviews.end()) {
762 if (++i == allviews.end()) {
766 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
768 if (rtav && (!rtav->is_track() || rtav->track()->rec_enable_control()->get_value())) {
772 if (!(*i)->hidden()) {
779 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
780 _editor.ensure_time_axis_view_is_visible (**i, false);
781 (*i)->begin_name_edit ();
784 } else if (edit_prev) {
786 TrackViewList const & allviews = _editor.get_track_views ();
787 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
789 if (i != allviews.begin()) {
791 if (i == allviews.begin()) {
797 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
799 if (rtav && (!rtav->is_track() || rtav->track()->rec_enable_control()->get_value())) {
803 if (!(*i)->hidden()) {
810 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
811 _editor.ensure_time_axis_view_is_visible (**i, false);
812 (*i)->begin_name_edit ();
818 TimeAxisView::name_entry_changed ()
823 TimeAxisView::can_edit_name () const
829 TimeAxisView::conditionally_add_to_selection ()
831 Selection& s (_editor.get_selection ());
833 if (!s.selected (this)) {
834 _editor.set_selected_track (*this, Selection::Set);
839 TimeAxisView::popup_display_menu (guint32 when)
841 conditionally_add_to_selection ();
843 build_display_menu ();
844 display_menu->popup (1, when);
848 TimeAxisView::set_selected (bool yn)
850 if (can_edit_name() && name_entry && name_entry->get_visible()) {
851 end_name_edit (RESPONSE_CANCEL);
854 if (yn == _selected) {
858 Selectable::set_selected (yn);
861 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
862 time_axis_frame.set_name ("MixerStripSelectedFrame");
863 controls_ebox.set_name (controls_base_selected_name);
864 controls_vbox.set_name (controls_base_selected_name);
865 time_axis_vbox.set_name (controls_base_selected_name);
867 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
868 time_axis_frame.set_name (controls_base_unselected_name);
869 controls_ebox.set_name (controls_base_unselected_name);
870 controls_vbox.set_name (controls_base_unselected_name);
871 time_axis_vbox.set_name (controls_base_unselected_name);
875 /* children will be set for the yn=true case. but when deselecting
876 the editor only has a list of top-level trackviews, so we
877 have to do this here.
880 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
881 (*i)->set_selected (false);
885 time_axis_frame.show();
890 TimeAxisView::build_display_menu ()
892 using namespace Menu_Helpers;
896 display_menu = new Menu;
897 display_menu->set_name ("ArdourContextMenu");
899 // Just let implementing classes define what goes into the manu
903 TimeAxisView::set_samples_per_pixel (double fpp)
905 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
906 (*i)->set_samples_per_pixel (fpp);
911 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
913 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
914 (*i)->show_timestretch (start, end, layers, layer);
919 TimeAxisView::hide_timestretch ()
921 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
922 (*i)->hide_timestretch ();
927 TimeAxisView::show_selection (TimeSelection& ts)
932 SelectionRect *rect; time_axis_frame.show();
935 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
936 (*i)->show_selection (ts);
939 if (selection_group->visible ()) {
940 while (!used_selection_rects.empty()) {
941 free_selection_rects.push_front (used_selection_rects.front());
942 used_selection_rects.pop_front();
943 free_selection_rects.front()->rect->hide();
944 free_selection_rects.front()->start_trim->hide();
945 free_selection_rects.front()->end_trim->hide();
947 selection_group->hide();
950 selection_group->show();
951 selection_group->raise_to_top();
953 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
954 framepos_t start, end;
959 cnt = end - start + 1;
961 rect = get_selection_rect ((*i).id);
963 x1 = _editor.sample_to_pixel (start);
964 x2 = _editor.sample_to_pixel (start + cnt - 1);
965 y2 = current_height() - 1;
967 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
969 // trim boxes are at the top for selections
972 rect->start_trim->set (ArdourCanvas::Rect (x1, 0, x1 + trim_handle_size, y2));
973 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
975 rect->start_trim->show();
976 rect->end_trim->show();
978 rect->start_trim->hide();
979 rect->end_trim->hide();
983 used_selection_rects.push_back (rect);
988 TimeAxisView::reshow_selection (TimeSelection& ts)
992 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
993 (*i)->show_selection (ts);
998 TimeAxisView::hide_selection ()
1000 if (selection_group->visible ()) {
1001 while (!used_selection_rects.empty()) {
1002 free_selection_rects.push_front (used_selection_rects.front());
1003 used_selection_rects.pop_front();
1004 free_selection_rects.front()->rect->hide();
1005 free_selection_rects.front()->start_trim->hide();
1006 free_selection_rects.front()->end_trim->hide();
1008 selection_group->hide();
1011 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1012 (*i)->hide_selection ();
1017 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
1019 /* find the selection rect this is for. we have the item corresponding to one
1020 of the trim handles.
1023 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1024 if ((*i)->start_trim == item || (*i)->end_trim == item) {
1026 /* make one trim handle be "above" the other so that if they overlap,
1027 the top one is the one last used.
1030 (*i)->rect->raise_to_top ();
1031 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
1032 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
1040 TimeAxisView::get_selection_rect (uint32_t id)
1042 SelectionRect *rect;
1044 /* check to see if we already have a visible rect for this particular selection ID */
1046 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1047 if ((*i)->id == id) {
1052 /* ditto for the free rect list */
1054 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1055 if ((*i)->id == id) {
1056 SelectionRect* ret = (*i);
1057 free_selection_rects.erase (i);
1062 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
1064 if (free_selection_rects.empty()) {
1066 rect = new SelectionRect;
1068 rect->rect = new ArdourCanvas::Rectangle (selection_group);
1069 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
1070 rect->rect->set_outline (false);
1071 rect->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1073 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
1074 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
1075 rect->start_trim->set_outline (false);
1076 rect->start_trim->set_fill (false);
1078 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
1079 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
1080 rect->end_trim->set_outline (false);
1081 rect->end_trim->set_fill (false);
1083 free_selection_rects.push_front (rect);
1085 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
1086 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
1087 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
1090 rect = free_selection_rects.front();
1092 free_selection_rects.pop_front();
1096 struct null_deleter { void operator()(void const *) const {} };
1099 TimeAxisView::is_child (TimeAxisView* tav)
1101 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1105 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1107 children.push_back (child);
1111 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1113 Children::iterator i;
1115 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1120 /** Get selectable things within a given range.
1121 * @param start Start time in session frames.
1122 * @param end End time in session frames.
1123 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1124 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1125 * @param result Filled in with selectable things.
1128 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/, bool /*within*/)
1134 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1140 TimeAxisView::add_ghost (RegionView* rv)
1142 GhostRegion* gr = rv->add_ghost (*this);
1145 ghosts.push_back(gr);
1150 TimeAxisView::remove_ghost (RegionView* rv)
1152 rv->remove_ghost_in (*this);
1156 TimeAxisView::erase_ghost (GhostRegion* gr)
1158 if (in_destructor) {
1162 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1171 TimeAxisView::touched (double top, double bot)
1173 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1174 y_position is the "origin" or "top" of the track.
1177 double mybot = _y_position + current_height();
1179 return ((_y_position <= bot && _y_position >= top) ||
1180 ((mybot <= bot) && (top < mybot)) ||
1181 (mybot >= bot && _y_position < top));
1185 TimeAxisView::set_parent (TimeAxisView& p)
1191 TimeAxisView::reset_height ()
1193 set_height (height);
1195 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1196 (*i)->set_height ((*i)->height);
1201 TimeAxisView::compute_heights ()
1203 // TODO this function should be re-evaluated when font-scaling changes (!)
1204 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1205 Gtk::Table one_row_table (1, 1);
1206 ArdourButton* test_button = manage (new ArdourButton);
1207 const int border_width = 2;
1208 const int frame_height = 2;
1209 extra_height = (2 * border_width) + frame_height;
1211 window.add (one_row_table);
1212 test_button->set_name ("mute button");
1213 test_button->set_text (S_("Mute|M"));
1214 test_button->set_tweaks (ArdourButton::TrackHeader);
1216 one_row_table.set_border_width (border_width);
1217 one_row_table.set_row_spacings (2);
1218 one_row_table.set_col_spacings (2);
1220 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1221 one_row_table.show_all ();
1223 Gtk::Requisition req(one_row_table.size_request ());
1224 button_height = req.height;
1228 TimeAxisView::color_handler ()
1230 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1234 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1236 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1237 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1239 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1240 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1242 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1243 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1246 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1248 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1249 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1251 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1252 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1254 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1255 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1259 /** @return Pair: TimeAxisView, layer index.
1260 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1261 * does. @param y is an offset from the top of the trackview area.
1263 * If the covering object is a child axis, then the child is returned.
1264 * TimeAxisView is 0 otherwise.
1266 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1267 * and is in stacked or expanded * region display mode, otherwise 0.
1269 std::pair<TimeAxisView*, double>
1270 TimeAxisView::covers_y_position (double y) const
1273 return std::make_pair ((TimeAxisView *) 0, 0);
1276 if (_y_position <= y && y < (_y_position + height)) {
1278 /* work out the layer index if appropriate */
1280 switch (layer_display ()) {
1286 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1287 /* clamp to max layers to be on the safe side; sometimes the above calculation
1288 returns a too-high value */
1289 if (l >= view()->layers ()) {
1290 l = view()->layers() - 1;
1296 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1298 if (l >= (view()->layers() - 0.5)) {
1299 l = view()->layers() - 0.5;
1305 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1308 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1310 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1316 return std::make_pair ((TimeAxisView *) 0, 0);
1320 TimeAxisView::covered_by_y_range (double y0, double y1) const
1326 /* if either the top or bottom of the axisview is in the vertical
1327 * range, we cover it.
1330 if ((y0 < _y_position && y1 < _y_position) ||
1331 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1335 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1336 if ((*i)->covered_by_y_range (y0, y1)) {
1345 TimeAxisView::preset_height (Height h)
1349 return (button_height * 2) + extra_height + 260;
1351 return (button_height * 2) + extra_height + 160;
1353 return (button_height * 2) + extra_height + 60;
1355 return (button_height * 2) + extra_height + 10;
1357 return button_height + extra_height;
1360 abort(); /* NOTREACHED */
1364 /** @return Child time axis views that are not hidden */
1365 TimeAxisView::Children
1366 TimeAxisView::get_child_list ()
1370 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1371 if (!(*i)->hidden()) {
1380 TimeAxisView::build_size_menu ()
1382 if (_size_menu && _size_menu->gobj ()) {
1388 using namespace Menu_Helpers;
1390 _size_menu = new Menu;
1391 _size_menu->set_name ("ArdourContextMenu");
1392 MenuList& items = _size_menu->items();
1394 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1395 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1396 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1397 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1398 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1402 TimeAxisView::reset_visual_state ()
1404 /* this method is not required to trigger a global redraw */
1406 string str = gui_property ("height");
1409 set_height (atoi (str));
1411 set_height (preset_height (HeightNormal));
1416 TrackViewList::filter_to_unique_playlists ()
1418 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1421 for (iterator i = begin(); i != end(); ++i) {
1422 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1424 /* not a route: include it anyway */
1427 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1429 if (playlists.insert (t->playlist()).second) {
1430 /* playlist not seen yet */
1434 /* not a track: include it anyway */