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 "rgb_macros.h"
36 using namespace Editing;
39 //------------------------------------------------------------------------------
40 /** Initialize static memeber data */
41 Pango::FontDescription TimeAxisViewItem::NAME_FONT;
42 bool TimeAxisViewItem::have_name_font = false;
43 const double TimeAxisViewItem::NAME_X_OFFSET = 15.0;
44 const double TimeAxisViewItem::NAME_Y_OFFSET = 15.0 ; /* XXX depends a lot on the font size, sigh. */
45 const double TimeAxisViewItem::NAME_HIGHLIGHT_SIZE = 15.0 ; /* ditto */
46 const double TimeAxisViewItem::NAME_HIGHLIGHT_THRESH = 32.0 ; /* ditto */
47 const double TimeAxisViewItem::GRAB_HANDLE_LENGTH = 6 ;
50 //---------------------------------------------------------------------------------------//
51 // Constructor / Desctructor
54 * Constructs a new TimeAxisViewItem.
56 * @param it_name the unique name/Id of this item
57 * @param parant the parent canvas group
58 * @param tv the TimeAxisView we are going to be added to
59 * @param spu samples per unit
61 * @param start the start point of this item
62 * @param duration the duration of this item
64 TimeAxisViewItem::TimeAxisViewItem(std::string it_name, Gnome::Canvas::Group& parent, TimeAxisView& tv, double spu, Gdk::Color& base_color,
65 jack_nframes_t start, jack_nframes_t duration,
66 Visibility visibility)
69 if (!have_name_font) {
70 NAME_FONT = get_font_for_style (N_("TimeAxisViewItemName"));
71 have_name_font = true;
75 samples_per_unit = spu ;
76 should_show_selection = true;
77 frame_position = start ;
78 item_duration = duration ;
79 name_connected = false;
81 position_locked = false ;
82 max_item_duration = ARDOUR::max_frames;
83 min_item_duration = 0 ;
84 show_vestigial = true;
87 warning << "Time Axis Item Duration == 0" << endl ;
90 group = new Gnome::Canvas::Group (parent);
92 vestigial_frame = new Gnome::Canvas::SimpleRect (*group);
93 vestigial_frame->set_property ("x1", (double) 0.0);
94 vestigial_frame->set_property ("y1", (double) 1.0);
95 vestigial_frame->set_property ("x2", 2.0);
96 vestigial_frame->set_property ("y2", (double) trackview.height);
97 vestigial_frame->set_property ("outline_color_rgba", color_map[cVestigialFrameOutline]);
98 vestigial_frame->set_property ("fill_color_rgba", color_map[cVestigialFrameFill]);
99 vestigial_frame->hide ();
101 if (visibility & ShowFrame) {
102 frame = new Gnome::Canvas::SimpleRect (*group);
103 frame->set_property ("x1", (double) 0.0);
104 frame->set_property ("y1", (double) 1.0);
105 frame->set_property ("x2", (double) trackview.editor.frame_to_pixel(duration));
106 frame->set_property ("y2", (double) trackview.height);
107 frame->set_property ("outline_color_rgba", color_map[cTimeAxisFrameOutline]);
108 frame->set_property ("fill_color_rgba", color_map[cTimeAxisFrameFill]);
114 if (visibility & ShowNameHighlight) {
115 name_highlight = new Gnome::Canvas::SimpleRect (*group);
116 name_highlight->set_property ("x1", (double) 1.0);
117 name_highlight->set_property ("x2", (double) (trackview.editor.frame_to_pixel(item_duration)) - 1);
118 name_highlight->set_property ("y1", (double) (trackview.height - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE));
119 name_highlight->set_property ("y2", (double) (trackview.height - 1));
120 name_highlight->set_property ("outline_color_rgba", color_map[cNameHighlightFill]);
121 name_highlight->set_property ("fill_color_rgba", color_map[cNameHighlightOutline]);
123 name_highlight->set_data ("timeaxisviewitem", this);
129 if (visibility & ShowNameText) {
130 name_text = new Gnome::Canvas::Text (*group);
131 name_text->set_property ("x", (double) TimeAxisViewItem::NAME_X_OFFSET);
132 name_text->set_property ("y", (double) trackview.height + 1.0 - TimeAxisViewItem::NAME_Y_OFFSET);
133 name_text->set_property ("font", NAME_FONT);
134 name_text->set_property ("anchor", GTK_ANCHOR_NW);
136 name_text->set_data ("timeaxisviewitem", this);
142 /* create our grab handles used for trimming/duration etc */
144 if (visibility & ShowHandles) {
145 frame_handle_start = new Gnome::Canvas::SimpleRect (*group);
146 frame_handle_start->set_property ("x1", (double) 0.0);
147 frame_handle_start->set_property ("x2", (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH);
148 frame_handle_start->set_property ("y1", (double) 1.0);
149 frame_handle_start->set_property ("y2", (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH+1);
150 frame_handle_start->set_property ("outline_color_rgba", color_map[cFrameHandleStartOutline]);
151 frame_handle_start->set_property ("fill_color_rgba", color_map[cFrameHandleStartFill]);
153 frame_handle_end = new Gnome::Canvas::SimpleRect (*group);
154 frame_handle_end->set_property ("x1", (double) (trackview.editor.frame_to_pixel(get_duration())) - (TimeAxisViewItem::GRAB_HANDLE_LENGTH));
155 frame_handle_end->set_property ("x2", (double) trackview.editor.frame_to_pixel(get_duration()));
156 frame_handle_end->set_property ("y1", (double) 1);
157 frame_handle_end->set_property ("y2", (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH + 1);
158 frame_handle_end->set_property ("outline_color_rgba", color_map[cFrameHandleEndOutline]);
159 frame_handle_end->set_property ("fill_color_rgba", color_map[cFrameHandleEndFill]);
162 frame_handle_start = 0;
163 frame_handle_end = 0;
166 set_color (base_color) ;
168 set_duration (item_duration, this) ;
169 set_position (start, this) ;
176 TimeAxisViewItem::~TimeAxisViewItem()
182 //---------------------------------------------------------------------------------------//
183 // Position and duration Accessors/Mutators
186 * Set the position of this item upon the timeline to the specified value
188 * @param pos the new position
189 * @param src the identity of the object that initiated the change
190 * @return true if the position change was a success, false otherwise
193 TimeAxisViewItem::set_position(jack_nframes_t pos, void* src, double* delta)
195 if (position_locked) {
199 frame_position = pos;
201 /* This sucks. The GnomeCanvas version I am using
202 doesn't correctly implement gnome_canvas_group_set_arg(),
203 so that simply setting the "x" arg of the group
204 fails to move the group. Instead, we have to
205 use gnome_canvas_item_move(), which does the right
206 thing. I see that in GNOME CVS, the current (Sept 2001)
207 version of GNOME Canvas rectifies this issue cleanly.
210 double old_unit_pos ;
211 double new_unit_pos = pos / samples_per_unit ;
213 old_unit_pos = group->property_x();
215 if (new_unit_pos != old_unit_pos) {
216 group->move (new_unit_pos - old_unit_pos, 0.0);
220 (*delta) = new_unit_pos - old_unit_pos;
223 PositionChanged (frame_position, src) ; /* EMIT_SIGNAL */
229 * Return the position of this item upon the timeline
231 * @return the position of this item
234 TimeAxisViewItem::get_position() const
236 return frame_position;
240 * Sets the duration of this item
242 * @param dur the new duration of this item
243 * @param src the identity of the object that initiated the change
244 * @return true if the duration change was succesful, false otherwise
247 TimeAxisViewItem::set_duration (jack_nframes_t dur, void* src)
249 if ((dur > max_item_duration) || (dur < min_item_duration)) {
250 warning << string_compose (_("new duration %1 frames is out of bounds for %2"), get_item_name(), dur)
261 double pixel_width = trackview.editor.frame_to_pixel (dur);
263 reset_width_dependent_items (pixel_width);
265 DurationChanged (dur, src) ; /* EMIT_SIGNAL */
270 * Returns the duration of this item
274 TimeAxisViewItem::get_duration() const
276 return (item_duration);
280 * Sets the maximum duration that this item make have.
282 * @param dur the new maximum duration
283 * @param src the identity of the object that initiated the change
286 TimeAxisViewItem::set_max_duration(jack_nframes_t dur, void* src)
288 max_item_duration = dur ;
289 MaxDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
293 * Returns the maxmimum duration that this item may be set to
295 * @return the maximum duration that this item may be set to
298 TimeAxisViewItem::get_max_duration() const
300 return (max_item_duration) ;
304 * Sets the minimu duration that this item may be set to
306 * @param the minimum duration that this item may be set to
307 * @param src the identity of the object that initiated the change
310 TimeAxisViewItem::set_min_duration(jack_nframes_t dur, void* src)
312 min_item_duration = dur ;
313 MinDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
317 * Returns the minimum duration that this item mey be set to
319 * @return the nimum duration that this item mey be set to
322 TimeAxisViewItem::get_min_duration() const
324 return(min_item_duration) ;
328 * Sets whether the position of this Item is locked to its current position
329 * Locked items cannot be moved until the item is unlocked again.
331 * @param yn set to true to lock this item to its current position
332 * @param src the identity of the object that initiated the change
335 TimeAxisViewItem::set_position_locked(bool yn, void* src)
337 position_locked = yn ;
338 set_trim_handle_colors() ;
339 PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
343 * Returns whether this item is locked to its current position
345 * @return true if this item is locked to its current posotion
349 TimeAxisViewItem::get_position_locked() const
351 return (position_locked);
355 * Sets whether the Maximum Duration constraint is active and should be enforced
357 * @param active set true to enforce the max duration constraint
358 * @param src the identity of the object that initiated the change
361 TimeAxisViewItem::set_max_duration_active(bool active, void* src)
363 max_duration_active = active ;
367 * Returns whether the Maximum Duration constraint is active and should be enforced
369 * @return true if the maximum duration constraint is active, false otherwise
372 TimeAxisViewItem::get_max_duration_active() const
374 return(max_duration_active) ;
378 * Sets whether the Minimum Duration constraint is active and should be enforced
380 * @param active set true to enforce the min duration constraint
381 * @param src the identity of the object that initiated the change
384 TimeAxisViewItem::set_min_duration_active(bool active, void* src)
386 min_duration_active = active ;
390 * Returns whether the Maximum Duration constraint is active and should be enforced
392 * @return true if the maximum duration constraint is active, false otherwise
395 TimeAxisViewItem::get_min_duration_active() const
397 return(min_duration_active) ;
400 //---------------------------------------------------------------------------------------//
401 // Name/Id Accessors/Mutators
404 * Set the name/Id of this item.
406 * @param new_name the new name of this item
407 * @param src the identity of the object that initiated the change
410 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
412 if (new_name != item_name) {
413 std::string temp_name = item_name ;
414 item_name = new_name ;
415 NameChanged (item_name, temp_name, src) ; /* EMIT_SIGNAL */
420 * Returns the name/id of this item
422 * @return the name/id of this item
425 TimeAxisViewItem::get_item_name() const
430 //---------------------------------------------------------------------------------------//
434 * Set to true to indicate that this item is currently selected
436 * @param yn true if this item is currently selected
437 * @param src the identity of the object that initiated the change
440 TimeAxisViewItem::set_selected(bool yn, void* src)
442 if (_selected != yn) {
445 Selected (_selected) ; /* EMIT_SIGNAL */
450 * Returns whether this item is currently selected.
452 * @return true if this item is currently selected, false otherwise
455 TimeAxisViewItem::get_selected() const
461 TimeAxisViewItem::set_should_show_selection (bool yn)
463 if (should_show_selection != yn) {
464 should_show_selection = yn;
469 //---------------------------------------------------------------------------------------//
470 // Parent Componenet Methods
473 * Returns the TimeAxisView that this item is upon
475 * @return the timeAxisView that this item is placed upon
478 TimeAxisViewItem::get_time_axis_view()
482 //---------------------------------------------------------------------------------------//
486 * Sets the displayed item text
487 * This item is the visual text name displayed on the canvas item, this can be different to the name of the item
489 * @param new_name the new name text to display
492 TimeAxisViewItem::set_name_text(std::string new_name)
495 name_text->set_property ("text", new_name.c_str());
500 * Set the height of this item
502 * @param h the new height
505 TimeAxisViewItem::set_height(double height)
507 if (name_highlight) {
508 if (height < NAME_HIGHLIGHT_THRESH) {
509 name_highlight->hide();
512 name_highlight->show();
516 if (height > NAME_HIGHLIGHT_SIZE) {
517 name_highlight->set_property ("y1", (double) height+1 - NAME_HIGHLIGHT_SIZE);
518 name_highlight->set_property ("y2", (double) height);
521 /* it gets hidden now anyway */
522 name_highlight->set_property ("y1", (double) 1.0);
523 name_highlight->set_property ("y2", (double) height);
528 name_text->set_property ("y", height+1 - NAME_Y_OFFSET);
529 if (height < NAME_HIGHLIGHT_THRESH) {
530 name_text->set_property ("fill_color_rgba", fill_color) ;
533 name_text->set_property ("fill_color_rgba", label_color) ;
538 frame->set_property ("y2", height+1) ;
541 vestigial_frame->set_property ("y2", height+1) ;
548 TimeAxisViewItem::set_color(Gdk::Color& base_color)
550 compute_colors (base_color);
558 TimeAxisViewItem::get_canvas_frame()
567 TimeAxisViewItem::get_canvas_group()
576 TimeAxisViewItem::get_name_highlight()
578 return(name_highlight) ;
585 TimeAxisViewItem::get_name_text()
591 * Calculates some contrasting color for displaying various parts of this item, based upon the base color
593 * @param color the base color of the item
596 TimeAxisViewItem::compute_colors(Gdk::Color& base_color)
598 unsigned char radius ;
601 unsigned char r,g,b ;
603 /* FILL: this is simple */
604 r = base_color.get_red()/256 ;
605 g = base_color.get_green()/256 ;
606 b = base_color.get_blue()/256 ;
607 fill_color = RGBA_TO_UINT(r,g,b,255) ;
610 if the overall saturation is strong, make the minor colors light.
611 if its weak, make them dark.
613 we do this by moving an equal distance to the other side of the
614 central circle in the color wheel from where we started.
617 radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f)) ;
618 minor_shift = 125 - radius ;
620 /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
622 r = base_color.get_red()/256;
623 g = base_color.get_green()/256;
624 b = base_color.get_blue()/256;
630 /* red sector => green */
635 /* green sector => blue */
643 /* blue sector => red */
648 /* green sector => blue */
657 label_color = RGBA_TO_UINT(r,g,b,255);
658 r = (base_color.get_red()/256) + 127 ;
659 g = (base_color.get_green()/256) + 127 ;
660 b = (base_color.get_blue()/256) + 127 ;
662 label_color = RGBA_TO_UINT(r,g,b,255);
664 /* XXX can we do better than this ? */
665 /* We're trying ;) */
668 //frame_color_r = 192;
669 //frame_color_g = 192;
670 //frame_color_b = 194;
672 //selected_frame_color_r = 182;
673 //selected_frame_color_g = 145;
674 //selected_frame_color_b = 168;
676 //handle_color_r = 25 ;
677 //handle_color_g = 0 ;
678 //handle_color_b = 255 ;
679 //lock_handle_color_r = 235 ;
680 //lock_handle_color_g = 16;
681 //lock_handle_color_b = 16;
685 * Convenience method to set the various canvas item colors
688 TimeAxisViewItem::set_colors()
692 double height = NAME_HIGHLIGHT_THRESH;
695 height = frame->property_y2();
698 if (height < NAME_HIGHLIGHT_THRESH) {
699 name_text->set_property ("fill_color_rgba", fill_color);
702 name_text->set_property ("fill_color_rgba", label_color);
706 if (name_highlight) {
707 name_highlight->set_property ("fill_color_rgba", fill_color);
708 name_highlight->set_property ("outline_color_rgba", fill_color);
710 set_trim_handle_colors() ;
714 * Sets the frame color depending on whether this item is selected
717 TimeAxisViewItem::set_frame_color()
722 if (_selected && should_show_selection) {
723 UINT_TO_RGBA(color_map[cSelectedFrameBase], &r, &g, &b, &a);
724 frame->set_property ("fill_color_rgba", RGBA_TO_UINT(r, g, b, fill_opacity));
726 UINT_TO_RGBA(color_map[cFrameBase], &r, &g, &b, &a);
727 frame->set_property ("fill_color_rgba", RGBA_TO_UINT(r, g, b, fill_opacity));
733 * Sets the colors of the start and end trim handle depending on object state
737 TimeAxisViewItem::set_trim_handle_colors()
739 if (frame_handle_start) {
740 if (position_locked) {
741 frame_handle_start->set_property ("fill_color_rgba", color_map[cTrimHandleLockedStart]);
742 frame_handle_end->set_property ("fill_color_rgba", color_map[cTrimHandleLockedEnd]);
744 frame_handle_start->set_property ("fill_color_rgba", color_map[cTrimHandleStart]);
745 frame_handle_end->set_property ("fill_color_rgba", color_map[cTrimHandleEnd]);
751 TimeAxisViewItem::get_samples_per_unit()
753 return(samples_per_unit) ;
757 TimeAxisViewItem::set_samples_per_unit (double spu)
759 samples_per_unit = spu ;
760 set_position (this->get_position(), this);
761 reset_width_dependent_items ((double)get_duration() / samples_per_unit);
765 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
767 if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
769 if (frame_handle_start) {
770 frame_handle_start->hide();
771 frame_handle_end->hide();
774 } if (pixel_width < 2.0) {
776 if (show_vestigial) {
777 vestigial_frame->show();
780 if (name_highlight) {
781 name_highlight->hide();
789 if (frame_handle_start) {
790 frame_handle_start->hide();
791 frame_handle_end->hide();
795 vestigial_frame->hide();
797 if (name_highlight) {
799 double height = name_highlight->property_y2 ();
801 if (height < NAME_HIGHLIGHT_THRESH) {
802 name_highlight->hide();
805 name_highlight->show();
807 reset_name_width (pixel_width);
810 name_highlight->set_property ("x2", pixel_width - 1.0);
815 frame->set_property ("x2", pixel_width);
818 if (frame_handle_start) {
819 if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
820 frame_handle_start->hide();
821 frame_handle_end->hide();
823 frame_handle_start->show();
824 frame_handle_end->set_property ("x1", pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH));
825 frame_handle_end->show();
826 frame_handle_end->set_property ("x2", pixel_width);
832 TimeAxisViewItem::reset_name_width (double pixel_width)
837 Pango::FontDescription fd (NAME_FONT);
839 if (name_text == 0) {
844 int namelen = ustr.length();
846 Glib::RefPtr<Pango::Layout> layout = group->get_canvas()->create_pango_layout (ustr);
847 layout->set_font_description (fd);
851 layout->set_text (ustr);
852 layout->get_pixel_size (width, height);
854 if (width < (pixel_width - NAME_X_OFFSET)) {
859 ustr = ustr.substr (0, namelen);
868 /* don't use name for event handling if it leaves no room
869 for trimming to work.
872 if (pixel_width - width < (NAME_X_OFFSET * 2.0)) {
873 if (name_connected) {
874 name_connected = false;
877 if (!name_connected) {
878 name_connected = true;
882 name_text->property_text() = ustr;
888 //---------------------------------------------------------------------------------------//
889 // Handle time axis removal
892 * Handles the Removal of this time axis item
893 * This _needs_ to be called to alert others of the removal properly, ie where the source
894 * of the removal came from.
896 * XXX Although im not too happy about this method of doing things, I cant think of a cleaner method
897 * just now to capture the source of the removal
899 * @param src the identity of the object that initiated the change
902 TimeAxisViewItem::remove_this_item(void* src)
905 defer to idle loop, otherwise we'll delete this object
906 while we're still inside this function ...
908 Glib::signal_idle().connect(bind (sigc::ptr_fun (&TimeAxisViewItem::idle_remove_this_item), this, src));
912 * Callback used to remove this time axis item during the gtk idle loop
913 * This is used to avoid deleting the obejct while inside the remove_this_item
916 * @param item the TimeAxisViewItem to remove
917 * @param src the identity of the object that initiated the change
920 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
922 item->ItemRemoved(item->get_item_name(), src) ; /* EMIT_SIGNAL */