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"
27 #include "gtkmm2ext/gui_thread.h"
29 #include "ardour_ui.h"
31 * ardour_ui.h was moved up in the include list
32 * due to a conflicting definition of 'Rect' between
33 * Apple's MacTypes.h file and GTK
36 #include "public_editor.h"
37 #include "time_axis_view_item.h"
38 #include "time_axis_view.h"
39 #include "simplerect.h"
41 #include "canvas_impl.h"
42 #include "rgb_macros.h"
47 using namespace Editing;
50 using namespace ARDOUR;
51 using namespace Gtkmm2ext;
53 Pango::FontDescription TimeAxisViewItem::NAME_FONT;
54 const double TimeAxisViewItem::NAME_X_OFFSET = 15.0;
55 const double TimeAxisViewItem::GRAB_HANDLE_TOP = 6;
56 const double TimeAxisViewItem::GRAB_HANDLE_WIDTH = 5;
58 int TimeAxisViewItem::NAME_HEIGHT;
59 double TimeAxisViewItem::NAME_Y_OFFSET;
60 double TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
61 double TimeAxisViewItem::NAME_HIGHLIGHT_THRESH;
64 TimeAxisViewItem::set_constant_heights ()
66 NAME_FONT = get_font_for_style (X_("TimeAxisViewItemName"));
72 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (X_("Hg")); /* ascender + descender */
76 layout->set_font_description (NAME_FONT);
77 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
80 NAME_Y_OFFSET = height + 3;
81 NAME_HIGHLIGHT_SIZE = height + 2;
82 NAME_HIGHLIGHT_THRESH = NAME_HIGHLIGHT_SIZE * 3;
86 * Construct a new TimeAxisViewItem.
88 * @param it_name the unique name of this item
89 * @param parent the parent canvas group
90 * @param tv the TimeAxisView we are going to be added to
91 * @param spu samples per unit
93 * @param start the start point of this item
94 * @param duration the duration of this item
95 * @param recording true if this is a recording region view
96 * @param automation true if this is an automation region view
98 TimeAxisViewItem::TimeAxisViewItem(
99 const string & it_name, ArdourCanvas::Group& parent, TimeAxisView& tv, double spu, Gdk::Color const & base_color,
100 framepos_t start, framecnt_t duration, bool recording, bool automation, Visibility vis
104 , _recregion (recording)
105 , _automation (automation)
108 group = new ArdourCanvas::Group (parent);
110 init (it_name, spu, base_color, start, duration, vis, true, true);
113 TimeAxisViewItem::TimeAxisViewItem (const TimeAxisViewItem& other)
116 , PBD::ScopedConnectionList()
117 , trackview (other.trackview)
118 , _recregion (other._recregion)
119 , _automation (other._automation)
120 , _dragging (other._dragging)
126 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
127 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
129 /* share the other's parent, but still create a new group */
131 Gnome::Canvas::Group* parent = other.group->property_parent();
133 group = new ArdourCanvas::Group (*parent);
135 _selected = other._selected;
137 init (other.item_name, other.samples_per_unit, c, other.frame_position,
138 other.item_duration, other.visibility, other.wide_enough_for_name, other.high_enough_for_name);
142 TimeAxisViewItem::init (
143 const string& it_name, double spu, Gdk::Color const & base_color, framepos_t start, framepos_t duration, Visibility vis, bool wide, bool high)
146 samples_per_unit = spu;
147 frame_position = start;
148 item_duration = duration;
149 name_connected = false;
151 position_locked = false;
152 max_item_duration = ARDOUR::max_framepos;
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;
164 warning << "Time Axis Item Duration == 0" << endl;
167 vestigial_frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, 2.0, trackview.current_height());
168 vestigial_frame->hide ();
169 vestigial_frame->property_outline_what() = 0xF;
170 vestigial_frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
171 vestigial_frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
173 if (visibility & ShowFrame) {
174 frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, trackview.editor().frame_to_pixel(duration), trackview.current_height());
176 frame->property_outline_pixels() = 1;
177 frame->property_outline_what() = 0xF;
180 frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
182 frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeAxisFrame.get();
185 frame->property_outline_what() = 0x1|0x2|0x4|0x8;
191 if (visibility & ShowNameHighlight) {
193 if (visibility & FullWidthNameHighlight) {
194 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());
196 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());
199 name_highlight->set_data ("timeaxisviewitem", this);
200 name_highlight->property_outline_what() = 0x4;
201 /* we should really use a canvas color property here */
202 name_highlight->property_outline_color_rgba() = RGBA_TO_UINT (0,0,0,255);
208 if (visibility & ShowNameText) {
209 name_pixbuf = new ArdourCanvas::Pixbuf(*group);
210 name_pixbuf->property_x() = NAME_X_OFFSET;
211 name_pixbuf->property_y() = trackview.current_height() + 1 - NAME_Y_OFFSET;
217 /* create our grab handles used for trimming/duration etc */
218 if (!_recregion && !_automation) {
219 double top = TimeAxisViewItem::GRAB_HANDLE_TOP;
220 double width = TimeAxisViewItem::GRAB_HANDLE_WIDTH;
222 frame_handle_start = new ArdourCanvas::SimpleRect (*group, 0.0, top, width, trackview.current_height());
223 frame_handle_start->property_outline_what() = 0x0;
224 frame_handle_end = new ArdourCanvas::SimpleRect (*group, 0.0, top, width, trackview.current_height());
225 frame_handle_end->property_outline_what() = 0x0;
227 frame_handle_start = frame_handle_end = 0;
230 set_color (base_color);
232 set_duration (item_duration, this);
233 set_position (start, this);
235 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&TimeAxisViewItem::parameter_changed, this, _1), gui_context ());
238 TimeAxisViewItem::~TimeAxisViewItem()
244 TimeAxisViewItem::hide_rect ()
246 rect_visible = false;
249 if (name_highlight) {
250 name_highlight->property_outline_what() = 0x0;
251 name_highlight->property_fill_color_rgba() = UINT_RGBA_CHANGE_A(fill_color,64);
256 TimeAxisViewItem::show_rect ()
261 if (name_highlight) {
262 name_highlight->property_outline_what() = 0x4;
263 name_highlight->property_fill_color_rgba() = fill_color;
269 * Set the position of this item on the timeline.
271 * @param pos the new position
272 * @param src the identity of the object that initiated the change
273 * @return true on success
277 TimeAxisViewItem::set_position(framepos_t pos, void* src, double* delta)
279 if (position_locked) {
283 frame_position = pos;
285 /* This sucks. The GnomeCanvas version I am using
286 doesn't correctly implement gnome_canvas_group_set_arg(),
287 so that simply setting the "x" arg of the group
288 fails to move the group. Instead, we have to
289 use gnome_canvas_item_move(), which does the right
290 thing. I see that in GNOME CVS, the current (Sept 2001)
291 version of GNOME Canvas rectifies this issue cleanly.
295 double new_unit_pos = pos / samples_per_unit;
297 old_unit_pos = group->property_x();
299 if (new_unit_pos != old_unit_pos) {
300 group->move (new_unit_pos - old_unit_pos, 0.0);
304 (*delta) = new_unit_pos - old_unit_pos;
307 PositionChanged (frame_position, src); /* EMIT_SIGNAL */
312 /** @return position of this item on the timeline */
314 TimeAxisViewItem::get_position() const
316 return frame_position;
320 * Set 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 on success
328 TimeAxisViewItem::set_duration (framecnt_t dur, void* src)
330 if ((dur > max_item_duration) || (dur < min_item_duration)) {
331 warning << string_compose (
332 P_("new duration %1 frame is out of bounds for %2", "new duration of %1 frames is out of bounds for %2", dur),
333 get_item_name(), dur)
344 reset_width_dependent_items (trackview.editor().frame_to_pixel (dur));
346 DurationChanged (dur, src); /* EMIT_SIGNAL */
350 /** @return duration of this item */
352 TimeAxisViewItem::get_duration() const
354 return item_duration;
358 * Set the maximum duration that this item can 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(framecnt_t dur, void* src)
366 max_item_duration = dur;
367 MaxDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
370 /** @return the maximum duration that this item may have */
372 TimeAxisViewItem::get_max_duration() const
374 return max_item_duration;
378 * Set the minimum duration that this item may have.
380 * @param the minimum duration that this item may be set to
381 * @param src the identity of the object that initiated the change
384 TimeAxisViewItem::set_min_duration(framecnt_t dur, void* src)
386 min_item_duration = dur;
387 MinDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
390 /** @return the minimum duration that this item mey have */
392 TimeAxisViewItem::get_min_duration() const
394 return min_item_duration;
398 * Set whether this item is locked to its current position.
399 * Locked items cannot be moved until the item is unlocked again.
401 * @param yn true to lock this item to its current position
402 * @param src the identity of the object that initiated the change
405 TimeAxisViewItem::set_position_locked(bool yn, void* src)
407 position_locked = yn;
408 set_trim_handle_colors();
409 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
412 /** @return true if this item is locked to its current position */
414 TimeAxisViewItem::get_position_locked() const
416 return position_locked;
420 * Set whether the maximum duration constraint is active.
422 * @param active set true to enforce the max duration constraint
423 * @param src the identity of the object that initiated the change
426 TimeAxisViewItem::set_max_duration_active (bool active, void* /*src*/)
428 max_duration_active = active;
431 /** @return true if the maximum duration constraint is active */
433 TimeAxisViewItem::get_max_duration_active() const
435 return max_duration_active;
439 * Set whether the minimum duration constraint is active.
441 * @param active set true to enforce the min duration constraint
442 * @param src the identity of the object that initiated the change
446 TimeAxisViewItem::set_min_duration_active (bool active, void* /*src*/)
448 min_duration_active = active;
451 /** @return true if the maximum duration constraint is active */
453 TimeAxisViewItem::get_min_duration_active() const
455 return min_duration_active;
459 * Set the name of this item.
461 * @param new_name the new name of this item
462 * @param src the identity of the object that initiated the change
466 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
468 if (new_name != item_name) {
469 std::string temp_name = item_name;
470 item_name = new_name;
471 NameChanged (item_name, temp_name, src); /* EMIT_SIGNAL */
475 /** @return the name of this item */
477 TimeAxisViewItem::get_item_name() const
483 * Set selection status.
485 * @param yn true if this item is currently selected
488 TimeAxisViewItem::set_selected(bool yn)
490 if (_selected != yn) {
491 Selectable::set_selected (yn);
496 /** @return the TimeAxisView that this item is on */
498 TimeAxisViewItem::get_time_axis_view () const
504 * Set the displayed item text.
505 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item.
507 * @param new_name the new name text to display
511 TimeAxisViewItem::set_name_text(const string& new_name)
517 last_item_width = trackview.editor().frame_to_pixel(item_duration);
518 name_pixbuf_width = pixel_width (new_name, NAME_FONT) + 2;
519 name_pixbuf->property_pixbuf() = pixbuf_from_string(new_name, NAME_FONT, name_pixbuf_width, NAME_HEIGHT, Gdk::Color ("#000000"));
524 * Set the height of this item.
526 * @param h new height
529 TimeAxisViewItem::set_height (double height)
533 if (name_highlight) {
534 if (height < NAME_HIGHLIGHT_THRESH) {
535 name_highlight->hide ();
536 high_enough_for_name = false;
539 name_highlight->show();
540 high_enough_for_name = true;
543 if (height > NAME_HIGHLIGHT_SIZE) {
544 name_highlight->property_y1() = (double) height - 1 - NAME_HIGHLIGHT_SIZE;
545 name_highlight->property_y2() = (double) height - 1;
548 /* it gets hidden now anyway */
549 name_highlight->property_y1() = (double) 1.0;
550 name_highlight->property_y2() = (double) height;
554 if (visibility & ShowNameText) {
555 name_pixbuf->property_y() = height + 1 - NAME_Y_OFFSET;
559 frame->property_y2() = height - 1;
560 if (frame_handle_start) {
561 frame_handle_start->property_y2() = height - 1;
562 frame_handle_end->property_y2() = height - 1;
566 vestigial_frame->property_y2() = height - 1;
568 update_name_pixbuf_visibility ();
573 TimeAxisViewItem::set_color (Gdk::Color const & base_color)
575 compute_colors (base_color);
580 TimeAxisViewItem::get_canvas_frame()
586 TimeAxisViewItem::get_canvas_group()
592 TimeAxisViewItem::get_name_highlight()
594 return name_highlight;
597 ArdourCanvas::Pixbuf*
598 TimeAxisViewItem::get_name_pixbuf()
604 * Calculate some contrasting color for displaying various parts of this item, based upon the base color.
606 * @param color the base color of the item
609 TimeAxisViewItem::compute_colors (Gdk::Color const & base_color)
611 unsigned char radius;
616 /* FILL: this is simple */
617 r = base_color.get_red()/256;
618 g = base_color.get_green()/256;
619 b = base_color.get_blue()/256;
620 fill_color = RGBA_TO_UINT(r,g,b,160);
623 if the overall saturation is strong, make the minor colors light.
624 if its weak, make them dark.
626 we do this by moving an equal distance to the other side of the
627 central circle in the color wheel from where we started.
630 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f));
631 minor_shift = 125 - radius;
633 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
635 r = base_color.get_red()/256;
636 g = base_color.get_green()/256;
637 b = base_color.get_blue()/256;
643 /* red sector => green */
648 /* green sector => blue */
656 /* blue sector => red */
661 /* green sector => blue */
670 label_color = RGBA_TO_UINT(r,g,b,255);
671 r = (base_color.get_red()/256) + 127;
672 g = (base_color.get_green()/256) + 127;
673 b = (base_color.get_blue()/256) + 127;
675 label_color = RGBA_TO_UINT(r,g,b,255);
677 /* XXX can we do better than this ? */
681 //frame_color_r = 192;
682 //frame_color_g = 192;
683 //frame_color_b = 194;
685 //selected_frame_color_r = 182;
686 //selected_frame_color_g = 145;
687 //selected_frame_color_b = 168;
689 //handle_color_r = 25;
690 //handle_color_g = 0;
691 //handle_color_b = 255;
692 //lock_handle_color_r = 235;
693 //lock_handle_color_g = 16;
694 //lock_handle_color_b = 16;
698 * Convenience method to set the various canvas item colors
701 TimeAxisViewItem::set_colors()
705 if (name_highlight) {
706 name_highlight->property_fill_color_rgba() = fill_color;
708 set_trim_handle_colors();
712 * Sets the frame color depending on whether this item is selected
715 TimeAxisViewItem::set_frame_color()
725 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
728 f = UINT_RGBA_CHANGE_A (f, fill_opacity);
732 f = UINT_RGBA_CHANGE_A (f, 0);
738 f = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
741 if (high_enough_for_name && !Config->get_color_regions_using_track_color()) {
742 f = ARDOUR_UI::config()->canvasvar_FrameBase.get();
748 f = UINT_RGBA_CHANGE_A (f, fill_opacity);
752 f = UINT_RGBA_CHANGE_A (f, 0);
757 frame->property_fill_color_rgba() = f;
761 f = ARDOUR_UI::config()->canvasvar_SelectedTimeAxisFrame.get();
763 f = ARDOUR_UI::config()->canvasvar_TimeAxisFrame.get();
767 f = UINT_RGBA_CHANGE_A (f, 64);
770 frame->property_outline_color_rgba() = f;
775 * Set the colors of the start and end trim handle depending on object state
778 TimeAxisViewItem::set_trim_handle_colors()
780 if (frame_handle_start) {
781 if (position_locked) {
782 frame_handle_start->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
783 frame_handle_end->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
785 frame_handle_start->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
786 frame_handle_end->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
791 /** @return the samples per unit of this item */
793 TimeAxisViewItem::get_samples_per_unit()
795 return samples_per_unit;
799 * Set the samples per unit of this item.
800 * This item is used to determine the relative visual size and position of this item
801 * based upon its duration and start value.
803 * @param spu the new samples per unit value
806 TimeAxisViewItem::set_samples_per_unit (double spu)
808 samples_per_unit = spu;
809 set_position (this->get_position(), this);
810 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
814 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
817 if (pixel_width < 2.0) {
819 if (show_vestigial) {
820 vestigial_frame->show();
823 if (name_highlight) {
824 name_highlight->hide();
831 if (frame_handle_start) {
832 frame_handle_start->hide();
833 frame_handle_end->hide();
836 wide_enough_for_name = false;
839 vestigial_frame->hide();
841 if (name_highlight) {
843 if (_height < NAME_HIGHLIGHT_THRESH) {
844 name_highlight->hide();
845 high_enough_for_name = false;
847 name_highlight->show();
848 if (!get_item_name().empty()) {
849 reset_name_width (pixel_width);
851 high_enough_for_name = true;
854 name_highlight->property_x2() = pixel_width;
859 frame->property_x2() = pixel_width;
862 if (frame_handle_start) {
863 if (pixel_width < (3 * TimeAxisViewItem::GRAB_HANDLE_WIDTH)) {
865 * there's less than GRAB_HANDLE_WIDTH of the region between
866 * the right-hand end of frame_handle_start and the left-hand
867 * end of frame_handle_end, so disable the handles
869 frame_handle_start->hide();
870 frame_handle_end->hide();
872 frame_handle_start->show();
873 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_WIDTH);
874 frame_handle_end->property_x2() = pixel_width;
875 frame_handle_end->show();
879 wide_enough_for_name = true;
882 update_name_pixbuf_visibility ();
886 TimeAxisViewItem::reset_name_width (double /*pixel_width*/)
890 bool pixbuf_holds_full_name;
896 it_width = trackview.editor().frame_to_pixel(item_duration);
897 pb_width = name_pixbuf_width;
899 pixbuf_holds_full_name = last_item_width > pb_width + NAME_X_OFFSET;
900 last_item_width = it_width;
902 if (pixbuf_holds_full_name && (it_width >= pb_width + NAME_X_OFFSET)) {
904 we've previously had the full name length showing
905 and its still showing.
910 if (pb_width > it_width - NAME_X_OFFSET) {
911 pb_width = it_width - NAME_X_OFFSET;
914 if (it_width <= NAME_X_OFFSET) {
915 wide_enough_for_name = false;
917 wide_enough_for_name = true;
920 update_name_pixbuf_visibility ();
926 name_pixbuf->property_pixbuf() = pixbuf_from_string(item_name, NAME_FONT, pb_width, NAME_HEIGHT, Gdk::Color ("#000000"));
930 * Callback used to remove this time axis item during the gtk idle loop.
931 * This is used to avoid deleting the obejct while inside the remove_this_item
934 * @param item the TimeAxisViewItem to remove.
935 * @param src the identity of the object that initiated the change.
938 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
940 item->ItemRemoved (item->get_item_name(), src); /* EMIT_SIGNAL */
947 TimeAxisViewItem::set_y (double y)
949 double const old = group->property_y ();
951 group->move (0, y - old);
956 TimeAxisViewItem::update_name_pixbuf_visibility ()
962 if (wide_enough_for_name && high_enough_for_name) {
963 name_pixbuf->show ();
965 name_pixbuf->hide ();
970 TimeAxisViewItem::parameter_changed (string p)
972 if (p == "color-regions-using-track-color") {