* first working version of editing MIDI channels of individual notes, see: http:...
[ardour.git] / gtk2_ardour / canvas-midi-event.cc
1 /*
2     Copyright (C) 2007 Paul Davis
3     Author: Dave 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 #include "canvas-midi-event.h"
22 #include "midi_region_view.h"
23 #include "public_editor.h"
24 #include "editing_syms.h"
25 #include "keyboard.h"
26
27 using namespace std;
28 using ARDOUR::MidiModel;
29
30 namespace Gnome {
31 namespace Canvas {
32
33
34 CanvasMidiEvent::CanvasMidiEvent(MidiRegionView& region, Item* item,
35                 const boost::shared_ptr<ARDOUR::Note> note)
36         : _region(region)
37         , _item(item)
38         , _text(0)
39         , _channel_selector_widget()
40         , _state(None)
41         , _note(note)
42         , _selected(false)
43 {
44         _text = new Text(*(item->property_parent()));
45 }
46
47 CanvasMidiEvent::~CanvasMidiEvent() 
48
49         if(_text) delete _text;
50         if(_channel_selector_widget) delete _channel_selector_widget;
51 }
52
53 void 
54 CanvasMidiEvent::move_event(double dx, double dy)
55 {
56         _item->move(dx, dy);
57         _text->move(dx, dy);
58 }
59
60 void
61 CanvasMidiEvent::show_velocity(void)
62 {
63         _text->property_x() = (x1() + x2()) /2;
64         _text->property_y() = (y1() + y2()) /2;
65         ostringstream velo(ios::ate);
66         velo << int(_note->velocity());
67         _text->property_text() = velo.str();
68         _text->property_justification() = Gtk::JUSTIFY_CENTER;
69         _text->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiNoteSelectedOutline.get();
70         _text->show();
71         _text->lower_to_bottom();
72         _text->raise(2);
73 }
74
75 void
76 CanvasMidiEvent::hide_velocity(void)
77 {
78         _text->hide();
79 }
80
81 void 
82 CanvasMidiEvent::on_channel_change(uint8_t channel)
83 {
84         _region.note_selected(this, true);
85         hide_channel_selector();
86         _region.change_channel(channel);
87 }
88
89 void
90 CanvasMidiEvent::show_channel_selector(void)
91 {
92         if(_channel_selector_widget == 0) {
93                 cerr << "Note has channel: " << int(_note->channel()) << endl;
94                 SingleMidiChannelSelector* _channel_selector = new SingleMidiChannelSelector(_note->channel());
95                 _channel_selector->show_all();
96                 _channel_selector->channel_selected.connect(
97                         sigc::mem_fun(this, &CanvasMidiEvent::on_channel_change));
98
99                 _channel_selector_widget = 
100                         new Widget(*(_item->property_parent()), 
101                                    x1(), 
102                                    y2() + 2, 
103                                    (Gtk::Widget &) *_channel_selector);
104                 
105                 _channel_selector_widget->hide();
106                 _channel_selector_widget->property_height() = 100;
107                 _channel_selector_widget->property_width() = 100;
108                 _channel_selector_widget->raise_to_top();
109                 _channel_selector_widget->show();
110         } else {
111                 hide_channel_selector();
112         }
113 }
114
115 void
116 CanvasMidiEvent::hide_channel_selector(void)
117 {
118         if(_channel_selector_widget) {
119                 _channel_selector_widget->hide();
120                 delete _channel_selector_widget;
121                 _channel_selector_widget = 0;
122         }
123 }
124
125 void
126 CanvasMidiEvent::selected(bool yn)
127 {
128         if (!_note) {
129                 return;
130         } else if (yn) {
131                 set_fill_color(UINT_INTERPOLATE(note_fill_color(_note->velocity()),
132                                         ARDOUR_UI::config()->canvasvar_MidiNoteSelectedOutline.get(), 0.1));
133                 set_outline_color(ARDOUR_UI::config()->canvasvar_MidiNoteSelectedOutline.get());
134                 show_velocity();
135         } else {
136                 set_fill_color(note_fill_color(_note->velocity()));
137                 set_outline_color(note_outline_color(_note->velocity()));
138                 hide_velocity();
139         }
140
141         _selected = yn;
142 }
143
144
145 bool
146 CanvasMidiEvent::on_event(GdkEvent* ev)
147 {
148         MidiStreamView *streamview = _region.midi_stream_view();
149         static uint8_t drag_delta_note = 0;
150         static double  drag_delta_x = 0;
151         static double last_x, last_y;
152         double event_x, event_y, dx, dy;
153         bool select_mod;
154         uint8_t d_velocity = 10;
155
156         if (_region.get_time_axis_view().editor.current_mouse_mode() != Editing::MouseNote)
157                 return false;
158
159         switch (ev->type) {
160         case GDK_SCROLL:
161                 if (Keyboard::modifier_state_equals (ev->scroll.state, Keyboard::Level4Modifier)) {
162                         d_velocity = 1;
163                 }
164
165                 if(ev->scroll.direction == GDK_SCROLL_UP) {
166                         _region.note_selected(this, true);
167                         if (_region.mouse_state() == MidiRegionView::SelectTouchDragging) {
168                                 // TODO: absolute velocity
169                         } else {
170                                 _region.change_velocity(d_velocity, true);
171                         }
172                         return true;
173                 } else if(ev->scroll.direction == GDK_SCROLL_DOWN) {
174                         _region.note_selected(this, true);
175                         if (_region.mouse_state() == MidiRegionView::SelectTouchDragging) {
176                                 // TODO: absolute velocity
177                         } else {
178                                 _region.change_velocity(-d_velocity, true);
179                         }
180                         return true;
181                 } else {
182                         return false;
183                 }
184                 
185         case GDK_KEY_PRESS:
186                 if (_note && ev->key.keyval == GDK_Delete) {
187                         selected(true);
188                         _region.start_delta_command();
189                         _region.command_remove_note(this);
190                 }
191                 break;
192
193         case GDK_KEY_RELEASE:
194                 if (ev->key.keyval == GDK_Delete) {
195                         _region.apply_command();
196                 }
197                 break;
198
199         case GDK_ENTER_NOTIFY:
200                 _region.note_entered(this);
201                 _item->grab_focus();
202                 show_velocity();
203                 Keyboard::magic_widget_grab_focus();
204                 break;
205
206         case GDK_LEAVE_NOTIFY:
207                 Keyboard::magic_widget_drop_focus();
208                 if(! selected()) {
209                         hide_velocity();
210                 }
211                 _region.get_canvas_group()->grab_focus();
212                 break;
213
214         case GDK_BUTTON_PRESS:
215                 if (ev->button.button == 1) {
216                         _state = Pressed;
217                 } else if (ev->button.button == 3) {
218                         show_channel_selector();
219                 }
220                 return true;
221
222         case GDK_MOTION_NOTIFY:
223                 event_x = ev->motion.x;
224                 event_y = ev->motion.y;
225
226                 switch (_state) {
227                 case Pressed: // Drag begin
228                         if (_region.mouse_state() != MidiRegionView::SelectTouchDragging) {
229                                 _item->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
230                                                 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
231                                 _state = Dragging;
232                                 _item->property_parent().get_value()->w2i(event_x, event_y);
233                                 event_x = _region.snap_to_pixel(event_x); 
234                                 last_x = event_x;
235                                 last_y = event_y;
236                                 drag_delta_x = 0;
237                                 drag_delta_note = 0;
238                                 _region.note_selected(this, true);
239                         }
240                         return true;
241
242                 case Dragging: // Drag motion
243                         if (ev->motion.is_hint) {
244                                 int t_x;
245                                 int t_y;
246                                 GdkModifierType state;
247                                 gdk_window_get_pointer(ev->motion.window, &t_x, &t_y, &state);
248                                 event_x = t_x;
249                                 event_y = t_y;
250                         }
251                         _item->property_parent().get_value()->w2i(event_x, event_y);
252                         
253                         // Snap
254                         event_x = _region.snap_to_pixel(event_x); 
255
256                         dx = event_x - last_x;
257                         dy = event_y - last_y;
258
259                         last_x = event_x;
260
261                         drag_delta_x += dx;
262
263                         // Snap to note rows
264                         if (abs(dy) < streamview->note_height()) {
265                                 dy = 0.0;
266                         } else {
267                                 int8_t this_delta_note;
268                                 if (dy > 0) {
269                                         this_delta_note = (int8_t)ceil(dy / streamview->note_height() / 2.0);
270                                 } else {
271                                         this_delta_note = (int8_t)floor(dy / streamview->note_height() / 2.0);
272                                 }
273                                 drag_delta_note -= this_delta_note;
274                                 dy = streamview->note_height() * this_delta_note;
275                                 last_y = last_y + dy;
276                         }
277
278                         _region.move_selection(dx, dy);
279
280                         return true;
281                 default:
282                         break;
283                 }
284                 break;
285
286         case GDK_BUTTON_RELEASE:
287                 select_mod = (ev->motion.state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK));
288                 event_x = ev->button.x;
289                 event_y = ev->button.y;
290                 _item->property_parent().get_value()->w2i(event_x, event_y);
291                 
292                 if(ev->button.button == 3) {
293                         return true;
294                 }
295                 
296                 switch (_state) {
297                 case Pressed: // Clicked
298                         if (_region.midi_view()->editor.current_midi_edit_mode() == Editing::MidiEditSelect) {
299                                 _state = None;
300
301                                 if (_selected && !select_mod && _region.selection_size() > 1)
302                                         _region.unique_select(this);
303                                 else if (_selected)
304                                         _region.note_deselected(this, select_mod);
305                                 else
306                                         _region.note_selected(this, select_mod);
307                         } else if (_region.midi_view()->editor.current_midi_edit_mode() == Editing::MidiEditErase) {
308                                 _region.start_delta_command();
309                                 _region.command_remove_note(this);
310                                 _region.apply_command();
311                         }
312
313                         return true;
314                 case Dragging: // Dropped
315                         _item->ungrab(ev->button.time);
316                         _state = None;
317
318                         if (_note)
319                                 _region.note_dropped(this,
320                                                 _region.midi_view()->editor.pixel_to_frame(abs(drag_delta_x))
321                                                                 * ((drag_delta_x < 0.0) ? -1 : 1),
322                                                 drag_delta_note);
323                         return true;
324                 default:
325                         break;
326                 }
327
328         default:
329                 break;
330         }
331
332         return false;
333 }
334
335 } // namespace Canvas
336 } // namespace Gnome
337