MuseScore/mscore/pianotools.cpp
Mark McKay 63d2db7b14 fix #273601: Updated UI for pianoroll editor.
Updating main piano roll display to have 12 rows per octave.

Updating piano keyboard to track main piano roll window.

Moving PianoKeyboard to its own file.

Better tracking of playhead during playback.

Drawing beat lines within measure.

Scrollbars now adjusted for stasis when zooming in the pianoview.

Piano ruler now showing tick marks respecting zoom level.

Fixing scroll issue during playback.

Taking direct control of selection.

PianoItem is no longer a Qt object.

Preping  for note group drag.

Can now drag pitch changes.

Adjusting viewport pos when staff set.

Removing CMakeLists.txt

Adding CMakeLists.txt back in.

Cleaning things up for resubmission.

Now displaying drumset names in PianoKeyboard when relevant.

Updating tracking signal in Pianoroll Editor.  Cleanup.

Locator lines no longer Qt objects.  Removing redundant note move on
mouse up.

Now adjusting final event for playback.

Now showing note voice name.  Using Tpc to use note name tha reflects
accidentals.

Better checking for Position when opening Pianoroll window.
Adding note for ctrl-click on rest.

Swithing velocity type will now also adjust to equivilent velocity
value.  Better checking for single note selection.

Pianoroll window.  Adding note for ctrl-click on rest.
equivilent velocity value.  Better checking for single note selection.

Ctrl-click now adds note to selection.

Note up/down now correct direction for drumroll

Fixing calcuation for switching note velocity.

Can now horiz resize piano keyboard.

Setting up new PianoLevels control for setting note event values.

Now drawing subbeat divisions.

Can now cut notes.

User can now select data type to display.

Can now use mouse to adjust levels.

Adding undo to note event changes.  Now smoother transition between
absolute and offset level values.

Smoother update communication between controls.

Adding labels to piano levels.  Removing unneccary blocking of scene()
in pianoview.

Now handles adjusting level with 1px wide mouse sweep.

mouse sweep.
'c8199ac5a'.

fix #273601: Updated UI for pianoroll editor.

Now handling setting null staff.  Adding missing .h files to CMakeLists.txt

Removing undo step to avoid selection problem.

Adding tool tip display.

Keyboard display now adjusting to reflect transposition of instrument.

Now showing part name in Piano Roll editor.

Moving color display options into preferences.

Levels editor now using preferences too.

Fixing incorrent note being highlighted in piano roll editor.  Base color now used for coloring levels panel.

Cleaning code

Investigating cut/pasete actions.

Adding popup menu.

Encapsulating selection into a command.  Creating minimal popup menu.

Can now insert notes of arbitrary duraition to piano roll.

Adding dark and light color schemes to piano roll editor.

Can now set subbeats to non powers of 2.

Fixing bad rebase.

Holding shift will clamp values to mousedown position.

Adding dropdown to modify which white lines are shown behind notes.
2018-10-21 04:27:23 -04:00

546 lines
19 KiB
C++

//=============================================================================
// MusE Score
// Linux Music Score Editor
// $Id:$
//
// 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::setPressedPitches(QSet<int> 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(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<const Ms::Note *> notes)
{
QSet<int> 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<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(Selection selection)
{
_piano->changeSelection(selection);
}
void PianoTools::clearSelection()
{
_piano->clearSelection();
}
}