1 /*
2 * Copyright (C) 2006-2014 David Robillard <d@drobilla.net>
3 * Copyright (C) 2007-2012 Carl Hetherington <carl@carlh.net>
4 * Copyright (C) 2007-2019 Paul Davis <paul@linuxaudiosystems.com>
5 * Copyright (C) 2007 Doug McLain <doug@nostar.net>
6 * Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
7 * Copyright (C) 2015-2017 Nick Mainsbridge <mainsbridge@gmail.com>
8 * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com>
9 * Copyright (C) 2018 Ben Loftis <ben@harrisonconsoles.com>
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License along
22 * with this program; if not, write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 */
25
26 #include <cmath>
27 #include <algorithm>
28
29 #include <gtkmm.h>
30
31 #include <gtkmm2ext/gtk_ui.h>
32
33 #include "ardour/playlist.h"
34 #include "ardour/profile.h"
35 #include "ardour/session.h"
36 #include "ardour/source.h"
37
38 #include "gtkmm2ext/colors.h"
39
40 #include "canvas/arrow.h"
41 #include "canvas/polygon.h"
42 #include "canvas/debug.h"
43 #include "canvas/pixbuf.h"
44 #include "canvas/text.h"
45 #include "canvas/line.h"
46
47 #include "streamview.h"
48 #include "region_view.h"
49 #include "automation_region_view.h"
50 #include "route_time_axis.h"
51 #include "public_editor.h"
52 #include "region_editor.h"
53 #include "ghostregion.h"
54 #include "route_time_axis.h"
55 #include "ui_config.h"
56 #include "utils.h"
57 #include "rgb_macros.h"
58 #include "gui_thread.h"
59
60 #include "pbd/i18n.h"
61
62 using namespace std;
63 using namespace ARDOUR;
64 using namespace ARDOUR_UI_UTILS;
65 using namespace PBD;
66 using namespace Editing;
67 using namespace Gtk;
68 using namespace ArdourCanvas;
69
70 static const int32_t sync_mark_width = 9;
71
72 PBD::Signal1<void,RegionView*> RegionView::RegionViewGoingAway;
73
RegionView(ArdourCanvas::Container * parent,TimeAxisView & tv,boost::shared_ptr<ARDOUR::Region> r,double spu,uint32_t basic_color,bool automation)74 RegionView::RegionView (ArdourCanvas::Container* parent,
75 TimeAxisView& tv,
76 boost::shared_ptr<ARDOUR::Region> r,
77 double spu,
78 uint32_t basic_color,
79 bool automation)
80 : TimeAxisViewItem (r->name(), *parent, tv, spu, basic_color, r->position(), r->length(), false, automation,
81 (automation ? TimeAxisViewItem::ShowFrame :
82 TimeAxisViewItem::Visibility ((UIConfiguration::instance().get_show_region_name() ? TimeAxisViewItem::ShowNameText : 0) |
83 TimeAxisViewItem::ShowNameHighlight| TimeAxisViewItem::ShowFrame)))
84 , _region (r)
85 , sync_mark(0)
86 , sync_line(0)
87 , editor(0)
88 , current_visible_sync_position(0.0)
89 , valid(false)
90 , _enable_display(false)
91 , _pixel_width(1.0)
92 , in_destructor(false)
93 , wait_for_data(false)
94 , _silence_text (0)
95 , _xrun_markers_visible (false)
96 , _cue_markers_visible (false)
97 {
98 UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &RegionView::parameter_changed));
99
100 for (SourceList::const_iterator s = _region->sources().begin(); s != _region->sources().end(); ++s) {
101 (*s)->CueMarkersChanged.connect (*this, invalidator (*this), boost::bind (&RegionView::update_cue_markers, this), gui_context());
102 }
103 }
104
RegionView(const RegionView & other)105 RegionView::RegionView (const RegionView& other)
106 : sigc::trackable(other)
107 , TimeAxisViewItem (other)
108 , _silence_text (0)
109 , _xrun_markers_visible (false)
110 , _cue_markers_visible (false)
111 {
112 UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &RegionView::parameter_changed));
113
114 /* derived concrete type will call init () */
115
116 _region = other._region;
117 current_visible_sync_position = other.current_visible_sync_position;
118 valid = false;
119 _pixel_width = other._pixel_width;
120
121 for (SourceList::const_iterator s = _region->sources().begin(); s != _region->sources().end(); ++s) {
122 (*s)->CueMarkersChanged.connect (*this, invalidator (*this), boost::bind (&RegionView::update_cue_markers, this), gui_context());
123 }
124 }
125
RegionView(const RegionView & other,boost::shared_ptr<Region> other_region)126 RegionView::RegionView (const RegionView& other, boost::shared_ptr<Region> other_region)
127 : sigc::trackable(other)
128 , TimeAxisViewItem (other)
129 , _silence_text (0)
130 , _xrun_markers_visible (false)
131 , _cue_markers_visible (false)
132 {
133 UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &RegionView::parameter_changed));
134
135 /* derived concrete type will call init () */
136 /* this is a pseudo-copy constructor used when dragging regions
137 around on the canvas.
138 */
139
140 /* derived concrete type will call init () */
141
142 _region = other_region;
143 current_visible_sync_position = other.current_visible_sync_position;
144 valid = false;
145 _pixel_width = other._pixel_width;
146
147 for (SourceList::const_iterator s = _region->sources().begin(); s != _region->sources().end(); ++s) {
148 (*s)->CueMarkersChanged.connect (*this, invalidator (*this), boost::bind (&RegionView::update_cue_markers, this), gui_context());
149 }
150 }
151
152
RegionView(ArdourCanvas::Container * parent,TimeAxisView & tv,boost::shared_ptr<ARDOUR::Region> r,double spu,uint32_t basic_color,bool recording,TimeAxisViewItem::Visibility visibility)153 RegionView::RegionView (ArdourCanvas::Container* parent,
154 TimeAxisView& tv,
155 boost::shared_ptr<ARDOUR::Region> r,
156 double spu,
157 uint32_t basic_color,
158 bool recording,
159 TimeAxisViewItem::Visibility visibility)
160 : TimeAxisViewItem (r->name(), *parent, tv, spu, basic_color, r->position(), r->length(), recording, false, visibility)
161 , _region (r)
162 , sync_mark(0)
163 , sync_line(0)
164 , editor(0)
165 , current_visible_sync_position(0.0)
166 , valid(false)
167 , _enable_display(false)
168 , _pixel_width(1.0)
169 , in_destructor(false)
170 , wait_for_data(false)
171 , _silence_text (0)
172 {
173 UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &RegionView::parameter_changed));
174
175 for (SourceList::const_iterator s = _region->sources().begin(); s != _region->sources().end(); ++s) {
176 (*s)->CueMarkersChanged.connect (*this, invalidator (*this), boost::bind (&RegionView::update_cue_markers, this), gui_context());
177 }
178 }
179
180 void
init(bool wfd)181 RegionView::init (bool wfd)
182 {
183 editor = 0;
184 valid = true;
185 in_destructor = false;
186 wait_for_data = wfd;
187 sync_mark = 0;
188 sync_line = 0;
189 sync_mark = 0;
190 sync_line = 0;
191
192 if (name_highlight) {
193 name_highlight->set_data ("regionview", this);
194 name_highlight->Event.connect (sigc::bind (sigc::mem_fun (PublicEditor::instance(), &PublicEditor::canvas_region_view_name_highlight_event), name_highlight, this));
195 }
196
197 if (frame_handle_start) {
198 frame_handle_start->set_data ("regionview", this);
199 frame_handle_start->set_data ("isleft", (void*) 1);
200 frame_handle_start->Event.connect (sigc::bind (sigc::mem_fun (PublicEditor::instance(), &PublicEditor::canvas_frame_handle_event), frame_handle_start, this));
201 frame_handle_start->raise_to_top();
202 }
203
204 if (frame_handle_end) {
205 frame_handle_end->set_data ("regionview", this);
206 frame_handle_end->set_data ("isleft", (void*) 0);
207 frame_handle_end->Event.connect (sigc::bind (sigc::mem_fun (PublicEditor::instance(), &PublicEditor::canvas_frame_handle_event), frame_handle_end, this));
208 frame_handle_end->raise_to_top();
209 }
210
211 if (name_text) {
212 name_text->set_data ("regionview", this);
213 name_text->Event.connect (sigc::bind (sigc::mem_fun (PublicEditor::instance(), &PublicEditor::canvas_region_view_name_event), name_text, this));
214 }
215
216 XrunPositions xrp;
217 _region->captured_xruns (xrp, true);
218 int arrow_size = (int)(7.0 * UIConfiguration::instance ().get_ui_scale ()) & ~1;
219 for (XrunPositions::const_iterator x = xrp.begin (); x != xrp.end (); ++x) {
220 ArdourCanvas::Arrow* canvas_item = new ArdourCanvas::Arrow(group);
221 canvas_item->set_color (UIConfiguration::instance().color ("neutral:background"));
222 canvas_item->set_show_head (1, true);
223 canvas_item->set_show_head (0, false);
224 canvas_item->set_head_width (1, arrow_size);
225 canvas_item->set_head_height (1, arrow_size);
226 canvas_item->set_y0 (arrow_size);
227 canvas_item->set_y1 (arrow_size);
228 canvas_item->raise_to_top ();
229 canvas_item->hide ();
230 _xrun_markers.push_back (make_pair(*x, canvas_item));
231 }
232
233 _xrun_markers_visible = false;
234 update_xrun_markers ();
235
236 _cue_markers_visible = false;
237 update_cue_markers ();
238
239 if (wfd) {
240 _enable_display = true;
241 }
242
243 /* derived class calls set_height () including RegionView::set_height() in ::init() */
244 //set_height (trackview.current_height());
245
246 _region->PropertyChanged.connect (*this, invalidator (*this), boost::bind (&RegionView::region_changed, this, _1), gui_context());
247
248 /* derived class calls set_colors () including RegionView::set_colors() in ::init() */
249 //set_colors ();
250 UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &RegionView::color_handler));
251
252 /* XXX sync mark drag? */
253 }
254
~RegionView()255 RegionView::~RegionView ()
256 {
257 in_destructor = true;
258
259 for (vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
260 delete *g;
261 }
262
263 for (list<ArdourCanvas::Rectangle*>::iterator i = _coverage_frame.begin (); i != _coverage_frame.end (); ++i) {
264 delete *i;
265 }
266
267 for (list<std::pair<samplepos_t, ArdourCanvas::Arrow*> >::iterator i = _xrun_markers.begin(); i != _xrun_markers.end(); ++i) {
268 delete ((*i).second);
269 }
270
271 drop_silent_frames ();
272
273 delete editor;
274 }
275
276 bool
canvas_group_event(GdkEvent * event)277 RegionView::canvas_group_event (GdkEvent* event)
278 {
279 if (!in_destructor) {
280 return trackview.editor().canvas_region_view_event (event, group, this);
281 }
282 return false;
283 }
284
285 void
set_silent_frames(const AudioIntervalResult & silences,double)286 RegionView::set_silent_frames (const AudioIntervalResult& silences, double /*threshold*/)
287 {
288 samplecnt_t shortest = max_samplecnt;
289
290 /* remove old silent frames */
291 drop_silent_frames ();
292
293 if (silences.empty()) {
294 return;
295 }
296
297 uint32_t const color = UIConfiguration::instance().color_mod ("silence", "silence");
298
299 for (AudioIntervalResult::const_iterator i = silences.begin(); i != silences.end(); ++i) {
300
301 ArdourCanvas::Rectangle* cr = new ArdourCanvas::Rectangle (group);
302 cr->set_ignore_events (true);
303 _silent_frames.push_back (cr);
304
305 /* coordinates for the rect are relative to the regionview origin */
306
307 cr->set_x0 (trackview.editor().sample_to_pixel (i->first - _region->start()));
308 cr->set_x1 (trackview.editor().sample_to_pixel (i->second - _region->start()));
309 cr->set_y0 (1);
310 cr->set_y1 (_height - 2);
311 cr->set_outline (false);
312 cr->set_fill_color (color);
313
314 shortest = min (shortest, i->second - i->first);
315 }
316
317 /* Find shortest audible segment */
318 samplecnt_t shortest_audible = max_samplecnt;
319
320 samplecnt_t s = _region->start();
321 for (AudioIntervalResult::const_iterator i = silences.begin(); i != silences.end(); ++i) {
322 samplecnt_t const dur = i->first - s;
323 if (dur > 0) {
324 shortest_audible = min (shortest_audible, dur);
325 }
326
327 s = i->second;
328 }
329
330 samplecnt_t const dur = _region->start() + _region->length() - 1 - s;
331 if (dur > 0) {
332 shortest_audible = min (shortest_audible, dur);
333 }
334
335 _silence_text = new ArdourCanvas::Text (group);
336 _silence_text->set_ignore_events (true);
337 _silence_text->set_font_description (get_font_for_style (N_("SilenceText")));
338 _silence_text->set_color (UIConfiguration::instance().color ("silence text"));
339
340 /* both positions are relative to the region start offset in source */
341
342 _silence_text->set_x_position (trackview.editor().sample_to_pixel (silences.front().first - _region->start()) + 10.0);
343 _silence_text->set_y_position (20.0);
344
345 double ms = (float) shortest/_region->session().sample_rate();
346
347 /* ms are now in seconds */
348
349 char const * sunits;
350
351 if (ms >= 60.0) {
352 sunits = _("minutes");
353 ms /= 60.0;
354 } else if (ms < 1.0) {
355 sunits = _("msecs");
356 ms *= 1000.0;
357 } else {
358 sunits = _("secs");
359 }
360
361 string text = string_compose (ngettext ("%1 silent segment", "%1 silent segments", silences.size()), silences.size())
362 + ", "
363 + string_compose (_("shortest = %1 %2"), ms, sunits);
364
365 if (shortest_audible != max_samplepos) {
366 /* ms are now in seconds */
367 double ma = (float) shortest_audible / _region->session().sample_rate();
368 char const * aunits;
369
370 if (ma >= 60.0) {
371 aunits = _("minutes");
372 ma /= 60.0;
373 } else if (ma < 1.0) {
374 aunits = _("msecs");
375 ma *= 1000.0;
376 } else {
377 aunits = _("secs");
378 }
379
380 text += string_compose (_("\n (shortest audible segment = %1 %2)"), ma, aunits);
381 }
382
383 _silence_text->set (text);
384 }
385
386 void
hide_silent_frames()387 RegionView::hide_silent_frames ()
388 {
389 for (list<ArdourCanvas::Rectangle*>::iterator i = _silent_frames.begin (); i != _silent_frames.end (); ++i) {
390 (*i)->hide ();
391 }
392 _silence_text->hide();
393 }
394
395 void
drop_silent_frames()396 RegionView::drop_silent_frames ()
397 {
398 for (list<ArdourCanvas::Rectangle*>::iterator i = _silent_frames.begin (); i != _silent_frames.end (); ++i) {
399 delete *i;
400 }
401 _silent_frames.clear ();
402
403 delete _silence_text;
404 _silence_text = 0;
405 }
406
407 gint
_lock_toggle(ArdourCanvas::Item *,GdkEvent * ev,void * arg)408 RegionView::_lock_toggle (ArdourCanvas::Item*, GdkEvent* ev, void* arg)
409 {
410 switch (ev->type) {
411 case GDK_BUTTON_RELEASE:
412 static_cast<RegionView*>(arg)->lock_toggle ();
413 return TRUE;
414 break;
415 default:
416 break;
417 }
418 return FALSE;
419 }
420
421 void
lock_toggle()422 RegionView::lock_toggle ()
423 {
424 _region->set_locked (!_region->locked());
425 }
426
427 void
region_changed(const PropertyChange & what_changed)428 RegionView::region_changed (const PropertyChange& what_changed)
429 {
430 ENSURE_GUI_THREAD (*this, &RegionView::region_changed, what_changed);
431
432 if (what_changed.contains (ARDOUR::bounds_change)) {
433 region_resized (what_changed);
434 region_sync_changed ();
435 update_cue_markers ();
436 }
437 if (what_changed.contains (ARDOUR::Properties::muted)) {
438 region_muted ();
439 }
440 if (what_changed.contains (ARDOUR::Properties::opaque)) {
441 region_opacity ();
442 }
443 if (what_changed.contains (ARDOUR::Properties::name)) {
444 region_renamed ();
445 }
446 if (what_changed.contains (ARDOUR::Properties::position_lock_style)) {
447 region_renamed ();
448 }
449 if (what_changed.contains (ARDOUR::Properties::sync_position)) {
450 region_sync_changed ();
451 }
452 if (what_changed.contains (ARDOUR::Properties::locked)) {
453 region_locked ();
454 }
455 }
456
457 void
region_locked()458 RegionView::region_locked ()
459 {
460 /* name will show locked status */
461 region_renamed ();
462 }
463
464 void
region_resized(const PropertyChange & what_changed)465 RegionView::region_resized (const PropertyChange& what_changed)
466 {
467 double unit_length;
468
469 if (what_changed.contains (ARDOUR::Properties::position)) {
470 set_position (_region->position(), 0);
471 }
472
473 PropertyChange s_and_l;
474 s_and_l.add (ARDOUR::Properties::start);
475 s_and_l.add (ARDOUR::Properties::length);
476
477 if (what_changed.contains (s_and_l)) {
478
479 set_duration (_region->length(), 0);
480
481 unit_length = _region->length() / samples_per_pixel;
482
483 for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
484 (*i)->set_duration (unit_length);
485 }
486
487 update_xrun_markers ();
488 }
489 }
490
491 void
reset_width_dependent_items(double pixel_width)492 RegionView::reset_width_dependent_items (double pixel_width)
493 {
494 TimeAxisViewItem::reset_width_dependent_items (pixel_width);
495 _pixel_width = pixel_width;
496
497 if (_xrun_markers_visible) {
498 const samplepos_t start = _region->start();
499 for (list<std::pair<samplepos_t, ArdourCanvas::Arrow*> >::iterator i = _xrun_markers.begin(); i != _xrun_markers.end(); ++i) {
500 float x_pos = trackview.editor().sample_to_pixel (i->first - start);
501 i->second->set_x (x_pos);
502 }
503 }
504 }
505
506 void
update_xrun_markers()507 RegionView::update_xrun_markers ()
508 {
509 const bool show_xruns_markers = UIConfiguration::instance().get_show_region_xrun_markers();
510 if (_xrun_markers_visible == show_xruns_markers && !_xrun_markers_visible) {
511 return;
512 }
513
514 const samplepos_t start = _region->start();
515 const samplepos_t length = _region->length();
516 for (list<std::pair<samplepos_t, ArdourCanvas::Arrow*> >::iterator i = _xrun_markers.begin(); i != _xrun_markers.end(); ++i) {
517 float x_pos = trackview.editor().sample_to_pixel (i->first - start);
518 i->second->set_x (x_pos);
519 if (show_xruns_markers && (i->first >= start && i->first < start + length)) {
520 i->second->show ();
521 } else {
522 i->second->hide ();
523 }
524 }
525 _xrun_markers_visible = show_xruns_markers;
526 }
527
~ViewCueMarker()528 RegionView::ViewCueMarker::~ViewCueMarker ()
529 {
530 delete view_marker;
531 }
532
533 void
update_cue_markers()534 RegionView::update_cue_markers ()
535 {
536 const bool show_cue_markers = UIConfiguration::instance().get_show_region_cue_markers();
537
538 if (_cue_markers_visible == show_cue_markers && !_cue_markers_visible) {
539 return;
540 }
541
542 const samplepos_t start = region()->start();
543 const samplepos_t end = region()->start() + region()->length();
544 const Gtkmm2ext::SVAModifier alpha = UIConfiguration::instance().modifier (X_("region mark"));
545 const uint32_t color = Gtkmm2ext::HSV (UIConfiguration::instance().color ("region mark")).mod (alpha).color();
546
547 /* We assume that if the region has multiple sources, any of them will
548 * be appropriate as the origin of cue markers. We use the first one.
549 */
550
551 boost::shared_ptr<Source> source = region()->source (0);
552 CueMarkers const & model_markers (source->cue_markers());
553
554 /* Remove any view markers that are no longer present in the model cue
555 * marker set
556 */
557
558 for (ViewCueMarkers::iterator v = _cue_markers.begin(); v != _cue_markers.end(); ) {
559 if (model_markers.find ((*v)->model_marker) == model_markers.end()) {
560 delete *v;
561 v = _cue_markers.erase (v);
562 } else {
563 ++v;
564 }
565 }
566
567 /* now check all the model markers and make sure we have view markers
568 * for them. Note that because we use Source::cue_markers() above the
569 * set will contain markers stamped with absolute, not region-relative,
570 * timestamps and some of them may be outside the Region.
571 */
572
573 for (CueMarkers::const_iterator c = model_markers.begin(); c != model_markers.end(); ++c) {
574
575 if (c->position() < start || c->position() >= end) {
576 /* not withing this region */
577 continue;
578 }
579
580 ViewCueMarkers::iterator existing = _cue_markers.end();
581
582 for (ViewCueMarkers::iterator v = _cue_markers.begin(); v != _cue_markers.end(); ++v) {
583 if ((*v)->model_marker == *c) {
584 existing = v;
585 break;
586 }
587 }
588
589 if (existing == _cue_markers.end()) {
590
591 /* Create a new ViewCueMarker */
592
593 ArdourMarker* mark = new ArdourMarker (trackview.editor(), *group, color , c->text(), ArdourMarker::RegionCue, c->position() - start, true, this);
594 mark->set_points_color (color);
595 mark->set_show_line (true);
596 /* make sure the line has a clean end, before the frame
597 of the region view
598 */
599 mark->set_line_height (trackview.current_height() - (1.5 * UIConfiguration::instance ().get_ui_scale ()));
600 mark->the_item().raise_to_top ();
601
602 if (show_cue_markers) {
603 mark->show ();
604 } else {
605 mark->hide ();
606 }
607
608 _cue_markers.push_back (new ViewCueMarker (mark, *c));
609
610 } else {
611
612 /* Move and control visibility for an existing ViewCueMarker */
613
614 if (show_cue_markers) {
615 (*existing)->view_marker->show ();
616 } else {
617 (*existing)->view_marker->hide ();
618 }
619
620 (*existing)->view_marker->set_position (c->position() - start);
621 (*existing)->view_marker->the_item().raise_to_top ();
622 }
623 }
624
625 _cue_markers_visible = show_cue_markers;
626 }
627
628 void
region_muted()629 RegionView::region_muted ()
630 {
631 set_frame_color ();
632 region_renamed ();
633 }
634
635 void
region_opacity()636 RegionView::region_opacity ()
637 {
638 set_frame_color ();
639 }
640
641 void
raise_to_top()642 RegionView::raise_to_top ()
643 {
644 _region->raise_to_top ();
645 }
646
647 void
lower_to_bottom()648 RegionView::lower_to_bottom ()
649 {
650 _region->lower_to_bottom ();
651 }
652
653 bool
set_position(samplepos_t pos,void *,double * ignored)654 RegionView::set_position (samplepos_t pos, void* /*src*/, double* ignored)
655 {
656 double delta;
657 bool ret;
658
659 if (!(ret = TimeAxisViewItem::set_position (pos, this, &delta))) {
660 return false;
661 }
662
663 if (ignored) {
664 *ignored = delta;
665 }
666
667 if (delta) {
668 for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
669 (*i)->group->move (ArdourCanvas::Duple (delta, 0.0));
670 }
671 }
672
673 return ret;
674 }
675
676 void
set_samples_per_pixel(double fpp)677 RegionView::set_samples_per_pixel (double fpp)
678 {
679 TimeAxisViewItem::set_samples_per_pixel (fpp);
680
681 for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
682 (*i)->set_samples_per_pixel (fpp);
683 (*i)->set_duration (_region->length() / fpp);
684 }
685
686 region_sync_changed ();
687 }
688
689 bool
set_duration(samplecnt_t samples,void * src)690 RegionView::set_duration (samplecnt_t samples, void *src)
691 {
692 if (!TimeAxisViewItem::set_duration (samples, src)) {
693 return false;
694 }
695
696 for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
697 (*i)->set_duration (_region->length() / samples_per_pixel);
698 }
699
700 return true;
701 }
702
703 void
set_colors()704 RegionView::set_colors ()
705 {
706 TimeAxisViewItem::set_colors ();
707 set_sync_mark_color ();
708
709 const Gtkmm2ext::SVAModifier alpha = UIConfiguration::instance().modifier (X_("region mark"));
710 const uint32_t color = Gtkmm2ext::HSV (UIConfiguration::instance().color ("region mark")).mod (alpha).color();
711
712 for (ViewCueMarkers::iterator c = _cue_markers.begin(); c != _cue_markers.end(); ++c) {
713 (*c)->view_marker->set_color_rgba (color);
714 }
715 }
716
717 void
parameter_changed(std::string const & p)718 RegionView::parameter_changed (std::string const& p)
719 {
720 if (p == "show-region-xrun-markers") {
721 update_xrun_markers ();
722 } else if (p == "show-region-cue-markers") {
723 update_cue_markers ();
724 }
725 }
726
727 void
set_sync_mark_color()728 RegionView::set_sync_mark_color ()
729 {
730 if (sync_mark) {
731 Gtkmm2ext::Color c = UIConfiguration::instance().color ("sync mark");
732 sync_mark->set_fill_color (c);
733 sync_mark->set_outline_color (c);
734 sync_line->set_outline_color (c);
735 }
736 }
737
738 uint32_t
get_fill_color() const739 RegionView::get_fill_color () const
740 {
741 Gtkmm2ext::Color f = TimeAxisViewItem::get_fill_color();
742 char const *modname;
743
744 if (_region->opaque() && ( !_dragging && !_region->muted () )) {
745 modname = "opaque region base";
746 } else {
747 modname = "transparent region base";
748 }
749
750 return Gtkmm2ext::HSV(f).mod (UIConfiguration::instance().modifier (modname)).color ();
751 }
752
753 void
show_region_editor()754 RegionView::show_region_editor ()
755 {
756 if (editor == 0) {
757 editor = new RegionEditor (trackview.session(), region());
758 }
759
760 editor->present ();
761 editor->show_all();
762 }
763
764 void
hide_region_editor()765 RegionView::hide_region_editor()
766 {
767 if (editor) {
768 editor->hide_all ();
769 }
770 }
771
772 std::string
make_name() const773 RegionView::make_name () const
774 {
775 std::string str;
776
777 // XXX nice to have some good icons for this
778 if (_region->position_lock_style() == MusicTime) {
779 str += "\u266B"; // BEAMED EIGHTH NOTES
780 }
781
782 if (_region->locked()) {
783 str += "\u2629"; // CROSS OF JERUSALEM
784 str += _region->name();
785 } else if (_region->position_locked()) {
786 str += "\u21B9"; // LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR
787 str += _region->name();
788 } else if (_region->video_locked()) {
789 str += '[';
790 str += _region->name();
791 str += ']';
792 } else {
793 str += _region->name();
794 }
795
796 if (_region->muted()) {
797 str = string ("!") + str;
798 }
799
800 return str;
801 }
802
803 void
region_renamed()804 RegionView::region_renamed ()
805 {
806 std::string str = make_name ();
807
808 set_item_name (str, this);
809 set_name_text (str);
810 }
811
812 void
region_sync_changed()813 RegionView::region_sync_changed ()
814 {
815 int sync_dir;
816 samplecnt_t sync_offset;
817
818 sync_offset = _region->sync_offset (sync_dir);
819
820 if (sync_offset == 0) {
821 /* no need for a sync mark */
822 if (sync_mark) {
823 sync_mark->hide();
824 sync_line->hide ();
825 }
826 return;
827 }
828
829 if (!sync_mark) {
830
831 /* points set below */
832
833 sync_mark = new ArdourCanvas::Polygon (group);
834 CANVAS_DEBUG_NAME (sync_mark, string_compose ("sync mark for %1", get_item_name()));
835 sync_line = new ArdourCanvas::Line (group);
836 CANVAS_DEBUG_NAME (sync_line, string_compose ("sync mark for %1", get_item_name()));
837
838 set_sync_mark_color ();
839 }
840
841 /* this has to handle both a genuine change of position, a change of samples_per_pixel
842 and a change in the bounds of the _region->
843 */
844
845 if (sync_offset == 0) {
846
847 /* no sync mark - its the start of the region */
848
849 sync_mark->hide();
850 sync_line->hide ();
851
852 } else {
853
854 if ((sync_dir < 0) || ((sync_dir > 0) && (sync_offset > _region->length()))) {
855
856 /* no sync mark - its out of the bounds of the region */
857
858 sync_mark->hide();
859 sync_line->hide ();
860
861 } else {
862
863 /* lets do it */
864
865 Points points;
866
867 //points = sync_mark->property_points().get_value();
868
869 double offset = sync_offset / samples_per_pixel;
870 points.push_back (ArdourCanvas::Duple (offset - ((sync_mark_width-1)/2), 1));
871 points.push_back (ArdourCanvas::Duple (offset + ((sync_mark_width-1)/2), 1));
872 points.push_back (ArdourCanvas::Duple (offset, sync_mark_width - 1));
873 points.push_back (ArdourCanvas::Duple (offset - ((sync_mark_width-1)/2), 1));
874 sync_mark->set (points);
875 sync_mark->show ();
876
877 sync_line->set (ArdourCanvas::Duple (offset, 0), ArdourCanvas::Duple (offset, trackview.current_height() - NAME_HIGHLIGHT_SIZE));
878 sync_line->show ();
879 }
880 }
881 }
882
883 void
move(double x_delta,double y_delta)884 RegionView::move (double x_delta, double y_delta)
885 {
886 if (!_region->can_move() || (x_delta == 0 && y_delta == 0)) {
887 return;
888 }
889
890 /* items will not prevent Item::move() moving
891 * them to a negative x-axis coordinate, which
892 * is legal, but we don't want that here.
893 */
894
895 ArdourCanvas::Item *item = get_canvas_group ();
896
897 if (item->position().x + x_delta < 0) {
898 x_delta = -item->position().x; /* move it to zero */
899 }
900
901 item->move (ArdourCanvas::Duple (x_delta, y_delta));
902
903 /* note: ghosts never leave their tracks so y_delta for them is always zero */
904
905 for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
906 (*i)->group->move (ArdourCanvas::Duple (x_delta, 0.0));
907 }
908 }
909
910 void
remove_ghost_in(TimeAxisView & tv)911 RegionView::remove_ghost_in (TimeAxisView& tv)
912 {
913 for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
914 if (&(*i)->trackview == &tv) {
915 delete *i;
916 break;
917 }
918 }
919 }
920
921 void
remove_ghost(GhostRegion * ghost)922 RegionView::remove_ghost (GhostRegion* ghost)
923 {
924 if (in_destructor) {
925 return;
926 }
927
928 for (vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
929 if (*i == ghost) {
930 ghosts.erase (i);
931 break;
932 }
933 }
934 }
935
936 void
set_height(double h)937 RegionView::set_height (double h)
938 {
939 TimeAxisViewItem::set_height(h);
940
941 if (sync_line) {
942 Points points;
943 int sync_dir;
944 samplecnt_t sync_offset;
945 sync_offset = _region->sync_offset (sync_dir);
946 double offset = sync_offset / samples_per_pixel;
947
948 sync_line->set (
949 ArdourCanvas::Duple (offset, 0),
950 ArdourCanvas::Duple (offset, h - NAME_HIGHLIGHT_SIZE)
951 );
952 }
953
954 for (list<ArdourCanvas::Rectangle*>::iterator i = _coverage_frame.begin(); i != _coverage_frame.end(); ++i) {
955 (*i)->set_y1 (h + 1);
956 }
957
958 for (list<ArdourCanvas::Rectangle*>::iterator i = _silent_frames.begin(); i != _silent_frames.end(); ++i) {
959 (*i)->set_y1 (h + 1);
960 }
961
962 for (ViewCueMarkers::iterator v = _cue_markers.begin(); v != _cue_markers.end(); ++v) {
963 (*v)->view_marker->set_line_height (h - (1.5 * UIConfiguration::instance().get_ui_scale()));
964 }
965 }
966
967 /** Remove old coverage frame and make new ones, if we're in a LayerDisplay mode
968 * which uses them. */
969 void
update_coverage_frame(LayerDisplay d)970 RegionView::update_coverage_frame (LayerDisplay d)
971 {
972 /* remove old coverage frame */
973 for (list<ArdourCanvas::Rectangle*>::iterator i = _coverage_frame.begin (); i != _coverage_frame.end (); ++i) {
974 delete *i;
975 }
976
977 _coverage_frame.clear ();
978
979 if (d != Stacked) {
980 /* don't do coverage frame unless we're in stacked mode */
981 return;
982 }
983
984 boost::shared_ptr<Playlist> pl (_region->playlist ());
985 if (!pl) {
986 return;
987 }
988
989 samplepos_t const position = _region->first_sample ();
990 samplepos_t t = position;
991 samplepos_t const end = _region->last_sample ();
992
993 ArdourCanvas::Rectangle* cr = 0;
994 bool me = false;
995
996 /* the color that will be used to show parts of regions that will not be heard */
997 uint32_t const non_playing_color = UIConfiguration::instance().color_mod ("covered region", "covered region base");
998
999
1000 while (t < end) {
1001
1002 t++;
1003
1004 /* is this region is on top at time t? */
1005 bool const new_me = (pl->top_unmuted_region_at (t) == _region);
1006 /* finish off any old rect, if required */
1007 if (cr && me != new_me) {
1008 cr->set_x1 (trackview.editor().sample_to_pixel (t - position));
1009 }
1010
1011 /* start off any new rect, if required */
1012 if (cr == 0 || me != new_me) {
1013 cr = new ArdourCanvas::Rectangle (group);
1014 _coverage_frame.push_back (cr);
1015 cr->set_x0 (trackview.editor().sample_to_pixel (t - position));
1016 cr->set_y0 (1);
1017 cr->set_y1 (_height + 1);
1018 cr->set_outline (false);
1019 cr->set_ignore_events (true);
1020 if (new_me) {
1021 cr->set_fill_color (UINT_RGBA_CHANGE_A (non_playing_color, 0));
1022 } else {
1023 cr->set_fill_color (non_playing_color);
1024 }
1025 }
1026 t = pl->find_next_region_boundary (t, 1);
1027 if (t < 0) {
1028 break;
1029 }
1030 me = new_me;
1031 }
1032
1033 t = pl->find_next_region_boundary (t, 1);
1034
1035 if (cr) {
1036 /* finish off the last rectangle */
1037 cr->set_x1 (trackview.editor().sample_to_pixel (end - position));
1038 }
1039
1040 if (frame_handle_start) {
1041 frame_handle_start->raise_to_top ();
1042 }
1043
1044 if (frame_handle_end) {
1045 frame_handle_end->raise_to_top ();
1046 }
1047
1048 if (name_highlight) {
1049 name_highlight->raise_to_top ();
1050 }
1051
1052 if (name_text) {
1053 name_text->raise_to_top ();
1054 }
1055 }
1056
1057 bool
trim_front(samplepos_t new_bound,bool no_overlap,const int32_t sub_num)1058 RegionView::trim_front (samplepos_t new_bound, bool no_overlap, const int32_t sub_num)
1059 {
1060 if (_region->locked()) {
1061 return false;
1062 }
1063
1064 samplepos_t const pre_trim_first_sample = _region->first_sample();
1065
1066 if (_region->position() == new_bound) {
1067 return false;
1068 }
1069
1070 _region->trim_front (new_bound, sub_num);
1071
1072 if (no_overlap) {
1073 /* Get the next region on the left of this region and shrink/expand it. */
1074 boost::shared_ptr<Playlist> playlist (_region->playlist());
1075 boost::shared_ptr<Region> region_left = playlist->find_next_region (pre_trim_first_sample, End, 0);
1076
1077 bool regions_touching = false;
1078
1079 if (region_left != 0 && (pre_trim_first_sample == region_left->last_sample() + 1)) {
1080 regions_touching = true;
1081 }
1082
1083 /* Only trim region on the left if the first sample has gone beyond the left region's last sample. */
1084 if (region_left != 0 && (region_left->last_sample() > _region->first_sample() || regions_touching)) {
1085 region_left->trim_end (_region->first_sample() - 1);
1086 }
1087 }
1088
1089 region_changed (ARDOUR::bounds_change);
1090
1091 return (pre_trim_first_sample != _region->first_sample()); // return true if we actually changed something
1092 }
1093
1094 bool
trim_end(samplepos_t new_bound,bool no_overlap,const int32_t sub_num)1095 RegionView::trim_end (samplepos_t new_bound, bool no_overlap, const int32_t sub_num)
1096 {
1097 if (_region->locked()) {
1098 return false;
1099 }
1100
1101 samplepos_t const pre_trim_last_sample = _region->last_sample();
1102
1103 _region->trim_end (new_bound, sub_num);
1104
1105 if (no_overlap) {
1106 /* Get the next region on the right of this region and shrink/expand it. */
1107 boost::shared_ptr<Playlist> playlist (_region->playlist());
1108 boost::shared_ptr<Region> region_right = playlist->find_next_region (pre_trim_last_sample, Start, 1);
1109
1110 bool regions_touching = false;
1111
1112 if (region_right != 0 && (pre_trim_last_sample == region_right->first_sample() - 1)) {
1113 regions_touching = true;
1114 }
1115
1116 /* Only trim region on the right if the last sample has gone beyond the right region's first sample. */
1117 if (region_right != 0 && (region_right->first_sample() < _region->last_sample() || regions_touching)) {
1118 region_right->trim_front (_region->last_sample() + 1, sub_num);
1119 }
1120
1121 region_changed (ARDOUR::bounds_change);
1122
1123 } else {
1124 region_changed (PropertyChange (ARDOUR::Properties::length));
1125 }
1126
1127 return (pre_trim_last_sample != _region->last_sample()); // return true if we actually changed something
1128 }
1129
1130
1131 void
thaw_after_trim()1132 RegionView::thaw_after_trim ()
1133 {
1134 if (_region->locked()) {
1135 return;
1136 }
1137
1138 _region->resume_property_changes ();
1139 }
1140
1141
1142 void
move_contents(sampleoffset_t distance)1143 RegionView::move_contents (sampleoffset_t distance)
1144 {
1145 if (_region->locked()) {
1146 return;
1147 }
1148 _region->move_start (distance);
1149 region_changed (PropertyChange (ARDOUR::Properties::start));
1150 }
1151
1152 /** Snap a sample offset within our region using the current snap settings.
1153 * @param x Frame offset from this region's position.
1154 * @param ensure_snap whether to ignore snap_mode (in the case of SnapOff) and magnetic snap.
1155 * Used when inverting snap mode logic with key modifiers, or snap distance calculation.
1156 * @return Snapped sample offset from this region's position.
1157 */
1158 MusicSample
snap_sample_to_sample(sampleoffset_t x,bool ensure_snap) const1159 RegionView::snap_sample_to_sample (sampleoffset_t x, bool ensure_snap) const
1160 {
1161 PublicEditor& editor = trackview.editor();
1162 /* x is region relative, convert it to global absolute samples */
1163 samplepos_t const session_sample = x + _region->position();
1164
1165 /* try a snap in either direction */
1166 MusicSample sample (session_sample, 0);
1167 editor.snap_to (sample, RoundNearest, SnapToAny_Visual, ensure_snap);
1168
1169 /* if we went off the beginning of the region, snap forwards */
1170 if (sample.sample < _region->position ()) {
1171 sample.sample = session_sample;
1172 editor.snap_to (sample, RoundUpAlways, SnapToAny_Visual, ensure_snap);
1173 }
1174
1175 /* back to region relative, keeping the relevant divisor */
1176 return MusicSample (sample.sample - _region->position(), sample.division);
1177 }
1178
1179 void
update_visibility()1180 RegionView::update_visibility ()
1181 {
1182 /* currently only the name visibility can be changed dynamically */
1183
1184 if (UIConfiguration::instance().get_show_region_name()) {
1185 visibility = Visibility (visibility | ShowNameText);
1186 } else {
1187 visibility = Visibility (visibility & ~ShowNameText);
1188 }
1189
1190 manage_name_text ();
1191 }
1192
1193 void
set_selected(bool yn)1194 RegionView::set_selected (bool yn)
1195 {
1196 _region->set_selected_for_solo(yn);
1197 TimeAxisViewItem::set_selected(yn);
1198 }
1199
1200 void
maybe_raise_cue_markers()1201 RegionView::maybe_raise_cue_markers ()
1202 {
1203 for (ViewCueMarkers::iterator v = _cue_markers.begin(); v != _cue_markers.end(); ++v) {
1204 (*v)->view_marker->the_item().raise_to_top ();
1205 }
1206 }
1207
1208 CueMarker
find_model_cue_marker(ArdourMarker * m)1209 RegionView::find_model_cue_marker (ArdourMarker* m)
1210 {
1211 for (ViewCueMarkers::iterator v = _cue_markers.begin(); v != _cue_markers.end(); ++v) {
1212 if ((*v)->view_marker == m) {
1213 return (*v)->model_marker;
1214 }
1215 }
1216
1217 return CueMarker (string(), 0); /* empty string signifies invalid */
1218 }
1219
1220 void
drop_cue_marker(ArdourMarker * m)1221 RegionView::drop_cue_marker (ArdourMarker* m)
1222 {
1223 for (ViewCueMarkers::iterator v = _cue_markers.begin(); v != _cue_markers.end(); ++v) {
1224 if ((*v)->view_marker == m) {
1225 delete m;
1226 _cue_markers.erase (v);
1227 return;
1228 }
1229 }
1230 }
1231