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