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 double TimeAxisViewItem::NAME_Y_OFFSET;
60 double TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
61 double TimeAxisViewItem::NAME_HIGHLIGHT_THRESH;
63 //---------------------------------------------------------------------------------------//
64 // Constructor / Desctructor
67 * Constructs a new TimeAxisViewItem.
69 * @param it_name the unique name/Id of this item
70 * @param parant the parent canvas group
71 * @param tv the TimeAxisView we are going to be added to
72 * @param spu samples per unit
74 * @param start the start point of this item
75 * @param duration the duration of this item
77 TimeAxisViewItem::TimeAxisViewItem(const string & it_name, ArdourCanvas::Group& parent, TimeAxisView& tv, double spu, Gdk::Color const & base_color,
78 nframes64_t start, nframes64_t duration, bool recording,
80 : trackview (tv), _recregion(recording)
82 if (!have_name_font) {
84 /* first constructed item sets up font info */
86 NAME_FONT = get_font_for_style (N_("TimeAxisViewItemName"));
92 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (X_("Hg")); /* ascender + descender */
96 layout->set_font_description (*NAME_FONT);
97 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
99 NAME_Y_OFFSET = height + 3;
100 NAME_HIGHLIGHT_SIZE = height + 2;
101 NAME_HIGHLIGHT_THRESH = NAME_HIGHLIGHT_SIZE * 3;
103 have_name_font = true;
106 group = new ArdourCanvas::Group (parent);
108 init (it_name, spu, base_color, start, duration, vis);
112 TimeAxisViewItem::TimeAxisViewItem (const TimeAxisViewItem& other)
113 : sigc::trackable(other)
114 , trackview (other.trackview)
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 init (other.item_name, other.samples_per_unit, c, other.frame_position, other.item_duration, other.visibility);
133 TimeAxisViewItem::init (const string& it_name, double spu, Gdk::Color const & base_color, nframes64_t start, nframes64_t duration, Visibility vis)
135 item_name = it_name ;
136 samples_per_unit = spu ;
137 should_show_selection = true;
138 frame_position = start ;
139 item_duration = duration ;
140 name_connected = false;
142 position_locked = false ;
143 max_item_duration = ARDOUR::max_frames;
144 min_item_duration = 0 ;
145 show_vestigial = true;
150 warning << "Time Axis Item Duration == 0" << endl ;
153 vestigial_frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, 2.0, trackview.current_height());
154 vestigial_frame->hide ();
155 vestigial_frame->property_outline_what() = 0xF;
156 vestigial_frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
157 vestigial_frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
159 if (visibility & ShowFrame) {
160 frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, trackview.editor().frame_to_pixel(duration), trackview.current_height());
161 frame->property_outline_pixels() = 1;
162 frame->property_outline_what() = 0xF;
163 frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeAxisFrame.get();
165 /* by default draw all 4 edges */
167 uint32_t outline_what = 0x1|0x2|0x4|0x8;
169 if (visibility & HideFrameLeft) {
170 outline_what &= ~(0x1);
173 if (visibility & HideFrameRight) {
174 outline_what &= ~(0x2);
177 if (visibility & HideFrameTB) {
178 outline_what &= ~(0x4 | 0x8);
181 frame->property_outline_what() = outline_what;
187 if (visibility & ShowNameHighlight) {
188 if (visibility & FullWidthNameHighlight) {
189 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);
191 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);
193 name_highlight->set_data ("timeaxisviewitem", this);
199 if (visibility & ShowNameText) {
200 name_pixbuf = new ArdourCanvas::Pixbuf(*group);
201 name_pixbuf->property_x() = NAME_X_OFFSET;
202 name_pixbuf->property_y() = trackview.current_height() - 1.0 - NAME_Y_OFFSET;
208 /* create our grab handles used for trimming/duration etc */
209 frame_handle_start = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
210 frame_handle_start->property_outline_what() = 0x0;
212 frame_handle_end = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
213 frame_handle_end->property_outline_what() = 0x0;
215 set_color (base_color) ;
217 set_duration (item_duration, this) ;
218 set_position (start, this) ;
224 TimeAxisViewItem::~TimeAxisViewItem()
230 //---------------------------------------------------------------------------------------//
231 // Position and duration Accessors/Mutators
234 * Set the position of this item upon the timeline to the specified value
236 * @param pos the new position
237 * @param src the identity of the object that initiated the change
238 * @return true if the position change was a success, false otherwise
241 TimeAxisViewItem::set_position(nframes64_t pos, void* src, double* delta)
243 if (position_locked) {
247 frame_position = pos;
249 /* This sucks. The GnomeCanvas version I am using
250 doesn't correctly implement gnome_canvas_group_set_arg(),
251 so that simply setting the "x" arg of the group
252 fails to move the group. Instead, we have to
253 use gnome_canvas_item_move(), which does the right
254 thing. I see that in GNOME CVS, the current (Sept 2001)
255 version of GNOME Canvas rectifies this issue cleanly.
258 double old_unit_pos ;
259 double new_unit_pos = pos / samples_per_unit ;
261 old_unit_pos = group->property_x();
263 if (new_unit_pos != old_unit_pos) {
264 group->move (new_unit_pos - old_unit_pos, 0.0);
268 (*delta) = new_unit_pos - old_unit_pos;
271 PositionChanged (frame_position, src) ; /* EMIT_SIGNAL */
277 * Return the position of this item upon the timeline
279 * @return the position of this item
282 TimeAxisViewItem::get_position() const
284 return frame_position;
288 * Sets the duration of this item
290 * @param dur the new duration of this item
291 * @param src the identity of the object that initiated the change
292 * @return true if the duration change was succesful, false otherwise
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 */
316 * Returns the duration of this item
320 TimeAxisViewItem::get_duration() const
322 return (item_duration);
326 * Sets the maximum duration that this item make have.
328 * @param dur the new maximum duration
329 * @param src the identity of the object that initiated the change
332 TimeAxisViewItem::set_max_duration(nframes64_t dur, void* src)
334 max_item_duration = dur ;
335 MaxDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
339 * Returns the maxmimum duration that this item may be set to
341 * @return the maximum duration that this item may be set to
344 TimeAxisViewItem::get_max_duration() const
346 return (max_item_duration) ;
350 * Sets the minimu duration that this item may be set to
352 * @param the minimum duration that this item may be set to
353 * @param src the identity of the object that initiated the change
356 TimeAxisViewItem::set_min_duration(nframes64_t dur, void* src)
358 min_item_duration = dur ;
359 MinDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
363 * Returns the minimum duration that this item mey be set to
365 * @return the nimum duration that this item mey be set to
368 TimeAxisViewItem::get_min_duration() const
370 return(min_item_duration) ;
374 * Sets whether the position of this Item is locked to its current position
375 * Locked items cannot be moved until the item is unlocked again.
377 * @param yn set to true to lock this item to its current position
378 * @param src the identity of the object that initiated the change
381 TimeAxisViewItem::set_position_locked(bool yn, void* src)
383 position_locked = yn ;
384 set_trim_handle_colors() ;
385 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
389 * Returns whether this item is locked to its current position
391 * @return true if this item is locked to its current posotion
395 TimeAxisViewItem::get_position_locked() const
397 return (position_locked);
401 * Sets whether the Maximum Duration constraint is active and should be enforced
403 * @param active set true to enforce the max duration constraint
404 * @param src the identity of the object that initiated the change
407 TimeAxisViewItem::set_max_duration_active (bool active, void* /*src*/)
409 max_duration_active = active;
413 * Returns whether the Maximum Duration constraint is active and should be enforced
415 * @return true if the maximum duration constraint is active, false otherwise
418 TimeAxisViewItem::get_max_duration_active() const
420 return(max_duration_active) ;
424 * Sets whether the Minimum Duration constraint is active and should be enforced
426 * @param active set true to enforce the min duration constraint
427 * @param src the identity of the object that initiated the change
430 TimeAxisViewItem::set_min_duration_active (bool active, void* /*src*/)
432 min_duration_active = active ;
436 * Returns whether the Maximum Duration constraint is active and should be enforced
438 * @return true if the maximum duration constraint is active, false otherwise
441 TimeAxisViewItem::get_min_duration_active() const
443 return(min_duration_active) ;
446 //---------------------------------------------------------------------------------------//
447 // Name/Id Accessors/Mutators
450 * Set the name/Id of this item.
452 * @param new_name the new name of this item
453 * @param src the identity of the object that initiated the change
456 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
458 if (new_name != item_name) {
459 std::string temp_name = item_name ;
460 item_name = new_name ;
461 NameChanged (item_name, temp_name, src) ; /* EMIT_SIGNAL */
466 * Returns the name/id of this item
468 * @return the name/id of this item
471 TimeAxisViewItem::get_item_name() const
476 //---------------------------------------------------------------------------------------//
480 * Set to true to indicate that this item is currently selected
482 * @param yn true if this item is currently selected
483 * @param src the identity of the object that initiated the change
486 TimeAxisViewItem::set_selected(bool yn)
488 if (_selected != yn) {
489 Selectable::set_selected (yn);
495 TimeAxisViewItem::set_should_show_selection (bool yn)
497 if (should_show_selection != yn) {
498 should_show_selection = yn;
503 //---------------------------------------------------------------------------------------//
504 // Parent Componenet Methods
507 * Returns the TimeAxisView that this item is upon
509 * @return the timeAxisView that this item is placed upon
512 TimeAxisViewItem::get_time_axis_view()
516 //---------------------------------------------------------------------------------------//
520 * Sets the displayed item text
521 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item
523 * @param new_name the new name text to display
526 TimeAxisViewItem::set_name_text(const ustring& new_name)
528 uint32_t pb_width, it_width;
531 font_size = NAME_FONT->get_size() / Pango::SCALE;
532 it_width = trackview.editor().frame_to_pixel(item_duration);
533 pb_width = new_name.length() * font_size;
535 if (pb_width > it_width - NAME_X_OFFSET) {
536 pb_width = it_width - NAME_X_OFFSET;
539 if (pb_width <= 0 || it_width < NAME_X_OFFSET) {
548 Glib::RefPtr<Gdk::Pixbuf> buf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, pb_width, NAME_HIGHLIGHT_SIZE);
550 cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, pb_width, NAME_HIGHLIGHT_SIZE );
551 cairo_t *cr = cairo_create (surface);
552 cairo_text_extents_t te;
553 cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1.0);
554 cairo_select_font_face (cr, NAME_FONT->get_family().c_str(),
555 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
556 cairo_set_font_size (cr, 10);
557 cairo_text_extents (cr, new_name.c_str(), &te);
559 cairo_move_to (cr, 0.5,
560 0.5 - te.height / 2 - te.y_bearing + NAME_HIGHLIGHT_SIZE / 2);
561 cairo_show_text (cr, new_name.c_str());
563 unsigned char* src = cairo_image_surface_get_data (surface);
564 convert_bgra_to_rgba(src, buf->get_pixels(), pb_width, NAME_HIGHLIGHT_SIZE);
567 name_pixbuf->property_pixbuf() = buf;
571 * Set the height of this item
573 * @param h the new height
576 TimeAxisViewItem::set_height (double height)
578 if (name_highlight) {
579 if (height < NAME_HIGHLIGHT_THRESH) {
580 name_highlight->hide();
584 name_highlight->show();
589 if (height > NAME_HIGHLIGHT_SIZE) {
590 name_highlight->property_y1() = (double) height - 1 - NAME_HIGHLIGHT_SIZE;
591 name_highlight->property_y2() = (double) height - 2;
594 /* it gets hidden now anyway */
595 name_highlight->property_y1() = (double) 1.0;
596 name_highlight->property_y2() = (double) height;
600 if (visibility & ShowNameText) {
601 name_pixbuf->property_y() = height - 1 - NAME_Y_OFFSET;
605 frame->property_y2() = height - 1;
606 frame_handle_start->property_y2() = height - 1;
607 frame_handle_end->property_y2() = height - 1;
610 vestigial_frame->property_y2() = height - 1;
617 TimeAxisViewItem::set_color (Gdk::Color const & base_color)
619 compute_colors (base_color);
627 TimeAxisViewItem::get_canvas_frame()
636 TimeAxisViewItem::get_canvas_group()
645 TimeAxisViewItem::get_name_highlight()
647 return (name_highlight) ;
653 ArdourCanvas::Pixbuf*
654 TimeAxisViewItem::get_name_pixbuf()
656 return (name_pixbuf) ;
660 * Calculates some contrasting color for displaying various parts of this item, based upon the base color
662 * @param color the base color of the item
665 TimeAxisViewItem::compute_colors (Gdk::Color const & base_color)
667 unsigned char radius ;
670 unsigned char r,g,b ;
672 /* FILL: this is simple */
673 r = base_color.get_red()/256 ;
674 g = base_color.get_green()/256 ;
675 b = base_color.get_blue()/256 ;
676 fill_color = RGBA_TO_UINT(r,g,b,160) ;
679 if the overall saturation is strong, make the minor colors light.
680 if its weak, make them dark.
682 we do this by moving an equal distance to the other side of the
683 central circle in the color wheel from where we started.
686 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f)) ;
687 minor_shift = 125 - radius ;
689 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
691 r = base_color.get_red()/256;
692 g = base_color.get_green()/256;
693 b = base_color.get_blue()/256;
699 /* red sector => green */
704 /* green sector => blue */
712 /* blue sector => red */
717 /* green sector => blue */
726 label_color = RGBA_TO_UINT(r,g,b,255);
727 r = (base_color.get_red()/256) + 127 ;
728 g = (base_color.get_green()/256) + 127 ;
729 b = (base_color.get_blue()/256) + 127 ;
731 label_color = RGBA_TO_UINT(r,g,b,255);
733 /* XXX can we do better than this ? */
734 /* We're trying ;) */
737 //frame_color_r = 192;
738 //frame_color_g = 192;
739 //frame_color_b = 194;
741 //selected_frame_color_r = 182;
742 //selected_frame_color_g = 145;
743 //selected_frame_color_b = 168;
745 //handle_color_r = 25 ;
746 //handle_color_g = 0 ;
747 //handle_color_b = 255 ;
748 //lock_handle_color_r = 235 ;
749 //lock_handle_color_g = 16;
750 //lock_handle_color_b = 16;
754 * Convenience method to set the various canvas item colors
757 TimeAxisViewItem::set_colors()
761 if (name_highlight) {
762 name_highlight->property_fill_color_rgba() = fill_color;
763 name_highlight->property_outline_color_rgba() = fill_color;
765 set_trim_handle_colors() ;
769 * Sets the frame color depending on whether this item is selected
772 TimeAxisViewItem::set_frame_color()
777 if (_selected && should_show_selection) {
778 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get(), &r, &g, &b, &a);
779 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
782 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_RecordingRect.get(), &r, &g, &b, &a);
783 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
785 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_FrameBase.get(), &r, &g, &b, &a);
786 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity ? fill_opacity : a);
793 * Sets the colors of the start and end trim handle depending on object state
797 TimeAxisViewItem::set_trim_handle_colors()
799 if (frame_handle_start) {
800 if (position_locked) {
801 frame_handle_start->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
802 frame_handle_end->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
804 frame_handle_start->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
805 frame_handle_end->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
811 TimeAxisViewItem::get_samples_per_unit()
813 return(samples_per_unit) ;
817 TimeAxisViewItem::set_samples_per_unit (double spu)
819 samples_per_unit = spu ;
820 set_position (this->get_position(), this);
821 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
825 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
827 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
829 if (frame_handle_start) {
830 frame_handle_start->hide();
831 frame_handle_end->hide();
834 } if (pixel_width < 2.0) {
836 if (show_vestigial) {
837 vestigial_frame->show();
840 if (name_highlight) {
841 name_highlight->hide();
849 if (frame_handle_start) {
850 frame_handle_start->hide();
851 frame_handle_end->hide();
855 vestigial_frame->hide();
857 if (name_highlight) {
859 double height = name_highlight->property_y2 ();
861 if (height < NAME_HIGHLIGHT_THRESH) {
862 name_highlight->hide();
865 name_highlight->show();
866 if (!get_item_name().empty()) {
867 reset_name_width (pixel_width);
871 if (visibility & FullWidthNameHighlight) {
872 name_highlight->property_x2() = pixel_width;
874 name_highlight->property_x2() = pixel_width - 1.0;
881 frame->property_x2() = pixel_width;
884 if (frame_handle_start) {
885 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
886 frame_handle_start->hide();
887 frame_handle_end->hide();
889 frame_handle_start->show();
890 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
891 frame_handle_end->show();
892 frame_handle_end->property_x2() = pixel_width;
898 TimeAxisViewItem::reset_name_width (double /*pixel_width*/)
900 set_name_text (item_name);
904 //---------------------------------------------------------------------------------------//
905 // Handle time axis removal
908 * Handles the Removal of this time axis item
909 * This _needs_ to be called to alert others of the removal properly, ie where the source
910 * of the removal came from.
912 * XXX Although im not too happy about this method of doing things, I cant think of a cleaner method
913 * just now to capture the source of the removal
915 * @param src the identity of the object that initiated the change
918 TimeAxisViewItem::remove_this_item(void* src)
921 defer to idle loop, otherwise we'll delete this object
922 while we're still inside this function ...
924 Glib::signal_idle().connect(bind (sigc::ptr_fun (&TimeAxisViewItem::idle_remove_this_item), this, src));
928 * Callback used to remove this time axis item during the gtk idle loop
929 * This is used to avoid deleting the obejct while inside the remove_this_item
932 * @param item the TimeAxisViewItem to remove
933 * @param src the identity of the object that initiated the change
936 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
938 item->ItemRemoved (item->get_item_name(), src) ; /* EMIT_SIGNAL */
945 TimeAxisViewItem::set_y (double y)
947 double const old = group->property_y ();
949 group->move (0, y - old);