MuseScore/mscore/editdrumset.cpp
2018-08-17 15:06:15 +02:00

632 lines
25 KiB
C++

//=============================================================================
// MuseScore
// Linux Music Score Editor
// $Id: editdrumset.cpp 5384 2012-02-27 12:21:49Z wschweer $
//
// Copyright (C) 2002-2007 Werner Schweer and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// 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 "editdrumset.h"
#include "musescore.h"
#include "libmscore/xml.h"
#include "libmscore/utils.h"
#include "libmscore/chord.h"
#include "libmscore/score.h"
#include "libmscore/note.h"
#include "libmscore/stem.h"
namespace Ms {
extern QMap<QString, QStringList>* smuflRanges();
enum Column : char { PITCH, NOTE, SHORTCUT, NAME };
//---------------------------------------------------------
// noteHeadNames
// "Sol" and "Alt. Brevis" omitted,
// as not being useful for drums
//---------------------------------------------------------
NoteHead::Group noteHeadNames[] = {
NoteHead::Group::HEAD_NORMAL,
NoteHead::Group::HEAD_CROSS,
NoteHead::Group::HEAD_PLUS,
NoteHead::Group::HEAD_XCIRCLE,
NoteHead::Group::HEAD_WITHX,
NoteHead::Group::HEAD_TRIANGLE_UP,
NoteHead::Group::HEAD_TRIANGLE_DOWN,
NoteHead::Group::HEAD_SLASH,
NoteHead::Group::HEAD_SLASHED1,
NoteHead::Group::HEAD_SLASHED2,
NoteHead::Group::HEAD_DIAMOND,
NoteHead::Group::HEAD_DIAMOND_OLD,
NoteHead::Group::HEAD_CIRCLED,
NoteHead::Group::HEAD_CIRCLED_LARGE,
NoteHead::Group::HEAD_LARGE_ARROW,
NoteHead::Group::HEAD_DO,
NoteHead::Group::HEAD_RE,
NoteHead::Group::HEAD_MI,
NoteHead::Group::HEAD_FA,
NoteHead::Group::HEAD_LA,
NoteHead::Group::HEAD_TI,
NoteHead::Group::HEAD_CUSTOM
};
//---------------------------------------------------------
// operator<
//---------------------------------------------------------
bool EditDrumsetTreeWidgetItem::operator<(const QTreeWidgetItem & other) const
{
if (treeWidget()->sortColumn() == Column::PITCH)
return data(Column::PITCH, Qt::UserRole) < other.data(Column::PITCH, Qt::UserRole);
else
return QTreeWidgetItem::operator<(other);
}
//---------------------------------------------------------
// EditDrumset
//---------------------------------------------------------
struct SymbolIcon {
SymId id;
QIcon icon;
SymbolIcon(SymId i, QIcon j)
: id(i), icon(j)
{}
static SymbolIcon generateIcon(const SymId& id, double w, double h, double defaultScale)
{
QIcon icon;
QPixmap image(w, h);
image.fill(Qt::transparent);
QPainter painter(&image);
const QRectF& bbox = ScoreFont::fallbackFont()->bbox(id, 1);
const qreal actualSymbolScale = std::min(w / bbox.width(), h / bbox.height());
qreal mag = std::min(defaultScale, actualSymbolScale);
const qreal& xStShift = (w - mag * bbox.width()) / 2 - mag*bbox.left();
const qreal& yStShift = (h - mag * bbox.height()) / 2 - mag*bbox.top();
const QPointF& stPtPos = QPointF(xStShift, yStShift);
ScoreFont::fallbackFont()->draw(id, &painter, mag, stPtPos);
painter.end();
icon.addPixmap(image);
return SymbolIcon(id, icon);
}
};
EditDrumset::EditDrumset(const Drumset* ds, QWidget* parent)
: QDialog(parent)
{
setObjectName("EditDrumset");
nDrumset = *ds;
setupUi(this);
setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
drumNote->setGrid(70, 80);
drumNote->setDrawGrid(false);
drumNote->setReadOnly(true);
updatePitchesList();
for (auto g : noteHeadNames)
noteHead->addItem(NoteHead::group2userName(g), int(g));
connect(pitchList, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
SLOT(itemChanged(QTreeWidgetItem*, QTreeWidgetItem*)));
connect(buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(bboxClicked(QAbstractButton*)));
connect(name, SIGNAL(textChanged(const QString&)), SLOT(nameChanged(const QString&)));
connect(noteHead, SIGNAL(currentIndexChanged(int)), SLOT(valueChanged()));
connect(staffLine, SIGNAL(valueChanged(int)), SLOT(valueChanged()));
connect(voice, SIGNAL(currentIndexChanged(int)), SLOT(valueChanged()));
connect(stemDirection, SIGNAL(currentIndexChanged(int)), SLOT(valueChanged()));
connect(shortcut, SIGNAL(currentIndexChanged(int)), SLOT(shortcutChanged()));
connect(loadButton, SIGNAL(clicked()), SLOT(load()));
connect(saveButton, SIGNAL(clicked()), SLOT(save()));
pitchList->setColumnWidth(0, 40);
pitchList->setColumnWidth(1, 60);
pitchList->setColumnWidth(2, 30);
QStringList validNoteheadRanges = { "Noteheads", "Round and square noteheads", "Slash noteheads", "Shape note noteheads", "Shape note noteheads supplement" };
QSet<QString> excludeSym = {"noteheadParenthesisLeft", "noteheadParenthesisRight", "noteheadParenthesis", "noteheadNull"};
QStringList primaryNoteheads = {
"noteheadXOrnate",
"noteheadXBlack",
"noteheadXHalf",
"noteheadXWhole",
"noteheadXDoubleWhole",
"noteheadSlashedBlack1",
"noteheadSlashedHalf1",
"noteheadSlashedWhole1",
"noteheadSlashedDoubleWhole1",
"noteheadSlashedBlack2",
"noteheadSlashedHalf2",
"noteheadSlashedWhole2",
"noteheadSlashedDoubleWhole2",
"noteheadSquareBlack",
"noteheadMoonBlack",
"noteheadTriangleUpRightBlack",
"noteheadTriangleDownBlack",
"noteheadTriangleUpBlack",
"noteheadTriangleLeftBlack",
"noteheadTriangleRoundDownBlack",
"noteheadDiamondBlack",
"noteheadDiamondHalf",
"noteheadDiamondWhole",
"noteheadDiamondDoubleWhole",
"noteheadRoundWhiteWithDot",
"noteheadVoidWithX",
"noteheadHalfWithX",
"noteheadWholeWithX",
"noteheadDoubleWholeWithX",
"noteheadLargeArrowUpBlack",
"noteheadLargeArrowUpHalf",
"noteheadLargeArrowUpWhole",
"noteheadLargeArrowUpDoubleWhole"
};
int w = quarterCmb->iconSize().width() * qApp->devicePixelRatio();
int h = quarterCmb->iconSize().height() * qApp->devicePixelRatio();
//default scale is 0.3, will use smaller scale for large noteheads symbols
const qreal defaultScale = 0.3 * qApp->devicePixelRatio();
QList<SymbolIcon> resNoteheads;
for (auto symName : primaryNoteheads) {
SymId id = Sym::name2id(symName);
resNoteheads.append(SymbolIcon::generateIcon(id, w, h, defaultScale));
}
for (QString range : validNoteheadRanges) {
for (auto symName : (*smuflRanges())[range]) {
SymId id = Sym::name2id(symName);
if (!excludeSym.contains(symName) && !primaryNoteheads.contains(symName))
resNoteheads.append(SymbolIcon::generateIcon(id, w, h, defaultScale));
}
}
QComboBox* combos[] = { wholeCmb, halfCmb, quarterCmb, doubleWholeCmb };
for (QComboBox* combo : combos) {
for (auto si : resNoteheads) {
SymId id = si.id;
QIcon icon = si.icon;
combo->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
combo->addItem(icon, Sym::id2userName(id), Sym::id2name(id));
}
}
wholeCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(SymId::noteheadWhole)));
halfCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(SymId::noteheadHalf)));
quarterCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(SymId::noteheadBlack)));
doubleWholeCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(SymId::noteheadDoubleWhole)));
connect(customGbox, SIGNAL(toggled(bool)), this, SLOT(customGboxToggled(bool)));
connect(quarterCmb, SIGNAL(currentIndexChanged(int)), SLOT(customQuarterChanged(int)));
MuseScore::restoreGeometry(this);
Q_ASSERT(pitchList->topLevelItemCount() > 0);
pitchList->setCurrentItem(pitchList->topLevelItem(0));
pitchList->setFocus();
}
//---------------------------------------------------------
// customGboxToggled
//---------------------------------------------------------
void EditDrumset::customGboxToggled(bool checked) {
noteHead->setEnabled(!checked);
if (checked)
noteHead->setCurrentIndex(noteHead->findData(int(NoteHead::Group::HEAD_CUSTOM)));
else
noteHead->setCurrentIndex(noteHead->findData(int(NoteHead::Group::HEAD_NORMAL)));
}
//---------------------------------------------------------
// updatePitchesList
//---------------------------------------------------------
void EditDrumset::updatePitchesList()
{
pitchList->clear();
for (int i = 0; i < 128; ++i) {
QTreeWidgetItem* item = new EditDrumsetTreeWidgetItem(pitchList);
item->setText(Column::PITCH, QString("%1").arg(i));
item->setText(Column::NOTE, pitch2string(i));
if (nDrumset.shortcut(i) == 0)
item->setText(Column::SHORTCUT, "");
else {
QString s(QChar(nDrumset.shortcut(i)));
item->setText(Column::SHORTCUT, s);
}
item->setText(Column::NAME, qApp->translate("drumset", nDrumset.name(i).toUtf8().constData()));
item->setData(Column::PITCH, Qt::UserRole, i);
}
pitchList->sortItems(3, Qt::SortOrder::DescendingOrder);
}
//---------------------------------------------------------
// refreshPitchesList
//---------------------------------------------------------
void EditDrumset::refreshPitchesList()
{
for (int i = 0; i < pitchList->topLevelItemCount(); ++i) {
QTreeWidgetItem* item = pitchList->topLevelItem(i);
int pitch = item->data(0, Qt::UserRole).toInt();
if (nDrumset.shortcut(pitch) == 0)
item->setText(Column::SHORTCUT, "");
else {
QString s(QChar(nDrumset.shortcut(pitch)));
item->setText(Column::SHORTCUT, s);
}
item->setText(Column::NAME, qApp->translate("drumset", nDrumset.name(pitch).toUtf8().constData()));
item->setData(0, Qt::UserRole, pitch);
}
}
void EditDrumset::setEnabledPitchControls(bool enable)
{
customGbox->setEnabled(enable);
noteHead->setEnabled(enable);
voice->setEnabled(enable);
shortcut->setEnabled(enable);
staffLine->setEnabled(enable);
stemDirection->setEnabled(enable);
drumNote->setEnabled(enable);
label_2->setEnabled(enable);
label_3->setEnabled(enable);
label_4->setEnabled(enable);
label_5->setEnabled(enable);
label_6->setEnabled(enable);
}
//---------------------------------------------------------
// nameChanged
//---------------------------------------------------------
void EditDrumset::nameChanged(const QString& n)
{
QTreeWidgetItem* item = pitchList->currentItem();
if (item) {
item->setText(Column::NAME, n);
int pitch = item->data(Column::PITCH, Qt::UserRole).toInt();
if (!n.isEmpty()) {
if (!nDrumset.isValid(pitch))
noteHead->setCurrentIndex(0);
}
else
nDrumset.drum(pitch).name.clear();
}
setEnabledPitchControls(!n.isEmpty());
}
//---------------------------------------------------------
// shortcutChanged
//---------------------------------------------------------
void EditDrumset::shortcutChanged()
{
QTreeWidgetItem* item = pitchList->currentItem();
if (!item)
return;
int pitch = item->data(Column::PITCH, Qt::UserRole).toInt();
int sc;
if (shortcut->currentIndex() == 7)
sc = 0;
else
sc = "ABCDEFG"[shortcut->currentIndex()];
if (QString(QChar(nDrumset.drum(pitch).shortcut)) != shortcut->currentText()) {
//
// remove conflicting shortcuts
//
for (int i = 0; i < DRUM_INSTRUMENTS; ++i) {
if (i == pitch)
continue;
if (nDrumset.drum(i).shortcut == sc)
nDrumset.drum(i).shortcut = 0;
}
nDrumset.drum(pitch).shortcut = sc;
if (shortcut->currentIndex() == 7)
item->setText(Column::SHORTCUT, "");
else
item->setText(Column::SHORTCUT, shortcut->currentText());
}
refreshPitchesList();
}
//---------------------------------------------------------
// bboxClicked
//---------------------------------------------------------
void EditDrumset::bboxClicked(QAbstractButton* button)
{
QDialogButtonBox::ButtonRole br = buttonBox->buttonRole(button);
switch(br) {
case QDialogButtonBox::ApplyRole:
apply();
break;
case QDialogButtonBox::AcceptRole:
apply();
// fall through
case QDialogButtonBox::RejectRole:
close();
break;
default:
qDebug("EditDrumSet: unknown button");
break;
}
}
//---------------------------------------------------------
// apply
//---------------------------------------------------------
void EditDrumset::apply()
{
valueChanged(); //save last changes in name
}
//---------------------------------------------------------
// fillCustomNoteheadsDataFromComboboxes
//---------------------------------------------------------
void EditDrumset::fillCustomNoteheadsDataFromComboboxes(int pitch)
{
nDrumset.drum(pitch).notehead = NoteHead::Group::HEAD_CUSTOM;
nDrumset.drum(pitch).noteheads[int(NoteHead::Type::HEAD_WHOLE)] = Sym::name2id(wholeCmb->currentData().toString());
nDrumset.drum(pitch).noteheads[int(NoteHead::Type::HEAD_QUARTER)] = Sym::name2id(quarterCmb->currentData().toString());
nDrumset.drum(pitch).noteheads[int(NoteHead::Type::HEAD_HALF)] = Sym::name2id(halfCmb->currentData().toString());
nDrumset.drum(pitch).noteheads[int(NoteHead::Type::HEAD_BREVIS)] = Sym::name2id(doubleWholeCmb->currentData().toString());
}
void EditDrumset::fillNoteheadsComboboxes(bool customGroup, int pitch)
{
if (customGroup) {
wholeCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(nDrumset.noteHeads(pitch, NoteHead::Type::HEAD_WHOLE))));
halfCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(nDrumset.noteHeads(pitch, NoteHead::Type::HEAD_HALF))));
quarterCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(nDrumset.noteHeads(pitch, NoteHead::Type::HEAD_QUARTER))));
doubleWholeCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(nDrumset.noteHeads(pitch, NoteHead::Type::HEAD_BREVIS))));
}
else {
const auto group = nDrumset.drum(pitch).notehead;
if (group == NoteHead::Group::HEAD_INVALID)
return;
wholeCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(Note::noteHead(0, group, NoteHead::Type::HEAD_WHOLE))));
halfCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(Note::noteHead(0, group, NoteHead::Type::HEAD_HALF))));
quarterCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(Note::noteHead(0, group, NoteHead::Type::HEAD_QUARTER))));
doubleWholeCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(Note::noteHead(0, group, NoteHead::Type::HEAD_BREVIS))));
}
}
//---------------------------------------------------------
// itemChanged
//---------------------------------------------------------
void EditDrumset::itemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous)
{
if (previous) {
int pitch = previous->data(0, Qt::UserRole).toInt();
nDrumset.drum(pitch).name = name->text();
if (customGbox->isChecked())
fillCustomNoteheadsDataFromComboboxes(pitch);
else {
const QVariant currData = noteHead->currentData();
if (currData.isValid())
nDrumset.drum(pitch).notehead = NoteHead::Group(currData.toInt());
}
nDrumset.drum(pitch).line = staffLine->value();
nDrumset.drum(pitch).voice = voice->currentIndex();
if (shortcut->currentIndex() == 7)
nDrumset.drum(pitch).shortcut = 0;
else
nDrumset.drum(pitch).shortcut = "ABCDEFG"[shortcut->currentIndex()];
nDrumset.drum(pitch).stemDirection = Direction(stemDirection->currentIndex());
previous->setText(Column::NAME, qApp->translate("drumset", nDrumset.name(pitch).toUtf8().constData()));
}
if (current == 0)
return;
staffLine->blockSignals(true);
voice->blockSignals(true);
stemDirection->blockSignals(true);
noteHead->blockSignals(true);
int pitch = current->data(0, Qt::UserRole).toInt();
name->setText(qApp->translate("drumset", nDrumset.name(pitch).toUtf8().constData()));
staffLine->setValue(nDrumset.line(pitch));
qDebug("BEFORE %d", nDrumset.voice(pitch));
voice->setCurrentIndex(nDrumset.voice(pitch));
qDebug("AFTER %d", nDrumset.voice(pitch));
stemDirection->setCurrentIndex(int(nDrumset.stemDirection(pitch)));
NoteHead::Group nh = nDrumset.noteHead(pitch);
bool isCustomGroup = (nh == NoteHead::Group::HEAD_CUSTOM);
if (nDrumset.isValid(pitch))
setCustomNoteheadsGUIEnabled(isCustomGroup);
noteHead->setCurrentIndex(noteHead->findData(int(nh)));
fillNoteheadsComboboxes(isCustomGroup, pitch);
if (nDrumset.shortcut(pitch) == 0)
shortcut->setCurrentIndex(7);
else
shortcut->setCurrentIndex(nDrumset.shortcut(pitch) - 'A');
staffLine->blockSignals(false);
voice->blockSignals(false);
stemDirection->blockSignals(false);
noteHead->blockSignals(false);
updateExample();
}
//---------------------------------------------------------
// setCustomNoteheadsGUIEnabled
//---------------------------------------------------------
void EditDrumset::setCustomNoteheadsGUIEnabled(bool enabled)
{
customGbox->setChecked(enabled);
noteHead->setEnabled(!enabled);
if (enabled)
noteHead->setCurrentIndex(noteHead->findData(int(NoteHead::Group::HEAD_CUSTOM)));
}
//---------------------------------------------------------
// valueChanged
//---------------------------------------------------------
void EditDrumset::valueChanged()
{
if(!pitchList->currentItem())
return;
int pitch = pitchList->currentItem()->data(Column::PITCH, Qt::UserRole).toInt();
nDrumset.drum(pitch).name = name->text();
if (customGbox->isChecked() || noteHead->currentIndex() == noteHead->findData(int(NoteHead::Group::HEAD_CUSTOM))) {
fillCustomNoteheadsDataFromComboboxes(pitch);
setCustomNoteheadsGUIEnabled(true);
}
else {
nDrumset.drum(pitch).notehead = NoteHead::Group(noteHead->currentData().toInt());
fillNoteheadsComboboxes(false, pitch);
setCustomNoteheadsGUIEnabled(false);
}
nDrumset.drum(pitch).line = staffLine->value();
nDrumset.drum(pitch).voice = voice->currentIndex();
nDrumset.drum(pitch).stemDirection = Direction(stemDirection->currentIndex());
if (QString(QChar(nDrumset.drum(pitch).shortcut)) != shortcut->currentText()) {
if (shortcut->currentText().isEmpty())
nDrumset.drum(pitch).shortcut = 0;
else
nDrumset.drum(pitch).shortcut = shortcut->currentText().at(0).toLatin1();
}
updateExample();
}
//---------------------------------------------------------
// updateExample
//---------------------------------------------------------
void EditDrumset::updateExample()
{
int pitch = pitchList->currentItem()->data(0, Qt::UserRole).toInt();
if (!nDrumset.isValid(pitch)) {
drumNote->add(0, 0, "");
return;
}
int line = nDrumset.line(pitch);
NoteHead::Group nh = nDrumset.noteHead(pitch);
int v = nDrumset.voice(pitch);
Direction dir = nDrumset.stemDirection(pitch);
bool up = (Direction::UP == dir) || (Direction::AUTO == dir && line > 4);
Chord* chord = new Chord(gscore);
chord->setDurationType(TDuration::DurationType::V_QUARTER);
chord->setStemDirection(dir);
chord->setTrack(v);
chord->setUp(up);
Note* note = new Note(gscore);
note->setParent(chord);
note->setTrack(v);
note->setPitch(pitch);
note->setTpcFromPitch();
note->setLine(line);
note->setPos(0.0, gscore->spatium() * .5 * line);
note->setHeadType(NoteHead::Type::HEAD_QUARTER);
note->setHeadGroup(nh);
note->setCachedNoteheadSym(Sym::name2id(quarterCmb->currentData().toString()));
chord->add(note);
Stem* stem = new Stem(gscore);
stem->setLen((up ? -3.0 : 3.0) * gscore->spatium());
chord->add(stem);
drumNote->add(0, chord, qApp->translate("drumset", nDrumset.name(pitch).toUtf8().constData()));
}
//---------------------------------------------------------
// load
//---------------------------------------------------------
void EditDrumset::load()
{
QString fname = mscore->getDrumsetFilename(true);
if (fname.isEmpty())
return;
QFile fp(fname);
if (!fp.open(QIODevice::ReadOnly))
return;
XmlReader e(&fp);
nDrumset.clear();
while (e.readNextStartElement()) {
if (e.name() == "museScore") {
if (e.attribute("version") != MSC_VERSION) {
QMessageBox::critical(this, tr("Drumset too old"), tr("MuseScore cannot load this drumset file."));
return;
}
while (e.readNextStartElement()) {
if (e.name() == "Drum")
nDrumset.load(e);
else
e.unknown();
}
}
}
fp.close();
updatePitchesList();
}
//---------------------------------------------------------
// save
//---------------------------------------------------------
void EditDrumset::save()
{
QString fname = mscore->getDrumsetFilename(false);
if (fname.isEmpty())
return;
QFile f(fname);
if (!f.open(QIODevice::WriteOnly)) {
QString s = tr("Open File\n%1\nfailed: %1").arg(strerror(errno));
QMessageBox::critical(mscore, tr("Open File"), s.arg(f.fileName()));
return;
}
valueChanged(); //save last changes in name
XmlWriter xml(0, &f);
xml.header();
xml.stag("museScore version=\"" MSC_VERSION "\"");
nDrumset.save(xml);
xml.etag();
if (f.error() != QFile::NoError) {
QString s = tr("Write File failed: %1").arg(f.errorString());
QMessageBox::critical(this, tr("Write Drumset"), s);
}
}
//---------------------------------------------------------
// hideEvent
//---------------------------------------------------------
void EditDrumset::hideEvent(QHideEvent* event)
{
MuseScore::saveGeometry(this);
QDialog::hideEvent(event);
}
//---------------------------------------------------------
// customQuarterChanged
//---------------------------------------------------------
void EditDrumset::customQuarterChanged(int)
{
updateExample();
}
}