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);
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 init (other.item_name, other.samples_per_unit, c, other.frame_position, other.item_duration, other.visibility);
135 TimeAxisViewItem::init (const string& it_name, double spu, Gdk::Color const & base_color, nframes64_t start, nframes64_t duration, Visibility vis)
137 item_name = it_name ;
138 samples_per_unit = spu ;
139 should_show_selection = true;
140 frame_position = start ;
141 item_duration = duration ;
142 name_connected = false;
144 position_locked = false ;
145 max_item_duration = ARDOUR::max_frames;
146 min_item_duration = 0 ;
147 show_vestigial = true;
150 name_pixbuf_width = 0;
154 warning << "Time Axis Item Duration == 0" << endl ;
157 vestigial_frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, 2.0, trackview.current_height());
158 vestigial_frame->hide ();
159 vestigial_frame->property_outline_what() = 0xF;
160 vestigial_frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
161 vestigial_frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
163 if (visibility & ShowFrame) {
164 frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, trackview.editor().frame_to_pixel(duration), trackview.current_height());
165 frame->property_outline_pixels() = 1;
166 frame->property_outline_what() = 0xF;
167 frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeAxisFrame.get();
169 /* by default draw all 4 edges */
171 uint32_t outline_what = 0x1|0x2|0x4|0x8;
173 if (visibility & HideFrameLeft) {
174 outline_what &= ~(0x1);
177 if (visibility & HideFrameRight) {
178 outline_what &= ~(0x2);
181 if (visibility & HideFrameTB) {
182 outline_what &= ~(0x4 | 0x8);
185 frame->property_outline_what() = outline_what;
191 if (visibility & ShowNameHighlight) {
192 if (visibility & FullWidthNameHighlight) {
193 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);
195 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);
197 name_highlight->set_data ("timeaxisviewitem", this);
203 if (visibility & ShowNameText) {
204 name_pixbuf = new ArdourCanvas::Pixbuf(*group);
205 name_pixbuf->property_x() = NAME_X_OFFSET;
206 name_pixbuf->property_y() = trackview.current_height() - 1.0 - NAME_Y_OFFSET;
212 /* create our grab handles used for trimming/duration etc */
213 frame_handle_start = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
214 frame_handle_start->property_outline_what() = 0x0;
216 frame_handle_end = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
217 frame_handle_end->property_outline_what() = 0x0;
219 set_color (base_color) ;
221 set_duration (item_duration, this) ;
222 set_position (start, this) ;
228 TimeAxisViewItem::~TimeAxisViewItem()
234 //---------------------------------------------------------------------------------------//
235 // Position and duration Accessors/Mutators
238 * Set the position of this item upon the timeline to the specified value
240 * @param pos the new position
241 * @param src the identity of the object that initiated the change
242 * @return true if the position change was a success, false otherwise
245 TimeAxisViewItem::set_position(nframes64_t pos, void* src, double* delta)
247 if (position_locked) {
251 frame_position = pos;
253 /* This sucks. The GnomeCanvas version I am using
254 doesn't correctly implement gnome_canvas_group_set_arg(),
255 so that simply setting the "x" arg of the group
256 fails to move the group. Instead, we have to
257 use gnome_canvas_item_move(), which does the right
258 thing. I see that in GNOME CVS, the current (Sept 2001)
259 version of GNOME Canvas rectifies this issue cleanly.
262 double old_unit_pos ;
263 double new_unit_pos = pos / samples_per_unit ;
265 old_unit_pos = group->property_x();
267 if (new_unit_pos != old_unit_pos) {
268 group->move (new_unit_pos - old_unit_pos, 0.0);
272 (*delta) = new_unit_pos - old_unit_pos;
275 PositionChanged (frame_position, src) ; /* EMIT_SIGNAL */
281 * Return the position of this item upon the timeline
283 * @return the position of this item
286 TimeAxisViewItem::get_position() const
288 return frame_position;
292 * Sets the duration of this item
294 * @param dur the new duration of this item
295 * @param src the identity of the object that initiated the change
296 * @return true if the duration change was succesful, false otherwise
299 TimeAxisViewItem::set_duration (nframes64_t dur, void* src)
301 if ((dur > max_item_duration) || (dur < min_item_duration)) {
302 warning << string_compose (_("new duration %1 frames is out of bounds for %2"), get_item_name(), dur)
313 reset_width_dependent_items (trackview.editor().frame_to_pixel (dur));
315 DurationChanged (dur, src) ; /* EMIT_SIGNAL */
320 * Returns the duration of this item
324 TimeAxisViewItem::get_duration() const
326 return (item_duration);
330 * Sets the maximum duration that this item make have.
332 * @param dur the new maximum duration
333 * @param src the identity of the object that initiated the change
336 TimeAxisViewItem::set_max_duration(nframes64_t dur, void* src)
338 max_item_duration = dur ;
339 MaxDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
343 * Returns the maxmimum duration that this item may be set to
345 * @return the maximum duration that this item may be set to
348 TimeAxisViewItem::get_max_duration() const
350 return (max_item_duration) ;
354 * Sets the minimu duration that this item may be set to
356 * @param the minimum duration that this item may be set to
357 * @param src the identity of the object that initiated the change
360 TimeAxisViewItem::set_min_duration(nframes64_t dur, void* src)
362 min_item_duration = dur ;
363 MinDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
367 * Returns the minimum duration that this item mey be set to
369 * @return the nimum duration that this item mey be set to
372 TimeAxisViewItem::get_min_duration() const
374 return(min_item_duration) ;
378 * Sets whether the position of this Item is locked to its current position
379 * Locked items cannot be moved until the item is unlocked again.
381 * @param yn set to true to lock this item to its current position
382 * @param src the identity of the object that initiated the change
385 TimeAxisViewItem::set_position_locked(bool yn, void* src)
387 position_locked = yn ;
388 set_trim_handle_colors() ;
389 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
393 * Returns whether this item is locked to its current position
395 * @return true if this item is locked to its current posotion
399 TimeAxisViewItem::get_position_locked() const
401 return (position_locked);
405 * Sets whether the Maximum Duration constraint is active and should be enforced
407 * @param active set true to enforce the max duration constraint
408 * @param src the identity of the object that initiated the change
411 TimeAxisViewItem::set_max_duration_active (bool active, void* /*src*/)
413 max_duration_active = active;
417 * Returns whether the Maximum Duration constraint is active and should be enforced
419 * @return true if the maximum duration constraint is active, false otherwise
422 TimeAxisViewItem::get_max_duration_active() const
424 return(max_duration_active) ;
428 * Sets whether the Minimum Duration constraint is active and should be enforced
430 * @param active set true to enforce the min duration constraint
431 * @param src the identity of the object that initiated the change
434 TimeAxisViewItem::set_min_duration_active (bool active, void* /*src*/)
436 min_duration_active = active ;
440 * Returns whether the Maximum Duration constraint is active and should be enforced
442 * @return true if the maximum duration constraint is active, false otherwise
445 TimeAxisViewItem::get_min_duration_active() const
447 return(min_duration_active) ;
450 //---------------------------------------------------------------------------------------//
451 // Name/Id Accessors/Mutators
454 * Set the name/Id of this item.
456 * @param new_name the new name of this item
457 * @param src the identity of the object that initiated the change
460 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
462 if (new_name != item_name) {
463 std::string temp_name = item_name ;
464 item_name = new_name ;
465 NameChanged (item_name, temp_name, src) ; /* EMIT_SIGNAL */
470 * Returns the name/id of this item
472 * @return the name/id of this item
475 TimeAxisViewItem::get_item_name() const
480 //---------------------------------------------------------------------------------------//
484 * Set to true to indicate that this item is currently selected
486 * @param yn true if this item is currently selected
487 * @param src the identity of the object that initiated the change
490 TimeAxisViewItem::set_selected(bool yn)
492 if (_selected != yn) {
493 Selectable::set_selected (yn);
499 TimeAxisViewItem::set_should_show_selection (bool yn)
501 if (should_show_selection != yn) {
502 should_show_selection = yn;
507 //---------------------------------------------------------------------------------------//
508 // Parent Componenet Methods
511 * Returns the TimeAxisView that this item is upon
513 * @return the timeAxisView that this item is placed upon
516 TimeAxisViewItem::get_time_axis_view()
520 //---------------------------------------------------------------------------------------//
524 * Sets the displayed item text
525 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item
527 * @param new_name the new name text to display
530 TimeAxisViewItem::set_name_text(const ustring& new_name)
536 last_item_width = trackview.editor().frame_to_pixel(item_duration);
537 name_pixbuf_width = pixel_width (new_name, *NAME_FONT) + 2;
538 name_pixbuf->property_pixbuf() = pixbuf_from_ustring(new_name, NAME_FONT, name_pixbuf_width, NAME_HEIGHT);
543 * Set the height of this item
545 * @param h the new height
548 TimeAxisViewItem::set_height (double height)
550 if (name_highlight) {
551 if (height < NAME_HIGHLIGHT_THRESH) {
552 name_highlight->hide();
556 name_highlight->show();
561 if (height > NAME_HIGHLIGHT_SIZE) {
562 name_highlight->property_y1() = (double) height - 1 - NAME_HIGHLIGHT_SIZE;
563 name_highlight->property_y2() = (double) height - 2;
566 /* it gets hidden now anyway */
567 name_highlight->property_y1() = (double) 1.0;
568 name_highlight->property_y2() = (double) height;
572 if (visibility & ShowNameText) {
573 name_pixbuf->property_y() = height - 1 - NAME_Y_OFFSET;
577 frame->property_y2() = height - 1;
578 frame_handle_start->property_y2() = height - 1;
579 frame_handle_end->property_y2() = height - 1;
582 vestigial_frame->property_y2() = height - 1;
589 TimeAxisViewItem::set_color (Gdk::Color const & base_color)
591 compute_colors (base_color);
599 TimeAxisViewItem::get_canvas_frame()
608 TimeAxisViewItem::get_canvas_group()
617 TimeAxisViewItem::get_name_highlight()
619 return (name_highlight) ;
625 ArdourCanvas::Pixbuf*
626 TimeAxisViewItem::get_name_pixbuf()
628 return (name_pixbuf) ;
632 * Calculates some contrasting color for displaying various parts of this item, based upon the base color
634 * @param color the base color of the item
637 TimeAxisViewItem::compute_colors (Gdk::Color const & base_color)
639 unsigned char radius ;
642 unsigned char r,g,b ;
644 /* FILL: this is simple */
645 r = base_color.get_red()/256 ;
646 g = base_color.get_green()/256 ;
647 b = base_color.get_blue()/256 ;
648 fill_color = RGBA_TO_UINT(r,g,b,160) ;
651 if the overall saturation is strong, make the minor colors light.
652 if its weak, make them dark.
654 we do this by moving an equal distance to the other side of the
655 central circle in the color wheel from where we started.
658 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f)) ;
659 minor_shift = 125 - radius ;
661 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
663 r = base_color.get_red()/256;
664 g = base_color.get_green()/256;
665 b = base_color.get_blue()/256;
671 /* red sector => green */
676 /* green sector => blue */
684 /* blue sector => red */
689 /* green sector => blue */
698 label_color = RGBA_TO_UINT(r,g,b,255);
699 r = (base_color.get_red()/256) + 127 ;
700 g = (base_color.get_green()/256) + 127 ;
701 b = (base_color.get_blue()/256) + 127 ;
703 label_color = RGBA_TO_UINT(r,g,b,255);
705 /* XXX can we do better than this ? */
706 /* We're trying ;) */
709 //frame_color_r = 192;
710 //frame_color_g = 192;
711 //frame_color_b = 194;
713 //selected_frame_color_r = 182;
714 //selected_frame_color_g = 145;
715 //selected_frame_color_b = 168;
717 //handle_color_r = 25 ;
718 //handle_color_g = 0 ;
719 //handle_color_b = 255 ;
720 //lock_handle_color_r = 235 ;
721 //lock_handle_color_g = 16;
722 //lock_handle_color_b = 16;
726 * Convenience method to set the various canvas item colors
729 TimeAxisViewItem::set_colors()
733 if (name_highlight) {
734 name_highlight->property_fill_color_rgba() = fill_color;
735 name_highlight->property_outline_color_rgba() = fill_color;
737 set_trim_handle_colors() ;
741 * Sets the frame color depending on whether this item is selected
744 TimeAxisViewItem::set_frame_color()
749 if (_selected && should_show_selection) {
750 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get(), &r, &g, &b, &a);
751 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
754 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_RecordingRect.get(), &r, &g, &b, &a);
755 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
757 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_FrameBase.get(), &r, &g, &b, &a);
758 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity ? fill_opacity : a);
765 * Sets the colors of the start and end trim handle depending on object state
769 TimeAxisViewItem::set_trim_handle_colors()
771 if (frame_handle_start) {
772 if (position_locked) {
773 frame_handle_start->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
774 frame_handle_end->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
776 frame_handle_start->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
777 frame_handle_end->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
783 TimeAxisViewItem::get_samples_per_unit()
785 return(samples_per_unit) ;
789 TimeAxisViewItem::set_samples_per_unit (double spu)
791 samples_per_unit = spu ;
792 set_position (this->get_position(), this);
793 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
797 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
799 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
801 if (frame_handle_start) {
802 frame_handle_start->hide();
803 frame_handle_end->hide();
806 } if (pixel_width < 2.0) {
808 if (show_vestigial) {
809 vestigial_frame->show();
812 if (name_highlight) {
813 name_highlight->hide();
821 if (frame_handle_start) {
822 frame_handle_start->hide();
823 frame_handle_end->hide();
827 vestigial_frame->hide();
829 if (name_highlight) {
831 double height = name_highlight->property_y2 ();
833 if (height < NAME_HIGHLIGHT_THRESH) {
834 name_highlight->hide();
837 name_highlight->show();
838 if (!get_item_name().empty()) {
839 reset_name_width (pixel_width);
843 if (visibility & FullWidthNameHighlight) {
844 name_highlight->property_x2() = pixel_width;
846 name_highlight->property_x2() = pixel_width - 1.0;
853 frame->property_x2() = pixel_width;
856 if (frame_handle_start) {
857 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
858 frame_handle_start->hide();
859 frame_handle_end->hide();
861 frame_handle_start->show();
862 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
863 frame_handle_end->show();
864 frame_handle_end->property_x2() = pixel_width;
870 TimeAxisViewItem::reset_name_width (double /*pixel_width*/)
874 bool pixbuf_holds_full_name;
880 it_width = trackview.editor().frame_to_pixel(item_duration);
881 pb_width = name_pixbuf_width;
883 pixbuf_holds_full_name = last_item_width > pb_width + NAME_X_OFFSET;
884 last_item_width = it_width;
886 if (pixbuf_holds_full_name && (it_width >= pb_width + NAME_X_OFFSET)) {
888 we've previously had the full name length showing
889 and its still showing.
894 if (pb_width > it_width - NAME_X_OFFSET) {
895 pb_width = it_width - NAME_X_OFFSET;
898 if (pb_width <= 0 || it_width <= NAME_X_OFFSET) {
905 name_pixbuf->property_pixbuf() = pixbuf_from_ustring(item_name, NAME_FONT, pb_width, NAME_HEIGHT);
909 //---------------------------------------------------------------------------------------//
910 // Handle time axis removal
913 * Handles the Removal of this time axis item
914 * This _needs_ to be called to alert others of the removal properly, ie where the source
915 * of the removal came from.
917 * XXX Although im not too happy about this method of doing things, I cant think of a cleaner method
918 * just now to capture the source of the removal
920 * @param src the identity of the object that initiated the change
923 TimeAxisViewItem::remove_this_item(void* src)
926 defer to idle loop, otherwise we'll delete this object
927 while we're still inside this function ...
929 Glib::signal_idle().connect(bind (sigc::ptr_fun (&TimeAxisViewItem::idle_remove_this_item), this, src));
933 * Callback used to remove this time axis item during the gtk idle loop
934 * This is used to avoid deleting the obejct while inside the remove_this_item
937 * @param item the TimeAxisViewItem to remove
938 * @param src the identity of the object that initiated the change
941 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
943 item->ItemRemoved (item->get_item_name(), src) ; /* EMIT_SIGNAL */
950 TimeAxisViewItem::set_y (double y)
952 double const old = group->property_y ();
954 group->move (0, y - old);