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.
21 #include <pbd/error.h>
23 #include <ardour/types.h>
24 #include <ardour/ardour.h>
26 #include <gtkmm2ext/utils.h>
28 #include "public_editor.h"
29 #include "time_axis_view_item.h"
30 #include "time_axis_view.h"
31 #include "simplerect.h"
33 #include "canvas_impl.h"
34 #include "rgb_macros.h"
39 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 jack_nframes_t start, jack_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 + 4;
92 NAME_HIGHLIGHT_SIZE = height + 6;
93 NAME_HIGHLIGHT_THRESH = NAME_HIGHLIGHT_SIZE * 2;
95 have_name_font = true;
99 samples_per_unit = spu ;
100 should_show_selection = true;
101 frame_position = start ;
102 item_duration = duration ;
103 name_connected = false;
105 position_locked = false ;
106 max_item_duration = ARDOUR::max_frames;
107 min_item_duration = 0 ;
108 show_vestigial = true;
112 warning << "Time Axis Item Duration == 0" << endl ;
115 group = new ArdourCanvas::Group (parent);
117 vestigial_frame = new ArdourCanvas::SimpleRect (*group);
118 vestigial_frame->property_x1() = (double) 0.0;
119 vestigial_frame->property_y1() = (double) 1.0;
120 vestigial_frame->property_x2() = 2.0;
121 vestigial_frame->property_y2() = (double) trackview.height;
122 vestigial_frame->property_outline_color_rgba() = color_map[cVestigialFrameOutline];
123 vestigial_frame->property_fill_color_rgba() = color_map[cVestigialFrameFill];
124 vestigial_frame->hide ();
126 if (visibility & ShowFrame) {
127 frame = new ArdourCanvas::SimpleRect (*group);
128 frame->property_x1() = (double) 0.0;
129 frame->property_y1() = (double) 1.0;
130 frame->property_x2() = (double) trackview.editor.frame_to_pixel(duration);
131 frame->property_y2() = (double) trackview.height;
132 frame->property_outline_color_rgba() = color_map[cTimeAxisFrameOutline];
133 frame->property_fill_color_rgba() = color_map[cTimeAxisFrameFill];
135 /* by default draw all 4 edges */
137 uint32_t outline_what = 0x1|0x2|0x4|0x8;
139 if (visibility & HideFrameLR) {
140 outline_what &= ~(0x1 | 0x2);
143 if (visibility & HideFrameTB) {
144 outline_what &= ~(0x4 | 0x8);
147 frame->property_outline_what() = outline_what;
153 if (visibility & ShowNameHighlight) {
154 name_highlight = new ArdourCanvas::SimpleRect (*group);
155 if (visibility & FullWidthNameHighlight) {
156 name_highlight->property_x1() = (double) 0.0;
157 name_highlight->property_x2() = (double) (trackview.editor.frame_to_pixel(item_duration));
159 name_highlight->property_x1() = (double) 1.0;
160 name_highlight->property_x2() = (double) (trackview.editor.frame_to_pixel(item_duration)) - 1;
162 name_highlight->property_y1() = (double) (trackview.height - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE);
163 name_highlight->property_y2() = (double) (trackview.height - 1);
164 name_highlight->property_outline_color_rgba() = color_map[cNameHighlightFill];
165 name_highlight->property_fill_color_rgba() = color_map[cNameHighlightOutline];
167 name_highlight->set_data ("timeaxisviewitem", this);
173 if (visibility & ShowNameText) {
174 name_text = new ArdourCanvas::Text (*group);
175 name_text->property_x() = (double) TimeAxisViewItem::NAME_X_OFFSET;
176 /* trackview.height is the bottom of the trackview. subtract 1 to get back to the bottom of the highlight,
177 then NAME_Y_OFFSET to position the text in the vertical center of the highlight
179 name_text->property_y() = (double) trackview.height - 1.0 - TimeAxisViewItem::NAME_Y_OFFSET;
180 name_text->property_font_desc() = NAME_FONT;
181 name_text->property_anchor() = Gtk::ANCHOR_NW;
183 name_text->set_data ("timeaxisviewitem", this);
189 /* create our grab handles used for trimming/duration etc */
191 if (visibility & ShowHandles) {
192 frame_handle_start = new ArdourCanvas::SimpleRect (*group);
193 frame_handle_start->property_x1() = (double) 0.0;
194 frame_handle_start->property_x2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH;
195 frame_handle_start->property_y1() = (double) 1.0;
196 frame_handle_start->property_y2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH+1;
197 frame_handle_start->property_outline_color_rgba() = color_map[cFrameHandleStartOutline];
198 frame_handle_start->property_fill_color_rgba() = color_map[cFrameHandleStartFill];
200 frame_handle_end = new ArdourCanvas::SimpleRect (*group);
201 frame_handle_end->property_x1() = (double) (trackview.editor.frame_to_pixel(get_duration())) - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
202 frame_handle_end->property_x2() = (double) trackview.editor.frame_to_pixel(get_duration());
203 frame_handle_end->property_y1() = (double) 1;
204 frame_handle_end->property_y2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH + 1;
205 frame_handle_end->property_outline_color_rgba() = color_map[cFrameHandleEndOutline];
206 frame_handle_end->property_fill_color_rgba() = color_map[cFrameHandleEndFill];
209 frame_handle_start = 0;
210 frame_handle_end = 0;
213 set_color (base_color) ;
215 set_duration (item_duration, this) ;
216 set_position (start, this) ;
223 TimeAxisViewItem::~TimeAxisViewItem()
229 //---------------------------------------------------------------------------------------//
230 // Position and duration Accessors/Mutators
233 * Set the position of this item upon the timeline to the specified value
235 * @param pos the new position
236 * @param src the identity of the object that initiated the change
237 * @return true if the position change was a success, false otherwise
240 TimeAxisViewItem::set_position(jack_nframes_t pos, void* src, double* delta)
242 if (position_locked) {
246 frame_position = pos;
248 /* This sucks. The GnomeCanvas version I am using
249 doesn't correctly implement gnome_canvas_group_set_arg(),
250 so that simply setting the "x" arg of the group
251 fails to move the group. Instead, we have to
252 use gnome_canvas_item_move(), which does the right
253 thing. I see that in GNOME CVS, the current (Sept 2001)
254 version of GNOME Canvas rectifies this issue cleanly.
257 double old_unit_pos ;
258 double new_unit_pos = pos / samples_per_unit ;
260 old_unit_pos = group->property_x();
262 if (new_unit_pos != old_unit_pos) {
263 group->move (new_unit_pos - old_unit_pos, 0.0);
267 (*delta) = new_unit_pos - old_unit_pos;
270 PositionChanged (frame_position, src) ; /* EMIT_SIGNAL */
276 * Return the position of this item upon the timeline
278 * @return the position of this item
281 TimeAxisViewItem::get_position() const
283 return frame_position;
287 * Sets the duration of this item
289 * @param dur the new duration of this item
290 * @param src the identity of the object that initiated the change
291 * @return true if the duration change was succesful, false otherwise
294 TimeAxisViewItem::set_duration (jack_nframes_t dur, void* src)
296 if ((dur > max_item_duration) || (dur < min_item_duration)) {
297 warning << string_compose (_("new duration %1 frames is out of bounds for %2"), get_item_name(), dur)
308 double pixel_width = trackview.editor.frame_to_pixel (dur);
310 reset_width_dependent_items (pixel_width);
312 DurationChanged (dur, src) ; /* EMIT_SIGNAL */
317 * Returns the duration of this item
321 TimeAxisViewItem::get_duration() const
323 return (item_duration);
327 * Sets the maximum duration that this item make have.
329 * @param dur the new maximum duration
330 * @param src the identity of the object that initiated the change
333 TimeAxisViewItem::set_max_duration(jack_nframes_t dur, void* src)
335 max_item_duration = dur ;
336 MaxDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
340 * Returns the maxmimum duration that this item may be set to
342 * @return the maximum duration that this item may be set to
345 TimeAxisViewItem::get_max_duration() const
347 return (max_item_duration) ;
351 * Sets the minimu duration that this item may be set to
353 * @param the minimum duration that this item may be set to
354 * @param src the identity of the object that initiated the change
357 TimeAxisViewItem::set_min_duration(jack_nframes_t dur, void* src)
359 min_item_duration = dur ;
360 MinDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
364 * Returns the minimum duration that this item mey be set to
366 * @return the nimum duration that this item mey be set to
369 TimeAxisViewItem::get_min_duration() const
371 return(min_item_duration) ;
375 * Sets whether the position of this Item is locked to its current position
376 * Locked items cannot be moved until the item is unlocked again.
378 * @param yn set to true to lock this item to its current position
379 * @param src the identity of the object that initiated the change
382 TimeAxisViewItem::set_position_locked(bool yn, void* src)
384 position_locked = yn ;
385 set_trim_handle_colors() ;
386 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
390 * Returns whether this item is locked to its current position
392 * @return true if this item is locked to its current posotion
396 TimeAxisViewItem::get_position_locked() const
398 return (position_locked);
402 * Sets whether the Maximum Duration constraint is active and should be enforced
404 * @param active set true to enforce the max duration constraint
405 * @param src the identity of the object that initiated the change
408 TimeAxisViewItem::set_max_duration_active(bool active, void* src)
410 max_duration_active = active ;
414 * Returns whether the Maximum Duration constraint is active and should be enforced
416 * @return true if the maximum duration constraint is active, false otherwise
419 TimeAxisViewItem::get_max_duration_active() const
421 return(max_duration_active) ;
425 * Sets whether the Minimum Duration constraint is active and should be enforced
427 * @param active set true to enforce the min duration constraint
428 * @param src the identity of the object that initiated the change
431 TimeAxisViewItem::set_min_duration_active(bool active, void* src)
433 min_duration_active = active ;
437 * Returns whether the Maximum Duration constraint is active and should be enforced
439 * @return true if the maximum duration constraint is active, false otherwise
442 TimeAxisViewItem::get_min_duration_active() const
444 return(min_duration_active) ;
447 //---------------------------------------------------------------------------------------//
448 // Name/Id Accessors/Mutators
451 * Set the name/Id of this item.
453 * @param new_name the new name of this item
454 * @param src the identity of the object that initiated the change
457 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
459 if (new_name != item_name) {
460 std::string temp_name = item_name ;
461 item_name = new_name ;
462 NameChanged (item_name, temp_name, src) ; /* EMIT_SIGNAL */
467 * Returns the name/id of this item
469 * @return the name/id of this item
472 TimeAxisViewItem::get_item_name() const
477 //---------------------------------------------------------------------------------------//
481 * Set to true to indicate that this item is currently selected
483 * @param yn true if this item is currently selected
484 * @param src the identity of the object that initiated the change
487 TimeAxisViewItem::set_selected(bool yn, void* src)
489 if (_selected != yn) {
492 Selected (_selected) ; /* EMIT_SIGNAL */
497 * Returns whether this item is currently selected.
499 * @return true if this item is currently selected, false otherwise
502 TimeAxisViewItem::get_selected() const
508 TimeAxisViewItem::set_should_show_selection (bool yn)
510 if (should_show_selection != yn) {
511 should_show_selection = yn;
516 //---------------------------------------------------------------------------------------//
517 // Parent Componenet Methods
520 * Returns the TimeAxisView that this item is upon
522 * @return the timeAxisView that this item is placed upon
525 TimeAxisViewItem::get_time_axis_view()
529 //---------------------------------------------------------------------------------------//
533 * Sets the displayed item text
534 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item
536 * @param new_name the new name text to display
539 TimeAxisViewItem::set_name_text(std::string new_name)
542 name_text->property_text() = new_name.c_str();
547 * Set the height of this item
549 * @param h the new height
552 TimeAxisViewItem::set_height(double height)
554 if (name_highlight) {
555 if (height < NAME_HIGHLIGHT_THRESH) {
556 name_highlight->hide();
561 name_highlight->show();
567 if (height > NAME_HIGHLIGHT_SIZE) {
568 name_highlight->property_y1() = (double) height+1 - NAME_HIGHLIGHT_SIZE;
569 name_highlight->property_y2() = (double) height;
572 /* it gets hidden now anyway */
573 name_highlight->property_y1() = (double) 1.0;
574 name_highlight->property_y2() = (double) height;
579 name_text->property_y() = height+1 - NAME_Y_OFFSET;
580 if (height < NAME_HIGHLIGHT_THRESH) {
581 name_text->property_fill_color_rgba() = fill_color;
584 name_text->property_fill_color_rgba() = label_color;
589 frame->property_y2() = height+1;
592 vestigial_frame->property_y2() = height+1;
599 TimeAxisViewItem::set_color(Gdk::Color& base_color)
601 compute_colors (base_color);
609 TimeAxisViewItem::get_canvas_frame()
618 TimeAxisViewItem::get_canvas_group()
627 TimeAxisViewItem::get_name_highlight()
629 return (name_highlight) ;
636 TimeAxisViewItem::get_name_text()
642 * Calculates some contrasting color for displaying various parts of this item, based upon the base color
644 * @param color the base color of the item
647 TimeAxisViewItem::compute_colors(Gdk::Color& base_color)
649 unsigned char radius ;
652 unsigned char r,g,b ;
654 /* FILL: this is simple */
655 r = base_color.get_red()/256 ;
656 g = base_color.get_green()/256 ;
657 b = base_color.get_blue()/256 ;
658 fill_color = RGBA_TO_UINT(r,g,b,255) ;
661 if the overall saturation is strong, make the minor colors light.
662 if its weak, make them dark.
664 we do this by moving an equal distance to the other side of the
665 central circle in the color wheel from where we started.
668 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f)) ;
669 minor_shift = 125 - radius ;
671 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
673 r = base_color.get_red()/256;
674 g = base_color.get_green()/256;
675 b = base_color.get_blue()/256;
681 /* red sector => green */
686 /* green sector => blue */
694 /* blue sector => red */
699 /* green sector => blue */
708 label_color = RGBA_TO_UINT(r,g,b,255);
709 r = (base_color.get_red()/256) + 127 ;
710 g = (base_color.get_green()/256) + 127 ;
711 b = (base_color.get_blue()/256) + 127 ;
713 label_color = RGBA_TO_UINT(r,g,b,255);
715 /* XXX can we do better than this ? */
716 /* We're trying ;) */
719 //frame_color_r = 192;
720 //frame_color_g = 192;
721 //frame_color_b = 194;
723 //selected_frame_color_r = 182;
724 //selected_frame_color_g = 145;
725 //selected_frame_color_b = 168;
727 //handle_color_r = 25 ;
728 //handle_color_g = 0 ;
729 //handle_color_b = 255 ;
730 //lock_handle_color_r = 235 ;
731 //lock_handle_color_g = 16;
732 //lock_handle_color_b = 16;
736 * Convenience method to set the various canvas item colors
739 TimeAxisViewItem::set_colors()
743 double height = NAME_HIGHLIGHT_THRESH;
746 height = frame->property_y2();
749 if (height < NAME_HIGHLIGHT_THRESH) {
750 name_text->property_fill_color_rgba() = fill_color;
753 name_text->property_fill_color_rgba() = label_color;
757 if (name_highlight) {
758 name_highlight->property_fill_color_rgba() = fill_color;
759 name_highlight->property_outline_color_rgba() = fill_color;
761 set_trim_handle_colors() ;
765 * Sets the frame color depending on whether this item is selected
768 TimeAxisViewItem::set_frame_color()
773 if (_selected && should_show_selection) {
774 UINT_TO_RGBA(color_map[cSelectedFrameBase], &r, &g, &b, &a);
775 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity);
777 UINT_TO_RGBA(color_map[cFrameBase], &r, &g, &b, &a);
778 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity);
784 * Sets the colors of the start and end trim handle depending on object state
788 TimeAxisViewItem::set_trim_handle_colors()
790 if (frame_handle_start) {
791 if (position_locked) {
792 frame_handle_start->property_fill_color_rgba() = color_map[cTrimHandleLockedStart];
793 frame_handle_end->property_fill_color_rgba() = color_map[cTrimHandleLockedEnd];
795 frame_handle_start->property_fill_color_rgba() = color_map[cTrimHandleStart];
796 frame_handle_end->property_fill_color_rgba() = color_map[cTrimHandleEnd];
802 TimeAxisViewItem::get_samples_per_unit()
804 return(samples_per_unit) ;
808 TimeAxisViewItem::set_samples_per_unit (double spu)
810 samples_per_unit = spu ;
811 set_position (this->get_position(), this);
812 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
816 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
818 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
820 if (frame_handle_start) {
821 frame_handle_start->hide();
822 frame_handle_end->hide();
825 } if (pixel_width < 2.0) {
827 if (show_vestigial) {
828 vestigial_frame->show();
831 if (name_highlight) {
832 name_highlight->hide();
842 if (frame_handle_start) {
843 frame_handle_start->hide();
844 frame_handle_end->hide();
848 vestigial_frame->hide();
850 if (name_highlight) {
852 double height = name_highlight->property_y2 ();
854 if (height < NAME_HIGHLIGHT_THRESH) {
855 name_highlight->hide();
860 name_highlight->show();
863 reset_name_width (pixel_width);
867 if (visibility & FullWidthNameHighlight) {
868 name_highlight->property_x2() = pixel_width;
870 name_highlight->property_x2() = pixel_width - 1.0;
877 frame->property_x2() = pixel_width;
880 if (frame_handle_start) {
881 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
882 frame_handle_start->hide();
883 frame_handle_end->hide();
885 frame_handle_start->show();
886 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
887 frame_handle_end->show();
888 frame_handle_end->property_x2() = pixel_width;
894 TimeAxisViewItem::reset_name_width (double pixel_width)
896 if (name_text == 0) {
902 ustring ustr = fit_to_pixels (item_name, (int) floor (pixel_width - NAME_X_OFFSET), NAME_FONT, width);
910 /* don't use name for event handling if it leaves no room
911 for trimming to work.
914 if (pixel_width - width < (NAME_X_OFFSET * 2.0)) {
915 if (name_connected) {
916 name_connected = false;
919 if (!name_connected) {
920 name_connected = true;
924 name_text->property_text() = ustr;
930 //---------------------------------------------------------------------------------------//
931 // Handle time axis removal
934 * Handles the Removal of this time axis item
935 * This _needs_ to be called to alert others of the removal properly, ie where the source
936 * of the removal came from.
938 * XXX Although im not too happy about this method of doing things, I cant think of a cleaner method
939 * just now to capture the source of the removal
941 * @param src the identity of the object that initiated the change
944 TimeAxisViewItem::remove_this_item(void* src)
947 defer to idle loop, otherwise we'll delete this object
948 while we're still inside this function ...
950 Glib::signal_idle().connect(bind (sigc::ptr_fun (&TimeAxisViewItem::idle_remove_this_item), this, src));
954 * Callback used to remove this time axis item during the gtk idle loop
955 * This is used to avoid deleting the obejct while inside the remove_this_item
958 * @param item the TimeAxisViewItem to remove
959 * @param src the identity of the object that initiated the change
962 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
964 item->ItemRemoved (item->get_item_name(), src) ; /* EMIT_SIGNAL */