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