fix dragging that involves locked regions; auto-rebinding patch for people to experim...
[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         : TimeAxisViewItem (r->name(), *parent, tv, spu, basic_color, r->position(), r->length(),
67                             TimeAxisViewItem::Visibility (TimeAxisViewItem::ShowNameText|
68                                                           TimeAxisViewItem::ShowNameHighlight|
69                                                           TimeAxisViewItem::ShowFrame))
70           , _region (r)
71           , sync_mark(0)
72           , sync_line(0)
73           , editor(0)
74           , current_visible_sync_position(0.0)
75           , valid(false)
76           , _pixel_width(1.0)
77           , _height(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         editor = other.editor;
90         current_visible_sync_position = other.current_visible_sync_position;
91         valid = false;
92         _pixel_width = other._pixel_width;
93         _height = other._height;
94 }
95
96 RegionView::RegionView (const RegionView& other, boost::shared_ptr<Region> other_region)
97         : TimeAxisViewItem (other)
98 {
99         /* this is a pseudo-copy constructor used when dragging regions 
100            around on the canvas.
101         */
102
103         /* derived concrete type will call init () */
104
105         _region = other_region;
106         editor = other.editor;
107         current_visible_sync_position = other.current_visible_sync_position;
108         valid = false;
109         _pixel_width = other._pixel_width;
110         _height = other._height;
111 }
112
113 RegionView::RegionView (ArdourCanvas::Group*         parent, 
114                         TimeAxisView&                tv,
115                         boost::shared_ptr<ARDOUR::Region> r,
116                         double                       spu,
117                         Gdk::Color&                  basic_color,
118                         TimeAxisViewItem::Visibility visibility)
119         : TimeAxisViewItem (r->name(), *parent, tv, spu, basic_color, r->position(), r->length(), visibility)
120         , _region (r)
121         , sync_mark(0)
122         , sync_line(0)
123         , editor(0)
124         , current_visible_sync_position(0.0)
125         , valid(false)
126         , _pixel_width(1.0)
127         , _height(1.0)
128         , in_destructor(false)
129         , wait_for_data(false)
130 {
131 }
132
133 void
134 RegionView::init (Gdk::Color& basic_color, bool wfd)
135 {
136         valid         = true;
137         in_destructor = false;
138         _height       = 0;
139         wait_for_data = wfd;
140         sync_mark     = 0;
141         sync_line     = 0;
142
143         compute_colors (basic_color);
144
145         name_highlight->set_data ("regionview", this);
146
147         if (name_text) {
148                 name_text->set_data ("regionview", this);
149         }
150
151         reset_width_dependent_items ((double) _region->length() / samples_per_unit);
152
153         set_height (trackview.height);
154
155         _region->StateChanged.connect (mem_fun(*this, &RegionView::region_changed));
156
157         group->signal_event().connect (bind (mem_fun (PublicEditor::instance(), &PublicEditor::canvas_region_view_event), group, this));
158         name_highlight->signal_event().connect (bind (mem_fun (PublicEditor::instance(), &PublicEditor::canvas_region_view_name_highlight_event), name_highlight, this));
159
160         set_colors ();
161
162         ColorsChanged.connect (mem_fun (*this, &RegionView::color_handler));
163         // set_pango_fontsize();
164         /* XXX sync mark drag? */
165 }
166
167 RegionView::~RegionView ()
168 {
169         in_destructor = true;
170
171         for (vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
172                 delete *g;
173         }
174
175         if (editor) {
176                 delete editor;
177         }
178 }
179
180 gint
181 RegionView::_lock_toggle (ArdourCanvas::Item* item, GdkEvent* ev, void* arg)
182 {
183         switch (ev->type) {
184         case GDK_BUTTON_RELEASE:
185                 static_cast<RegionView*>(arg)->lock_toggle ();
186                 return TRUE;
187                 break;
188         default:
189                 break;
190         } 
191         return FALSE;
192 }
193
194 void
195 RegionView::lock_toggle ()
196 {
197         _region->set_locked (!_region->locked());
198 }
199
200 void
201 RegionView::region_changed (Change what_changed)
202 {
203         ENSURE_GUI_THREAD (bind (mem_fun(*this, &RegionView::region_changed), what_changed));
204
205         if (what_changed & BoundsChanged) {
206                 region_resized (what_changed);
207                 region_sync_changed ();
208         }
209         if (what_changed & Region::MuteChanged) {
210                 region_muted ();
211         }
212         if (what_changed & Region::OpacityChanged) {
213                 region_opacity ();
214         }
215         if (what_changed & ARDOUR::NameChanged) {
216                 region_renamed ();
217         }
218         if (what_changed & Region::SyncOffsetChanged) {
219                 region_sync_changed ();
220         }
221         if (what_changed & Region::LayerChanged) {
222                 region_layered ();
223         }
224         if (what_changed & Region::LockChanged) {
225                 region_locked ();
226         }
227 }
228
229 void
230 RegionView::region_locked ()
231 {
232         /* name will show locked status */
233         region_renamed ();
234 }
235
236 void
237 RegionView::region_resized (Change what_changed)
238 {
239         double unit_length;
240
241         if (what_changed & ARDOUR::PositionChanged) {
242                 set_position (_region->position(), 0);
243         }
244
245         if (what_changed & Change (StartChanged|LengthChanged)) {
246
247                 set_duration (_region->length(), 0);
248
249                 unit_length = _region->length() / samples_per_unit;
250                 
251                 for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
252
253                         (*i)->set_duration (unit_length);
254
255                 }
256         }
257 }
258
259 void
260 RegionView::reset_width_dependent_items (double pixel_width)
261 {
262         TimeAxisViewItem::reset_width_dependent_items (pixel_width);
263         _pixel_width = pixel_width;
264 }
265
266 void
267 RegionView::region_layered ()
268 {
269         RouteTimeAxisView *rtv = dynamic_cast<RouteTimeAxisView*>(&get_time_axis_view());
270         assert(rtv);
271         rtv->view()->region_layered (this);
272 }
273         
274 void
275 RegionView::region_muted ()
276 {
277         set_frame_color ();
278         region_renamed ();
279 }
280
281 void
282 RegionView::region_opacity ()
283 {
284         set_frame_color ();
285 }
286
287 void
288 RegionView::raise ()
289 {
290         _region->raise ();
291 }
292
293 void
294 RegionView::raise_to_top ()
295 {
296         _region->raise_to_top ();
297 }
298
299 void
300 RegionView::lower ()
301 {
302         _region->lower ();
303 }
304
305 void
306 RegionView::lower_to_bottom ()
307 {
308         _region->lower_to_bottom ();
309 }
310
311 bool
312 RegionView::set_position (nframes_t pos, void* src, double* ignored)
313 {
314         double delta;
315         bool ret;
316
317         if (!(ret = TimeAxisViewItem::set_position (pos, this, &delta))) {
318                 return false;
319         }
320
321         if (ignored) {
322                 *ignored = delta;
323         }
324
325         if (delta) {
326                 for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
327                         (*i)->group->move (delta, 0.0);
328                 }
329         }
330
331         return ret;
332 }
333
334 void
335 RegionView::set_samples_per_unit (gdouble spu)
336 {
337         TimeAxisViewItem::set_samples_per_unit (spu);
338
339         for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
340                 (*i)->set_samples_per_unit (spu);
341                 (*i)->set_duration (_region->length() / samples_per_unit);
342         }
343
344         region_sync_changed ();
345 }
346
347 bool
348 RegionView::set_duration (nframes_t frames, void *src)
349 {
350         if (!TimeAxisViewItem::set_duration (frames, src)) {
351                 return false;
352         }
353         
354         for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
355                 (*i)->set_duration (_region->length() / samples_per_unit);
356         }
357
358         return true;
359 }
360
361 void
362 RegionView::compute_colors (Gdk::Color& basic_color)
363 {
364         TimeAxisViewItem::compute_colors (basic_color);
365 }
366
367 void
368 RegionView::set_colors ()
369 {
370         TimeAxisViewItem::set_colors ();
371         
372         if (sync_mark) {
373                 sync_mark->property_fill_color_rgba() = fill_color;
374                 sync_line->property_fill_color_rgba() = fill_color;
375         }
376 }
377
378 void
379 RegionView::set_frame_color ()
380 {
381         if (_region->opaque()) {
382                 fill_opacity = 130;
383         } else {
384                 fill_opacity = 0;
385         }
386
387         TimeAxisViewItem::set_frame_color ();
388 }
389
390 void
391 RegionView::fake_set_opaque (bool yn)
392 {
393        if (yn) {
394                fill_opacity = 130;
395        } else {
396                fill_opacity = 0;
397        }
398        
399        set_frame_color ();
400 }
401
402 void
403 RegionView::hide_region_editor()
404 {
405         if (editor) {
406                 editor->hide_all ();
407         }
408 }
409
410 void
411 RegionView::region_renamed ()
412 {
413         string str;
414
415         if (_region->locked()) {
416                 str += '>';
417                 str += _region->name();
418                 str += '<';
419         } else {
420                 str = _region->name();
421         }
422
423         if (_region->speed_mismatch (trackview.session().frame_rate())) {
424                 str = string ("*") + str;
425         }
426
427         if (_region->muted()) {
428                 str = string ("!") + str;
429         }
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, _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->locked() || (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
527 void
528 RegionView::remove_ghost (GhostRegion* ghost)
529 {
530         if (in_destructor) {
531                 return;
532         }
533
534         for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
535                 if (*i == ghost) {
536                         ghosts.erase (i);
537                         break;
538                 }
539         }
540 }
541
542 uint32_t
543 RegionView::get_fill_color ()
544 {
545         return fill_color;
546 }
547
548 void
549 RegionView::set_height (double h)
550 {
551         if (sync_line) {
552                 Points points;
553                 int sync_dir;
554                 nframes_t sync_offset;
555                 sync_offset = _region->sync_offset (sync_dir);
556                 double offset = sync_offset / samples_per_unit;
557
558                 points.push_back (Gnome::Art::Point (offset, 0));
559                 points.push_back (Gnome::Art::Point (offset, h - NAME_HIGHLIGHT_SIZE));
560                 sync_line->property_points().set_value (points);
561         }
562 }