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>
28 #include "ardour_ui.h"
30 * ardour_ui.h was moved up in the include list
31 * due to a conflicting definition of 'Rect' between
32 * Apple's MacTypes.h file and GTK
35 #include "public_editor.h"
36 #include "time_axis_view_item.h"
37 #include "time_axis_view.h"
38 #include "simplerect.h"
40 #include "canvas_impl.h"
41 #include "rgb_macros.h"
46 using namespace Editing;
49 using namespace ARDOUR;
51 Pango::FontDescription* TimeAxisViewItem::NAME_FONT = 0;
52 bool TimeAxisViewItem::have_name_font = false;
53 const double TimeAxisViewItem::NAME_X_OFFSET = 15.0;
54 const double TimeAxisViewItem::GRAB_HANDLE_LENGTH = 6;
56 int TimeAxisViewItem::NAME_HEIGHT;
57 double TimeAxisViewItem::NAME_Y_OFFSET;
58 double TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
59 double TimeAxisViewItem::NAME_HIGHLIGHT_THRESH;
62 * Construct a new TimeAxisViewItem.
64 * @param it_name the unique name of this item
65 * @param parant the parent canvas group
66 * @param tv the TimeAxisView we are going to be added to
67 * @param spu samples per unit
69 * @param start the start point of this item
70 * @param duration the duration of this item
72 TimeAxisViewItem::TimeAxisViewItem(const string & it_name, ArdourCanvas::Group& parent, TimeAxisView& tv, double spu, Gdk::Color const & base_color,
73 nframes64_t start, nframes64_t duration, bool recording,
77 , _recregion (recording)
79 if (!have_name_font) {
81 /* first constructed item sets up font info */
83 NAME_FONT = get_font_for_style (N_("TimeAxisViewItemName"));
89 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (X_("Hg")); /* ascender + descender */
93 layout->set_font_description (*NAME_FONT);
94 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
97 NAME_Y_OFFSET = height + 3;
98 NAME_HIGHLIGHT_SIZE = height + 2;
99 NAME_HIGHLIGHT_THRESH = NAME_HIGHLIGHT_SIZE * 3;
101 have_name_font = true;
104 group = new ArdourCanvas::Group (parent);
106 init (it_name, spu, base_color, start, duration, vis, true, true);
109 TimeAxisViewItem::TimeAxisViewItem (const TimeAxisViewItem& other)
110 : sigc::trackable(other)
111 , PBD::ScopedConnectionList()
112 , trackview (other.trackview)
113 , _recregion (other._recregion)
119 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
120 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
122 /* share the other's parent, but still create a new group */
124 Gnome::Canvas::Group* parent = other.group->property_parent();
126 group = new ArdourCanvas::Group (*parent);
128 _selected = other._selected;
131 other.item_name, other.samples_per_unit, c, other.frame_position,
132 other.item_duration, other.visibility, other.wide_enough_for_name, other.high_enough_for_name
137 TimeAxisViewItem::init (
138 const string& it_name, double spu, Gdk::Color const & base_color, nframes64_t start, nframes64_t duration, Visibility vis, bool wide, bool high
142 samples_per_unit = spu;
143 should_show_selection = true;
144 frame_position = start;
145 item_duration = duration;
146 name_connected = false;
148 position_locked = false;
149 max_item_duration = ARDOUR::max_frames;
150 min_item_duration = 0;
151 show_vestigial = true;
154 name_pixbuf_width = 0;
156 wide_enough_for_name = wide;
157 high_enough_for_name = high;
160 warning << "Time Axis Item Duration == 0" << endl;
163 vestigial_frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, 2.0, trackview.current_height());
164 vestigial_frame->hide ();
165 vestigial_frame->property_outline_what() = 0xF;
166 vestigial_frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
167 vestigial_frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
169 if (visibility & ShowFrame) {
170 frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, trackview.editor().frame_to_pixel(duration), trackview.current_height());
171 frame->property_outline_pixels() = 1;
172 frame->property_outline_what() = 0xF;
173 frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeAxisFrame.get();
175 /* by default draw all 4 edges */
177 uint32_t outline_what = 0x1|0x2|0x4|0x8;
179 if (visibility & HideFrameLeft) {
180 outline_what &= ~(0x1);
183 if (visibility & HideFrameRight) {
184 outline_what &= ~(0x2);
187 if (visibility & HideFrameTB) {
188 outline_what &= ~(0x4 | 0x8);
191 frame->property_outline_what() = outline_what;
197 if (visibility & ShowNameHighlight) {
198 if (visibility & FullWidthNameHighlight) {
199 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() - 1);
201 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() - 1);
203 name_highlight->set_data ("timeaxisviewitem", this);
209 if (visibility & ShowNameText) {
210 name_pixbuf = new ArdourCanvas::Pixbuf(*group);
211 name_pixbuf->property_x() = NAME_X_OFFSET;
212 name_pixbuf->property_y() = trackview.current_height() + 1 - NAME_Y_OFFSET;
218 /* create our grab handles used for trimming/duration etc */
219 frame_handle_start = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
220 frame_handle_start->property_outline_what() = 0x0;
222 frame_handle_end = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
223 frame_handle_end->property_outline_what() = 0x0;
225 set_color (base_color);
227 set_duration (item_duration, this);
228 set_position (start, this);
231 TimeAxisViewItem::~TimeAxisViewItem()
238 * Set the position of this item on the timeline.
240 * @param pos the new position
241 * @param src the identity of the object that initiated the change
242 * @return true on success
246 TimeAxisViewItem::set_position(nframes64_t pos, void* src, double* delta)
248 if (position_locked) {
252 frame_position = pos;
254 /* This sucks. The GnomeCanvas version I am using
255 doesn't correctly implement gnome_canvas_group_set_arg(),
256 so that simply setting the "x" arg of the group
257 fails to move the group. Instead, we have to
258 use gnome_canvas_item_move(), which does the right
259 thing. I see that in GNOME CVS, the current (Sept 2001)
260 version of GNOME Canvas rectifies this issue cleanly.
264 double new_unit_pos = pos / samples_per_unit;
266 old_unit_pos = group->property_x();
268 if (new_unit_pos != old_unit_pos) {
269 group->move (new_unit_pos - old_unit_pos, 0.0);
273 (*delta) = new_unit_pos - old_unit_pos;
276 PositionChanged (frame_position, src); /* EMIT_SIGNAL */
281 /** @return position of this item on the timeline */
283 TimeAxisViewItem::get_position() const
285 return frame_position;
289 * Set the duration of this item.
291 * @param dur the new duration of this item
292 * @param src the identity of the object that initiated the change
293 * @return true on success
297 TimeAxisViewItem::set_duration (nframes64_t dur, void* src)
299 if ((dur > max_item_duration) || (dur < min_item_duration)) {
300 warning << string_compose (_("new duration %1 frames is out of bounds for %2"), get_item_name(), dur)
311 reset_width_dependent_items (trackview.editor().frame_to_pixel (dur));
313 DurationChanged (dur, src); /* EMIT_SIGNAL */
317 /** @return duration of this item */
319 TimeAxisViewItem::get_duration() const
321 return item_duration;
325 * Set the maximum duration that this item can have.
327 * @param dur the new maximum duration
328 * @param src the identity of the object that initiated the change
331 TimeAxisViewItem::set_max_duration(nframes64_t dur, void* src)
333 max_item_duration = dur;
334 MaxDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
337 /** @return the maximum duration that this item may have */
339 TimeAxisViewItem::get_max_duration() const
341 return max_item_duration;
345 * Set the minimum duration that this item may have.
347 * @param the minimum duration that this item may be set to
348 * @param src the identity of the object that initiated the change
351 TimeAxisViewItem::set_min_duration(nframes64_t dur, void* src)
353 min_item_duration = dur;
354 MinDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
357 /** @return the minimum duration that this item mey have */
359 TimeAxisViewItem::get_min_duration() const
361 return min_item_duration;
365 * Set whether this item is locked to its current position.
366 * Locked items cannot be moved until the item is unlocked again.
368 * @param yn true to lock this item to its current position
369 * @param src the identity of the object that initiated the change
372 TimeAxisViewItem::set_position_locked(bool yn, void* src)
374 position_locked = yn;
375 set_trim_handle_colors();
376 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
379 /** @return true if this item is locked to its current position */
381 TimeAxisViewItem::get_position_locked() const
383 return position_locked;
387 * Set whether the maximum duration constraint is active.
389 * @param active set true to enforce the max duration constraint
390 * @param src the identity of the object that initiated the change
393 TimeAxisViewItem::set_max_duration_active (bool active, void* /*src*/)
395 max_duration_active = active;
398 /** @return true if the maximum duration constraint is active */
400 TimeAxisViewItem::get_max_duration_active() const
402 return max_duration_active;
406 * Set whether the minimum duration constraint is active.
408 * @param active set true to enforce the min duration constraint
409 * @param src the identity of the object that initiated the change
413 TimeAxisViewItem::set_min_duration_active (bool active, void* /*src*/)
415 min_duration_active = active;
418 /** @return true if the maximum duration constraint is active */
420 TimeAxisViewItem::get_min_duration_active() const
422 return min_duration_active;
426 * Set the name of this item.
428 * @param new_name the new name of this item
429 * @param src the identity of the object that initiated the change
433 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
435 if (new_name != item_name) {
436 std::string temp_name = item_name;
437 item_name = new_name;
438 NameChanged (item_name, temp_name, src); /* EMIT_SIGNAL */
442 /** @return the name of this item */
444 TimeAxisViewItem::get_item_name() const
450 * Set selection status.
452 * @param yn true if this item is currently selected
455 TimeAxisViewItem::set_selected(bool yn)
457 if (_selected != yn) {
458 Selectable::set_selected (yn);
464 * Set whether an item should show its selection status.
466 * @param yn true if this item should show its selected status
470 TimeAxisViewItem::set_should_show_selection (bool yn)
472 if (should_show_selection != yn) {
473 should_show_selection = yn;
478 /** @return the TimeAxisView that this item is on */
480 TimeAxisViewItem::get_time_axis_view()
486 * Set the displayed item text.
487 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item.
489 * @param new_name the new name text to display
493 TimeAxisViewItem::set_name_text(const ustring& new_name)
499 last_item_width = trackview.editor().frame_to_pixel(item_duration);
500 name_pixbuf_width = pixel_width (new_name, *NAME_FONT) + 2;
501 name_pixbuf->property_pixbuf() = pixbuf_from_ustring(new_name, NAME_FONT, name_pixbuf_width, NAME_HEIGHT, Gdk::Color ("#000000"));
506 * Set the height of this item.
508 * @param h new height
511 TimeAxisViewItem::set_height (double height)
515 if (name_highlight) {
516 if (height < NAME_HIGHLIGHT_THRESH) {
517 name_highlight->hide ();
518 high_enough_for_name = false;
521 name_highlight->show();
522 high_enough_for_name = true;
525 if (height > NAME_HIGHLIGHT_SIZE) {
526 name_highlight->property_y1() = (double) height - 1 - NAME_HIGHLIGHT_SIZE;
527 name_highlight->property_y2() = (double) height - 2;
530 /* it gets hidden now anyway */
531 name_highlight->property_y1() = (double) 1.0;
532 name_highlight->property_y2() = (double) height;
536 if (visibility & ShowNameText) {
537 name_pixbuf->property_y() = height + 1 - NAME_Y_OFFSET;
541 frame->property_y2() = height - 1;
542 frame_handle_start->property_y2() = height - 1;
543 frame_handle_end->property_y2() = height - 1;
546 vestigial_frame->property_y2() = height - 1;
548 update_name_pixbuf_visibility ();
552 TimeAxisViewItem::set_color (Gdk::Color const & base_color)
554 compute_colors (base_color);
559 TimeAxisViewItem::get_canvas_frame()
565 TimeAxisViewItem::get_canvas_group()
571 TimeAxisViewItem::get_name_highlight()
573 return name_highlight;
576 ArdourCanvas::Pixbuf*
577 TimeAxisViewItem::get_name_pixbuf()
583 * Calculate some contrasting color for displaying various parts of this item, based upon the base color.
585 * @param color the base color of the item
588 TimeAxisViewItem::compute_colors (Gdk::Color const & base_color)
590 unsigned char radius;
595 /* FILL: this is simple */
596 r = base_color.get_red()/256;
597 g = base_color.get_green()/256;
598 b = base_color.get_blue()/256;
599 fill_color = RGBA_TO_UINT(r,g,b,160);
602 if the overall saturation is strong, make the minor colors light.
603 if its weak, make them dark.
605 we do this by moving an equal distance to the other side of the
606 central circle in the color wheel from where we started.
609 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f));
610 minor_shift = 125 - radius;
612 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
614 r = base_color.get_red()/256;
615 g = base_color.get_green()/256;
616 b = base_color.get_blue()/256;
622 /* red sector => green */
627 /* green sector => blue */
635 /* blue sector => red */
640 /* green sector => blue */
649 label_color = RGBA_TO_UINT(r,g,b,255);
650 r = (base_color.get_red()/256) + 127;
651 g = (base_color.get_green()/256) + 127;
652 b = (base_color.get_blue()/256) + 127;
654 label_color = RGBA_TO_UINT(r,g,b,255);
656 /* XXX can we do better than this ? */
660 //frame_color_r = 192;
661 //frame_color_g = 192;
662 //frame_color_b = 194;
664 //selected_frame_color_r = 182;
665 //selected_frame_color_g = 145;
666 //selected_frame_color_b = 168;
668 //handle_color_r = 25;
669 //handle_color_g = 0;
670 //handle_color_b = 255;
671 //lock_handle_color_r = 235;
672 //lock_handle_color_g = 16;
673 //lock_handle_color_b = 16;
677 * Convenience method to set the various canvas item colors
680 TimeAxisViewItem::set_colors()
684 if (name_highlight) {
685 name_highlight->property_fill_color_rgba() = fill_color;
686 name_highlight->property_outline_color_rgba() = fill_color;
688 set_trim_handle_colors();
692 * Sets the frame color depending on whether this item is selected
695 TimeAxisViewItem::set_frame_color()
700 if (_selected && should_show_selection) {
701 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get(), &r, &g, &b, &a);
702 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
705 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_RecordingRect.get(), &r, &g, &b, &a);
706 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
708 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_FrameBase.get(), &r, &g, &b, &a);
709 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity ? fill_opacity : a);
716 * Set the colors of the start and end trim handle depending on object state
719 TimeAxisViewItem::set_trim_handle_colors()
721 if (frame_handle_start) {
722 if (position_locked) {
723 frame_handle_start->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
724 frame_handle_end->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
726 frame_handle_start->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
727 frame_handle_end->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
732 /** @return the samples per unit of this item */
734 TimeAxisViewItem::get_samples_per_unit()
736 return samples_per_unit;
740 * Set the samples per unit of this item.
741 * This item is used to determine the relative visual size and position of this item
742 * based upon its duration and start value.
744 * @param spu the new samples per unit value
747 TimeAxisViewItem::set_samples_per_unit (double spu)
749 samples_per_unit = spu;
750 set_position (this->get_position(), this);
751 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
755 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
757 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
759 if (frame_handle_start) {
760 frame_handle_start->hide();
761 frame_handle_end->hide();
764 } if (pixel_width < 2.0) {
766 if (show_vestigial) {
767 vestigial_frame->show();
770 if (name_highlight) {
771 name_highlight->hide();
778 if (frame_handle_start) {
779 frame_handle_start->hide();
780 frame_handle_end->hide();
783 wide_enough_for_name = false;
786 vestigial_frame->hide();
788 if (name_highlight) {
790 if (_height < NAME_HIGHLIGHT_THRESH) {
791 name_highlight->hide();
792 high_enough_for_name = false;
794 name_highlight->show();
795 if (!get_item_name().empty()) {
796 reset_name_width (pixel_width);
798 high_enough_for_name = true;
801 if (visibility & FullWidthNameHighlight) {
802 name_highlight->property_x2() = pixel_width;
804 name_highlight->property_x2() = pixel_width - 1.0;
811 frame->property_x2() = pixel_width;
814 if (frame_handle_start) {
815 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
816 frame_handle_start->hide();
817 frame_handle_end->hide();
819 frame_handle_start->show();
820 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
821 frame_handle_end->show();
822 frame_handle_end->property_x2() = pixel_width;
826 update_name_pixbuf_visibility ();
830 TimeAxisViewItem::reset_name_width (double /*pixel_width*/)
834 bool pixbuf_holds_full_name;
840 it_width = trackview.editor().frame_to_pixel(item_duration);
841 pb_width = name_pixbuf_width;
843 pixbuf_holds_full_name = last_item_width > pb_width + NAME_X_OFFSET;
844 last_item_width = it_width;
846 if (pixbuf_holds_full_name && (it_width >= pb_width + NAME_X_OFFSET)) {
848 we've previously had the full name length showing
849 and its still showing.
854 if (pb_width > it_width - NAME_X_OFFSET) {
855 pb_width = it_width - NAME_X_OFFSET;
858 if (it_width <= NAME_X_OFFSET) {
859 wide_enough_for_name = false;
861 wide_enough_for_name = true;
864 update_name_pixbuf_visibility ();
866 name_pixbuf->property_pixbuf() = pixbuf_from_ustring(item_name, NAME_FONT, pb_width, NAME_HEIGHT, Gdk::Color ("#000000"));
871 * Callback used to remove this time axis item during the gtk idle loop.
872 * This is used to avoid deleting the obejct while inside the remove_this_item
875 * @param item the TimeAxisViewItem to remove.
876 * @param src the identity of the object that initiated the change.
879 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
881 item->ItemRemoved (item->get_item_name(), src); /* EMIT_SIGNAL */
888 TimeAxisViewItem::set_y (double y)
890 double const old = group->property_y ();
892 group->move (0, y - old);
897 TimeAxisViewItem::update_name_pixbuf_visibility ()
903 if (wide_enough_for_name && high_enough_for_name) {
904 name_pixbuf->show ();
906 name_pixbuf->hide ();