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