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.
20 #include "pbd/error.h"
21 #include "pbd/stacktrace.h"
23 #include "ardour/types.h"
24 #include "ardour/ardour.h"
26 #include "gtkmm2ext/utils.h"
27 #include "gtkmm2ext/gui_thread.h"
29 #include "ardour_ui.h"
31 * ardour_ui.h was moved up in the include list
32 * due to a conflicting definition of 'Rect' between
33 * Apple's MacTypes.h file and GTK
36 #include "public_editor.h"
37 #include "time_axis_view_item.h"
38 #include "time_axis_view.h"
39 #include "simplerect.h"
41 #include "canvas_impl.h"
42 #include "rgb_macros.h"
47 using namespace Editing;
50 using namespace ARDOUR;
51 using namespace Gtkmm2ext;
53 Pango::FontDescription* TimeAxisViewItem::NAME_FONT = 0;
54 const double TimeAxisViewItem::NAME_X_OFFSET = 15.0;
55 const double TimeAxisViewItem::GRAB_HANDLE_LENGTH = 6;
57 int TimeAxisViewItem::NAME_HEIGHT;
58 double TimeAxisViewItem::NAME_Y_OFFSET;
59 double TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
60 double TimeAxisViewItem::NAME_HIGHLIGHT_THRESH;
63 TimeAxisViewItem::set_constant_heights ()
65 NAME_FONT = get_font_for_style (X_("TimeAxisViewItemName"));
71 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (X_("Hg")); /* ascender + descender */
75 layout->set_font_description (*NAME_FONT);
76 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
79 NAME_Y_OFFSET = height + 3;
80 NAME_HIGHLIGHT_SIZE = height + 2;
81 NAME_HIGHLIGHT_THRESH = NAME_HIGHLIGHT_SIZE * 3;
85 * Construct a new TimeAxisViewItem.
87 * @param it_name the unique name of this item
88 * @param parent the parent canvas group
89 * @param tv the TimeAxisView we are going to be added to
90 * @param spu samples per unit
92 * @param start the start point of this item
93 * @param duration the duration of this item
94 * @param recording true if this is a recording region view
95 * @param automation true if this is an automation region view
97 TimeAxisViewItem::TimeAxisViewItem(
98 const string & it_name, ArdourCanvas::Group& parent, TimeAxisView& tv, double spu, Gdk::Color const & base_color,
99 framepos_t start, framecnt_t duration, bool recording, bool automation, Visibility vis
103 , _recregion (recording)
104 , _automation (automation)
106 group = new ArdourCanvas::Group (parent);
108 init (it_name, spu, base_color, start, duration, vis, true, true);
111 TimeAxisViewItem::TimeAxisViewItem (const TimeAxisViewItem& other)
112 : sigc::trackable(other)
113 , PBD::ScopedConnectionList()
114 , trackview (other.trackview)
115 , _recregion (other._recregion)
116 , _automation (other._automation)
122 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
123 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
125 /* share the other's parent, but still create a new group */
127 Gnome::Canvas::Group* parent = other.group->property_parent();
129 group = new ArdourCanvas::Group (*parent);
131 _selected = other._selected;
134 other.item_name, other.samples_per_unit, c, other.frame_position,
135 other.item_duration, other.visibility, other.wide_enough_for_name, other.high_enough_for_name
140 TimeAxisViewItem::init (
141 const string& it_name, double spu, Gdk::Color const & base_color, framepos_t start, framepos_t duration, Visibility vis, bool wide, bool high)
144 samples_per_unit = spu;
145 frame_position = start;
146 item_duration = duration;
147 name_connected = false;
149 position_locked = false;
150 max_item_duration = ARDOUR::max_framepos;
151 min_item_duration = 0;
152 show_vestigial = true;
155 name_pixbuf_width = 0;
157 wide_enough_for_name = wide;
158 high_enough_for_name = high;
162 warning << "Time Axis Item Duration == 0" << endl;
165 vestigial_frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, 2.0, trackview.current_height());
166 vestigial_frame->hide ();
167 vestigial_frame->property_outline_what() = 0xF;
168 vestigial_frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
169 vestigial_frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
171 if (visibility & ShowFrame) {
172 frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, trackview.editor().frame_to_pixel(duration), trackview.current_height());
174 frame->property_outline_pixels() = 1;
175 frame->property_outline_what() = 0xF;
178 frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
180 frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeAxisFrame.get();
183 frame->property_outline_what() = 0x1|0x2|0x4|0x8;
189 if (visibility & ShowNameHighlight) {
191 if (visibility & FullWidthNameHighlight) {
192 name_highlight = new ArdourCanvas::SimpleRect (*group, 0.0, trackview.editor().frame_to_pixel(item_duration), trackview.current_height() - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE, trackview.current_height());
194 name_highlight = new ArdourCanvas::SimpleRect (*group, 1.0, trackview.editor().frame_to_pixel(item_duration) - 1, trackview.current_height() - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE, trackview.current_height());
197 name_highlight->set_data ("timeaxisviewitem", this);
198 name_highlight->property_outline_what() = 0x4;
199 /* we should really use a canvas color property here */
200 name_highlight->property_outline_color_rgba() = RGBA_TO_UINT (0,0,0,255);
206 if (visibility & ShowNameText) {
207 name_pixbuf = new ArdourCanvas::Pixbuf(*group);
208 name_pixbuf->property_x() = NAME_X_OFFSET;
209 name_pixbuf->property_y() = trackview.current_height() + 1 - NAME_Y_OFFSET;
215 /* create our grab handles used for trimming/duration etc */
216 if (!_recregion && !_automation) {
217 frame_handle_start = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
218 frame_handle_start->property_outline_what() = 0x0;
219 frame_handle_end = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
220 frame_handle_end->property_outline_what() = 0x0;
222 frame_handle_start = frame_handle_end = 0;
225 set_color (base_color);
227 set_duration (item_duration, this);
228 set_position (start, this);
230 Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&TimeAxisViewItem::parameter_changed, this, _1), gui_context ());
233 TimeAxisViewItem::~TimeAxisViewItem()
239 TimeAxisViewItem::hide_rect ()
241 rect_visible = false;
244 if (name_highlight) {
245 name_highlight->property_outline_what() = 0x0;
250 TimeAxisViewItem::show_rect ()
255 if (name_highlight) {
256 name_highlight->property_outline_what() = 0x4;
262 * Set the position of this item on the timeline.
264 * @param pos the new position
265 * @param src the identity of the object that initiated the change
266 * @return true on success
270 TimeAxisViewItem::set_position(framepos_t pos, void* src, double* delta)
272 if (position_locked) {
276 frame_position = pos;
278 /* This sucks. The GnomeCanvas version I am using
279 doesn't correctly implement gnome_canvas_group_set_arg(),
280 so that simply setting the "x" arg of the group
281 fails to move the group. Instead, we have to
282 use gnome_canvas_item_move(), which does the right
283 thing. I see that in GNOME CVS, the current (Sept 2001)
284 version of GNOME Canvas rectifies this issue cleanly.
288 double new_unit_pos = pos / samples_per_unit;
290 old_unit_pos = group->property_x();
292 if (new_unit_pos != old_unit_pos) {
293 group->move (new_unit_pos - old_unit_pos, 0.0);
297 (*delta) = new_unit_pos - old_unit_pos;
300 PositionChanged (frame_position, src); /* EMIT_SIGNAL */
305 /** @return position of this item on the timeline */
307 TimeAxisViewItem::get_position() const
309 return frame_position;
313 * Set the duration of this item.
315 * @param dur the new duration of this item
316 * @param src the identity of the object that initiated the change
317 * @return true on success
321 TimeAxisViewItem::set_duration (framepos_t dur, void* src)
323 if ((dur > max_item_duration) || (dur < min_item_duration)) {
324 warning << string_compose (_("new duration %1 frames is out of bounds for %2"), get_item_name(), dur)
335 reset_width_dependent_items (trackview.editor().frame_to_pixel (dur));
337 DurationChanged (dur, src); /* EMIT_SIGNAL */
341 /** @return duration of this item */
343 TimeAxisViewItem::get_duration() const
345 return item_duration;
349 * Set the maximum duration that this item can have.
351 * @param dur the new maximum duration
352 * @param src the identity of the object that initiated the change
355 TimeAxisViewItem::set_max_duration(framecnt_t dur, void* src)
357 max_item_duration = dur;
358 MaxDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
361 /** @return the maximum duration that this item may have */
363 TimeAxisViewItem::get_max_duration() const
365 return max_item_duration;
369 * Set the minimum duration that this item may have.
371 * @param the minimum duration that this item may be set to
372 * @param src the identity of the object that initiated the change
375 TimeAxisViewItem::set_min_duration(framecnt_t dur, void* src)
377 min_item_duration = dur;
378 MinDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
381 /** @return the minimum duration that this item mey have */
383 TimeAxisViewItem::get_min_duration() const
385 return min_item_duration;
389 * Set whether this item is locked to its current position.
390 * Locked items cannot be moved until the item is unlocked again.
392 * @param yn true to lock this item to its current position
393 * @param src the identity of the object that initiated the change
396 TimeAxisViewItem::set_position_locked(bool yn, void* src)
398 position_locked = yn;
399 set_trim_handle_colors();
400 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
403 /** @return true if this item is locked to its current position */
405 TimeAxisViewItem::get_position_locked() const
407 return position_locked;
411 * Set whether the maximum duration constraint is active.
413 * @param active set true to enforce the max duration constraint
414 * @param src the identity of the object that initiated the change
417 TimeAxisViewItem::set_max_duration_active (bool active, void* /*src*/)
419 max_duration_active = active;
422 /** @return true if the maximum duration constraint is active */
424 TimeAxisViewItem::get_max_duration_active() const
426 return max_duration_active;
430 * Set whether the minimum duration constraint is active.
432 * @param active set true to enforce the min duration constraint
433 * @param src the identity of the object that initiated the change
437 TimeAxisViewItem::set_min_duration_active (bool active, void* /*src*/)
439 min_duration_active = active;
442 /** @return true if the maximum duration constraint is active */
444 TimeAxisViewItem::get_min_duration_active() const
446 return min_duration_active;
450 * Set the name of this item.
452 * @param new_name the new name of this item
453 * @param src the identity of the object that initiated the change
457 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
459 if (new_name != item_name) {
460 std::string temp_name = item_name;
461 item_name = new_name;
462 NameChanged (item_name, temp_name, src); /* EMIT_SIGNAL */
466 /** @return the name of this item */
468 TimeAxisViewItem::get_item_name() const
474 * Set selection status.
476 * @param yn true if this item is currently selected
479 TimeAxisViewItem::set_selected(bool yn)
481 if (_selected != yn) {
482 Selectable::set_selected (yn);
487 /** @return the TimeAxisView that this item is on */
489 TimeAxisViewItem::get_time_axis_view () const
495 * Set the displayed item text.
496 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item.
498 * @param new_name the new name text to display
502 TimeAxisViewItem::set_name_text(const string& new_name)
508 last_item_width = trackview.editor().frame_to_pixel(item_duration);
509 name_pixbuf_width = pixel_width (new_name, *NAME_FONT) + 2;
510 name_pixbuf->property_pixbuf() = pixbuf_from_string(new_name, NAME_FONT, name_pixbuf_width, NAME_HEIGHT, Gdk::Color ("#000000"));
515 * Set the height of this item.
517 * @param h new height
520 TimeAxisViewItem::set_height (double height)
524 if (name_highlight) {
525 if (height < NAME_HIGHLIGHT_THRESH) {
526 name_highlight->hide ();
527 high_enough_for_name = false;
530 name_highlight->show();
531 high_enough_for_name = true;
534 if (height > NAME_HIGHLIGHT_SIZE) {
535 name_highlight->property_y1() = (double) height - 1 - NAME_HIGHLIGHT_SIZE;
536 name_highlight->property_y2() = (double) height - 1;
539 /* it gets hidden now anyway */
540 name_highlight->property_y1() = (double) 1.0;
541 name_highlight->property_y2() = (double) height;
545 if (visibility & ShowNameText) {
546 name_pixbuf->property_y() = height + 1 - NAME_Y_OFFSET;
550 frame->property_y2() = height - 1;
551 if (frame_handle_start) {
552 frame_handle_start->property_y2() = height - 1;
553 frame_handle_end->property_y2() = height - 1;
557 vestigial_frame->property_y2() = height - 1;
559 update_name_pixbuf_visibility ();
564 TimeAxisViewItem::set_color (Gdk::Color const & base_color)
566 compute_colors (base_color);
571 TimeAxisViewItem::get_canvas_frame()
577 TimeAxisViewItem::get_canvas_group()
583 TimeAxisViewItem::get_name_highlight()
585 return name_highlight;
588 ArdourCanvas::Pixbuf*
589 TimeAxisViewItem::get_name_pixbuf()
595 * Calculate some contrasting color for displaying various parts of this item, based upon the base color.
597 * @param color the base color of the item
600 TimeAxisViewItem::compute_colors (Gdk::Color const & base_color)
602 unsigned char radius;
607 /* FILL: this is simple */
608 r = base_color.get_red()/256;
609 g = base_color.get_green()/256;
610 b = base_color.get_blue()/256;
611 fill_color = RGBA_TO_UINT(r,g,b,160);
614 if the overall saturation is strong, make the minor colors light.
615 if its weak, make them dark.
617 we do this by moving an equal distance to the other side of the
618 central circle in the color wheel from where we started.
621 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f));
622 minor_shift = 125 - radius;
624 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
626 r = base_color.get_red()/256;
627 g = base_color.get_green()/256;
628 b = base_color.get_blue()/256;
634 /* red sector => green */
639 /* green sector => blue */
647 /* blue sector => red */
652 /* green sector => blue */
661 label_color = RGBA_TO_UINT(r,g,b,255);
662 r = (base_color.get_red()/256) + 127;
663 g = (base_color.get_green()/256) + 127;
664 b = (base_color.get_blue()/256) + 127;
666 label_color = RGBA_TO_UINT(r,g,b,255);
668 /* XXX can we do better than this ? */
672 //frame_color_r = 192;
673 //frame_color_g = 192;
674 //frame_color_b = 194;
676 //selected_frame_color_r = 182;
677 //selected_frame_color_g = 145;
678 //selected_frame_color_b = 168;
680 //handle_color_r = 25;
681 //handle_color_g = 0;
682 //handle_color_b = 255;
683 //lock_handle_color_r = 235;
684 //lock_handle_color_g = 16;
685 //lock_handle_color_b = 16;
689 * Convenience method to set the various canvas item colors
692 TimeAxisViewItem::set_colors()
696 if (name_highlight) {
697 name_highlight->property_fill_color_rgba() = fill_color;
699 set_trim_handle_colors();
703 * Sets the frame color depending on whether this item is selected
706 TimeAxisViewItem::set_frame_color()
716 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
719 f = UINT_RGBA_CHANGE_A (f, fill_opacity);
723 f = UINT_RGBA_CHANGE_A (f, 0);
729 f = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
732 if (high_enough_for_name && !Config->get_color_regions_using_track_color()) {
733 f = ARDOUR_UI::config()->canvasvar_FrameBase.get();
739 f = UINT_RGBA_CHANGE_A (f, fill_opacity);
743 f = UINT_RGBA_CHANGE_A (f, 0);
748 frame->property_fill_color_rgba() = f;
751 f = ARDOUR_UI::config()->canvasvar_TimeAxisFrame.get();
754 f = UINT_RGBA_CHANGE_A (f, 64);
757 frame->property_outline_color_rgba() = f;
762 * Set the colors of the start and end trim handle depending on object state
765 TimeAxisViewItem::set_trim_handle_colors()
767 if (frame_handle_start) {
768 if (position_locked) {
769 frame_handle_start->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
770 frame_handle_end->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
772 frame_handle_start->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
773 frame_handle_end->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
778 /** @return the samples per unit of this item */
780 TimeAxisViewItem::get_samples_per_unit()
782 return samples_per_unit;
786 * Set the samples per unit of this item.
787 * This item is used to determine the relative visual size and position of this item
788 * based upon its duration and start value.
790 * @param spu the new samples per unit value
793 TimeAxisViewItem::set_samples_per_unit (double spu)
795 samples_per_unit = spu;
796 set_position (this->get_position(), this);
797 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
801 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
803 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
805 if (frame_handle_start) {
806 frame_handle_start->hide();
807 frame_handle_end->hide();
812 if (pixel_width < 2.0) {
814 if (show_vestigial) {
815 vestigial_frame->show();
818 if (name_highlight) {
819 name_highlight->hide();
826 if (frame_handle_start) {
827 frame_handle_start->hide();
828 frame_handle_end->hide();
831 wide_enough_for_name = false;
834 vestigial_frame->hide();
836 if (name_highlight) {
838 if (_height < NAME_HIGHLIGHT_THRESH) {
839 name_highlight->hide();
840 high_enough_for_name = false;
842 name_highlight->show();
843 if (!get_item_name().empty()) {
844 reset_name_width (pixel_width);
846 high_enough_for_name = true;
849 name_highlight->property_x2() = pixel_width;
854 frame->property_x2() = pixel_width;
857 if (frame_handle_start) {
858 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
859 frame_handle_start->hide();
860 frame_handle_end->hide();
862 frame_handle_start->show();
863 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
864 frame_handle_end->show();
865 frame_handle_end->property_x2() = pixel_width;
869 update_name_pixbuf_visibility ();
873 TimeAxisViewItem::reset_name_width (double /*pixel_width*/)
877 bool pixbuf_holds_full_name;
883 it_width = trackview.editor().frame_to_pixel(item_duration);
884 pb_width = name_pixbuf_width;
886 pixbuf_holds_full_name = last_item_width > pb_width + NAME_X_OFFSET;
887 last_item_width = it_width;
889 if (pixbuf_holds_full_name && (it_width >= pb_width + NAME_X_OFFSET)) {
891 we've previously had the full name length showing
892 and its still showing.
897 if (pb_width > it_width - NAME_X_OFFSET) {
898 pb_width = it_width - NAME_X_OFFSET;
901 if (it_width <= NAME_X_OFFSET) {
902 wide_enough_for_name = false;
904 wide_enough_for_name = true;
907 update_name_pixbuf_visibility ();
909 name_pixbuf->property_pixbuf() = pixbuf_from_string(item_name, NAME_FONT, pb_width, NAME_HEIGHT, Gdk::Color ("#000000"));
914 * Callback used to remove this time axis item during the gtk idle loop.
915 * This is used to avoid deleting the obejct while inside the remove_this_item
918 * @param item the TimeAxisViewItem to remove.
919 * @param src the identity of the object that initiated the change.
922 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
924 item->ItemRemoved (item->get_item_name(), src); /* EMIT_SIGNAL */
931 TimeAxisViewItem::set_y (double y)
933 double const old = group->property_y ();
935 group->move (0, y - old);
940 TimeAxisViewItem::update_name_pixbuf_visibility ()
946 if (wide_enough_for_name && high_enough_for_name) {
947 name_pixbuf->show ();
949 name_pixbuf->hide ();
954 TimeAxisViewItem::parameter_changed (string p)
956 if (p == "color-regions-using-track-color") {