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