4251df344c13fed53c85b5ee8bd587e26846018d
[ardour.git] / gtk2_ardour / region_view.cc
1 /*
2     Copyright (C) 2001-2006 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 <cmath>
21 #include <cassert>
22 #include <algorithm>
23
24 #include <gtkmm.h>
25
26 #include <gtkmm2ext/gtk_ui.h>
27 #include <pbd/stacktrace.h>
28
29 #include <ardour/playlist.h>
30 #include <ardour/audioregion.h>
31 #include <ardour/audiosource.h>
32 #include <ardour/audio_diskstream.h>
33
34 #include "ardour_ui.h"
35 #include "streamview.h"
36 #include "region_view.h"
37 #include "automation_region_view.h"
38 #include "route_time_axis.h"
39 #include "simplerect.h"
40 #include "simpleline.h"
41 #include "waveview.h"
42 #include "public_editor.h"
43 #include "region_editor.h"
44 #include "ghostregion.h"
45 #include "route_time_axis.h"
46 #include "utils.h"
47 #include "rgb_macros.h"
48 #include "gui_thread.h"
49
50 #include "i18n.h"
51
52 using namespace sigc;
53 using namespace ARDOUR;
54 using namespace PBD;
55 using namespace Editing;
56 using namespace ArdourCanvas;
57
58 static const int32_t sync_mark_width = 9;
59
60 sigc::signal<void,RegionView*> RegionView::RegionViewGoingAway;
61
62 RegionView::RegionView (ArdourCanvas::Group*              parent, 
63                         TimeAxisView&                     tv,
64                         boost::shared_ptr<ARDOUR::Region> r,
65                         double                            spu,
66                         Gdk::Color&                       basic_color)
67         : TimeAxisViewItem (r->name(), *parent, tv, spu, basic_color, r->position(), r->length(),
68                             TimeAxisViewItem::Visibility (TimeAxisViewItem::ShowNameText|
69                                                           TimeAxisViewItem::ShowNameHighlight|
70                                                           TimeAxisViewItem::ShowFrame))
71           , _region (r)
72           , sync_mark(0)
73           , sync_line(0)
74           , editor(0)
75           , current_visible_sync_position(0.0)
76           , valid(false)
77           , _enable_display(false)
78           , _pixel_width(1.0)
79           , in_destructor(false)
80           , wait_for_data(false)
81 {
82 }
83
84 RegionView::RegionView (const RegionView& other)
85         : TimeAxisViewItem (other)
86 {
87         /* derived concrete type will call init () */
88
89         _region = other._region;
90         editor = 0;
91         current_visible_sync_position = other.current_visible_sync_position;
92         valid = false;
93         _enable_display = false;
94         _pixel_width = other._pixel_width;
95 }
96
97 RegionView::RegionView (ArdourCanvas::Group*         parent, 
98                         TimeAxisView&                tv,
99                         boost::shared_ptr<ARDOUR::Region> r,
100                         double                       spu,
101                         Gdk::Color&                  basic_color,
102                         TimeAxisViewItem::Visibility visibility)
103         : TimeAxisViewItem (r->name(), *parent, tv, spu, basic_color, r->position(), r->length(), visibility)
104         , _region (r)
105         , sync_mark(0)
106         , sync_line(0)
107         , editor(0)
108         , current_visible_sync_position(0.0)
109         , valid(false)
110         , _enable_display(false)
111         , _pixel_width(1.0)
112         , in_destructor(false)
113         , wait_for_data(false)
114 {
115 }
116
117 void
118 RegionView::init (Gdk::Color& basic_color, bool wfd)
119 {
120         valid           = true;
121         _enable_display = false;
122         in_destructor   = false;
123         wait_for_data   = wfd;
124         sync_mark     = 0;
125         sync_line     = 0;
126         sync_mark     = 0;
127         sync_line     = 0;
128
129         compute_colors (basic_color);
130
131         name_highlight->set_data ("regionview", this);
132
133         if (name_text) {
134                 name_text->set_data ("regionview", this);
135         }
136
137         if (wfd)
138                 _enable_display = true;
139
140         set_y_position_and_height (0, trackview.height - 2);
141
142         _region->StateChanged.connect (mem_fun(*this, &RegionView::region_changed));
143
144         group->signal_event().connect (bind (mem_fun (PublicEditor::instance(), &PublicEditor::canvas_region_view_event), group, this));
145         name_highlight->signal_event().connect (bind (mem_fun (PublicEditor::instance(), &PublicEditor::canvas_region_view_name_highlight_event), name_highlight, this));
146
147         set_colors ();
148
149         ColorsChanged.connect (mem_fun (*this, &RegionView::color_handler));
150         // set_pango_fontsize();
151         /* XXX sync mark drag? */
152 }
153
154 RegionView::~RegionView ()
155 {
156         in_destructor = true;
157
158         for (vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
159                 delete *g;
160         }
161
162         if (editor) {
163                 delete editor;
164         }
165 }
166
167 gint
168 RegionView::_lock_toggle (ArdourCanvas::Item* item, GdkEvent* ev, void* arg)
169 {
170         switch (ev->type) {
171         case GDK_BUTTON_RELEASE:
172                 static_cast<RegionView*>(arg)->lock_toggle ();
173                 return TRUE;
174                 break;
175         default:
176                 break;
177         } 
178         return FALSE;
179 }
180
181 void
182 RegionView::lock_toggle ()
183 {
184         _region->set_locked (!_region->locked());
185 }
186
187 void
188 RegionView::region_changed (Change what_changed)
189 {
190         ENSURE_GUI_THREAD (bind (mem_fun(*this, &RegionView::region_changed), what_changed));
191
192         if (what_changed & BoundsChanged) {
193                 region_resized (what_changed);
194                 region_sync_changed ();
195         }
196         if (what_changed & Region::MuteChanged) {
197                 region_muted ();
198         }
199         if (what_changed & Region::OpacityChanged) {
200                 region_opacity ();
201         }
202         if (what_changed & ARDOUR::NameChanged) {
203                 region_renamed ();
204         }
205         if (what_changed & Region::SyncOffsetChanged) {
206                 region_sync_changed ();
207         }
208         /* 
209            this should not be needed now that only playlist can change layering
210         */
211         /*
212         if (what_changed & Region::LayerChanged) {
213                 region_layered ();
214         }
215         */
216         if (what_changed & Region::LockChanged) {
217                 region_locked ();
218         }
219 }
220
221 void
222 RegionView::region_locked ()
223 {
224         /* name will show locked status */
225         region_renamed ();
226 }
227
228 void
229 RegionView::region_resized (Change what_changed)
230 {
231         double unit_length;
232
233         if (what_changed & ARDOUR::PositionChanged) {
234                 set_position (_region->position(), 0);
235         }
236
237         if (what_changed & Change (StartChanged|LengthChanged)) {
238
239                 set_duration (_region->length(), 0);
240
241                 unit_length = _region->length() / samples_per_unit;
242                 
243                 for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
244
245                         (*i)->set_duration (unit_length);
246
247                 }
248         }
249 }
250
251 void
252 RegionView::reset_width_dependent_items (double pixel_width)
253 {
254         TimeAxisViewItem::reset_width_dependent_items (pixel_width);
255         _pixel_width = pixel_width;
256
257         /*for (AutomationChildren::iterator i = _automation_children.begin();
258                         i != _automation_children.end(); ++i) {
259                 i->second->reset_width_dependent_items(pixel_width);
260         }*/
261 }
262
263 void
264 RegionView::region_layered ()
265 {
266         RouteTimeAxisView *rtv = dynamic_cast<RouteTimeAxisView*>(&get_time_axis_view());
267         assert(rtv);
268         rtv->view()->region_layered (this);
269 }
270         
271 void
272 RegionView::region_muted ()
273 {
274         set_frame_color ();
275         region_renamed ();
276 }
277
278 void
279 RegionView::region_opacity ()
280 {
281         set_frame_color ();
282 }
283
284 void
285 RegionView::raise_to_top ()
286 {
287         _region->raise_to_top ();
288 }
289
290 void
291 RegionView::lower_to_bottom ()
292 {
293         _region->lower_to_bottom ();
294 }
295
296 bool
297 RegionView::set_position (nframes_t pos, void* src, double* ignored)
298 {
299         double delta;
300         bool ret;
301
302         if (!(ret = TimeAxisViewItem::set_position (pos, this, &delta))) {
303                 return false;
304         }
305
306         if (ignored) {
307                 *ignored = delta;
308         }
309
310         if (delta) {
311                 for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
312                         (*i)->group->move (delta, 0.0);
313                 }
314         
315                 for (AutomationChildren::iterator i = _automation_children.begin();
316                                 i != _automation_children.end(); ++i) {
317                         i->second->get_canvas_group()->move(delta, 0.0);
318                 }
319         }
320
321         return ret;
322 }
323
324 void
325 RegionView::set_samples_per_unit (gdouble spu)
326 {
327         TimeAxisViewItem::set_samples_per_unit (spu);
328
329         for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
330                 (*i)->set_samples_per_unit (spu);
331                 (*i)->set_duration (_region->length() / samples_per_unit);
332         }
333
334         region_sync_changed ();
335 }
336
337 bool
338 RegionView::set_duration (nframes_t frames, void *src)
339 {
340         if (!TimeAxisViewItem::set_duration (frames, src)) {
341                 return false;
342         }
343         
344         for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
345                 (*i)->set_duration (_region->length() / samples_per_unit);
346         }
347
348         return true;
349 }
350
351 void
352 RegionView::compute_colors (Gdk::Color& basic_color)
353 {
354         TimeAxisViewItem::compute_colors (basic_color);
355 }
356
357 void
358 RegionView::set_colors ()
359 {
360         TimeAxisViewItem::set_colors ();
361         
362         if (sync_mark) {
363                 sync_mark->property_fill_color_rgba() = fill_color;
364                 sync_line->property_fill_color_rgba() = fill_color;
365         }
366 }
367
368 void
369 RegionView::set_frame_color ()
370 {
371         if (_region->opaque()) {
372                 fill_opacity = 130;
373         } else {
374                 fill_opacity = 60;
375         }
376
377         TimeAxisViewItem::set_frame_color ();
378 }
379
380 void
381 RegionView::fake_set_opaque (bool yn)
382 {
383        if (yn) {
384                fill_opacity = 130;
385        } else {
386                fill_opacity = 60;
387        }
388        
389        set_frame_color ();
390 }
391
392 void
393 RegionView::hide_region_editor()
394 {
395         if (editor) {
396                 editor->hide_all ();
397         }
398 }
399
400 Glib::ustring
401 RegionView::make_name () const
402 {
403         Glib::ustring str;
404
405         // XXX nice to have some good icons for this
406
407         if (_region->locked()) {
408                 str += '>';
409                 str += _region->name();
410                 str += '<';
411         } else if (_region->position_locked()) {
412                 str += '{';
413                 str += _region->name();
414                 str += '}';
415         } else {
416                 str = _region->name();
417         }
418
419         if (_region->muted()) {
420                 str = string ("!") + str;
421         }
422
423         return str;
424 }
425
426 void
427 RegionView::region_renamed ()
428 {
429         Glib::ustring str = make_name ();
430
431         set_item_name (str, this);
432         set_name_text (str);
433         reset_width_dependent_items (_pixel_width);
434 }
435
436 void
437 RegionView::region_sync_changed ()
438 {
439         int sync_dir;
440         nframes_t sync_offset;
441
442         sync_offset = _region->sync_offset (sync_dir);
443
444         if (sync_offset == 0) {
445                 /* no need for a sync mark */
446                 if (sync_mark) {
447                         sync_mark->hide();
448                         sync_line->hide ();
449                 }
450                 return;
451         }
452
453         if (!sync_mark) {
454
455                 /* points set below */
456                 
457                 sync_mark =  new ArdourCanvas::Polygon (*group);
458                 sync_mark->property_fill_color_rgba() = fill_color;
459
460                 sync_line = new ArdourCanvas::Line (*group);
461                 sync_line->property_fill_color_rgba() = fill_color;
462                 sync_line->property_width_pixels() = 1;
463         }
464
465         /* this has to handle both a genuine change of position, a change of samples_per_unit,
466            and a change in the bounds of the _region->
467          */
468
469         if (sync_offset == 0) {
470
471                 /* no sync mark - its the start of the region */
472                 
473                 sync_mark->hide();
474                 sync_line->hide ();
475
476         } else {
477
478                 if ((sync_dir < 0) || ((sync_dir > 0) && (sync_offset > _region->length()))) { 
479
480                         /* no sync mark - its out of the bounds of the region */
481
482                         sync_mark->hide();
483                         sync_line->hide ();
484
485                 } else {
486
487                         /* lets do it */
488
489                         Points points;
490                         
491                         //points = sync_mark->property_points().get_value();
492                         
493                         double offset = sync_offset / samples_per_unit;
494                         points.push_back (Gnome::Art::Point (offset - ((sync_mark_width-1)/2), 1));
495                         points.push_back (Gnome::Art::Point (offset + ((sync_mark_width-1)/2), 1));
496                         points.push_back (Gnome::Art::Point (offset, sync_mark_width - 1));
497                         points.push_back (Gnome::Art::Point (offset - ((sync_mark_width-1)/2), 1));     
498                         sync_mark->property_points().set_value (points);
499                         sync_mark->show ();
500
501                         points.clear ();
502                         points.push_back (Gnome::Art::Point (offset, 0));
503                         points.push_back (Gnome::Art::Point (offset, trackview.height - NAME_HIGHLIGHT_SIZE));
504
505                         sync_line->property_points().set_value (points);
506                         sync_line->show ();
507                 }
508         }
509 }
510
511 void
512 RegionView::move (double x_delta, double y_delta)
513 {
514         if (!_region->can_move() || (x_delta == 0 && y_delta == 0)) {
515                 return;
516         }
517
518         get_canvas_group()->move (x_delta, y_delta);
519
520         /* note: ghosts never leave their tracks so y_delta for them is always zero */
521
522         for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
523                 (*i)->group->move (x_delta, 0.0);
524         }
525                 
526         for (AutomationChildren::iterator i = _automation_children.begin();
527                         i != _automation_children.end(); ++i) {
528                 i->second->get_canvas_group()->move(x_delta, 0.0);
529         }
530 }
531
532 void
533 RegionView::remove_ghost_in (TimeAxisView& tv)
534 {
535         for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
536                 if (&(*i)->trackview == &tv) {
537                         delete *i;
538                         break;
539                 }
540         }
541 }
542
543 void
544 RegionView::remove_ghost (GhostRegion* ghost)
545 {
546         if (in_destructor) {
547                 return;
548         }
549
550         for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
551                 if (*i == ghost) {
552                         ghosts.erase (i);
553                         break;
554                 }
555         }
556 }
557
558 uint32_t
559 RegionView::get_fill_color ()
560 {
561         return fill_color;
562 }
563
564 void
565 RegionView::set_height (double h)
566 {
567         if (sync_line) {
568                 Points points;
569                 int sync_dir;
570                 nframes_t sync_offset;
571                 sync_offset = _region->sync_offset (sync_dir);
572                 double offset = sync_offset / samples_per_unit;
573
574                 points.push_back (Gnome::Art::Point (offset, 0));
575                 points.push_back (Gnome::Art::Point (offset, h - NAME_HIGHLIGHT_SIZE));
576                 sync_line->property_points().set_value (points);
577         }
578 }