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;
177 warning << "Time Axis Item Duration == 0" << endl ;
180 vestigial_frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, 2.0, trackview.current_height());
181 vestigial_frame->hide ();
182 vestigial_frame->property_outline_what() = 0xF;
183 vestigial_frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
184 vestigial_frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
186 if (visibility & ShowFrame) {
187 frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, trackview.editor().frame_to_pixel(duration), trackview.current_height());
188 frame->property_outline_pixels() = 1;
189 frame->property_outline_what() = 0xF;
190 frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeAxisFrame.get();
192 /* by default draw all 4 edges */
194 uint32_t outline_what = 0x1|0x2|0x4|0x8;
196 if (visibility & HideFrameLeft) {
197 outline_what &= ~(0x1);
200 if (visibility & HideFrameRight) {
201 outline_what &= ~(0x2);
204 if (visibility & HideFrameTB) {
205 outline_what &= ~(0x4 | 0x8);
208 frame->property_outline_what() = outline_what;
214 if (visibility & ShowNameHighlight) {
215 if (visibility & FullWidthNameHighlight) {
216 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);
218 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);
220 name_highlight->set_data ("timeaxisviewitem", this);
226 if (visibility & ShowNameText) {
227 name_pixbuf = new ArdourCanvas::Pixbuf(*group);
228 name_pixbuf->property_x() = NAME_X_OFFSET;
229 name_pixbuf->property_y() = trackview.current_height() - 1.0 - NAME_Y_OFFSET;
233 /* create our grab handles used for trimming/duration etc */
235 if (visibility & ShowHandles) {
237 frame_handle_start = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 1.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH+1);
238 frame_handle_start->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_FrameHandle.get();
240 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);
241 frame_handle_end->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_FrameHandle.get();
244 frame_handle_start = 0;
245 frame_handle_end = 0;
248 set_color (base_color) ;
250 set_duration (item_duration, this) ;
251 set_position (start, this) ;
257 TimeAxisViewItem::~TimeAxisViewItem()
263 //---------------------------------------------------------------------------------------//
264 // Position and duration Accessors/Mutators
267 * Set the position of this item upon the timeline to the specified value
269 * @param pos the new position
270 * @param src the identity of the object that initiated the change
271 * @return true if the position change was a success, false otherwise
274 TimeAxisViewItem::set_position(nframes_t pos, void* src, double* delta)
276 if (position_locked) {
280 frame_position = pos;
282 /* This sucks. The GnomeCanvas version I am using
283 doesn't correctly implement gnome_canvas_group_set_arg(),
284 so that simply setting the "x" arg of the group
285 fails to move the group. Instead, we have to
286 use gnome_canvas_item_move(), which does the right
287 thing. I see that in GNOME CVS, the current (Sept 2001)
288 version of GNOME Canvas rectifies this issue cleanly.
291 double old_unit_pos ;
292 double new_unit_pos = pos / samples_per_unit ;
294 old_unit_pos = group->property_x();
296 if (new_unit_pos != old_unit_pos) {
297 group->move (new_unit_pos - old_unit_pos, 0.0);
301 (*delta) = new_unit_pos - old_unit_pos;
304 PositionChanged (frame_position, src) ; /* EMIT_SIGNAL */
310 * Return the position of this item upon the timeline
312 * @return the position of this item
315 TimeAxisViewItem::get_position() const
317 return frame_position;
321 * Sets the duration of this item
323 * @param dur the new duration of this item
324 * @param src the identity of the object that initiated the change
325 * @return true if the duration change was succesful, false otherwise
328 TimeAxisViewItem::set_duration (nframes_t dur, void* src)
330 if ((dur > max_item_duration) || (dur < min_item_duration)) {
331 warning << string_compose (_("new duration %1 frames is out of bounds for %2"), get_item_name(), dur)
342 reset_width_dependent_items (trackview.editor().frame_to_pixel (dur));
344 DurationChanged (dur, src) ; /* EMIT_SIGNAL */
349 * Returns the duration of this item
353 TimeAxisViewItem::get_duration() const
355 return (item_duration);
359 * Sets the maximum duration that this item make have.
361 * @param dur the new maximum duration
362 * @param src the identity of the object that initiated the change
365 TimeAxisViewItem::set_max_duration(nframes_t dur, void* src)
367 max_item_duration = dur ;
368 MaxDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
372 * Returns the maxmimum duration that this item may be set to
374 * @return the maximum duration that this item may be set to
377 TimeAxisViewItem::get_max_duration() const
379 return (max_item_duration) ;
383 * Sets the minimu duration that this item may be set to
385 * @param the minimum duration that this item may be set to
386 * @param src the identity of the object that initiated the change
389 TimeAxisViewItem::set_min_duration(nframes_t dur, void* src)
391 min_item_duration = dur ;
392 MinDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
396 * Returns the minimum duration that this item mey be set to
398 * @return the nimum duration that this item mey be set to
401 TimeAxisViewItem::get_min_duration() const
403 return(min_item_duration) ;
407 * Sets whether the position of this Item is locked to its current position
408 * Locked items cannot be moved until the item is unlocked again.
410 * @param yn set to true to lock this item to its current position
411 * @param src the identity of the object that initiated the change
414 TimeAxisViewItem::set_position_locked(bool yn, void* src)
416 position_locked = yn ;
417 set_trim_handle_colors() ;
418 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
422 * Returns whether this item is locked to its current position
424 * @return true if this item is locked to its current posotion
428 TimeAxisViewItem::get_position_locked() const
430 return (position_locked);
434 * Sets whether the Maximum Duration constraint is active and should be enforced
436 * @param active set true to enforce the max duration constraint
437 * @param src the identity of the object that initiated the change
440 TimeAxisViewItem::set_max_duration_active(bool active, void* src)
442 max_duration_active = active ;
446 * Returns whether the Maximum Duration constraint is active and should be enforced
448 * @return true if the maximum duration constraint is active, false otherwise
451 TimeAxisViewItem::get_max_duration_active() const
453 return(max_duration_active) ;
457 * Sets whether the Minimum Duration constraint is active and should be enforced
459 * @param active set true to enforce the min duration constraint
460 * @param src the identity of the object that initiated the change
463 TimeAxisViewItem::set_min_duration_active(bool active, void* src)
465 min_duration_active = active ;
469 * Returns whether the Maximum Duration constraint is active and should be enforced
471 * @return true if the maximum duration constraint is active, false otherwise
474 TimeAxisViewItem::get_min_duration_active() const
476 return(min_duration_active) ;
479 //---------------------------------------------------------------------------------------//
480 // Name/Id Accessors/Mutators
483 * Set the name/Id of this item.
485 * @param new_name the new name of this item
486 * @param src the identity of the object that initiated the change
489 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
491 if (new_name != item_name) {
492 std::string temp_name = item_name ;
493 item_name = new_name ;
494 NameChanged (item_name, temp_name, src) ; /* EMIT_SIGNAL */
499 * Returns the name/id of this item
501 * @return the name/id of this item
504 TimeAxisViewItem::get_item_name() const
509 //---------------------------------------------------------------------------------------//
513 * Set to true to indicate that this item is currently selected
515 * @param yn true if this item is currently selected
516 * @param src the identity of the object that initiated the change
519 TimeAxisViewItem::set_selected(bool yn)
521 if (_selected != yn) {
522 Selectable::set_selected (yn);
528 TimeAxisViewItem::set_should_show_selection (bool yn)
530 if (should_show_selection != yn) {
531 should_show_selection = yn;
536 //---------------------------------------------------------------------------------------//
537 // Parent Componenet Methods
540 * Returns the TimeAxisView that this item is upon
542 * @return the timeAxisView that this item is placed upon
545 TimeAxisViewItem::get_time_axis_view()
549 //---------------------------------------------------------------------------------------//
553 * Sets the displayed item text
554 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item
556 * @param new_name the new name text to display
559 TimeAxisViewItem::set_name_text(const ustring& new_name)
561 uint32_t pb_width, it_width;
564 if (!name_pixbuf) return;
566 font_size = NAME_FONT->get_size() / Pango::SCALE;
567 it_width = trackview.editor().frame_to_pixel(item_duration);
568 pb_width = new_name.length() * font_size;
570 if (pb_width > it_width - NAME_X_OFFSET) {
571 pb_width = it_width - NAME_X_OFFSET;
574 if (pb_width <= 0 || it_width < NAME_X_OFFSET) {
581 Glib::RefPtr<Gdk::Pixbuf> buf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, pb_width, NAME_HIGHLIGHT_SIZE);
583 cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, pb_width, NAME_HIGHLIGHT_SIZE );
584 cairo_t *cr = cairo_create (surface);
585 cairo_text_extents_t te;
586 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
587 cairo_select_font_face (cr, NAME_FONT->get_family().c_str(),
588 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
589 cairo_set_font_size (cr, 10);
590 cairo_text_extents (cr, new_name.c_str(), &te);
592 cairo_move_to (cr, 0.5,
593 0.5 - te.height / 2 - te.y_bearing + NAME_HIGHLIGHT_SIZE / 2);
594 cairo_show_text (cr, new_name.c_str());
596 unsigned char* src = cairo_image_surface_get_data (surface);
597 convert_bgra_to_rgba(src, buf->get_pixels(), pb_width, NAME_HIGHLIGHT_SIZE);
600 name_pixbuf->property_pixbuf() = buf;
604 * Set the height of this item
606 * @param h the new height
609 TimeAxisViewItem::set_height (double height)
611 if (name_highlight) {
612 if (height < NAME_HIGHLIGHT_THRESH) {
613 name_highlight->hide();
617 name_highlight->show();
622 if (height > NAME_HIGHLIGHT_SIZE) {
623 name_highlight->property_y1() = (double) height+1 - NAME_HIGHLIGHT_SIZE;
624 name_highlight->property_y2() = (double) height;
627 /* it gets hidden now anyway */
628 name_highlight->property_y1() = (double) 1.0;
629 name_highlight->property_y2() = (double) height;
633 if (visibility & ShowNameText) {
634 name_pixbuf->property_y() = height+1 - NAME_Y_OFFSET;
638 frame->property_y2() = height+1;
641 vestigial_frame->property_y2() = height+1;
648 TimeAxisViewItem::set_color(Gdk::Color& base_color)
650 compute_colors (base_color);
658 TimeAxisViewItem::get_canvas_frame()
667 TimeAxisViewItem::get_canvas_group()
676 TimeAxisViewItem::get_name_highlight()
678 return (name_highlight) ;
684 ArdourCanvas::Pixbuf*
685 TimeAxisViewItem::get_name_pixbuf()
687 return (name_pixbuf) ;
691 * Calculates some contrasting color for displaying various parts of this item, based upon the base color
693 * @param color the base color of the item
696 TimeAxisViewItem::compute_colors(Gdk::Color& base_color)
698 unsigned char radius ;
701 unsigned char r,g,b ;
703 /* FILL: this is simple */
704 r = base_color.get_red()/256 ;
705 g = base_color.get_green()/256 ;
706 b = base_color.get_blue()/256 ;
707 fill_color = RGBA_TO_UINT(r,g,b,160) ;
710 if the overall saturation is strong, make the minor colors light.
711 if its weak, make them dark.
713 we do this by moving an equal distance to the other side of the
714 central circle in the color wheel from where we started.
717 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f)) ;
718 minor_shift = 125 - radius ;
720 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
722 r = base_color.get_red()/256;
723 g = base_color.get_green()/256;
724 b = base_color.get_blue()/256;
730 /* red sector => green */
735 /* green sector => blue */
743 /* blue sector => red */
748 /* green sector => blue */
757 label_color = RGBA_TO_UINT(r,g,b,255);
758 r = (base_color.get_red()/256) + 127 ;
759 g = (base_color.get_green()/256) + 127 ;
760 b = (base_color.get_blue()/256) + 127 ;
762 label_color = RGBA_TO_UINT(r,g,b,255);
764 /* XXX can we do better than this ? */
765 /* We're trying ;) */
768 //frame_color_r = 192;
769 //frame_color_g = 192;
770 //frame_color_b = 194;
772 //selected_frame_color_r = 182;
773 //selected_frame_color_g = 145;
774 //selected_frame_color_b = 168;
776 //handle_color_r = 25 ;
777 //handle_color_g = 0 ;
778 //handle_color_b = 255 ;
779 //lock_handle_color_r = 235 ;
780 //lock_handle_color_g = 16;
781 //lock_handle_color_b = 16;
785 * Convenience method to set the various canvas item colors
788 TimeAxisViewItem::set_colors()
792 if (name_highlight) {
793 name_highlight->property_fill_color_rgba() = fill_color;
794 name_highlight->property_outline_color_rgba() = fill_color;
796 set_trim_handle_colors() ;
800 * Sets the frame color depending on whether this item is selected
803 TimeAxisViewItem::set_frame_color()
808 if (_selected && should_show_selection) {
809 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get(), &r, &g, &b, &a);
810 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
813 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_RecordingRect.get(), &r, &g, &b, &a);
814 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
816 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_FrameBase.get(), &r, &g, &b, &a);
817 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity ? fill_opacity : a);
824 * Sets the colors of the start and end trim handle depending on object state
828 TimeAxisViewItem::set_trim_handle_colors()
830 if (frame_handle_start) {
831 if (position_locked) {
832 frame_handle_start->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
833 frame_handle_end->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
835 frame_handle_start->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandle.get();
836 frame_handle_end->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandle.get();
842 TimeAxisViewItem::get_samples_per_unit()
844 return(samples_per_unit) ;
848 TimeAxisViewItem::set_samples_per_unit (double spu)
850 samples_per_unit = spu ;
851 set_position (this->get_position(), this);
852 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
856 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
858 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
860 if (frame_handle_start) {
861 frame_handle_start->hide();
862 frame_handle_end->hide();
865 } if (pixel_width < 2.0) {
867 if (show_vestigial) {
868 vestigial_frame->show();
871 if (name_highlight) {
872 name_highlight->hide();
880 if (frame_handle_start) {
881 frame_handle_start->hide();
882 frame_handle_end->hide();
886 vestigial_frame->hide();
888 if (name_highlight) {
890 double height = name_highlight->property_y2 ();
892 if (height < NAME_HIGHLIGHT_THRESH) {
893 name_highlight->hide();
896 name_highlight->show();
897 if (!get_item_name().empty()) {
898 reset_name_width (pixel_width);
902 if (visibility & FullWidthNameHighlight) {
903 name_highlight->property_x2() = pixel_width;
905 name_highlight->property_x2() = pixel_width - 1.0;
912 frame->property_x2() = pixel_width;
915 if (frame_handle_start) {
916 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
917 frame_handle_start->hide();
918 frame_handle_end->hide();
920 frame_handle_start->show();
921 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
922 frame_handle_end->show();
923 frame_handle_end->property_x2() = pixel_width;
929 TimeAxisViewItem::reset_name_width (double pixel_width)
931 set_name_text (item_name);
935 //---------------------------------------------------------------------------------------//
936 // Handle time axis removal
939 * Handles the Removal of this time axis item
940 * This _needs_ to be called to alert others of the removal properly, ie where the source
941 * of the removal came from.
943 * XXX Although im not too happy about this method of doing things, I cant think of a cleaner method
944 * just now to capture the source of the removal
946 * @param src the identity of the object that initiated the change
949 TimeAxisViewItem::remove_this_item(void* src)
952 defer to idle loop, otherwise we'll delete this object
953 while we're still inside this function ...
955 Glib::signal_idle().connect(bind (sigc::ptr_fun (&TimeAxisViewItem::idle_remove_this_item), this, src));
959 * Callback used to remove this time axis item during the gtk idle loop
960 * This is used to avoid deleting the obejct while inside the remove_this_item
963 * @param item the TimeAxisViewItem to remove
964 * @param src the identity of the object that initiated the change
967 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
969 item->ItemRemoved (item->get_item_name(), src) ; /* EMIT_SIGNAL */
976 TimeAxisViewItem::set_y (double y)
978 double const old = group->property_y ();
980 group->move (0, y - old);