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