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