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