Add stacked lanes mode which displays regions on different layers at different positi...
[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 <pbd/error.h>
21 #include <pbd/stacktrace.h>
22
23 #include <ardour/types.h>
24 #include <ardour/ardour.h>
25
26 #include <gtkmm2ext/utils.h>
27
28 #include "public_editor.h"
29 #include "time_axis_view_item.h"
30 #include "time_axis_view.h"
31 #include "simplerect.h"
32 #include "utils.h"
33 #include "canvas_impl.h"
34 #include "rgb_macros.h"
35
36 #include "i18n.h"
37
38 using namespace std;
39 using namespace Editing;
40 using namespace Glib;
41 using namespace PBD;
42
43 //------------------------------------------------------------------------------
44 /** Initialize const static memeber data */
45
46 Pango::FontDescription TimeAxisViewItem::NAME_FONT;
47 bool TimeAxisViewItem::have_name_font = false;
48 const double TimeAxisViewItem::NAME_X_OFFSET = 15.0;
49 const double TimeAxisViewItem::GRAB_HANDLE_LENGTH = 6 ;
50
51 double TimeAxisViewItem::NAME_Y_OFFSET;
52 double TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
53 double TimeAxisViewItem::NAME_HIGHLIGHT_THRESH;
54
55
56 //---------------------------------------------------------------------------------------//
57 // Constructor / Desctructor
58
59 /**
60  * Constructs a new TimeAxisViewItem.
61  *
62  * @param it_name the unique name/Id of this item
63  * @param parant the parent canvas group
64  * @param tv the TimeAxisView we are going to be added to
65  * @param spu samples per unit
66  * @param base_color
67  * @param start the start point of this item
68  * @param duration the duration of this item
69  */
70 TimeAxisViewItem::TimeAxisViewItem(const string & it_name, ArdourCanvas::Group& parent, TimeAxisView& tv, double spu, Gdk::Color& base_color, 
71                                    nframes_t start, nframes_t duration,
72                                    Visibility vis)
73         : trackview (tv)
74 {
75         if (!have_name_font) {
76
77                 /* first constructed item sets up font info */
78
79                 NAME_FONT = get_font_for_style (N_("TimeAxisViewItemName"));
80                 
81                 Gtk::Window win;
82                 Gtk::Label foo;
83                 win.add (foo);
84
85                 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (X_("Hg")); /* ascender + descender */
86                 int width;
87                 int height;
88
89                 layout->set_font_description (NAME_FONT);
90                 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
91
92                 NAME_Y_OFFSET = height + 6;
93                 NAME_HIGHLIGHT_SIZE = height + 6;
94                 NAME_HIGHLIGHT_THRESH = NAME_HIGHLIGHT_SIZE * 2;
95
96                 have_name_font = true;
97         }
98
99         group = new ArdourCanvas::Group (parent);
100         
101         init (it_name, spu, base_color, start, duration, vis);
102
103 }
104
105 TimeAxisViewItem::TimeAxisViewItem (const TimeAxisViewItem& other)
106         : trackview (other.trackview)
107 {
108
109         Gdk::Color c;
110         int r,g,b,a;
111
112         UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
113         c.set_rgb_p (r/255.0, g/255.0, b/255.0);
114
115         /* share the other's parent, but still create a new group */
116
117         Gnome::Canvas::Group* parent = other.group->property_parent();
118         
119         group = new ArdourCanvas::Group (*parent);
120
121         init (other.item_name, other.samples_per_unit, c, other.frame_position, other.item_duration, other.visibility);
122 }
123
124
125 void
126 TimeAxisViewItem::init (const string& it_name, double spu, Gdk::Color& base_color, nframes_t start, nframes_t duration, Visibility vis)
127 {
128         item_name = it_name ;
129         name_text_width = ::pixel_width (it_name, NAME_FONT);
130         last_name_text_width = 0;
131         samples_per_unit = spu ;
132         should_show_selection = true;
133         frame_position = start ;
134         item_duration = duration ;
135         name_connected = false;
136         fill_opacity = 60;
137         position_locked = false ;
138         max_item_duration = ARDOUR::max_frames;
139         min_item_duration = 0 ;
140         show_vestigial = true;
141         visibility = vis;
142         _sensitive = true;
143
144         if (duration == 0) {
145                 warning << "Time Axis Item Duration == 0" << endl ;
146         }
147
148         vestigial_frame = new ArdourCanvas::SimpleRect (*group);
149         vestigial_frame->property_x1() = (double) 0.0;
150         vestigial_frame->property_y1() = (double) 1.0;
151         vestigial_frame->property_x2() = 2.0;
152         vestigial_frame->property_y2() = (double) trackview.height;
153         vestigial_frame->property_outline_color_rgba() = color_map[cVestigialFrameOutline];
154         vestigial_frame->property_fill_color_rgba() = color_map[cVestigialFrameFill];
155         vestigial_frame->hide ();
156
157         if (visibility & ShowFrame) {
158                 frame = new ArdourCanvas::SimpleRect (*group);
159                 frame->property_x1() = (double) 0.0;
160                 frame->property_y1() = (double) 1.0;
161                 frame->property_x2() = (double) trackview.editor.frame_to_pixel(duration);
162                 frame->property_y2() = (double) trackview.height;
163                 frame->property_outline_color_rgba() = color_map[cTimeAxisFrameOutline];
164                 frame->property_fill_color_rgba() = color_map[cTimeAxisFrameFill];
165
166                 /* by default draw all 4 edges */
167
168                 uint32_t outline_what = 0x1|0x2|0x4|0x8;
169
170                 if (visibility & HideFrameLeft) {
171                         outline_what &= ~(0x1);
172                 }
173
174                 if (visibility & HideFrameRight) {
175                         outline_what &= ~(0x2);
176                 }
177
178                 if (visibility & HideFrameTB) {
179                         outline_what &= ~(0x4 | 0x8);
180                 }
181
182                 frame->property_outline_what() = outline_what;
183                     
184         } else {
185                 frame = 0;
186         }
187
188         if (visibility & ShowNameHighlight) {
189                 name_highlight = new ArdourCanvas::SimpleRect (*group);
190                 if (visibility & FullWidthNameHighlight) {
191                         name_highlight->property_x1() = (double) 0.0;
192                         name_highlight->property_x2() = (double) (trackview.editor.frame_to_pixel(item_duration));
193                 } else {
194                         name_highlight->property_x1() = (double) 1.0;
195                         name_highlight->property_x2() = (double) (trackview.editor.frame_to_pixel(item_duration)) - 1;
196                 }
197                 name_highlight->property_y1() = (double) (trackview.height - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE);
198                 name_highlight->property_y2() = (double) (trackview.height - 1);
199                 name_highlight->property_outline_color_rgba() = color_map[cNameHighlightFill];
200                 name_highlight->property_fill_color_rgba() = color_map[cNameHighlightOutline];
201
202                 name_highlight->set_data ("timeaxisviewitem", this);
203
204         } else {
205                 name_highlight = 0;
206         }
207
208         if (visibility & ShowNameText) {
209                 name_text = new ArdourCanvas::Text (*group);
210                 name_text->property_x() = (double) TimeAxisViewItem::NAME_X_OFFSET;
211                 /* trackview.height is the bottom of the trackview. subtract 1 to get back to the bottom of the highlight,
212                    then NAME_Y_OFFSET to position the text in the vertical center of the highlight
213                 */
214                 name_text->property_y() = (double) trackview.height - 1.0 - TimeAxisViewItem::NAME_Y_OFFSET;
215                 name_text->property_font_desc() = NAME_FONT;
216                 name_text->property_anchor() = Gtk::ANCHOR_NW;
217
218                 name_text->set_data ("timeaxisviewitem", this);
219                 
220         } else {
221                 name_text = 0;
222         }
223
224         /* create our grab handles used for trimming/duration etc */
225
226         if (visibility & ShowHandles) {
227                 frame_handle_start = new ArdourCanvas::SimpleRect (*group);
228                 frame_handle_start->property_x1() = (double) 0.0;
229                 frame_handle_start->property_x2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH;
230                 frame_handle_start->property_y1() = (double) 1.0;
231                 frame_handle_start->property_y2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH+1;
232                 frame_handle_start->property_outline_color_rgba() = color_map[cFrameHandleStartOutline];
233                 frame_handle_start->property_fill_color_rgba() = color_map[cFrameHandleStartFill];
234                 
235                 frame_handle_end = new ArdourCanvas::SimpleRect (*group);
236                 frame_handle_end->property_x1() = (double) (trackview.editor.frame_to_pixel(get_duration())) - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
237                 frame_handle_end->property_x2() = (double) trackview.editor.frame_to_pixel(get_duration());
238                 frame_handle_end->property_y1() = (double) 1;
239                 frame_handle_end->property_y2() = (double) TimeAxisViewItem::GRAB_HANDLE_LENGTH + 1;
240                 frame_handle_end->property_outline_color_rgba() = color_map[cFrameHandleEndOutline];
241                 frame_handle_end->property_fill_color_rgba() = color_map[cFrameHandleEndFill];
242
243         } else {
244                 frame_handle_start = 0;
245                 frame_handle_end = 0;
246         }
247
248         set_color (base_color) ;
249
250         set_duration (item_duration, this) ;
251         set_position (start, this) ;
252 }
253
254 /**
255  * Destructor
256  */
257 TimeAxisViewItem::~TimeAxisViewItem()
258 {
259         delete group;
260 }
261
262
263 //---------------------------------------------------------------------------------------//
264 // Position and duration Accessors/Mutators
265
266 /**
267  * Set the position of this item upon the timeline to the specified value
268  *
269  * @param pos the new position
270  * @param src the identity of the object that initiated the change
271  * @return true if the position change was a success, false otherwise
272  */
273 bool
274 TimeAxisViewItem::set_position(nframes_t pos, void* src, double* delta)
275 {
276         if (position_locked) {
277                 return false;
278         }
279
280         frame_position = pos;
281         
282         /*  This sucks. The GnomeCanvas version I am using
283             doesn't correctly implement gnome_canvas_group_set_arg(),
284             so that simply setting the "x" arg of the group
285             fails to move the group. Instead, we have to
286             use gnome_canvas_item_move(), which does the right
287             thing. I see that in GNOME CVS, the current (Sept 2001)
288             version of GNOME Canvas rectifies this issue cleanly.
289         */
290         
291         double old_unit_pos ;
292         double new_unit_pos = pos / samples_per_unit ;
293
294         old_unit_pos = group->property_x();
295
296         if (new_unit_pos != old_unit_pos) {
297                 group->move (new_unit_pos - old_unit_pos, 0.0);
298         }
299         
300         if (delta) {
301                 (*delta) = new_unit_pos - old_unit_pos;
302         }
303         
304         PositionChanged (frame_position, src) ; /* EMIT_SIGNAL */
305
306         return true;
307 }
308
309 /**
310  * Return the position of this item upon the timeline
311  *
312  * @return the position of this item
313  */
314 nframes_t
315 TimeAxisViewItem::get_position() const
316 {
317         return frame_position;
318 }
319
320 /**
321  * Sets the duration of this item
322  *
323  * @param dur the new duration of this item
324  * @param src the identity of the object that initiated the change
325  * @return true if the duration change was succesful, false otherwise
326  */
327 bool
328 TimeAxisViewItem::set_duration (nframes_t dur, void* src)
329 {
330         if ((dur > max_item_duration) || (dur < min_item_duration)) {
331                 warning << string_compose (_("new duration %1 frames is out of bounds for %2"), get_item_name(), dur)
332                         << endmsg;
333                 return false;
334         }
335
336         if (dur == 0) {
337                 group->hide();
338         }
339
340         item_duration = dur;
341         
342         reset_width_dependent_items (trackview.editor.frame_to_pixel (dur));
343         
344         DurationChanged (dur, src) ; /* EMIT_SIGNAL */
345         return true;
346 }
347
348 /**
349  * Returns the duration of this item
350  *
351  */
352 nframes_t
353 TimeAxisViewItem::get_duration() const
354 {
355         return (item_duration);
356 }
357
358 /**
359  * Sets the maximum duration that this item make have.
360  *
361  * @param dur the new maximum duration
362  * @param src the identity of the object that initiated the change
363  */
364 void
365 TimeAxisViewItem::set_max_duration(nframes_t dur, void* src)
366 {
367         max_item_duration = dur ;
368         MaxDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
369 }
370                 
371 /**
372  * Returns the maxmimum duration that this item may be set to
373  *
374  * @return the maximum duration that this item may be set to
375  */
376 nframes_t
377 TimeAxisViewItem::get_max_duration() const
378 {
379         return (max_item_duration) ;
380 }
381
382 /**
383  * Sets the minimu duration that this item may be set to
384  *
385  * @param the minimum duration that this item may be set to
386  * @param src the identity of the object that initiated the change
387  */
388 void
389 TimeAxisViewItem::set_min_duration(nframes_t dur, void* src)
390 {
391         min_item_duration = dur ;
392         MinDurationChanged(max_item_duration, src) ; /* EMIT_SIGNAL */
393 }
394                 
395 /**
396  * Returns the minimum duration that this item mey be set to
397  *
398  * @return the nimum duration that this item mey be set to
399  */
400 nframes_t
401 TimeAxisViewItem::get_min_duration() const
402 {
403         return(min_item_duration) ;
404 }
405
406 /**
407  * Sets whether the position of this Item is locked to its current position
408  * Locked items cannot be moved until the item is unlocked again.
409  *
410  * @param yn set to true to lock this item to its current position
411  * @param src the identity of the object that initiated the change
412  */
413 void
414 TimeAxisViewItem::set_position_locked(bool yn, void* src)
415 {
416         position_locked = yn ;
417         set_trim_handle_colors() ;
418         PositionLockChanged (position_locked, src); /* EMIT_SIGNAL */
419 }
420
421 /**
422  * Returns whether this item is locked to its current position
423  *
424  * @return true if this item is locked to its current posotion
425  *         false otherwise
426  */
427 bool
428 TimeAxisViewItem::get_position_locked() const
429 {
430         return (position_locked);
431 }
432
433 /**
434  * Sets whether the Maximum Duration constraint is active and should be enforced
435  *
436  * @param active set true to enforce the max duration constraint
437  * @param src the identity of the object that initiated the change
438  */
439 void
440 TimeAxisViewItem::set_max_duration_active(bool active, void* src)
441 {
442         max_duration_active = active ;
443 }
444                 
445 /**
446  * Returns whether the Maximum Duration constraint is active and should be enforced
447  *
448  * @return true if the maximum duration constraint is active, false otherwise
449  */
450 bool
451 TimeAxisViewItem::get_max_duration_active() const
452 {
453         return(max_duration_active) ;
454 }
455                 
456 /**
457  * Sets whether the Minimum Duration constraint is active and should be enforced
458  *
459  * @param active set true to enforce the min duration constraint
460  * @param src the identity of the object that initiated the change
461  */
462 void
463 TimeAxisViewItem::set_min_duration_active(bool active, void* src)
464 {
465         min_duration_active = active ;
466 }
467                 
468 /**
469  * Returns whether the Maximum Duration constraint is active and should be enforced
470  *
471  * @return true if the maximum duration constraint is active, false otherwise
472  */
473 bool
474 TimeAxisViewItem::get_min_duration_active() const
475 {
476         return(min_duration_active) ;
477 }
478
479 //---------------------------------------------------------------------------------------//
480 // Name/Id Accessors/Mutators
481
482 /**
483  * Set the name/Id of this item.
484  *
485  * @param new_name the new name of this item
486  * @param src the identity of the object that initiated the change
487  */
488 void
489 TimeAxisViewItem::set_item_name(std::string new_name, void* src)
490 {
491         if (new_name != item_name) {
492                 std::string temp_name = item_name ;
493                 item_name = new_name ;
494                 name_text_width = ::pixel_width (new_name, NAME_FONT);
495                 NameChanged (item_name, temp_name, src) ; /* EMIT_SIGNAL */
496         }
497 }
498
499 /**
500  * Returns the name/id of this item
501  *
502  * @return the name/id of this item
503  */
504 std::string
505 TimeAxisViewItem::get_item_name() const
506 {
507         return(item_name) ;
508 }
509
510 //---------------------------------------------------------------------------------------//
511 // Selection Methods
512
513 /**
514  * Set to true to indicate that this item is currently selected
515  *
516  * @param yn true if this item is currently selected
517  * @param src the identity of the object that initiated the change
518  */
519 void
520 TimeAxisViewItem::set_selected(bool yn)
521 {
522         if (_selected != yn) {
523                 Selectable::set_selected (yn);
524                 set_frame_color ();
525         }
526 }
527
528 void 
529 TimeAxisViewItem::set_should_show_selection (bool yn)
530 {
531         if (should_show_selection != yn) {
532                 should_show_selection = yn;
533                 set_frame_color ();
534         }
535 }
536
537 //---------------------------------------------------------------------------------------//
538 // Parent Componenet Methods
539
540 /**
541  * Returns the TimeAxisView that this item is upon
542  *
543  * @return the timeAxisView that this item is placed upon
544  */
545 TimeAxisView&
546 TimeAxisViewItem::get_time_axis_view()
547 {
548         return trackview;
549 }               
550 //---------------------------------------------------------------------------------------//
551 // ui methods & data
552
553 /**
554  * Sets the displayed item text
555  * This item is the visual text name displayed on the canvas item, this can be different to the name of the item
556  *
557  * @param new_name the new name text to display
558  */
559 void
560 TimeAxisViewItem::set_name_text(const ustring& new_name)
561 {
562         if (name_text) {
563                 name_text->property_text() = new_name;
564                 name_text_width = pixel_width (new_name, NAME_FONT);
565                 name_text_size_cache.clear ();
566         }
567 }
568
569 /**
570  * Set the y position and height of this item.
571  *
572  * @param y the new y position
573  * @param h the new height
574  */             
575 void
576 TimeAxisViewItem::set_y_position_and_height (double y, double h)
577 {
578         if (name_highlight) {
579                 if (h < NAME_HIGHLIGHT_THRESH) {
580                         name_highlight->hide();
581                         if (name_text) {
582                                 name_text->hide();
583                         }
584                 } else {
585                         name_highlight->show();
586                         if (name_text) {
587                                 name_text->show();
588                         }
589                 }
590
591                 if (h > NAME_HIGHLIGHT_SIZE) {
592                         name_highlight->property_y1() = (double) y + h + 1 - NAME_HIGHLIGHT_SIZE;
593                         name_highlight->property_y2() = (double) y + h;
594                 }
595                 else {
596                         /* it gets hidden now anyway */
597                         name_highlight->property_y1() = (double) y;
598                         name_highlight->property_y2() = (double) y + h;
599                 }
600         }
601
602         if (name_text) {
603                 name_text->property_y() = y + h + 1 - NAME_Y_OFFSET;
604                 if (h < NAME_HIGHLIGHT_THRESH) {
605                         name_text->property_fill_color_rgba() =  fill_color;
606                 }
607                 else {
608                         name_text->property_fill_color_rgba() = label_color;
609                 }
610         }
611
612         if (frame) {
613                 frame->property_y1() = y;
614                 frame->property_y2() = y + h + 1;
615         }
616
617         vestigial_frame->property_y1() = y;
618         vestigial_frame->property_y2() = y + h + 1;
619 }
620
621 /**
622  * 
623  */
624 void
625 TimeAxisViewItem::set_color(Gdk::Color& base_color)
626 {
627         compute_colors (base_color);
628         set_colors ();
629 }
630
631 /**
632  * 
633  */
634 ArdourCanvas::Item*
635 TimeAxisViewItem::get_canvas_frame()
636 {
637         return(frame) ;
638 }
639
640 /**
641  * 
642  */
643 ArdourCanvas::Item*
644 TimeAxisViewItem::get_canvas_group()
645 {
646         return (group) ;
647 }
648
649 /**
650  * 
651  */
652 ArdourCanvas::Item*
653 TimeAxisViewItem::get_name_highlight()
654 {
655         return (name_highlight) ;
656 }
657
658 /**
659  * 
660  */
661 ArdourCanvas::Text*
662 TimeAxisViewItem::get_name_text()
663 {
664         return (name_text) ;
665 }
666
667 /**
668  * Calculates 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& 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,255) ;
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         if (name_text) {
769                 double height = NAME_HIGHLIGHT_THRESH;
770
771                 if (frame) {
772                         height = frame->property_y2();
773                 }
774
775                 if (height < NAME_HIGHLIGHT_THRESH) {
776                         name_text->property_fill_color_rgba() =  fill_color;
777                 }
778                 else {
779                         name_text->property_fill_color_rgba() = label_color;
780                 }
781         }
782
783         if (name_highlight) {
784                 name_highlight->property_fill_color_rgba() = fill_color;
785                 name_highlight->property_outline_color_rgba() = fill_color;
786         }
787         set_trim_handle_colors() ;
788 }
789
790 /**
791  * Sets the frame color depending on whether this item is selected
792  */
793 void
794 TimeAxisViewItem::set_frame_color()
795 {
796         if (frame) {
797                 uint32_t r,g,b,a;
798                 
799                 if (_selected && should_show_selection) {
800                         UINT_TO_RGBA(color_map[cSelectedFrameBase], &r, &g, &b, &a);
801                         frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity);
802                 } else {
803                         UINT_TO_RGBA(color_map[cFrameBase], &r, &g, &b, &a);
804                         frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity);
805                 }
806         }
807 }
808
809 /**
810  * Sets the colors of the start and end trim handle depending on object state
811  *
812  */
813 void
814 TimeAxisViewItem::set_trim_handle_colors()
815 {
816         if (frame_handle_start) {
817                 if (position_locked) {
818                         frame_handle_start->property_fill_color_rgba() = color_map[cTrimHandleLockedStart];
819                         frame_handle_end->property_fill_color_rgba() = color_map[cTrimHandleLockedEnd];
820                 } else {
821                         frame_handle_start->property_fill_color_rgba() = color_map[cTrimHandleStart];
822                         frame_handle_end->property_fill_color_rgba() = color_map[cTrimHandleEnd];
823                 }
824         }
825 }
826
827 double
828 TimeAxisViewItem::get_samples_per_unit()
829 {
830         return(samples_per_unit) ;
831 }
832
833 void
834 TimeAxisViewItem::set_samples_per_unit (double spu)
835 {
836         samples_per_unit = spu ;
837         set_position (this->get_position(), this);
838         reset_width_dependent_items ((double)get_duration() / samples_per_unit);
839 }
840
841 void
842 TimeAxisViewItem::reset_width_dependent_items (double pixel_width)
843 {
844         if (pixel_width < GRAB_HANDLE_LENGTH * 2) {
845
846                 if (frame_handle_start) {
847                         frame_handle_start->hide();
848                         frame_handle_end->hide();
849                 }
850
851         } if (pixel_width < 2.0) {
852
853                 if (show_vestigial) {
854                         vestigial_frame->show();
855                 }
856
857                 if (name_highlight) {
858                         name_highlight->hide();
859                         if (name_text) {
860                                 name_text->hide();
861                         }
862                 }
863
864                 if (frame) {
865                         frame->hide();
866                 }
867
868                 if (frame_handle_start) {
869                         frame_handle_start->hide();
870                         frame_handle_end->hide();
871                 }
872                 
873         } else {
874                 vestigial_frame->hide();
875
876                 if (name_highlight) {
877
878                         double height = name_highlight->property_y2 ();
879
880                         if (height < NAME_HIGHLIGHT_THRESH) {
881                                 name_highlight->hide();
882                                 if (name_text) {
883                                         name_text->hide();
884                                 }
885                         } else {
886                                 name_highlight->show();
887                                 if (name_text && !get_item_name().empty()) {
888                                         name_text->show();
889                                         reset_name_width (pixel_width);
890                                 }
891                         }
892
893                         if (visibility & FullWidthNameHighlight) {
894                                 name_highlight->property_x2() = pixel_width;
895                         } else {
896                                 name_highlight->property_x2() = pixel_width - 1.0;
897                         }
898
899                 }
900
901                 if (frame) {
902                         frame->show();
903                         frame->property_x2() = pixel_width;
904                 }
905
906                 if (frame_handle_start) {
907                         if (pixel_width < (2*TimeAxisViewItem::GRAB_HANDLE_LENGTH)) {
908                                 frame_handle_start->hide();
909                                 frame_handle_end->hide();
910                         }
911                         frame_handle_start->show();
912                         frame_handle_end->property_x1() = pixel_width - (TimeAxisViewItem::GRAB_HANDLE_LENGTH);
913                         frame_handle_end->show();
914                         frame_handle_end->property_x2() = pixel_width;
915                 }
916         }
917 }
918
919 void
920 TimeAxisViewItem::reset_name_width (double pixel_width)
921 {
922         if (name_text == 0) {
923                 return;
924         }
925
926         int limit = (int) floor (pixel_width - NAME_X_OFFSET);
927         bool shrinking = (last_name_text_width > pixel_width);
928         int actual_width;
929         ustring ustr;
930         ustring::size_type n;
931
932         if ((last_name_text_width &&                                       // we did this once
933              shrinking &&                                                  // we're getting smaller
934              (name_text_width <= limit) &&                                 // fits the new size
935              (name_text_width <= last_name_text_width - NAME_X_OFFSET))) { // fit into the old size too
936                 last_name_text_width = pixel_width;
937                 return;
938         }
939
940         /* now check the cache of existing truncations */
941
942         Gtk::Label foo;
943         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
944
945         for (n = item_name.length(); n > 0; --n) {
946                 
947                 map<ustring::size_type,int>::iterator i;
948
949                 if ((i = name_text_size_cache.find (n)) != name_text_size_cache.end()) {
950
951                         /* we know the length of this substring already */
952                         
953                         if ((actual_width = (*i).second) < limit) {
954
955                                 /* it fits, use it */
956                                 
957                                 ustr = item_name.substr (0, n);
958                                 break;
959                         }
960                                                 
961                 } else {
962                         
963                         /* we don't know the length of this substring already, so compute
964                            it and put it into the cache.
965                          */
966
967                         layout->set_text (item_name.substr (0, n));
968                         
969                         int width, height;
970                         Gtkmm2ext::get_ink_pixel_size (layout, width, height);
971                         
972                         name_text_size_cache[n] = width;
973
974                         if ((actual_width = width) < limit) {
975                                 ustr = item_name.substr (0, n);
976                                 break;
977                         }
978                 }
979         }
980
981         if (n == 0) {
982                 name_text->property_text() = "";
983                 last_name_text_width = pixel_width;
984                 return;
985         } 
986
987         /* don't use name for event handling if it leaves no room
988            for trimming to work.
989         */
990         
991         if (pixel_width - actual_width < (NAME_X_OFFSET * 2.0)) {
992                 if (name_connected) {
993                         name_connected = false;
994                 }
995         } else {
996                 if (!name_connected) {
997                         name_connected = true;
998                 }
999         }
1000         
1001         name_text->property_text() = ustr;
1002         name_text_width = actual_width;
1003         name_text->show();
1004         last_name_text_width = pixel_width;
1005
1006 }
1007
1008
1009 //---------------------------------------------------------------------------------------//
1010 // Handle time axis removal
1011
1012 /**
1013  * Handles the Removal of this time axis item
1014  * This _needs_ to be called to alert others of the removal properly, ie where the source
1015  * of the removal came from.
1016  *
1017  * XXX Although im not too happy about this method of doing things, I cant think of a cleaner method
1018  *     just now to capture the source of the removal
1019  *
1020  * @param src the identity of the object that initiated the change
1021  */
1022 void
1023 TimeAxisViewItem::remove_this_item(void* src)
1024 {
1025         /*
1026            defer to idle loop, otherwise we'll delete this object
1027            while we're still inside this function ...
1028         */
1029         Glib::signal_idle().connect(bind (sigc::ptr_fun (&TimeAxisViewItem::idle_remove_this_item), this, src));
1030 }
1031
1032 /**
1033  * Callback used to remove this time axis item during the gtk idle loop
1034  * This is used to avoid deleting the obejct while inside the remove_this_item
1035  * method
1036  *
1037  * @param item the TimeAxisViewItem to remove
1038  * @param src the identity of the object that initiated the change
1039  */
1040 gint
1041 TimeAxisViewItem::idle_remove_this_item(TimeAxisViewItem* item, void* src)
1042 {
1043         item->ItemRemoved (item->get_item_name(), src) ; /* EMIT_SIGNAL */
1044         delete item;
1045         item = 0;
1046         return false;
1047 }