//============================================================================= // 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(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::setPressedPitches(QSet pitches) { _pressedPitches = 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())); 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::heartBeat(QList notes) { QSet pitches; for (const Note* note : notes) { pitches.insert(note->ppitch()); } _piano->setPressedPitches(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(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(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(); } }