MuseScore/mscore/pianotools.cpp
2019-02-08 16:10:12 +02:00

546 lines
19 KiB
C++

//=============================================================================
// MusE Score
// Linux Music Score Editor
//
// Copyright (C) 2011-2016 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 "pianotools.h"
#include "preferences.h"
#include "libmscore/chord.h"
namespace Ms {
static const qreal KEY_WIDTH = 13.0;
static const qreal BKEY_WIDTH = 8.0;
static const qreal KEY_HEIGHT = 40.0;
static const qreal BKEY_HEIGHT = 25.0;
//---------------------------------------------------------
// HPiano
//---------------------------------------------------------
HPiano::HPiano(QWidget* parent)
: QGraphicsView(parent)
{
scaleVal = 1.0;
setLineWidth(0);
setMidLineWidth(0);
setScene(new QGraphicsScene);
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
setResizeAnchor(QGraphicsView::AnchorUnderMouse);
setMouseTracking(true);
setRubberBandSelectionMode(Qt::IntersectsItemBoundingRect);
setDragMode(QGraphicsView::RubberBandDrag);
setScale(2.5);
grabGesture(Qt::PinchGesture); // laptop pad (Mac) and touchscreen
scene()->setSceneRect(0.0, 0.0, KEY_WIDTH * 52, KEY_HEIGHT);
QSizePolicy policy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setSizePolicy(policy);
int margin = 16;
setMaximumSize(QSize((KEY_WIDTH * 52 + margin/2) * scaleVal, 1000));
_firstKey = 21;
_lastKey = 108; // 88 key piano
qreal x = 0.0;
for (int i = _firstKey; i <= _lastKey; ++i) {
PianoKeyItem* k = new PianoKeyItem(this, i);
switch(i % 12) {
case 0:
case 5:
k->setType(i == _lastKey ? 6 : 0);
k->setPos(x, 0);
x += KEY_WIDTH;
break;
case 2:
k->setType(1);
k->setPos(x, 0);
x += KEY_WIDTH;
break;
case 4:
case 11:
k->setType(2);
k->setPos(x, 0);
x += KEY_WIDTH;
break;
case 7:
k->setType(3);
k->setPos(x, 0);
x += KEY_WIDTH;
break;
case 9:
k->setType(i == _firstKey ? 5 : 4);
k->setPos(x, 0);
x += KEY_WIDTH;
break;
case 1:
case 6:
k->setType(7);
k->setPos(x - BKEY_WIDTH * 5/9, 0);
break;
case 3:
case 10:
k->setType(7);
k->setPos(x - BKEY_WIDTH * 4/9, 0);
break;
case 8:
k->setType(7);
k->setPos(x - BKEY_WIDTH / 2.0, 0);
break;
}
keys.append(k);
scene()->addItem(k);
}
}
//---------------------------------------------------------
// setScale
//---------------------------------------------------------
void HPiano::setScale(qreal s)
{
if (s > 16.0)
s = 16.0;
else if (s < .5)
s = .5;
if (s != scaleVal) {
scaleVal = s;
int margin = 16;
QDockWidget* par = static_cast<QDockWidget*>(parent());
if (par) {
if (!par->isFloating())
setMaximumSize(QSize((KEY_WIDTH * 52 + margin/2) * scaleVal, 1000));
else
setMaximumSize(QSize((KEY_WIDTH * 52 + margin/2) * scaleVal, (KEY_HEIGHT + margin) * scaleVal));
}
else
setMaximumSize(QSize((KEY_WIDTH * 52 + margin/2) * scaleVal, (KEY_HEIGHT + margin) * scaleVal));
setMinimumSize(QSize(100 * scaleVal, (KEY_HEIGHT + margin) * scaleVal));
QTransform t;
t.scale(scaleVal, scaleVal);
setTransform(t, false);
}
}
//---------------------------------------------------------
// sizeHint
//---------------------------------------------------------
QSize HPiano::sizeHint() const
{
return QSize(KEY_WIDTH * 52 + 1, KEY_HEIGHT+1);
}
//---------------------------------------------------------
// pressKeys
//---------------------------------------------------------
void HPiano::setPressedPlaybackPitches(QSet<int> pitches)
{
_pressedPlaybackPitches = pitches;
updateAllKeys();
}
//---------------------------------------------------------
// pressPitch
//---------------------------------------------------------
void HPiano::pressPitch(int pitch)
{
_pressedPitches.insert(pitch);
updateAllKeys();
}
//---------------------------------------------------------
// releasePitch
//---------------------------------------------------------
void HPiano::releasePitch(int pitch)
{
_pressedPitches.remove(pitch);
updateAllKeys();
}
//---------------------------------------------------------
// changeSelection
//---------------------------------------------------------
void HPiano::changeSelection(const Selection& selection)
{
for (PianoKeyItem* key : keys) {
key->setHighlighted(false);
key->setSelected(false);
}
for (Note* n : selection.noteList()) {
if (n->epitch() >= _firstKey && n->epitch() <= _lastKey)
keys[n->epitch() - _firstKey]->setSelected(true);
for (Note* other : n->chord()->notes())
if (other->epitch() >= _firstKey && other->epitch() <= _lastKey)
keys[other->epitch() - _firstKey]->setHighlighted(true);
}
for (PianoKeyItem* key : keys)
key->update();
}
// used when currentScore() is NULL; same as above except the for loop
void HPiano::clearSelection()
{
for (PianoKeyItem* key : keys) {
key->setHighlighted(false);
key->setSelected(false);
key->update();
}
}
//---------------------------------------------------------
// updateAllKeys
//---------------------------------------------------------
void HPiano::updateAllKeys()
{
for (PianoKeyItem* key : keys) {
key->setPressed(_pressedPitches.contains(key->pitch())
|| _pressedPlaybackPitches.contains(key->pitch()));
key->update();
}
}
void HPiano::setMaximum(bool top_level) {
int margin = 16;
if (!top_level)
setMaximumSize(QSize((KEY_WIDTH * 52 + margin/2) * scaleVal, 1000));
else
setMaximumSize(QSize((KEY_WIDTH * 52 + margin/2) * scaleVal, (KEY_HEIGHT + margin) * scaleVal));
updateAllKeys();
}
//---------------------------------------------------------
// PianoKeyItem
//---------------------------------------------------------
PianoKeyItem::PianoKeyItem(HPiano* _piano, int p)
: QGraphicsPathItem()
{
piano = _piano;
_pitch = p;
_pressed = false;
_selected = false;
_highlighted = false;
type = -1;
QString pitchNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
QString text = pitchNames[_pitch % 12] + QString::number((_pitch / 12) - 1);
setToolTip(text);
}
//---------------------------------------------------------
// setType
//---------------------------------------------------------
void PianoKeyItem::setType(int val)
{
type = val;
QPainterPath path;
qreal triangle = 1.0;
qreal htriangle = triangle/2;
switch(type) {
case 0:
path.moveTo(0,0);
path.lineTo(0, KEY_HEIGHT-triangle);
path.lineTo(triangle, KEY_HEIGHT);
path.lineTo(KEY_WIDTH-triangle, KEY_HEIGHT);
path.lineTo(KEY_WIDTH, KEY_HEIGHT-triangle);
path.lineTo(KEY_WIDTH, BKEY_HEIGHT);
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 5/9, BKEY_HEIGHT);
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 5/9, 0);
break;
case 1:
path.moveTo(BKEY_WIDTH * 4/9, 0);
path.lineTo(BKEY_WIDTH * 4/9, BKEY_HEIGHT);
path.lineTo(0, BKEY_HEIGHT);
path.lineTo(0, KEY_HEIGHT-triangle);
path.lineTo(triangle, KEY_HEIGHT);
path.lineTo(KEY_WIDTH-triangle, KEY_HEIGHT);
path.lineTo(KEY_WIDTH, KEY_HEIGHT-triangle);
path.lineTo(KEY_WIDTH, BKEY_HEIGHT);
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 4/9, BKEY_HEIGHT);
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 4/9, 0);
break;
case 2:
path.moveTo(BKEY_WIDTH * 5/9, 0);
path.lineTo(BKEY_WIDTH * 5/9, BKEY_HEIGHT);
path.lineTo(0, BKEY_HEIGHT);
path.lineTo(0, KEY_HEIGHT-triangle);
path.lineTo(triangle, KEY_HEIGHT);
path.lineTo(KEY_WIDTH-triangle, KEY_HEIGHT);
path.lineTo(KEY_WIDTH, KEY_HEIGHT-triangle);
path.lineTo(KEY_WIDTH, BKEY_HEIGHT);
path.lineTo(KEY_WIDTH, 0);
break;
case 3:
path.moveTo(BKEY_WIDTH * 4/9, 0);
path.lineTo(BKEY_WIDTH * 4/9, BKEY_HEIGHT);
path.lineTo(0, BKEY_HEIGHT);
path.lineTo(0, KEY_HEIGHT-triangle);
path.lineTo(triangle, KEY_HEIGHT);
path.lineTo(KEY_WIDTH-triangle, KEY_HEIGHT);
path.lineTo(KEY_WIDTH, KEY_HEIGHT-triangle);
path.lineTo(KEY_WIDTH, BKEY_HEIGHT);
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 5/9, BKEY_HEIGHT);
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 5/9, 0);
break;
case 4:
path.moveTo(BKEY_WIDTH * 5/9, 0);
path.lineTo(BKEY_WIDTH * 5/9, BKEY_HEIGHT);
path.lineTo(0, BKEY_HEIGHT);
path.lineTo(0, KEY_HEIGHT-triangle);
path.lineTo(triangle, KEY_HEIGHT);
path.lineTo(KEY_WIDTH-triangle, KEY_HEIGHT);
path.lineTo(KEY_WIDTH, KEY_HEIGHT-triangle);
path.lineTo(KEY_WIDTH, BKEY_HEIGHT);
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 4/9, BKEY_HEIGHT);
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 4/9, 0);
break;
case 5:
path.moveTo(0,0);
path.lineTo(0, KEY_HEIGHT-triangle);
path.lineTo(triangle, KEY_HEIGHT);
path.lineTo(KEY_WIDTH-triangle, KEY_HEIGHT);
path.lineTo(KEY_WIDTH, KEY_HEIGHT-triangle);
path.lineTo(KEY_WIDTH, BKEY_HEIGHT);
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 4/9, BKEY_HEIGHT);
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 4/9, 0);
break;
case 6:
path.moveTo(0,0);
path.lineTo(0, KEY_HEIGHT-triangle);
path.lineTo(triangle, KEY_HEIGHT);
path.lineTo(KEY_WIDTH-triangle, KEY_HEIGHT);
path.lineTo(KEY_WIDTH, KEY_HEIGHT-triangle);
path.lineTo(KEY_WIDTH, 0);
break;
case 7:
path.moveTo(0,0);
path.lineTo(0, BKEY_HEIGHT-htriangle);
path.lineTo(htriangle, BKEY_HEIGHT);
path.lineTo(BKEY_WIDTH-htriangle, BKEY_HEIGHT);
path.lineTo(BKEY_WIDTH, BKEY_HEIGHT-htriangle);
path.lineTo(BKEY_WIDTH, 0);
break;
default:
break;
}
path.closeSubpath();
setPath(path);
}
//---------------------------------------------------------
// mousePressEvent
//---------------------------------------------------------
void PianoKeyItem::mousePressEvent(QGraphicsSceneMouseEvent*)
{
_pressed = true;
update();
bool chord = qApp->keyboardModifiers() & Qt::ShiftModifier;
emit piano->keyPressed(_pitch, chord, 80);
}
//---------------------------------------------------------
// mouseReleaseEvent
//---------------------------------------------------------
void PianoKeyItem::mouseReleaseEvent(QGraphicsSceneMouseEvent*)
{
_pressed = false;
update();
emit piano->keyReleased(_pitch, false, 0);
}
//---------------------------------------------------------
// paint
//---------------------------------------------------------
void PianoKeyItem::paint(QPainter* p, const QStyleOptionGraphicsItem* /*o*/, QWidget*)
{
p->setRenderHint(QPainter::Antialiasing, true);
p->setPen(QPen(Qt::black, .8));
if (_pressed) {
QColor c(preferences.getColor(PREF_UI_PIANO_HIGHLIGHTCOLOR));
c.setAlpha(180);
p->setBrush(c);
}
else if (_selected) {
QColor c(preferences.getColor(PREF_UI_PIANO_HIGHLIGHTCOLOR));
c.setAlpha(100);
p->setBrush(c);
}
else if (_highlighted)
p->setBrush(type >= 7 ? QColor(125, 125, 125) : QColor(200, 200, 200));
else
p->setBrush(type >= 7 ? Qt::black : Qt::white);
p->drawPath(path());
if (_pitch % 12 == 0) {
QFont f("FreeSerif", 6);
p->setFont(f);
QString text = "C" + QString::number((_pitch / 12) - 1);
p->drawText(QRectF(KEY_WIDTH / 2, KEY_HEIGHT - 8, 0, 0),
Qt::AlignCenter | Qt::TextDontClip, text);
}
}
//---------------------------------------------------------
// PianoTools
//---------------------------------------------------------
PianoTools::PianoTools(QWidget* parent)
: QDockWidget(parent)
{
setObjectName("piano");
setAllowedAreas(Qt::DockWidgetAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea));
_piano = new HPiano;
_piano->setFocusPolicy(Qt::ClickFocus);
setWidget(_piano);
QSizePolicy policy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setSizePolicy(policy);
connect(_piano, SIGNAL(keyPressed(int, bool, int)), SIGNAL(keyPressed(int, bool, int)));
connect(_piano, SIGNAL(keyReleased(int, bool, int)), SIGNAL(keyReleased(int, bool, int)));
connect(this, SIGNAL(topLevelChanged(bool)), _piano, SLOT(setMaximum(bool)));
retranslate();
}
//---------------------------------------------------------
// retranslate
//---------------------------------------------------------
void PianoTools::retranslate()
{
setWindowTitle(tr("Piano Keyboard"));
}
//---------------------------------------------------------
// heartBeat
//---------------------------------------------------------
void PianoTools::setPlaybackNotes(QList<const Ms::Note *> notes)
{
QSet<int> pitches;
for (const Note* note : notes) {
pitches.insert(note->ppitch());
}
_piano->setPressedPlaybackPitches(pitches);
}
//---------------------------------------------------------
// changeEvent
//---------------------------------------------------------
void PianoTools::changeEvent(QEvent *event)
{
QDockWidget::changeEvent(event);
if (event->type() == QEvent::LanguageChange)
retranslate();
}
//---------------------------------------------------------
// wheelEvent
//---------------------------------------------------------
void HPiano::wheelEvent(QWheelEvent* event)
{
static int deltaSum = 0;
deltaSum += event->delta();
int step = deltaSum / 120;
deltaSum %= 120;
qreal mag = scaleVal;
if (event->modifiers() & Qt::ControlModifier) {
if (step > 0) {
for (int i = 0; i < step; ++i)
mag *= 1.1;
}
else {
for (int i = 0; i < -step; ++i)
mag /= 1.1;
}
setScale(mag);
}
}
//---------------------------------------------------------
// gestureEvent
// fired on touchscreen gestures as well as Mac touchpad gestures
//---------------------------------------------------------
bool HPiano::event(QEvent* event)
{
if (event->type() == QEvent::Gesture) {
return gestureEvent(static_cast<QGestureEvent*>(event));
}
return QGraphicsView::event(event);
}
//---------------------------------------------------------
// gestureEvent
// fired on touchscreen gestures as well as Mac touchpad gestures
//---------------------------------------------------------
bool HPiano::gestureEvent(QGestureEvent *event)
{
if (QGesture *gesture = event->gesture(Qt::PinchGesture)) {
// Zoom in/out when receiving a pinch gesture
QPinchGesture *pinch = static_cast<QPinchGesture *>(gesture);
static qreal magStart = 1.0;
if (pinch->state() == Qt::GestureStarted) {
magStart = scaleVal;
}
if (pinch->changeFlags() & QPinchGesture::ScaleFactorChanged) {
// On Windows, totalScaleFactor() contains the net magnification.
// On OS X, totalScaleFactor() is 1, and scaleFactor() contains the net magnification.
qreal value = pinch->totalScaleFactor();
if (value == 1) {
value = pinch->scaleFactor();
}
// Qt 5.4 doesn't report pinch->centerPoint() correctly
setScale(magStart*value);
}
}
return true;
}
//---------------------------------------------------------
// changeSelection
//---------------------------------------------------------
void PianoTools::changeSelection(const Selection& selection)
{
_piano->changeSelection(selection);
}
void PianoTools::clearSelection()
{
_piano->clearSelection();
}
}