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_framepos;
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());
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());
195 name_highlight->set_data ("timeaxisviewitem", this);
196 name_highlight->property_outline_what() = 0x4;
197 /* we should really use a canvas color property here */
198 name_highlight->property_outline_color_rgba() = RGBA_TO_UINT (0,0,0,255);
204 if (visibility & ShowNameText) {
205 name_pixbuf = new ArdourCanvas::Pixbuf(*group);
206 name_pixbuf->property_x() = NAME_X_OFFSET;
207 name_pixbuf->property_y() = trackview.current_height() + 1 - NAME_Y_OFFSET;
213 /* create our grab handles used for trimming/duration etc */
214 if (!_recregion && !_automation) {
215 frame_handle_start = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
216 frame_handle_start->property_outline_what() = 0x0;
217 frame_handle_end = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
218 frame_handle_end->property_outline_what() = 0x0;
220 frame_handle_start = frame_handle_end = 0;
223 set_color (base_color);
225 set_duration (item_duration, this);
226 set_position (start, this);
229 TimeAxisViewItem::~TimeAxisViewItem()
236 * Set the position of this item on the timeline.
238 * @param pos the new position
239 * @param src the identity of the object that initiated the change
240 * @return true on success
244 TimeAxisViewItem::set_position(nframes64_t pos, void* src, double* delta)
246 if (position_locked) {
250 frame_position = pos;
252 /* This sucks. The GnomeCanvas version I am using
253 doesn't correctly implement gnome_canvas_group_set_arg(),
254 so that simply setting the "x" arg of the group
255 fails to move the group. Instead, we have to
256 use gnome_canvas_item_move(), which does the right
257 thing. I see that in GNOME CVS, the current (Sept 2001)
258 version of GNOME Canvas rectifies this issue cleanly.
262 double new_unit_pos = pos / samples_per_unit;
264 old_unit_pos = group->property_x();
266 if (new_unit_pos != old_unit_pos) {
267 group->move (new_unit_pos - old_unit_pos, 0.0);
271 (*delta) = new_unit_pos - old_unit_pos;
274 PositionChanged (frame_position, src); /* EMIT_SIGNAL */
279 /** @return position of this item on the timeline */
281 TimeAxisViewItem::get_position() const
283 return frame_position;
287 * Set the duration of this item.
289 * @param dur the new duration of this item
290 * @param src the identity of the object that initiated the change
291 * @return true on success
295 TimeAxisViewItem::set_duration (nframes64_t dur, void* src)
297 if ((dur > max_item_duration) || (dur < min_item_duration)) {
298 warning << string_compose (_("new duration %1 frames is out of bounds for %2"), get_item_name(), dur)
309 reset_width_dependent_items (trackview.editor().frame_to_pixel (dur));
311 DurationChanged (dur, src); /* EMIT_SIGNAL */
315 /** @return duration of this item */
317 TimeAxisViewItem::get_duration() const
319 return item_duration;
323 * Set the maximum duration that this item can have.
325 * @param dur the new maximum duration
326 * @param src the identity of the object that initiated the change
329 TimeAxisViewItem::set_max_duration(nframes64_t dur, void* src)
331 max_item_duration = dur;
332 MaxDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
335 /** @return the maximum duration that this item may have */
337 TimeAxisViewItem::get_max_duration() const
339 return max_item_duration;
343 * Set the minimum duration that this item may have.
345 * @param the minimum duration that this item may be set to
346 * @param src the identity of the object that initiated the change
349 TimeAxisViewItem::set_min_duration(nframes64_t dur, void* src)
351 min_item_duration = dur;
352 MinDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
355 /** @return the minimum duration that this item mey have */
357 TimeAxisViewItem::get_min_duration() const
359 return min_item_duration;
363 * Set whether this item is locked to its current position.
364 * Locked items cannot be moved until the item is unlocked again.
366 * @param yn true to lock this item to its current position
367 * @param src the identity of the object that initiated the change
370 TimeAxisViewItem::set_position_locked(bool yn, void* src)
372 position_locked = yn;
373 set_trim_handle_colors();
374 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
377 /** @return true if this item is locked to its current position */
379 TimeAxisViewItem::get_position_locked() const
381 return position_locked;
385 * Set whether the maximum duration constraint is active.
387 * @param active set true to enforce the max duration constraint
388 * @param src the identity of the object that initiated the change
391 TimeAxisViewItem::set_max_duration_active (bool active, void* /*src*/)
393 max_duration_active = active;
396 /** @return true if the maximum duration constraint is active */
398 TimeAxisViewItem::get_max_duration_active() const
400 return max_duration_active;
404 * Set whether the minimum duration constraint is active.
406 * @param active set true to enforce the min duration constraint
407 * @param src the identity of the object that initiated the change
411 TimeAxisViewItem::set_min_duration_active (bool active, void* /*src*/)
413 min_duration_active = active;
416 /** @return true if the maximum duration constraint is active */
418 TimeAxisViewItem::get_min_duration_active() const
420 return min_duration_active;
424 * Set the name of this item.
426 * @param new_name the new name of this item
427 * @param src the identity of the object that initiated the change
431 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
433 if (new_name != item_name) {
434 std::string temp_name = item_name;
435 item_name = new_name;
436 NameChanged (item_name, temp_name, src); /* EMIT_SIGNAL */
440 /** @return the name of this item */
442 TimeAxisViewItem::get_item_name() const
448 * Set selection status.
450 * @param yn true if this item is currently selected
453 TimeAxisViewItem::set_selected(bool yn)
455 if (_selected != yn) {
456 Selectable::set_selected (yn);
462 * Set whether an item should show its selection status.
464 * @param yn true if this item should show its selected status
468 TimeAxisViewItem::set_should_show_selection (bool yn)
470 if (should_show_selection != yn) {
471 should_show_selection = yn;
476 /** @return the TimeAxisView that this item is on */
478 TimeAxisViewItem::get_time_axis_view () const
484 * Set the displayed item text.
485 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item.
487 * @param new_name the new name text to display
491 TimeAxisViewItem::set_name_text(const string& new_name)
497 last_item_width = trackview.editor().frame_to_pixel(item_duration);
498 name_pixbuf_width = pixel_width (new_name, *NAME_FONT) + 2;
499 name_pixbuf->property_pixbuf() = pixbuf_from_string(new_name, NAME_FONT, name_pixbuf_width, NAME_HEIGHT, Gdk::Color ("#000000"));
504 * Set the height of this item.
506 * @param h new height
509 TimeAxisViewItem::set_height (double height)
513 if (name_highlight) {
514 if (height < NAME_HIGHLIGHT_THRESH) {
515 name_highlight->hide ();
516 high_enough_for_name = false;
519 name_highlight->show();
520 high_enough_for_name = true;
523 if (height > NAME_HIGHLIGHT_SIZE) {
524 name_highlight->property_y1() = (double) height - 1 - NAME_HIGHLIGHT_SIZE;
525 name_highlight->property_y2() = (double) height - 1;
528 /* it gets hidden now anyway */
529 name_highlight->property_y1() = (double) 1.0;
530 name_highlight->property_y2() = (double) height;
534 if (visibility & ShowNameText) {
535 name_pixbuf->property_y() = height + 1 - NAME_Y_OFFSET;
539 frame->property_y2() = height - 1;
540 if (frame_handle_start) {
541 frame_handle_start->property_y2() = height - 1;
542 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;
687 set_trim_handle_colors();
691 * Sets the frame color depending on whether this item is selected
694 TimeAxisViewItem::set_frame_color()
699 if (_selected && should_show_selection) {
700 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get(), &r, &g, &b, &a);
701 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
704 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_RecordingRect.get(), &r, &g, &b, &a);
705 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
707 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_FrameBase.get(), &r, &g, &b, &a);
708 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity ? fill_opacity : a);
715 * Set the colors of the start and end trim handle depending on object state
718 TimeAxisViewItem::set_trim_handle_colors()
720 if (frame_handle_start) {
721 if (position_locked) {
722 frame_handle_start->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
723 frame_handle_end->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
725 frame_handle_start->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
726 frame_handle_end->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
731 /** @return the samples per unit of this item */
733 TimeAxisViewItem::get_samples_per_unit()
735 return samples_per_unit;
739 * Set the samples per unit of this item.
740 * This item is used to determine the relative visual size and position of this item
741 * based upon its duration and start value.
743 * @param spu the new samples per unit value
746 TimeAxisViewItem::set_samples_per_unit (double spu)
748 samples_per_unit = spu;
749 set_position (this->get_position(), this);
750 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
754 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
756 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
758 if (frame_handle_start) {
759 frame_handle_start->hide();
760 frame_handle_end->hide();
765 if (pixel_width < 2.0) {
767 if (show_vestigial) {
768 vestigial_frame->show();
771 if (name_highlight) {
772 name_highlight->hide();
779 if (frame_handle_start) {
780 frame_handle_start->hide();
781 frame_handle_end->hide();
784 wide_enough_for_name = false;
787 vestigial_frame->hide();
789 if (name_highlight) {
791 if (_height < NAME_HIGHLIGHT_THRESH) {
792 name_highlight->hide();
793 high_enough_for_name = false;
795 name_highlight->show();
796 if (!get_item_name().empty()) {
797 reset_name_width (pixel_width);
799 high_enough_for_name = true;
802 if (visibility & FullWidthNameHighlight) {
803 name_highlight->property_x2() = pixel_width;
805 name_highlight->property_x2() = pixel_width - 1.0;
812 frame->property_x2() = pixel_width;
815 if (frame_handle_start) {
816 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
817 frame_handle_start->hide();
818 frame_handle_end->hide();
820 frame_handle_start->show();
821 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
822 frame_handle_end->show();
823 frame_handle_end->property_x2() = pixel_width;
827 update_name_pixbuf_visibility ();
831 TimeAxisViewItem::reset_name_width (double /*pixel_width*/)
835 bool pixbuf_holds_full_name;
841 it_width = trackview.editor().frame_to_pixel(item_duration);
842 pb_width = name_pixbuf_width;
844 pixbuf_holds_full_name = last_item_width > pb_width + NAME_X_OFFSET;
845 last_item_width = it_width;
847 if (pixbuf_holds_full_name && (it_width >= pb_width + NAME_X_OFFSET)) {
849 we've previously had the full name length showing
850 and its still showing.
855 if (pb_width > it_width - NAME_X_OFFSET) {
856 pb_width = it_width - NAME_X_OFFSET;
859 if (it_width <= NAME_X_OFFSET) {
860 wide_enough_for_name = false;
862 wide_enough_for_name = true;
865 update_name_pixbuf_visibility ();
867 name_pixbuf->property_pixbuf() = pixbuf_from_string(item_name, NAME_FONT, pb_width, NAME_HEIGHT, Gdk::Color ("#000000"));
872 * Callback used to remove this time axis item during the gtk idle loop.
873 * This is used to avoid deleting the obejct while inside the remove_this_item
876 * @param item the TimeAxisViewItem to remove.
877 * @param src the identity of the object that initiated the change.
880 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
882 item->ItemRemoved (item->get_item_name(), src); /* EMIT_SIGNAL */
889 TimeAxisViewItem::set_y (double y)
891 double const old = group->property_y ();
893 group->move (0, y - old);
898 TimeAxisViewItem::update_name_pixbuf_visibility ()
904 if (wide_enough_for_name && high_enough_for_name) {
905 name_pixbuf->show ();
907 name_pixbuf->hide ();