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 "canvas/group.h"
30 #include "canvas/rectangle.h"
31 #include "canvas/debug.h"
32 #include "canvas/text.h"
34 #include "ardour_ui.h"
36 * ardour_ui.h was moved up in the include list
37 * due to a conflicting definition of 'Rect' between
38 * Apple's MacTypes.h file and GTK
41 #include "public_editor.h"
42 #include "time_axis_view_item.h"
43 #include "time_axis_view.h"
45 #include "rgb_macros.h"
50 using namespace Editing;
53 using namespace ARDOUR;
54 using namespace Gtkmm2ext;
56 Pango::FontDescription TimeAxisViewItem::NAME_FONT;
57 const double TimeAxisViewItem::NAME_X_OFFSET = 15.0;
58 const double TimeAxisViewItem::GRAB_HANDLE_TOP = 6;
59 const double TimeAxisViewItem::GRAB_HANDLE_WIDTH = 5;
61 int TimeAxisViewItem::NAME_HEIGHT;
62 double TimeAxisViewItem::NAME_Y_OFFSET;
63 double TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
64 double TimeAxisViewItem::NAME_HIGHLIGHT_THRESH;
67 TimeAxisViewItem::set_constant_heights ()
69 NAME_FONT = get_font_for_style (X_("TimeAxisViewItemName"));
75 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (X_("Hg")); /* ascender + descender */
79 layout->set_font_description (NAME_FONT);
80 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
83 NAME_Y_OFFSET = height + 4;
84 NAME_HIGHLIGHT_SIZE = height + 2;
85 NAME_HIGHLIGHT_THRESH = NAME_HIGHLIGHT_SIZE * 3;
89 * Construct a new TimeAxisViewItem.
91 * @param it_name the unique name of this item
92 * @param parent the parent canvas group
93 * @param tv the TimeAxisView we are going to be added to
94 * @param spu samples per unit
96 * @param start the start point of this item
97 * @param duration the duration of this item
98 * @param recording true if this is a recording region view
99 * @param automation true if this is an automation region view
101 TimeAxisViewItem::TimeAxisViewItem(
102 const string & it_name, ArdourCanvas::Group& parent, TimeAxisView& tv, double spu, Gdk::Color const & base_color,
103 framepos_t start, framecnt_t duration, bool recording, bool automation, Visibility vis
107 , _recregion (recording)
108 , _automation (automation)
111 group = new ArdourCanvas::Group (&parent);
112 CANVAS_DEBUG_NAME (group, string_compose ("TAVI group for %1", it_name));
114 init (it_name, spu, base_color, start, duration, vis, true, true);
117 TimeAxisViewItem::TimeAxisViewItem (const TimeAxisViewItem& other)
120 , PBD::ScopedConnectionList()
121 , trackview (other.trackview)
122 , _recregion (other._recregion)
123 , _automation (other._automation)
124 , _dragging (other._dragging)
130 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
131 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
133 /* share the other's parent, but still create a new group */
135 ArdourCanvas::Group* parent = other.group->parent();
137 group = new ArdourCanvas::Group (parent);
139 _selected = other._selected;
141 init (other.item_name, other.samples_per_pixel, c, other.frame_position,
142 other.item_duration, other.visibility, other.wide_enough_for_name, other.high_enough_for_name);
146 TimeAxisViewItem::init (const string& it_name, double fpp, Gdk::Color const & base_color, framepos_t start, framepos_t duration, Visibility vis, bool wide, bool high)
149 samples_per_pixel = fpp;
150 frame_position = start;
151 item_duration = duration;
152 name_connected = false;
154 position_locked = false;
155 max_item_duration = ARDOUR::max_framepos;
156 min_item_duration = 0;
157 show_vestigial = true;
162 wide_enough_for_name = wide;
163 high_enough_for_name = high;
167 warning << "Time Axis Item Duration == 0" << endl;
170 vestigial_frame = new ArdourCanvas::Rectangle (group, ArdourCanvas::Rect (0.0, 1.0, 2.0, trackview.current_height()));
171 vestigial_frame->hide ();
172 vestigial_frame->set_outline_color (ARDOUR_UI::config()->canvasvar_VestigialFrame.get());
173 vestigial_frame->set_fill_color (ARDOUR_UI::config()->canvasvar_VestigialFrame.get());
175 if (visibility & ShowFrame) {
176 frame = new ArdourCanvas::Rectangle (group,
177 ArdourCanvas::Rect (0.0, 1.0,
178 trackview.editor().sample_to_pixel(duration),
179 trackview.current_height()));
182 frame->set_outline_color (ARDOUR_UI::config()->canvasvar_RecordingRect.get());
184 frame->set_outline_color (ARDOUR_UI::config()->canvasvar_TimeAxisFrame.get());
192 if (visibility & ShowNameHighlight) {
194 if (visibility & FullWidthNameHighlight) {
195 name_highlight = new ArdourCanvas::Rectangle (group,
196 ArdourCanvas::Rect (0.0, trackview.editor().sample_to_pixel(item_duration),
197 trackview.current_height() - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE,
198 trackview.current_height()));
200 name_highlight = new ArdourCanvas::Rectangle (group,
201 ArdourCanvas::Rect (1.0, trackview.editor().sample_to_pixel(item_duration) - 1,
202 trackview.current_height() - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE,
203 trackview.current_height()));
206 name_highlight->set_data ("timeaxisviewitem", this);
207 name_highlight->set_outline_what (ArdourCanvas::Rectangle::TOP);
208 /* we should really use a canvas color property here */
209 name_highlight->set_outline_color (RGBA_TO_UINT (0,0,0,255));
215 if (visibility & ShowNameText) {
216 name_text = new ArdourCanvas::Text (group);
217 name_text->set_position (ArdourCanvas::Duple (NAME_X_OFFSET, trackview.current_height() - NAME_Y_OFFSET));
218 name_text->set_font_description (NAME_FONT);
224 /* create our grab handles used for trimming/duration etc */
225 if (!_recregion && !_automation) {
226 double top = TimeAxisViewItem::GRAB_HANDLE_TOP;
227 double width = TimeAxisViewItem::GRAB_HANDLE_WIDTH;
229 frame_handle_start = new ArdourCanvas::Rectangle (group, ArdourCanvas::Rect (0.0, top, width, trackview.current_height()));
230 frame_handle_start->set_outline_what (ArdourCanvas::Rectangle::What (0));
231 frame_handle_end = new ArdourCanvas::Rectangle (group, ArdourCanvas::Rect (0.0, top, width, trackview.current_height()));
232 frame_handle_end->set_outline_what (ArdourCanvas::Rectangle::What (0));
234 frame_handle_start = frame_handle_end = 0;
237 set_color (base_color);
239 set_duration (item_duration, this);
240 set_position (start, this);
242 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&TimeAxisViewItem::parameter_changed, this, _1), gui_context ());
245 TimeAxisViewItem::~TimeAxisViewItem()
251 TimeAxisViewItem::hide_rect ()
253 rect_visible = false;
256 if (name_highlight) {
257 name_highlight->set_outline_what (ArdourCanvas::Rectangle::What (0));
258 name_highlight->set_fill_color (UINT_RGBA_CHANGE_A (fill_color, 64));
263 TimeAxisViewItem::show_rect ()
268 if (name_highlight) {
269 name_highlight->set_outline_what (ArdourCanvas::Rectangle::TOP);
270 name_highlight->set_fill_color (fill_color);
275 * Set the position of this item on the timeline.
277 * @param pos the new position
278 * @param src the identity of the object that initiated the change
279 * @return true on success
283 TimeAxisViewItem::set_position(framepos_t pos, void* src, double* delta)
285 if (position_locked) {
289 frame_position = pos;
291 /* This sucks. The GnomeCanvas version I am using
292 doesn't correctly implement gnome_canvas_group_set_arg(),
293 so that simply setting the "x" arg of the group
294 fails to move the group. Instead, we have to
295 use gnome_canvas_item_move(), which does the right
296 thing. I see that in GNOME CVS, the current (Sept 2001)
297 version of GNOME Canvas rectifies this issue cleanly.
301 double new_unit_pos = pos / samples_per_pixel;
303 old_unit_pos = group->position().x;
305 if (new_unit_pos != old_unit_pos) {
306 group->set_x_position (new_unit_pos);
310 (*delta) = new_unit_pos - old_unit_pos;
313 PositionChanged (frame_position, src); /* EMIT_SIGNAL */
318 /** @return position of this item on the timeline */
320 TimeAxisViewItem::get_position() const
322 return frame_position;
326 * Set the duration of this item.
328 * @param dur the new duration of this item
329 * @param src the identity of the object that initiated the change
330 * @return true on success
334 TimeAxisViewItem::set_duration (framecnt_t dur, void* src)
336 if ((dur > max_item_duration) || (dur < min_item_duration)) {
337 warning << string_compose (
338 P_("new duration %1 frame is out of bounds for %2", "new duration of %1 frames is out of bounds for %2", dur),
339 get_item_name(), dur)
350 reset_width_dependent_items (trackview.editor().sample_to_pixel (dur));
352 DurationChanged (dur, src); /* EMIT_SIGNAL */
356 /** @return duration of this item */
358 TimeAxisViewItem::get_duration() const
360 return item_duration;
364 * Set the maximum duration that this item can have.
366 * @param dur the new maximum duration
367 * @param src the identity of the object that initiated the change
370 TimeAxisViewItem::set_max_duration(framecnt_t dur, void* src)
372 max_item_duration = dur;
373 MaxDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
376 /** @return the maximum duration that this item may have */
378 TimeAxisViewItem::get_max_duration() const
380 return max_item_duration;
384 * Set the minimum duration that this item may have.
386 * @param the minimum duration that this item may be set to
387 * @param src the identity of the object that initiated the change
390 TimeAxisViewItem::set_min_duration(framecnt_t dur, void* src)
392 min_item_duration = dur;
393 MinDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
396 /** @return the minimum duration that this item mey have */
398 TimeAxisViewItem::get_min_duration() const
400 return min_item_duration;
404 * Set whether this item is locked to its current position.
405 * Locked items cannot be moved until the item is unlocked again.
407 * @param yn true to lock this item to its current position
408 * @param src the identity of the object that initiated the change
411 TimeAxisViewItem::set_position_locked(bool yn, void* src)
413 position_locked = yn;
414 set_trim_handle_colors();
415 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
418 /** @return true if this item is locked to its current position */
420 TimeAxisViewItem::get_position_locked() const
422 return position_locked;
426 * Set whether the maximum duration constraint is active.
428 * @param active set true to enforce the max duration constraint
429 * @param src the identity of the object that initiated the change
432 TimeAxisViewItem::set_max_duration_active (bool active, void* /*src*/)
434 max_duration_active = active;
437 /** @return true if the maximum duration constraint is active */
439 TimeAxisViewItem::get_max_duration_active() const
441 return max_duration_active;
445 * Set whether the minimum duration constraint is active.
447 * @param active set true to enforce the min duration constraint
448 * @param src the identity of the object that initiated the change
452 TimeAxisViewItem::set_min_duration_active (bool active, void* /*src*/)
454 min_duration_active = active;
457 /** @return true if the maximum duration constraint is active */
459 TimeAxisViewItem::get_min_duration_active() const
461 return min_duration_active;
465 * Set the name of this item.
467 * @param new_name the new name of this item
468 * @param src the identity of the object that initiated the change
472 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
474 if (new_name != item_name) {
475 std::string temp_name = item_name;
476 item_name = new_name;
477 NameChanged (item_name, temp_name, src); /* EMIT_SIGNAL */
481 /** @return the name of this item */
483 TimeAxisViewItem::get_item_name() const
489 * Set selection status.
491 * @param yn true if this item is currently selected
494 TimeAxisViewItem::set_selected(bool yn)
496 if (_selected != yn) {
497 Selectable::set_selected (yn);
502 /** @return the TimeAxisView that this item is on */
504 TimeAxisViewItem::get_time_axis_view () const
510 * Set the displayed item text.
511 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item.
513 * @param new_name the new name text to display
517 TimeAxisViewItem::set_name_text(const string& new_name)
523 last_item_width = trackview.editor().sample_to_pixel(item_duration);
524 name_text_width = pixel_width (new_name, NAME_FONT) + 2;
525 name_text->set (new_name);
526 // CAIROCANVAS need to limit text to name_text_width or something
530 * Set the height of this item.
532 * @param h new height
535 TimeAxisViewItem::set_height (double height)
539 if (name_highlight) {
540 if (height < NAME_HIGHLIGHT_THRESH) {
541 name_highlight->hide ();
542 high_enough_for_name = false;
545 name_highlight->show();
546 high_enough_for_name = true;
549 if (height > NAME_HIGHLIGHT_SIZE) {
550 name_highlight->set_y0 ((double) height - 1 - NAME_HIGHLIGHT_SIZE);
551 name_highlight->set_y1 ((double) height - 1);
554 /* it gets hidden now anyway */
555 name_highlight->set_y0 (1);
556 name_highlight->set_y1 (height);
560 if (visibility & ShowNameText) {
561 name_text->set_y_position (height + 1 - NAME_Y_OFFSET);
565 frame->set_y1 (height - 1);
566 if (frame_handle_start) {
567 frame_handle_start->set_y1 (height - 1);
568 frame_handle_end->set_y1 (height - 1);
572 vestigial_frame->set_y1 (height - 1);
574 update_name_text_visibility ();
579 TimeAxisViewItem::set_color (Gdk::Color const & base_color)
581 compute_colors (base_color);
586 TimeAxisViewItem::get_canvas_frame()
592 TimeAxisViewItem::get_canvas_group()
598 TimeAxisViewItem::get_name_highlight()
600 return name_highlight;
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->set_fill_color (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->set_fill_color (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->set_outline_color (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->set_fill_color (ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get());
783 frame_handle_end->set_fill_color (ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get());
785 frame_handle_start->set_fill_color (RGBA_TO_UINT (1, 1, 1, 0)); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
786 frame_handle_end->set_fill_color (RGBA_TO_UINT (1, 1, 1, 0)); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
791 /** @return the frames per pixel */
793 TimeAxisViewItem::get_samples_per_pixel () const
795 return samples_per_pixel;
798 /** Set the frames per pixel of this item.
799 * This item is used to determine the relative visual size and position of this item
800 * based upon its duration and start value.
802 * @param fpp the new frames per pixel
805 TimeAxisViewItem::set_samples_per_pixel (double fpp)
807 samples_per_pixel = fpp;
808 set_position (this->get_position(), this);
809 reset_width_dependent_items ((double) get_duration() / samples_per_pixel);
813 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
816 if (pixel_width < 2.0) {
818 if (show_vestigial) {
819 vestigial_frame->show();
822 if (name_highlight) {
823 name_highlight->hide();
830 if (frame_handle_start) {
831 frame_handle_start->hide();
832 frame_handle_end->hide();
835 wide_enough_for_name = false;
838 vestigial_frame->hide();
840 if (name_highlight) {
842 if (_height < NAME_HIGHLIGHT_THRESH) {
843 name_highlight->hide();
844 high_enough_for_name = false;
846 name_highlight->show();
847 if (!get_item_name().empty()) {
848 reset_name_width (pixel_width);
850 high_enough_for_name = true;
853 name_highlight->set_x1 (pixel_width);
858 frame->set_x1 (pixel_width);
861 if (frame_handle_start) {
862 if (pixel_width < (3 * TimeAxisViewItem::GRAB_HANDLE_WIDTH)) {
864 * there's less than GRAB_HANDLE_WIDTH of the region between
865 * the right-hand end of frame_handle_start and the left-hand
866 * end of frame_handle_end, so disable the handles
868 frame_handle_start->hide();
869 frame_handle_end->hide();
871 frame_handle_start->show();
872 frame_handle_end->set_x0 (pixel_width - (TimeAxisViewItem::GRAB_HANDLE_WIDTH));
873 frame_handle_end->set_x1 (pixel_width);
874 frame_handle_end->show();
878 wide_enough_for_name = true;
881 update_name_text_visibility ();
885 TimeAxisViewItem::reset_name_width (double /*pixel_width*/)
889 bool pixbuf_holds_full_name;
895 it_width = trackview.editor().sample_to_pixel(item_duration);
896 pb_width = name_text_width;
898 pixbuf_holds_full_name = last_item_width > pb_width + NAME_X_OFFSET;
899 last_item_width = it_width;
901 if (pixbuf_holds_full_name && (it_width >= pb_width + NAME_X_OFFSET)) {
903 we've previously had the full name length showing
904 and its still showing.
909 if (pb_width > it_width - NAME_X_OFFSET) {
910 pb_width = it_width - NAME_X_OFFSET;
913 if (it_width <= NAME_X_OFFSET) {
914 wide_enough_for_name = false;
916 wide_enough_for_name = true;
919 update_name_text_visibility ();
925 name_text->set (item_name);
926 // CAIROCANVAS need to limit text length to pb_width
931 * Callback used to remove this time axis item during the gtk idle loop.
932 * This is used to avoid deleting the obejct while inside the remove_this_item
935 * @param item the TimeAxisViewItem to remove.
936 * @param src the identity of the object that initiated the change.
939 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
941 item->ItemRemoved (item->get_item_name(), src); /* EMIT_SIGNAL */
948 TimeAxisViewItem::set_y (double y)
950 group->set_y_position (y);
954 TimeAxisViewItem::update_name_text_visibility ()
960 if (wide_enough_for_name && high_enough_for_name) {
968 TimeAxisViewItem::parameter_changed (string p)
970 if (p == "color-regions-using-track-color") {