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_LENGTH = 6;
57 int TimeAxisViewItem::NAME_HEIGHT;
58 double TimeAxisViewItem::NAME_Y_OFFSET;
59 double TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
60 double TimeAxisViewItem::NAME_HIGHLIGHT_THRESH;
63 TimeAxisViewItem::set_constant_heights ()
65 NAME_FONT = get_font_for_style (X_("TimeAxisViewItemName"));
71 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (X_("Hg")); /* ascender + descender */
75 layout->set_font_description (NAME_FONT);
76 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
79 NAME_Y_OFFSET = height + 3;
80 NAME_HIGHLIGHT_SIZE = height + 2;
81 NAME_HIGHLIGHT_THRESH = NAME_HIGHLIGHT_SIZE * 3;
85 * Construct a new TimeAxisViewItem.
87 * @param it_name the unique name of this item
88 * @param parent the parent canvas group
89 * @param tv the TimeAxisView we are going to be added to
90 * @param spu samples per unit
92 * @param start the start point of this item
93 * @param duration the duration of this item
94 * @param recording true if this is a recording region view
95 * @param automation true if this is an automation region view
97 TimeAxisViewItem::TimeAxisViewItem(
98 const string & it_name, ArdourCanvas::Group& parent, TimeAxisView& tv, double spu, Gdk::Color const & base_color,
99 framepos_t start, framecnt_t duration, bool recording, bool automation, Visibility vis
103 , _recregion (recording)
104 , _automation (automation)
106 group = new ArdourCanvas::Group (parent);
108 init (it_name, spu, base_color, start, duration, vis, true, true);
111 TimeAxisViewItem::TimeAxisViewItem (const TimeAxisViewItem& other)
114 , PBD::ScopedConnectionList()
115 , trackview (other.trackview)
116 , _recregion (other._recregion)
117 , _automation (other._automation)
123 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
124 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
126 /* share the other's parent, but still create a new group */
128 Gnome::Canvas::Group* parent = other.group->property_parent();
130 group = new ArdourCanvas::Group (*parent);
132 _selected = other._selected;
135 other.item_name, other.samples_per_unit, c, other.frame_position,
136 other.item_duration, other.visibility, other.wide_enough_for_name, other.high_enough_for_name
141 TimeAxisViewItem::init (
142 const string& it_name, double spu, Gdk::Color const & base_color, framepos_t start, framepos_t duration, Visibility vis, bool wide, bool high)
145 samples_per_unit = spu;
146 frame_position = start;
147 item_duration = duration;
148 name_connected = false;
150 position_locked = false;
151 max_item_duration = ARDOUR::max_framepos;
152 min_item_duration = 0;
153 show_vestigial = true;
156 name_pixbuf_width = 0;
158 wide_enough_for_name = wide;
159 high_enough_for_name = high;
163 warning << "Time Axis Item Duration == 0" << endl;
166 vestigial_frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, 2.0, trackview.current_height());
167 vestigial_frame->hide ();
168 vestigial_frame->property_outline_what() = 0xF;
169 vestigial_frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
170 vestigial_frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
172 if (visibility & ShowFrame) {
173 frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, trackview.editor().frame_to_pixel(duration), trackview.current_height());
175 frame->property_outline_pixels() = 1;
176 frame->property_outline_what() = 0xF;
179 frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
181 frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeAxisFrame.get();
184 frame->property_outline_what() = 0x1|0x2|0x4|0x8;
190 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());
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());
198 name_highlight->set_data ("timeaxisviewitem", this);
199 name_highlight->property_outline_what() = 0x4;
200 /* we should really use a canvas color property here */
201 name_highlight->property_outline_color_rgba() = RGBA_TO_UINT (0,0,0,255);
207 if (visibility & ShowNameText) {
208 name_pixbuf = new ArdourCanvas::Pixbuf(*group);
209 name_pixbuf->property_x() = NAME_X_OFFSET;
210 name_pixbuf->property_y() = trackview.current_height() + 1 - NAME_Y_OFFSET;
216 /* create our grab handles used for trimming/duration etc */
217 if (!_recregion && !_automation) {
218 frame_handle_start = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
219 frame_handle_start->property_outline_what() = 0x0;
220 frame_handle_end = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
221 frame_handle_end->property_outline_what() = 0x0;
223 frame_handle_start = frame_handle_end = 0;
226 set_color (base_color);
228 set_duration (item_duration, this);
229 set_position (start, this);
231 Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&TimeAxisViewItem::parameter_changed, this, _1), gui_context ());
234 TimeAxisViewItem::~TimeAxisViewItem()
240 TimeAxisViewItem::hide_rect ()
242 rect_visible = false;
245 if (name_highlight) {
246 name_highlight->property_outline_what() = 0x0;
247 name_highlight->property_fill_color_rgba() = UINT_RGBA_CHANGE_A(fill_color,64);
252 TimeAxisViewItem::show_rect ()
257 if (name_highlight) {
258 name_highlight->property_outline_what() = 0x4;
259 name_highlight->property_fill_color_rgba() = fill_color;
265 * Set the position of this item on the timeline.
267 * @param pos the new position
268 * @param src the identity of the object that initiated the change
269 * @return true on success
273 TimeAxisViewItem::set_position(framepos_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.
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 */
308 /** @return position of this item on the timeline */
310 TimeAxisViewItem::get_position() const
312 return frame_position;
316 * Set the duration of this item.
318 * @param dur the new duration of this item
319 * @param src the identity of the object that initiated the change
320 * @return true on success
324 TimeAxisViewItem::set_duration (framepos_t dur, void* src)
326 if ((dur > max_item_duration) || (dur < min_item_duration)) {
327 warning << string_compose (_("new duration %1 frames is out of bounds for %2"), get_item_name(), dur)
338 reset_width_dependent_items (trackview.editor().frame_to_pixel (dur));
340 DurationChanged (dur, src); /* EMIT_SIGNAL */
344 /** @return duration of this item */
346 TimeAxisViewItem::get_duration() const
348 return item_duration;
352 * Set the maximum duration that this item can have.
354 * @param dur the new maximum duration
355 * @param src the identity of the object that initiated the change
358 TimeAxisViewItem::set_max_duration(framecnt_t dur, void* src)
360 max_item_duration = dur;
361 MaxDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
364 /** @return the maximum duration that this item may have */
366 TimeAxisViewItem::get_max_duration() const
368 return max_item_duration;
372 * Set the minimum duration that this item may have.
374 * @param the minimum duration that this item may be set to
375 * @param src the identity of the object that initiated the change
378 TimeAxisViewItem::set_min_duration(framecnt_t dur, void* src)
380 min_item_duration = dur;
381 MinDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
384 /** @return the minimum duration that this item mey have */
386 TimeAxisViewItem::get_min_duration() const
388 return min_item_duration;
392 * Set whether this item is locked to its current position.
393 * Locked items cannot be moved until the item is unlocked again.
395 * @param yn true to lock this item to its current position
396 * @param src the identity of the object that initiated the change
399 TimeAxisViewItem::set_position_locked(bool yn, void* src)
401 position_locked = yn;
402 set_trim_handle_colors();
403 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
406 /** @return true if this item is locked to its current position */
408 TimeAxisViewItem::get_position_locked() const
410 return position_locked;
414 * Set whether the maximum duration constraint is active.
416 * @param active set true to enforce the max duration constraint
417 * @param src the identity of the object that initiated the change
420 TimeAxisViewItem::set_max_duration_active (bool active, void* /*src*/)
422 max_duration_active = active;
425 /** @return true if the maximum duration constraint is active */
427 TimeAxisViewItem::get_max_duration_active() const
429 return max_duration_active;
433 * Set whether the minimum duration constraint is active.
435 * @param active set true to enforce the min duration constraint
436 * @param src the identity of the object that initiated the change
440 TimeAxisViewItem::set_min_duration_active (bool active, void* /*src*/)
442 min_duration_active = active;
445 /** @return true if the maximum duration constraint is active */
447 TimeAxisViewItem::get_min_duration_active() const
449 return min_duration_active;
453 * Set the name of this item.
455 * @param new_name the new name of this item
456 * @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 */
469 /** @return the name of this item */
471 TimeAxisViewItem::get_item_name() const
477 * Set selection status.
479 * @param yn true if this item is currently selected
482 TimeAxisViewItem::set_selected(bool yn)
484 if (_selected != yn) {
485 Selectable::set_selected (yn);
490 /** @return the TimeAxisView that this item is on */
492 TimeAxisViewItem::get_time_axis_view () const
498 * Set the displayed item text.
499 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item.
501 * @param new_name the new name text to display
505 TimeAxisViewItem::set_name_text(const string& new_name)
511 last_item_width = trackview.editor().frame_to_pixel(item_duration);
512 name_pixbuf_width = pixel_width (new_name, NAME_FONT) + 2;
513 name_pixbuf->property_pixbuf() = pixbuf_from_string(new_name, NAME_FONT, name_pixbuf_width, NAME_HEIGHT, Gdk::Color ("#000000"));
518 * Set the height of this item.
520 * @param h new height
523 TimeAxisViewItem::set_height (double height)
527 if (name_highlight) {
528 if (height < NAME_HIGHLIGHT_THRESH) {
529 name_highlight->hide ();
530 high_enough_for_name = false;
533 name_highlight->show();
534 high_enough_for_name = true;
537 if (height > NAME_HIGHLIGHT_SIZE) {
538 name_highlight->property_y1() = (double) height - 1 - NAME_HIGHLIGHT_SIZE;
539 name_highlight->property_y2() = (double) height - 1;
542 /* it gets hidden now anyway */
543 name_highlight->property_y1() = (double) 1.0;
544 name_highlight->property_y2() = (double) height;
548 if (visibility & ShowNameText) {
549 name_pixbuf->property_y() = height + 1 - NAME_Y_OFFSET;
553 frame->property_y2() = height - 1;
554 if (frame_handle_start) {
555 frame_handle_start->property_y2() = height - 1;
556 frame_handle_end->property_y2() = height - 1;
560 vestigial_frame->property_y2() = height - 1;
562 update_name_pixbuf_visibility ();
567 TimeAxisViewItem::set_color (Gdk::Color const & base_color)
569 compute_colors (base_color);
574 TimeAxisViewItem::get_canvas_frame()
580 TimeAxisViewItem::get_canvas_group()
586 TimeAxisViewItem::get_name_highlight()
588 return name_highlight;
591 ArdourCanvas::Pixbuf*
592 TimeAxisViewItem::get_name_pixbuf()
598 * Calculate some contrasting color for displaying various parts of this item, based upon the base color.
600 * @param color the base color of the item
603 TimeAxisViewItem::compute_colors (Gdk::Color const & base_color)
605 unsigned char radius;
610 /* FILL: this is simple */
611 r = base_color.get_red()/256;
612 g = base_color.get_green()/256;
613 b = base_color.get_blue()/256;
614 fill_color = RGBA_TO_UINT(r,g,b,160);
617 if the overall saturation is strong, make the minor colors light.
618 if its weak, make them dark.
620 we do this by moving an equal distance to the other side of the
621 central circle in the color wheel from where we started.
624 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f));
625 minor_shift = 125 - radius;
627 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
629 r = base_color.get_red()/256;
630 g = base_color.get_green()/256;
631 b = base_color.get_blue()/256;
637 /* red sector => green */
642 /* green sector => blue */
650 /* blue sector => red */
655 /* green sector => blue */
664 label_color = RGBA_TO_UINT(r,g,b,255);
665 r = (base_color.get_red()/256) + 127;
666 g = (base_color.get_green()/256) + 127;
667 b = (base_color.get_blue()/256) + 127;
669 label_color = RGBA_TO_UINT(r,g,b,255);
671 /* XXX can we do better than this ? */
675 //frame_color_r = 192;
676 //frame_color_g = 192;
677 //frame_color_b = 194;
679 //selected_frame_color_r = 182;
680 //selected_frame_color_g = 145;
681 //selected_frame_color_b = 168;
683 //handle_color_r = 25;
684 //handle_color_g = 0;
685 //handle_color_b = 255;
686 //lock_handle_color_r = 235;
687 //lock_handle_color_g = 16;
688 //lock_handle_color_b = 16;
692 * Convenience method to set the various canvas item colors
695 TimeAxisViewItem::set_colors()
699 if (name_highlight) {
700 name_highlight->property_fill_color_rgba() = fill_color;
702 set_trim_handle_colors();
706 * Sets the frame color depending on whether this item is selected
709 TimeAxisViewItem::set_frame_color()
719 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
722 f = UINT_RGBA_CHANGE_A (f, fill_opacity);
726 f = UINT_RGBA_CHANGE_A (f, 0);
732 f = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
735 if (high_enough_for_name && !Config->get_color_regions_using_track_color()) {
736 f = ARDOUR_UI::config()->canvasvar_FrameBase.get();
742 f = UINT_RGBA_CHANGE_A (f, fill_opacity);
746 f = UINT_RGBA_CHANGE_A (f, 0);
751 frame->property_fill_color_rgba() = f;
755 f = ARDOUR_UI::config()->canvasvar_SelectedTimeAxisFrame.get();
757 f = ARDOUR_UI::config()->canvasvar_TimeAxisFrame.get();
761 f = UINT_RGBA_CHANGE_A (f, 64);
764 frame->property_outline_color_rgba() = f;
769 * Set the colors of the start and end trim handle depending on object state
772 TimeAxisViewItem::set_trim_handle_colors()
774 if (frame_handle_start) {
775 if (position_locked) {
776 frame_handle_start->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
777 frame_handle_end->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
779 frame_handle_start->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
780 frame_handle_end->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
785 /** @return the samples per unit of this item */
787 TimeAxisViewItem::get_samples_per_unit()
789 return samples_per_unit;
793 * Set the samples per unit of this item.
794 * This item is used to determine the relative visual size and position of this item
795 * based upon its duration and start value.
797 * @param spu the new samples per unit value
800 TimeAxisViewItem::set_samples_per_unit (double spu)
802 samples_per_unit = spu;
803 set_position (this->get_position(), this);
804 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
808 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
810 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
812 if (frame_handle_start) {
813 frame_handle_start->hide();
814 frame_handle_end->hide();
819 if (pixel_width < 2.0) {
821 if (show_vestigial) {
822 vestigial_frame->show();
825 if (name_highlight) {
826 name_highlight->hide();
833 if (frame_handle_start) {
834 frame_handle_start->hide();
835 frame_handle_end->hide();
838 wide_enough_for_name = false;
841 vestigial_frame->hide();
843 if (name_highlight) {
845 if (_height < NAME_HIGHLIGHT_THRESH) {
846 name_highlight->hide();
847 high_enough_for_name = false;
849 name_highlight->show();
850 if (!get_item_name().empty()) {
851 reset_name_width (pixel_width);
853 high_enough_for_name = true;
856 name_highlight->property_x2() = pixel_width;
861 frame->property_x2() = pixel_width;
864 if (frame_handle_start) {
865 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
866 frame_handle_start->hide();
867 frame_handle_end->hide();
869 frame_handle_start->show();
870 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
871 frame_handle_end->show();
872 frame_handle_end->property_x2() = pixel_width;
875 wide_enough_for_name = true;
878 update_name_pixbuf_visibility ();
882 TimeAxisViewItem::reset_name_width (double /*pixel_width*/)
886 bool pixbuf_holds_full_name;
892 it_width = trackview.editor().frame_to_pixel(item_duration);
893 pb_width = name_pixbuf_width;
895 pixbuf_holds_full_name = last_item_width > pb_width + NAME_X_OFFSET;
896 last_item_width = it_width;
898 if (pixbuf_holds_full_name && (it_width >= pb_width + NAME_X_OFFSET)) {
900 we've previously had the full name length showing
901 and its still showing.
906 if (pb_width > it_width - NAME_X_OFFSET) {
907 pb_width = it_width - NAME_X_OFFSET;
910 if (it_width <= NAME_X_OFFSET) {
911 wide_enough_for_name = false;
913 wide_enough_for_name = true;
916 update_name_pixbuf_visibility ();
918 name_pixbuf->property_pixbuf() = pixbuf_from_string(item_name, NAME_FONT, pb_width, NAME_HEIGHT, Gdk::Color ("#000000"));
923 * Callback used to remove this time axis item during the gtk idle loop.
924 * This is used to avoid deleting the obejct while inside the remove_this_item
927 * @param item the TimeAxisViewItem to remove.
928 * @param src the identity of the object that initiated the change.
931 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
933 item->ItemRemoved (item->get_item_name(), src); /* EMIT_SIGNAL */
940 TimeAxisViewItem::set_y (double y)
942 double const old = group->property_y ();
944 group->move (0, y - old);
949 TimeAxisViewItem::update_name_pixbuf_visibility ()
955 if (wide_enough_for_name && high_enough_for_name) {
956 name_pixbuf->show ();
958 name_pixbuf->hide ();
963 TimeAxisViewItem::parameter_changed (string p)
965 if (p == "color-regions-using-track-color") {