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 //------------------------------------------------------------------------------
52 /** Initialize const static memeber data */
54 Pango::FontDescription* TimeAxisViewItem::NAME_FONT = 0;
55 bool TimeAxisViewItem::have_name_font = false;
56 const double TimeAxisViewItem::NAME_X_OFFSET = 15.0;
57 const double TimeAxisViewItem::GRAB_HANDLE_LENGTH = 6 ;
59 int TimeAxisViewItem::NAME_HEIGHT;
60 double TimeAxisViewItem::NAME_Y_OFFSET;
61 double TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
62 double TimeAxisViewItem::NAME_HIGHLIGHT_THRESH;
64 //---------------------------------------------------------------------------------------//
65 // Constructor / Desctructor
68 * Constructs a new TimeAxisViewItem.
70 * @param it_name the unique name/Id of this item
71 * @param parant the parent canvas group
72 * @param tv the TimeAxisView we are going to be added to
73 * @param spu samples per unit
75 * @param start the start point of this item
76 * @param duration the duration of this item
78 TimeAxisViewItem::TimeAxisViewItem(const string & it_name, ArdourCanvas::Group& parent, TimeAxisView& tv, double spu, Gdk::Color const & base_color,
79 nframes64_t start, nframes64_t duration, bool recording,
81 : trackview (tv), _height (1.0), _recregion(recording)
83 if (!have_name_font) {
85 /* first constructed item sets up font info */
87 NAME_FONT = get_font_for_style (N_("TimeAxisViewItemName"));
93 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (X_("Hg")); /* ascender + descender */
97 layout->set_font_description (*NAME_FONT);
98 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
100 NAME_HEIGHT = height;
101 NAME_Y_OFFSET = height + 3;
102 NAME_HIGHLIGHT_SIZE = height + 2;
103 NAME_HIGHLIGHT_THRESH = NAME_HIGHLIGHT_SIZE * 3;
105 have_name_font = true;
108 group = new ArdourCanvas::Group (parent);
110 init (it_name, spu, base_color, start, duration, vis, true, true);
114 TimeAxisViewItem::TimeAxisViewItem (const TimeAxisViewItem& other)
115 : sigc::trackable(other)
116 , PBD::ScopedConnectionList()
117 , trackview (other.trackview)
118 , _recregion (other._recregion)
124 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
125 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
127 /* share the other's parent, but still create a new group */
129 Gnome::Canvas::Group* parent = other.group->property_parent();
131 group = new ArdourCanvas::Group (*parent);
133 _selected = other._selected;
136 other.item_name, other.samples_per_unit, c, other.frame_position,
137 other.item_duration, other.visibility, other.wide_enough_for_name, other.high_enough_for_name
142 TimeAxisViewItem::init (
143 const string& it_name, double spu, Gdk::Color const & base_color, nframes64_t start, nframes64_t duration, Visibility vis, bool wide, bool high
146 item_name = it_name ;
147 samples_per_unit = spu ;
148 should_show_selection = true;
149 frame_position = start ;
150 item_duration = duration ;
151 name_connected = false;
153 position_locked = false ;
154 max_item_duration = ARDOUR::max_frames;
155 min_item_duration = 0 ;
156 show_vestigial = true;
159 name_pixbuf_width = 0;
161 wide_enough_for_name = wide;
162 high_enough_for_name = high;
165 warning << "Time Axis Item Duration == 0" << endl ;
168 vestigial_frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, 2.0, trackview.current_height());
169 vestigial_frame->hide ();
170 vestigial_frame->property_outline_what() = 0xF;
171 vestigial_frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
172 vestigial_frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
174 if (visibility & ShowFrame) {
175 frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, trackview.editor().frame_to_pixel(duration), trackview.current_height());
176 frame->property_outline_pixels() = 1;
177 frame->property_outline_what() = 0xF;
178 frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeAxisFrame.get();
180 /* by default draw all 4 edges */
182 uint32_t outline_what = 0x1|0x2|0x4|0x8;
184 if (visibility & HideFrameLeft) {
185 outline_what &= ~(0x1);
188 if (visibility & HideFrameRight) {
189 outline_what &= ~(0x2);
192 if (visibility & HideFrameTB) {
193 outline_what &= ~(0x4 | 0x8);
196 frame->property_outline_what() = outline_what;
202 if (visibility & ShowNameHighlight) {
203 if (visibility & FullWidthNameHighlight) {
204 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);
206 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);
208 name_highlight->set_data ("timeaxisviewitem", this);
214 if (visibility & ShowNameText) {
215 name_pixbuf = new ArdourCanvas::Pixbuf(*group);
216 name_pixbuf->property_x() = NAME_X_OFFSET;
217 name_pixbuf->property_y() = trackview.current_height() + 1 - NAME_Y_OFFSET;
223 /* create our grab handles used for trimming/duration etc */
224 frame_handle_start = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
225 frame_handle_start->property_outline_what() = 0x0;
227 frame_handle_end = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
228 frame_handle_end->property_outline_what() = 0x0;
230 set_color (base_color) ;
232 set_duration (item_duration, this) ;
233 set_position (start, this) ;
239 TimeAxisViewItem::~TimeAxisViewItem()
245 //---------------------------------------------------------------------------------------//
246 // Position and duration Accessors/Mutators
249 * Set the position of this item upon the timeline to the specified value
251 * @param pos the new position
252 * @param src the identity of the object that initiated the change
253 * @return true if the position change was a success, false otherwise
256 TimeAxisViewItem::set_position(nframes64_t pos, void* src, double* delta)
258 if (position_locked) {
262 frame_position = pos;
264 /* This sucks. The GnomeCanvas version I am using
265 doesn't correctly implement gnome_canvas_group_set_arg(),
266 so that simply setting the "x" arg of the group
267 fails to move the group. Instead, we have to
268 use gnome_canvas_item_move(), which does the right
269 thing. I see that in GNOME CVS, the current (Sept 2001)
270 version of GNOME Canvas rectifies this issue cleanly.
273 double old_unit_pos ;
274 double new_unit_pos = pos / samples_per_unit ;
276 old_unit_pos = group->property_x();
278 if (new_unit_pos != old_unit_pos) {
279 group->move (new_unit_pos - old_unit_pos, 0.0);
283 (*delta) = new_unit_pos - old_unit_pos;
286 PositionChanged (frame_position, src) ; /* EMIT_SIGNAL */
292 * Return the position of this item upon the timeline
294 * @return the position of this item
297 TimeAxisViewItem::get_position() const
299 return frame_position;
303 * Sets the duration of this item
305 * @param dur the new duration of this item
306 * @param src the identity of the object that initiated the change
307 * @return true if the duration change was succesful, false otherwise
310 TimeAxisViewItem::set_duration (nframes64_t dur, void* src)
312 if ((dur > max_item_duration) || (dur < min_item_duration)) {
313 warning << string_compose (_("new duration %1 frames is out of bounds for %2"), get_item_name(), dur)
324 reset_width_dependent_items (trackview.editor().frame_to_pixel (dur));
326 DurationChanged (dur, src) ; /* EMIT_SIGNAL */
331 * Returns the duration of this item
335 TimeAxisViewItem::get_duration() const
337 return (item_duration);
341 * Sets the maximum duration that this item make have.
343 * @param dur the new maximum duration
344 * @param src the identity of the object that initiated the change
347 TimeAxisViewItem::set_max_duration(nframes64_t dur, void* src)
349 max_item_duration = dur ;
350 MaxDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
354 * Returns the maxmimum duration that this item may be set to
356 * @return the maximum duration that this item may be set to
359 TimeAxisViewItem::get_max_duration() const
361 return (max_item_duration) ;
365 * Sets the minimu duration that this item may be set to
367 * @param the minimum duration that this item may be set to
368 * @param src the identity of the object that initiated the change
371 TimeAxisViewItem::set_min_duration(nframes64_t dur, void* src)
373 min_item_duration = dur ;
374 MinDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
378 * Returns the minimum duration that this item mey be set to
380 * @return the nimum duration that this item mey be set to
383 TimeAxisViewItem::get_min_duration() const
385 return(min_item_duration) ;
389 * Sets whether the position of this Item is locked to its current position
390 * Locked items cannot be moved until the item is unlocked again.
392 * @param yn set to 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 */
404 * Returns whether this item is locked to its current position
406 * @return true if this item is locked to its current posotion
410 TimeAxisViewItem::get_position_locked() const
412 return (position_locked);
416 * Sets whether the Maximum Duration constraint is active and should be enforced
418 * @param active set true to enforce the max duration constraint
419 * @param src the identity of the object that initiated the change
422 TimeAxisViewItem::set_max_duration_active (bool active, void* /*src*/)
424 max_duration_active = active;
428 * Returns whether the Maximum Duration constraint is active and should be enforced
430 * @return true if the maximum duration constraint is active, false otherwise
433 TimeAxisViewItem::get_max_duration_active() const
435 return(max_duration_active) ;
439 * Sets whether the Minimum Duration constraint is active and should be enforced
441 * @param active set true to enforce the min duration constraint
442 * @param src the identity of the object that initiated the change
445 TimeAxisViewItem::set_min_duration_active (bool active, void* /*src*/)
447 min_duration_active = active ;
451 * Returns whether the Maximum Duration constraint is active and should be enforced
453 * @return true if the maximum duration constraint is active, false otherwise
456 TimeAxisViewItem::get_min_duration_active() const
458 return(min_duration_active) ;
461 //---------------------------------------------------------------------------------------//
462 // Name/Id Accessors/Mutators
465 * Set the name/Id of this item.
467 * @param new_name the new name of this item
468 * @param src the identity of the object that initiated the change
471 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
473 if (new_name != item_name) {
474 std::string temp_name = item_name ;
475 item_name = new_name ;
476 NameChanged (item_name, temp_name, src) ; /* EMIT_SIGNAL */
481 * Returns the name/id of this item
483 * @return the name/id of this item
486 TimeAxisViewItem::get_item_name() const
491 //---------------------------------------------------------------------------------------//
495 * Set to true to indicate that this item is currently selected
497 * @param yn true if this item is currently selected
498 * @param src the identity of the object that initiated the change
501 TimeAxisViewItem::set_selected(bool yn)
503 if (_selected != yn) {
504 Selectable::set_selected (yn);
510 TimeAxisViewItem::set_should_show_selection (bool yn)
512 if (should_show_selection != yn) {
513 should_show_selection = yn;
518 //---------------------------------------------------------------------------------------//
519 // Parent Componenet Methods
522 * Returns the TimeAxisView that this item is upon
524 * @return the timeAxisView that this item is placed upon
527 TimeAxisViewItem::get_time_axis_view()
531 //---------------------------------------------------------------------------------------//
535 * Sets the displayed item text
536 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item
538 * @param new_name the new name text to display
541 TimeAxisViewItem::set_name_text(const ustring& new_name)
547 last_item_width = trackview.editor().frame_to_pixel(item_duration);
548 name_pixbuf_width = pixel_width (new_name, *NAME_FONT) + 2;
549 name_pixbuf->property_pixbuf() = pixbuf_from_ustring(new_name, NAME_FONT, name_pixbuf_width, NAME_HEIGHT, Gdk::Color ("#000000"));
554 * Set the height of this item
556 * @param h the new height
559 TimeAxisViewItem::set_height (double height)
563 if (name_highlight) {
564 if (height < NAME_HIGHLIGHT_THRESH) {
565 name_highlight->hide ();
566 high_enough_for_name = false;
569 name_highlight->show();
570 high_enough_for_name = true;
573 if (height > NAME_HIGHLIGHT_SIZE) {
574 name_highlight->property_y1() = (double) height - 1 - NAME_HIGHLIGHT_SIZE;
575 name_highlight->property_y2() = (double) height - 2;
578 /* it gets hidden now anyway */
579 name_highlight->property_y1() = (double) 1.0;
580 name_highlight->property_y2() = (double) height;
584 if (visibility & ShowNameText) {
585 name_pixbuf->property_y() = height + 1 - NAME_Y_OFFSET;
589 frame->property_y2() = height - 1;
590 frame_handle_start->property_y2() = height - 1;
591 frame_handle_end->property_y2() = height - 1;
594 vestigial_frame->property_y2() = height - 1;
596 update_name_pixbuf_visibility ();
603 TimeAxisViewItem::set_color (Gdk::Color const & base_color)
605 compute_colors (base_color);
613 TimeAxisViewItem::get_canvas_frame()
622 TimeAxisViewItem::get_canvas_group()
631 TimeAxisViewItem::get_name_highlight()
633 return (name_highlight) ;
639 ArdourCanvas::Pixbuf*
640 TimeAxisViewItem::get_name_pixbuf()
642 return (name_pixbuf) ;
646 * Calculates some contrasting color for displaying various parts of this item, based upon the base color
648 * @param color the base color of the item
651 TimeAxisViewItem::compute_colors (Gdk::Color const & base_color)
653 unsigned char radius ;
656 unsigned char r,g,b ;
658 /* FILL: this is simple */
659 r = base_color.get_red()/256 ;
660 g = base_color.get_green()/256 ;
661 b = base_color.get_blue()/256 ;
662 fill_color = RGBA_TO_UINT(r,g,b,160) ;
665 if the overall saturation is strong, make the minor colors light.
666 if its weak, make them dark.
668 we do this by moving an equal distance to the other side of the
669 central circle in the color wheel from where we started.
672 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f)) ;
673 minor_shift = 125 - radius ;
675 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
677 r = base_color.get_red()/256;
678 g = base_color.get_green()/256;
679 b = base_color.get_blue()/256;
685 /* red sector => green */
690 /* green sector => blue */
698 /* blue sector => red */
703 /* green sector => blue */
712 label_color = RGBA_TO_UINT(r,g,b,255);
713 r = (base_color.get_red()/256) + 127 ;
714 g = (base_color.get_green()/256) + 127 ;
715 b = (base_color.get_blue()/256) + 127 ;
717 label_color = RGBA_TO_UINT(r,g,b,255);
719 /* XXX can we do better than this ? */
720 /* We're trying ;) */
723 //frame_color_r = 192;
724 //frame_color_g = 192;
725 //frame_color_b = 194;
727 //selected_frame_color_r = 182;
728 //selected_frame_color_g = 145;
729 //selected_frame_color_b = 168;
731 //handle_color_r = 25 ;
732 //handle_color_g = 0 ;
733 //handle_color_b = 255 ;
734 //lock_handle_color_r = 235 ;
735 //lock_handle_color_g = 16;
736 //lock_handle_color_b = 16;
740 * Convenience method to set the various canvas item colors
743 TimeAxisViewItem::set_colors()
747 if (name_highlight) {
748 name_highlight->property_fill_color_rgba() = fill_color;
749 name_highlight->property_outline_color_rgba() = fill_color;
751 set_trim_handle_colors() ;
755 * Sets the frame color depending on whether this item is selected
758 TimeAxisViewItem::set_frame_color()
763 if (_selected && should_show_selection) {
764 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get(), &r, &g, &b, &a);
765 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
768 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_RecordingRect.get(), &r, &g, &b, &a);
769 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
771 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_FrameBase.get(), &r, &g, &b, &a);
772 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity ? fill_opacity : a);
779 * Sets the colors of the start and end trim handle depending on object state
783 TimeAxisViewItem::set_trim_handle_colors()
785 if (frame_handle_start) {
786 if (position_locked) {
787 frame_handle_start->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
788 frame_handle_end->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
790 frame_handle_start->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
791 frame_handle_end->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
797 TimeAxisViewItem::get_samples_per_unit()
799 return(samples_per_unit) ;
803 TimeAxisViewItem::set_samples_per_unit (double spu)
805 samples_per_unit = spu ;
806 set_position (this->get_position(), this);
807 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
811 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
813 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
815 if (frame_handle_start) {
816 frame_handle_start->hide();
817 frame_handle_end->hide();
820 } if (pixel_width < 2.0) {
822 if (show_vestigial) {
823 vestigial_frame->show();
826 if (name_highlight) {
827 name_highlight->hide();
834 if (frame_handle_start) {
835 frame_handle_start->hide();
836 frame_handle_end->hide();
839 wide_enough_for_name = false;
842 vestigial_frame->hide();
844 if (name_highlight) {
846 if (_height < NAME_HIGHLIGHT_THRESH) {
847 name_highlight->hide();
848 high_enough_for_name = false;
850 name_highlight->show();
851 if (!get_item_name().empty()) {
852 reset_name_width (pixel_width);
854 high_enough_for_name = true;
857 if (visibility & FullWidthNameHighlight) {
858 name_highlight->property_x2() = pixel_width;
860 name_highlight->property_x2() = pixel_width - 1.0;
867 frame->property_x2() = pixel_width;
870 if (frame_handle_start) {
871 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
872 frame_handle_start->hide();
873 frame_handle_end->hide();
875 frame_handle_start->show();
876 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
877 frame_handle_end->show();
878 frame_handle_end->property_x2() = pixel_width;
882 update_name_pixbuf_visibility ();
886 TimeAxisViewItem::reset_name_width (double /*pixel_width*/)
890 bool pixbuf_holds_full_name;
896 it_width = trackview.editor().frame_to_pixel(item_duration);
897 pb_width = name_pixbuf_width;
899 pixbuf_holds_full_name = last_item_width > pb_width + NAME_X_OFFSET;
900 last_item_width = it_width;
902 if (pixbuf_holds_full_name && (it_width >= pb_width + NAME_X_OFFSET)) {
904 we've previously had the full name length showing
905 and its still showing.
910 if (pb_width > it_width - NAME_X_OFFSET) {
911 pb_width = it_width - NAME_X_OFFSET;
914 if (it_width <= NAME_X_OFFSET) {
915 wide_enough_for_name = false;
917 wide_enough_for_name = true;
920 update_name_pixbuf_visibility ();
922 name_pixbuf->property_pixbuf() = pixbuf_from_ustring(item_name, NAME_FONT, pb_width, NAME_HEIGHT, Gdk::Color ("#000000"));
927 //---------------------------------------------------------------------------------------//
928 // Handle time axis removal
931 * Handles the Removal of this time axis item
932 * This _needs_ to be called to alert others of the removal properly, ie where the source
933 * of the removal came from.
935 * XXX Although im not too happy about this method of doing things, I cant think of a cleaner method
936 * just now to capture the source of the removal
938 * @param src the identity of the object that initiated the change
941 TimeAxisViewItem::remove_this_item(void* src)
944 defer to idle loop, otherwise we'll delete this object
945 while we're still inside this function ...
947 Glib::signal_idle().connect(sigc::bind (sigc::ptr_fun (&TimeAxisViewItem::idle_remove_this_item), this, src));
951 * Callback used to remove this time axis item during the gtk idle loop
952 * This is used to avoid deleting the obejct while inside the remove_this_item
955 * @param item the TimeAxisViewItem to remove
956 * @param src the identity of the object that initiated the change
959 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
961 item->ItemRemoved (item->get_item_name(), src) ; /* EMIT_SIGNAL */
968 TimeAxisViewItem::set_y (double y)
970 double const old = group->property_y ();
972 group->move (0, y - old);
977 TimeAxisViewItem::update_name_pixbuf_visibility ()
983 if (wide_enough_for_name && high_enough_for_name) {
984 name_pixbuf->show ();
986 name_pixbuf->hide ();