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 , trackview (other.trackview)
122 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
123 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
125 /* share the other's parent, but still create a new group */
127 Gnome::Canvas::Group* parent = other.group->property_parent();
129 group = new ArdourCanvas::Group (*parent);
131 _selected = other._selected;
134 other.item_name, other.samples_per_unit, c, other.frame_position,
135 other.item_duration, other.visibility, other.wide_enough_for_name, other.high_enough_for_name
140 TimeAxisViewItem::init (
141 const string& it_name, double spu, Gdk::Color const & base_color, nframes64_t start, nframes64_t duration, Visibility vis, bool wide, bool high
144 item_name = it_name ;
145 samples_per_unit = spu ;
146 should_show_selection = true;
147 frame_position = start ;
148 item_duration = duration ;
149 name_connected = false;
151 position_locked = false ;
152 max_item_duration = ARDOUR::max_frames;
153 min_item_duration = 0 ;
154 show_vestigial = true;
157 name_pixbuf_width = 0;
159 wide_enough_for_name = wide;
160 high_enough_for_name = high;
163 warning << "Time Axis Item Duration == 0" << endl ;
166 vestigial_frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, 2.0, trackview.current_height());
167 vestigial_frame->hide ();
168 vestigial_frame->property_outline_what() = 0xF;
169 vestigial_frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
170 vestigial_frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
172 if (visibility & ShowFrame) {
173 frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, trackview.editor().frame_to_pixel(duration), trackview.current_height());
174 frame->property_outline_pixels() = 1;
175 frame->property_outline_what() = 0xF;
176 frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeAxisFrame.get();
178 /* by default draw all 4 edges */
180 uint32_t outline_what = 0x1|0x2|0x4|0x8;
182 if (visibility & HideFrameLeft) {
183 outline_what &= ~(0x1);
186 if (visibility & HideFrameRight) {
187 outline_what &= ~(0x2);
190 if (visibility & HideFrameTB) {
191 outline_what &= ~(0x4 | 0x8);
194 frame->property_outline_what() = outline_what;
200 if (visibility & ShowNameHighlight) {
201 if (visibility & FullWidthNameHighlight) {
202 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);
204 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);
206 name_highlight->set_data ("timeaxisviewitem", this);
212 if (visibility & ShowNameText) {
213 name_pixbuf = new ArdourCanvas::Pixbuf(*group);
214 name_pixbuf->property_x() = NAME_X_OFFSET;
215 name_pixbuf->property_y() = trackview.current_height() + 1 - NAME_Y_OFFSET;
221 /* create our grab handles used for trimming/duration etc */
222 frame_handle_start = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
223 frame_handle_start->property_outline_what() = 0x0;
225 frame_handle_end = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
226 frame_handle_end->property_outline_what() = 0x0;
228 set_color (base_color) ;
230 set_duration (item_duration, this) ;
231 set_position (start, this) ;
237 TimeAxisViewItem::~TimeAxisViewItem()
243 //---------------------------------------------------------------------------------------//
244 // Position and duration Accessors/Mutators
247 * Set the position of this item upon the timeline to the specified value
249 * @param pos the new position
250 * @param src the identity of the object that initiated the change
251 * @return true if the position change was a success, false otherwise
254 TimeAxisViewItem::set_position(nframes64_t pos, void* src, double* delta)
256 if (position_locked) {
260 frame_position = pos;
262 /* This sucks. The GnomeCanvas version I am using
263 doesn't correctly implement gnome_canvas_group_set_arg(),
264 so that simply setting the "x" arg of the group
265 fails to move the group. Instead, we have to
266 use gnome_canvas_item_move(), which does the right
267 thing. I see that in GNOME CVS, the current (Sept 2001)
268 version of GNOME Canvas rectifies this issue cleanly.
271 double old_unit_pos ;
272 double new_unit_pos = pos / samples_per_unit ;
274 old_unit_pos = group->property_x();
276 if (new_unit_pos != old_unit_pos) {
277 group->move (new_unit_pos - old_unit_pos, 0.0);
281 (*delta) = new_unit_pos - old_unit_pos;
284 PositionChanged (frame_position, src) ; /* EMIT_SIGNAL */
290 * Return the position of this item upon the timeline
292 * @return the position of this item
295 TimeAxisViewItem::get_position() const
297 return frame_position;
301 * Sets the duration of this item
303 * @param dur the new duration of this item
304 * @param src the identity of the object that initiated the change
305 * @return true if the duration change was succesful, false otherwise
308 TimeAxisViewItem::set_duration (nframes64_t dur, void* src)
310 if ((dur > max_item_duration) || (dur < min_item_duration)) {
311 warning << string_compose (_("new duration %1 frames is out of bounds for %2"), get_item_name(), dur)
322 reset_width_dependent_items (trackview.editor().frame_to_pixel (dur));
324 DurationChanged (dur, src) ; /* EMIT_SIGNAL */
329 * Returns the duration of this item
333 TimeAxisViewItem::get_duration() const
335 return (item_duration);
339 * Sets the maximum duration that this item make have.
341 * @param dur the new maximum duration
342 * @param src the identity of the object that initiated the change
345 TimeAxisViewItem::set_max_duration(nframes64_t dur, void* src)
347 max_item_duration = dur ;
348 MaxDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
352 * Returns the maxmimum duration that this item may be set to
354 * @return the maximum duration that this item may be set to
357 TimeAxisViewItem::get_max_duration() const
359 return (max_item_duration) ;
363 * Sets the minimu duration that this item may be set to
365 * @param the minimum duration that this item may be set to
366 * @param src the identity of the object that initiated the change
369 TimeAxisViewItem::set_min_duration(nframes64_t dur, void* src)
371 min_item_duration = dur ;
372 MinDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
376 * Returns the minimum duration that this item mey be set to
378 * @return the nimum duration that this item mey be set to
381 TimeAxisViewItem::get_min_duration() const
383 return(min_item_duration) ;
387 * Sets whether the position of this Item is locked to its current position
388 * Locked items cannot be moved until the item is unlocked again.
390 * @param yn set to true to lock this item to its current position
391 * @param src the identity of the object that initiated the change
394 TimeAxisViewItem::set_position_locked(bool yn, void* src)
396 position_locked = yn ;
397 set_trim_handle_colors() ;
398 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
402 * Returns whether this item is locked to its current position
404 * @return true if this item is locked to its current posotion
408 TimeAxisViewItem::get_position_locked() const
410 return (position_locked);
414 * Sets whether the Maximum Duration constraint is active and should be enforced
416 * @param active set true to enforce the max duration constraint
417 * @param src the identity of the object that initiated the change
420 TimeAxisViewItem::set_max_duration_active (bool active, void* /*src*/)
422 max_duration_active = active;
426 * Returns whether the Maximum Duration constraint is active and should be enforced
428 * @return true if the maximum duration constraint is active, false otherwise
431 TimeAxisViewItem::get_max_duration_active() const
433 return(max_duration_active) ;
437 * Sets whether the Minimum Duration constraint is active and should be enforced
439 * @param active set true to enforce the min duration constraint
440 * @param src the identity of the object that initiated the change
443 TimeAxisViewItem::set_min_duration_active (bool active, void* /*src*/)
445 min_duration_active = active ;
449 * Returns whether the Maximum Duration constraint is active and should be enforced
451 * @return true if the maximum duration constraint is active, false otherwise
454 TimeAxisViewItem::get_min_duration_active() const
456 return(min_duration_active) ;
459 //---------------------------------------------------------------------------------------//
460 // Name/Id Accessors/Mutators
463 * Set the name/Id of this item.
465 * @param new_name the new name of this item
466 * @param src the identity of the object that initiated the change
469 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
471 if (new_name != item_name) {
472 std::string temp_name = item_name ;
473 item_name = new_name ;
474 NameChanged (item_name, temp_name, src) ; /* EMIT_SIGNAL */
479 * Returns the name/id of this item
481 * @return the name/id of this item
484 TimeAxisViewItem::get_item_name() const
489 //---------------------------------------------------------------------------------------//
493 * Set to true to indicate that this item is currently selected
495 * @param yn true if this item is currently selected
496 * @param src the identity of the object that initiated the change
499 TimeAxisViewItem::set_selected(bool yn)
501 if (_selected != yn) {
502 Selectable::set_selected (yn);
508 TimeAxisViewItem::set_should_show_selection (bool yn)
510 if (should_show_selection != yn) {
511 should_show_selection = yn;
516 //---------------------------------------------------------------------------------------//
517 // Parent Componenet Methods
520 * Returns the TimeAxisView that this item is upon
522 * @return the timeAxisView that this item is placed upon
525 TimeAxisViewItem::get_time_axis_view()
529 //---------------------------------------------------------------------------------------//
533 * Sets the displayed item text
534 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item
536 * @param new_name the new name text to display
539 TimeAxisViewItem::set_name_text(const ustring& new_name)
545 last_item_width = trackview.editor().frame_to_pixel(item_duration);
546 name_pixbuf_width = pixel_width (new_name, *NAME_FONT) + 2;
547 name_pixbuf->property_pixbuf() = pixbuf_from_ustring(new_name, NAME_FONT, name_pixbuf_width, NAME_HEIGHT, Gdk::Color ("#000000"));
552 * Set the height of this item
554 * @param h the new height
557 TimeAxisViewItem::set_height (double height)
559 if (name_highlight) {
560 if (height < NAME_HIGHLIGHT_THRESH) {
561 name_highlight->hide ();
562 high_enough_for_name = false;
565 name_highlight->show();
566 high_enough_for_name = true;
569 if (height > NAME_HIGHLIGHT_SIZE) {
570 name_highlight->property_y1() = (double) height - 1 - NAME_HIGHLIGHT_SIZE;
571 name_highlight->property_y2() = (double) height - 2;
574 /* it gets hidden now anyway */
575 name_highlight->property_y1() = (double) 1.0;
576 name_highlight->property_y2() = (double) height;
580 if (visibility & ShowNameText) {
581 name_pixbuf->property_y() = height + 1 - NAME_Y_OFFSET;
585 frame->property_y2() = height - 1;
586 frame_handle_start->property_y2() = height - 1;
587 frame_handle_end->property_y2() = height - 1;
590 vestigial_frame->property_y2() = height - 1;
592 update_name_pixbuf_visibility ();
599 TimeAxisViewItem::set_color (Gdk::Color const & base_color)
601 compute_colors (base_color);
609 TimeAxisViewItem::get_canvas_frame()
618 TimeAxisViewItem::get_canvas_group()
627 TimeAxisViewItem::get_name_highlight()
629 return (name_highlight) ;
635 ArdourCanvas::Pixbuf*
636 TimeAxisViewItem::get_name_pixbuf()
638 return (name_pixbuf) ;
642 * Calculates some contrasting color for displaying various parts of this item, based upon the base color
644 * @param color the base color of the item
647 TimeAxisViewItem::compute_colors (Gdk::Color const & base_color)
649 unsigned char radius ;
652 unsigned char r,g,b ;
654 /* FILL: this is simple */
655 r = base_color.get_red()/256 ;
656 g = base_color.get_green()/256 ;
657 b = base_color.get_blue()/256 ;
658 fill_color = RGBA_TO_UINT(r,g,b,160) ;
661 if the overall saturation is strong, make the minor colors light.
662 if its weak, make them dark.
664 we do this by moving an equal distance to the other side of the
665 central circle in the color wheel from where we started.
668 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f)) ;
669 minor_shift = 125 - radius ;
671 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
673 r = base_color.get_red()/256;
674 g = base_color.get_green()/256;
675 b = base_color.get_blue()/256;
681 /* red sector => green */
686 /* green sector => blue */
694 /* blue sector => red */
699 /* green sector => blue */
708 label_color = RGBA_TO_UINT(r,g,b,255);
709 r = (base_color.get_red()/256) + 127 ;
710 g = (base_color.get_green()/256) + 127 ;
711 b = (base_color.get_blue()/256) + 127 ;
713 label_color = RGBA_TO_UINT(r,g,b,255);
715 /* XXX can we do better than this ? */
716 /* We're trying ;) */
719 //frame_color_r = 192;
720 //frame_color_g = 192;
721 //frame_color_b = 194;
723 //selected_frame_color_r = 182;
724 //selected_frame_color_g = 145;
725 //selected_frame_color_b = 168;
727 //handle_color_r = 25 ;
728 //handle_color_g = 0 ;
729 //handle_color_b = 255 ;
730 //lock_handle_color_r = 235 ;
731 //lock_handle_color_g = 16;
732 //lock_handle_color_b = 16;
736 * Convenience method to set the various canvas item colors
739 TimeAxisViewItem::set_colors()
743 if (name_highlight) {
744 name_highlight->property_fill_color_rgba() = fill_color;
745 name_highlight->property_outline_color_rgba() = fill_color;
747 set_trim_handle_colors() ;
751 * Sets the frame color depending on whether this item is selected
754 TimeAxisViewItem::set_frame_color()
759 if (_selected && should_show_selection) {
760 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get(), &r, &g, &b, &a);
761 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
764 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_RecordingRect.get(), &r, &g, &b, &a);
765 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
767 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_FrameBase.get(), &r, &g, &b, &a);
768 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity ? fill_opacity : a);
775 * Sets the colors of the start and end trim handle depending on object state
779 TimeAxisViewItem::set_trim_handle_colors()
781 if (frame_handle_start) {
782 if (position_locked) {
783 frame_handle_start->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
784 frame_handle_end->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
786 frame_handle_start->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
787 frame_handle_end->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
793 TimeAxisViewItem::get_samples_per_unit()
795 return(samples_per_unit) ;
799 TimeAxisViewItem::set_samples_per_unit (double spu)
801 samples_per_unit = spu ;
802 set_position (this->get_position(), this);
803 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
807 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
809 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
811 if (frame_handle_start) {
812 frame_handle_start->hide();
813 frame_handle_end->hide();
816 } if (pixel_width < 2.0) {
818 if (show_vestigial) {
819 vestigial_frame->show();
822 if (name_highlight) {
823 name_highlight->hide();
830 if (frame_handle_start) {
831 frame_handle_start->hide();
832 frame_handle_end->hide();
835 wide_enough_for_name = false;
838 vestigial_frame->hide();
840 if (name_highlight) {
842 double height = name_highlight->property_y2 ();
844 if (height < NAME_HIGHLIGHT_THRESH) {
845 name_highlight->hide();
846 high_enough_for_name = false;
848 name_highlight->show();
849 if (!get_item_name().empty()) {
850 reset_name_width (pixel_width);
852 high_enough_for_name = true;
855 if (visibility & FullWidthNameHighlight) {
856 name_highlight->property_x2() = pixel_width;
858 name_highlight->property_x2() = pixel_width - 1.0;
865 frame->property_x2() = pixel_width;
868 if (frame_handle_start) {
869 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
870 frame_handle_start->hide();
871 frame_handle_end->hide();
873 frame_handle_start->show();
874 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
875 frame_handle_end->show();
876 frame_handle_end->property_x2() = pixel_width;
882 TimeAxisViewItem::reset_name_width (double /*pixel_width*/)
886 bool pixbuf_holds_full_name;
892 it_width = trackview.editor().frame_to_pixel(item_duration);
893 pb_width = name_pixbuf_width;
895 pixbuf_holds_full_name = last_item_width > pb_width + NAME_X_OFFSET;
896 last_item_width = it_width;
898 if (pixbuf_holds_full_name && (it_width >= pb_width + NAME_X_OFFSET)) {
900 we've previously had the full name length showing
901 and its still showing.
906 if (pb_width > it_width - NAME_X_OFFSET) {
907 pb_width = it_width - NAME_X_OFFSET;
910 if (it_width <= NAME_X_OFFSET) {
911 wide_enough_for_name = false;
913 wide_enough_for_name = true;
916 update_name_pixbuf_visibility ();
918 name_pixbuf->property_pixbuf() = pixbuf_from_ustring(item_name, NAME_FONT, pb_width, NAME_HEIGHT, Gdk::Color ("#000000"));
923 //---------------------------------------------------------------------------------------//
924 // Handle time axis removal
927 * Handles the Removal of this time axis item
928 * This _needs_ to be called to alert others of the removal properly, ie where the source
929 * of the removal came from.
931 * XXX Although im not too happy about this method of doing things, I cant think of a cleaner method
932 * just now to capture the source of the removal
934 * @param src the identity of the object that initiated the change
937 TimeAxisViewItem::remove_this_item(void* src)
940 defer to idle loop, otherwise we'll delete this object
941 while we're still inside this function ...
943 Glib::signal_idle().connect(sigc::bind (sigc::ptr_fun (&TimeAxisViewItem::idle_remove_this_item), this, src));
947 * Callback used to remove this time axis item during the gtk idle loop
948 * This is used to avoid deleting the obejct while inside the remove_this_item
951 * @param item the TimeAxisViewItem to remove
952 * @param src the identity of the object that initiated the change
955 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
957 item->ItemRemoved (item->get_item_name(), src) ; /* EMIT_SIGNAL */
964 TimeAxisViewItem::set_y (double y)
966 double const old = group->property_y ();
968 group->move (0, y - old);
973 TimeAxisViewItem::update_name_pixbuf_visibility ()
979 if (wide_enough_for_name && high_enough_for_name) {
980 name_pixbuf->show ();
982 name_pixbuf->hide ();