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 "public_editor.h"
29 #include "time_axis_view_item.h"
30 #include "time_axis_view.h"
31 #include "simplerect.h"
33 #include "canvas_impl.h"
34 #include "rgb_macros.h"
35 #include "ardour_ui.h"
40 using namespace Editing;
43 using namespace ARDOUR;
45 //------------------------------------------------------------------------------
46 /** Initialize const static memeber data */
48 Pango::FontDescription* TimeAxisViewItem::NAME_FONT = 0;
49 bool TimeAxisViewItem::have_name_font = false;
50 const double TimeAxisViewItem::NAME_X_OFFSET = 15.0;
51 const double TimeAxisViewItem::GRAB_HANDLE_LENGTH = 6 ;
53 double TimeAxisViewItem::NAME_Y_OFFSET;
54 double TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
55 double TimeAxisViewItem::NAME_HIGHLIGHT_THRESH;
58 convert_color_channel (guint8 src,
61 return alpha ? ((guint (src) << 8) - src) / alpha : 0;
65 convert_bgra_to_rgba (guint8 const* src,
70 guint8 const* src_pixel = src;
71 guint8* dst_pixel = dst;
73 for (int y = 0; y < height; y++)
74 for (int x = 0; x < width; x++)
76 dst_pixel[0] = convert_color_channel (src_pixel[2],
78 dst_pixel[1] = convert_color_channel (src_pixel[1],
80 dst_pixel[2] = convert_color_channel (src_pixel[0],
82 dst_pixel[3] = src_pixel[3];
89 //---------------------------------------------------------------------------------------//
90 // Constructor / Desctructor
93 * Constructs a new TimeAxisViewItem.
95 * @param it_name the unique name/Id of this item
96 * @param parant the parent canvas group
97 * @param tv the TimeAxisView we are going to be added to
98 * @param spu samples per unit
100 * @param start the start point of this item
101 * @param duration the duration of this item
103 TimeAxisViewItem::TimeAxisViewItem(const string & it_name, ArdourCanvas::Group& parent, TimeAxisView& tv, double spu, Gdk::Color& base_color,
104 nframes_t start, nframes_t duration, bool recording,
106 : trackview (tv), _recregion(recording)
108 if (!have_name_font) {
110 /* first constructed item sets up font info */
112 NAME_FONT = get_font_for_style (N_("TimeAxisViewItemName"));
118 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (X_("Hg")); /* ascender + descender */
122 layout->set_font_description (*NAME_FONT);
123 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
125 NAME_Y_OFFSET = height + 5;
126 NAME_HIGHLIGHT_SIZE = height + 6;
127 NAME_HIGHLIGHT_THRESH = NAME_HIGHLIGHT_SIZE * 2;
129 have_name_font = true;
132 group = new ArdourCanvas::Group (parent);
134 init (it_name, spu, base_color, start, duration, vis);
138 TimeAxisViewItem::TimeAxisViewItem (const TimeAxisViewItem& other)
139 : sigc::trackable(other)
140 , trackview (other.trackview)
146 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
147 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
149 /* share the other's parent, but still create a new group */
151 Gnome::Canvas::Group* parent = other.group->property_parent();
153 group = new ArdourCanvas::Group (*parent);
155 init (other.item_name, other.samples_per_unit, c, other.frame_position, other.item_duration, other.visibility);
159 TimeAxisViewItem::init (const string& it_name, double spu, Gdk::Color& base_color, nframes_t start, nframes_t duration, Visibility vis)
161 item_name = it_name ;
162 samples_per_unit = spu ;
163 should_show_selection = true;
164 frame_position = start ;
165 item_duration = duration ;
166 name_connected = false;
168 position_locked = false ;
169 max_item_duration = ARDOUR::max_frames;
170 min_item_duration = 0 ;
171 show_vestigial = true;
176 warning << "Time Axis Item Duration == 0" << endl ;
179 vestigial_frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, 2.0, trackview.current_height());
180 vestigial_frame->hide ();
181 vestigial_frame->property_outline_what() = 0xF;
182 vestigial_frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
183 vestigial_frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
185 if (visibility & ShowFrame) {
186 frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, trackview.editor().frame_to_pixel(duration), trackview.current_height());
187 frame->property_outline_pixels() = 1;
188 frame->property_outline_what() = 0xF;
189 frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeAxisFrame.get();
191 /* by default draw all 4 edges */
193 uint32_t outline_what = 0x1|0x2|0x4|0x8;
195 if (visibility & HideFrameLeft) {
196 outline_what &= ~(0x1);
199 if (visibility & HideFrameRight) {
200 outline_what &= ~(0x2);
203 if (visibility & HideFrameTB) {
204 outline_what &= ~(0x4 | 0x8);
207 frame->property_outline_what() = outline_what;
213 if (visibility & ShowNameHighlight) {
214 if (visibility & FullWidthNameHighlight) {
215 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);
217 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);
219 name_highlight->set_data ("timeaxisviewitem", this);
225 if (visibility & ShowNameText) {
226 name_pixbuf = new ArdourCanvas::Pixbuf(*group);
227 name_pixbuf->property_x() = NAME_X_OFFSET;
228 name_pixbuf->property_y() = trackview.current_height() - 1.0 - NAME_Y_OFFSET;
232 /* create our grab handles used for trimming/duration etc */
234 if (visibility & ShowHandles) {
236 frame_handle_start = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 1.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH+1);
237 frame_handle_start->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_FrameHandle.get();
239 frame_handle_end = new ArdourCanvas::SimpleRect (*group, trackview.editor().frame_to_pixel(get_duration()) - TimeAxisViewItem::GRAB_HANDLE_LENGTH, trackview.editor().frame_to_pixel(get_duration()), 1.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH + 1);
240 frame_handle_end->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_FrameHandle.get();
243 frame_handle_start = 0;
244 frame_handle_end = 0;
247 set_color (base_color) ;
249 set_duration (item_duration, this) ;
250 set_position (start, this) ;
256 TimeAxisViewItem::~TimeAxisViewItem()
262 //---------------------------------------------------------------------------------------//
263 // Position and duration Accessors/Mutators
266 * Set the position of this item upon the timeline to the specified value
268 * @param pos the new position
269 * @param src the identity of the object that initiated the change
270 * @return true if the position change was a success, false otherwise
273 TimeAxisViewItem::set_position(nframes_t pos, void* src, double* delta)
275 if (position_locked) {
279 frame_position = pos;
281 /* This sucks. The GnomeCanvas version I am using
282 doesn't correctly implement gnome_canvas_group_set_arg(),
283 so that simply setting the "x" arg of the group
284 fails to move the group. Instead, we have to
285 use gnome_canvas_item_move(), which does the right
286 thing. I see that in GNOME CVS, the current (Sept 2001)
287 version of GNOME Canvas rectifies this issue cleanly.
290 double old_unit_pos ;
291 double new_unit_pos = pos / samples_per_unit ;
293 old_unit_pos = group->property_x();
295 if (new_unit_pos != old_unit_pos) {
296 group->move (new_unit_pos - old_unit_pos, 0.0);
300 (*delta) = new_unit_pos - old_unit_pos;
303 PositionChanged (frame_position, src) ; /* EMIT_SIGNAL */
309 * Return the position of this item upon the timeline
311 * @return the position of this item
314 TimeAxisViewItem::get_position() const
316 return frame_position;
320 * Sets the duration of this item
322 * @param dur the new duration of this item
323 * @param src the identity of the object that initiated the change
324 * @return true if the duration change was succesful, false otherwise
327 TimeAxisViewItem::set_duration (nframes_t dur, void* src)
329 if ((dur > max_item_duration) || (dur < min_item_duration)) {
330 warning << string_compose (_("new duration %1 frames is out of bounds for %2"), get_item_name(), dur)
341 reset_width_dependent_items (trackview.editor().frame_to_pixel (dur));
343 DurationChanged (dur, src) ; /* EMIT_SIGNAL */
348 * Returns the duration of this item
352 TimeAxisViewItem::get_duration() const
354 return (item_duration);
358 * Sets the maximum duration that this item make have.
360 * @param dur the new maximum duration
361 * @param src the identity of the object that initiated the change
364 TimeAxisViewItem::set_max_duration(nframes_t dur, void* src)
366 max_item_duration = dur ;
367 MaxDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
371 * Returns the maxmimum duration that this item may be set to
373 * @return the maximum duration that this item may be set to
376 TimeAxisViewItem::get_max_duration() const
378 return (max_item_duration) ;
382 * Sets the minimu duration that this item may be set to
384 * @param the minimum duration that this item may be set to
385 * @param src the identity of the object that initiated the change
388 TimeAxisViewItem::set_min_duration(nframes_t dur, void* src)
390 min_item_duration = dur ;
391 MinDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
395 * Returns the minimum duration that this item mey be set to
397 * @return the nimum duration that this item mey be set to
400 TimeAxisViewItem::get_min_duration() const
402 return(min_item_duration) ;
406 * Sets whether the position of this Item is locked to its current position
407 * Locked items cannot be moved until the item is unlocked again.
409 * @param yn set to true to lock this item to its current position
410 * @param src the identity of the object that initiated the change
413 TimeAxisViewItem::set_position_locked(bool yn, void* src)
415 position_locked = yn ;
416 set_trim_handle_colors() ;
417 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
421 * Returns whether this item is locked to its current position
423 * @return true if this item is locked to its current posotion
427 TimeAxisViewItem::get_position_locked() const
429 return (position_locked);
433 * Sets whether the Maximum Duration constraint is active and should be enforced
435 * @param active set true to enforce the max duration constraint
436 * @param src the identity of the object that initiated the change
439 TimeAxisViewItem::set_max_duration_active(bool active, void* src)
441 max_duration_active = active ;
445 * Returns whether the Maximum Duration constraint is active and should be enforced
447 * @return true if the maximum duration constraint is active, false otherwise
450 TimeAxisViewItem::get_max_duration_active() const
452 return(max_duration_active) ;
456 * Sets whether the Minimum Duration constraint is active and should be enforced
458 * @param active set true to enforce the min duration constraint
459 * @param src the identity of the object that initiated the change
462 TimeAxisViewItem::set_min_duration_active(bool active, void* src)
464 min_duration_active = active ;
468 * Returns whether the Maximum Duration constraint is active and should be enforced
470 * @return true if the maximum duration constraint is active, false otherwise
473 TimeAxisViewItem::get_min_duration_active() const
475 return(min_duration_active) ;
478 //---------------------------------------------------------------------------------------//
479 // Name/Id Accessors/Mutators
482 * Set the name/Id of this item.
484 * @param new_name the new name of this item
485 * @param src the identity of the object that initiated the change
488 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
490 if (new_name != item_name) {
491 std::string temp_name = item_name ;
492 item_name = new_name ;
493 NameChanged (item_name, temp_name, src) ; /* EMIT_SIGNAL */
498 * Returns the name/id of this item
500 * @return the name/id of this item
503 TimeAxisViewItem::get_item_name() const
508 //---------------------------------------------------------------------------------------//
512 * Set to true to indicate that this item is currently selected
514 * @param yn true if this item is currently selected
515 * @param src the identity of the object that initiated the change
518 TimeAxisViewItem::set_selected(bool yn)
520 if (_selected != yn) {
521 Selectable::set_selected (yn);
527 TimeAxisViewItem::set_should_show_selection (bool yn)
529 if (should_show_selection != yn) {
530 should_show_selection = yn;
535 //---------------------------------------------------------------------------------------//
536 // Parent Componenet Methods
539 * Returns the TimeAxisView that this item is upon
541 * @return the timeAxisView that this item is placed upon
544 TimeAxisViewItem::get_time_axis_view()
548 //---------------------------------------------------------------------------------------//
552 * Sets the displayed item text
553 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item
555 * @param new_name the new name text to display
558 TimeAxisViewItem::set_name_text(const ustring& new_name)
560 uint32_t pb_width, it_width;
563 font_size = NAME_FONT->get_size() / Pango::SCALE;
564 it_width = trackview.editor().frame_to_pixel(item_duration);
565 pb_width = new_name.length() * font_size;
567 if (pb_width > it_width - NAME_X_OFFSET) {
568 pb_width = it_width - NAME_X_OFFSET;
571 if (pb_width <= 0 || it_width < NAME_X_OFFSET) {
578 Glib::RefPtr<Gdk::Pixbuf> buf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, pb_width, NAME_HIGHLIGHT_SIZE);
580 cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, pb_width, NAME_HIGHLIGHT_SIZE );
581 cairo_t *cr = cairo_create (surface);
582 cairo_text_extents_t te;
583 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
584 cairo_select_font_face (cr, NAME_FONT->get_family().c_str(),
585 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
586 cairo_set_font_size (cr, 10);
587 cairo_text_extents (cr, new_name.c_str(), &te);
589 cairo_move_to (cr, 0.5,
590 0.5 - te.height / 2 - te.y_bearing + NAME_HIGHLIGHT_SIZE / 2);
591 cairo_show_text (cr, new_name.c_str());
593 unsigned char* src = cairo_image_surface_get_data (surface);
594 convert_bgra_to_rgba(src, buf->get_pixels(), pb_width, NAME_HIGHLIGHT_SIZE);
597 name_pixbuf->property_pixbuf() = buf;
601 * Set the height of this item
603 * @param h the new height
606 TimeAxisViewItem::set_height (double height)
608 if (name_highlight) {
609 if (height < NAME_HIGHLIGHT_THRESH) {
610 name_highlight->hide();
614 name_highlight->show();
619 if (height > NAME_HIGHLIGHT_SIZE) {
620 name_highlight->property_y1() = (double) height+1 - NAME_HIGHLIGHT_SIZE;
621 name_highlight->property_y2() = (double) height;
624 /* it gets hidden now anyway */
625 name_highlight->property_y1() = (double) 1.0;
626 name_highlight->property_y2() = (double) height;
630 if (visibility & ShowNameText) {
631 name_pixbuf->property_y() = height+1 - NAME_Y_OFFSET;
635 frame->property_y2() = height+1;
638 vestigial_frame->property_y2() = height+1;
645 TimeAxisViewItem::set_color(Gdk::Color& base_color)
647 compute_colors (base_color);
655 TimeAxisViewItem::get_canvas_frame()
664 TimeAxisViewItem::get_canvas_group()
673 TimeAxisViewItem::get_name_highlight()
675 return (name_highlight) ;
681 ArdourCanvas::Pixbuf*
682 TimeAxisViewItem::get_name_pixbuf()
684 return (name_pixbuf) ;
688 * Calculates some contrasting color for displaying various parts of this item, based upon the base color
690 * @param color the base color of the item
693 TimeAxisViewItem::compute_colors(Gdk::Color& base_color)
695 unsigned char radius ;
698 unsigned char r,g,b ;
700 /* FILL: this is simple */
701 r = base_color.get_red()/256 ;
702 g = base_color.get_green()/256 ;
703 b = base_color.get_blue()/256 ;
704 fill_color = RGBA_TO_UINT(r,g,b,160) ;
707 if the overall saturation is strong, make the minor colors light.
708 if its weak, make them dark.
710 we do this by moving an equal distance to the other side of the
711 central circle in the color wheel from where we started.
714 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f)) ;
715 minor_shift = 125 - radius ;
717 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
719 r = base_color.get_red()/256;
720 g = base_color.get_green()/256;
721 b = base_color.get_blue()/256;
727 /* red sector => green */
732 /* green sector => blue */
740 /* blue sector => red */
745 /* green sector => blue */
754 label_color = RGBA_TO_UINT(r,g,b,255);
755 r = (base_color.get_red()/256) + 127 ;
756 g = (base_color.get_green()/256) + 127 ;
757 b = (base_color.get_blue()/256) + 127 ;
759 label_color = RGBA_TO_UINT(r,g,b,255);
761 /* XXX can we do better than this ? */
762 /* We're trying ;) */
765 //frame_color_r = 192;
766 //frame_color_g = 192;
767 //frame_color_b = 194;
769 //selected_frame_color_r = 182;
770 //selected_frame_color_g = 145;
771 //selected_frame_color_b = 168;
773 //handle_color_r = 25 ;
774 //handle_color_g = 0 ;
775 //handle_color_b = 255 ;
776 //lock_handle_color_r = 235 ;
777 //lock_handle_color_g = 16;
778 //lock_handle_color_b = 16;
782 * Convenience method to set the various canvas item colors
785 TimeAxisViewItem::set_colors()
789 if (name_highlight) {
790 name_highlight->property_fill_color_rgba() = fill_color;
791 name_highlight->property_outline_color_rgba() = fill_color;
793 set_trim_handle_colors() ;
797 * Sets the frame color depending on whether this item is selected
800 TimeAxisViewItem::set_frame_color()
805 if (_selected && should_show_selection) {
806 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get(), &r, &g, &b, &a);
807 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
810 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_RecordingRect.get(), &r, &g, &b, &a);
811 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
813 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_FrameBase.get(), &r, &g, &b, &a);
814 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity ? fill_opacity : a);
821 * Sets the colors of the start and end trim handle depending on object state
825 TimeAxisViewItem::set_trim_handle_colors()
827 if (frame_handle_start) {
828 if (position_locked) {
829 frame_handle_start->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
830 frame_handle_end->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
832 frame_handle_start->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandle.get();
833 frame_handle_end->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandle.get();
839 TimeAxisViewItem::get_samples_per_unit()
841 return(samples_per_unit) ;
845 TimeAxisViewItem::set_samples_per_unit (double spu)
847 samples_per_unit = spu ;
848 set_position (this->get_position(), this);
849 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
853 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
855 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
857 if (frame_handle_start) {
858 frame_handle_start->hide();
859 frame_handle_end->hide();
862 } if (pixel_width < 2.0) {
864 if (show_vestigial) {
865 vestigial_frame->show();
868 if (name_highlight) {
869 name_highlight->hide();
877 if (frame_handle_start) {
878 frame_handle_start->hide();
879 frame_handle_end->hide();
883 vestigial_frame->hide();
885 if (name_highlight) {
887 double height = name_highlight->property_y2 ();
889 if (height < NAME_HIGHLIGHT_THRESH) {
890 name_highlight->hide();
893 name_highlight->show();
894 if (!get_item_name().empty()) {
895 reset_name_width (pixel_width);
899 if (visibility & FullWidthNameHighlight) {
900 name_highlight->property_x2() = pixel_width;
902 name_highlight->property_x2() = pixel_width - 1.0;
909 frame->property_x2() = pixel_width;
912 if (frame_handle_start) {
913 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
914 frame_handle_start->hide();
915 frame_handle_end->hide();
917 frame_handle_start->show();
918 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
919 frame_handle_end->show();
920 frame_handle_end->property_x2() = pixel_width;
926 TimeAxisViewItem::reset_name_width (double pixel_width)
928 set_name_text (item_name);
932 //---------------------------------------------------------------------------------------//
933 // Handle time axis removal
936 * Handles the Removal of this time axis item
937 * This _needs_ to be called to alert others of the removal properly, ie where the source
938 * of the removal came from.
940 * XXX Although im not too happy about this method of doing things, I cant think of a cleaner method
941 * just now to capture the source of the removal
943 * @param src the identity of the object that initiated the change
946 TimeAxisViewItem::remove_this_item(void* src)
949 defer to idle loop, otherwise we'll delete this object
950 while we're still inside this function ...
952 Glib::signal_idle().connect(bind (sigc::ptr_fun (&TimeAxisViewItem::idle_remove_this_item), this, src));
956 * Callback used to remove this time axis item during the gtk idle loop
957 * This is used to avoid deleting the obejct while inside the remove_this_item
960 * @param item the TimeAxisViewItem to remove
961 * @param src the identity of the object that initiated the change
964 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
966 item->ItemRemoved (item->get_item_name(), src) ; /* EMIT_SIGNAL */
973 TimeAxisViewItem::set_y (double y)
975 double const old = group->property_y ();
977 group->move (0, y - old);