an awful lot of tweaks to drawing details
[ardour.git] / gtk2_ardour / time_axis_view_item.cc
1 /*
2     Copyright (C) 2003 Paul Davis
3
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.
8
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.
13
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.
17
18 */
19
20 #include <utility>
21
22 #include "pbd/error.h"
23 #include "pbd/stacktrace.h"
24
25 #include "ardour/types.h"
26 #include "ardour/ardour.h"
27
28 #include "gtkmm2ext/utils.h"
29 #include "gtkmm2ext/gui_thread.h"
30
31 #include "canvas/group.h"
32 #include "canvas/rectangle.h"
33 #include "canvas/debug.h"
34 #include "canvas/text.h"
35 #include "canvas/utils.h"
36
37 #include "ardour_ui.h"
38 /*
39  * ardour_ui.h was moved up in the include list
40  * due to a conflicting definition of 'Rect' between
41  * Apple's MacTypes.h file and GTK
42  */
43
44 #include "public_editor.h"
45 #include "time_axis_view_item.h"
46 #include "time_axis_view.h"
47 #include "utils.h"
48 #include "rgb_macros.h"
49
50 #include "i18n.h"
51
52 using namespace std;
53 using namespace Editing;
54 using namespace Glib;
55 using namespace PBD;
56 using namespace ARDOUR;
57 using namespace Gtkmm2ext;
58
59 Pango::FontDescription TimeAxisViewItem::NAME_FONT;
60 const double TimeAxisViewItem::NAME_X_OFFSET = 15.0;
61 const double TimeAxisViewItem::GRAB_HANDLE_TOP = 6;
62 const double TimeAxisViewItem::GRAB_HANDLE_WIDTH = 5;
63
64 int    TimeAxisViewItem::NAME_HEIGHT;
65 double TimeAxisViewItem::NAME_Y_OFFSET;
66 double TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
67 double TimeAxisViewItem::NAME_HIGHLIGHT_THRESH;
68
69 void
70 TimeAxisViewItem::set_constant_heights ()
71 {
72         NAME_FONT = get_font_for_style (X_("TimeAxisViewItemName"));
73
74         Gtk::Window win;
75         Gtk::Label foo;
76         win.add (foo);
77
78         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (X_("Hg")); /* ascender + descender */
79         int width = 0;
80         int height = 0;
81
82         layout->set_font_description (NAME_FONT);
83         Gtkmm2ext::get_ink_pixel_size (layout, width, height);
84
85         NAME_HEIGHT = height;
86         NAME_Y_OFFSET = height + 5; // XXX this offset is magic
87         NAME_HIGHLIGHT_SIZE = height + 2;
88         NAME_HIGHLIGHT_THRESH = NAME_HIGHLIGHT_SIZE * 3;
89 }
90
91 /**
92  * Construct a new TimeAxisViewItem.
93  *
94  * @param it_name the unique name of this item
95  * @param parent the parent canvas group
96  * @param tv the TimeAxisView we are going to be added to
97  * @param spu samples per unit
98  * @param base_color
99  * @param start the start point of this item
100  * @param duration the duration of this item
101  * @param recording true if this is a recording region view
102  * @param automation true if this is an automation region view
103  */
104 TimeAxisViewItem::TimeAxisViewItem(
105         const string & it_name, ArdourCanvas::Group& parent, TimeAxisView& tv, double spu, Gdk::Color const & base_color,
106         framepos_t start, framecnt_t duration, bool recording, bool automation, Visibility vis
107         )
108         : trackview (tv)
109         , _height (1.0)
110         , _recregion (recording)
111         , _automation (automation)
112         , _dragging (false)
113 {
114         init (it_name, &parent, spu, base_color, start, duration, vis, true, true);
115 }
116
117 TimeAxisViewItem::TimeAxisViewItem (const TimeAxisViewItem& other)
118         : trackable (other)
119         , Selectable (other)
120         , PBD::ScopedConnectionList()
121         , trackview (other.trackview)
122         , _recregion (other._recregion)
123         , _automation (other._automation)
124         , _dragging (other._dragging)
125 {
126
127         Gdk::Color c;
128         int r,g,b,a;
129
130         UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
131         c.set_rgb_p (r/255.0, g/255.0, b/255.0);
132
133         /* share the other's parent, but still create a new group */
134
135         ArdourCanvas::Group* parent = other.group->parent();
136         
137         _selected = other._selected;
138         
139         init (other.item_name, parent, other.samples_per_pixel, c, other.frame_position,
140               other.item_duration, other.visibility, other.wide_enough_for_name, other.high_enough_for_name);
141 }
142
143 void
144 TimeAxisViewItem::init (const string& it_name, ArdourCanvas::Group* parent, double fpp, Gdk::Color const & base_color, 
145                         framepos_t start, framepos_t duration, Visibility vis, 
146                         bool wide, bool high)
147 {
148         group = new ArdourCanvas::Group (parent);
149         CANVAS_DEBUG_NAME (group, string_compose ("TAVI group for %1", get_item_name()));
150         group->Event.connect (sigc::mem_fun (*this, &TimeAxisViewItem::canvas_group_event));
151
152         item_name = it_name;
153         samples_per_pixel = fpp;
154         frame_position = start;
155         item_duration = duration;
156         name_connected = false;
157         fill_opacity = 60;
158         position_locked = false;
159         max_item_duration = ARDOUR::max_framepos;
160         min_item_duration = 0;
161         show_vestigial = true;
162         visibility = vis;
163         _sensitive = true;
164         name_text_width = 0;
165         last_item_width = 0;
166         wide_enough_for_name = wide;
167         high_enough_for_name = high;
168         rect_visible = true;
169
170         if (duration == 0) {
171                 warning << "Time Axis Item Duration == 0" << endl;
172         }
173
174         vestigial_frame = new ArdourCanvas::Rectangle (group, ArdourCanvas::Rect (0.0, 1.0, 2.0, trackview.current_height()));
175         CANVAS_DEBUG_NAME (vestigial_frame, string_compose ("vestigial frame for %1", get_item_name()));
176         vestigial_frame->hide ();
177         vestigial_frame->set_outline_color (ARDOUR_UI::config()->get_canvasvar_VestigialFrame());
178         vestigial_frame->set_fill_color (ARDOUR_UI::config()->get_canvasvar_VestigialFrame());
179
180         if (visibility & ShowFrame) {
181                 frame = new ArdourCanvas::Rectangle (group, 
182                                                      ArdourCanvas::Rect (0.0, 1.0, 
183                                                                          trackview.editor().sample_to_pixel(duration), 
184                                                                          trackview.current_height()));
185                 CANVAS_DEBUG_NAME (frame, string_compose ("frame for %1", get_item_name()));
186
187                 if (_recregion) {
188                         frame->set_outline_color (ARDOUR_UI::config()->get_canvasvar_RecordingRect());
189                 } else {
190                         frame->set_outline_color (ARDOUR_UI::config()->get_canvasvar_TimeAxisFrame());
191                 }
192
193         } else {
194
195                 frame = 0;
196         }
197
198         if (visibility & ShowNameHighlight) {
199
200                 if (visibility & FullWidthNameHighlight) {
201                         name_highlight = new ArdourCanvas::Rectangle (group, 
202                                                                       ArdourCanvas::Rect (0.0, trackview.editor().sample_to_pixel(item_duration),
203                                                                                           trackview.current_height() - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE, 
204                                                                                           trackview.current_height()));
205                         CANVAS_DEBUG_NAME (name_highlight, string_compose ("name highlight for %1", get_item_name()));
206                 } else {
207                         name_highlight = new ArdourCanvas::Rectangle (group, 
208                                                                       ArdourCanvas::Rect (1.0, trackview.editor().sample_to_pixel(item_duration) - 1, 
209                                                                                           trackview.current_height() - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE, 
210                                                                                           trackview.current_height()));
211                         CANVAS_DEBUG_NAME (name_highlight, string_compose ("name highlight for %1", get_item_name()));
212                 }
213
214                 name_highlight->set_data ("timeaxisviewitem", this);
215                 name_highlight->set_outline_what (ArdourCanvas::Rectangle::TOP);
216                 /* we should really use a canvas color property here */
217                 name_highlight->set_outline_color (RGBA_TO_UINT (0,0,0,255));
218
219         } else {
220                 name_highlight = 0;
221         }
222
223         if (visibility & ShowNameText) {
224                 name_text = new ArdourCanvas::Text (group);
225                 CANVAS_DEBUG_NAME (name_text, string_compose ("name text for %1", get_item_name()));
226                 name_text->set_position (ArdourCanvas::Duple (NAME_X_OFFSET, trackview.current_height() - NAME_Y_OFFSET));
227                 name_text->set_font_description (NAME_FONT);
228                 
229         } else {
230                 name_text = 0;
231         }
232
233         /* create our grab handles used for trimming/duration etc */
234         if (!_recregion && !_automation) {
235                 double top   = TimeAxisViewItem::GRAB_HANDLE_TOP;
236                 double width = TimeAxisViewItem::GRAB_HANDLE_WIDTH;
237
238                 frame_handle_start = new ArdourCanvas::Rectangle (group, ArdourCanvas::Rect (0.0, top, width, trackview.current_height()));
239                 CANVAS_DEBUG_NAME (frame_handle_start, "TAVI frame handle start");
240                 frame_handle_start->set_outline (false);
241                 frame_handle_start->set_fill (false);
242                 frame_handle_start->Event.connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisViewItem::frame_handle_crossing), frame_handle_start));
243
244                 frame_handle_end = new ArdourCanvas::Rectangle (group, ArdourCanvas::Rect (0.0, top, width, trackview.current_height()));
245                 CANVAS_DEBUG_NAME (frame_handle_end, "TAVI frame handle end");
246                 frame_handle_end->set_outline (false);
247                 frame_handle_end->set_fill (false);
248                 frame_handle_end->Event.connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisViewItem::frame_handle_crossing), frame_handle_end));
249         } else {
250                 frame_handle_start = frame_handle_end = 0;
251         }
252
253         set_color (base_color);
254
255         set_duration (item_duration, this);
256         set_position (start, this);
257
258         Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&TimeAxisViewItem::parameter_changed, this, _1), gui_context ());
259         ARDOUR_UI::config()->ParameterChanged.connect (sigc::mem_fun (*this, &TimeAxisViewItem::parameter_changed));
260 }
261
262 TimeAxisViewItem::~TimeAxisViewItem()
263 {
264         delete group;
265 }
266
267 bool
268 TimeAxisViewItem::canvas_group_event (GdkEvent* /*ev*/)
269 {
270         return false;
271 }
272
273 void
274 TimeAxisViewItem::hide_rect ()
275 {
276         rect_visible = false;
277         set_frame_color ();
278
279         if (name_highlight) {
280                 name_highlight->set_outline_what (ArdourCanvas::Rectangle::What (0));
281                 name_highlight->set_fill_color (UINT_RGBA_CHANGE_A (fill_color, 64));
282         }
283 }
284
285 void
286 TimeAxisViewItem::show_rect ()
287 {
288         rect_visible = true;
289         set_frame_color ();
290
291         if (name_highlight) {
292                 name_highlight->set_outline_what (ArdourCanvas::Rectangle::TOP);
293                 name_highlight->set_fill_color (fill_color);
294         }
295 }
296
297 /**
298  * Set the position of this item on the timeline.
299  *
300  * @param pos the new position
301  * @param src the identity of the object that initiated the change
302  * @return true on success
303  */
304
305 bool
306 TimeAxisViewItem::set_position(framepos_t pos, void* src, double* delta)
307 {
308         if (position_locked) {
309                 return false;
310         }
311
312         frame_position = pos;
313
314         /*  This sucks. The GnomeCanvas version I am using
315             doesn't correctly implement gnome_canvas_group_set_arg(),
316             so that simply setting the "x" arg of the group
317             fails to move the group. Instead, we have to
318             use gnome_canvas_item_move(), which does the right
319             thing. I see that in GNOME CVS, the current (Sept 2001)
320             version of GNOME Canvas rectifies this issue cleanly.
321         */
322
323         double old_unit_pos;
324         double new_unit_pos = pos / samples_per_pixel;
325
326         old_unit_pos = group->position().x;
327
328         if (new_unit_pos != old_unit_pos) {
329                 group->set_x_position (new_unit_pos);
330         }
331
332         if (delta) {
333                 (*delta) = new_unit_pos - old_unit_pos;
334         }
335
336         PositionChanged (frame_position, src); /* EMIT_SIGNAL */
337
338         return true;
339 }
340
341 /** @return position of this item on the timeline */
342 framepos_t
343 TimeAxisViewItem::get_position() const
344 {
345         return frame_position;
346 }
347
348 /**
349  * Set the duration of this item.
350  *
351  * @param dur the new duration of this item
352  * @param src the identity of the object that initiated the change
353  * @return true on success
354  */
355
356 bool
357 TimeAxisViewItem::set_duration (framecnt_t dur, void* src)
358 {
359         if ((dur > max_item_duration) || (dur < min_item_duration)) {
360                 warning << string_compose (
361                                 P_("new duration %1 frame is out of bounds for %2", "new duration of %1 frames is out of bounds for %2", dur),
362                                 get_item_name(), dur)
363                         << endmsg;
364                 return false;
365         }
366
367         if (dur == 0) {
368                 group->hide();
369         }
370
371         item_duration = dur;
372
373         reset_width_dependent_items (trackview.editor().sample_to_pixel (dur));
374
375         DurationChanged (dur, src); /* EMIT_SIGNAL */
376         return true;
377 }
378
379 /** @return duration of this item */
380 framepos_t
381 TimeAxisViewItem::get_duration() const
382 {
383         return item_duration;
384 }
385
386 /**
387  * Set the maximum duration that this item can have.
388  *
389  * @param dur the new maximum duration
390  * @param src the identity of the object that initiated the change
391  */
392 void
393 TimeAxisViewItem::set_max_duration(framecnt_t dur, void* src)
394 {
395         max_item_duration = dur;
396         MaxDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
397 }
398
399 /** @return the maximum duration that this item may have */
400 framecnt_t
401 TimeAxisViewItem::get_max_duration() const
402 {
403         return max_item_duration;
404 }
405
406 /**
407  * Set the minimum duration that this item may have.
408  *
409  * @param the minimum duration that this item may be set to
410  * @param src the identity of the object that initiated the change
411  */
412 void
413 TimeAxisViewItem::set_min_duration(framecnt_t dur, void* src)
414 {
415         min_item_duration = dur;
416         MinDurationChanged(max_item_duration, src); /* EMIT_SIGNAL */
417 }
418
419 /** @return the minimum duration that this item mey have */
420 framecnt_t
421 TimeAxisViewItem::get_min_duration() const
422 {
423         return min_item_duration;
424 }
425
426 /**
427  * Set whether this item is locked to its current position.
428  * Locked items cannot be moved until the item is unlocked again.
429  *
430  * @param yn true to lock this item to its current position
431  * @param src the identity of the object that initiated the change
432  */
433 void
434 TimeAxisViewItem::set_position_locked(bool yn, void* src)
435 {
436         position_locked = yn;
437         set_trim_handle_colors();
438         PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
439 }
440
441 /** @return true if this item is locked to its current position */
442 bool
443 TimeAxisViewItem::get_position_locked() const
444 {
445         return position_locked;
446 }
447
448 /**
449  * Set whether the maximum duration constraint is active.
450  *
451  * @param active set true to enforce the max duration constraint
452  * @param src the identity of the object that initiated the change
453  */
454 void
455 TimeAxisViewItem::set_max_duration_active (bool active, void* /*src*/)
456 {
457         max_duration_active = active;
458 }
459
460 /** @return true if the maximum duration constraint is active */
461 bool
462 TimeAxisViewItem::get_max_duration_active() const
463 {
464         return max_duration_active;
465 }
466
467 /**
468  * Set whether the minimum duration constraint is active.
469  *
470  * @param active set true to enforce the min duration constraint
471  * @param src the identity of the object that initiated the change
472  */
473
474 void
475 TimeAxisViewItem::set_min_duration_active (bool active, void* /*src*/)
476 {
477         min_duration_active = active;
478 }
479
480 /** @return true if the maximum duration constraint is active */
481 bool
482 TimeAxisViewItem::get_min_duration_active() const
483 {
484         return min_duration_active;
485 }
486
487 /**
488  * Set the name of this item.
489  *
490  * @param new_name the new name of this item
491  * @param src the identity of the object that initiated the change
492  */
493
494 void
495 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
496 {
497         if (new_name != item_name) {
498                 std::string temp_name = item_name;
499                 item_name = new_name;
500                 NameChanged (item_name, temp_name, src); /* EMIT_SIGNAL */
501         }
502 }
503
504 /** @return the name of this item */
505 std::string
506 TimeAxisViewItem::get_item_name() const
507 {
508         return item_name;
509 }
510
511 /**
512  * Set selection status.
513  *
514  * @param yn true if this item is currently selected
515  */
516 void
517 TimeAxisViewItem::set_selected(bool yn)
518 {
519         if (_selected != yn) {
520                 Selectable::set_selected (yn);
521                 set_frame_color ();
522         }
523 }
524
525 /** @return the TimeAxisView that this item is on */
526 TimeAxisView&
527 TimeAxisViewItem::get_time_axis_view () const
528 {
529         return trackview;
530 }
531
532 /**
533  * Set the displayed item text.
534  * This item is the visual text name displayed on the canvas item, this can be different to the name of the item.
535  *
536  * @param new_name the new name text to display
537  */
538
539 void
540 TimeAxisViewItem::set_name_text(const string& new_name)
541 {
542         if (!name_text) {
543                 return;
544         }
545
546         last_item_width = trackview.editor().sample_to_pixel(item_duration);
547         name_text_width = pixel_width (new_name, NAME_FONT) + 2;
548         name_text->set (new_name);
549
550 }
551
552 /**
553  * Set the height of this item.
554  *
555  * @param h new height
556  */
557 void
558 TimeAxisViewItem::set_height (double height)
559 {
560         _height = height;
561
562         if (name_highlight) {
563                 if (height < NAME_HIGHLIGHT_THRESH) {
564                         name_highlight->hide ();
565                         high_enough_for_name = false;
566
567                 } else {
568                         name_highlight->show();
569                         high_enough_for_name = true;
570                 }
571
572                 if (height > NAME_HIGHLIGHT_SIZE) {
573                         name_highlight->set_y0 ((double) height - 1 - NAME_HIGHLIGHT_SIZE);
574                         name_highlight->set_y1 ((double) height - 1);
575                 }
576                 else {
577                         /* it gets hidden now anyway */
578                         name_highlight->set_y0 (1);
579                         name_highlight->set_y1 (height);
580                 }
581         }
582
583         if (visibility & ShowNameText) {
584                 name_text->set_y_position (height + 1 - NAME_Y_OFFSET);
585         }
586
587         if (frame) {
588                 frame->set_y1 (height - 1);
589                 if (frame_handle_start) {
590                         frame_handle_start->set_y1 (height - 1);
591                         frame_handle_end->set_y1 (height - 1);
592                 }
593         }
594
595         vestigial_frame->set_y1 (height - 1);
596
597         update_name_text_visibility ();
598         set_colors ();
599 }
600
601 void
602 TimeAxisViewItem::set_color (Gdk::Color const & base_color)
603 {
604         compute_colors (base_color);
605         set_colors ();
606 }
607
608 ArdourCanvas::Item*
609 TimeAxisViewItem::get_canvas_frame()
610 {
611         return frame;
612 }
613
614 ArdourCanvas::Group*
615 TimeAxisViewItem::get_canvas_group()
616 {
617         return group;
618 }
619
620 ArdourCanvas::Item*
621 TimeAxisViewItem::get_name_highlight()
622 {
623         return name_highlight;
624 }
625
626 /**
627  * Calculate some contrasting color for displaying various parts of this item, based upon the base color.
628  *
629  * @param color the base color of the item
630  */
631 void
632 TimeAxisViewItem::compute_colors (Gdk::Color const & base_color)
633 {
634         unsigned char radius;
635         char minor_shift;
636
637         unsigned char r,g,b;
638
639         /* FILL: this is simple */
640         r = base_color.get_red()/256;
641         g = base_color.get_green()/256;
642         b = base_color.get_blue()/256;
643         fill_color = RGBA_TO_UINT(r,g,b,160);
644
645         /*  for minor colors:
646                 if the overall saturation is strong, make the minor colors light.
647                 if its weak, make them dark.
648
649                 we do this by moving an equal distance to the other side of the
650                 central circle in the color wheel from where we started.
651         */
652
653         radius = (unsigned char) rint (floor (sqrt (static_cast<double>(r*r + g*g + b+b))/3.0f));
654         minor_shift = 125 - radius;
655
656         /* LABEL: rotate around color wheel by 120 degrees anti-clockwise */
657
658         r = base_color.get_red()/256;
659         g = base_color.get_green()/256;
660         b = base_color.get_blue()/256;
661
662         if (r > b)
663         {
664                 if (r > g)
665                 {
666                         /* red sector => green */
667                         swap (r,g);
668                 }
669                 else
670                 {
671                         /* green sector => blue */
672                         swap (g,b);
673                 }
674         }
675         else
676         {
677                 if (b > g)
678                 {
679                         /* blue sector => red */
680                         swap (b,r);
681                 }
682                 else
683                 {
684                         /* green sector => blue */
685                         swap (g,b);
686                 }
687         }
688
689         r += minor_shift;
690         b += minor_shift;
691         g += minor_shift;
692
693         label_color = RGBA_TO_UINT(r,g,b,255);
694         r = (base_color.get_red()/256)   + 127;
695         g = (base_color.get_green()/256) + 127;
696         b = (base_color.get_blue()/256)  + 127;
697
698         label_color = RGBA_TO_UINT(r,g,b,255);
699
700         /* XXX can we do better than this ? */
701         /* We're trying;) */
702         /* NUKECOLORS */
703
704         //frame_color_r = 192;
705         //frame_color_g = 192;
706         //frame_color_b = 194;
707
708         //selected_frame_color_r = 182;
709         //selected_frame_color_g = 145;
710         //selected_frame_color_b = 168;
711
712         //handle_color_r = 25;
713         //handle_color_g = 0;
714         //handle_color_b = 255;
715         //lock_handle_color_r = 235;
716         //lock_handle_color_g = 16;
717         //lock_handle_color_b = 16;
718 }
719
720 /**
721  * Convenience method to set the various canvas item colors
722  */
723 void
724 TimeAxisViewItem::set_colors()
725 {
726         set_frame_color();
727
728         if (name_highlight) {
729                 name_highlight->set_fill_color (fill_color);
730         }
731         set_trim_handle_colors();
732 }
733
734 uint32_t
735 TimeAxisViewItem::get_fill_color () const
736 {
737         uint32_t f = 0;
738
739         if (_selected) {
740
741                 f = ARDOUR_UI::config()->get_canvasvar_SelectedFrameBase();
742
743         } else {
744
745                 if (_recregion) {
746                         f = ARDOUR_UI::config()->get_canvasvar_RecordingRect();
747                 } else {
748
749                         if (high_enough_for_name && !Config->get_color_regions_using_track_color()) {
750                                 f = ARDOUR_UI::config()->get_canvasvar_FrameBase();
751                         } else {
752                                 f = fill_color;
753                         }
754                 }
755         }
756
757         return f;
758 }
759
760 /**
761  * Sets the frame color depending on whether this item is selected
762  */
763 void
764 TimeAxisViewItem::set_frame_color()
765 {
766         uint32_t f = 0;
767
768         if (!frame) {
769                 return;
770         }
771
772         f = get_fill_color ();
773
774         if (fill_opacity) {
775                 f = UINT_RGBA_CHANGE_A (f, fill_opacity);
776         }
777         
778         if (!rect_visible) {
779                 f = UINT_RGBA_CHANGE_A (f, 0);
780         }
781
782         frame->set_fill_color (f);
783         set_frame_gradient ();
784
785         if (!_recregion) {
786                 if (_selected) {
787                         f = ARDOUR_UI::config()->get_canvasvar_SelectedTimeAxisFrame();
788                 } else {
789                         f = ARDOUR_UI::config()->get_canvasvar_TimeAxisFrame();
790                 }
791
792                 if (!rect_visible) {
793                         f = UINT_RGBA_CHANGE_A (f, 64);
794                 }
795
796                 frame->set_outline_color (f);
797         }
798 }
799
800 void
801 TimeAxisViewItem::set_frame_gradient ()
802 {
803         if (ARDOUR_UI::config()->get_timeline_item_gradient_depth() == 0.0) {
804                 frame->set_gradient (ArdourCanvas::Fill::StopList (), 0);
805                 return;
806         }
807                 
808         ArdourCanvas::Fill::StopList stops;
809         double r, g, b, a;
810         double h, s, v;
811         ArdourCanvas::Color f (get_fill_color());
812
813         /* need to get alpha value */
814         ArdourCanvas::color_to_rgba (f, r, g, b, a);
815         
816         stops.push_back (std::make_pair (0.0, f));
817         
818         /* now a darker version */
819         
820         ArdourCanvas::color_to_hsv (f, h, s, v);
821
822         v = min (1.0, v * (1.0 - ARDOUR_UI::config()->get_timeline_item_gradient_depth()));
823         
824         ArdourCanvas::Color darker = ArdourCanvas::hsv_to_color (h, s, v, a);
825         stops.push_back (std::make_pair (1.0, darker));
826         
827         frame->set_gradient (stops, _height);
828 }
829
830 /**
831  * Set the colors of the start and end trim handle depending on object state
832  */
833 void
834 TimeAxisViewItem::set_trim_handle_colors()
835 {
836         if (frame_handle_start) {
837                 if (position_locked) {
838                         frame_handle_start->set_fill_color (ARDOUR_UI::config()->get_canvasvar_TrimHandleLocked());
839                         frame_handle_end->set_fill_color (ARDOUR_UI::config()->get_canvasvar_TrimHandleLocked());
840                 } else {
841                         frame_handle_start->set_fill_color (ARDOUR_UI::config()->get_canvasvar_TrimHandle());
842                         frame_handle_end->set_fill_color (ARDOUR_UI::config()->get_canvasvar_TrimHandle());
843                 }
844         }
845 }
846
847 bool
848 TimeAxisViewItem::frame_handle_crossing (GdkEvent* ev, ArdourCanvas::Rectangle* item)
849 {
850         switch (ev->type) {
851         case GDK_LEAVE_NOTIFY:
852                 item->set_fill (false);
853                 break;
854         case GDK_ENTER_NOTIFY:
855                 item->set_fill (true);
856                 break;
857         default:
858                 break;
859         }
860         return false;
861 }
862
863 /** @return the frames per pixel */
864 double
865 TimeAxisViewItem::get_samples_per_pixel () const
866 {
867         return samples_per_pixel;
868 }
869
870 /** Set the frames per pixel of this item.
871  *  This item is used to determine the relative visual size and position of this item
872  *  based upon its duration and start value.
873  *
874  *  @param fpp the new frames per pixel
875  */
876 void
877 TimeAxisViewItem::set_samples_per_pixel (double fpp)
878 {
879         samples_per_pixel = fpp;
880         set_position (this->get_position(), this);
881         reset_width_dependent_items ((double) get_duration() / samples_per_pixel);
882 }
883
884 void
885 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
886 {
887         if (pixel_width < 2.0) {
888
889                 if (show_vestigial) {
890                         vestigial_frame->show();
891                 }
892
893                 if (name_highlight) {
894                         name_highlight->hide();
895                 }
896
897                 if (frame) {
898                         frame->hide();
899                 }
900
901                 if (frame_handle_start) {
902                         frame_handle_start->hide();
903                         frame_handle_end->hide();
904                 }
905
906                 wide_enough_for_name = false;
907
908         } else {
909                 vestigial_frame->hide();
910
911                 if (name_highlight) {
912
913                         if (_height < NAME_HIGHLIGHT_THRESH) {
914                                 name_highlight->hide();
915                                 high_enough_for_name = false;
916                         } else {
917                                 name_highlight->show();
918                                 if (!get_item_name().empty()) {
919                                         reset_name_width (pixel_width);
920                                 }
921                                 high_enough_for_name = true;
922                         }
923
924                         name_highlight->set_x1 (pixel_width);
925                 }
926
927                 if (frame) {
928                         frame->show();
929                         frame->set_x1 (pixel_width);
930                 }
931
932                 if (frame_handle_start) {
933                         if (pixel_width < (3 * TimeAxisViewItem::GRAB_HANDLE_WIDTH)) {
934                                 /*
935                                  * there's less than GRAB_HANDLE_WIDTH of the region between 
936                                  * the right-hand end of frame_handle_start and the left-hand
937                                  * end of frame_handle_end, so disable the handles
938                                  */
939
940                                 frame_handle_start->hide();
941                                 frame_handle_end->hide();
942                         } else {
943                                 frame_handle_start->show();
944                                 frame_handle_end->set_x0 (pixel_width - (TimeAxisViewItem::GRAB_HANDLE_WIDTH));
945                                 frame_handle_end->set_x1 (pixel_width);
946                                 frame_handle_end->show();
947                         }
948                 }
949
950                 wide_enough_for_name = true;
951         }
952
953         update_name_text_visibility ();
954 }
955
956 void
957 TimeAxisViewItem::reset_name_width (double /*pixel_width*/)
958 {
959         uint32_t it_width;
960         int pb_width;
961         bool showing_full_name;
962
963         if (!name_text) {
964                 return;
965         }
966
967         it_width = trackview.editor().sample_to_pixel(item_duration);
968         pb_width = name_text_width;
969
970         showing_full_name = last_item_width > pb_width + NAME_X_OFFSET;
971         last_item_width = it_width;
972
973         if (showing_full_name && (it_width >= pb_width + NAME_X_OFFSET)) {
974                 /*
975                   we've previously had the full name length showing
976                   and its still showing.
977                 */
978                 return;
979         }
980
981         if (pb_width > it_width - NAME_X_OFFSET) {
982                 pb_width = it_width - NAME_X_OFFSET;
983         }
984
985         if (it_width <= NAME_X_OFFSET) {
986                 wide_enough_for_name = false;
987         } else {
988                 wide_enough_for_name = true;
989         }
990
991         update_name_text_visibility ();
992
993         if (pb_width < 1) {
994                 pb_width = 1;
995         }
996
997         name_text->set (item_name);
998         name_text->clamp_width (pb_width);
999 }
1000
1001 /**
1002  * Callback used to remove this time axis item during the gtk idle loop.
1003  * This is used to avoid deleting the obejct while inside the remove_this_item
1004  * method.
1005  *
1006  * @param item the TimeAxisViewItem to remove.
1007  * @param src the identity of the object that initiated the change.
1008  */
1009 gint
1010 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
1011 {
1012         item->ItemRemoved (item->get_item_name(), src); /* EMIT_SIGNAL */
1013         delete item;
1014         item = 0;
1015         return false;
1016 }
1017
1018 void
1019 TimeAxisViewItem::set_y (double y)
1020 {
1021         group->set_y_position (y);
1022 }
1023
1024 void
1025 TimeAxisViewItem::update_name_text_visibility ()
1026 {
1027         if (!name_text) {
1028                 return;
1029         }
1030
1031         if (wide_enough_for_name && high_enough_for_name) {
1032                 name_text->show ();
1033         } else {
1034                 name_text->hide ();
1035         }
1036 }
1037
1038 void
1039 TimeAxisViewItem::parameter_changed (string p)
1040 {
1041         if (p == "color-regions-using-track-color") {
1042                 set_frame_color ();
1043         } else if (p == "timeline-item-gradient-depth") {
1044                 set_frame_gradient ();
1045         }
1046 }