MuseScore/mscore/events.cpp
Dmitri Ovodok 95b196ecbe fix #299923: more correct fix for hairpin handle dragging crash
The previous fix (1c80f51e9f) leads
to a crash if dragging a hairpin itself while being in normal mode.
2020-01-21 11:56:31 +02:00

1194 lines
43 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2017 Werner Schweer & 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
// as published by the Free Software Foundation and appearing in
// the file LICENCE.GPL
//=============================================================================
#include "scoreview.h"
#include "magbox.h"
#include "musescore.h"
#include "seq.h"
#include "texttools.h"
#include "fotomode.h"
#include "tourhandler.h"
#include "scoreaccessibility.h"
#include "libmscore/score.h"
#include "libmscore/keysig.h"
#include "libmscore/segment.h"
#include "libmscore/utils.h"
#include "libmscore/text.h"
#include "libmscore/measure.h"
#include "libmscore/stafflines.h"
#include "libmscore/chord.h"
#include "libmscore/shadownote.h"
#include "libmscore/repeatlist.h"
#include "libmscore/select.h"
#include "libmscore/staff.h"
namespace Ms {
//---------------------------------------------------------
// event
//---------------------------------------------------------
bool ScoreView::event(QEvent* event)
{
switch (event->type()) {
case QEvent::KeyPress: {
QKeyEvent* ke = static_cast<QKeyEvent*>(event);
const int key = ke->key();
if (key != Qt::Key_Tab && key != Qt::Key_Backtab)
break;
if (textEditMode()) {
// block Tab/Backtab in text editing mode
return true;
}
if (hasEditGrips() || editMode()) {
if (ke->key() == Qt::Key_Tab)
editData.element->nextGrip(editData);
else
editData.element->prevGrip(editData);
updateGrips();
_score->update();
return true;
}
}
break;
case QEvent::ShortcutOverride: {
QKeyEvent* ke = static_cast<QKeyEvent*>(event);
switch (ke->key()) {
case Qt::Key_Left:
case Qt::Key_Right:
case Qt::Key_Up:
case Qt::Key_Down: {
if (!hasEditGrips())
break;
const auto m = ke->modifiers();
// KeypadModifier is necessary on MacOS as arrow keys seem to always
// trigger that modifier there. However it would probably be appropriate
// to allow it on other systems too.
const auto allowedModifiers = (editData.curGrip == Grip::NO_GRIP)
? (Qt::KeypadModifier | Qt::ShiftModifier)
: (Qt::KeypadModifier | Qt::ShiftModifier | Qt::ControlModifier);
if ((m & ~allowedModifiers) == 0) {
ke->accept();
return true;
}
}
break;
default:
break;
}
}
break;
case QEvent::Gesture:
return gestureEvent(static_cast<QGestureEvent*>(event));
case QEvent::MouseButtonPress:
if (qApp->focusWidget() != this) {
QMouseEvent* me = static_cast<QMouseEvent*>(event);
if (me->button() == Qt::LeftButton)
this->setFocus();
}
break;
default:
break;
}
return QWidget::event(event);
}
//---------------------------------------------------------
// gestureEvent
// fired on touchscreen gestures as well as Mac touchpad gestures
//---------------------------------------------------------
bool ScoreView::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 = lmag();
}
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();
}
zoom(magStart*value, pinch->centerPoint());
}
}
return true;
}
//---------------------------------------------------------
// wheelEvent
//---------------------------------------------------------
void ScoreView::wheelEvent(QWheelEvent* event)
{
#define PIXELSSTEPSFACTOR 5
QPoint pixelsScrolled = event->pixelDelta();
QPoint stepsScrolled = event->angleDelta();
int dx = 0, dy = 0, n = 0;
qreal nReal = 0.0;
if (!pixelsScrolled.isNull()) {
dx = pixelsScrolled.x();
dy = pixelsScrolled.y();
nReal = static_cast<qreal>(dy) / PIXELSSTEPSFACTOR;
}
else if (!stepsScrolled.isNull()) {
dx = static_cast<qreal>(stepsScrolled.x()) * qMax(2, width() / 10) / 120;
dy = static_cast<qreal>(stepsScrolled.y()) * qMax(2, height() / 10) / 120;
nReal = static_cast<qreal>(stepsScrolled.y()) / 120;
}
n = (int) nReal;
//this functionality seems currently blocked by the context menu
if (event->buttons() & Qt::RightButton) {
bool up = n > 0;
if (!up)
n = -n;
score()->startCmd();
for (int i = 0; i < n; ++i)
score()->upDown(up, UpDownMode::CHROMATIC);
score()->endCmd();
return;
}
if (event->modifiers() & Qt::ControlModifier) { // Windows touch pad pinches also execute this
QApplication::sendPostedEvents(this, 0);
zoomStep(nReal, event->pos());
return;
}
//make shift+scroll go horizontally
if (event->modifiers() & Qt::ShiftModifier && dx == 0) {
dx = dy;
dy = 0;
}
if (dx == 0 && dy == 0)
return;
constraintCanvas(&dx, &dy);
_matrix.setMatrix(_matrix.m11(), _matrix.m12(), _matrix.m13(), _matrix.m21(),
_matrix.m22(), _matrix.m23(), _matrix.dx()+dx, _matrix.dy()+dy, _matrix.m33());
imatrix = _matrix.inverted();
scroll(dx, dy, QRect(0, 0, width(), height()));
emit viewRectChanged();
emit offsetChanged(_matrix.dx(), _matrix.dy());
}
//---------------------------------------------------------
// resizeEvent
//---------------------------------------------------------
void ScoreView::resizeEvent(QResizeEvent* /*ev*/)
{
if (_magIdx != MagIdx::MAG_FREE)
setMag(mscore->getMag(this));
emit sizeChanged();
// The score may need to be repositioned now.
// So figure out how far it needs to move in each direction...
int dx = 0, dy = 0;
constraintCanvas(&dx, &dy);
if (dx == 0 && dy == 0)
return;
// ...and adjust its position accordingly.
_matrix.setMatrix(_matrix.m11(), _matrix.m12(), _matrix.m13(), _matrix.m21(),
_matrix.m22(), _matrix.m23(), _matrix.dx()+dx, _matrix.dy()+dy, _matrix.m33());
imatrix = _matrix.inverted();
scroll(dx, dy, QRect(0, 0, width(), height()));
emit viewRectChanged();
emit offsetChanged(_matrix.dx(), _matrix.dy());
}
//---------------------------------------------------------
// focusInEvent
//---------------------------------------------------------
void ScoreView::focusInEvent(QFocusEvent* event)
{
if (this != mscore->currentScoreView())
mscore->setCurrentScoreView(this);
if (mscore->splitScreen()) {
if (!focusFrame) {
focusFrame = new QFocusFrame;
QPalette p(focusFrame->palette());
p.setColor(QPalette::WindowText, Qt::blue);
focusFrame->setPalette(p);
}
focusFrame->setWidget(static_cast<QWidget*>(this));
}
if (state == ViewState::NORMAL)
updateEditElement();
QWidget::focusInEvent(event);
}
//---------------------------------------------------------
// focusOutEvent
//---------------------------------------------------------
void ScoreView::focusOutEvent(QFocusEvent* event)
{
if (state == ViewState::NORMAL)
setEditElement(nullptr);
if (focusFrame)
focusFrame->setWidget(0);
QWidget::focusOutEvent(event);
}
//---------------------------------------------------------
// startTextEditingOnMouseRelease
//---------------------------------------------------------
bool ScoreView::startTextEditingOnMouseRelease(QMouseEvent* mouseEvent)
{
if (!editData.element || mouseEvent->button() != Qt::LeftButton)
return false;
if (!(editData.element->isEditable() && editData.element->isTextBase()))
return false;
TextBase* textBase = toTextBase(editData.element);
if (!editData.element->canvasBoundingRect().contains(toLogical(mouseEvent->pos()))) {
// mouse up OUTSIDE textbase - clear any priming
textBase->setPrimed(false);
return false;
}
if (!textBase->isPrimed()) {
// mouse up INSIDE textBase - set priming
textBase->setPrimed(true);
return false;
}
// mouse up INSIDE primed textBase - start editing
startEditMode(textBase);
setCursor(QCursor(Qt::IBeamCursor));
textBase->setPrimed(false);
return true;
}
//---------------------------------------------------------
// mouseReleaseEvent
//---------------------------------------------------------
void ScoreView::mouseReleaseEvent(QMouseEvent* mouseEvent)
{
editData.buttons = Qt::NoButton;
if (seq)
seq->stopNoteTimer();
switch (state) {
case ViewState::DRAG:
case ViewState::DRAG_OBJECT:
case ViewState::LASSO:
changeState(ViewState::NORMAL);
break;
case ViewState::DRAG_EDIT:
if (editData.element && editData.element->normalModeEditBehavior() == Element::EditBehavior::Edit) {
changeState(ViewState::NORMAL);
break;
}
changeState(ViewState::EDIT);
break;
case ViewState::FOTO_DRAG:
case ViewState::FOTO_DRAG_EDIT:
case ViewState::FOTO_DRAG_OBJECT:
changeState(ViewState::FOTO);
break;
case ViewState::NORMAL:
if (startTextEditingOnMouseRelease(mouseEvent))
break;
if (modifySelection) {
_score->select(elementToSelect);
modifySelection = false;
elementToSelect = nullptr;
_score->update();
mscore->endCmd();
}
break;
case ViewState::EDIT:
case ViewState::NOTE_ENTRY:
case ViewState::PLAY:
case ViewState::ENTRY_PLAY:
case ViewState::FOTO:
break;
case ViewState::FOTO_LASSO:
changeState(ViewState::FOTO);
break;
}
}
//---------------------------------------------------------
// mousePressEventNormal
// handle mouse press event for NORMAL mode
//---------------------------------------------------------
void ScoreView::mousePressEventNormal(QMouseEvent* ev)
{
_score->masterScore()->cmdState().reset(); // DEBUG: should not be necessary
modifySelection = false;
elementToSelect = nullptr;
Qt::KeyboardModifiers keyState = ev->modifiers();
SelectType st = SelectType::SINGLE;
if (keyState == Qt::NoModifier)
st = SelectType::SINGLE;
else if (keyState & Qt::ShiftModifier)
st = SelectType::RANGE;
else if (keyState & Qt::ControlModifier)
st = SelectType::ADD;
Element* e = editData.element;
if (e) {
if (keyState == (Qt::ShiftModifier | Qt::ControlModifier)) {
cloneElement(e);
return;
}
if (e->isKeySig() && (keyState != Qt::ControlModifier) && st == SelectType::SINGLE) {
// special case: select for all staves
Segment* s = toKeySig(e)->segment();
bool first = true;
for (int staffIdx = 0; staffIdx < _score->nstaves(); ++staffIdx) {
Element* ee = s->element(staffIdx * VOICES);
if (ee) {
ee->score()->select(ee, first ? SelectType::SINGLE : SelectType::ADD);
first = false;
}
}
}
else {
if (st == SelectType::ADD) {
// e is the top element in stacking order,
// but we want to replace it with "first non-measure element after a selected element"
// (if such an element exists)
QList<Element*> ll = elementsNear(editData.startMove);
bool found = false;
for (Element* ee : ll) {
if (found) {
e = ee;
if (!e->isMeasure())
break;
}
else if (ee->selected()) {
found = true;
ee->score()->deselect(ee);
e = nullptr;
}
}
}
if (e) {
if (!e->selected())
e->score()->select(e, st, -1);
else if (st != SelectType::ADD) {
modifySelection = true;
elementToSelect = e;
}
}
}
if (e && e->isNote()) {
e->score()->updateCapo();
mscore->play(e);
}
if (e) {
_score = e->score();
_score->setUpdateAll();
}
}
else {
// special case: check if measure is selected
int staffIdx;
Measure* m = _score->pos2measure(editData.startMove, &staffIdx, 0, 0, 0);
if (m) {
QRectF r = m->staffLines(staffIdx)->canvasBoundingRect();
if (_score->staff(staffIdx)->lines(m->tick()) == 1) {
r.setHeight(m->spatium() * 2);
r.translate(0.0, -m->spatium());
}
if (r.contains(editData.startMove)) {
//TourHandler::startTour("select-tour");
_score->select(m, st, staffIdx);
_score->setUpdateAll();
}
else if (st != SelectType::ADD)
modifySelection = true;
}
else if (st != SelectType::ADD)
modifySelection = true;
}
_score->update();
mscore->endCmd();
}
//---------------------------------------------------------
// mousePressEvent
//---------------------------------------------------------
void ScoreView::mousePressEvent(QMouseEvent* ev)
{
if (tripleClickPending) {
if (textEditMode()) {
TextBase* textBase = toTextBase(editData.element);
textBase->multiClickSelect(editData, MultiClick::Triple);
mscore->textTools()->updateTools(editData);
textBase->endHexState(editData);
update();
return;
}
}
editData.startMovePixel = ev->pos();
editData.startMove = toLogical(ev->pos());
editData.lastPos = editData.startMove;
editData.pos = editData.startMove;
editData.buttons = ev->buttons();
editData.modifiers = qApp->keyboardModifiers();
bool gripFound = false;
if (hasEditGrips() && ev->button() == Qt::LeftButton) {
switch (state) {
case ViewState::NORMAL:
case ViewState::EDIT:
case ViewState::FOTO: {
const qreal a = editData.grip[0].width() * 0.5;
for (int i = 0; i < editData.grips; ++i) {
if (editData.grip[i].adjusted(-a, -a, a, a).contains(editData.startMove)) {
editData.curGrip = Grip(i);
updateGrips();
score()->update();
gripFound = true;
break;
}
}
if (!gripFound)
editData.curGrip = Grip::NO_GRIP;
}
break;
default:
break;
}
}
switch (state) {
case ViewState::NORMAL: {
if (gripFound)
break;
if (ev->button() == Qt::RightButton) // context menu?
break;
if (editData.element
&& editData.element->isEditable()
&& editData.element->isTextBase()
&& !editData.element->canvasBoundingRect().contains(toLogical(ev->pos()))) {
// mouse down OUTSIDE of textBase - clear priming -
toTextBase(editData.element)->setPrimed(false);
}
setEditElement(elementNear(editData.startMove));
mousePressEventNormal(ev);
}
break;
case ViewState::FOTO: {
if (ev->buttons() & Qt::RightButton)
break;
setEditElement(_foto);
if (gripFound)
changeState(ViewState::FOTO_DRAG_EDIT);
else if (_foto->canvasBoundingRect().contains(editData.startMove))
changeState(ViewState::FOTO_DRAG_OBJECT);
else if (ev->modifiers() & Qt::ShiftModifier)
changeState(ViewState::FOTO_LASSO);
else
changeState(ViewState::FOTO_DRAG);
}
break;
case ViewState::NOTE_ENTRY: {
_score->startCmd();
bool restMode = _score->inputState().rest();
if (ev->button() == Qt::RightButton)
_score->inputState().setRest(!restMode);
_score->putNote(editData.startMove, ev->modifiers() & Qt::ShiftModifier, ev->modifiers() & Qt::ControlModifier);
if (ev->button() == Qt::RightButton)
_score->inputState().setRest(restMode);
_score->endCmd();
if (_score->inputState().cr())
adjustCanvasPosition(_score->inputState().cr(), false);
shadowNote->setVisible(false);
}
break;
case ViewState::EDIT: {
if (gripFound)
break;
if (!editData.element->canvasBoundingRect().contains(editData.startMove)) {
changeState(ViewState::NORMAL);
// changeState may trigger layout and destroy some elements
// so we should search elementNear after changeState.
setEditElement(elementNear(editData.startMove));
mousePressEventNormal(ev);
}
else {
editData.element->mousePress(editData);
score()->update();
if (editData.element->isTextBase() && mscore->textTools())
mscore->textTools()->updateTools(editData);
}
}
break;
case ViewState::PLAY: {
Element* e = elementNear(editData.startMove);
if (seq && e && (e->isNote() || e->isRest())) {
if (e->isNote())
e = e->parent();
ChordRest* cr = toChordRest(e);
seq->seek(seq->score()->repeatList().tick2utick(cr->tick().ticks()));
}
}
break;
default:
qDebug("mousePressEvent in state %d", int(state));
break;
}
}
//---------------------------------------------------------
// adjustCursor
//---------------------------------------------------------
void ScoreView::adjustCursorForTextEditing(QMouseEvent* mouseEvent)
{
if (!editData.element)
return;
if (!(state == ViewState::EDIT || state == ViewState::DRAG_EDIT))
return;
if (!editData.element->isTextBase())
return;
if (editData.element->canvasBoundingRect().contains(toLogical(mouseEvent->pos())))
setCursor(QCursor(Qt::IBeamCursor));
else
setCursor(QCursor(Qt::ArrowCursor));
}
//---------------------------------------------------------
// mouseMoveEvent
//---------------------------------------------------------
void ScoreView::mouseMoveEvent(QMouseEvent* me)
{
modifySelection = false;
adjustCursorForTextEditing(me);
if (state != ViewState::NOTE_ENTRY && editData.buttons == Qt::NoButton)
return;
// start some drag operations after a minimum of movement:
bool drag = (me->pos() - editData.startMovePixel).manhattanLength() > 4;
switch (state) {
case ViewState::NORMAL:
if (!drag)
return;
if (!editData.element && (me->modifiers() & Qt::ShiftModifier)) {
changeState(ViewState::LASSO);
break;
}
if (editData.element) {
if (editData.element->normalModeEditBehavior() == Element::EditBehavior::Edit && editData.curGrip != Grip::NO_GRIP) {
score()->startCmd();
editData.element->startEditDrag(editData);
changeState(ViewState::DRAG_EDIT);
break;
}
if (editData.element->isMovable()) {
if (editData.element->normalModeEditBehavior() == Element::EditBehavior::Edit)
endEdit();
changeState(ViewState::DRAG_OBJECT);
break;
}
}
changeState(ViewState::DRAG);
break;
case ViewState::NOTE_ENTRY: {
QPointF p = toLogical(me->pos());
QRectF r(shadowNote->canvasBoundingRect());
setShadowNote(p);
r |= shadowNote->canvasBoundingRect();
update(toPhysical(r).adjusted(-2, -2, 2, 2));
}
break;
case ViewState::DRAG:
case ViewState::FOTO_DRAG:
dragScoreView(me);
break;
case ViewState::DRAG_OBJECT:
case ViewState::FOTO_DRAG_OBJECT:
doDragElement(me);
break;
case ViewState::DRAG_EDIT:
case ViewState::FOTO_DRAG_EDIT:
doDragEdit(me);
break;
case ViewState::LASSO:
doDragLasso(me);
break;
case ViewState::EDIT:
if (!drag)
return;
score()->startCmd();
editData.element->startEditDrag(editData);
changeState(ViewState::DRAG_EDIT);
break;
case ViewState::PLAY:
if (drag)
dragScoreView(me);
break;
case ViewState::FOTO_LASSO:
doDragFoto(me);
break;
default:
break;
}
update();
}
//---------------------------------------------------------
// tripleClickTimeOut
//---------------------------------------------------------
void ScoreView::tripleClickTimeOut()
{
tripleClickPending = false;
}
//---------------------------------------------------------
// mouseDoubleClickEvent
//---------------------------------------------------------
void ScoreView::mouseDoubleClickEvent(QMouseEvent* mouseEvent)
{
QTimer::singleShot(QApplication::doubleClickInterval(), this, SLOT(tripleClickTimeOut()));
tripleClickPending = true;
if (textEditMode()) {
// double click on a textBase element that is being edited - select word
TextBase* textBase = toTextBase(editData.element);
textBase->multiClickSelect(editData, MultiClick::Double);
mscore->textTools()->updateTools(editData);
textBase->endHexState(editData);
update();
return;
}
if (state != ViewState::NORMAL)
return;
Element* clickedElement = elementNear(toLogical(mouseEvent->pos()));
if (!(clickedElement && clickedElement->isEditable()))
return;
startEditMode(clickedElement);
if (clickedElement->isTextBase()) {
setCursor(QCursor(Qt::IBeamCursor));
toTextBase(clickedElement)->setPrimed(false);
}
}
//---------------------------------------------------------
// ScoreViewCmdContext
//---------------------------------------------------------
class ScoreViewCmdContext {
Score* s;
ScoreView* view;
bool _updateGrips = false;
public:
ScoreViewCmdContext(ScoreView* v, bool updateGrips)
: s(v->score()), view(v), _updateGrips(updateGrips)
{
s->startCmd();
}
~ScoreViewCmdContext()
{
s->endCmd();
if (_updateGrips)
view->updateGrips();
}
};
//---------------------------------------------------------
// keyPressEvent
//---------------------------------------------------------
void ScoreView::keyPressEvent(QKeyEvent* ev)
{
editData.key = ev->key();
editData.modifiers = ev->modifiers();
editData.s = ev->text();
if (state != ViewState::EDIT) {
const bool shiftModifier = ev->modifiers() & Qt::ShiftModifier;
if (hasEditGrips() && !(shiftModifier && ev->key() == Qt::Key_Backtab)) {
switch (ev->key()) {
case Qt::Key_Left:
case Qt::Key_Right:
case Qt::Key_Up:
case Qt::Key_Down:
// Move focus to default grip if arrow keys are pressed and no grip is focused
if (editData.curGrip == Grip::NO_GRIP)
editData.curGrip = editData.element->defaultGrip();
break;
default:
break;
}
ScoreViewCmdContext ctx(this, /* updateGrips */ true);
if (!editData.element->edit(editData))
handleArrowKeyPress(ev);
}
return;
}
if (MScore::debugMode)
qDebug("keyPressEvent key 0x%02x(%c) mod 0x%04x <%s> nativeKey 0x%02x scancode %d",
editData.key, editData.key, int(editData.modifiers), qPrintable(editData.s), ev->nativeVirtualKey(), ev->nativeScanCode());
if (editData.element->isLyrics()) {
if (editKeyLyrics())
return;
}
else if (editData.element->isHarmony()) {
if (editData.key == Qt::Key_Space && !(editData.modifiers & CONTROL_MODIFIER)) {
harmonyBeatsTab(true, editData.modifiers & Qt::ShiftModifier);
return;
}
}
else if (editData.element->isFiguredBass()) {
if (editData.key == Qt::Key_Space && !(editData.modifiers & CONTROL_MODIFIER)) {
figuredBassTab(false, editData.modifiers & Qt::ShiftModifier);
return;
}
}
else if (editData.element->isSticking()) {
if (editKeySticking())
return;
}
ScoreViewCmdContext cc(this, hasEditGrips());
const bool textEdit = textEditMode();
#ifdef Q_OS_WIN // Japenese IME on Windows needs to know when Ctrl/Alt/Shift/CapsLock is pressed while in predit
if (textEdit) {
TextBase* text = toTextBase(editData.element);
if (text->cursor(editData)->format()->preedit() && QGuiApplication::inputMethod()->locale().script() == QLocale::JapaneseScript &&
((editData.key == Qt::Key_Control || (editData.modifiers & Qt::ControlModifier)) ||
(editData.key == Qt::Key_Alt || (editData.modifiers & Qt::AltModifier)) ||
(editData.key == Qt::Key_Shift || (editData.modifiers & Qt::ShiftModifier)) ||
(editData.key == Qt::Key_CapsLock))) {
return; // musescore will ignore this key event so that the IME can handle it
}
}
#endif
if (!((editData.modifiers & Qt::ShiftModifier) && (editData.key == Qt::Key_Backtab))) {
if (editData.element->edit(editData)) {
if (state != ViewState::EDIT) {
// textTab or other function may have terminated edit mode
mscore->endCmd();
return;
}
if (textEdit)
mscore->textTools()->updateTools(editData);
return;
}
}
const bool handled = handleArrowKeyPress(ev);
if (!handled)
ev->ignore();
}
//---------------------------------------------------------
// handleArrowKeysPress
//---------------------------------------------------------
bool ScoreView::handleArrowKeyPress(const QKeyEvent* ev)
{
QPointF delta;
qreal _spatium = editData.element->spatium();
qreal xval, yval;
if (editData.element->isBeam()) {
xval = 0.25 * _spatium;
if (editData.modifiers & Qt::ControlModifier)
xval = _spatium;
else if (editData.modifiers & Qt::AltModifier)
xval = 4 * _spatium;
}
else {
xval = MScore::nudgeStep * _spatium;
if (editData.modifiers & Qt::ControlModifier)
xval = MScore::nudgeStep10 * _spatium;
else if (editData.modifiers & Qt::AltModifier)
xval = MScore::nudgeStep50 * _spatium;
}
yval = xval;
if (mscore->vRaster()) {
qreal vRaster = _spatium / MScore::vRaster();
if (yval < vRaster)
yval = vRaster;
}
if (mscore->hRaster()) {
qreal hRaster = _spatium / MScore::hRaster();
if (xval < hRaster)
xval = hRaster;
}
// TODO: if raster, then xval/yval should be multiple of raster
switch (ev->key()) {
case Qt::Key_Left:
delta = QPointF(-xval, 0);
break;
case Qt::Key_Right:
delta = QPointF(xval, 0);
break;
case Qt::Key_Up:
delta = QPointF(0, -yval);
break;
case Qt::Key_Down:
delta = QPointF(0, yval);
break;
default:
return false;
}
editData.delta = delta;
editData.hRaster = mscore->hRaster();
editData.vRaster = mscore->vRaster();
if (editData.curGrip != Grip::NO_GRIP && int(editData.curGrip) < editData.grips)
editData.pos = editData.grip[int(editData.curGrip)].center() + delta;
editData.element->startEditDrag(editData);
editData.element->editDrag(editData);
editData.element->endEditDrag(editData);
return true;
}
//---------------------------------------------------------
// keyReleaseEvent
//---------------------------------------------------------
void ScoreView::keyReleaseEvent(QKeyEvent* ev)
{
if (textEditMode()) {
auto modifiers = Qt::ControlModifier | Qt::ShiftModifier;
if ((ev->modifiers() & modifiers) == 0) {
TextBase* text = toTextBase(editData.element);
text->endHexState(editData);
ev->accept();
update();
}
}
}
//---------------------------------------------------------
// contextPopup
//---------------------------------------------------------
void ScoreView::contextMenuEvent(QContextMenuEvent* ev)
{
if (state == ViewState::NOTE_ENTRY) {
// Do not show context menu in note input mode.
return;
}
if (state == ViewState::FOTO) {
fotoContextPopup(ev);
return;
}
QPoint gp = ev->globalPos();
editData.startMove = toLogical(ev->pos());
editData.buttons = Qt::NoButton;
Element* e = nullptr;
if (ev->reason() == QContextMenuEvent::Keyboard)
e = score()->selection().element();
else
e = elementNear(editData.startMove);
if (e) {
if (!e->selected()) {
// bool control = (ev->modifiers() & Qt::ControlModifier) ? true : false;
// _score->select(e, control ? SelectType::ADD : SelectType::SINGLE, 0);
// editData.element = e;
// select(ev);
}
if (seq)
seq->stopNotes(); // stop now because we dont get a mouseRelease event
objectPopup(gp, e);
}
else {
int staffIdx;
Measure* m = nullptr;
if (ev->reason() == QContextMenuEvent::Keyboard) {
// find measure based on selection
m = score()->selection().findMeasure();
}
else {
// find nearest measure based on mouse pointer location
m = _score->pos2measure(editData.startMove, &staffIdx, 0, 0, 0);
// but only use it if mouse pointer is within the staff
if (m && !m->staffLines(staffIdx)->canvasBoundingRect().contains(editData.startMove))
m = nullptr;
}
if (m)
measurePopup(ev, m);
else {
QMenu* popup = new QMenu();
popup->addAction(getAction("edit-style"));
popup->addAction(getAction("page-settings"));
popup->addAction(getAction("load-style"));
_score->update();
popup->popup(gp);
}
}
ev->accept();
}
//---------------------------------------------------------
// escapeCmd
//---------------------------------------------------------
void ScoreView::escapeCmd()
{
switch (state) {
case ViewState::EDIT:
changeState(ViewState::NORMAL);
break;
case ViewState::FOTO:
case ViewState::NOTE_ENTRY:
case ViewState::PLAY:
changeState(ViewState::NORMAL);
break;
case ViewState::NORMAL:
_score->deselectAll();
update();
break;
default:
break;
}
}
//---------------------------------------------------------
// stateName
//---------------------------------------------------------
static const char* stateName(ViewState s)
{
switch (s) {
case ViewState::NORMAL: return "NORMAL";
case ViewState::DRAG: return "DRAG";
case ViewState::DRAG_OBJECT: return "DRAG_OBJECT";
case ViewState::EDIT: return "EDIT";
case ViewState::DRAG_EDIT: return "DRAG_EDIT";
case ViewState::LASSO: return "LASSO";
case ViewState::NOTE_ENTRY: return "NOTE_ENTRY";
case ViewState::PLAY: return "PLAY";
case ViewState::ENTRY_PLAY: return "ENTRY_PLAY";
case ViewState::FOTO: return "FOTO";
case ViewState::FOTO_DRAG: return "FOTO_DRAG";
case ViewState::FOTO_DRAG_EDIT: return "FOTO_DRAG_EDIT";
case ViewState::FOTO_DRAG_OBJECT: return "FOTO_DRAG_OBJECT";
case ViewState::FOTO_LASSO: return "FOTO_LASSO";
}
return "";
}
//---------------------------------------------------------
// seqStopped
//---------------------------------------------------------
void ScoreView::seqStopped()
{
changeState(ViewState::NORMAL);
}
//---------------------------------------------------------
// changeState
//---------------------------------------------------------
void ScoreView::changeState(ViewState s)
{
// if (state == ViewState::EDIT && s == ViewState::EDIT) {
// startEdit();
// return;
// }
if (s == ViewState::PLAY && !seq)
return;
if (s == state)
return;
qDebug("changeState %s -> %s", stateName(state), stateName(s));
//
// end current state
//
switch (state) {
case ViewState::NOTE_ENTRY:
endNoteEntry();
break;
case ViewState::DRAG:
break;
case ViewState::FOTO_DRAG_OBJECT:
for (Element* e : _score->selection().elements()) {
e->endDrag(editData);
e->triggerLayout();
}
setDropTarget(0); // this also resets dropAnchor
_score->endCmd();
break;
case ViewState::DRAG_OBJECT:
endDrag();
break;
case ViewState::FOTO_DRAG_EDIT:
case ViewState::DRAG_EDIT:
endDragEdit();
break;
case ViewState::FOTO:
if (s != ViewState::FOTO_DRAG && s != ViewState::FOTO_DRAG_EDIT && s != ViewState::FOTO_DRAG_OBJECT)
stopFotomode();
break;
case ViewState::LASSO:
endLasso();
break;
case ViewState::FOTO_LASSO:
endFotoDrag();
break;
case ViewState::PLAY:
seq->stop();
break;
case ViewState::EDIT:
setMouseTracking(false);
break;
default:
break;
}
//
// start new state
//
switch (s) {
case ViewState::NORMAL:
if (state == ViewState::EDIT) {
_blockShowEdit = true; // otherwise may jump on clicking outside the text element being edited
endEdit();
editData.element = nullptr; // editData.element will be determined by selection state in normal mode
_blockShowEdit = false;
}
setCursor(QCursor(Qt::ArrowCursor));
break;
case ViewState::DRAG:
setCursor(QCursor(Qt::SizeAllCursor));
break;
case ViewState::FOTO_DRAG_OBJECT:
_score->select(_foto);
// fall through
case ViewState::DRAG_OBJECT:
setCursor(QCursor(Qt::ArrowCursor));
startDrag();
break;
case ViewState::FOTO_DRAG:
setCursor(QCursor(Qt::SizeAllCursor));
break;
case ViewState::NOTE_ENTRY:
setCursor(QCursor(Qt::ArrowCursor));
startNoteEntry();
break;
case ViewState::DRAG_EDIT:
case ViewState::FOTO_DRAG_EDIT:
setCursor(QCursor(Qt::ArrowCursor));
break;
case ViewState::FOTO:
setCursor(QCursor(Qt::ArrowCursor));
if (state != ViewState::FOTO_DRAG && state != ViewState::FOTO_DRAG_EDIT && state != ViewState::FOTO_DRAG_OBJECT)
startFotomode();
if (state == ViewState::FOTO_DRAG_OBJECT)
startEdit();
break;
case ViewState::EDIT:
if (state != ViewState::DRAG_EDIT)
startEdit();
break;
case ViewState::LASSO:
break;
case ViewState::PLAY:
seq->start();
break;
case ViewState::ENTRY_PLAY:
break;
case ViewState::FOTO_LASSO:
startFotoDrag();
break;
}
state = s;
mscore->changeState(mscoreState());
if (mscoreState() & STATE_ALLTEXTUAL_EDIT)
setMouseTracking(true);
}
//---------------------------------------------------------
// inputMethodEvent
//---------------------------------------------------------
void ScoreView::inputMethodEvent(QInputMethodEvent* event)
{
if (textEditMode())
toTextBase(editData.element)->inputTransition(editData, event);
}
} // namespace Ms