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;
43 //------------------------------------------------------------------------------
44 /** Initialize const static memeber data */
46 Pango::FontDescription TimeAxisViewItem::NAME_FONT;
47 bool TimeAxisViewItem::have_name_font = false;
48 const double TimeAxisViewItem::NAME_X_OFFSET = 15.0;
49 const double TimeAxisViewItem::GRAB_HANDLE_LENGTH = 6 ;
51 double TimeAxisViewItem::NAME_Y_OFFSET;
52 double TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
53 double TimeAxisViewItem::NAME_HIGHLIGHT_THRESH;
56 //---------------------------------------------------------------------------------------//
57 // Constructor / Desctructor
60 * Constructs a new TimeAxisViewItem.
62 * @param it_name the unique name/Id of this item
63 * @param parant the parent canvas group
64 * @param tv the TimeAxisView we are going to be added to
65 * @param spu samples per unit
67 * @param start the start point of this item
68 * @param duration the duration of this item
70 TimeAxisViewItem::TimeAxisViewItem(const string & it_name, ArdourCanvas::Group& parent, TimeAxisView& tv, double spu, Gdk::Color& base_color,
71 jack_nframes_t start, jack_nframes_t duration,
75 if (!have_name_font) {
77 /* first constructed item sets up font info */
79 NAME_FONT = get_font_for_style (N_("TimeAxisViewItemName"));
85 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (X_("Hg")); /* ascender + descender */
89 layout->set_font_description (NAME_FONT);
90 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
92 NAME_Y_OFFSET = height + 4;
93 NAME_HIGHLIGHT_SIZE = height + 6;
94 NAME_HIGHLIGHT_THRESH = NAME_HIGHLIGHT_SIZE * 2;
96 have_name_font = true;
100 samples_per_unit = spu ;
101 should_show_selection = true;
102 frame_position = start ;
103 item_duration = duration ;
104 name_connected = false;
106 position_locked = false ;
107 max_item_duration = ARDOUR::max_frames;
108 min_item_duration = 0 ;
109 show_vestigial = true;
113 warning << "Time Axis Item Duration == 0" << endl ;
116 group = new ArdourCanvas::Group (parent);
118 vestigial_frame = new ArdourCanvas::SimpleRect (*group);
119 vestigial_frame->property_x1() = (double) 0.0;
120 vestigial_frame->property_y1() = (double) 1.0;
121 vestigial_frame->property_x2() = 2.0;
122 vestigial_frame->property_y2() = (double) trackview.height;
123 vestigial_frame->property_outline_color_rgba() = color_map[cVestigialFrameOutline];
124 vestigial_frame->property_fill_color_rgba() = color_map[cVestigialFrameFill];
125 vestigial_frame->hide ();
127 if (visibility & ShowFrame) {
128 frame = new ArdourCanvas::SimpleRect (*group);
129 frame->property_x1() = (double) 0.0;
130 frame->property_y1() = (double) 1.0;
131 frame->property_x2() = (double) trackview.editor.frame_to_pixel(duration);
132 frame->property_y2() = (double) trackview.height;
133 frame->property_outline_color_rgba() = color_map[cTimeAxisFrameOutline];
134 frame->property_fill_color_rgba() = color_map[cTimeAxisFrameFill];
136 /* by default draw all 4 edges */
138 uint32_t outline_what = 0x1|0x2|0x4|0x8;
140 if (visibility & HideFrameLeft) {
141 outline_what &= ~(0x1);
144 if (visibility & HideFrameRight) {
145 outline_what &= ~(0x2);
148 if (visibility & HideFrameTB) {
149 outline_what &= ~(0x4 | 0x8);
152 frame->property_outline_what() = outline_what;
158 if (visibility & ShowNameHighlight) {
159 name_highlight = new ArdourCanvas::SimpleRect (*group);
160 if (visibility & FullWidthNameHighlight) {
161 name_highlight->property_x1() = (double) 0.0;
162 name_highlight->property_x2() = (double) (trackview.editor.frame_to_pixel(item_duration));
164 name_highlight->property_x1() = (double) 1.0;
165 name_highlight->property_x2() = (double) (trackview.editor.frame_to_pixel(item_duration)) - 1;
167 name_highlight->property_y1() = (double) (trackview.height - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE);
168 name_highlight->property_y2() = (double) (trackview.height - 1);
169 name_highlight->property_outline_color_rgba() = color_map[cNameHighlightFill];
170 name_highlight->property_fill_color_rgba() = color_map[cNameHighlightOutline];
172 name_highlight->set_data ("timeaxisviewitem", this);
178 if (visibility & ShowNameText) {
179 name_text = new ArdourCanvas::Text (*group);
180 name_text->property_x() = (double) TimeAxisViewItem::NAME_X_OFFSET;
181 /* trackview.height is the bottom of the trackview. subtract 1 to get back to the bottom of the highlight,
182 then NAME_Y_OFFSET to position the text in the vertical center of the highlight
184 name_text->property_y() = (double) trackview.height - 1.0 - TimeAxisViewItem::NAME_Y_OFFSET;
185 name_text->property_font_desc() = NAME_FONT;
186 name_text->property_anchor() = Gtk::ANCHOR_NW;
188 name_text->set_data ("timeaxisviewitem", this);
194 /* create our grab handles used for trimming/duration etc */
196 if (visibility & ShowHandles) {
197 frame_handle_start = new ArdourCanvas::SimpleRect (*group);
198 frame_handle_start->property_x1() = (double) 0.0;
199 frame_handle_start->property_x2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH;
200 frame_handle_start->property_y1() = (double) 1.0;
201 frame_handle_start->property_y2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH+1;
202 frame_handle_start->property_outline_color_rgba() = color_map[cFrameHandleStartOutline];
203 frame_handle_start->property_fill_color_rgba() = color_map[cFrameHandleStartFill];
205 frame_handle_end = new ArdourCanvas::SimpleRect (*group);
206 frame_handle_end->property_x1() = (double) (trackview.editor.frame_to_pixel(get_duration())) - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
207 frame_handle_end->property_x2() = (double) trackview.editor.frame_to_pixel(get_duration());
208 frame_handle_end->property_y1() = (double) 1;
209 frame_handle_end->property_y2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH + 1;
210 frame_handle_end->property_outline_color_rgba() = color_map[cFrameHandleEndOutline];
211 frame_handle_end->property_fill_color_rgba() = color_map[cFrameHandleEndFill];
214 frame_handle_start = 0;
215 frame_handle_end = 0;
218 set_color (base_color) ;
220 set_duration (item_duration, this) ;
221 set_position (start, this) ;
228 TimeAxisViewItem::~TimeAxisViewItem()
234 //---------------------------------------------------------------------------------------//
235 // Position and duration Accessors/Mutators
238 * Set the position of this item upon the timeline to the specified value
240 * @param pos the new position
241 * @param src the identity of the object that initiated the change
242 * @return true if the position change was a success, false otherwise
245 TimeAxisViewItem::set_position(jack_nframes_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.
262 double old_unit_pos ;
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 */
281 * Return the position of this item upon the timeline
283 * @return the position of this item
286 TimeAxisViewItem::get_position() const
288 return frame_position;
292 * Sets the duration of this item
294 * @param dur the new duration of this item
295 * @param src the identity of the object that initiated the change
296 * @return true if the duration change was succesful, false otherwise
299 TimeAxisViewItem::set_duration (jack_nframes_t dur, void* src)
301 if ((dur > max_item_duration) || (dur < min_item_duration)) {
302 warning << string_compose (_("new duration %1 frames is out of bounds for %2"), get_item_name(), dur)
313 double pixel_width = trackview.editor.frame_to_pixel (dur);
315 reset_width_dependent_items (pixel_width);
317 DurationChanged (dur, src) ; /* EMIT_SIGNAL */
322 * Returns the duration of this item
326 TimeAxisViewItem::get_duration() const
328 return (item_duration);
332 * Sets the maximum duration that this item make have.
334 * @param dur the new maximum duration
335 * @param src the identity of the object that initiated the change
338 TimeAxisViewItem::set_max_duration(jack_nframes_t dur, void* src)
340 max_item_duration = dur ;
341 MaxDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
345 * Returns the maxmimum duration that this item may be set to
347 * @return the maximum duration that this item may be set to
350 TimeAxisViewItem::get_max_duration() const
352 return (max_item_duration) ;
356 * Sets the minimu duration that this item may be set to
358 * @param the minimum duration that this item may be set to
359 * @param src the identity of the object that initiated the change
362 TimeAxisViewItem::set_min_duration(jack_nframes_t dur, void* src)
364 min_item_duration = dur ;
365 MinDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
369 * Returns the minimum duration that this item mey be set to
371 * @return the nimum duration that this item mey be set to
374 TimeAxisViewItem::get_min_duration() const
376 return(min_item_duration) ;
380 * Sets whether the position of this Item is locked to its current position
381 * Locked items cannot be moved until the item is unlocked again.
383 * @param yn set to true to lock this item to its current position
384 * @param src the identity of the object that initiated the change
387 TimeAxisViewItem::set_position_locked(bool yn, void* src)
389 position_locked = yn ;
390 set_trim_handle_colors() ;
391 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
395 * Returns whether this item is locked to its current position
397 * @return true if this item is locked to its current posotion
401 TimeAxisViewItem::get_position_locked() const
403 return (position_locked);
407 * Sets whether the Maximum Duration constraint is active and should be enforced
409 * @param active set true to enforce the max duration constraint
410 * @param src the identity of the object that initiated the change
413 TimeAxisViewItem::set_max_duration_active(bool active, void* src)
415 max_duration_active = active ;
419 * Returns whether the Maximum Duration constraint is active and should be enforced
421 * @return true if the maximum duration constraint is active, false otherwise
424 TimeAxisViewItem::get_max_duration_active() const
426 return(max_duration_active) ;
430 * Sets whether the Minimum Duration constraint is active and should be enforced
432 * @param active set true to enforce the min duration constraint
433 * @param src the identity of the object that initiated the change
436 TimeAxisViewItem::set_min_duration_active(bool active, void* src)
438 min_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_min_duration_active() const
449 return(min_duration_active) ;
452 //---------------------------------------------------------------------------------------//
453 // Name/Id Accessors/Mutators
456 * Set the name/Id of this item.
458 * @param new_name the new name of this item
459 * @param src the identity of the object that initiated the change
462 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
464 if (new_name != item_name) {
465 std::string temp_name = item_name ;
466 item_name = new_name ;
467 NameChanged (item_name, temp_name, src) ; /* EMIT_SIGNAL */
472 * Returns the name/id of this item
474 * @return the name/id of this item
477 TimeAxisViewItem::get_item_name() const
482 //---------------------------------------------------------------------------------------//
486 * Set to true to indicate that this item is currently selected
488 * @param yn true if this item is currently selected
489 * @param src the identity of the object that initiated the change
492 TimeAxisViewItem::set_selected(bool yn)
494 if (_selected != yn) {
495 Selectable::set_selected (yn);
501 TimeAxisViewItem::set_should_show_selection (bool yn)
503 if (should_show_selection != yn) {
504 should_show_selection = yn;
509 //---------------------------------------------------------------------------------------//
510 // Parent Componenet Methods
513 * Returns the TimeAxisView that this item is upon
515 * @return the timeAxisView that this item is placed upon
518 TimeAxisViewItem::get_time_axis_view()
522 //---------------------------------------------------------------------------------------//
526 * Sets the displayed item text
527 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item
529 * @param new_name the new name text to display
532 TimeAxisViewItem::set_name_text(std::string new_name)
535 name_text->property_text() = new_name.c_str();
540 * Set the height of this item
542 * @param h the new height
545 TimeAxisViewItem::set_height(double height)
547 if (name_highlight) {
548 if (height < NAME_HIGHLIGHT_THRESH) {
549 name_highlight->hide();
554 name_highlight->show();
560 if (height > NAME_HIGHLIGHT_SIZE) {
561 name_highlight->property_y1() = (double) height+1 - NAME_HIGHLIGHT_SIZE;
562 name_highlight->property_y2() = (double) height;
565 /* it gets hidden now anyway */
566 name_highlight->property_y1() = (double) 1.0;
567 name_highlight->property_y2() = (double) height;
572 name_text->property_y() = height+1 - NAME_Y_OFFSET;
573 if (height < NAME_HIGHLIGHT_THRESH) {
574 name_text->property_fill_color_rgba() = fill_color;
577 name_text->property_fill_color_rgba() = label_color;
582 frame->property_y2() = height+1;
585 vestigial_frame->property_y2() = height+1;
592 TimeAxisViewItem::set_color(Gdk::Color& base_color)
594 compute_colors (base_color);
602 TimeAxisViewItem::get_canvas_frame()
611 TimeAxisViewItem::get_canvas_group()
620 TimeAxisViewItem::get_name_highlight()
622 return (name_highlight) ;
629 TimeAxisViewItem::get_name_text()
635 * Calculates some contrasting color for displaying various parts of this item, based upon the base color
637 * @param color the base color of the item
640 TimeAxisViewItem::compute_colors(Gdk::Color& base_color)
642 unsigned char radius ;
645 unsigned char r,g,b ;
647 /* FILL: this is simple */
648 r = base_color.get_red()/256 ;
649 g = base_color.get_green()/256 ;
650 b = base_color.get_blue()/256 ;
651 fill_color = RGBA_TO_UINT(r,g,b,255) ;
654 if the overall saturation is strong, make the minor colors light.
655 if its weak, make them dark.
657 we do this by moving an equal distance to the other side of the
658 central circle in the color wheel from where we started.
661 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f)) ;
662 minor_shift = 125 - radius ;
664 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
666 r = base_color.get_red()/256;
667 g = base_color.get_green()/256;
668 b = base_color.get_blue()/256;
674 /* red sector => green */
679 /* green sector => blue */
687 /* blue sector => red */
692 /* green sector => blue */
701 label_color = RGBA_TO_UINT(r,g,b,255);
702 r = (base_color.get_red()/256) + 127 ;
703 g = (base_color.get_green()/256) + 127 ;
704 b = (base_color.get_blue()/256) + 127 ;
706 label_color = RGBA_TO_UINT(r,g,b,255);
708 /* XXX can we do better than this ? */
709 /* We're trying ;) */
712 //frame_color_r = 192;
713 //frame_color_g = 192;
714 //frame_color_b = 194;
716 //selected_frame_color_r = 182;
717 //selected_frame_color_g = 145;
718 //selected_frame_color_b = 168;
720 //handle_color_r = 25 ;
721 //handle_color_g = 0 ;
722 //handle_color_b = 255 ;
723 //lock_handle_color_r = 235 ;
724 //lock_handle_color_g = 16;
725 //lock_handle_color_b = 16;
729 * Convenience method to set the various canvas item colors
732 TimeAxisViewItem::set_colors()
736 double height = NAME_HIGHLIGHT_THRESH;
739 height = frame->property_y2();
742 if (height < NAME_HIGHLIGHT_THRESH) {
743 name_text->property_fill_color_rgba() = fill_color;
746 name_text->property_fill_color_rgba() = label_color;
750 if (name_highlight) {
751 name_highlight->property_fill_color_rgba() = fill_color;
752 name_highlight->property_outline_color_rgba() = fill_color;
754 set_trim_handle_colors() ;
758 * Sets the frame color depending on whether this item is selected
761 TimeAxisViewItem::set_frame_color()
766 if (_selected && should_show_selection) {
767 UINT_TO_RGBA(color_map[cSelectedFrameBase], &r, &g, &b, &a);
768 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity);
770 UINT_TO_RGBA(color_map[cFrameBase], &r, &g, &b, &a);
771 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity);
777 * Sets the colors of the start and end trim handle depending on object state
781 TimeAxisViewItem::set_trim_handle_colors()
783 if (frame_handle_start) {
784 if (position_locked) {
785 frame_handle_start->property_fill_color_rgba() = color_map[cTrimHandleLockedStart];
786 frame_handle_end->property_fill_color_rgba() = color_map[cTrimHandleLockedEnd];
788 frame_handle_start->property_fill_color_rgba() = color_map[cTrimHandleStart];
789 frame_handle_end->property_fill_color_rgba() = color_map[cTrimHandleEnd];
795 TimeAxisViewItem::get_samples_per_unit()
797 return(samples_per_unit) ;
801 TimeAxisViewItem::set_samples_per_unit (double spu)
803 samples_per_unit = spu ;
804 set_position (this->get_position(), this);
805 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
809 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
811 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
813 if (frame_handle_start) {
814 frame_handle_start->hide();
815 frame_handle_end->hide();
818 } if (pixel_width < 2.0) {
820 if (show_vestigial) {
821 vestigial_frame->show();
824 if (name_highlight) {
825 name_highlight->hide();
835 if (frame_handle_start) {
836 frame_handle_start->hide();
837 frame_handle_end->hide();
841 vestigial_frame->hide();
843 if (name_highlight) {
845 double height = name_highlight->property_y2 ();
847 if (height < NAME_HIGHLIGHT_THRESH) {
848 name_highlight->hide();
853 name_highlight->show();
856 reset_name_width (pixel_width);
860 if (visibility & FullWidthNameHighlight) {
861 name_highlight->property_x2() = pixel_width;
863 name_highlight->property_x2() = pixel_width - 1.0;
870 frame->property_x2() = pixel_width;
873 if (frame_handle_start) {
874 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
875 frame_handle_start->hide();
876 frame_handle_end->hide();
878 frame_handle_start->show();
879 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
880 frame_handle_end->show();
881 frame_handle_end->property_x2() = pixel_width;
887 TimeAxisViewItem::reset_name_width (double pixel_width)
889 if (name_text == 0) {
895 ustring ustr = fit_to_pixels (item_name, (int) floor (pixel_width - NAME_X_OFFSET), NAME_FONT, width);
903 /* don't use name for event handling if it leaves no room
904 for trimming to work.
907 if (pixel_width - width < (NAME_X_OFFSET * 2.0)) {
908 if (name_connected) {
909 name_connected = false;
912 if (!name_connected) {
913 name_connected = true;
917 name_text->property_text() = ustr;
923 //---------------------------------------------------------------------------------------//
924 // Handle time axis removal
927 * Handles the Removal of this time axis item
928 * This _needs_ to be called to alert others of the removal properly, ie where the source
929 * of the removal came from.
931 * XXX Although im not too happy about this method of doing things, I cant think of a cleaner method
932 * just now to capture the source of the removal
934 * @param src the identity of the object that initiated the change
937 TimeAxisViewItem::remove_this_item(void* src)
940 defer to idle loop, otherwise we'll delete this object
941 while we're still inside this function ...
943 Glib::signal_idle().connect(bind (sigc::ptr_fun (&TimeAxisViewItem::idle_remove_this_item), this, src));
947 * Callback used to remove this time axis item during the gtk idle loop
948 * This is used to avoid deleting the obejct while inside the remove_this_item
951 * @param item the TimeAxisViewItem to remove
952 * @param src the identity of the object that initiated the change
955 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
957 item->ItemRemoved (item->get_item_name(), src) ; /* EMIT_SIGNAL */