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/pixbuf.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 + 3;
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.frames_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 frames_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;
160 name_pixbuf_width = 0;
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_what (0xF);
173 vestigial_frame->set_outline_color (ARDOUR_UI::config()->canvasvar_VestigialFrame.get());
174 vestigial_frame->set_fill_color (ARDOUR_UI::config()->canvasvar_VestigialFrame.get());
176 if (visibility & ShowFrame) {
177 frame = new ArdourCanvas::Rectangle (group,
178 ArdourCanvas::Rect (0.0, 1.0,
179 trackview.editor().frame_to_pixel(duration),
180 trackview.current_height()));
182 frame->set_outline_width (1);
183 frame->set_outline_what (0xF);
186 frame->set_outline_color (ARDOUR_UI::config()->canvasvar_RecordingRect.get());
188 frame->set_outline_color (ARDOUR_UI::config()->canvasvar_TimeAxisFrame.get());
191 frame->set_outline_what (0x1|0x2|0x4|0x8);
197 if (visibility & ShowNameHighlight) {
199 if (visibility & FullWidthNameHighlight) {
200 name_highlight = new ArdourCanvas::Rectangle (group,
201 ArdourCanvas::Rect (0.0, trackview.editor().frame_to_pixel(item_duration),
202 trackview.current_height() - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE, trackview.current_height()));
204 name_highlight = new ArdourCanvas::Rectangle (group,
205 ArdourCanvas::Rect (1.0, trackview.editor().frame_to_pixel(item_duration) - 1,
206 trackview.current_height() - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE, trackview.current_height()));
209 name_highlight->set_data ("timeaxisviewitem", this);
210 name_highlight->set_outline_what (0x4);
211 /* we should really use a canvas color property here */
212 name_highlight->set_outline_color (RGBA_TO_UINT (0,0,0,255));
218 if (visibility & ShowNameText) {
219 name_pixbuf = new ArdourCanvas::Pixbuf(group);
220 name_pixbuf->set_position (ArdourCanvas::Duple (NAME_X_OFFSET, trackview.current_height() + 1 - NAME_Y_OFFSET));
226 /* create our grab handles used for trimming/duration etc */
227 if (!_recregion && !_automation) {
228 double top = TimeAxisViewItem::GRAB_HANDLE_TOP;
229 double width = TimeAxisViewItem::GRAB_HANDLE_WIDTH;
231 frame_handle_start = new ArdourCanvas::Rectangle (group, ArdourCanvas::Rect (0.0, top, width, trackview.current_height()));
232 frame_handle_start->set_outline_what (0x0);
233 frame_handle_end = new ArdourCanvas::Rectangle (group, ArdourCanvas::Rect (0.0, top, width, trackview.current_height()));
234 frame_handle_end->set_outline_what (0x0);
236 frame_handle_start = frame_handle_end = 0;
239 set_color (base_color);
241 set_duration (item_duration, this);
242 set_position (start, this);
244 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&TimeAxisViewItem::parameter_changed, this, _1), gui_context ());
247 TimeAxisViewItem::~TimeAxisViewItem()
253 TimeAxisViewItem::hide_rect ()
255 rect_visible = false;
258 if (name_highlight) {
259 name_highlight->set_outline_what (0);
260 name_highlight->set_fill_color (UINT_RGBA_CHANGE_A (fill_color, 64));
265 TimeAxisViewItem::show_rect ()
270 if (name_highlight) {
271 name_highlight->set_outline_what (0x4);
272 name_highlight->set_fill_color (fill_color);
278 * Set the position of this item on the timeline.
280 * @param pos the new position
281 * @param src the identity of the object that initiated the change
282 * @return true on success
286 TimeAxisViewItem::set_position(framepos_t pos, void* src, double* delta)
288 if (position_locked) {
292 frame_position = pos;
294 /* This sucks. The GnomeCanvas version I am using
295 doesn't correctly implement gnome_canvas_group_set_arg(),
296 so that simply setting the "x" arg of the group
297 fails to move the group. Instead, we have to
298 use gnome_canvas_item_move(), which does the right
299 thing. I see that in GNOME CVS, the current (Sept 2001)
300 version of GNOME Canvas rectifies this issue cleanly.
304 double new_unit_pos = pos / frames_per_pixel;
306 old_unit_pos = group->position().x;
308 if (new_unit_pos != old_unit_pos) {
309 group->set_x_position (new_unit_pos);
313 (*delta) = new_unit_pos - old_unit_pos;
316 PositionChanged (frame_position, src); /* EMIT_SIGNAL */
321 /** @return position of this item on the timeline */
323 TimeAxisViewItem::get_position() const
325 return frame_position;
329 * Set the duration of this item.
331 * @param dur the new duration of this item
332 * @param src the identity of the object that initiated the change
333 * @return true on success
337 TimeAxisViewItem::set_duration (framecnt_t dur, void* src)
339 if ((dur > max_item_duration) || (dur < min_item_duration)) {
340 warning << string_compose (
341 P_("new duration %1 frame is out of bounds for %2", "new duration of %1 frames is out of bounds for %2", dur),
342 get_item_name(), dur)
353 reset_width_dependent_items (trackview.editor().frame_to_pixel (dur));
355 DurationChanged (dur, src); /* EMIT_SIGNAL */
359 /** @return duration of this item */
361 TimeAxisViewItem::get_duration() const
363 return item_duration;
367 * Set the maximum duration that this item can have.
369 * @param dur the new maximum duration
370 * @param src the identity of the object that initiated the change
373 TimeAxisViewItem::set_max_duration(framecnt_t dur, void* src)
375 max_item_duration = dur;
376 MaxDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
379 /** @return the maximum duration that this item may have */
381 TimeAxisViewItem::get_max_duration() const
383 return max_item_duration;
387 * Set the minimum duration that this item may have.
389 * @param the minimum duration that this item may be set to
390 * @param src the identity of the object that initiated the change
393 TimeAxisViewItem::set_min_duration(framecnt_t dur, void* src)
395 min_item_duration = dur;
396 MinDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
399 /** @return the minimum duration that this item mey have */
401 TimeAxisViewItem::get_min_duration() const
403 return min_item_duration;
407 * Set whether this item is locked to its current position.
408 * Locked items cannot be moved until the item is unlocked again.
410 * @param yn 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 */
421 /** @return true if this item is locked to its current position */
423 TimeAxisViewItem::get_position_locked() const
425 return position_locked;
429 * Set whether the maximum duration constraint is active.
431 * @param active set true to enforce the max duration constraint
432 * @param src the identity of the object that initiated the change
435 TimeAxisViewItem::set_max_duration_active (bool active, void* /*src*/)
437 max_duration_active = active;
440 /** @return true if the maximum duration constraint is active */
442 TimeAxisViewItem::get_max_duration_active() const
444 return max_duration_active;
448 * Set whether the minimum duration constraint is active.
450 * @param active set true to enforce the min duration constraint
451 * @param src the identity of the object that initiated the change
455 TimeAxisViewItem::set_min_duration_active (bool active, void* /*src*/)
457 min_duration_active = active;
460 /** @return true if the maximum duration constraint is active */
462 TimeAxisViewItem::get_min_duration_active() const
464 return min_duration_active;
468 * Set the name of this item.
470 * @param new_name the new name of this item
471 * @param src the identity of the object that initiated the change
475 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
477 if (new_name != item_name) {
478 std::string temp_name = item_name;
479 item_name = new_name;
480 NameChanged (item_name, temp_name, src); /* EMIT_SIGNAL */
484 /** @return the name of this item */
486 TimeAxisViewItem::get_item_name() const
492 * Set selection status.
494 * @param yn true if this item is currently selected
497 TimeAxisViewItem::set_selected(bool yn)
499 if (_selected != yn) {
500 Selectable::set_selected (yn);
505 /** @return the TimeAxisView that this item is on */
507 TimeAxisViewItem::get_time_axis_view () const
513 * Set the displayed item text.
514 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item.
516 * @param new_name the new name text to display
520 TimeAxisViewItem::set_name_text(const string& new_name)
526 last_item_width = trackview.editor().frame_to_pixel(item_duration);
527 name_pixbuf_width = pixel_width (new_name, NAME_FONT) + 2;
528 name_pixbuf->set (pixbuf_from_string(new_name, NAME_FONT, name_pixbuf_width, NAME_HEIGHT, Gdk::Color ("#000000")));
533 * Set the height of this item.
535 * @param h new height
538 TimeAxisViewItem::set_height (double height)
542 if (name_highlight) {
543 if (height < NAME_HIGHLIGHT_THRESH) {
544 name_highlight->hide ();
545 high_enough_for_name = false;
548 name_highlight->show();
549 high_enough_for_name = true;
552 if (height > NAME_HIGHLIGHT_SIZE) {
553 name_highlight->set_y0 ((double) height - 1 - NAME_HIGHLIGHT_SIZE);
554 name_highlight->set_y1 ((double) height - 1);
557 /* it gets hidden now anyway */
558 name_highlight->set_y0 (1);
559 name_highlight->set_y1 (height);
563 if (visibility & ShowNameText) {
564 name_pixbuf->set_y_position (height + 1 - NAME_Y_OFFSET);
568 frame->set_y1 (height - 1);
569 if (frame_handle_start) {
570 frame_handle_start->set_y1 (height - 1);
571 frame_handle_end->set_y1 (height - 1);
575 vestigial_frame->set_y1 (height - 1);
577 update_name_pixbuf_visibility ();
582 TimeAxisViewItem::set_color (Gdk::Color const & base_color)
584 compute_colors (base_color);
589 TimeAxisViewItem::get_canvas_frame()
595 TimeAxisViewItem::get_canvas_group()
601 TimeAxisViewItem::get_name_highlight()
603 return name_highlight;
606 ArdourCanvas::Pixbuf*
607 TimeAxisViewItem::get_name_pixbuf()
613 * Calculate some contrasting color for displaying various parts of this item, based upon the base color.
615 * @param color the base color of the item
618 TimeAxisViewItem::compute_colors (Gdk::Color const & base_color)
620 unsigned char radius;
625 /* FILL: this is simple */
626 r = base_color.get_red()/256;
627 g = base_color.get_green()/256;
628 b = base_color.get_blue()/256;
629 fill_color = RGBA_TO_UINT(r,g,b,160);
632 if the overall saturation is strong, make the minor colors light.
633 if its weak, make them dark.
635 we do this by moving an equal distance to the other side of the
636 central circle in the color wheel from where we started.
639 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f));
640 minor_shift = 125 - radius;
642 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
644 r = base_color.get_red()/256;
645 g = base_color.get_green()/256;
646 b = base_color.get_blue()/256;
652 /* red sector => green */
657 /* green sector => blue */
665 /* blue sector => red */
670 /* green sector => blue */
679 label_color = RGBA_TO_UINT(r,g,b,255);
680 r = (base_color.get_red()/256) + 127;
681 g = (base_color.get_green()/256) + 127;
682 b = (base_color.get_blue()/256) + 127;
684 label_color = RGBA_TO_UINT(r,g,b,255);
686 /* XXX can we do better than this ? */
690 //frame_color_r = 192;
691 //frame_color_g = 192;
692 //frame_color_b = 194;
694 //selected_frame_color_r = 182;
695 //selected_frame_color_g = 145;
696 //selected_frame_color_b = 168;
698 //handle_color_r = 25;
699 //handle_color_g = 0;
700 //handle_color_b = 255;
701 //lock_handle_color_r = 235;
702 //lock_handle_color_g = 16;
703 //lock_handle_color_b = 16;
707 * Convenience method to set the various canvas item colors
710 TimeAxisViewItem::set_colors()
714 if (name_highlight) {
715 name_highlight->set_fill_color (fill_color);
717 set_trim_handle_colors();
721 * Sets the frame color depending on whether this item is selected
724 TimeAxisViewItem::set_frame_color()
734 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
737 f = UINT_RGBA_CHANGE_A (f, fill_opacity);
741 f = UINT_RGBA_CHANGE_A (f, 0);
747 f = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
750 if (high_enough_for_name && !Config->get_color_regions_using_track_color()) {
751 f = ARDOUR_UI::config()->canvasvar_FrameBase.get();
757 f = UINT_RGBA_CHANGE_A (f, fill_opacity);
761 f = UINT_RGBA_CHANGE_A (f, 0);
766 frame->set_fill_color (f);
770 f = ARDOUR_UI::config()->canvasvar_SelectedTimeAxisFrame.get();
772 f = ARDOUR_UI::config()->canvasvar_TimeAxisFrame.get();
776 f = UINT_RGBA_CHANGE_A (f, 64);
779 frame->set_outline_color (f);
784 * Set the colors of the start and end trim handle depending on object state
787 TimeAxisViewItem::set_trim_handle_colors()
789 if (frame_handle_start) {
790 if (position_locked) {
791 frame_handle_start->set_fill_color (ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get());
792 frame_handle_end->set_fill_color (ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get());
794 frame_handle_start->set_fill_color (RGBA_TO_UINT (1, 1, 1, 0)); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
795 frame_handle_end->set_fill_color (RGBA_TO_UINT (1, 1, 1, 0)); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
800 /** @return the frames per pixel */
802 TimeAxisViewItem::get_frames_per_pixel () const
804 return frames_per_pixel;
807 /** Set the frames per pixel of this item.
808 * This item is used to determine the relative visual size and position of this item
809 * based upon its duration and start value.
811 * @param fpp the new frames per pixel
814 TimeAxisViewItem::set_frames_per_pixel (double fpp)
816 frames_per_pixel = fpp;
817 set_position (this->get_position(), this);
818 reset_width_dependent_items ((double) get_duration() / frames_per_pixel);
822 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
825 if (pixel_width < 2.0) {
827 if (show_vestigial) {
828 vestigial_frame->show();
831 if (name_highlight) {
832 name_highlight->hide();
839 if (frame_handle_start) {
840 frame_handle_start->hide();
841 frame_handle_end->hide();
844 wide_enough_for_name = false;
847 vestigial_frame->hide();
849 if (name_highlight) {
851 if (_height < NAME_HIGHLIGHT_THRESH) {
852 name_highlight->hide();
853 high_enough_for_name = false;
855 name_highlight->show();
856 if (!get_item_name().empty()) {
857 reset_name_width (pixel_width);
859 high_enough_for_name = true;
862 name_highlight->set_x1 (pixel_width);
867 frame->set_x1 (pixel_width);
870 if (frame_handle_start) {
871 if (pixel_width < (3 * TimeAxisViewItem::GRAB_HANDLE_WIDTH)) {
873 * there's less than GRAB_HANDLE_WIDTH of the region between
874 * the right-hand end of frame_handle_start and the left-hand
875 * end of frame_handle_end, so disable the handles
877 frame_handle_start->hide();
878 frame_handle_end->hide();
880 frame_handle_start->show();
881 frame_handle_end->set_x0 (pixel_width - (TimeAxisViewItem::GRAB_HANDLE_WIDTH));
882 frame_handle_end->set_x1 (pixel_width);
883 frame_handle_end->show();
887 wide_enough_for_name = true;
890 update_name_pixbuf_visibility ();
894 TimeAxisViewItem::reset_name_width (double /*pixel_width*/)
898 bool pixbuf_holds_full_name;
904 it_width = trackview.editor().frame_to_pixel(item_duration);
905 pb_width = name_pixbuf_width;
907 pixbuf_holds_full_name = last_item_width > pb_width + NAME_X_OFFSET;
908 last_item_width = it_width;
910 if (pixbuf_holds_full_name && (it_width >= pb_width + NAME_X_OFFSET)) {
912 we've previously had the full name length showing
913 and its still showing.
918 if (pb_width > it_width - NAME_X_OFFSET) {
919 pb_width = it_width - NAME_X_OFFSET;
922 if (it_width <= NAME_X_OFFSET) {
923 wide_enough_for_name = false;
925 wide_enough_for_name = true;
928 update_name_pixbuf_visibility ();
934 name_pixbuf->set (pixbuf_from_string(item_name, NAME_FONT, pb_width, NAME_HEIGHT, Gdk::Color ("#000000")));
938 * Callback used to remove this time axis item during the gtk idle loop.
939 * This is used to avoid deleting the obejct while inside the remove_this_item
942 * @param item the TimeAxisViewItem to remove.
943 * @param src the identity of the object that initiated the change.
946 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
948 item->ItemRemoved (item->get_item_name(), src); /* EMIT_SIGNAL */
955 TimeAxisViewItem::set_y (double y)
957 group->set_y_position (y);
961 TimeAxisViewItem::update_name_pixbuf_visibility ()
967 if (wide_enough_for_name && high_enough_for_name) {
968 name_pixbuf->show ();
970 name_pixbuf->hide ();
975 TimeAxisViewItem::parameter_changed (string p)
977 if (p == "color-regions-using-track-color") {