Merge branch 'master' into cairocanvas
[ardour.git] / gtk2_ardour / ghostregion.cc
1 /*
2     Copyright (C) 2000-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
20 #include "evoral/Note.hpp"
21 #include "canvas/group.h"
22 #include "canvas/rectangle.h"
23 #include "canvas/wave_view.h"
24 #include "ardour_ui.h"
25 #include "automation_time_axis.h"
26 #include "ghostregion.h"
27 #include "midi_streamview.h"
28 #include "midi_time_axis.h"
29 #include "rgb_macros.h"
30 #include "note.h"
31
32 using namespace std;
33 using namespace Editing;
34 using namespace ArdourCanvas;
35 using namespace ARDOUR;
36
37 PBD::Signal1<void,GhostRegion*> GhostRegion::CatchDeletion;
38
39 GhostRegion::GhostRegion (ArdourCanvas::Group* parent, TimeAxisView& tv, TimeAxisView& source_tv, double initial_pos)
40         : trackview (tv)
41         , source_trackview (source_tv)
42 {
43         group = new ArdourCanvas::Group (parent);
44         group->set_position (ArdourCanvas::Duple (initial_pos, 0));
45
46         base_rect = new ArdourCanvas::Rectangle (group);
47         base_rect->set_x0 (0);
48         base_rect->set_y0 (0);
49         base_rect->set_y1 (trackview.current_height());
50         base_rect->set_outline_what (0);
51
52         if (!is_automation_ghost()) {
53                 base_rect->hide();
54         }
55
56         GhostRegion::set_colors();
57
58         /* the parent group of a ghostregion is a dedicated group for ghosts,
59            so the new ghost would want to get to the top of that group*/
60         group->raise_to_top ();
61 }
62
63 GhostRegion::~GhostRegion ()
64 {
65         CatchDeletion (this);
66         delete base_rect;
67         delete group;
68 }
69
70 void
71 GhostRegion::set_duration (double units)
72 {
73         base_rect->set_x1 (units);
74 }
75
76 void
77 GhostRegion::set_height ()
78 {
79         base_rect->set_y1 (trackview.current_height());
80 }
81
82 void
83 GhostRegion::set_colors ()
84 {
85         if (is_automation_ghost()) {
86                 base_rect->set_outline_color (ARDOUR_UI::config()->canvasvar_GhostTrackBase.get());
87                 base_rect->set_fill_color (ARDOUR_UI::config()->canvasvar_GhostTrackBase.get());
88         }
89 }
90
91 guint
92 GhostRegion::source_track_color(unsigned char alpha)
93 {
94         Gdk::Color color = source_trackview.color();
95         return RGBA_TO_UINT (color.get_red() / 256, color.get_green() / 256, color.get_blue() / 256, alpha);
96 }
97
98 bool
99 GhostRegion::is_automation_ghost()
100 {
101         return (dynamic_cast<AutomationTimeAxisView*>(&trackview)) != 0;
102 }
103
104 AudioGhostRegion::AudioGhostRegion(TimeAxisView& tv, TimeAxisView& source_tv, double initial_unit_pos)
105         : GhostRegion(tv.ghost_group(), tv, source_tv, initial_unit_pos)
106 {
107
108 }
109
110 void
111 AudioGhostRegion::set_frames_per_pixel (double fpp)
112 {
113         for (vector<WaveView*>::iterator i = waves.begin(); i != waves.end(); ++i) {
114                 (*i)->set_frames_per_pixel (fpp);
115         }
116 }
117
118 void
119 AudioGhostRegion::set_height ()
120 {
121         vector<WaveView*>::iterator i;
122         uint32_t n;
123
124         GhostRegion::set_height();
125
126         double const ht = ((trackview.current_height()) / (double) waves.size());
127
128         for (n = 0, i = waves.begin(); i != waves.end(); ++i, ++n) {
129                 (*i)->set_height (ht);
130                 (*i)->set_y_position (n * ht);
131         }
132 }
133
134 void
135 AudioGhostRegion::set_colors ()
136 {
137         GhostRegion::set_colors();
138         guint fill_color;
139
140         if (is_automation_ghost()) {
141                 fill_color = ARDOUR_UI::config()->canvasvar_GhostTrackWaveFill.get();
142         }
143         else {
144                 fill_color = source_track_color(200);
145         }
146
147         for (uint32_t n=0; n < waves.size(); ++n) {
148                 waves[n]->set_outline_color (ARDOUR_UI::config()->canvasvar_GhostTrackWave.get());
149                 waves[n]->set_fill_color (fill_color);
150                 waves[n]->property_clip_color() = ARDOUR_UI::config()->canvasvar_GhostTrackWaveClip.get();
151                 waves[n]->property_zero_color() = ARDOUR_UI::config()->canvasvar_GhostTrackZeroLine.get();
152         }
153 }
154
155 /** The general constructor; called when the destination timeaxisview doesn't have
156  *  a midistreamview.
157  *
158  *  @param tv TimeAxisView that this ghost region is on.
159  *  @param source_tv TimeAxisView that we are the ghost for.
160  */
161 MidiGhostRegion::MidiGhostRegion(TimeAxisView& tv, TimeAxisView& source_tv, double initial_unit_pos)
162         : GhostRegion(tv.ghost_group(), tv, source_tv, initial_unit_pos)
163         , _optimization_iterator (events.end ())
164 {
165         base_rect->lower_to_bottom();
166         update_range ();
167
168         midi_view()->NoteRangeChanged.connect (sigc::mem_fun (*this, &MidiGhostRegion::update_range));
169 }
170
171 /**
172  *  @param msv MidiStreamView that this ghost region is on.
173  *  @param source_tv TimeAxisView that we are the ghost for.
174  */
175 MidiGhostRegion::MidiGhostRegion(MidiStreamView& msv, TimeAxisView& source_tv, double initial_unit_pos)
176         : GhostRegion(msv.midi_underlay_group, msv.trackview(), source_tv, initial_unit_pos)
177         , _optimization_iterator (events.end ())
178 {
179         base_rect->lower_to_bottom();
180         update_range ();
181
182         midi_view()->NoteRangeChanged.connect (sigc::mem_fun (*this, &MidiGhostRegion::update_range));
183 }
184
185 MidiGhostRegion::~MidiGhostRegion()
186 {
187         clear_events ();
188 }
189
190 MidiGhostRegion::GhostEvent::GhostEvent (NoteBase* e, ArdourCanvas::Group* g)
191         : event (e)
192 {
193         rect = new ArdourCanvas::Rectangle (g, ArdourCanvas::Rect (e->x0(), e->y0(), e->x1(), e->y1()));
194 }
195
196 MidiGhostRegion::GhostEvent::~GhostEvent ()
197 {
198         /* event is not ours to delete */
199         delete rect;
200 }
201
202 void
203 MidiGhostRegion::set_frames_per_pixel (double /*spu*/)
204 {
205 }
206
207 /** @return MidiStreamView that we are providing a ghost for */
208 MidiStreamView*
209 MidiGhostRegion::midi_view ()
210 {
211         StreamView* sv = source_trackview.view ();
212         assert (sv);
213         MidiStreamView* msv = dynamic_cast<MidiStreamView*> (sv);
214         assert (msv);
215
216         return msv;
217 }
218
219 void
220 MidiGhostRegion::set_height ()
221 {
222         GhostRegion::set_height();
223         update_range();
224 }
225
226 void
227 MidiGhostRegion::set_colors()
228 {
229         guint fill = source_track_color(200);
230
231         GhostRegion::set_colors();
232
233         for (EventList::iterator it = events.begin(); it != events.end(); ++it) {
234                 (*it)->rect->set_fill_color (fill);
235                 (*it)->rect->set_outline_color (ARDOUR_UI::config()->canvasvar_GhostTrackMidiOutline.get());
236         }
237 }
238
239 void
240 MidiGhostRegion::update_range ()
241 {
242         MidiStreamView* mv = midi_view();
243
244         if (!mv) {
245                 return;
246         }
247
248         double const h = trackview.current_height() / double (mv->contents_note_range ());
249
250         for (EventList::iterator it = events.begin(); it != events.end(); ++it) {
251                 uint8_t const note_num = (*it)->event->note()->note();
252
253                 if (note_num < mv->lowest_note() || note_num > mv->highest_note()) {
254                         (*it)->rect->hide();
255                 } else {
256                         (*it)->rect->show();
257                         double const y = trackview.current_height() - (note_num + 1 - mv->lowest_note()) * h + 1;
258                         (*it)->rect->set_y0 (y);
259                         (*it)->rect->set_y1 (y + h);
260                 }
261         }
262 }
263
264 void
265 MidiGhostRegion::add_note (NoteBase* n)
266 {
267         GhostEvent* event = new GhostEvent (n, group);
268         events.push_back (event);
269
270         event->rect->set_fill_color (source_track_color(200));
271         event->rect->set_outline_color (ARDOUR_UI::config()->canvasvar_GhostTrackMidiOutline.get());
272
273         MidiStreamView* mv = midi_view();
274
275         if (mv) {
276                 const uint8_t note_num = n->note()->note();
277
278                 if (note_num < mv->lowest_note() || note_num > mv->highest_note()) {
279                         event->rect->hide();
280                 } else {
281                         const double y = mv->note_to_y(note_num);
282                         event->rect->set_y0 (y);
283                         event->rect->set_y1 (y + mv->note_height());
284                 }
285         }
286 }
287
288 void
289 MidiGhostRegion::clear_events()
290 {
291         for (EventList::iterator it = events.begin(); it != events.end(); ++it) {
292                 delete *it;
293         }
294
295         events.clear();
296         _optimization_iterator = events.end ();
297 }
298
299 /** Update the x positions of our representation of a parent's note.
300  *  @param parent The CanvasNote from the parent MidiRegionView.
301  */
302 void
303 MidiGhostRegion::update_note (NoteBase* parent)
304 {
305         GhostEvent* ev = find_event (parent);
306         if (!ev) {
307                 return;
308         }
309
310         double const x1 = parent->x0 ();
311         double const x2 = parent->x1 ();
312         ev->rect->set_x0 (x1);
313         ev->rect->set_x1 (x2);
314 }
315
316 void
317 MidiGhostRegion::remove_note (NoteBase* note)
318 {
319         GhostEvent* ev = find_event (note);
320         if (!ev) {
321                 return;
322         }
323
324         events.remove (ev);
325         delete ev;
326         _optimization_iterator = events.end ();
327 }
328
329 /** Given a note in our parent region (ie the actual MidiRegionView), find our
330  *  representation of it.
331  *  @return Our Event, or 0 if not found.
332  */
333
334 MidiGhostRegion::GhostEvent *
335 MidiGhostRegion::find_event (NoteBase* parent)
336 {
337         /* we are using _optimization_iterator to speed up the common case where a caller
338            is going through our notes in order.
339         */
340
341         if (_optimization_iterator != events.end()) {
342                 ++_optimization_iterator;
343         }
344
345         if (_optimization_iterator != events.end() && (*_optimization_iterator)->event == parent) {
346                 return *_optimization_iterator;
347         }
348
349         for (_optimization_iterator = events.begin(); _optimization_iterator != events.end(); ++_optimization_iterator) {
350                 if ((*_optimization_iterator)->event == parent) {
351                         return *_optimization_iterator;
352                 }
353         }
354
355         return 0;
356 }