MuseScore/mscore/harmonyedit.cpp
2015-11-16 14:24:47 +01:00

618 lines
20 KiB
C++

//=============================================================================
// MuseScore
// Linux Music Score Editor
// $Id:$
//
// Copyright (C) 2009-2011 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 "musescore.h"
#include "harmonyedit.h"
#include "harmonycanvas.h"
#include "palette.h"
#include "libmscore/accidental.h"
#include "libmscore/score.h"
#include "icons.h"
#include "libmscore/pitchspelling.h"
#include "libmscore/symbol.h"
#include "libmscore/chordlist.h"
#include "libmscore/mscore.h"
#include "libmscore/xml.h"
namespace Ms {
extern bool useFactorySettings;
//---------------------------------------------------------
// ChordStyleEditor
//---------------------------------------------------------
ChordStyleEditor::ChordStyleEditor(QWidget* parent)
: QDialog(parent)
{
setupUi(this);
setWindowTitle(tr("MuseScore: Chord Symbols Style Editor"));
setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
fileButton->setIcon(*icons[int(Icons::fileOpen_ICON)]);
chordList = 0;
score = 0;
connect(fileButton, SIGNAL(clicked()), SLOT(fileButtonClicked()));
connect(saveButton, SIGNAL(clicked()), SLOT(saveButtonClicked()));
connect(harmonyList, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
SLOT(harmonyChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
}
//---------------------------------------------------------
// setScore
//---------------------------------------------------------
void ChordStyleEditor::setScore(Score* s)
{
score = s;
setChordList(s->style()->chordList());
}
//---------------------------------------------------------
// editChordStyle
//---------------------------------------------------------
void MuseScore::editChordStyle()
{
if (chordStyleEditor == 0) {
chordStyleEditor = new ChordStyleEditor(0);
chordStyleEditor->restore();
}
chordStyleEditor->setScore(cs);
chordStyleEditor->show();
chordStyleEditor->raise();
}
//---------------------------------------------------------
// fileButtonClicked
//---------------------------------------------------------
void ChordStyleEditor::fileButtonClicked()
{
QString fn = mscore->getChordStyleFilename(true);
if (fn.isEmpty())
return;
loadChordDescriptionFile(fn);
}
//---------------------------------------------------------
// saveButtonClicked
//---------------------------------------------------------
void ChordStyleEditor::saveButtonClicked()
{
if (!chordList)
return;
canvas->updateChordDescription();
QString fn = mscore->getChordStyleFilename(false);
if (fn.isEmpty())
return;
chordList->write(fn);
}
//---------------------------------------------------------
// loadChordDescriptionFile
//---------------------------------------------------------
void ChordStyleEditor::loadChordDescriptionFile(const QString& s)
{
ChordList* cl = new ChordList;
if (!cl->read("chords.xml")) {
qDebug("cannot read <chords.xml>");
return;
}
if (!cl->read(s)) {
qDebug("cannot read <%s>", qPrintable(s));
return;
}
setChordList(cl);
}
//---------------------------------------------------------
// setChordList
//---------------------------------------------------------
void ChordStyleEditor::setChordList(ChordList* cl)
{
harmonyList->clear();
foreach (const ChordDescription& d, *cl) {
QTreeWidgetItem* item = new QTreeWidgetItem;
item->setData(0, Qt::UserRole, QVariant::fromValue<void*>((void*)&d));
item->setText(0, QString("%1").arg(d.id));
if (!d.names.isEmpty())
item->setText(1, QString("%1").arg(d.names.front()));
harmonyList->addTopLevelItem(item);
}
delete chordList;
chordList = new ChordList(*cl);
canvas->setChordDescription(0, 0);
paletteTab->clear();
foreach(const ChordFont& f, chordList->fonts) {
// create symbol palette
Palette* p = new Palette();
PaletteScrollArea* accPalette = new PaletteScrollArea(p);
QSizePolicy policy1(QSizePolicy::Expanding, QSizePolicy::Expanding);
accPalette->setSizePolicy(policy1);
accPalette->setRestrictHeight(false);
p->setGrid(50, 50);
paletteTab->addTab(accPalette, f.family);
QFont qf(f.family);
qf.setStyleStrategy(QFont::NoFontMerging);
int size = lrint(20.0 * DPI / PPI);
qf.setPixelSize(size);
QFontMetricsF fi(qf);
for (int i = 0; i < 255; ++i) {
if (fi.inFont(QChar(i))) {
FSymbol* s = new FSymbol(gscore);
s->setFont(qf);
s->setCode(i);
p->append(s, "??");
}
}
}
raise();
}
//---------------------------------------------------------
// harmonyChanged
//---------------------------------------------------------
void ChordStyleEditor::harmonyChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous)
{
if (previous) {
// ChordDescription* d = static_cast<ChordDescription*>(previous->data(0, Qt::UserRole).value<void*>());
canvas->updateChordDescription();
}
if (current) {
ChordDescription* d = static_cast<ChordDescription*>(current->data(0, Qt::UserRole).value<void*>());
canvas->setChordDescription(d, chordList);
}
}
//---------------------------------------------------------
// save
//---------------------------------------------------------
void ChordStyleEditor::save()
{
QSettings settings;
settings.beginGroup("ChordStyleEditor");
settings.setValue("splitter1", splitter1->saveState());
settings.setValue("splitter2", splitter2->saveState());
// settings.setValue("list", harmonyList->saveState());
settings.setValue("col1", harmonyList->columnWidth(0));
}
//---------------------------------------------------------
// restore
//---------------------------------------------------------
void ChordStyleEditor::restore()
{
if (!useFactorySettings) {
QSettings settings;
settings.beginGroup("ChordStyleEditor");
splitter1->restoreState(settings.value("splitter1").toByteArray());
splitter2->restoreState(settings.value("splitter2").toByteArray());
harmonyList->setColumnWidth(0, settings.value("col1", 30).toInt());
}
}
//---------------------------------------------------------
// HarmonyCanvas
//---------------------------------------------------------
HarmonyCanvas::HarmonyCanvas(QWidget* parent)
: QFrame(parent)
{
setFrameStyle(QFrame::StyledPanel | QFrame::Raised);
setAcceptDrops(true);
setFocusPolicy(Qt::StrongFocus);
extraMag = 3.0;
chordDescription = 0;
chordList = 0;
moveElement = 0;
QAction* a = getAction("delete");
addAction(a);
connect(a, SIGNAL(triggered()), SLOT(deleteAction()));
}
//---------------------------------------------------------
// paintEvent
//---------------------------------------------------------
void HarmonyCanvas::paintEvent(QPaintEvent* event)
{
QFrame::paintEvent(event);
if (!chordDescription)
return;
qreal spatium = gscore->spatium();
qreal mag = PALETTE_SPATIUM * extraMag / spatium;
spatium = SPATIUM20;
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing, true);
qreal wh = double(height());
qreal ww = double(width());
_matrix = QTransform(mag, 0.0, 0.0, mag, ww*.1, wh*.8);
imatrix = _matrix.inverted();
p.setWorldTransform(_matrix);
QRectF f = imatrix.mapRect(QRectF(0.0, 0.0, ww, wh));
p.setPen(QPen(Qt::darkGray));
p.drawLine(f.x(), 0.0, f.width(), 0.0);
p.drawLine(0.0, f.y(), 0.0, f.height());
foreach(const TextSegment* ts, textList) {
p.setFont(ts->font);
QPen pen(ts->select ? Qt::blue : palette().color(QPalette::Text));
p.setPen(pen);
p.drawText(ts->x, ts->y, ts->text);
}
if (dragElement && dragElement->type() == Element::Type::FSYMBOL) {
FSymbol* sb = static_cast<FSymbol*>(dragElement);
double _spatium = 2.0 * PALETTE_SPATIUM / extraMag;
const TextStyle* st = &gscore->textStyle(TextStyleType::HARMONY);
QFont ff(st->fontPx(_spatium));
ff.setFamily(sb->font().family());
QString s;
int code = sb->code();
if (code & 0xffff0000) {
s = QChar(QChar::highSurrogate(code));
s += QChar(QChar::lowSurrogate(code));
}
else
s = QChar(code);
p.setFont(ff);
QPen pen(Qt::yellow);
p.setPen(pen);
p.drawText(dragElement->pos(), s);
}
}
//---------------------------------------------------------
// render
//---------------------------------------------------------
void HarmonyCanvas::render(const QList<RenderAction>& renderList, double& x, double& y, int tpc, NoteSpellingType noteSpelling, NoteCaseType noteCase)
{
QStack<QPointF> stack;
int fontIdx = 0;
double _spatium = 2.0 * PALETTE_SPATIUM / extraMag;
// qreal mag = PALETTE_SPATIUM * extraMag / _spatium;
QList<QFont> fontList; // temp values used in render()
const TextStyle* st = &gscore->textStyle(TextStyleType::HARMONY);
foreach(ChordFont cf, chordList->fonts) {
if (cf.family.isEmpty() || cf.family == "default")
fontList.append(st->fontPx(_spatium * cf.mag));
else {
QFont ff(st->fontPx(_spatium * cf.mag));
ff.setFamily(cf.family);
fontList.append(ff);
}
}
if (fontList.isEmpty())
fontList.append(st->fontPx(_spatium));
foreach(const RenderAction& a, renderList) {
if (a.type == RenderAction::RenderActionType::SET) {
TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
ChordSymbol cs = chordList->symbol(a.text);
if (cs.isValid()) {
ts->font = fontList[cs.fontIdx];
ts->setText(cs.value);
}
else
ts->setText(a.text);
textList.append(ts);
x += ts->width();
}
else if (a.type == RenderAction::RenderActionType::MOVE) {
x += a.movex;// * mag;
y += a.movey; // * mag;
}
else if (a.type == RenderAction::RenderActionType::PUSH)
stack.push(QPointF(x,y));
else if (a.type == RenderAction::RenderActionType::POP) {
if (!stack.isEmpty()) {
QPointF pt = stack.pop();
x = pt.x();
y = pt.y();
}
else
qDebug("RenderAction::RenderActionType::POP: stack empty");
}
else if (a.type == RenderAction::RenderActionType::NOTE) {
QString c;
int acc;
tpc2name(tpc, noteSpelling, noteCase, c, acc);
TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
QString lookup = "note" + c;
ChordSymbol cs = chordList->symbol(lookup);
if (!cs.isValid())
cs = chordList->symbol(c);
if (cs.isValid()) {
ts->font = fontList[cs.fontIdx];
ts->setText(cs.value);
}
else
ts->setText(c);
textList.append(ts);
x += ts->width();
}
else if (a.type == RenderAction::RenderActionType::ACCIDENTAL) {
QString c;
QString acc;
tpc2name(tpc, noteSpelling, noteCase, c, acc);
if (acc != "") {
TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
ChordSymbol cs = chordList->symbol(acc);
if (cs.isValid()) {
ts->font = fontList[cs.fontIdx];
ts->setText(cs.value);
}
else
ts->setText(acc);
textList.append(ts);
x += ts->width();
}
}
}
}
//---------------------------------------------------------
// mousePressEvent
//---------------------------------------------------------
void HarmonyCanvas::mousePressEvent(QMouseEvent* event)
{
startMove = imatrix.map(QPointF(event->pos()));
moveElement = 0;
foreach(TextSegment* ts, textList) {
QRectF r = ts->boundingRect().translated(ts->x, ts->y);
ts->select = r.contains(startMove);
if (ts->select)
moveElement = ts;
}
update();
}
//---------------------------------------------------------
// mouseMoveEvent
//---------------------------------------------------------
void HarmonyCanvas::mouseMoveEvent(QMouseEvent* event)
{
if (moveElement == 0)
return;
QPointF p = imatrix.map(QPointF(event->pos()));
QPointF delta = p - startMove;
moveElement->x += delta.x();
moveElement->y += delta.y();
startMove = p;
update();
}
//---------------------------------------------------------
// mouseReleaseEvent
//---------------------------------------------------------
void HarmonyCanvas::mouseReleaseEvent(QMouseEvent*)
{
}
//---------------------------------------------------------
// setChordDescription
//---------------------------------------------------------
void HarmonyCanvas::setChordDescription(ChordDescription* sd, ChordList* sl)
{
chordDescription = sd;
chordList = sl;
foreach(TextSegment* s, textList)
delete s;
textList.clear();
if (chordList) {
int tpc = 14;
double x = 0.0, y = 0.0;
NoteSpellingType rootSpelling, baseSpelling;
NoteCaseType rootCase = NoteCaseType::AUTO;
NoteCaseType baseCase = NoteCaseType::AUTO;
Harmony h(gscore);
h.determineRootBaseSpelling(rootSpelling, rootCase, baseSpelling, baseCase);
render(chordList->renderListRoot, x, y, tpc, rootSpelling, rootCase);
render(chordDescription->renderList, x, y, tpc, baseSpelling, baseCase);
}
moveElement = 0;
dragElement = 0;
update();
}
//---------------------------------------------------------
// dropEvent
//---------------------------------------------------------
void HarmonyCanvas::dropEvent(QDropEvent* event)
{
if (dragElement && dragElement->type() == Element::Type::FSYMBOL) {
FSymbol* sb = static_cast<FSymbol*>(dragElement);
double _spatium = 2.0 * PALETTE_SPATIUM / extraMag;
const TextStyle* st = &gscore->textStyle(TextStyleType::HARMONY);
QFont ff(st->fontPx(_spatium));
ff.setFamily(sb->font().family());
// qDebug("drop %s", dragElement->name());
QString s;
int code = sb->code();
if (code & 0xffff0000) {
s = QChar(QChar::highSurrogate(code));
s += QChar(QChar::lowSurrogate(code));
}
else
s = QChar(code);
QPointF pt = imatrix.map(event->pos());
TextSegment* ts = new TextSegment(s, ff, pt.x(), pt.y());
textList.append(ts);
delete dragElement;
dragElement = 0;
update();
}
}
//---------------------------------------------------------
// dragEnterEvent
//---------------------------------------------------------
void HarmonyCanvas::dragEnterEvent(QDragEnterEvent* event)
{
const QMimeData* data = event->mimeData();
if (data->hasFormat(mimeSymbolFormat)) {
QByteArray a = data->data(mimeSymbolFormat);
XmlReader e(a);
QPointF dragOffset;
Fraction duration;
Element::Type type = Element::readType(e, &dragOffset, &duration);
if (type == Element::Type::FSYMBOL) {
event->acceptProposedAction();
dragElement = Element::create(type, gscore);
dragElement->read(e);
dragElement->layout();
}
}
}
//---------------------------------------------------------
// dragLeaveEvent
//---------------------------------------------------------
void HarmonyCanvas::dragLeaveEvent(QDragLeaveEvent*)
{
delete dragElement;
dragElement = 0;
update();
}
//---------------------------------------------------------
// dragMoveEvent
//---------------------------------------------------------
void HarmonyCanvas::dragMoveEvent(QDragMoveEvent* event)
{
event->acceptProposedAction();
if (dragElement && dragElement->type() == Element::Type::FSYMBOL) {
dragElement->setPos(imatrix.map(event->pos()));
update();
}
}
//---------------------------------------------------------
// deleteAction
//---------------------------------------------------------
void HarmonyCanvas::deleteAction()
{
if (moveElement) {
textList.removeOne(moveElement);
update();
}
}
//---------------------------------------------------------
// updateHarmony
//---------------------------------------------------------
static void updateHarmony(void*, Element* e)
{
if (e->type() == Element::Type::HARMONY)
static_cast<Harmony*>(e)->render();
}
//---------------------------------------------------------
// accept
//---------------------------------------------------------
void ChordStyleEditor::accept()
{
canvas->updateChordDescription();
score->style()->setChordList(chordList);
chordList = 0;
score->scanElements(0, updateHarmony);
QDialog::accept();
}
//---------------------------------------------------------
// updateCurrentChordDescription
//---------------------------------------------------------
void HarmonyCanvas::updateChordDescription()
{
if (!chordDescription)
return;
chordDescription->renderList.clear();
int idx = 0;
double x = 0, y = 0;
foreach(const TextSegment* ts, textList) {
++idx;
if (idx == 1) { // dont save base
x = ts->x + ts->width();
y = ts->y;
continue;
}
if (ts->x != x || ts->y != y) {
RenderAction ra(RenderAction::RenderActionType::MOVE);
ra.movex = ts->x - x;
ra.movey = ts->y - y;
chordDescription->renderList.append(ra);
}
RenderAction ra(RenderAction::RenderActionType::SET);
ra.text = ts->text;
chordDescription->renderList.append(ra);
x = ts->x + ts->width();
y = ts->y;
}
chordDescription->exportOk = true;
}
}