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), _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)
561 if (name_highlight) {
562 if (height < NAME_HIGHLIGHT_THRESH) {
563 name_highlight->hide ();
564 high_enough_for_name = false;
567 name_highlight->show();
568 high_enough_for_name = true;
571 if (height > NAME_HIGHLIGHT_SIZE) {
572 name_highlight->property_y1() = (double) height - 1 - NAME_HIGHLIGHT_SIZE;
573 name_highlight->property_y2() = (double) height - 2;
576 /* it gets hidden now anyway */
577 name_highlight->property_y1() = (double) 1.0;
578 name_highlight->property_y2() = (double) height;
582 if (visibility & ShowNameText) {
583 name_pixbuf->property_y() = height + 1 - NAME_Y_OFFSET;
587 frame->property_y2() = height - 1;
588 frame_handle_start->property_y2() = height - 1;
589 frame_handle_end->property_y2() = height - 1;
592 vestigial_frame->property_y2() = height - 1;
594 update_name_pixbuf_visibility ();
601 TimeAxisViewItem::set_color (Gdk::Color const & base_color)
603 compute_colors (base_color);
611 TimeAxisViewItem::get_canvas_frame()
620 TimeAxisViewItem::get_canvas_group()
629 TimeAxisViewItem::get_name_highlight()
631 return (name_highlight) ;
637 ArdourCanvas::Pixbuf*
638 TimeAxisViewItem::get_name_pixbuf()
640 return (name_pixbuf) ;
644 * Calculates some contrasting color for displaying various parts of this item, based upon the base color
646 * @param color the base color of the item
649 TimeAxisViewItem::compute_colors (Gdk::Color const & base_color)
651 unsigned char radius ;
654 unsigned char r,g,b ;
656 /* FILL: this is simple */
657 r = base_color.get_red()/256 ;
658 g = base_color.get_green()/256 ;
659 b = base_color.get_blue()/256 ;
660 fill_color = RGBA_TO_UINT(r,g,b,160) ;
663 if the overall saturation is strong, make the minor colors light.
664 if its weak, make them dark.
666 we do this by moving an equal distance to the other side of the
667 central circle in the color wheel from where we started.
670 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f)) ;
671 minor_shift = 125 - radius ;
673 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
675 r = base_color.get_red()/256;
676 g = base_color.get_green()/256;
677 b = base_color.get_blue()/256;
683 /* red sector => green */
688 /* green sector => blue */
696 /* blue sector => red */
701 /* green sector => blue */
710 label_color = RGBA_TO_UINT(r,g,b,255);
711 r = (base_color.get_red()/256) + 127 ;
712 g = (base_color.get_green()/256) + 127 ;
713 b = (base_color.get_blue()/256) + 127 ;
715 label_color = RGBA_TO_UINT(r,g,b,255);
717 /* XXX can we do better than this ? */
718 /* We're trying ;) */
721 //frame_color_r = 192;
722 //frame_color_g = 192;
723 //frame_color_b = 194;
725 //selected_frame_color_r = 182;
726 //selected_frame_color_g = 145;
727 //selected_frame_color_b = 168;
729 //handle_color_r = 25 ;
730 //handle_color_g = 0 ;
731 //handle_color_b = 255 ;
732 //lock_handle_color_r = 235 ;
733 //lock_handle_color_g = 16;
734 //lock_handle_color_b = 16;
738 * Convenience method to set the various canvas item colors
741 TimeAxisViewItem::set_colors()
745 if (name_highlight) {
746 name_highlight->property_fill_color_rgba() = fill_color;
747 name_highlight->property_outline_color_rgba() = fill_color;
749 set_trim_handle_colors() ;
753 * Sets the frame color depending on whether this item is selected
756 TimeAxisViewItem::set_frame_color()
761 if (_selected && should_show_selection) {
762 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get(), &r, &g, &b, &a);
763 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
766 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_RecordingRect.get(), &r, &g, &b, &a);
767 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
769 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_FrameBase.get(), &r, &g, &b, &a);
770 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity ? fill_opacity : a);
777 * Sets the colors of the start and end trim handle depending on object state
781 TimeAxisViewItem::set_trim_handle_colors()
783 if (frame_handle_start) {
784 if (position_locked) {
785 frame_handle_start->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
786 frame_handle_end->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
788 frame_handle_start->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
789 frame_handle_end->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
795 TimeAxisViewItem::get_samples_per_unit()
797 return(samples_per_unit) ;
801 TimeAxisViewItem::set_samples_per_unit (double spu)
803 samples_per_unit = spu ;
804 set_position (this->get_position(), this);
805 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
809 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
811 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
813 if (frame_handle_start) {
814 frame_handle_start->hide();
815 frame_handle_end->hide();
818 } if (pixel_width < 2.0) {
820 if (show_vestigial) {
821 vestigial_frame->show();
824 if (name_highlight) {
825 name_highlight->hide();
832 if (frame_handle_start) {
833 frame_handle_start->hide();
834 frame_handle_end->hide();
837 wide_enough_for_name = false;
840 vestigial_frame->hide();
842 if (name_highlight) {
844 double height = name_highlight->property_y2 ();
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;
884 TimeAxisViewItem::reset_name_width (double /*pixel_width*/)
888 bool pixbuf_holds_full_name;
894 it_width = trackview.editor().frame_to_pixel(item_duration);
895 pb_width = name_pixbuf_width;
897 pixbuf_holds_full_name = last_item_width > pb_width + NAME_X_OFFSET;
898 last_item_width = it_width;
900 if (pixbuf_holds_full_name && (it_width >= pb_width + NAME_X_OFFSET)) {
902 we've previously had the full name length showing
903 and its still showing.
908 if (pb_width > it_width - NAME_X_OFFSET) {
909 pb_width = it_width - NAME_X_OFFSET;
912 if (it_width <= NAME_X_OFFSET) {
913 wide_enough_for_name = false;
915 wide_enough_for_name = true;
918 update_name_pixbuf_visibility ();
920 name_pixbuf->property_pixbuf() = pixbuf_from_ustring(item_name, NAME_FONT, pb_width, NAME_HEIGHT, Gdk::Color ("#000000"));
925 //---------------------------------------------------------------------------------------//
926 // Handle time axis removal
929 * Handles the Removal of this time axis item
930 * This _needs_ to be called to alert others of the removal properly, ie where the source
931 * of the removal came from.
933 * XXX Although im not too happy about this method of doing things, I cant think of a cleaner method
934 * just now to capture the source of the removal
936 * @param src the identity of the object that initiated the change
939 TimeAxisViewItem::remove_this_item(void* src)
942 defer to idle loop, otherwise we'll delete this object
943 while we're still inside this function ...
945 Glib::signal_idle().connect(sigc::bind (sigc::ptr_fun (&TimeAxisViewItem::idle_remove_this_item), this, src));
949 * Callback used to remove this time axis item during the gtk idle loop
950 * This is used to avoid deleting the obejct while inside the remove_this_item
953 * @param item the TimeAxisViewItem to remove
954 * @param src the identity of the object that initiated the change
957 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
959 item->ItemRemoved (item->get_item_name(), src) ; /* EMIT_SIGNAL */
966 TimeAxisViewItem::set_y (double y)
968 double const old = group->property_y ();
970 group->move (0, y - old);
975 TimeAxisViewItem::update_name_pixbuf_visibility ()
981 if (wide_enough_for_name && high_enough_for_name) {
982 name_pixbuf->show ();
984 name_pixbuf->hide ();