2 Copyright (C) 2003 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.
22 #include "pbd/error.h"
23 #include "pbd/stacktrace.h"
25 #include "ardour/types.h"
26 #include "ardour/ardour.h"
28 #include "gtkmm2ext/utils.h"
29 #include "gtkmm2ext/gui_thread.h"
31 #include "canvas/group.h"
32 #include "canvas/rectangle.h"
33 #include "canvas/debug.h"
34 #include "canvas/text.h"
35 #include "canvas/utils.h"
37 #include "ardour_ui.h"
39 * ardour_ui.h was moved up in the include list
40 * due to a conflicting definition of 'Rect' between
41 * Apple's MacTypes.h file and GTK
44 #include "public_editor.h"
45 #include "time_axis_view_item.h"
46 #include "time_axis_view.h"
48 #include "rgb_macros.h"
53 using namespace Editing;
56 using namespace ARDOUR;
57 using namespace Gtkmm2ext;
59 Pango::FontDescription TimeAxisViewItem::NAME_FONT;
60 const double TimeAxisViewItem::NAME_X_OFFSET = 15.0;
61 const double TimeAxisViewItem::GRAB_HANDLE_TOP = 6;
62 const double TimeAxisViewItem::GRAB_HANDLE_WIDTH = 5;
64 int TimeAxisViewItem::NAME_HEIGHT;
65 double TimeAxisViewItem::NAME_Y_OFFSET;
66 double TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
67 double TimeAxisViewItem::NAME_HIGHLIGHT_THRESH;
70 TimeAxisViewItem::set_constant_heights ()
72 NAME_FONT = get_font_for_style (X_("TimeAxisViewItemName"));
78 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (X_("Hg")); /* ascender + descender */
82 layout->set_font_description (NAME_FONT);
83 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
86 NAME_Y_OFFSET = height + 5; // XXX this offset is magic
87 NAME_HIGHLIGHT_SIZE = height + 2;
88 NAME_HIGHLIGHT_THRESH = NAME_HIGHLIGHT_SIZE * 3;
92 * Construct a new TimeAxisViewItem.
94 * @param it_name the unique name of this item
95 * @param parent the parent canvas group
96 * @param tv the TimeAxisView we are going to be added to
97 * @param spu samples per unit
99 * @param start the start point of this item
100 * @param duration the duration of this item
101 * @param recording true if this is a recording region view
102 * @param automation true if this is an automation region view
104 TimeAxisViewItem::TimeAxisViewItem(
105 const string & it_name, ArdourCanvas::Group& parent, TimeAxisView& tv, double spu, Gdk::Color const & base_color,
106 framepos_t start, framecnt_t duration, bool recording, bool automation, Visibility vis
110 , _recregion (recording)
111 , _automation (automation)
114 group = new ArdourCanvas::Group (&parent);
115 CANVAS_DEBUG_NAME (group, string_compose ("TAVI group for %1", it_name));
117 init (it_name, spu, base_color, start, duration, vis, true, true);
120 TimeAxisViewItem::TimeAxisViewItem (const TimeAxisViewItem& other)
123 , PBD::ScopedConnectionList()
124 , trackview (other.trackview)
125 , _recregion (other._recregion)
126 , _automation (other._automation)
127 , _dragging (other._dragging)
133 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
134 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
136 /* share the other's parent, but still create a new group */
138 ArdourCanvas::Group* parent = other.group->parent();
140 group = new ArdourCanvas::Group (parent);
142 _selected = other._selected;
144 init (other.item_name, other.samples_per_pixel, c, other.frame_position,
145 other.item_duration, other.visibility, other.wide_enough_for_name, other.high_enough_for_name);
149 TimeAxisViewItem::init (const string& it_name, double fpp, Gdk::Color const & base_color, framepos_t start, framepos_t duration, Visibility vis, bool wide, bool high)
152 samples_per_pixel = fpp;
153 frame_position = start;
154 item_duration = duration;
155 name_connected = false;
157 position_locked = false;
158 max_item_duration = ARDOUR::max_framepos;
159 min_item_duration = 0;
160 show_vestigial = true;
165 wide_enough_for_name = wide;
166 high_enough_for_name = high;
170 warning << "Time Axis Item Duration == 0" << endl;
173 vestigial_frame = new ArdourCanvas::Rectangle (group, ArdourCanvas::Rect (0.0, 1.0, 2.0, trackview.current_height()));
174 vestigial_frame->hide ();
175 vestigial_frame->set_outline_color (ARDOUR_UI::config()->get_canvasvar_VestigialFrame());
176 vestigial_frame->set_fill_color (ARDOUR_UI::config()->get_canvasvar_VestigialFrame());
178 if (visibility & ShowFrame) {
179 frame = new ArdourCanvas::Rectangle (group,
180 ArdourCanvas::Rect (0.0, 1.0,
181 trackview.editor().sample_to_pixel(duration),
182 trackview.current_height()));
185 frame->set_outline_color (ARDOUR_UI::config()->get_canvasvar_RecordingRect());
187 frame->set_outline_color (ARDOUR_UI::config()->get_canvasvar_TimeAxisFrame());
195 if (visibility & ShowNameHighlight) {
197 if (visibility & FullWidthNameHighlight) {
198 name_highlight = new ArdourCanvas::Rectangle (group,
199 ArdourCanvas::Rect (0.0, trackview.editor().sample_to_pixel(item_duration),
200 trackview.current_height() - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE,
201 trackview.current_height()));
203 name_highlight = new ArdourCanvas::Rectangle (group,
204 ArdourCanvas::Rect (1.0, trackview.editor().sample_to_pixel(item_duration) - 1,
205 trackview.current_height() - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE,
206 trackview.current_height()));
209 name_highlight->set_data ("timeaxisviewitem", this);
210 name_highlight->set_outline_what (ArdourCanvas::Rectangle::TOP);
211 /* we should really use a canvas color property here */
212 name_highlight->set_outline_color (RGBA_TO_UINT (0,0,0,255));
218 if (visibility & ShowNameText) {
219 name_text = new ArdourCanvas::Text (group);
220 name_text->set_position (ArdourCanvas::Duple (NAME_X_OFFSET, trackview.current_height() - NAME_Y_OFFSET));
221 name_text->set_font_description (NAME_FONT);
227 /* create our grab handles used for trimming/duration etc */
228 if (!_recregion && !_automation) {
229 double top = TimeAxisViewItem::GRAB_HANDLE_TOP;
230 double width = TimeAxisViewItem::GRAB_HANDLE_WIDTH;
232 frame_handle_start = new ArdourCanvas::Rectangle (group, ArdourCanvas::Rect (0.0, top, width, trackview.current_height()));
233 frame_handle_start->set_outline_what (ArdourCanvas::Rectangle::What (0));
234 frame_handle_end = new ArdourCanvas::Rectangle (group, ArdourCanvas::Rect (0.0, top, width, trackview.current_height()));
235 frame_handle_end->set_outline_what (ArdourCanvas::Rectangle::What (0));
237 frame_handle_start = frame_handle_end = 0;
240 set_color (base_color);
242 set_duration (item_duration, this);
243 set_position (start, this);
245 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&TimeAxisViewItem::parameter_changed, this, _1), gui_context ());
246 ARDOUR_UI::config()->ParameterChanged.connect (sigc::mem_fun (*this, &TimeAxisViewItem::parameter_changed));
249 TimeAxisViewItem::~TimeAxisViewItem()
255 TimeAxisViewItem::hide_rect ()
257 rect_visible = false;
260 if (name_highlight) {
261 name_highlight->set_outline_what (ArdourCanvas::Rectangle::What (0));
262 name_highlight->set_fill_color (UINT_RGBA_CHANGE_A (fill_color, 64));
267 TimeAxisViewItem::show_rect ()
272 if (name_highlight) {
273 name_highlight->set_outline_what (ArdourCanvas::Rectangle::TOP);
274 name_highlight->set_fill_color (fill_color);
279 * Set the position of this item on the timeline.
281 * @param pos the new position
282 * @param src the identity of the object that initiated the change
283 * @return true on success
287 TimeAxisViewItem::set_position(framepos_t pos, void* src, double* delta)
289 if (position_locked) {
293 frame_position = pos;
295 /* This sucks. The GnomeCanvas version I am using
296 doesn't correctly implement gnome_canvas_group_set_arg(),
297 so that simply setting the "x" arg of the group
298 fails to move the group. Instead, we have to
299 use gnome_canvas_item_move(), which does the right
300 thing. I see that in GNOME CVS, the current (Sept 2001)
301 version of GNOME Canvas rectifies this issue cleanly.
305 double new_unit_pos = pos / samples_per_pixel;
307 old_unit_pos = group->position().x;
309 if (new_unit_pos != old_unit_pos) {
310 group->set_x_position (new_unit_pos);
314 (*delta) = new_unit_pos - old_unit_pos;
317 PositionChanged (frame_position, src); /* EMIT_SIGNAL */
322 /** @return position of this item on the timeline */
324 TimeAxisViewItem::get_position() const
326 return frame_position;
330 * Set the duration of this item.
332 * @param dur the new duration of this item
333 * @param src the identity of the object that initiated the change
334 * @return true on success
338 TimeAxisViewItem::set_duration (framecnt_t dur, void* src)
340 if ((dur > max_item_duration) || (dur < min_item_duration)) {
341 warning << string_compose (
342 P_("new duration %1 frame is out of bounds for %2", "new duration of %1 frames is out of bounds for %2", dur),
343 get_item_name(), dur)
354 reset_width_dependent_items (trackview.editor().sample_to_pixel (dur));
356 DurationChanged (dur, src); /* EMIT_SIGNAL */
360 /** @return duration of this item */
362 TimeAxisViewItem::get_duration() const
364 return item_duration;
368 * Set the maximum duration that this item can have.
370 * @param dur the new maximum duration
371 * @param src the identity of the object that initiated the change
374 TimeAxisViewItem::set_max_duration(framecnt_t dur, void* src)
376 max_item_duration = dur;
377 MaxDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
380 /** @return the maximum duration that this item may have */
382 TimeAxisViewItem::get_max_duration() const
384 return max_item_duration;
388 * Set the minimum duration that this item may have.
390 * @param the minimum duration that this item may be set to
391 * @param src the identity of the object that initiated the change
394 TimeAxisViewItem::set_min_duration(framecnt_t dur, void* src)
396 min_item_duration = dur;
397 MinDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
400 /** @return the minimum duration that this item mey have */
402 TimeAxisViewItem::get_min_duration() const
404 return min_item_duration;
408 * Set whether this item is locked to its current position.
409 * Locked items cannot be moved until the item is unlocked again.
411 * @param yn true to lock this item to its current position
412 * @param src the identity of the object that initiated the change
415 TimeAxisViewItem::set_position_locked(bool yn, void* src)
417 position_locked = yn;
418 set_trim_handle_colors();
419 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
422 /** @return true if this item is locked to its current position */
424 TimeAxisViewItem::get_position_locked() const
426 return position_locked;
430 * Set whether the maximum duration constraint is active.
432 * @param active set true to enforce the max duration constraint
433 * @param src the identity of the object that initiated the change
436 TimeAxisViewItem::set_max_duration_active (bool active, void* /*src*/)
438 max_duration_active = active;
441 /** @return true if the maximum duration constraint is active */
443 TimeAxisViewItem::get_max_duration_active() const
445 return max_duration_active;
449 * Set whether the minimum duration constraint is active.
451 * @param active set true to enforce the min duration constraint
452 * @param src the identity of the object that initiated the change
456 TimeAxisViewItem::set_min_duration_active (bool active, void* /*src*/)
458 min_duration_active = active;
461 /** @return true if the maximum duration constraint is active */
463 TimeAxisViewItem::get_min_duration_active() const
465 return min_duration_active;
469 * Set the name of this item.
471 * @param new_name the new name of this item
472 * @param src the identity of the object that initiated the change
476 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
478 if (new_name != item_name) {
479 std::string temp_name = item_name;
480 item_name = new_name;
481 NameChanged (item_name, temp_name, src); /* EMIT_SIGNAL */
485 /** @return the name of this item */
487 TimeAxisViewItem::get_item_name() const
493 * Set selection status.
495 * @param yn true if this item is currently selected
498 TimeAxisViewItem::set_selected(bool yn)
500 if (_selected != yn) {
501 Selectable::set_selected (yn);
506 /** @return the TimeAxisView that this item is on */
508 TimeAxisViewItem::get_time_axis_view () const
514 * Set the displayed item text.
515 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item.
517 * @param new_name the new name text to display
521 TimeAxisViewItem::set_name_text(const string& new_name)
527 last_item_width = trackview.editor().sample_to_pixel(item_duration);
528 name_text_width = pixel_width (new_name, NAME_FONT) + 2;
529 name_text->set (new_name);
534 * Set the height of this item.
536 * @param h new height
539 TimeAxisViewItem::set_height (double height)
543 if (name_highlight) {
544 if (height < NAME_HIGHLIGHT_THRESH) {
545 name_highlight->hide ();
546 high_enough_for_name = false;
549 name_highlight->show();
550 high_enough_for_name = true;
553 if (height > NAME_HIGHLIGHT_SIZE) {
554 name_highlight->set_y0 ((double) height - 1 - NAME_HIGHLIGHT_SIZE);
555 name_highlight->set_y1 ((double) height - 1);
558 /* it gets hidden now anyway */
559 name_highlight->set_y0 (1);
560 name_highlight->set_y1 (height);
564 if (visibility & ShowNameText) {
565 name_text->set_y_position (height + 1 - NAME_Y_OFFSET);
569 frame->set_y1 (height - 1);
570 if (frame_handle_start) {
571 frame_handle_start->set_y1 (height - 1);
572 frame_handle_end->set_y1 (height - 1);
576 vestigial_frame->set_y1 (height - 1);
578 update_name_text_visibility ();
583 TimeAxisViewItem::set_color (Gdk::Color const & base_color)
585 compute_colors (base_color);
590 TimeAxisViewItem::get_canvas_frame()
596 TimeAxisViewItem::get_canvas_group()
602 TimeAxisViewItem::get_name_highlight()
604 return name_highlight;
608 * Calculate some contrasting color for displaying various parts of this item, based upon the base color.
610 * @param color the base color of the item
613 TimeAxisViewItem::compute_colors (Gdk::Color const & base_color)
615 unsigned char radius;
620 /* FILL: this is simple */
621 r = base_color.get_red()/256;
622 g = base_color.get_green()/256;
623 b = base_color.get_blue()/256;
624 fill_color = RGBA_TO_UINT(r,g,b,160);
627 if the overall saturation is strong, make the minor colors light.
628 if its weak, make them dark.
630 we do this by moving an equal distance to the other side of the
631 central circle in the color wheel from where we started.
634 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f));
635 minor_shift = 125 - radius;
637 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
639 r = base_color.get_red()/256;
640 g = base_color.get_green()/256;
641 b = base_color.get_blue()/256;
647 /* red sector => green */
652 /* green sector => blue */
660 /* blue sector => red */
665 /* green sector => blue */
674 label_color = RGBA_TO_UINT(r,g,b,255);
675 r = (base_color.get_red()/256) + 127;
676 g = (base_color.get_green()/256) + 127;
677 b = (base_color.get_blue()/256) + 127;
679 label_color = RGBA_TO_UINT(r,g,b,255);
681 /* XXX can we do better than this ? */
685 //frame_color_r = 192;
686 //frame_color_g = 192;
687 //frame_color_b = 194;
689 //selected_frame_color_r = 182;
690 //selected_frame_color_g = 145;
691 //selected_frame_color_b = 168;
693 //handle_color_r = 25;
694 //handle_color_g = 0;
695 //handle_color_b = 255;
696 //lock_handle_color_r = 235;
697 //lock_handle_color_g = 16;
698 //lock_handle_color_b = 16;
702 * Convenience method to set the various canvas item colors
705 TimeAxisViewItem::set_colors()
709 if (name_highlight) {
710 name_highlight->set_fill_color (fill_color);
712 set_trim_handle_colors();
716 * Sets the frame color depending on whether this item is selected
719 TimeAxisViewItem::set_frame_color()
729 f = ARDOUR_UI::config()->get_canvasvar_SelectedFrameBase();
732 f = UINT_RGBA_CHANGE_A (f, fill_opacity);
736 f = UINT_RGBA_CHANGE_A (f, 0);
742 f = ARDOUR_UI::config()->get_canvasvar_RecordingRect();
745 if (high_enough_for_name && !Config->get_color_regions_using_track_color()) {
746 f = ARDOUR_UI::config()->get_canvasvar_FrameBase();
752 f = UINT_RGBA_CHANGE_A (f, fill_opacity);
756 f = UINT_RGBA_CHANGE_A (f, 0);
761 frame->set_fill_color (f);
762 set_frame_gradient ();
766 f = ARDOUR_UI::config()->get_canvasvar_SelectedTimeAxisFrame();
768 f = ARDOUR_UI::config()->get_canvasvar_TimeAxisFrame();
772 f = UINT_RGBA_CHANGE_A (f, 64);
775 frame->set_outline_color (f);
780 TimeAxisViewItem::set_frame_gradient ()
782 if (ARDOUR_UI::config()->get_timeline_item_gradient_depth() == 0.0) {
783 frame->set_gradient (ArdourCanvas::Fill::StopList (), 0);
787 ArdourCanvas::Fill::StopList stops;
790 ArdourCanvas::Color f (frame->fill_color());
792 /* need to get alpha value */
793 ArdourCanvas::color_to_rgba (f, r, g, b, a);
795 stops.push_back (std::make_pair (0.0, f));
797 /* now a darker version */
799 ArdourCanvas::color_to_hsv (f, h, s, v);
800 s *= ARDOUR_UI::config()->get_timeline_item_gradient_depth();
805 ArdourCanvas::Color darker = ArdourCanvas::hsv_to_color (h, s, v, a);
806 stops.push_back (std::make_pair (1.0, darker));
808 frame->set_gradient (stops, _height);
812 * Set the colors of the start and end trim handle depending on object state
815 TimeAxisViewItem::set_trim_handle_colors()
817 if (frame_handle_start) {
818 if (position_locked) {
819 frame_handle_start->set_fill_color (ARDOUR_UI::config()->get_canvasvar_TrimHandleLocked());
820 frame_handle_end->set_fill_color (ARDOUR_UI::config()->get_canvasvar_TrimHandleLocked());
822 frame_handle_start->set_fill_color (RGBA_TO_UINT (1, 1, 1, 0)); //ARDOUR_UI::config()->get_canvasvar_TrimHandle();
823 frame_handle_end->set_fill_color (RGBA_TO_UINT (1, 1, 1, 0)); //ARDOUR_UI::config()->get_canvasvar_TrimHandle();
828 /** @return the frames per pixel */
830 TimeAxisViewItem::get_samples_per_pixel () const
832 return samples_per_pixel;
835 /** Set the frames per pixel of this item.
836 * This item is used to determine the relative visual size and position of this item
837 * based upon its duration and start value.
839 * @param fpp the new frames per pixel
842 TimeAxisViewItem::set_samples_per_pixel (double fpp)
844 samples_per_pixel = fpp;
845 set_position (this->get_position(), this);
846 reset_width_dependent_items ((double) get_duration() / samples_per_pixel);
850 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
852 if (pixel_width < 2.0) {
854 if (show_vestigial) {
855 vestigial_frame->show();
858 if (name_highlight) {
859 name_highlight->hide();
866 if (frame_handle_start) {
867 frame_handle_start->hide();
868 frame_handle_end->hide();
871 wide_enough_for_name = false;
874 vestigial_frame->hide();
876 if (name_highlight) {
878 if (_height < NAME_HIGHLIGHT_THRESH) {
879 name_highlight->hide();
880 high_enough_for_name = false;
882 name_highlight->show();
883 if (!get_item_name().empty()) {
884 reset_name_width (pixel_width);
886 high_enough_for_name = true;
889 name_highlight->set_x1 (pixel_width);
894 frame->set_x1 (pixel_width);
897 if (frame_handle_start) {
898 if (pixel_width < (3 * TimeAxisViewItem::GRAB_HANDLE_WIDTH)) {
900 * there's less than GRAB_HANDLE_WIDTH of the region between
901 * the right-hand end of frame_handle_start and the left-hand
902 * end of frame_handle_end, so disable the handles
904 frame_handle_start->hide();
905 frame_handle_end->hide();
907 frame_handle_start->show();
908 frame_handle_end->set_x0 (pixel_width - (TimeAxisViewItem::GRAB_HANDLE_WIDTH));
909 frame_handle_end->set_x1 (pixel_width);
910 frame_handle_end->show();
914 wide_enough_for_name = true;
917 update_name_text_visibility ();
921 TimeAxisViewItem::reset_name_width (double /*pixel_width*/)
925 bool showing_full_name;
931 it_width = trackview.editor().sample_to_pixel(item_duration);
932 pb_width = name_text_width;
934 showing_full_name = last_item_width > pb_width + NAME_X_OFFSET;
935 last_item_width = it_width;
937 if (showing_full_name && (it_width >= pb_width + NAME_X_OFFSET)) {
939 we've previously had the full name length showing
940 and its still showing.
945 if (pb_width > it_width - NAME_X_OFFSET) {
946 pb_width = it_width - NAME_X_OFFSET;
949 if (it_width <= NAME_X_OFFSET) {
950 wide_enough_for_name = false;
952 wide_enough_for_name = true;
955 update_name_text_visibility ();
961 name_text->set (item_name);
962 name_text->clamp_width (pb_width);
966 * Callback used to remove this time axis item during the gtk idle loop.
967 * This is used to avoid deleting the obejct while inside the remove_this_item
970 * @param item the TimeAxisViewItem to remove.
971 * @param src the identity of the object that initiated the change.
974 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
976 item->ItemRemoved (item->get_item_name(), src); /* EMIT_SIGNAL */
983 TimeAxisViewItem::set_y (double y)
985 group->set_y_position (y);
989 TimeAxisViewItem::update_name_text_visibility ()
995 if (wide_enough_for_name && high_enough_for_name) {
1003 TimeAxisViewItem::parameter_changed (string p)
1005 if (p == "color-regions-using-track-color") {
1007 } else if (p == "timeline-item-gradient-depth") {
1008 set_frame_gradient ();