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