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