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 const double TimeAxisViewItem::NAME_X_OFFSET = 15.0;
53 const double TimeAxisViewItem::GRAB_HANDLE_LENGTH = 6;
55 int TimeAxisViewItem::NAME_HEIGHT;
56 double TimeAxisViewItem::NAME_Y_OFFSET;
57 double TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
58 double TimeAxisViewItem::NAME_HIGHLIGHT_THRESH;
61 TimeAxisViewItem::set_constant_heights ()
63 NAME_FONT = get_font_for_style (X_("TimeAxisViewItemName"));
69 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (X_("Hg")); /* ascender + descender */
73 layout->set_font_description (*NAME_FONT);
74 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
77 NAME_Y_OFFSET = height + 3;
78 NAME_HIGHLIGHT_SIZE = height + 2;
79 NAME_HIGHLIGHT_THRESH = NAME_HIGHLIGHT_SIZE * 3;
83 * Construct a new TimeAxisViewItem.
85 * @param it_name the unique name of this item
86 * @param parent the parent canvas group
87 * @param tv the TimeAxisView we are going to be added to
88 * @param spu samples per unit
90 * @param start the start point of this item
91 * @param duration the duration of this item
92 * @param recording true if this is a recording region view
93 * @param automation true if this is an automation region view
95 TimeAxisViewItem::TimeAxisViewItem(
96 const string & it_name, ArdourCanvas::Group& parent, TimeAxisView& tv, double spu, Gdk::Color const & base_color,
97 nframes64_t start, nframes64_t duration, bool recording, bool automation, Visibility vis
101 , _recregion (recording)
102 , _automation (automation)
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)
114 , _automation (other._automation)
120 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
121 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
123 /* share the other's parent, but still create a new group */
125 Gnome::Canvas::Group* parent = other.group->property_parent();
127 group = new ArdourCanvas::Group (*parent);
129 _selected = other._selected;
132 other.item_name, other.samples_per_unit, c, other.frame_position,
133 other.item_duration, other.visibility, other.wide_enough_for_name, other.high_enough_for_name
138 TimeAxisViewItem::init (
139 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());
172 frame->property_outline_pixels() = 1;
173 frame->property_outline_what() = 0xF;
176 frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
178 frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeAxisFrame.get();
181 frame->property_outline_what() = 0x1|0x2|0x4|0x8;
187 if (visibility & ShowNameHighlight) {
189 if (visibility & FullWidthNameHighlight) {
190 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);
192 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);
195 name_highlight->set_data ("timeaxisviewitem", this);
201 if (visibility & ShowNameText) {
202 name_pixbuf = new ArdourCanvas::Pixbuf(*group);
203 name_pixbuf->property_x() = NAME_X_OFFSET;
204 name_pixbuf->property_y() = trackview.current_height() + 1 - NAME_Y_OFFSET;
210 /* create our grab handles used for trimming/duration etc */
211 if (!_recregion && !_automation) {
212 frame_handle_start = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
213 frame_handle_start->property_outline_what() = 0x0;
214 frame_handle_end = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
215 frame_handle_end->property_outline_what() = 0x0;
217 frame_handle_start = frame_handle_end = 0;
220 set_color (base_color);
222 set_duration (item_duration, this);
223 set_position (start, this);
226 TimeAxisViewItem::~TimeAxisViewItem()
233 * Set the position of this item on the timeline.
235 * @param pos the new position
236 * @param src the identity of the object that initiated the change
237 * @return true on success
241 TimeAxisViewItem::set_position(nframes64_t pos, void* src, double* delta)
243 if (position_locked) {
247 frame_position = pos;
249 /* This sucks. The GnomeCanvas version I am using
250 doesn't correctly implement gnome_canvas_group_set_arg(),
251 so that simply setting the "x" arg of the group
252 fails to move the group. Instead, we have to
253 use gnome_canvas_item_move(), which does the right
254 thing. I see that in GNOME CVS, the current (Sept 2001)
255 version of GNOME Canvas rectifies this issue cleanly.
259 double new_unit_pos = pos / samples_per_unit;
261 old_unit_pos = group->property_x();
263 if (new_unit_pos != old_unit_pos) {
264 group->move (new_unit_pos - old_unit_pos, 0.0);
268 (*delta) = new_unit_pos - old_unit_pos;
271 PositionChanged (frame_position, src); /* EMIT_SIGNAL */
276 /** @return position of this item on the timeline */
278 TimeAxisViewItem::get_position() const
280 return frame_position;
284 * Set the duration of this item.
286 * @param dur the new duration of this item
287 * @param src the identity of the object that initiated the change
288 * @return true on success
292 TimeAxisViewItem::set_duration (nframes64_t dur, void* src)
294 if ((dur > max_item_duration) || (dur < min_item_duration)) {
295 warning << string_compose (_("new duration %1 frames is out of bounds for %2"), get_item_name(), dur)
306 reset_width_dependent_items (trackview.editor().frame_to_pixel (dur));
308 DurationChanged (dur, src); /* EMIT_SIGNAL */
312 /** @return duration of this item */
314 TimeAxisViewItem::get_duration() const
316 return item_duration;
320 * Set the maximum duration that this item can have.
322 * @param dur the new maximum duration
323 * @param src the identity of the object that initiated the change
326 TimeAxisViewItem::set_max_duration(nframes64_t dur, void* src)
328 max_item_duration = dur;
329 MaxDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
332 /** @return the maximum duration that this item may have */
334 TimeAxisViewItem::get_max_duration() const
336 return max_item_duration;
340 * Set the minimum duration that this item may have.
342 * @param the minimum duration that this item may be set to
343 * @param src the identity of the object that initiated the change
346 TimeAxisViewItem::set_min_duration(nframes64_t dur, void* src)
348 min_item_duration = dur;
349 MinDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
352 /** @return the minimum duration that this item mey have */
354 TimeAxisViewItem::get_min_duration() const
356 return min_item_duration;
360 * Set whether this item is locked to its current position.
361 * Locked items cannot be moved until the item is unlocked again.
363 * @param yn true to lock this item to its current position
364 * @param src the identity of the object that initiated the change
367 TimeAxisViewItem::set_position_locked(bool yn, void* src)
369 position_locked = yn;
370 set_trim_handle_colors();
371 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
374 /** @return true if this item is locked to its current position */
376 TimeAxisViewItem::get_position_locked() const
378 return position_locked;
382 * Set whether the maximum duration constraint is active.
384 * @param active set true to enforce the max duration constraint
385 * @param src the identity of the object that initiated the change
388 TimeAxisViewItem::set_max_duration_active (bool active, void* /*src*/)
390 max_duration_active = active;
393 /** @return true if the maximum duration constraint is active */
395 TimeAxisViewItem::get_max_duration_active() const
397 return max_duration_active;
401 * Set whether the minimum duration constraint is active.
403 * @param active set true to enforce the min duration constraint
404 * @param src the identity of the object that initiated the change
408 TimeAxisViewItem::set_min_duration_active (bool active, void* /*src*/)
410 min_duration_active = active;
413 /** @return true if the maximum duration constraint is active */
415 TimeAxisViewItem::get_min_duration_active() const
417 return min_duration_active;
421 * Set the name of this item.
423 * @param new_name the new name of this item
424 * @param src the identity of the object that initiated the change
428 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
430 if (new_name != item_name) {
431 std::string temp_name = item_name;
432 item_name = new_name;
433 NameChanged (item_name, temp_name, src); /* EMIT_SIGNAL */
437 /** @return the name of this item */
439 TimeAxisViewItem::get_item_name() const
445 * Set selection status.
447 * @param yn true if this item is currently selected
450 TimeAxisViewItem::set_selected(bool yn)
452 if (_selected != yn) {
453 Selectable::set_selected (yn);
459 * Set whether an item should show its selection status.
461 * @param yn true if this item should show its selected status
465 TimeAxisViewItem::set_should_show_selection (bool yn)
467 if (should_show_selection != yn) {
468 should_show_selection = yn;
473 /** @return the TimeAxisView that this item is on */
475 TimeAxisViewItem::get_time_axis_view () const
481 * Set the displayed item text.
482 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item.
484 * @param new_name the new name text to display
488 TimeAxisViewItem::set_name_text(const ustring& new_name)
494 last_item_width = trackview.editor().frame_to_pixel(item_duration);
495 name_pixbuf_width = pixel_width (new_name, *NAME_FONT) + 2;
496 name_pixbuf->property_pixbuf() = pixbuf_from_ustring(new_name, NAME_FONT, name_pixbuf_width, NAME_HEIGHT, Gdk::Color ("#000000"));
501 * Set the height of this item.
503 * @param h new height
506 TimeAxisViewItem::set_height (double height)
510 if (name_highlight) {
511 if (height < NAME_HIGHLIGHT_THRESH) {
512 name_highlight->hide ();
513 high_enough_for_name = false;
516 name_highlight->show();
517 high_enough_for_name = true;
520 if (height > NAME_HIGHLIGHT_SIZE) {
521 name_highlight->property_y1() = (double) height - 1 - NAME_HIGHLIGHT_SIZE;
522 name_highlight->property_y2() = (double) height - 2;
525 /* it gets hidden now anyway */
526 name_highlight->property_y1() = (double) 1.0;
527 name_highlight->property_y2() = (double) height;
531 if (visibility & ShowNameText) {
532 name_pixbuf->property_y() = height + 1 - NAME_Y_OFFSET;
536 frame->property_y2() = height - 1;
537 if (frame_handle_start) {
538 frame_handle_start->property_y2() = height - 1;
539 frame_handle_end->property_y2() = height - 1;
543 vestigial_frame->property_y2() = height - 1;
545 update_name_pixbuf_visibility ();
549 TimeAxisViewItem::set_color (Gdk::Color const & base_color)
551 compute_colors (base_color);
556 TimeAxisViewItem::get_canvas_frame()
562 TimeAxisViewItem::get_canvas_group()
568 TimeAxisViewItem::get_name_highlight()
570 return name_highlight;
573 ArdourCanvas::Pixbuf*
574 TimeAxisViewItem::get_name_pixbuf()
580 * Calculate some contrasting color for displaying various parts of this item, based upon the base color.
582 * @param color the base color of the item
585 TimeAxisViewItem::compute_colors (Gdk::Color const & base_color)
587 unsigned char radius;
592 /* FILL: this is simple */
593 r = base_color.get_red()/256;
594 g = base_color.get_green()/256;
595 b = base_color.get_blue()/256;
596 fill_color = RGBA_TO_UINT(r,g,b,160);
599 if the overall saturation is strong, make the minor colors light.
600 if its weak, make them dark.
602 we do this by moving an equal distance to the other side of the
603 central circle in the color wheel from where we started.
606 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f));
607 minor_shift = 125 - radius;
609 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
611 r = base_color.get_red()/256;
612 g = base_color.get_green()/256;
613 b = base_color.get_blue()/256;
619 /* red sector => green */
624 /* green sector => blue */
632 /* blue sector => red */
637 /* green sector => blue */
646 label_color = RGBA_TO_UINT(r,g,b,255);
647 r = (base_color.get_red()/256) + 127;
648 g = (base_color.get_green()/256) + 127;
649 b = (base_color.get_blue()/256) + 127;
651 label_color = RGBA_TO_UINT(r,g,b,255);
653 /* XXX can we do better than this ? */
657 //frame_color_r = 192;
658 //frame_color_g = 192;
659 //frame_color_b = 194;
661 //selected_frame_color_r = 182;
662 //selected_frame_color_g = 145;
663 //selected_frame_color_b = 168;
665 //handle_color_r = 25;
666 //handle_color_g = 0;
667 //handle_color_b = 255;
668 //lock_handle_color_r = 235;
669 //lock_handle_color_g = 16;
670 //lock_handle_color_b = 16;
674 * Convenience method to set the various canvas item colors
677 TimeAxisViewItem::set_colors()
681 if (name_highlight) {
682 name_highlight->property_fill_color_rgba() = fill_color;
683 name_highlight->property_outline_color_rgba() = fill_color;
685 set_trim_handle_colors();
689 * Sets the frame color depending on whether this item is selected
692 TimeAxisViewItem::set_frame_color()
697 if (_selected && should_show_selection) {
698 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get(), &r, &g, &b, &a);
699 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
702 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_RecordingRect.get(), &r, &g, &b, &a);
703 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
705 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_FrameBase.get(), &r, &g, &b, &a);
706 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity ? fill_opacity : a);
713 * Set the colors of the start and end trim handle depending on object state
716 TimeAxisViewItem::set_trim_handle_colors()
718 if (frame_handle_start) {
719 if (position_locked) {
720 frame_handle_start->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
721 frame_handle_end->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
723 frame_handle_start->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
724 frame_handle_end->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
729 /** @return the samples per unit of this item */
731 TimeAxisViewItem::get_samples_per_unit()
733 return samples_per_unit;
737 * Set the samples per unit of this item.
738 * This item is used to determine the relative visual size and position of this item
739 * based upon its duration and start value.
741 * @param spu the new samples per unit value
744 TimeAxisViewItem::set_samples_per_unit (double spu)
746 samples_per_unit = spu;
747 set_position (this->get_position(), this);
748 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
752 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
754 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
756 if (frame_handle_start) {
757 frame_handle_start->hide();
758 frame_handle_end->hide();
763 if (pixel_width < 2.0) {
765 if (show_vestigial) {
766 vestigial_frame->show();
769 if (name_highlight) {
770 name_highlight->hide();
777 if (frame_handle_start) {
778 frame_handle_start->hide();
779 frame_handle_end->hide();
782 wide_enough_for_name = false;
785 vestigial_frame->hide();
787 if (name_highlight) {
789 if (_height < NAME_HIGHLIGHT_THRESH) {
790 name_highlight->hide();
791 high_enough_for_name = false;
793 name_highlight->show();
794 if (!get_item_name().empty()) {
795 reset_name_width (pixel_width);
797 high_enough_for_name = true;
800 if (visibility & FullWidthNameHighlight) {
801 name_highlight->property_x2() = pixel_width;
803 name_highlight->property_x2() = pixel_width - 1.0;
810 frame->property_x2() = pixel_width;
813 if (frame_handle_start) {
814 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
815 frame_handle_start->hide();
816 frame_handle_end->hide();
818 frame_handle_start->show();
819 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
820 frame_handle_end->show();
821 frame_handle_end->property_x2() = pixel_width;
825 update_name_pixbuf_visibility ();
829 TimeAxisViewItem::reset_name_width (double /*pixel_width*/)
833 bool pixbuf_holds_full_name;
839 it_width = trackview.editor().frame_to_pixel(item_duration);
840 pb_width = name_pixbuf_width;
842 pixbuf_holds_full_name = last_item_width > pb_width + NAME_X_OFFSET;
843 last_item_width = it_width;
845 if (pixbuf_holds_full_name && (it_width >= pb_width + NAME_X_OFFSET)) {
847 we've previously had the full name length showing
848 and its still showing.
853 if (pb_width > it_width - NAME_X_OFFSET) {
854 pb_width = it_width - NAME_X_OFFSET;
857 if (it_width <= NAME_X_OFFSET) {
858 wide_enough_for_name = false;
860 wide_enough_for_name = true;
863 update_name_pixbuf_visibility ();
865 name_pixbuf->property_pixbuf() = pixbuf_from_ustring(item_name, NAME_FONT, pb_width, NAME_HEIGHT, Gdk::Color ("#000000"));
870 * Callback used to remove this time axis item during the gtk idle loop.
871 * This is used to avoid deleting the obejct while inside the remove_this_item
874 * @param item the TimeAxisViewItem to remove.
875 * @param src the identity of the object that initiated the change.
878 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
880 item->ItemRemoved (item->get_item_name(), src); /* EMIT_SIGNAL */
887 TimeAxisViewItem::set_y (double y)
889 double const old = group->property_y ();
891 group->move (0, y - old);
896 TimeAxisViewItem::update_name_pixbuf_visibility ()
902 if (wide_enough_for_name && high_enough_for_name) {
903 name_pixbuf->show ();
905 name_pixbuf->hide ();