Merge branch 'master' into cairocanvas
[ardour.git] / gtk2_ardour / note_base.cc
1 /*
2     Copyright (C) 2007 Paul Davis
3     Author: David Robillard
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 #include <iostream>
21
22 #include "gtkmm2ext/keyboard.h"
23
24 #include "canvas/text.h"
25
26 #include "note_base.h"
27 #include "midi_region_view.h"
28 #include "public_editor.h"
29 #include "editing_syms.h"
30 #include "keyboard.h"
31
32 using namespace std;
33 using namespace Gtkmm2ext;
34 using ARDOUR::MidiModel;
35 using namespace ArdourCanvas;
36
37 PBD::Signal1<void,NoteBase*> NoteBase::NoteBaseDeleted;
38
39 /// dividing the hue circle in 16 parts, hand adjusted for equal look, courtesy Thorsten Wilms
40 const uint32_t NoteBase::midi_channel_colors[16] = {
41           0xd32d2dff,  0xd36b2dff,  0xd3972dff,  0xd3d12dff,
42           0xa0d32dff,  0x7dd32dff,  0x2dd45eff,  0x2dd3c4ff,
43           0x2da5d3ff,  0x2d6fd3ff,  0x432dd3ff,  0x662dd3ff,
44           0x832dd3ff,  0xa92dd3ff,  0xd32dbfff,  0xd32d67ff
45         };
46
47 NoteBase::NoteBase(MidiRegionView& region, bool with_events, const boost::shared_ptr<NoteType> note)
48         : _region(region)
49         , _item (0)
50         , _text(0)
51 //      , _channel_selector_widget()
52         , _state(None)
53         , _note(note)
54         , _with_events (with_events)
55         , _selected(false)
56         , _valid (true)
57         , _mouse_x_fraction (-1.0)
58         , _mouse_y_fraction (-1.0)
59 {
60 }
61
62 NoteBase::~NoteBase()
63 {
64         NoteBaseDeleted (this);
65
66         delete _text;
67
68         /* XXX */
69 //      delete _channel_selector_widget;
70 }
71
72 void
73 NoteBase::set_item (Item* item)
74 {
75         _item = item;
76         _item->set_data ("notebase", this);
77
78         if (_with_events) {
79                 _item->Event.connect (sigc::mem_fun (*this, &NoteBase::event_handler));
80         }
81 }
82
83 void
84 NoteBase::invalidate ()
85 {
86         _valid = false;
87 }
88
89 void
90 NoteBase::validate ()
91 {
92         _valid = true;
93 }
94
95 void
96 NoteBase::show_velocity()
97 {
98         if (!_text) {
99                 _text = new Text (_item->parent ());
100                 _text->set_ignore_events (true);
101                 _text->set_color (ARDOUR_UI::config()->canvasvar_MidiNoteVelocityText.get());
102                 _text->set_alignment (Pango::ALIGN_CENTER);
103         }
104
105         _text->set_x_position ((x0() + x1()) / 2);
106         _text->set_y_position ((y0() + y1()) / 2);
107         ostringstream velo(ios::ate);
108         velo << int(_note->velocity());
109         _text->set (velo.str ());
110         _text->show();
111         _text->raise_to_top();
112 }
113
114 void
115 NoteBase::hide_velocity()
116 {
117         delete _text;
118         _text = 0;
119 }
120
121 void
122 NoteBase::on_channel_selection_change(uint16_t selection)
123 {
124         // make note change its color if its channel is not marked active
125         if ( (selection & (1 << _note->channel())) == 0 ) {
126                 set_fill_color(ARDOUR_UI::config()->canvasvar_MidiNoteInactiveChannel.get());
127                 set_outline_color(calculate_outline(ARDOUR_UI::config()->canvasvar_MidiNoteInactiveChannel.get()));
128         } else {
129                 // set the color according to the notes selection state
130                 set_selected(_selected);
131         }
132         // this forces the item to update..... maybe slow...
133         _item->hide();
134         _item->show();
135 }
136
137 void
138 NoteBase::on_channel_change(uint8_t channel)
139 {
140         _region.note_selected(this, true);
141         hide_channel_selector();
142         _region.change_channel(channel);
143 }
144
145 void
146 NoteBase::show_channel_selector ()
147 {
148 #if 0   
149         if (_channel_selector_widget == 0) {
150           
151                 if(_region.channel_selector_scoped_note() != 0){
152                     _region.channel_selector_scoped_note()->hide_channel_selector();
153                     _region.set_channel_selector_scoped_note(0);
154                 }
155
156                 SingleMidiChannelSelector* _channel_selector = new SingleMidiChannelSelector(_note->channel());
157                 _channel_selector->show_all();
158                 _channel_selector->channel_selected.connect(
159                         sigc::mem_fun(this, &NoteBase::on_channel_change));
160
161                 _channel_selector->clicked.connect (
162                         sigc::mem_fun (this, &NoteBase::hide_channel_selector));
163
164                 _channel_selector_widget = new Widget(*(_item->property_parent()),
165                                                       x1(),
166                                                       y2() + 2,
167                                                       (Gtk::Widget &) *_channel_selector);
168
169                 _channel_selector_widget->hide();
170                 _channel_selector_widget->property_height() = 100;
171                 _channel_selector_widget->property_width() = 100;
172                 _channel_selector_widget->raise_to_top();
173                 _channel_selector_widget->show();
174                 
175                 _region.set_channel_selector_scoped_note(this);
176         } else {
177                 hide_channel_selector();
178         }
179 #endif  
180 }
181
182 void
183 NoteBase::hide_channel_selector ()
184 {
185 #if 0
186         if (_channel_selector_widget) {
187                 _channel_selector_widget->hide();
188                 delete _channel_selector_widget;
189                 _channel_selector_widget = 0;
190         }
191 #endif  
192 }
193
194 void
195 NoteBase::set_selected(bool selected)
196 {
197         if (!_note) {
198                 return;
199         }
200
201         _selected = selected;
202         set_fill_color (base_color ());
203         
204         if (_selected) {
205                 set_outline_color(calculate_outline(ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get()));
206                 
207                 if(_region.channel_selector_scoped_note() != 0){
208                     _region.channel_selector_scoped_note()->hide_channel_selector();
209                     _region.set_channel_selector_scoped_note(0);
210                 }
211         } else {
212                 set_outline_color(calculate_outline(base_color()));
213                 hide_channel_selector();
214         }
215
216 }
217
218 #define SCALE_USHORT_TO_UINT8_T(x) ((x) / 257)
219
220 uint32_t
221 NoteBase::base_color()
222 {
223         using namespace ARDOUR;
224
225         ColorMode mode = _region.color_mode();
226
227         const uint8_t min_opacity = 15;
228         uint8_t       opacity = std::max(min_opacity, uint8_t(_note->velocity() + _note->velocity()));
229
230         switch (mode) {
231         case TrackColor:
232         {
233                 Gdk::Color color = _region.midi_stream_view()->get_region_color();
234                 return UINT_INTERPOLATE (RGBA_TO_UINT(
235                                                  SCALE_USHORT_TO_UINT8_T(color.get_red()),
236                                                  SCALE_USHORT_TO_UINT8_T(color.get_green()),
237                                                  SCALE_USHORT_TO_UINT8_T(color.get_blue()),
238                                                  opacity), 
239                                          ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(), 0.5);
240         }
241
242         case ChannelColors:
243                 return UINT_INTERPOLATE (UINT_RGBA_CHANGE_A (NoteBase::midi_channel_colors[_note->channel()],
244                                                              opacity), 
245                                          ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(), 0.5);
246
247         default:
248                 return meter_style_fill_color(_note->velocity(), selected());
249         };
250
251         return 0;
252 }
253
254 void
255 NoteBase::set_mouse_fractions (GdkEvent* ev)
256 {
257         double ix, iy;
258         bool set_cursor = false;
259
260         switch (ev->type) {
261         case GDK_MOTION_NOTIFY:
262                 ix = ev->motion.x;
263                 iy = ev->motion.y;
264                 set_cursor = true;
265                 break;
266         case GDK_ENTER_NOTIFY:
267                 ix = ev->crossing.x;
268                 iy = ev->crossing.y;
269                 set_cursor = true;
270                 break;
271         case GDK_BUTTON_PRESS:
272         case GDK_BUTTON_RELEASE:
273                 ix = ev->button.x;
274                 iy = ev->button.y;
275                 break;
276         default:
277                 _mouse_x_fraction = -1.0;
278                 _mouse_y_fraction = -1.0;
279                 return;
280         }
281
282         boost::optional<Rect> bbox = _item->bounding_box ();
283         assert (bbox);
284
285         _item->canvas_to_item (ix, iy);
286         /* XXX: CANVAS */
287         /* hmm, something wrong here. w2i should give item-local coordinates
288            but it doesn't. for now, finesse this.
289         */
290         ix = ix - bbox.get().x0;
291         iy = iy - bbox.get().y0;
292
293         /* fraction of width/height */
294         double xf;
295         double yf;
296         bool notify = false;
297
298         xf = ix / bbox.get().width ();
299         yf = iy / bbox.get().height ();
300
301         if (xf != _mouse_x_fraction || yf != _mouse_y_fraction) {
302                 notify = true;
303         }
304
305         _mouse_x_fraction = xf;
306         _mouse_y_fraction = yf;
307
308         if (notify) {
309                 if (big_enough_to_trim()) {
310                         _region.note_mouse_position (_mouse_x_fraction, _mouse_y_fraction, set_cursor);
311                 } else {
312                         /* pretend the mouse is in the middle, because this is not big enough
313                            to trim right now.
314                         */
315                         _region.note_mouse_position (0.5, 0.5, set_cursor);
316                 }
317         }
318 }
319
320 bool
321 NoteBase::event_handler (GdkEvent* ev)
322 {
323         if (!_region.get_time_axis_view().editor().internal_editing()) {
324                 return false;
325         }
326
327         switch (ev->type) {
328         case GDK_ENTER_NOTIFY:
329                 set_mouse_fractions (ev);
330                 _region.note_entered (this);
331                 break;
332
333         case GDK_LEAVE_NOTIFY:
334                 set_mouse_fractions (ev);
335                 _region.note_left (this);
336                 break;
337
338         case GDK_MOTION_NOTIFY:
339                 set_mouse_fractions (ev);
340                 break;
341
342         case GDK_BUTTON_PRESS:
343                 set_mouse_fractions (ev);
344                 if (ev->button.button == 3 && Keyboard::no_modifiers_active (ev->button.state) && _selected) {
345                         show_channel_selector();
346                         return true;
347                 }
348                 break;
349
350         case GDK_BUTTON_RELEASE:
351                 set_mouse_fractions (ev);
352                 if (ev->button.button == 3 && Keyboard::no_modifiers_active (ev->button.state)) {
353                         return true;
354                 }
355                 break;
356
357         default:
358                 break;
359         }
360
361         return _region.get_time_axis_view().editor().canvas_note_event (ev, _item);
362 }
363
364 bool
365 NoteBase::mouse_near_ends () const
366 {
367         return (_mouse_x_fraction >= 0.0 && _mouse_x_fraction < 0.25) ||
368                 (_mouse_x_fraction >= 0.75 && _mouse_x_fraction < 1.0);
369 }
370
371 bool
372 NoteBase::big_enough_to_trim () const
373 {
374         return (x1() - x0()) > 10;
375 }
376