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 "public_editor.h"
27 #include "time_axis_view_item.h"
28 #include "time_axis_view.h"
29 #include "simplerect.h"
31 #include "canvas_impl.h"
32 #include "rgb_macros.h"
37 using namespace Editing;
40 //------------------------------------------------------------------------------
41 /** Initialize static memeber data */
42 Pango::FontDescription TimeAxisViewItem::NAME_FONT;
43 bool TimeAxisViewItem::have_name_font = false;
44 const double TimeAxisViewItem::NAME_X_OFFSET = 15.0;
45 const double TimeAxisViewItem::NAME_Y_OFFSET = 15.0 ; /* XXX depends a lot on the font size, sigh. */
46 const double TimeAxisViewItem::NAME_HIGHLIGHT_SIZE = 15.0 ; /* ditto */
47 const double TimeAxisViewItem::NAME_HIGHLIGHT_THRESH = 32.0 ; /* ditto */
48 const double TimeAxisViewItem::GRAB_HANDLE_LENGTH = 6 ;
51 //---------------------------------------------------------------------------------------//
52 // Constructor / Desctructor
55 * Constructs a new TimeAxisViewItem.
57 * @param it_name the unique name/Id of this item
58 * @param parant the parent canvas group
59 * @param tv the TimeAxisView we are going to be added to
60 * @param spu samples per unit
62 * @param start the start point of this item
63 * @param duration the duration of this item
65 TimeAxisViewItem::TimeAxisViewItem(ARDOUR::stringcr_t it_name, ArdourCanvas::Group& parent, TimeAxisView& tv, double spu, Gdk::Color& base_color,
66 jack_nframes_t start, jack_nframes_t duration,
67 Visibility visibility)
70 if (!have_name_font) {
71 NAME_FONT = get_font_for_style (N_("TimeAxisViewItemName"));
72 have_name_font = true;
76 samples_per_unit = spu ;
77 should_show_selection = true;
78 frame_position = start ;
79 item_duration = duration ;
80 name_connected = false;
82 position_locked = false ;
83 max_item_duration = ARDOUR::max_frames;
84 min_item_duration = 0 ;
85 show_vestigial = true;
88 warning << "Time Axis Item Duration == 0" << endl ;
91 group = new ArdourCanvas::Group (parent);
93 vestigial_frame = new ArdourCanvas::SimpleRect (*group);
94 vestigial_frame->property_x1() = (double) 0.0;
95 vestigial_frame->property_y1() = (double) 1.0;
96 vestigial_frame->property_x2() = 2.0;
97 vestigial_frame->property_y2() = (double) trackview.height;
98 vestigial_frame->property_outline_color_rgba() = color_map[cVestigialFrameOutline];
99 vestigial_frame->property_fill_color_rgba() = color_map[cVestigialFrameFill];
100 vestigial_frame->hide ();
102 if (visibility & ShowFrame) {
103 frame = new ArdourCanvas::SimpleRect (*group);
104 frame->property_x1() = (double) 0.0;
105 frame->property_y1() = (double) 1.0;
106 frame->property_x2() = (double) trackview.editor.frame_to_pixel(duration);
107 frame->property_y2() = (double) trackview.height;
108 frame->property_outline_color_rgba() = color_map[cTimeAxisFrameOutline];
109 frame->property_fill_color_rgba() = color_map[cTimeAxisFrameFill];
115 if (visibility & ShowNameHighlight) {
116 name_highlight = new ArdourCanvas::SimpleRect (*group);
117 name_highlight->property_x1() = (double) 1.0;
118 name_highlight->property_x2() = (double) (trackview.editor.frame_to_pixel(item_duration)) - 1;
119 name_highlight->property_y1() = (double) (trackview.height - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE);
120 name_highlight->property_y2() = (double) (trackview.height - 1);
121 name_highlight->property_outline_color_rgba() = color_map[cNameHighlightFill];
122 name_highlight->property_fill_color_rgba() = color_map[cNameHighlightOutline];
124 name_highlight->set_data ("timeaxisviewitem", this);
130 if (visibility & ShowNameText) {
131 name_text = new ArdourCanvas::Text (*group);
132 name_text->property_x() = (double) TimeAxisViewItem::NAME_X_OFFSET;
133 name_text->property_y() = (double) trackview.height + 1.0 - TimeAxisViewItem::NAME_Y_OFFSET;
134 name_text->property_font_desc() = NAME_FONT;
135 name_text->property_anchor() = Gtk::ANCHOR_NW;
137 name_text->set_data ("timeaxisviewitem", this);
143 /* create our grab handles used for trimming/duration etc */
145 if (visibility & ShowHandles) {
146 frame_handle_start = new ArdourCanvas::SimpleRect (*group);
147 frame_handle_start->property_x1() = (double) 0.0;
148 frame_handle_start->property_x2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH;
149 frame_handle_start->property_y1() = (double) 1.0;
150 frame_handle_start->property_y2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH+1;
151 frame_handle_start->property_outline_color_rgba() = color_map[cFrameHandleStartOutline];
152 frame_handle_start->property_fill_color_rgba() = color_map[cFrameHandleStartFill];
154 frame_handle_end = new ArdourCanvas::SimpleRect (*group);
155 frame_handle_end->property_x1() = (double) (trackview.editor.frame_to_pixel(get_duration())) - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
156 frame_handle_end->property_x2() = (double) trackview.editor.frame_to_pixel(get_duration());
157 frame_handle_end->property_y1() = (double) 1;
158 frame_handle_end->property_y2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH + 1;
159 frame_handle_end->property_outline_color_rgba() = color_map[cFrameHandleEndOutline];
160 frame_handle_end->property_fill_color_rgba() = color_map[cFrameHandleEndFill];
163 frame_handle_start = 0;
164 frame_handle_end = 0;
167 set_color (base_color) ;
169 set_duration (item_duration, this) ;
170 set_position (start, this) ;
177 TimeAxisViewItem::~TimeAxisViewItem()
183 //---------------------------------------------------------------------------------------//
184 // Position and duration Accessors/Mutators
187 * Set the position of this item upon the timeline to the specified value
189 * @param pos the new position
190 * @param src the identity of the object that initiated the change
191 * @return true if the position change was a success, false otherwise
194 TimeAxisViewItem::set_position(jack_nframes_t pos, void* src, double* delta)
196 if (position_locked) {
200 frame_position = pos;
202 /* This sucks. The GnomeCanvas version I am using
203 doesn't correctly implement gnome_canvas_group_set_arg(),
204 so that simply setting the "x" arg of the group
205 fails to move the group. Instead, we have to
206 use gnome_canvas_item_move(), which does the right
207 thing. I see that in GNOME CVS, the current (Sept 2001)
208 version of GNOME Canvas rectifies this issue cleanly.
211 double old_unit_pos ;
212 double new_unit_pos = pos / samples_per_unit ;
214 old_unit_pos = group->property_x();
216 if (new_unit_pos != old_unit_pos) {
217 group->move (new_unit_pos - old_unit_pos, 0.0);
221 (*delta) = new_unit_pos - old_unit_pos;
224 PositionChanged (frame_position, src) ; /* EMIT_SIGNAL */
230 * Return the position of this item upon the timeline
232 * @return the position of this item
235 TimeAxisViewItem::get_position() const
237 return frame_position;
241 * Sets the duration of this item
243 * @param dur the new duration of this item
244 * @param src the identity of the object that initiated the change
245 * @return true if the duration change was succesful, false otherwise
248 TimeAxisViewItem::set_duration (jack_nframes_t dur, void* src)
250 if ((dur > max_item_duration) || (dur < min_item_duration)) {
251 warning << string_compose (_("new duration %1 frames is out of bounds for %2"), get_item_name(), dur)
262 double pixel_width = trackview.editor.frame_to_pixel (dur);
264 reset_width_dependent_items (pixel_width);
266 DurationChanged (dur, src) ; /* EMIT_SIGNAL */
271 * Returns the duration of this item
275 TimeAxisViewItem::get_duration() const
277 return (item_duration);
281 * Sets the maximum duration that this item make have.
283 * @param dur the new maximum duration
284 * @param src the identity of the object that initiated the change
287 TimeAxisViewItem::set_max_duration(jack_nframes_t dur, void* src)
289 max_item_duration = dur ;
290 MaxDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
294 * Returns the maxmimum duration that this item may be set to
296 * @return the maximum duration that this item may be set to
299 TimeAxisViewItem::get_max_duration() const
301 return (max_item_duration) ;
305 * Sets the minimu duration that this item may be set to
307 * @param the minimum duration that this item may be set to
308 * @param src the identity of the object that initiated the change
311 TimeAxisViewItem::set_min_duration(jack_nframes_t dur, void* src)
313 min_item_duration = dur ;
314 MinDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
318 * Returns the minimum duration that this item mey be set to
320 * @return the nimum duration that this item mey be set to
323 TimeAxisViewItem::get_min_duration() const
325 return(min_item_duration) ;
329 * Sets whether the position of this Item is locked to its current position
330 * Locked items cannot be moved until the item is unlocked again.
332 * @param yn set to true to lock this item to its current position
333 * @param src the identity of the object that initiated the change
336 TimeAxisViewItem::set_position_locked(bool yn, void* src)
338 position_locked = yn ;
339 set_trim_handle_colors() ;
340 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
344 * Returns whether this item is locked to its current position
346 * @return true if this item is locked to its current posotion
350 TimeAxisViewItem::get_position_locked() const
352 return (position_locked);
356 * Sets whether the Maximum Duration constraint is active and should be enforced
358 * @param active set true to enforce the max duration constraint
359 * @param src the identity of the object that initiated the change
362 TimeAxisViewItem::set_max_duration_active(bool active, void* src)
364 max_duration_active = active ;
368 * Returns whether the Maximum Duration constraint is active and should be enforced
370 * @return true if the maximum duration constraint is active, false otherwise
373 TimeAxisViewItem::get_max_duration_active() const
375 return(max_duration_active) ;
379 * Sets whether the Minimum Duration constraint is active and should be enforced
381 * @param active set true to enforce the min duration constraint
382 * @param src the identity of the object that initiated the change
385 TimeAxisViewItem::set_min_duration_active(bool active, void* src)
387 min_duration_active = active ;
391 * Returns whether the Maximum Duration constraint is active and should be enforced
393 * @return true if the maximum duration constraint is active, false otherwise
396 TimeAxisViewItem::get_min_duration_active() const
398 return(min_duration_active) ;
401 //---------------------------------------------------------------------------------------//
402 // Name/Id Accessors/Mutators
405 * Set the name/Id of this item.
407 * @param new_name the new name of this item
408 * @param src the identity of the object that initiated the change
411 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
413 if (new_name != item_name) {
414 std::string temp_name = item_name ;
415 item_name = new_name ;
416 NameChanged (item_name, temp_name, src) ; /* EMIT_SIGNAL */
421 * Returns the name/id of this item
423 * @return the name/id of this item
426 TimeAxisViewItem::get_item_name() const
431 //---------------------------------------------------------------------------------------//
435 * Set to true to indicate that this item is currently selected
437 * @param yn true if this item is currently selected
438 * @param src the identity of the object that initiated the change
441 TimeAxisViewItem::set_selected(bool yn, void* src)
443 if (_selected != yn) {
446 Selected (_selected) ; /* EMIT_SIGNAL */
451 * Returns whether this item is currently selected.
453 * @return true if this item is currently selected, false otherwise
456 TimeAxisViewItem::get_selected() const
462 TimeAxisViewItem::set_should_show_selection (bool yn)
464 if (should_show_selection != yn) {
465 should_show_selection = yn;
470 //---------------------------------------------------------------------------------------//
471 // Parent Componenet Methods
474 * Returns the TimeAxisView that this item is upon
476 * @return the timeAxisView that this item is placed upon
479 TimeAxisViewItem::get_time_axis_view()
483 //---------------------------------------------------------------------------------------//
487 * Sets the displayed item text
488 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item
490 * @param new_name the new name text to display
493 TimeAxisViewItem::set_name_text(std::string new_name)
496 name_text->property_text() = new_name.c_str();
501 * Set the height of this item
503 * @param h the new height
506 TimeAxisViewItem::set_height(double height)
508 if (name_highlight) {
509 if (height < NAME_HIGHLIGHT_THRESH) {
510 name_highlight->hide();
513 name_highlight->show();
517 if (height > NAME_HIGHLIGHT_SIZE) {
518 name_highlight->property_y1() = (double) height+1 - NAME_HIGHLIGHT_SIZE;
519 name_highlight->property_y2() = (double) height;
522 /* it gets hidden now anyway */
523 name_highlight->property_y1() = (double) 1.0;
524 name_highlight->property_y2() = (double) height;
529 name_text->property_y() = height+1 - NAME_Y_OFFSET;
530 if (height < NAME_HIGHLIGHT_THRESH) {
531 name_text->property_fill_color_rgba() = fill_color;
534 name_text->property_fill_color_rgba() = label_color;
539 frame->property_y2() = height+1;
542 vestigial_frame->property_y2() = height+1;
549 TimeAxisViewItem::set_color(Gdk::Color& base_color)
551 compute_colors (base_color);
559 TimeAxisViewItem::get_canvas_frame()
568 TimeAxisViewItem::get_canvas_group()
577 TimeAxisViewItem::get_name_highlight()
579 return(name_highlight) ;
586 TimeAxisViewItem::get_name_text()
592 * Calculates some contrasting color for displaying various parts of this item, based upon the base color
594 * @param color the base color of the item
597 TimeAxisViewItem::compute_colors(Gdk::Color& base_color)
599 unsigned char radius ;
602 unsigned char r,g,b ;
604 /* FILL: this is simple */
605 r = base_color.get_red()/256 ;
606 g = base_color.get_green()/256 ;
607 b = base_color.get_blue()/256 ;
608 fill_color = RGBA_TO_UINT(r,g,b,255) ;
611 if the overall saturation is strong, make the minor colors light.
612 if its weak, make them dark.
614 we do this by moving an equal distance to the other side of the
615 central circle in the color wheel from where we started.
618 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f)) ;
619 minor_shift = 125 - radius ;
621 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
623 r = base_color.get_red()/256;
624 g = base_color.get_green()/256;
625 b = base_color.get_blue()/256;
631 /* red sector => green */
636 /* green sector => blue */
644 /* blue sector => red */
649 /* green sector => blue */
658 label_color = RGBA_TO_UINT(r,g,b,255);
659 r = (base_color.get_red()/256) + 127 ;
660 g = (base_color.get_green()/256) + 127 ;
661 b = (base_color.get_blue()/256) + 127 ;
663 label_color = RGBA_TO_UINT(r,g,b,255);
665 /* XXX can we do better than this ? */
666 /* We're trying ;) */
669 //frame_color_r = 192;
670 //frame_color_g = 192;
671 //frame_color_b = 194;
673 //selected_frame_color_r = 182;
674 //selected_frame_color_g = 145;
675 //selected_frame_color_b = 168;
677 //handle_color_r = 25 ;
678 //handle_color_g = 0 ;
679 //handle_color_b = 255 ;
680 //lock_handle_color_r = 235 ;
681 //lock_handle_color_g = 16;
682 //lock_handle_color_b = 16;
686 * Convenience method to set the various canvas item colors
689 TimeAxisViewItem::set_colors()
693 double height = NAME_HIGHLIGHT_THRESH;
696 height = frame->property_y2();
699 if (height < NAME_HIGHLIGHT_THRESH) {
700 name_text->property_fill_color_rgba() = fill_color;
703 name_text->property_fill_color_rgba() = label_color;
707 if (name_highlight) {
708 name_highlight->property_fill_color_rgba() = fill_color;
709 name_highlight->property_outline_color_rgba() = fill_color;
711 set_trim_handle_colors() ;
715 * Sets the frame color depending on whether this item is selected
718 TimeAxisViewItem::set_frame_color()
723 if (_selected && should_show_selection) {
724 UINT_TO_RGBA(color_map[cSelectedFrameBase], &r, &g, &b, &a);
725 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity);
727 UINT_TO_RGBA(color_map[cFrameBase], &r, &g, &b, &a);
728 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity);
734 * Sets the colors of the start and end trim handle depending on object state
738 TimeAxisViewItem::set_trim_handle_colors()
740 if (frame_handle_start) {
741 if (position_locked) {
742 frame_handle_start->property_fill_color_rgba() = color_map[cTrimHandleLockedStart];
743 frame_handle_end->property_fill_color_rgba() = color_map[cTrimHandleLockedEnd];
745 frame_handle_start->property_fill_color_rgba() = color_map[cTrimHandleStart];
746 frame_handle_end->property_fill_color_rgba() = color_map[cTrimHandleEnd];
752 TimeAxisViewItem::get_samples_per_unit()
754 return(samples_per_unit) ;
758 TimeAxisViewItem::set_samples_per_unit (double spu)
760 samples_per_unit = spu ;
761 set_position (this->get_position(), this);
762 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
766 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
768 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
770 if (frame_handle_start) {
771 frame_handle_start->hide();
772 frame_handle_end->hide();
775 } if (pixel_width < 2.0) {
777 if (show_vestigial) {
778 vestigial_frame->show();
781 if (name_highlight) {
782 name_highlight->hide();
790 if (frame_handle_start) {
791 frame_handle_start->hide();
792 frame_handle_end->hide();
796 vestigial_frame->hide();
798 if (name_highlight) {
800 double height = name_highlight->property_y2 ();
802 if (height < NAME_HIGHLIGHT_THRESH) {
803 name_highlight->hide();
806 name_highlight->show();
808 reset_name_width (pixel_width);
811 name_highlight->property_x2() = pixel_width - 1.0;
816 frame->property_x2() = pixel_width;
819 if (frame_handle_start) {
820 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
821 frame_handle_start->hide();
822 frame_handle_end->hide();
824 frame_handle_start->show();
825 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
826 frame_handle_end->show();
827 frame_handle_end->property_x2() = pixel_width;
833 TimeAxisViewItem::reset_name_width (double pixel_width)
838 Pango::FontDescription fd (NAME_FONT);
840 if (name_text == 0) {
845 int namelen = ustr.length();
847 Glib::RefPtr<Pango::Layout> layout = group->get_canvas()->create_pango_layout (ustr);
848 layout->set_font_description (fd);
852 layout->set_text (ustr);
853 layout->get_pixel_size (width, height);
855 if (width < (pixel_width - NAME_X_OFFSET)) {
860 ustr = ustr.substr (0, namelen);
869 /* don't use name for event handling if it leaves no room
870 for trimming to work.
873 if (pixel_width - width < (NAME_X_OFFSET * 2.0)) {
874 if (name_connected) {
875 name_connected = false;
878 if (!name_connected) {
879 name_connected = true;
883 name_text->property_text() = ustr;
889 //---------------------------------------------------------------------------------------//
890 // Handle time axis removal
893 * Handles the Removal of this time axis item
894 * This _needs_ to be called to alert others of the removal properly, ie where the source
895 * of the removal came from.
897 * XXX Although im not too happy about this method of doing things, I cant think of a cleaner method
898 * just now to capture the source of the removal
900 * @param src the identity of the object that initiated the change
903 TimeAxisViewItem::remove_this_item(void* src)
906 defer to idle loop, otherwise we'll delete this object
907 while we're still inside this function ...
909 Glib::signal_idle().connect(bind (sigc::ptr_fun (&TimeAxisViewItem::idle_remove_this_item), this, src));
913 * Callback used to remove this time axis item during the gtk idle loop
914 * This is used to avoid deleting the obejct while inside the remove_this_item
917 * @param item the TimeAxisViewItem to remove
918 * @param src the identity of the object that initiated the change
921 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
923 item->ItemRemoved(item->get_item_name(), src) ; /* EMIT_SIGNAL */