Basic canvas note event handling framework.

Note dragging (non-functional).


git-svn-id: svn://localhost/ardour2/trunk@2187 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
David Robillard 2007-07-30 16:33:10 +00:00
parent 633d9131af
commit 2cbaa2751c
10 changed files with 398 additions and 89 deletions

View file

@ -109,6 +109,7 @@ canvas-simplerect.c
simplerect.cc
canvas-waveview.c
diamond.cc
canvas-midi-event.cc
crossfade_edit.cc
crossfade_view.cc
curvetest.cc

41
gtk2_ardour/canvas-hit.h Normal file
View file

@ -0,0 +1,41 @@
/*
Copyright (C) 2007 Paul Davis
Author: Dave Robillard
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __gtk_ardour_canvas_hit_h__
#define __gtk_ardour_canvas_hit_h__
#include <iostream>
#include "simplerect.h"
#include "diamond.h"
namespace Gnome {
namespace Canvas {
class CanvasHit : public Diamond, public CanvasMidiEvent {
public:
CanvasHit(MidiRegionView& region, Group& group, double size)
: Diamond(group, size), CanvasMidiEvent(region, this) {}
bool on_event(GdkEvent* ev) { return CanvasMidiEvent::on_event(ev); }
};
} // namespace Gnome
} // namespace Canvas
#endif /* __gtk_ardour_canvas_hit_h__ */

View file

@ -0,0 +1,122 @@
/*
Copyright (C) 2007 Paul Davis
Author: Dave Robillard
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <iostream>
#include "canvas-midi-event.h"
#include "midi_region_view.h"
#include "public_editor.h"
#include "editing_syms.h"
using namespace std;
namespace Gnome {
namespace Canvas {
CanvasMidiEvent::CanvasMidiEvent(MidiRegionView& region, Item* item)
: _region(region)
, _item(item)
, _state(None)
{
}
bool
CanvasMidiEvent::on_event(GdkEvent* ev)
{
static double last_x, last_y;
double event_x, event_y;
if (_region.get_time_axis_view().editor.current_mouse_mode() != Editing::MouseNote)
return false;
switch (ev->type) {
/*case GDK_ENTER_NOTIFY:
cerr << "ENTERED: " << ev->crossing.state << endl;
if ( (ev->crossing.state & GDK_BUTTON2_MASK) ) {
}
break;
*/
case GDK_KEY_PRESS:
cerr << "EVENT KEY PRESS\n"; // doesn't work :/
break;
case GDK_BUTTON_PRESS:
_state = Pressed;
return true;
case GDK_MOTION_NOTIFY:
event_x = ev->motion.x;
event_y = ev->motion.y;
_item->property_parent().get_value()->w2i(event_x, event_y);
switch (_state) {
case Pressed:
_item->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
_state = Dragging;
last_x = event_x;
last_y = event_y;
return true;
case Dragging:
if (ev->motion.is_hint) {
int t_x;
int t_y;
GdkModifierType state;
gdk_window_get_pointer(ev->motion.window, &t_x, &t_y, &state);
event_x = t_x;
event_y = t_y;
}
_item->move(event_x - last_x, event_y - last_y);
last_x = event_x;
last_y = event_y;
return true;
default:
break;
}
break;
case GDK_BUTTON_RELEASE:
switch (_state) {
case Pressed: // Clicked
_state = None;
return true;
case Dragging: // Dropped
_item->ungrab(ev->button.time);
_state = None;
return true;
default:
break;
}
default:
break;
}
return false;
}
} // namespace Canvas
} // namespace Gnome

View file

@ -0,0 +1,59 @@
/*
Copyright (C) 2007 Paul Davis
Author: Dave Robillard
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __gtk_ardour_canvas_midi_event_h__
#define __gtk_ardour_canvas_midi_event_h__
#include "simplerect.h"
class Editor;
class MidiRegionView;
namespace Gnome {
namespace Canvas {
/** This manages all the event handling for any MIDI event on the canvas.
*
* This is not actually a canvas item itself to avoid the dreaded diamond,
* since various types of canvas items (Note (rect), Hit (diamond), etc)
* need to share this functionality but can't share an ancestor.
*
* Note: Because of this, derived classes need to manually bounce events to
* on_event, it won't happen automatically.
*/
class CanvasMidiEvent {
public:
CanvasMidiEvent(MidiRegionView& region, Item* item);
virtual ~CanvasMidiEvent() {}
virtual bool on_event(GdkEvent* ev);
private:
enum State { None, Pressed, Dragging };
MidiRegionView& _region;
Item* const _item;
State _state;
};
} // namespace Gnome
} // namespace Canvas
#endif /* __gtk_ardour_canvas_midi_event_h__ */

43
gtk2_ardour/canvas-note.h Normal file
View file

@ -0,0 +1,43 @@
/*
Copyright (C) 2007 Paul Davis
Author: Dave Robillard
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __gtk_ardour_canvas_note_h__
#define __gtk_ardour_canvas_note_h__
#include <iostream>
#include "simplerect.h"
#include "canvas-midi-event.h"
namespace Gnome {
namespace Canvas {
class CanvasNote : public SimpleRect, public CanvasMidiEvent {
public:
CanvasNote(MidiRegionView& region, Group& group)
: SimpleRect(group), CanvasMidiEvent(region, this)
{
}
bool on_event(GdkEvent* ev) { return CanvasMidiEvent::on_event(ev); }
};
} // namespace Gnome
} // namespace Canvas
#endif /* __gtk_ardour_canvas_note_h__ */

View file

@ -21,14 +21,15 @@
#define __ardour_diamond_h__
#include <libgnomecanvasmm/polygon.h>
#include "canvas-midi-event.h"
namespace Gnome {
namespace Canvas {
class Diamond : public Gnome::Canvas::Polygon {
class Diamond : public Polygon {
public:
Diamond(Gnome::Canvas::Group& group, double height);
Diamond(Group& group, double height);
};

View file

@ -41,7 +41,7 @@
#include "midi_time_axis.h"
#include "simplerect.h"
#include "simpleline.h"
#include "diamond.h"
#include "canvas-hit.h"
#include "public_editor.h"
#include "ghostregion.h"
#include "midi_time_axis.h"
@ -108,56 +108,124 @@ MidiRegionView::init (Gdk::Color& basic_color, bool wfd)
bool
MidiRegionView::canvas_event(GdkEvent* ev)
{
if (trackview.editor.current_mouse_mode() == MouseNote) {
if (ev->type == GDK_BUTTON_PRESS) {
MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
MidiStreamView* const view = mtv->midi_view();
const uint8_t note_range = view->highest_note() - view->lowest_note() + 1;
const double footer_height = name_highlight->property_y2() - name_highlight->property_y1();
const double roll_height = trackview.height - footer_height;
double x = ev->button.x;
double y = ev->button.y;
get_canvas_group()->w2i(x, y);
enum State { None, Pressed, Dragging };
static State _state;
double note = floor((roll_height - y) / roll_height * (double)note_range) + view->lowest_note();
assert(note >= 0.0);
assert(note <= 127.0);
const nframes_t stamp = trackview.editor.pixel_to_frame (x);
assert(stamp >= 0);
//assert(stamp <= _region->length());
const Meter& m = trackview.session().tempo_map().meter_at(stamp);
const Tempo& t = trackview.session().tempo_map().tempo_at(stamp);
double dur = m.frames_per_bar(t, trackview.session().frame_rate()) / m.beats_per_bar();
MidiModel* model = midi_region()->midi_source(0)->model();
// Add a 1 beat long note (for now)
const MidiModel::Note new_note(stamp, dur, (uint8_t)note, 0x40);
model->begin_command();
model->add_note(new_note);
model->finish_command();
view->update_bounds(new_note.note());
add_note(new_note);
}
}
static double last_x, last_y;
double event_x, event_y;
if (trackview.editor.current_mouse_mode() != MouseNote)
return false;
switch (ev->type) {
case GDK_BUTTON_PRESS:
//group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, ev->button.time);
// This should happen on release...
if (ev->button.button == 1)
create_note_at(ev->button.x, ev->button.y);
_state = Pressed;
return true;
case GDK_MOTION_NOTIFY:
cerr << "MOTION\n";
event_x = ev->motion.x;
event_y = ev->motion.y;
group->w2i(event_x, event_y);
switch (_state) {
case Pressed:
cerr << "SELECT DRAG START\n";
//group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
// Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
_state = Dragging;
last_x = event_x;
last_y = event_y;
return true;
case Dragging:
if (ev->motion.is_hint) {
int t_x;
int t_y;
GdkModifierType state;
gdk_window_get_pointer(ev->motion.window, &t_x, &t_y, &state);
event_x = t_x;
event_y = t_y;
}
cerr << "SELECT DRAG" << endl;
//move(event_x - last_x, event_y - last_y);
last_x = event_x;
last_y = event_y;
return true;
default:
break;
}
break;
case GDK_BUTTON_RELEASE:
cerr << "RELEASE\n";
//group->ungrab(ev->button.time);
switch (_state) {
case Pressed:
cerr << "CLICK\n";
//create_note_at(ev->button.x, ev->button.y);
_state = None;
return true;
case Dragging:
cerr << "SELECT RECT DONE\n";
_state = None;
return true;
default:
break;
}
default:
break;
}
return false;
}
bool
MidiRegionView::note_canvas_event(GdkEvent* ev)
/** Add a note to the model, and the view, at a canvas (click) coordinate */
void
MidiRegionView::create_note_at(double x, double y)
{
cerr << "NOTE CANVAS EVENT" << endl;
MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
MidiStreamView* const view = mtv->midi_view();
return true;
const uint8_t note_range = view->highest_note() - view->lowest_note() + 1;
const double footer_height = name_highlight->property_y2() - name_highlight->property_y1();
const double roll_height = trackview.height - footer_height;
get_canvas_group()->w2i(x, y);
double note = floor((roll_height - y) / roll_height * (double)note_range) + view->lowest_note();
assert(note >= 0.0);
assert(note <= 127.0);
const nframes_t stamp = trackview.editor.pixel_to_frame (x);
assert(stamp >= 0);
//assert(stamp <= _region->length());
const Meter& m = trackview.session().tempo_map().meter_at(stamp);
const Tempo& t = trackview.session().tempo_map().tempo_at(stamp);
double dur = m.frames_per_bar(t, trackview.session().frame_rate()) / m.beats_per_bar();
MidiModel* model = midi_region()->midi_source(0)->model();
// Add a 1 beat long note (for now)
const MidiModel::Note new_note(stamp, dur, (uint8_t)note, 0x40);
model->begin_command();
model->add_note(new_note);
model->finish_command();
view->update_bounds(new_note.note());
add_note(new_note);
}
@ -279,7 +347,7 @@ MidiRegionView::add_ghost (AutomationTimeAxisView& atv)
void
MidiRegionView::begin_write()
{
_active_notes = new ArdourCanvas::SimpleRect*[128];
_active_notes = new CanvasNote*[128];
for (unsigned i=0; i < 128; ++i)
_active_notes[i] = NULL;
}
@ -323,7 +391,7 @@ MidiRegionView::add_event (const MidiEvent& ev)
const double y1 = trackview.height - (pixel_range * (note - view->lowest_note() + 1))
- footer_height - 3.0;
ArdourCanvas::SimpleRect * ev_rect = new Gnome::Canvas::SimpleRect(*group);
CanvasNote* ev_rect = new CanvasNote(*this, *group);
ev_rect->property_x1() = trackview.editor.frame_to_pixel (
(nframes_t)ev.time);
ev_rect->property_y1() = y1;
@ -335,8 +403,6 @@ MidiRegionView::add_event (const MidiEvent& ev)
ev_rect->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
ev_rect->property_fill_color_rgba() = 0xFFFFFF66;
ev_rect->signal_event().connect(sigc::mem_fun(this, &MidiRegionView::note_canvas_event));
ev_rect->raise_to_top();
_events.push_back(ev_rect);
@ -358,14 +424,12 @@ MidiRegionView::add_event (const MidiEvent& ev)
const double y = trackview.height - (pixel_range * (note - view->lowest_note() + 1))
- footer_height - 3.0;
Diamond* ev_diamond = new Diamond(*group, std::min(pixel_range, 5.0));
CanvasHit* ev_diamond = new CanvasHit(*this, *group, std::min(pixel_range, 5.0));
ev_diamond->move(x, y);
ev_diamond->show();
ev_diamond->property_outline_color_rgba() = 0xFFFFFFDD;
ev_diamond->property_fill_color_rgba() = 0xFFFFFF66;
ev_diamond->signal_event().connect(sigc::mem_fun(this, &MidiRegionView::note_canvas_event));
_events.push_back(ev_diamond);
}
}
@ -385,7 +449,7 @@ MidiRegionView::extend_active_notes()
}
/** Add a MIDI note (with duration).
/** Add a MIDI note to the view (with duration).
*
* This does no 'realtime' note resolution, notes from a MidiModel have a
* duration so they can be drawn in full immediately.
@ -419,7 +483,7 @@ MidiRegionView::add_note (const MidiModel::Note& note)
const double y1 = trackview.height - (pixel_range * (note.note() - view->lowest_note() + 1))
- footer_height - 3.0;
ArdourCanvas::SimpleRect * ev_rect = new Gnome::Canvas::SimpleRect(*group);
ArdourCanvas::SimpleRect * ev_rect = new CanvasNote(*this, *group);
ev_rect->property_x1() = trackview.editor.frame_to_pixel((nframes_t)note.time());
ev_rect->property_y1() = y1;
ev_rect->property_x2() = trackview.editor.frame_to_pixel((nframes_t)(note.end_time()));
@ -437,7 +501,7 @@ MidiRegionView::add_note (const MidiModel::Note& note)
const double y = trackview.height - (pixel_range * (note.note() - view->lowest_note() + 1))
- footer_height - 3.0;
Diamond* ev_diamond = new Diamond(*group, std::min(pixel_range, 5.0));
CanvasHit* ev_diamond = new CanvasHit(*this, *group, std::min(pixel_range, 5.0));
ev_diamond->move(x, y);
ev_diamond->show();
ev_diamond->property_fill_color_rgba() = fill;

View file

@ -33,6 +33,7 @@
#include "automation_line.h"
#include "enums.h"
#include "canvas.h"
#include "canvas-note.h"
namespace ARDOUR {
class MidiRegion;
@ -70,6 +71,8 @@ class MidiRegionView : public RegionView
void end_write();
void extend_active_notes();
void create_note_at(double x, double y);
protected:
/* this constructor allows derived types
@ -101,7 +104,7 @@ class MidiRegionView : public RegionView
bool note_canvas_event(GdkEvent* ev);
std::vector<ArdourCanvas::Item*> _events;
ArdourCanvas::SimpleRect** _active_notes;
ArdourCanvas::CanvasNote** _active_notes;
};
#endif /* __gtk_ardour_midi_region_view_h__ */

View file

@ -827,18 +827,6 @@ MidiDiskstream::do_refill ()
return 0;
}
/* if there are 2+ chunks of disk i/o possible for
this track, let the caller know so that it can arrange
for us to be called again, ASAP.
*/
// FIXME: using disk_io_chunk_frames as an event count, not good
// count vs duration semantic differences are nonexistant for audio,
// which makes translating for MIDI code confusing...
if (_playback_buf->write_space() >= (_slaved?3:2) * disk_io_chunk_frames) {
ret = 1;
}
/* if we're running close to normal speed and there isn't enough
space to do disk_io_chunk_frames of I/O, then don't bother.
@ -847,7 +835,7 @@ MidiDiskstream::do_refill ()
*/
if ((write_space < disk_io_chunk_frames) && fabs (_actual_speed) < 2.0f) {
cerr << "No refill 1\n";
//cerr << "No refill 1\n";
return 0;
}
@ -857,12 +845,12 @@ MidiDiskstream::do_refill ()
*/
if (_slaved && write_space < (_playback_buf->capacity() / 2)) {
cerr << "No refill 2\n";
//cerr << "No refill 2\n";
return 0;
}
if (reversed) {
cerr << "No refill 3 (reverse)\n";
//cerr << "No refill 3 (reverse)\n";
return 0;
}
@ -874,26 +862,10 @@ MidiDiskstream::do_refill ()
return 0;
}
#if 0
// or this
if (file_frame > max_frames - total_space) {
/* to close to the end: read what we can, and zero fill the rest */
zero_fill = total_space - (max_frames - file_frame);
total_space = max_frames - file_frame;
} else {
zero_fill = 0;
}
#endif
// At this point we:
// At this point we...
assert(_playback_buf->write_space() > 0); // ... have something to write to, and
assert(file_frame <= max_frames); // ... something to write
// So (read it, then) write it:
nframes_t file_frame_tmp = file_frame;
nframes_t to_read = min(disk_io_chunk_frames, (max_frames - file_frame));

View file

@ -86,7 +86,7 @@ MidiModel::read (MidiRingBuffer& dst, nframes_t start, nframes_t nframes, nframe
{
size_t read_events = 0;
//cerr << "MM READ " << start << " .. " << nframes << endl;
cerr << "MM READ @ " << start << " + " << nframes << endl;
/* FIXME: cache last lookup value to avoid the search */
@ -140,6 +140,9 @@ MidiModel::read (MidiRingBuffer& dst, nframes_t start, nframes_t nframes, nframe
}
}
if (read_events > 0)
cerr << "MM READ " << read_events << " EVENTS" << endl;
return read_events;
}