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