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 & HideFrameLR) {
141 outline_what &= ~(0x1 | 0x2);
144 if (visibility & HideFrameTB) {
145 outline_what &= ~(0x4 | 0x8);
148 frame->property_outline_what() = outline_what;
154 if (visibility & ShowNameHighlight) {
155 name_highlight = new ArdourCanvas::SimpleRect (*group);
156 if (visibility & FullWidthNameHighlight) {
157 name_highlight->property_x1() = (double) 0.0;
158 name_highlight->property_x2() = (double) (trackview.editor.frame_to_pixel(item_duration));
160 name_highlight->property_x1() = (double) 1.0;
161 name_highlight->property_x2() = (double) (trackview.editor.frame_to_pixel(item_duration)) - 1;
163 name_highlight->property_y1() = (double) (trackview.height - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE);
164 name_highlight->property_y2() = (double) (trackview.height - 1);
165 name_highlight->property_outline_color_rgba() = color_map[cNameHighlightFill];
166 name_highlight->property_fill_color_rgba() = color_map[cNameHighlightOutline];
168 name_highlight->set_data ("timeaxisviewitem", this);
174 if (visibility & ShowNameText) {
175 name_text = new ArdourCanvas::Text (*group);
176 name_text->property_x() = (double) TimeAxisViewItem::NAME_X_OFFSET;
177 /* trackview.height is the bottom of the trackview. subtract 1 to get back to the bottom of the highlight,
178 then NAME_Y_OFFSET to position the text in the vertical center of the highlight
180 name_text->property_y() = (double) trackview.height - 1.0 - TimeAxisViewItem::NAME_Y_OFFSET;
181 name_text->property_font_desc() = NAME_FONT;
182 name_text->property_anchor() = Gtk::ANCHOR_NW;
184 name_text->set_data ("timeaxisviewitem", this);
190 /* create our grab handles used for trimming/duration etc */
192 if (visibility & ShowHandles) {
193 frame_handle_start = new ArdourCanvas::SimpleRect (*group);
194 frame_handle_start->property_x1() = (double) 0.0;
195 frame_handle_start->property_x2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH;
196 frame_handle_start->property_y1() = (double) 1.0;
197 frame_handle_start->property_y2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH+1;
198 frame_handle_start->property_outline_color_rgba() = color_map[cFrameHandleStartOutline];
199 frame_handle_start->property_fill_color_rgba() = color_map[cFrameHandleStartFill];
201 frame_handle_end = new ArdourCanvas::SimpleRect (*group);
202 frame_handle_end->property_x1() = (double) (trackview.editor.frame_to_pixel(get_duration())) - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
203 frame_handle_end->property_x2() = (double) trackview.editor.frame_to_pixel(get_duration());
204 frame_handle_end->property_y1() = (double) 1;
205 frame_handle_end->property_y2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH + 1;
206 frame_handle_end->property_outline_color_rgba() = color_map[cFrameHandleEndOutline];
207 frame_handle_end->property_fill_color_rgba() = color_map[cFrameHandleEndFill];
210 frame_handle_start = 0;
211 frame_handle_end = 0;
214 set_color (base_color) ;
216 set_duration (item_duration, this) ;
217 set_position (start, this) ;
224 TimeAxisViewItem::~TimeAxisViewItem()
230 //---------------------------------------------------------------------------------------//
231 // Position and duration Accessors/Mutators
234 * Set the position of this item upon the timeline to the specified value
236 * @param pos the new position
237 * @param src the identity of the object that initiated the change
238 * @return true if the position change was a success, false otherwise
241 TimeAxisViewItem::set_position(jack_nframes_t pos, void* src, double* delta)
243 if (position_locked) {
247 frame_position = pos;
249 /* This sucks. The GnomeCanvas version I am using
250 doesn't correctly implement gnome_canvas_group_set_arg(),
251 so that simply setting the "x" arg of the group
252 fails to move the group. Instead, we have to
253 use gnome_canvas_item_move(), which does the right
254 thing. I see that in GNOME CVS, the current (Sept 2001)
255 version of GNOME Canvas rectifies this issue cleanly.
258 double old_unit_pos ;
259 double new_unit_pos = pos / samples_per_unit ;
261 old_unit_pos = group->property_x();
263 if (new_unit_pos != old_unit_pos) {
264 group->move (new_unit_pos - old_unit_pos, 0.0);
268 (*delta) = new_unit_pos - old_unit_pos;
271 PositionChanged (frame_position, src) ; /* EMIT_SIGNAL */
277 * Return the position of this item upon the timeline
279 * @return the position of this item
282 TimeAxisViewItem::get_position() const
284 return frame_position;
288 * Sets the duration of this item
290 * @param dur the new duration of this item
291 * @param src the identity of the object that initiated the change
292 * @return true if the duration change was succesful, false otherwise
295 TimeAxisViewItem::set_duration (jack_nframes_t dur, void* src)
297 if ((dur > max_item_duration) || (dur < min_item_duration)) {
298 warning << string_compose (_("new duration %1 frames is out of bounds for %2"), get_item_name(), dur)
309 double pixel_width = trackview.editor.frame_to_pixel (dur);
311 reset_width_dependent_items (pixel_width);
313 DurationChanged (dur, src) ; /* EMIT_SIGNAL */
318 * Returns the duration of this item
322 TimeAxisViewItem::get_duration() const
324 return (item_duration);
328 * Sets the maximum duration that this item make have.
330 * @param dur the new maximum duration
331 * @param src the identity of the object that initiated the change
334 TimeAxisViewItem::set_max_duration(jack_nframes_t dur, void* src)
336 max_item_duration = dur ;
337 MaxDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
341 * Returns the maxmimum duration that this item may be set to
343 * @return the maximum duration that this item may be set to
346 TimeAxisViewItem::get_max_duration() const
348 return (max_item_duration) ;
352 * Sets the minimu duration that this item may be set to
354 * @param the minimum duration that this item may be set to
355 * @param src the identity of the object that initiated the change
358 TimeAxisViewItem::set_min_duration(jack_nframes_t dur, void* src)
360 min_item_duration = dur ;
361 MinDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
365 * Returns the minimum duration that this item mey be set to
367 * @return the nimum duration that this item mey be set to
370 TimeAxisViewItem::get_min_duration() const
372 return(min_item_duration) ;
376 * Sets whether the position of this Item is locked to its current position
377 * Locked items cannot be moved until the item is unlocked again.
379 * @param yn set to true to lock this item to its current position
380 * @param src the identity of the object that initiated the change
383 TimeAxisViewItem::set_position_locked(bool yn, void* src)
385 position_locked = yn ;
386 set_trim_handle_colors() ;
387 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
391 * Returns whether this item is locked to its current position
393 * @return true if this item is locked to its current posotion
397 TimeAxisViewItem::get_position_locked() const
399 return (position_locked);
403 * Sets whether the Maximum Duration constraint is active and should be enforced
405 * @param active set true to enforce the max duration constraint
406 * @param src the identity of the object that initiated the change
409 TimeAxisViewItem::set_max_duration_active(bool active, void* src)
411 max_duration_active = active ;
415 * Returns whether the Maximum Duration constraint is active and should be enforced
417 * @return true if the maximum duration constraint is active, false otherwise
420 TimeAxisViewItem::get_max_duration_active() const
422 return(max_duration_active) ;
426 * Sets whether the Minimum Duration constraint is active and should be enforced
428 * @param active set true to enforce the min duration constraint
429 * @param src the identity of the object that initiated the change
432 TimeAxisViewItem::set_min_duration_active(bool active, void* src)
434 min_duration_active = active ;
438 * Returns whether the Maximum Duration constraint is active and should be enforced
440 * @return true if the maximum duration constraint is active, false otherwise
443 TimeAxisViewItem::get_min_duration_active() const
445 return(min_duration_active) ;
448 //---------------------------------------------------------------------------------------//
449 // Name/Id Accessors/Mutators
452 * Set the name/Id of this item.
454 * @param new_name the new name of this item
455 * @param src the identity of the object that initiated the change
458 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
460 if (new_name != item_name) {
461 std::string temp_name = item_name ;
462 item_name = new_name ;
463 NameChanged (item_name, temp_name, src) ; /* EMIT_SIGNAL */
468 * Returns the name/id of this item
470 * @return the name/id of this item
473 TimeAxisViewItem::get_item_name() const
478 //---------------------------------------------------------------------------------------//
482 * Set to true to indicate that this item is currently selected
484 * @param yn true if this item is currently selected
485 * @param src the identity of the object that initiated the change
488 TimeAxisViewItem::set_selected(bool yn)
490 if (_selected != yn) {
491 Selectable::set_selected (yn);
497 TimeAxisViewItem::set_should_show_selection (bool yn)
499 if (should_show_selection != yn) {
500 should_show_selection = yn;
505 //---------------------------------------------------------------------------------------//
506 // Parent Componenet Methods
509 * Returns the TimeAxisView that this item is upon
511 * @return the timeAxisView that this item is placed upon
514 TimeAxisViewItem::get_time_axis_view()
518 //---------------------------------------------------------------------------------------//
522 * Sets the displayed item text
523 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item
525 * @param new_name the new name text to display
528 TimeAxisViewItem::set_name_text(std::string new_name)
531 name_text->property_text() = new_name.c_str();
536 * Set the height of this item
538 * @param h the new height
541 TimeAxisViewItem::set_height(double height)
543 if (name_highlight) {
544 if (height < NAME_HIGHLIGHT_THRESH) {
545 name_highlight->hide();
550 name_highlight->show();
556 if (height > NAME_HIGHLIGHT_SIZE) {
557 name_highlight->property_y1() = (double) height+1 - NAME_HIGHLIGHT_SIZE;
558 name_highlight->property_y2() = (double) height;
561 /* it gets hidden now anyway */
562 name_highlight->property_y1() = (double) 1.0;
563 name_highlight->property_y2() = (double) height;
568 name_text->property_y() = height+1 - NAME_Y_OFFSET;
569 if (height < NAME_HIGHLIGHT_THRESH) {
570 name_text->property_fill_color_rgba() = fill_color;
573 name_text->property_fill_color_rgba() = label_color;
578 frame->property_y2() = height+1;
581 vestigial_frame->property_y2() = height+1;
588 TimeAxisViewItem::set_color(Gdk::Color& base_color)
590 compute_colors (base_color);
598 TimeAxisViewItem::get_canvas_frame()
607 TimeAxisViewItem::get_canvas_group()
616 TimeAxisViewItem::get_name_highlight()
618 return (name_highlight) ;
625 TimeAxisViewItem::get_name_text()
631 * Calculates some contrasting color for displaying various parts of this item, based upon the base color
633 * @param color the base color of the item
636 TimeAxisViewItem::compute_colors(Gdk::Color& base_color)
638 unsigned char radius ;
641 unsigned char r,g,b ;
643 /* FILL: this is simple */
644 r = base_color.get_red()/256 ;
645 g = base_color.get_green()/256 ;
646 b = base_color.get_blue()/256 ;
647 fill_color = RGBA_TO_UINT(r,g,b,255) ;
650 if the overall saturation is strong, make the minor colors light.
651 if its weak, make them dark.
653 we do this by moving an equal distance to the other side of the
654 central circle in the color wheel from where we started.
657 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f)) ;
658 minor_shift = 125 - radius ;
660 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
662 r = base_color.get_red()/256;
663 g = base_color.get_green()/256;
664 b = base_color.get_blue()/256;
670 /* red sector => green */
675 /* green sector => blue */
683 /* blue sector => red */
688 /* green sector => blue */
697 label_color = RGBA_TO_UINT(r,g,b,255);
698 r = (base_color.get_red()/256) + 127 ;
699 g = (base_color.get_green()/256) + 127 ;
700 b = (base_color.get_blue()/256) + 127 ;
702 label_color = RGBA_TO_UINT(r,g,b,255);
704 /* XXX can we do better than this ? */
705 /* We're trying ;) */
708 //frame_color_r = 192;
709 //frame_color_g = 192;
710 //frame_color_b = 194;
712 //selected_frame_color_r = 182;
713 //selected_frame_color_g = 145;
714 //selected_frame_color_b = 168;
716 //handle_color_r = 25 ;
717 //handle_color_g = 0 ;
718 //handle_color_b = 255 ;
719 //lock_handle_color_r = 235 ;
720 //lock_handle_color_g = 16;
721 //lock_handle_color_b = 16;
725 * Convenience method to set the various canvas item colors
728 TimeAxisViewItem::set_colors()
732 double height = NAME_HIGHLIGHT_THRESH;
735 height = frame->property_y2();
738 if (height < NAME_HIGHLIGHT_THRESH) {
739 name_text->property_fill_color_rgba() = fill_color;
742 name_text->property_fill_color_rgba() = label_color;
746 if (name_highlight) {
747 name_highlight->property_fill_color_rgba() = fill_color;
748 name_highlight->property_outline_color_rgba() = fill_color;
750 set_trim_handle_colors() ;
754 * Sets the frame color depending on whether this item is selected
757 TimeAxisViewItem::set_frame_color()
762 if (_selected && should_show_selection) {
763 UINT_TO_RGBA(color_map[cSelectedFrameBase], &r, &g, &b, &a);
764 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity);
766 UINT_TO_RGBA(color_map[cFrameBase], &r, &g, &b, &a);
767 frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity);
773 * Sets the colors of the start and end trim handle depending on object state
777 TimeAxisViewItem::set_trim_handle_colors()
779 if (frame_handle_start) {
780 if (position_locked) {
781 frame_handle_start->property_fill_color_rgba() = color_map[cTrimHandleLockedStart];
782 frame_handle_end->property_fill_color_rgba() = color_map[cTrimHandleLockedEnd];
784 frame_handle_start->property_fill_color_rgba() = color_map[cTrimHandleStart];
785 frame_handle_end->property_fill_color_rgba() = color_map[cTrimHandleEnd];
791 TimeAxisViewItem::get_samples_per_unit()
793 return(samples_per_unit) ;
797 TimeAxisViewItem::set_samples_per_unit (double spu)
799 samples_per_unit = spu ;
800 set_position (this->get_position(), this);
801 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
805 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
807 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
809 if (frame_handle_start) {
810 frame_handle_start->hide();
811 frame_handle_end->hide();
814 } if (pixel_width < 2.0) {
816 if (show_vestigial) {
817 vestigial_frame->show();
820 if (name_highlight) {
821 name_highlight->hide();
831 if (frame_handle_start) {
832 frame_handle_start->hide();
833 frame_handle_end->hide();
837 vestigial_frame->hide();
839 if (name_highlight) {
841 double height = name_highlight->property_y2 ();
843 if (height < NAME_HIGHLIGHT_THRESH) {
844 name_highlight->hide();
849 name_highlight->show();
852 reset_name_width (pixel_width);
856 if (visibility & FullWidthNameHighlight) {
857 name_highlight->property_x2() = pixel_width;
859 name_highlight->property_x2() = pixel_width - 1.0;
866 frame->property_x2() = pixel_width;
869 if (frame_handle_start) {
870 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
871 frame_handle_start->hide();
872 frame_handle_end->hide();
874 frame_handle_start->show();
875 frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
876 frame_handle_end->show();
877 frame_handle_end->property_x2() = pixel_width;
883 TimeAxisViewItem::reset_name_width (double pixel_width)
885 if (name_text == 0) {
891 ustring ustr = fit_to_pixels (item_name, (int) floor (pixel_width - NAME_X_OFFSET), NAME_FONT, width);
899 /* don't use name for event handling if it leaves no room
900 for trimming to work.
903 if (pixel_width - width < (NAME_X_OFFSET * 2.0)) {
904 if (name_connected) {
905 name_connected = false;
908 if (!name_connected) {
909 name_connected = true;
913 name_text->property_text() = ustr;
919 //---------------------------------------------------------------------------------------//
920 // Handle time axis removal
923 * Handles the Removal of this time axis item
924 * This _needs_ to be called to alert others of the removal properly, ie where the source
925 * of the removal came from.
927 * XXX Although im not too happy about this method of doing things, I cant think of a cleaner method
928 * just now to capture the source of the removal
930 * @param src the identity of the object that initiated the change
933 TimeAxisViewItem::remove_this_item(void* src)
936 defer to idle loop, otherwise we'll delete this object
937 while we're still inside this function ...
939 Glib::signal_idle().connect(bind (sigc::ptr_fun (&TimeAxisViewItem::idle_remove_this_item), this, src));
943 * Callback used to remove this time axis item during the gtk idle loop
944 * This is used to avoid deleting the obejct while inside the remove_this_item
947 * @param item the TimeAxisViewItem to remove
948 * @param src the identity of the object that initiated the change
951 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
953 item->ItemRemoved (item->get_item_name(), src) ; /* EMIT_SIGNAL */