//============================================================================= // 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 "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" namespace Ms { //--------------------------------------------------------- // event //--------------------------------------------------------- bool ScoreView::event(QEvent* event) { if (event->type() == QEvent::KeyPress && editData.element) { QKeyEvent* ke = static_cast(event); if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) { if (editData.element->isText()) return true; bool rv = true; if (ke->key() == Qt::Key_Tab) { rv = editData.element->nextGrip(editData); updateGrips(); _score->update(); if (rv) return true; } else if (ke->key() == Qt::Key_Backtab) rv = editData.element->prevGrip(editData); updateGrips(); _score->update(); if (rv) return true; } } // else if (event->type() == CloneDrag) { //TODO:drag Element* e = static_cast(event)->element(); // cloneElement(e); // } else if (event->type() == QEvent::Gesture) { return gestureEvent(static_cast(event)); } else if (event->type() == QEvent::MouseButtonPress && qApp->focusWidget() != this) { QMouseEvent* me = static_cast(event); if (me->button() == Qt::LeftButton) this->setFocus(); } 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(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(dy) / PIXELSSTEPSFACTOR; } else if (!stepsScrolled.isNull()) { dx = static_cast(stepsScrolled.x()) * qMax(2, width() / 10) / 120; dy = static_cast(stepsScrolled.y()) * qMax(2, height() / 10) / 120; nReal = static_cast(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(); } //--------------------------------------------------------- // 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(this)); } QWidget::focusInEvent(event); } //--------------------------------------------------------- // focusOutEvent //--------------------------------------------------------- void ScoreView::focusOutEvent(QFocusEvent* event) { if (focusFrame) focusFrame->setWidget(0); QWidget::focusOutEvent(event); } //--------------------------------------------------------- // mouseReleaseEvent //--------------------------------------------------------- void ScoreView::mouseReleaseEvent(QMouseEvent*) { 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: 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: case ViewState::EDIT: case ViewState::NOTE_ENTRY: case ViewState::PLAY: case ViewState::ENTRY_PLAY: case ViewState::FOTO: case ViewState::FOTO_LASSO: break; } } //--------------------------------------------------------- // mousePressEventNormal // handle mouse press event for NORMAL mode //--------------------------------------------------------- void ScoreView::mousePressEventNormal(QMouseEvent* ev) { _score->masterScore()->cmdState().reset(); // DEBUG: should not be necessary 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* e = s->element(staffIdx * VOICES); if (e) { e->score()->select(e, first ? SelectType::SINGLE : SelectType::ADD); first = false; } } } else { if (st == SelectType::ADD && e->selected()) e->score()->deselect(e); else e->score()->select(e, st, -1); } if (e->isNote()) mscore->play(e); if (e) { _score = e->score(); _score->setUpdateAll(); } } else { // special case: chacke if measure is selected int staffIdx; Measure* m = _score->pos2measure(editData.startMove, &staffIdx, 0, 0, 0); if (m && m->staffLines(staffIdx)->canvasBoundingRect().contains(editData.startMove)) { _score->select(m, st, staffIdx); _score->setUpdateAll(); } else _score->deselectAll(); } _score->update(); mscore->endCmd(); } //--------------------------------------------------------- // mousePressEvent //--------------------------------------------------------- void ScoreView::mousePressEvent(QMouseEvent* ev) { 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(); Element* e = elementNear(editData.startMove); qDebug("element %s", e ? e->name() : "--"); switch (state) { case ViewState::NORMAL: if (ev->button() == Qt::RightButton) // context menu? break; editData.element = e; mousePressEventNormal(ev); break; case ViewState::FOTO: { if (ev->buttons() & Qt::RightButton) break; editData.element = _foto; bool gripClicked = false; qreal a = editData.grip[0].width() * 1.0; 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(); gripClicked = true; score()->update(); break; } } if (gripClicked) changeState(ViewState::FOTO_DRAG_EDIT); else if (_foto->canvasBoundingRect().contains(editData.startMove)) changeState(ViewState::FOTO_DRAG_OBJECT); else changeState(ViewState::FOTO_DRAG); } break; case ViewState::NOTE_ENTRY: _score->startCmd(); _score->putNote(editData.startMove, ev->modifiers() & Qt::ShiftModifier, ev->modifiers() & Qt::ControlModifier); _score->endCmd(); if (_score->inputState().cr()) adjustCanvasPosition(_score->inputState().cr(), false); break; case ViewState::EDIT: { if (editData.grips) { qreal a = editData.grip[0].width() * 1.0; bool gripFound = false; 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.element = e; changeState(ViewState::NORMAL); mousePressEventNormal(ev); break; } } else { if (!editData.element->canvasBoundingRect().contains(editData.startMove)) { editData.element = e; changeState(ViewState::NORMAL); mousePressEventNormal(ev); } else { editData.element->mousePress(editData); score()->update(); } } } break; default: qDebug("mousePressEvent in state %d", int(state)); break; } } //--------------------------------------------------------- // mouseMoveEvent //--------------------------------------------------------- void ScoreView::mouseMoveEvent(QMouseEvent* 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 (!editData.element && (me->modifiers() & Qt::ShiftModifier)) changeState(ViewState::LASSO); else if (editData.element && !(me->modifiers())) { if (!drag) return; changeState(ViewState::DRAG_OBJECT); } else 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; default: break; } update(); } //--------------------------------------------------------- // mouseDoubleClickEvent //--------------------------------------------------------- void ScoreView::mouseDoubleClickEvent(QMouseEvent* me) { if (state == ViewState::NORMAL) { QPointF p = toLogical(me->pos()); Element* e = elementNear(p); if (e && e->isEditable()) { startEditMode(e); changeState(ViewState::EDIT); } } } //--------------------------------------------------------- // CmdContext //--------------------------------------------------------- struct CmdContext { Score* s; CmdContext(Score* _s) : s(_s) { s->startCmd(); } ~CmdContext() { s->endCmd(); } }; //--------------------------------------------------------- // keyPressEvent //--------------------------------------------------------- void ScoreView::keyPressEvent(QKeyEvent* ev) { if (state != ViewState::EDIT) return; CmdContext cmdContext(_score); editData.key = ev->key(); editData.modifiers = ev->modifiers(); editData.s = ev->text(); 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(ev)) 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; } } #ifdef Q_OS_WIN // Japenese IME on Windows needs to know when Contrl/Alt/Shift/CapsLock is pressed while in predit if (editData.element->isText()) { Text* text = toText(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 (editData.element->isText()) mscore->textTools()->updateTools(editData); else updateGrips(); return; } } 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 (editData.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: ev->ignore(); return; } 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); updateGrips(); } //--------------------------------------------------------- // keyReleaseEvent //--------------------------------------------------------- void ScoreView::keyReleaseEvent(QKeyEvent* ev) { if (state == ViewState::EDIT) { auto modifiers = Qt::ControlModifier | Qt::ShiftModifier; if (editData.element->isText() && ((ev->modifiers() & modifiers) == 0)) { Text* text = toText(editData.element); text->endHexState(); ev->accept(); update(); } } } //--------------------------------------------------------- // contextPopup //--------------------------------------------------------- void ScoreView::contextMenuEvent(QContextMenuEvent* ev) { if (state == ViewState::FOTO) { fotoContextPopup(ev); return; } QPoint gp = ev->globalPos(); editData.startMove = toLogical(ev->pos()); Element* 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 = _score->pos2measure(editData.startMove, &staffIdx, 0, 0, 0); if (m && m->staffLines(staffIdx)->canvasBoundingRect().contains(editData.startMove)) measurePopup(gp, 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) { std::string p; switch (s) { case ViewState::NORMAL: p = "NORMAL"; break; case ViewState::DRAG: p = "DRAG"; break; case ViewState::DRAG_OBJECT: p = "DRAG_OBJECT"; break; case ViewState::EDIT: p = "EDIT"; break; case ViewState::DRAG_EDIT: p = "DRAG_EDIT"; break; case ViewState::LASSO: p = "LASSO"; break; case ViewState::NOTE_ENTRY: p = "NOTE_ENTRY"; break; case ViewState::PLAY: p = "PLAY"; break; case ViewState::ENTRY_PLAY: p = "ENTRY_PLAY"; break; case ViewState::FOTO: p = "FOTO"; break; case ViewState::FOTO_DRAG: p = "FOTO_DRAG"; break; case ViewState::FOTO_DRAG_EDIT: p = "FOTO_DRAG_EDIT"; break; case ViewState::FOTO_DRAG_OBJECT: p = "FOTO_DRAG_OBJECT"; break; case ViewState::FOTO_LASSO: p = "FOTO_LASSO"; break; } return p.c_str(); } //--------------------------------------------------------- // seqStopped //--------------------------------------------------------- void ScoreView::seqStopped() { changeState(ViewState::NORMAL); } //--------------------------------------------------------- // changeState //--------------------------------------------------------- void ScoreView::changeState(ViewState s) { qDebug("changeState %s -> %s", stateName(state), stateName(s)); // if (state == ViewState::EDIT && s == ViewState::EDIT) { // startEdit(); // return; // } if (s == ViewState::PLAY && !seq) return; if (s == state) return; // // 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::PLAY: seq->stop(); break; default: break; } // // start new state // switch (s) { case ViewState::NORMAL: if (state == ViewState::EDIT) endEdit(); 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: case ViewState::FOTO_LASSO: break; } state = s; mscore->changeState(mscoreState()); } } // namespace Ms