Support cut / copy / paste of MIDI automation.
[ardour.git] / gtk2_ardour / automation_streamview.cc
1 /*
2     Copyright (C) 2001-2007 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 #include <cmath>
20 #include <cassert>
21 #include <utility>
22
23 #include <gtkmm.h>
24
25 #include <gtkmm2ext/gtk_ui.h>
26
27 #include "ardour/midi_playlist.h"
28 #include "ardour/midi_region.h"
29 #include "ardour/midi_source.h"
30 #include "ardour/midi_diskstream.h"
31 #include "ardour/midi_track.h"
32 #include "ardour/smf_source.h"
33 #include "ardour/region_factory.h"
34
35 #include "automation_streamview.h"
36 #include "region_view.h"
37 #include "automation_region_view.h"
38 #include "automation_time_axis.h"
39 #include "canvas-simplerect.h"
40 #include "region_selection.h"
41 #include "selection.h"
42 #include "public_editor.h"
43 #include "ardour_ui.h"
44 #include "rgb_macros.h"
45 #include "gui_thread.h"
46 #include "utils.h"
47 #include "simplerect.h"
48 #include "simpleline.h"
49
50 using namespace std;
51 using namespace ARDOUR;
52 using namespace PBD;
53 using namespace Editing;
54
55 AutomationStreamView::AutomationStreamView (AutomationTimeAxisView& tv)
56         : StreamView (*dynamic_cast<RouteTimeAxisView*>(tv.get_parent()),
57                       new ArdourCanvas::Group(*tv.canvas_background()),
58                       new ArdourCanvas::Group(*tv.canvas_display()))
59         , _controller(tv.controller())
60         , _automation_view(tv)
61         , _pending_automation_state (Off)
62 {
63         //canvas_rect->property_fill_color_rgba() = stream_base_color;
64         canvas_rect->property_outline_color_rgba() = RGBA_BLACK;
65 }
66
67 AutomationStreamView::~AutomationStreamView ()
68 {
69 }
70
71
72 RegionView*
73 AutomationStreamView::add_region_view_internal (boost::shared_ptr<Region> region, bool wfd, bool /*recording*/)
74 {
75         if ( ! region) {
76                 cerr << "No region" << endl;
77                 return NULL;
78         }
79
80         if (wfd) {
81                 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(region);
82                 if (mr)
83                         mr->midi_source()->load_model();
84         }
85
86         const boost::shared_ptr<AutomationControl> control = boost::dynamic_pointer_cast<AutomationControl> (
87                 region->control (_controller->controllable()->parameter(), true)
88                 );
89
90         boost::shared_ptr<AutomationList> list;
91         if (control) {
92                 list = boost::dynamic_pointer_cast<AutomationList>(control->list());
93                 assert(!control->list() || list);
94         }
95
96         AutomationRegionView *region_view;
97         std::list<RegionView *>::iterator i;
98
99         for (i = region_views.begin(); i != region_views.end(); ++i) {
100                 if ((*i)->region() == region) {
101
102                         /* great. we already have an AutomationRegionView for this Region. use it again. */
103                         AutomationRegionView* arv = dynamic_cast<AutomationRegionView*>(*i);;
104
105                         if (arv->line())
106                                 arv->line()->set_list (list);
107                         (*i)->set_valid (true);
108                         (*i)->enable_display(wfd);
109                         display_region(arv);
110
111                         return NULL;
112                 }
113         }
114
115         region_view = new AutomationRegionView (_canvas_group, _automation_view, region,
116                         _controller->controllable()->parameter(), list,
117                         _samples_per_unit, region_color);
118
119         region_view->init (region_color, false);
120         region_views.push_front (region_view);
121
122         /* follow global waveform setting */
123
124         if (wfd) {
125                 region_view->enable_display(true);
126                 //region_view->midi_region()->midi_source(0)->load_model();
127         }
128
129         display_region(region_view);
130
131         /* catch regionview going away */
132         region->DropReferences.connect (*this, invalidator (*this), boost::bind (&AutomationStreamView::remove_region_view, this, boost::weak_ptr<Region>(region)), gui_context());
133
134         /* setup automation state for this region */
135         boost::shared_ptr<AutomationLine> line = region_view->line ();
136         if (line && line->the_list()) {
137                 line->the_list()->set_automation_state (automation_state ());
138         }
139         
140         RegionViewAdded (region_view);
141
142         return region_view;
143 }
144
145 void
146 AutomationStreamView::display_region(AutomationRegionView* region_view)
147 {
148         region_view->line().reset();
149 }
150
151 void
152 AutomationStreamView::set_automation_state (AutoState state)
153 {
154         /* XXX: not sure if this is right, but for now the automation state is basically held by
155            the regions' AutomationLists.  Each region is always set to have the same AutoState.
156         */
157         
158         if (region_views.empty()) {
159                 _pending_automation_state = state;
160         } else {
161                 list<boost::shared_ptr<AutomationLine> > lines = get_lines ();
162
163                 for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
164                         if ((*i)->the_list()) {
165                                 (*i)->the_list()->set_automation_state (state);
166                         }
167                 }
168         }
169 }
170
171 void
172 AutomationStreamView::redisplay_track ()
173 {
174         list<RegionView *>::iterator i, tmp;
175
176         // Flag region views as invalid and disable drawing
177         for (i = region_views.begin(); i != region_views.end(); ++i) {
178                 (*i)->set_valid (false);
179                 (*i)->enable_display(false);
180         }
181
182         // Add and display region views, and flag them as valid
183         if (_trackview.is_track()) {
184                 _trackview.track()->playlist()->foreach_region (
185                         sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view))
186                         );
187         }
188
189         // Stack regions by layer, and remove invalid regions
190         layer_regions();
191 }
192
193
194 void
195 AutomationStreamView::setup_rec_box ()
196 {
197 }
198
199 void
200 AutomationStreamView::color_handler ()
201 {
202         /*if (_trackview.is_midi_track()) {
203                 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get();
204         }
205
206         if (!_trackview.is_midi_track()) {
207                 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiBusBase.get();;
208         }*/
209 }
210
211 AutoState
212 AutomationStreamView::automation_state () const
213 {
214         if (region_views.empty()) {
215                 return _pending_automation_state;
216         }
217
218         boost::shared_ptr<AutomationLine> line = ((AutomationRegionView*) region_views.front())->line ();
219         if (!line || !line->the_list()) {
220                 return Off;
221         }
222
223         return line->the_list()->automation_state ();
224 }
225
226 bool
227 AutomationStreamView::has_automation () const
228 {
229         list<boost::shared_ptr<AutomationLine> > lines = get_lines ();
230         
231         for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
232                 if ((*i)->npoints() > 0) {
233                         return true;
234                 }
235         }
236
237         return false;
238 }
239
240 /** Our parent AutomationTimeAxisView calls this when the user requests a particular
241  *  InterpolationStyle; tell the AutomationLists in our regions.
242  */
243 void
244 AutomationStreamView::set_interpolation (AutomationList::InterpolationStyle s)
245 {
246         list<boost::shared_ptr<AutomationLine> > lines = get_lines ();
247         
248         for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
249                 (*i)->the_list()->set_interpolation (s);
250         }
251 }
252
253 AutomationList::InterpolationStyle
254 AutomationStreamView::interpolation () const
255 {
256         if (region_views.empty()) {
257                 return AutomationList::Linear;
258         }
259
260         AutomationRegionView* v = dynamic_cast<AutomationRegionView*> (region_views.front());
261         assert (v);
262
263         return v->line()->the_list()->interpolation ();
264 }
265
266 /** Clear all automation displayed in this view */
267 void
268 AutomationStreamView::clear ()
269 {
270         list<boost::shared_ptr<AutomationLine> > lines = get_lines ();
271         
272         for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
273                 (*i)->clear ();
274         }
275 }
276
277 /** @param start Start position in session frames.
278  *  @param end End position in session frames.
279  */
280 void
281 AutomationStreamView::get_selectables (framepos_t start, framepos_t end, double botfrac, double topfrac, list<Selectable*>& results)
282 {
283         for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
284                 AutomationRegionView* arv = dynamic_cast<AutomationRegionView*> (*i);
285                 assert (arv);
286                 arv->line()->get_selectables (start, end, botfrac, topfrac, results);
287         }
288 }
289
290 void
291 AutomationStreamView::set_selected_points (PointSelection& ps)
292 {
293         list<boost::shared_ptr<AutomationLine> > lines = get_lines ();
294         
295         for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
296                 (*i)->set_selected_points (ps);
297         }
298 }
299
300 list<boost::shared_ptr<AutomationLine> >
301 AutomationStreamView::get_lines () const
302 {
303         list<boost::shared_ptr<AutomationLine> > lines;
304         
305         for (list<RegionView*>::const_iterator i = region_views.begin(); i != region_views.end(); ++i) {
306                 AutomationRegionView* arv = dynamic_cast<AutomationRegionView*> (*i);
307                 assert (arv);
308                 lines.push_back (arv->line());
309         }
310
311         return lines;
312 }
313
314 struct RegionPositionSorter {
315         bool operator() (RegionView* a, RegionView* b) {
316                 return a->region()->position() < b->region()->position();
317         }
318 };
319         
320
321 /** @param pos Position, in session frames.
322  *  @return AutomationLine to paste to for that position, or 0 if there is none appropriate.
323  */
324 boost::shared_ptr<AutomationLine>
325 AutomationStreamView::paste_line (framepos_t pos)
326 {
327         /* XXX: not sure how best to pick this; for now, just use the last region which starts before pos */
328
329         if (region_views.empty()) {
330                 return boost::shared_ptr<AutomationLine> ();
331         }
332
333         region_views.sort (RegionPositionSorter ());
334
335         list<RegionView*>::const_iterator prev = region_views.begin ();
336
337         for (list<RegionView*>::const_iterator i = region_views.begin(); i != region_views.end(); ++i) {
338                 if ((*i)->region()->position() > pos) {
339                         break;
340                 }
341                 prev = i;
342         }
343
344         boost::shared_ptr<Region> r = (*prev)->region ();
345
346         /* If *prev doesn't cover pos, it's no good */
347         if (r->position() > pos || ((r->position() + r->length()) < pos)) {
348                 return boost::shared_ptr<AutomationLine> ();
349         }
350
351         AutomationRegionView* arv = dynamic_cast<AutomationRegionView*> (*prev);
352         assert (arv);
353
354         return arv->line ();
355 }