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(const string & it_name, ArdourCanvas::Group& parent, TimeAxisView& tv, double spu, Gdk::Color& base_color,
66 jack_nframes_t start, jack_nframes_t duration,
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;
89 warning << "Time Axis Item Duration == 0" << endl ;
92 group = new ArdourCanvas::Group (parent);
94 vestigial_frame = new ArdourCanvas::SimpleRect (*group);
95 vestigial_frame->property_x1() = (double) 0.0;
96 vestigial_frame->property_y1() = (double) 1.0;
97 vestigial_frame->property_x2() = 2.0;
98 vestigial_frame->property_y2() = (double) trackview.height;
99 vestigial_frame->property_outline_color_rgba() = color_map[cVestigialFrameOutline];
100 vestigial_frame->property_fill_color_rgba() = color_map[cVestigialFrameFill];
101 vestigial_frame->hide ();
103 if (visibility & ShowFrame) {
104 frame = new ArdourCanvas::SimpleRect (*group);
105 frame->property_x1() = (double) 0.0;
106 frame->property_y1() = (double) 1.0;
107 frame->property_x2() = (double) trackview.editor.frame_to_pixel(duration);
108 frame->property_y2() = (double) trackview.height;
109 frame->property_outline_color_rgba() = color_map[cTimeAxisFrameOutline];
110 frame->property_fill_color_rgba() = color_map[cTimeAxisFrameFill];
112 /* by default draw all 4 edges */
114 uint32_t outline_what = 0x1|0x2|0x4|0x8;
116 if (visibility & HideFrameLR) {
117 outline_what &= ~(0x1 | 0x2);
120 if (visibility & HideFrameTB) {
121 outline_what &= ~(0x4 | 0x8);
124 frame->property_outline_what() = outline_what;
130 if (visibility & ShowNameHighlight) {
131 name_highlight = new ArdourCanvas::SimpleRect (*group);
132 if (visibility & FullWidthNameHighlight) {
133 name_highlight->property_x1() = (double) 0.0;
134 name_highlight->property_x2() = (double) (trackview.editor.frame_to_pixel(item_duration));
136 name_highlight->property_x1() = (double) 1.0;
137 name_highlight->property_x2() = (double) (trackview.editor.frame_to_pixel(item_duration)) - 1;
139 name_highlight->property_y1() = (double) (trackview.height - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE);
140 name_highlight->property_y2() = (double) (trackview.height - 1);
141 name_highlight->property_outline_color_rgba() = color_map[cNameHighlightFill];
142 name_highlight->property_fill_color_rgba() = color_map[cNameHighlightOutline];
144 name_highlight->set_data ("timeaxisviewitem", this);
150 if (visibility & ShowNameText) {
151 name_text = new ArdourCanvas::Text (*group);
152 name_text->property_x() = (double) TimeAxisViewItem::NAME_X_OFFSET;
153 name_text->property_y() = (double) trackview.height + 1.0 - TimeAxisViewItem::NAME_Y_OFFSET;
154 name_text->property_font_desc() = NAME_FONT;
155 name_text->property_anchor() = Gtk::ANCHOR_NW;
157 name_text->set_data ("timeaxisviewitem", this);
163 /* create our grab handles used for trimming/duration etc */
165 if (visibility & ShowHandles) {
166 frame_handle_start = new ArdourCanvas::SimpleRect (*group);
167 frame_handle_start->property_x1() = (double) 0.0;
168 frame_handle_start->property_x2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH;
169 frame_handle_start->property_y1() = (double) 1.0;
170 frame_handle_start->property_y2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH+1;
171 frame_handle_start->property_outline_color_rgba() = color_map[cFrameHandleStartOutline];
172 frame_handle_start->property_fill_color_rgba() = color_map[cFrameHandleStartFill];
174 frame_handle_end = new ArdourCanvas::SimpleRect (*group);
175 frame_handle_end->property_x1() = (double) (trackview.editor.frame_to_pixel(get_duration())) - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
176 frame_handle_end->property_x2() = (double) trackview.editor.frame_to_pixel(get_duration());
177 frame_handle_end->property_y1() = (double) 1;
178 frame_handle_end->property_y2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH + 1;
179 frame_handle_end->property_outline_color_rgba() = color_map[cFrameHandleEndOutline];
180 frame_handle_end->property_fill_color_rgba() = color_map[cFrameHandleEndFill];
183 frame_handle_start = 0;
184 frame_handle_end = 0;
187 set_color (base_color) ;
189 set_duration (item_duration, this) ;
190 set_position (start, this) ;
197 TimeAxisViewItem::~TimeAxisViewItem()
203 //---------------------------------------------------------------------------------------//
204 // Position and duration Accessors/Mutators
207 * Set the position of this item upon the timeline to the specified value
209 * @param pos the new position
210 * @param src the identity of the object that initiated the change
211 * @return true if the position change was a success, false otherwise
214 TimeAxisViewItem::set_position(jack_nframes_t pos, void* src, double* delta)
216 if (position_locked) {
220 frame_position = pos;
222 /* This sucks. The GnomeCanvas version I am using
223 doesn't correctly implement gnome_canvas_group_set_arg(),
224 so that simply setting the "x" arg of the group
225 fails to move the group. Instead, we have to
226 use gnome_canvas_item_move(), which does the right
227 thing. I see that in GNOME CVS, the current (Sept 2001)
228 version of GNOME Canvas rectifies this issue cleanly.
231 double old_unit_pos ;
232 double new_unit_pos = pos / samples_per_unit ;
234 old_unit_pos = group->property_x();
236 if (new_unit_pos != old_unit_pos) {
237 group->move (new_unit_pos - old_unit_pos, 0.0);
241 (*delta) = new_unit_pos - old_unit_pos;
244 PositionChanged (frame_position, src) ; /* EMIT_SIGNAL */
250 * Return the position of this item upon the timeline
252 * @return the position of this item
255 TimeAxisViewItem::get_position() const
257 return frame_position;
261 * Sets the duration of this item
263 * @param dur the new duration of this item
264 * @param src the identity of the object that initiated the change
265 * @return true if the duration change was succesful, false otherwise
268 TimeAxisViewItem::set_duration (jack_nframes_t dur, void* src)
270 if ((dur > max_item_duration) || (dur < min_item_duration)) {
271 warning << string_compose (_("new duration %1 frames is out of bounds for %2"), get_item_name(), dur)
282 double pixel_width = trackview.editor.frame_to_pixel (dur);
284 reset_width_dependent_items (pixel_width);
286 DurationChanged (dur, src) ; /* EMIT_SIGNAL */
291 * Returns the duration of this item
295 TimeAxisViewItem::get_duration() const
297 return (item_duration);
301 * Sets the maximum duration that this item make have.
303 * @param dur the new maximum duration
304 * @param src the identity of the object that initiated the change
307 TimeAxisViewItem::set_max_duration(jack_nframes_t dur, void* src)
309 max_item_duration = dur ;
310 MaxDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
314 * Returns the maxmimum duration that this item may be set to
316 * @return the maximum duration that this item may be set to
319 TimeAxisViewItem::get_max_duration() const
321 return (max_item_duration) ;
325 * Sets the minimu duration that this item may be set to
327 * @param the minimum duration that this item may be set to
328 * @param src the identity of the object that initiated the change
331 TimeAxisViewItem::set_min_duration(jack_nframes_t dur, void* src)
333 min_item_duration = dur ;
334 MinDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
338 * Returns the minimum duration that this item mey be set to
340 * @return the nimum duration that this item mey be set to
343 TimeAxisViewItem::get_min_duration() const
345 return(min_item_duration) ;
349 * Sets whether the position of this Item is locked to its current position
350 * Locked items cannot be moved until the item is unlocked again.
352 * @param yn set to true to lock this item to its current position
353 * @param src the identity of the object that initiated the change
356 TimeAxisViewItem::set_position_locked(bool yn, void* src)
358 position_locked = yn ;
359 set_trim_handle_colors() ;
360 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
364 * Returns whether this item is locked to its current position
366 * @return true if this item is locked to its current posotion
370 TimeAxisViewItem::get_position_locked() const
372 return (position_locked);
376 * Sets whether the Maximum Duration constraint is active and should be enforced
378 * @param active set true to enforce the max duration constraint
379 * @param src the identity of the object that initiated the change
382 TimeAxisViewItem::set_max_duration_active(bool active, void* src)
384 max_duration_active = active ;
388 * Returns whether the Maximum Duration constraint is active and should be enforced
390 * @return true if the maximum duration constraint is active, false otherwise
393 TimeAxisViewItem::get_max_duration_active() const
395 return(max_duration_active) ;
399 * Sets whether the Minimum Duration constraint is active and should be enforced
401 * @param active set true to enforce the min duration constraint
402 * @param src the identity of the object that initiated the change
405 TimeAxisViewItem::set_min_duration_active(bool active, void* src)
407 min_duration_active = active ;
411 * Returns whether the Maximum Duration constraint is active and should be enforced
413 * @return true if the maximum duration constraint is active, false otherwise
416 TimeAxisViewItem::get_min_duration_active() const
418 return(min_duration_active) ;
421 //---------------------------------------------------------------------------------------//
422 // Name/Id Accessors/Mutators
425 * Set the name/Id 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
431 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
433 if (new_name != item_name) {
434 std::string temp_name = item_name ;
435 item_name = new_name ;
436 NameChanged (item_name, temp_name, src) ; /* EMIT_SIGNAL */
441 * Returns the name/id of this item
443 * @return the name/id of this item
446 TimeAxisViewItem::get_item_name() const
451 //---------------------------------------------------------------------------------------//
455 * Set to true to indicate that this item is currently selected
457 * @param yn true if this item is currently selected
458 * @param src the identity of the object that initiated the change
461 TimeAxisViewItem::set_selected(bool yn, void* src)
463 if (_selected != yn) {
466 Selected (_selected) ; /* EMIT_SIGNAL */
471 * Returns whether this item is currently selected.
473 * @return true if this item is currently selected, false otherwise
476 TimeAxisViewItem::get_selected() const
482 TimeAxisViewItem::set_should_show_selection (bool yn)
484 if (should_show_selection != yn) {
485 should_show_selection = yn;
490 //---------------------------------------------------------------------------------------//
491 // Parent Componenet Methods
494 * Returns the TimeAxisView that this item is upon
496 * @return the timeAxisView that this item is placed upon
499 TimeAxisViewItem::get_time_axis_view()
503 //---------------------------------------------------------------------------------------//
507 * Sets the displayed item text
508 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item
510 * @param new_name the new name text to display
513 TimeAxisViewItem::set_name_text(std::string new_name)
516 name_text->property_text() = new_name.c_str();
521 * Set the height of this item
523 * @param h the new height
526 TimeAxisViewItem::set_height(double height)
528 if (name_highlight) {
529 if (height < NAME_HIGHLIGHT_THRESH) {
530 name_highlight->hide();
535 name_highlight->show();
541 if (height > NAME_HIGHLIGHT_SIZE) {
542 name_highlight->property_y1() = (double) height+1 - NAME_HIGHLIGHT_SIZE;
543 name_highlight->property_y2() = (double) height;
546 /* it gets hidden now anyway */
547 name_highlight->property_y1() = (double) 1.0;
548 name_highlight->property_y2() = (double) height;
553 name_text->property_y() = height+1 - NAME_Y_OFFSET;
554 if (height < NAME_HIGHLIGHT_THRESH) {
555 name_text->property_fill_color_rgba() = fill_color;
558 name_text->property_fill_color_rgba() = label_color;
563 frame->property_y2() = height+1;
566 vestigial_frame->property_y2() = height+1;
573 TimeAxisViewItem::set_color(Gdk::Color& base_color)
575 compute_colors (base_color);
583 TimeAxisViewItem::get_canvas_frame()
592 TimeAxisViewItem::get_canvas_group()
601 TimeAxisViewItem::get_name_highlight()
603 return (name_highlight) ;
610 TimeAxisViewItem::get_name_text()
616 * Calculates some contrasting color for displaying various parts of this item, based upon the base color
618 * @param color the base color of the item
621 TimeAxisViewItem::compute_colors(Gdk::Color& base_color)
623 unsigned char radius ;
626 unsigned char r,g,b ;
628 /* FILL: this is simple */
629 r = base_color.get_red()/256 ;
630 g = base_color.get_green()/256 ;
631 b = base_color.get_blue()/256 ;
632 fill_color = RGBA_TO_UINT(r,g,b,255) ;
635 if the overall saturation is strong, make the minor colors light.
636 if its weak, make them dark.
638 we do this by moving an equal distance to the other side of the
639 central circle in the color wheel from where we started.
642 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f)) ;
643 minor_shift = 125 - radius ;
645 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
647 r = base_color.get_red()/256;
648 g = base_color.get_green()/256;
649 b = base_color.get_blue()/256;
655 /* red sector => green */
660 /* green sector => blue */
668 /* blue sector => red */
673 /* green sector => blue */
682 label_color = RGBA_TO_UINT(r,g,b,255);
683 r = (base_color.get_red()/256) + 127 ;
684 g = (base_color.get_green()/256) + 127 ;
685 b = (base_color.get_blue()/256) + 127 ;
687 label_color = RGBA_TO_UINT(r,g,b,255);
689 /* XXX can we do better than this ? */
690 /* We're trying ;) */
693 //frame_color_r = 192;
694 //frame_color_g = 192;
695 //frame_color_b = 194;
697 //selected_frame_color_r = 182;
698 //selected_frame_color_g = 145;
699 //selected_frame_color_b = 168;
701 //handle_color_r = 25 ;
702 //handle_color_g = 0 ;
703 //handle_color_b = 255 ;
704 //lock_handle_color_r = 235 ;
705 //lock_handle_color_g = 16;
706 //lock_handle_color_b = 16;
710 * Convenience method to set the various canvas item colors
713 TimeAxisViewItem::set_colors()
717 double height = NAME_HIGHLIGHT_THRESH;
720 height = frame->property_y2();
723 if (height < NAME_HIGHLIGHT_THRESH) {
724 name_text->property_fill_color_rgba() = fill_color;
727 name_text->property_fill_color_rgba() = label_color;
731 if (name_highlight) {
732 name_highlight->property_fill_color_rgba() = fill_color;
733 name_highlight->property_outline_color_rgba() = fill_color;
735 set_trim_handle_colors() ;
739 * Sets the frame color depending on whether this item is selected
742 TimeAxisViewItem::set_frame_color()
747 if (_selected && should_show_selection) {
748 UINT_TO_RGBA(color_map[cSelectedFrameBase], &r, &g, &b, &a);
749 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity);
751 UINT_TO_RGBA(color_map[cFrameBase], &r, &g, &b, &a);
752 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity);
758 * Sets the colors of the start and end trim handle depending on object state
762 TimeAxisViewItem::set_trim_handle_colors()
764 if (frame_handle_start) {
765 if (position_locked) {
766 frame_handle_start->property_fill_color_rgba() = color_map[cTrimHandleLockedStart];
767 frame_handle_end->property_fill_color_rgba() = color_map[cTrimHandleLockedEnd];
769 frame_handle_start->property_fill_color_rgba() = color_map[cTrimHandleStart];
770 frame_handle_end->property_fill_color_rgba() = color_map[cTrimHandleEnd];
776 TimeAxisViewItem::get_samples_per_unit()
778 return(samples_per_unit) ;
782 TimeAxisViewItem::set_samples_per_unit (double spu)
784 samples_per_unit = spu ;
785 set_position (this->get_position(), this);
786 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
790 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
792 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
794 if (frame_handle_start) {
795 frame_handle_start->hide();
796 frame_handle_end->hide();
799 } if (pixel_width < 2.0) {
801 if (show_vestigial) {
802 vestigial_frame->show();
805 if (name_highlight) {
806 name_highlight->hide();
816 if (frame_handle_start) {
817 frame_handle_start->hide();
818 frame_handle_end->hide();
822 vestigial_frame->hide();
824 if (name_highlight) {
826 double height = name_highlight->property_y2 ();
828 if (height < NAME_HIGHLIGHT_THRESH) {
829 name_highlight->hide();
834 name_highlight->show();
837 reset_name_width (pixel_width);
841 if (visibility & FullWidthNameHighlight) {
842 name_highlight->property_x2() = pixel_width;
844 name_highlight->property_x2() = pixel_width - 1.0;
851 frame->property_x2() = pixel_width;
854 if (frame_handle_start) {
855 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
856 frame_handle_start->hide();
857 frame_handle_end->hide();
859 frame_handle_start->show();
860 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
861 frame_handle_end->show();
862 frame_handle_end->property_x2() = pixel_width;
868 TimeAxisViewItem::reset_name_width (double pixel_width)
870 if (name_text == 0) {
875 ustring ustr = fit_to_pixels (item_name, (int) floor (pixel_width - NAME_X_OFFSET), NAME_FONT, width);
883 /* don't use name for event handling if it leaves no room
884 for trimming to work.
887 if (pixel_width - width < (NAME_X_OFFSET * 2.0)) {
888 if (name_connected) {
889 name_connected = false;
892 if (!name_connected) {
893 name_connected = true;
897 name_text->property_text() = ustr;
903 //---------------------------------------------------------------------------------------//
904 // Handle time axis removal
907 * Handles the Removal of this time axis item
908 * This _needs_ to be called to alert others of the removal properly, ie where the source
909 * of the removal came from.
911 * XXX Although im not too happy about this method of doing things, I cant think of a cleaner method
912 * just now to capture the source of the removal
914 * @param src the identity of the object that initiated the change
917 TimeAxisViewItem::remove_this_item(void* src)
920 defer to idle loop, otherwise we'll delete this object
921 while we're still inside this function ...
923 Glib::signal_idle().connect(bind (sigc::ptr_fun (&TimeAxisViewItem::idle_remove_this_item), this, src));
927 * Callback used to remove this time axis item during the gtk idle loop
928 * This is used to avoid deleting the obejct while inside the remove_this_item
931 * @param item the TimeAxisViewItem to remove
932 * @param src the identity of the object that initiated the change
935 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
937 item->ItemRemoved(item->get_item_name(), src) ; /* EMIT_SIGNAL */