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>
22 #include <ardour/types.h>
23 #include <ardour/ardour.h>
25 #include <gtkmm2ext/utils.h>
27 #include "public_editor.h"
28 #include "time_axis_view_item.h"
29 #include "time_axis_view.h"
30 #include "simplerect.h"
32 #include "canvas_impl.h"
33 #include "rgb_macros.h"
38 using namespace Editing;
42 //------------------------------------------------------------------------------
43 /** Initialize const static memeber data */
45 Pango::FontDescription TimeAxisViewItem::NAME_FONT;
46 bool TimeAxisViewItem::have_name_font = false;
47 const double TimeAxisViewItem::NAME_X_OFFSET = 15.0;
48 const double TimeAxisViewItem::GRAB_HANDLE_LENGTH = 6 ;
50 double TimeAxisViewItem::NAME_Y_OFFSET;
51 double TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
52 double TimeAxisViewItem::NAME_HIGHLIGHT_THRESH;
55 //---------------------------------------------------------------------------------------//
56 // Constructor / Desctructor
59 * Constructs a new TimeAxisViewItem.
61 * @param it_name the unique name/Id of this item
62 * @param parant the parent canvas group
63 * @param tv the TimeAxisView we are going to be added to
64 * @param spu samples per unit
66 * @param start the start point of this item
67 * @param duration the duration of this item
69 TimeAxisViewItem::TimeAxisViewItem(const string & it_name, ArdourCanvas::Group& parent, TimeAxisView& tv, double spu, Gdk::Color& base_color,
70 nframes_t start, nframes_t duration,
74 if (!have_name_font) {
76 /* first constructed item sets up font info */
78 NAME_FONT = get_font_for_style (N_("TimeAxisViewItemName"));
84 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (X_("Hg")); /* ascender + descender */
88 layout->set_font_description (NAME_FONT);
89 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
91 NAME_Y_OFFSET = height + 6;
92 NAME_HIGHLIGHT_SIZE = height + 6;
93 NAME_HIGHLIGHT_THRESH = NAME_HIGHLIGHT_SIZE * 2;
95 have_name_font = true;
98 group = new ArdourCanvas::Group (parent);
100 init (it_name, spu, base_color, start, duration, vis);
104 TimeAxisViewItem::TimeAxisViewItem (const TimeAxisViewItem& other)
105 : trackview (other.trackview)
111 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
112 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
114 /* share the other's parent, but still create a new group */
116 Gnome::Canvas::Group* parent = other.group->property_parent();
118 group = new ArdourCanvas::Group (*parent);
120 init (other.item_name, other.samples_per_unit, c, other.frame_position, other.item_duration, other.visibility);
125 TimeAxisViewItem::init (const string& it_name, double spu, Gdk::Color& base_color, nframes_t start, nframes_t duration, Visibility vis)
127 item_name = it_name ;
128 samples_per_unit = spu ;
129 should_show_selection = true;
130 frame_position = start ;
131 item_duration = duration ;
132 name_connected = false;
134 position_locked = false ;
135 max_item_duration = ARDOUR::max_frames;
136 min_item_duration = 0 ;
137 show_vestigial = true;
141 warning << "Time Axis Item Duration == 0" << endl ;
144 vestigial_frame = new ArdourCanvas::SimpleRect (*group);
145 vestigial_frame->property_x1() = (double) 0.0;
146 vestigial_frame->property_y1() = (double) 1.0;
147 vestigial_frame->property_x2() = 2.0;
148 vestigial_frame->property_y2() = (double) trackview.height;
149 vestigial_frame->property_outline_color_rgba() = color_map[cVestigialFrameOutline];
150 vestigial_frame->property_fill_color_rgba() = color_map[cVestigialFrameFill];
151 vestigial_frame->hide ();
153 if (visibility & ShowFrame) {
154 frame = new ArdourCanvas::SimpleRect (*group);
155 frame->property_x1() = (double) 0.0;
156 frame->property_y1() = (double) 1.0;
157 frame->property_x2() = (double) trackview.editor.frame_to_pixel(duration);
158 frame->property_y2() = (double) trackview.height;
159 frame->property_outline_color_rgba() = color_map[cTimeAxisFrameOutline];
160 frame->property_fill_color_rgba() = color_map[cTimeAxisFrameFill];
162 /* by default draw all 4 edges */
164 uint32_t outline_what = 0x1|0x2|0x4|0x8;
166 if (visibility & HideFrameLeft) {
167 outline_what &= ~(0x1);
170 if (visibility & HideFrameRight) {
171 outline_what &= ~(0x2);
174 if (visibility & HideFrameTB) {
175 outline_what &= ~(0x4 | 0x8);
178 frame->property_outline_what() = outline_what;
184 if (visibility & ShowNameHighlight) {
185 name_highlight = new ArdourCanvas::SimpleRect (*group);
186 if (visibility & FullWidthNameHighlight) {
187 name_highlight->property_x1() = (double) 0.0;
188 name_highlight->property_x2() = (double) (trackview.editor.frame_to_pixel(item_duration));
190 name_highlight->property_x1() = (double) 1.0;
191 name_highlight->property_x2() = (double) (trackview.editor.frame_to_pixel(item_duration)) - 1;
193 name_highlight->property_y1() = (double) (trackview.height - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE);
194 name_highlight->property_y2() = (double) (trackview.height - 1);
195 name_highlight->property_outline_color_rgba() = color_map[cNameHighlightFill];
196 name_highlight->property_fill_color_rgba() = color_map[cNameHighlightOutline];
198 name_highlight->set_data ("timeaxisviewitem", this);
204 if (visibility & ShowNameText) {
205 name_text = new ArdourCanvas::Text (*group);
206 name_text->property_x() = (double) TimeAxisViewItem::NAME_X_OFFSET;
207 /* trackview.height is the bottom of the trackview. subtract 1 to get back to the bottom of the highlight,
208 then NAME_Y_OFFSET to position the text in the vertical center of the highlight
210 name_text->property_y() = (double) trackview.height - 1.0 - TimeAxisViewItem::NAME_Y_OFFSET;
211 name_text->property_font_desc() = NAME_FONT;
212 name_text->property_anchor() = Gtk::ANCHOR_NW;
214 name_text->set_data ("timeaxisviewitem", this);
220 /* create our grab handles used for trimming/duration etc */
222 if (visibility & ShowHandles) {
223 frame_handle_start = new ArdourCanvas::SimpleRect (*group);
224 frame_handle_start->property_x1() = (double) 0.0;
225 frame_handle_start->property_x2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH;
226 frame_handle_start->property_y1() = (double) 1.0;
227 frame_handle_start->property_y2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH+1;
228 frame_handle_start->property_outline_color_rgba() = color_map[cFrameHandleStartOutline];
229 frame_handle_start->property_fill_color_rgba() = color_map[cFrameHandleStartFill];
231 frame_handle_end = new ArdourCanvas::SimpleRect (*group);
232 frame_handle_end->property_x1() = (double) (trackview.editor.frame_to_pixel(get_duration())) - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
233 frame_handle_end->property_x2() = (double) trackview.editor.frame_to_pixel(get_duration());
234 frame_handle_end->property_y1() = (double) 1;
235 frame_handle_end->property_y2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH + 1;
236 frame_handle_end->property_outline_color_rgba() = color_map[cFrameHandleEndOutline];
237 frame_handle_end->property_fill_color_rgba() = color_map[cFrameHandleEndFill];
240 frame_handle_start = 0;
241 frame_handle_end = 0;
244 set_color (base_color) ;
246 set_duration (item_duration, this) ;
247 set_position (start, this) ;
253 TimeAxisViewItem::~TimeAxisViewItem()
259 //---------------------------------------------------------------------------------------//
260 // Position and duration Accessors/Mutators
263 * Set the position of this item upon the timeline to the specified value
265 * @param pos the new position
266 * @param src the identity of the object that initiated the change
267 * @return true if the position change was a success, false otherwise
270 TimeAxisViewItem::set_position(nframes_t pos, void* src, double* delta)
272 if (position_locked) {
276 frame_position = pos;
278 /* This sucks. The GnomeCanvas version I am using
279 doesn't correctly implement gnome_canvas_group_set_arg(),
280 so that simply setting the "x" arg of the group
281 fails to move the group. Instead, we have to
282 use gnome_canvas_item_move(), which does the right
283 thing. I see that in GNOME CVS, the current (Sept 2001)
284 version of GNOME Canvas rectifies this issue cleanly.
287 double old_unit_pos ;
288 double new_unit_pos = pos / samples_per_unit ;
290 old_unit_pos = group->property_x();
292 if (new_unit_pos != old_unit_pos) {
293 group->move (new_unit_pos - old_unit_pos, 0.0);
297 (*delta) = new_unit_pos - old_unit_pos;
300 PositionChanged (frame_position, src) ; /* EMIT_SIGNAL */
306 * Return the position of this item upon the timeline
308 * @return the position of this item
311 TimeAxisViewItem::get_position() const
313 return frame_position;
317 * Sets the duration of this item
319 * @param dur the new duration of this item
320 * @param src the identity of the object that initiated the change
321 * @return true if the duration change was succesful, false otherwise
324 TimeAxisViewItem::set_duration (nframes_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 */
345 * Returns the duration of this item
349 TimeAxisViewItem::get_duration() const
351 return (item_duration);
355 * Sets the maximum duration that this item make have.
357 * @param dur the new maximum duration
358 * @param src the identity of the object that initiated the change
361 TimeAxisViewItem::set_max_duration(nframes_t dur, void* src)
363 max_item_duration = dur ;
364 MaxDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
368 * Returns the maxmimum duration that this item may be set to
370 * @return the maximum duration that this item may be set to
373 TimeAxisViewItem::get_max_duration() const
375 return (max_item_duration) ;
379 * Sets the minimu duration that this item may be set to
381 * @param the minimum duration that this item may be set to
382 * @param src the identity of the object that initiated the change
385 TimeAxisViewItem::set_min_duration(nframes_t dur, void* src)
387 min_item_duration = dur ;
388 MinDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
392 * Returns the minimum duration that this item mey be set to
394 * @return the nimum duration that this item mey be set to
397 TimeAxisViewItem::get_min_duration() const
399 return(min_item_duration) ;
403 * Sets whether the position of this Item is locked to its current position
404 * Locked items cannot be moved until the item is unlocked again.
406 * @param yn set to true to lock this item to its current position
407 * @param src the identity of the object that initiated the change
410 TimeAxisViewItem::set_position_locked(bool yn, void* src)
412 position_locked = yn ;
413 set_trim_handle_colors() ;
414 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
418 * Returns whether this item is locked to its current position
420 * @return true if this item is locked to its current posotion
424 TimeAxisViewItem::get_position_locked() const
426 return (position_locked);
430 * Sets whether the Maximum Duration constraint is active and should be enforced
432 * @param active set true to enforce the max duration constraint
433 * @param src the identity of the object that initiated the change
436 TimeAxisViewItem::set_max_duration_active(bool active, void* src)
438 max_duration_active = active ;
442 * Returns whether the Maximum Duration constraint is active and should be enforced
444 * @return true if the maximum duration constraint is active, false otherwise
447 TimeAxisViewItem::get_max_duration_active() const
449 return(max_duration_active) ;
453 * Sets whether the Minimum Duration constraint is active and should be enforced
455 * @param active set true to enforce the min duration constraint
456 * @param src the identity of the object that initiated the change
459 TimeAxisViewItem::set_min_duration_active(bool active, void* src)
461 min_duration_active = active ;
465 * Returns whether the Maximum Duration constraint is active and should be enforced
467 * @return true if the maximum duration constraint is active, false otherwise
470 TimeAxisViewItem::get_min_duration_active() const
472 return(min_duration_active) ;
475 //---------------------------------------------------------------------------------------//
476 // Name/Id Accessors/Mutators
479 * Set the name/Id of this item.
481 * @param new_name the new name of this item
482 * @param src the identity of the object that initiated the change
485 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
487 if (new_name != item_name) {
488 std::string temp_name = item_name ;
489 item_name = new_name ;
490 NameChanged (item_name, temp_name, src) ; /* EMIT_SIGNAL */
495 * Returns the name/id of this item
497 * @return the name/id of this item
500 TimeAxisViewItem::get_item_name() const
505 //---------------------------------------------------------------------------------------//
509 * Set to true to indicate that this item is currently selected
511 * @param yn true if this item is currently selected
512 * @param src the identity of the object that initiated the change
515 TimeAxisViewItem::set_selected(bool yn)
517 if (_selected != yn) {
518 Selectable::set_selected (yn);
524 TimeAxisViewItem::set_should_show_selection (bool yn)
526 if (should_show_selection != yn) {
527 should_show_selection = yn;
532 //---------------------------------------------------------------------------------------//
533 // Parent Componenet Methods
536 * Returns the TimeAxisView that this item is upon
538 * @return the timeAxisView that this item is placed upon
541 TimeAxisViewItem::get_time_axis_view()
545 //---------------------------------------------------------------------------------------//
549 * Sets the displayed item text
550 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item
552 * @param new_name the new name text to display
555 TimeAxisViewItem::set_name_text(std::string new_name)
558 name_text->property_text() = new_name.c_str();
563 * Set the height of this item
565 * @param h the new height
568 TimeAxisViewItem::set_height(double height)
570 if (name_highlight) {
571 if (height < NAME_HIGHLIGHT_THRESH) {
572 name_highlight->hide();
577 name_highlight->show();
583 if (height > NAME_HIGHLIGHT_SIZE) {
584 name_highlight->property_y1() = (double) height+1 - NAME_HIGHLIGHT_SIZE;
585 name_highlight->property_y2() = (double) height;
588 /* it gets hidden now anyway */
589 name_highlight->property_y1() = (double) 1.0;
590 name_highlight->property_y2() = (double) height;
595 name_text->property_y() = height+1 - NAME_Y_OFFSET;
596 if (height < NAME_HIGHLIGHT_THRESH) {
597 name_text->property_fill_color_rgba() = fill_color;
600 name_text->property_fill_color_rgba() = label_color;
605 frame->property_y2() = height+1;
608 vestigial_frame->property_y2() = height+1;
615 TimeAxisViewItem::set_color(Gdk::Color& base_color)
617 compute_colors (base_color);
625 TimeAxisViewItem::get_canvas_frame()
634 TimeAxisViewItem::get_canvas_group()
643 TimeAxisViewItem::get_name_highlight()
645 return (name_highlight) ;
652 TimeAxisViewItem::get_name_text()
658 * Calculates some contrasting color for displaying various parts of this item, based upon the base color
660 * @param color the base color of the item
663 TimeAxisViewItem::compute_colors(Gdk::Color& base_color)
665 unsigned char radius ;
668 unsigned char r,g,b ;
670 /* FILL: this is simple */
671 r = base_color.get_red()/256 ;
672 g = base_color.get_green()/256 ;
673 b = base_color.get_blue()/256 ;
674 fill_color = RGBA_TO_UINT(r,g,b,255) ;
677 if the overall saturation is strong, make the minor colors light.
678 if its weak, make them dark.
680 we do this by moving an equal distance to the other side of the
681 central circle in the color wheel from where we started.
684 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f)) ;
685 minor_shift = 125 - radius ;
687 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
689 r = base_color.get_red()/256;
690 g = base_color.get_green()/256;
691 b = base_color.get_blue()/256;
697 /* red sector => green */
702 /* green sector => blue */
710 /* blue sector => red */
715 /* green sector => blue */
724 label_color = RGBA_TO_UINT(r,g,b,255);
725 r = (base_color.get_red()/256) + 127 ;
726 g = (base_color.get_green()/256) + 127 ;
727 b = (base_color.get_blue()/256) + 127 ;
729 label_color = RGBA_TO_UINT(r,g,b,255);
731 /* XXX can we do better than this ? */
732 /* We're trying ;) */
735 //frame_color_r = 192;
736 //frame_color_g = 192;
737 //frame_color_b = 194;
739 //selected_frame_color_r = 182;
740 //selected_frame_color_g = 145;
741 //selected_frame_color_b = 168;
743 //handle_color_r = 25 ;
744 //handle_color_g = 0 ;
745 //handle_color_b = 255 ;
746 //lock_handle_color_r = 235 ;
747 //lock_handle_color_g = 16;
748 //lock_handle_color_b = 16;
752 * Convenience method to set the various canvas item colors
755 TimeAxisViewItem::set_colors()
759 double height = NAME_HIGHLIGHT_THRESH;
762 height = frame->property_y2();
765 if (height < NAME_HIGHLIGHT_THRESH) {
766 name_text->property_fill_color_rgba() = fill_color;
769 name_text->property_fill_color_rgba() = label_color;
773 if (name_highlight) {
774 name_highlight->property_fill_color_rgba() = fill_color;
775 name_highlight->property_outline_color_rgba() = fill_color;
777 set_trim_handle_colors() ;
781 * Sets the frame color depending on whether this item is selected
784 TimeAxisViewItem::set_frame_color()
789 if (_selected && should_show_selection) {
790 UINT_TO_RGBA(color_map[cSelectedFrameBase], &r, &g, &b, &a);
791 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity);
793 UINT_TO_RGBA(color_map[cFrameBase], &r, &g, &b, &a);
794 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity);
800 * Sets the colors of the start and end trim handle depending on object state
804 TimeAxisViewItem::set_trim_handle_colors()
806 if (frame_handle_start) {
807 if (position_locked) {
808 frame_handle_start->property_fill_color_rgba() = color_map[cTrimHandleLockedStart];
809 frame_handle_end->property_fill_color_rgba() = color_map[cTrimHandleLockedEnd];
811 frame_handle_start->property_fill_color_rgba() = color_map[cTrimHandleStart];
812 frame_handle_end->property_fill_color_rgba() = color_map[cTrimHandleEnd];
818 TimeAxisViewItem::get_samples_per_unit()
820 return(samples_per_unit) ;
824 TimeAxisViewItem::set_samples_per_unit (double spu)
826 samples_per_unit = spu ;
827 set_position (this->get_position(), this);
828 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
832 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
834 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
836 if (frame_handle_start) {
837 frame_handle_start->hide();
838 frame_handle_end->hide();
841 } if (pixel_width < 2.0) {
843 if (show_vestigial) {
844 vestigial_frame->show();
847 if (name_highlight) {
848 name_highlight->hide();
858 if (frame_handle_start) {
859 frame_handle_start->hide();
860 frame_handle_end->hide();
864 vestigial_frame->hide();
866 if (name_highlight) {
868 double height = name_highlight->property_y2 ();
870 if (height < NAME_HIGHLIGHT_THRESH) {
871 name_highlight->hide();
876 name_highlight->show();
877 if (name_text && !get_item_name().empty()) {
879 reset_name_width (pixel_width);
883 if (visibility & FullWidthNameHighlight) {
884 name_highlight->property_x2() = pixel_width;
886 name_highlight->property_x2() = pixel_width - 1.0;
893 frame->property_x2() = pixel_width;
896 if (frame_handle_start) {
897 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
898 frame_handle_start->hide();
899 frame_handle_end->hide();
901 frame_handle_start->show();
902 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
903 frame_handle_end->show();
904 frame_handle_end->property_x2() = pixel_width;
910 TimeAxisViewItem::reset_name_width (double pixel_width)
912 if (name_text == 0) {
918 ustring ustr = fit_to_pixels (item_name, (int) floor (pixel_width - NAME_X_OFFSET), NAME_FONT, width);
926 /* don't use name for event handling if it leaves no room
927 for trimming to work.
930 if (pixel_width - width < (NAME_X_OFFSET * 2.0)) {
931 if (name_connected) {
932 name_connected = false;
935 if (!name_connected) {
936 name_connected = true;
940 name_text->property_text() = ustr;
946 //---------------------------------------------------------------------------------------//
947 // Handle time axis removal
950 * Handles the Removal of this time axis item
951 * This _needs_ to be called to alert others of the removal properly, ie where the source
952 * of the removal came from.
954 * XXX Although im not too happy about this method of doing things, I cant think of a cleaner method
955 * just now to capture the source of the removal
957 * @param src the identity of the object that initiated the change
960 TimeAxisViewItem::remove_this_item(void* src)
963 defer to idle loop, otherwise we'll delete this object
964 while we're still inside this function ...
966 Glib::signal_idle().connect(bind (sigc::ptr_fun (&TimeAxisViewItem::idle_remove_this_item), this, src));
970 * Callback used to remove this time axis item during the gtk idle loop
971 * This is used to avoid deleting the obejct while inside the remove_this_item
974 * @param item the TimeAxisViewItem to remove
975 * @param src the identity of the object that initiated the change
978 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
980 item->ItemRemoved (item->get_item_name(), src) ; /* EMIT_SIGNAL */