13
0
livetrax/gtk2_ardour/midi_list_editor.cc
Paul Davis 737f91174f tidy up MIDI list editor; make delete/backspace actually delete notes, and make Primary-z and Primary-r do the undo thing
git-svn-id: svn://localhost/ardour2/branches/3.0@11397 d708f5d6-7413-0410-9779-e7cbd77b26cf
2012-01-31 03:03:13 +00:00

274 lines
6.6 KiB
C++

/*
Copyright (C) 2009 Paul Davis
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 <cmath>
#include "evoral/midi_util.h"
#include "evoral/Note.hpp"
#include "ardour/beats_frames_converter.h"
#include "ardour/midi_model.h"
#include "ardour/midi_region.h"
#include "ardour/midi_source.h"
#include "ardour/session.h"
#include "ardour/tempo.h"
#include "gtkmm2ext/gui_thread.h"
#include "gtkmm2ext/keyboard.h"
#include "midi_list_editor.h"
#include "i18n.h"
using namespace std;
using namespace Gtk;
using namespace Glib;
using namespace ARDOUR;
MidiListEditor::MidiListEditor (Session* s, boost::shared_ptr<MidiRegion> r)
: ArdourWindow (r->name())
, region (r)
{
/* We do not handle nested sources/regions. Caller should have tackled this */
if (r->max_source_level() > 0) {
throw failed_constructor();
}
set_session (s);
model = ListStore::create (columns);
view.set_model (model);
view.signal_key_press_event().connect (sigc::mem_fun (*this, &MidiListEditor::key_press));
view.signal_key_release_event().connect (sigc::mem_fun (*this, &MidiListEditor::key_release));
view.append_column (_("Start"), columns.start);
view.append_column (_("Channel"), columns.channel);
view.append_column (_("Num"), columns.note);
view.append_column (_("Name"), columns.note_name);
view.append_column (_("Vel"), columns.velocity);
view.append_column (_("Length"), columns.length);
view.append_column (_("End"), columns.end);
view.set_headers_visible (true);
view.set_rules_hint (true);
view.get_selection()->set_mode (SELECTION_MULTIPLE);
for (int i = 0; i < 6; ++i) {
CellRendererText* renderer = dynamic_cast<CellRendererText*>(view.get_column_cell_renderer (i));
renderer->property_editable() = true;
renderer->signal_editing_started().connect (sigc::bind (sigc::mem_fun (*this, &MidiListEditor::editing_started), i));
renderer->signal_editing_canceled().connect (sigc::mem_fun (*this, &MidiListEditor::editing_canceled));
renderer->signal_edited().connect (sigc::mem_fun (*this, &MidiListEditor::edited));
}
scroller.add (view);
scroller.set_policy (POLICY_NEVER, POLICY_AUTOMATIC);
redisplay_model ();
region->midi_source(0)->model()->ContentsChanged.connect (content_connection, invalidator (*this),
boost::bind (&MidiListEditor::redisplay_model, this), gui_context());
view.show ();
scroller.show ();
add (scroller);
set_size_request (-1, 400);
}
MidiListEditor::~MidiListEditor ()
{
}
bool
MidiListEditor::key_press (GdkEventKey* ev)
{
bool editing = !_current_edit.empty();
bool ret = false;
if (editing) {
switch (ev->keyval) {
case GDK_Tab:
break;
case GDK_Right:
break;
case GDK_Left:
break;
case GDK_Up:
break;
case GDK_Down:
break;
case GDK_Escape:
break;
}
}
return ret;
}
bool
MidiListEditor::key_release (GdkEventKey* ev)
{
bool ret = false;
switch (ev->keyval) {
case GDK_Delete:
case GDK_BackSpace:
delete_selected_note ();
ret = true;
break;
case GDK_z:
if (_session && Gtkmm2ext::Keyboard::modifier_state_contains (ev->state, Gtkmm2ext::Keyboard::PrimaryModifier)) {
_session->undo (1);
}
break;
case GDK_r:
if (_session && Gtkmm2ext::Keyboard::modifier_state_contains (ev->state, Gtkmm2ext::Keyboard::PrimaryModifier)) {
_session->redo (1);
}
break;
default:
break;
}
return ret;
}
void
MidiListEditor::delete_selected_note ()
{
Glib::RefPtr<TreeSelection> selection = view.get_selection();
TreeView::Selection::ListHandle_Path rows = selection->get_selected_rows ();
if (rows.empty()) {
return;
}
typedef vector<boost::shared_ptr<NoteType> > Notes;
Notes to_delete;
for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin(); i != rows.end(); ++i) {
TreeIter iter;
if ((iter = model->get_iter (*i))) {
boost::shared_ptr<NoteType> note = (*iter)[columns._note];
to_delete.push_back (note);
}
}
boost::shared_ptr<MidiModel> m (region->midi_source(0)->model());
MidiModel::NoteDiffCommand* cmd = m->new_note_diff_command (_("delete notes (from list)"));
for (Notes::iterator i = to_delete.begin(); i != to_delete.end(); ++i) {
cmd->remove (*i);
}
m->apply_command (*_session, cmd);
}
void
MidiListEditor::editing_started (CellEditable*, const string& path, int colno)
{
_current_edit = path;
cerr << "Now editing " << _current_edit << " Column " << colno << endl;
}
void
MidiListEditor::editing_canceled ()
{
_current_edit = "";
}
void
MidiListEditor::edited (const std::string& path, const std::string& /* text */)
{
TreeModel::iterator iter = model->get_iter (path);
cerr << "Edit at " << path << endl;
if (!iter) {
return;
}
boost::shared_ptr<NoteType> note = (*iter)[columns._note];
cerr << "Edited " << *note << endl;
redisplay_model ();
/* keep selected row(s), move cursor there, to allow us to continue editing */
}
void
MidiListEditor::redisplay_model ()
{
view.set_model (Glib::RefPtr<Gtk::ListStore>(0));
model->clear ();
if (_session) {
BeatsFramesConverter conv (_session->tempo_map(), region->position());
MidiModel::Notes notes = region->midi_source(0)->model()->notes();
TreeModel::Row row;
stringstream ss;
for (MidiModel::Notes::iterator i = notes.begin(); i != notes.end(); ++i) {
row = *(model->append());
row[columns.channel] = (*i)->channel() + 1;
row[columns.note_name] = Evoral::midi_note_name ((*i)->note());
row[columns.note] = (*i)->note();
row[columns.velocity] = (*i)->velocity();
Timecode::BBT_Time bbt;
double dur;
_session->tempo_map().bbt_time (conv.to ((*i)->time()), bbt);
ss.str ("");
ss << bbt;
row[columns.start] = ss.str();
bbt.bars = 0;
dur = (*i)->end_time() - (*i)->time();
bbt.beats = floor (dur);
bbt.ticks = (uint32_t) lrint (fmod (dur, 1.0) * Timecode::BBT_Time::ticks_per_beat);
_session->tempo_map().bbt_duration_at (region->position(), bbt, 0);
ss.str ("");
ss << bbt;
row[columns.length] = ss.str();
_session->tempo_map().bbt_time (conv.to ((*i)->end_time()), bbt);
ss.str ("");
ss << bbt;
row[columns.end] = ss.str();
row[columns._note] = (*i);
}
}
view.set_model (model);
}