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