/* 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 #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 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(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 selection = view.get_selection(); TreeView::Selection::ListHandle_Path rows = selection->get_selected_rows (); if (rows.empty()) { return; } typedef vector > 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 note = (*iter)[columns._note]; to_delete.push_back (note); } } boost::shared_ptr 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 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(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); }