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