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;
50 using namespace Gtkmm2ext;
52 Pango::FontDescription* TimeAxisViewItem::NAME_FONT = 0;
53 const double TimeAxisViewItem::NAME_X_OFFSET = 15.0;
54 const double TimeAxisViewItem::GRAB_HANDLE_LENGTH = 6;
56 int TimeAxisViewItem::NAME_HEIGHT;
57 double TimeAxisViewItem::NAME_Y_OFFSET;
58 double TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
59 double TimeAxisViewItem::NAME_HIGHLIGHT_THRESH;
62 TimeAxisViewItem::set_constant_heights ()
64 NAME_FONT = get_font_for_style (X_("TimeAxisViewItemName"));
70 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (X_("Hg")); /* ascender + descender */
74 layout->set_font_description (*NAME_FONT);
75 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
78 NAME_Y_OFFSET = height + 3;
79 NAME_HIGHLIGHT_SIZE = height + 2;
80 NAME_HIGHLIGHT_THRESH = NAME_HIGHLIGHT_SIZE * 3;
84 * Construct a new TimeAxisViewItem.
86 * @param it_name the unique name of this item
87 * @param parent the parent canvas group
88 * @param tv the TimeAxisView we are going to be added to
89 * @param spu samples per unit
91 * @param start the start point of this item
92 * @param duration the duration of this item
93 * @param recording true if this is a recording region view
94 * @param automation true if this is an automation region view
96 TimeAxisViewItem::TimeAxisViewItem(
97 const string & it_name, ArdourCanvas::Group& parent, TimeAxisView& tv, double spu, Gdk::Color const & base_color,
98 framepos_t start, framecnt_t duration, bool recording, bool automation, Visibility vis
102 , _recregion (recording)
103 , _automation (automation)
105 group = new ArdourCanvas::Group (parent);
107 init (it_name, spu, base_color, start, duration, vis, true, true);
110 TimeAxisViewItem::TimeAxisViewItem (const TimeAxisViewItem& other)
111 : sigc::trackable(other)
112 , PBD::ScopedConnectionList()
113 , trackview (other.trackview)
114 , _recregion (other._recregion)
115 , _automation (other._automation)
121 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
122 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
124 /* share the other's parent, but still create a new group */
126 Gnome::Canvas::Group* parent = other.group->property_parent();
128 group = new ArdourCanvas::Group (*parent);
130 _selected = other._selected;
133 other.item_name, other.samples_per_unit, c, other.frame_position,
134 other.item_duration, other.visibility, other.wide_enough_for_name, other.high_enough_for_name
139 TimeAxisViewItem::init (
140 const string& it_name, double spu, Gdk::Color const & base_color, framepos_t start, framepos_t duration, Visibility vis, bool wide, bool high)
143 samples_per_unit = spu;
144 should_show_selection = true;
145 frame_position = start;
146 item_duration = duration;
147 name_connected = false;
149 position_locked = false;
150 max_item_duration = ARDOUR::max_framepos;
151 min_item_duration = 0;
152 show_vestigial = true;
155 name_pixbuf_width = 0;
157 wide_enough_for_name = wide;
158 high_enough_for_name = high;
161 warning << "Time Axis Item Duration == 0" << endl;
164 vestigial_frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, 2.0, trackview.current_height());
165 vestigial_frame->hide ();
166 vestigial_frame->property_outline_what() = 0xF;
167 vestigial_frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
168 vestigial_frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_VestigialFrame.get();
170 if (visibility & ShowFrame) {
171 frame = new ArdourCanvas::SimpleRect (*group, 0.0, 1.0, trackview.editor().frame_to_pixel(duration), trackview.current_height());
173 frame->property_outline_pixels() = 1;
174 frame->property_outline_what() = 0xF;
177 frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
179 frame->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeAxisFrame.get();
182 frame->property_outline_what() = 0x1|0x2|0x4|0x8;
188 if (visibility & ShowNameHighlight) {
190 if (visibility & FullWidthNameHighlight) {
191 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());
193 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());
196 name_highlight->set_data ("timeaxisviewitem", this);
197 name_highlight->property_outline_what() = 0x4;
198 /* we should really use a canvas color property here */
199 name_highlight->property_outline_color_rgba() = RGBA_TO_UINT (0,0,0,255);
205 if (visibility & ShowNameText) {
206 name_pixbuf = new ArdourCanvas::Pixbuf(*group);
207 name_pixbuf->property_x() = NAME_X_OFFSET;
208 name_pixbuf->property_y() = trackview.current_height() + 1 - NAME_Y_OFFSET;
214 /* create our grab handles used for trimming/duration etc */
215 if (!_recregion && !_automation) {
216 frame_handle_start = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
217 frame_handle_start->property_outline_what() = 0x0;
218 frame_handle_end = new ArdourCanvas::SimpleRect (*group, 0.0, TimeAxisViewItem::GRAB_HANDLE_LENGTH, 5.0, trackview.current_height());
219 frame_handle_end->property_outline_what() = 0x0;
221 frame_handle_start = frame_handle_end = 0;
224 set_color (base_color);
226 set_duration (item_duration, this);
227 set_position (start, this);
230 TimeAxisViewItem::~TimeAxisViewItem()
237 * Set the position of this item on the timeline.
239 * @param pos the new position
240 * @param src the identity of the object that initiated the change
241 * @return true on success
245 TimeAxisViewItem::set_position(framepos_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.
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 */
280 /** @return position of this item on the timeline */
282 TimeAxisViewItem::get_position() const
284 return frame_position;
288 * Set the duration of this item.
290 * @param dur the new duration of this item
291 * @param src the identity of the object that initiated the change
292 * @return true on success
296 TimeAxisViewItem::set_duration (framepos_t dur, void* src)
298 if ((dur > max_item_duration) || (dur < min_item_duration)) {
299 warning << string_compose (_("new duration %1 frames is out of bounds for %2"), get_item_name(), dur)
310 reset_width_dependent_items (trackview.editor().frame_to_pixel (dur));
312 DurationChanged (dur, src); /* EMIT_SIGNAL */
316 /** @return duration of this item */
318 TimeAxisViewItem::get_duration() const
320 return item_duration;
324 * Set the maximum duration that this item can have.
326 * @param dur the new maximum duration
327 * @param src the identity of the object that initiated the change
330 TimeAxisViewItem::set_max_duration(framecnt_t dur, void* src)
332 max_item_duration = dur;
333 MaxDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
336 /** @return the maximum duration that this item may have */
338 TimeAxisViewItem::get_max_duration() const
340 return max_item_duration;
344 * Set the minimum duration that this item may have.
346 * @param the minimum duration that this item may be set to
347 * @param src the identity of the object that initiated the change
350 TimeAxisViewItem::set_min_duration(framecnt_t dur, void* src)
352 min_item_duration = dur;
353 MinDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
356 /** @return the minimum duration that this item mey have */
358 TimeAxisViewItem::get_min_duration() const
360 return min_item_duration;
364 * Set whether this item is locked to its current position.
365 * Locked items cannot be moved until the item is unlocked again.
367 * @param yn true to lock this item to its current position
368 * @param src the identity of the object that initiated the change
371 TimeAxisViewItem::set_position_locked(bool yn, void* src)
373 position_locked = yn;
374 set_trim_handle_colors();
375 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
378 /** @return true if this item is locked to its current position */
380 TimeAxisViewItem::get_position_locked() const
382 return position_locked;
386 * Set whether the maximum duration constraint is active.
388 * @param active set true to enforce the max duration constraint
389 * @param src the identity of the object that initiated the change
392 TimeAxisViewItem::set_max_duration_active (bool active, void* /*src*/)
394 max_duration_active = active;
397 /** @return true if the maximum duration constraint is active */
399 TimeAxisViewItem::get_max_duration_active() const
401 return max_duration_active;
405 * Set whether the minimum duration constraint is active.
407 * @param active set true to enforce the min duration constraint
408 * @param src the identity of the object that initiated the change
412 TimeAxisViewItem::set_min_duration_active (bool active, void* /*src*/)
414 min_duration_active = active;
417 /** @return true if the maximum duration constraint is active */
419 TimeAxisViewItem::get_min_duration_active() const
421 return min_duration_active;
425 * Set the name of this item.
427 * @param new_name the new name of this item
428 * @param src the identity of the object that initiated the change
432 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
434 if (new_name != item_name) {
435 std::string temp_name = item_name;
436 item_name = new_name;
437 NameChanged (item_name, temp_name, src); /* EMIT_SIGNAL */
441 /** @return the name of this item */
443 TimeAxisViewItem::get_item_name() const
449 * Set selection status.
451 * @param yn true if this item is currently selected
454 TimeAxisViewItem::set_selected(bool yn)
456 if (_selected != yn) {
457 Selectable::set_selected (yn);
463 * Set whether an item should show its selection status.
465 * @param yn true if this item should show its selected status
469 TimeAxisViewItem::set_should_show_selection (bool yn)
471 if (should_show_selection != yn) {
472 should_show_selection = yn;
477 /** @return the TimeAxisView that this item is on */
479 TimeAxisViewItem::get_time_axis_view () const
485 * Set the displayed item text.
486 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item.
488 * @param new_name the new name text to display
492 TimeAxisViewItem::set_name_text(const string& new_name)
498 last_item_width = trackview.editor().frame_to_pixel(item_duration);
499 name_pixbuf_width = pixel_width (new_name, *NAME_FONT) + 2;
500 name_pixbuf->property_pixbuf() = pixbuf_from_string(new_name, NAME_FONT, name_pixbuf_width, NAME_HEIGHT, Gdk::Color ("#000000"));
505 * Set the height of this item.
507 * @param h new height
510 TimeAxisViewItem::set_height (double height)
514 if (name_highlight) {
515 if (height < NAME_HIGHLIGHT_THRESH) {
516 name_highlight->hide ();
517 high_enough_for_name = false;
520 name_highlight->show();
521 high_enough_for_name = true;
524 if (height > NAME_HIGHLIGHT_SIZE) {
525 name_highlight->property_y1() = (double) height - 1 - NAME_HIGHLIGHT_SIZE;
526 name_highlight->property_y2() = (double) height - 1;
529 /* it gets hidden now anyway */
530 name_highlight->property_y1() = (double) 1.0;
531 name_highlight->property_y2() = (double) height;
535 if (visibility & ShowNameText) {
536 name_pixbuf->property_y() = height + 1 - NAME_Y_OFFSET;
540 frame->property_y2() = height - 1;
541 if (frame_handle_start) {
542 frame_handle_start->property_y2() = height - 1;
543 frame_handle_end->property_y2() = height - 1;
547 vestigial_frame->property_y2() = height - 1;
549 update_name_pixbuf_visibility ();
553 TimeAxisViewItem::set_color (Gdk::Color const & base_color)
555 compute_colors (base_color);
560 TimeAxisViewItem::get_canvas_frame()
566 TimeAxisViewItem::get_canvas_group()
572 TimeAxisViewItem::get_name_highlight()
574 return name_highlight;
577 ArdourCanvas::Pixbuf*
578 TimeAxisViewItem::get_name_pixbuf()
584 * Calculate some contrasting color for displaying various parts of this item, based upon the base color.
586 * @param color the base color of the item
589 TimeAxisViewItem::compute_colors (Gdk::Color const & base_color)
591 unsigned char radius;
596 /* FILL: this is simple */
597 r = base_color.get_red()/256;
598 g = base_color.get_green()/256;
599 b = base_color.get_blue()/256;
600 fill_color = RGBA_TO_UINT(r,g,b,160);
603 if the overall saturation is strong, make the minor colors light.
604 if its weak, make them dark.
606 we do this by moving an equal distance to the other side of the
607 central circle in the color wheel from where we started.
610 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f));
611 minor_shift = 125 - radius;
613 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
615 r = base_color.get_red()/256;
616 g = base_color.get_green()/256;
617 b = base_color.get_blue()/256;
623 /* red sector => green */
628 /* green sector => blue */
636 /* blue sector => red */
641 /* green sector => blue */
650 label_color = RGBA_TO_UINT(r,g,b,255);
651 r = (base_color.get_red()/256) + 127;
652 g = (base_color.get_green()/256) + 127;
653 b = (base_color.get_blue()/256) + 127;
655 label_color = RGBA_TO_UINT(r,g,b,255);
657 /* XXX can we do better than this ? */
661 //frame_color_r = 192;
662 //frame_color_g = 192;
663 //frame_color_b = 194;
665 //selected_frame_color_r = 182;
666 //selected_frame_color_g = 145;
667 //selected_frame_color_b = 168;
669 //handle_color_r = 25;
670 //handle_color_g = 0;
671 //handle_color_b = 255;
672 //lock_handle_color_r = 235;
673 //lock_handle_color_g = 16;
674 //lock_handle_color_b = 16;
678 * Convenience method to set the various canvas item colors
681 TimeAxisViewItem::set_colors()
685 if (name_highlight) {
686 name_highlight->property_fill_color_rgba() = fill_color;
688 set_trim_handle_colors();
692 * Sets the frame color depending on whether this item is selected
695 TimeAxisViewItem::set_frame_color()
700 if (_selected && should_show_selection) {
701 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get(), &r, &g, &b, &a);
702 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
705 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_RecordingRect.get(), &r, &g, &b, &a);
706 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, a);
708 UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_FrameBase.get(), &r, &g, &b, &a);
709 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity ? fill_opacity : a);
716 * Set the colors of the start and end trim handle depending on object state
719 TimeAxisViewItem::set_trim_handle_colors()
721 if (frame_handle_start) {
722 if (position_locked) {
723 frame_handle_start->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
724 frame_handle_end->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TrimHandleLocked.get();
726 frame_handle_start->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
727 frame_handle_end->property_fill_color_rgba() = RGBA_TO_UINT(1, 1, 1, 0); //ARDOUR_UI::config()->canvasvar_TrimHandle.get();
732 /** @return the samples per unit of this item */
734 TimeAxisViewItem::get_samples_per_unit()
736 return samples_per_unit;
740 * Set the samples per unit of this item.
741 * This item is used to determine the relative visual size and position of this item
742 * based upon its duration and start value.
744 * @param spu the new samples per unit value
747 TimeAxisViewItem::set_samples_per_unit (double spu)
749 samples_per_unit = spu;
750 set_position (this->get_position(), this);
751 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
755 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
757 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
759 if (frame_handle_start) {
760 frame_handle_start->hide();
761 frame_handle_end->hide();
766 if (pixel_width < 2.0) {
768 if (show_vestigial) {
769 vestigial_frame->show();
772 if (name_highlight) {
773 name_highlight->hide();
780 if (frame_handle_start) {
781 frame_handle_start->hide();
782 frame_handle_end->hide();
785 wide_enough_for_name = false;
788 vestigial_frame->hide();
790 if (name_highlight) {
792 if (_height < NAME_HIGHLIGHT_THRESH) {
793 name_highlight->hide();
794 high_enough_for_name = false;
796 name_highlight->show();
797 if (!get_item_name().empty()) {
798 reset_name_width (pixel_width);
800 high_enough_for_name = true;
803 name_highlight->property_x2() = pixel_width;
808 frame->property_x2() = pixel_width;
811 if (frame_handle_start) {
812 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
813 frame_handle_start->hide();
814 frame_handle_end->hide();
816 frame_handle_start->show();
817 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
818 frame_handle_end->show();
819 frame_handle_end->property_x2() = pixel_width;
823 update_name_pixbuf_visibility ();
827 TimeAxisViewItem::reset_name_width (double /*pixel_width*/)
831 bool pixbuf_holds_full_name;
837 it_width = trackview.editor().frame_to_pixel(item_duration);
838 pb_width = name_pixbuf_width;
840 pixbuf_holds_full_name = last_item_width > pb_width + NAME_X_OFFSET;
841 last_item_width = it_width;
843 if (pixbuf_holds_full_name && (it_width >= pb_width + NAME_X_OFFSET)) {
845 we've previously had the full name length showing
846 and its still showing.
851 if (pb_width > it_width - NAME_X_OFFSET) {
852 pb_width = it_width - NAME_X_OFFSET;
855 if (it_width <= NAME_X_OFFSET) {
856 wide_enough_for_name = false;
858 wide_enough_for_name = true;
861 update_name_pixbuf_visibility ();
863 name_pixbuf->property_pixbuf() = pixbuf_from_string(item_name, NAME_FONT, pb_width, NAME_HEIGHT, Gdk::Color ("#000000"));
868 * Callback used to remove this time axis item during the gtk idle loop.
869 * This is used to avoid deleting the obejct while inside the remove_this_item
872 * @param item the TimeAxisViewItem to remove.
873 * @param src the identity of the object that initiated the change.
876 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
878 item->ItemRemoved (item->get_item_name(), src); /* EMIT_SIGNAL */
885 TimeAxisViewItem::set_y (double y)
887 double const old = group->property_y ();
889 group->move (0, y - old);
894 TimeAxisViewItem::update_name_pixbuf_visibility ()
900 if (wide_enough_for_name && high_enough_for_name) {
901 name_pixbuf->show ();
903 name_pixbuf->hide ();