MuseScore/src/notation/internal/notationinteraction.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

2964 lines
84 KiB
C++
Raw Normal View History

/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA 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 3 as
* published by the Free Software Foundation.
*
* 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, see <https://www.gnu.org/licenses/>.
*/
2020-06-16 10:33:18 +02:00
#include "notationinteraction.h"
#include "log.h"
#include <memory>
#include <QRectF>
2020-06-16 10:48:13 +02:00
#include <QPainter>
2020-12-05 16:59:48 +01:00
#include <QClipboard>
#include <QApplication>
2020-06-16 10:33:18 +02:00
#include "ptrutils.h"
2020-06-16 10:33:18 +02:00
#include "libmscore/score.h"
#include "libmscore/page.h"
#include "libmscore/shadownote.h"
#include "libmscore/staff.h"
#include "libmscore/measure.h"
#include "libmscore/part.h"
#include "libmscore/drumset.h"
#include "libmscore/rest.h"
#include "libmscore/slur.h"
#include "libmscore/system.h"
#include "libmscore/chord.h"
#include "libmscore/elementgroup.h"
#include "libmscore/textframe.h"
2021-02-10 14:24:56 +01:00
#include "libmscore/figuredbass.h"
#include "libmscore/stafflines.h"
#include "libmscore/icon.h"
2020-07-09 14:31:34 +02:00
#include "libmscore/undo.h"
#include "libmscore/navigate.h"
#include "libmscore/keysig.h"
2020-12-04 14:21:54 +01:00
#include "libmscore/instrchange.h"
2021-05-25 12:37:51 +02:00
#include "libmscore/lasso.h"
2020-06-16 10:33:18 +02:00
2020-08-05 10:37:49 +02:00
#include "masternotation.h"
2020-06-16 10:33:18 +02:00
#include "scorecallbacks.h"
#include "notationnoteinput.h"
#include "notationselection.h"
2020-06-16 10:33:18 +02:00
2020-12-04 14:21:54 +01:00
#include "instrumentsconverter.h"
2021-06-29 23:42:51 +02:00
#include "draw/pen.h"
2020-08-27 13:20:03 +02:00
using namespace mu::notation;
2020-06-16 10:33:18 +02:00
2020-10-23 19:30:10 +02:00
NotationInteraction::NotationInteraction(Notation* notation, INotationUndoStackPtr undoStack)
: m_notation(notation), m_undoStack(undoStack), m_gripEditData(&m_scoreCallbacks),
m_lasso(new Ms::Lasso(notation->score()))
2020-06-16 10:33:18 +02:00
{
m_noteInput = std::make_shared<NotationNoteInput>(notation, this, m_undoStack);
m_selection = std::make_shared<NotationSelection>(notation);
2020-06-16 10:33:18 +02:00
m_noteInput->stateChanged().onNotify(this, [this]() {
if (!m_noteInput->isNoteInputMode()) {
hideShadowNote();
}
});
m_dragData.ed = Ms::EditData(&m_scoreCallbacks);
m_dropData.ed = Ms::EditData(&m_scoreCallbacks);
2020-06-16 10:33:18 +02:00
}
NotationInteraction::~NotationInteraction()
{
delete m_shadowNote;
}
void NotationInteraction::init()
{
2020-12-15 14:56:47 +01:00
m_shadowNote = new Ms::ShadowNote(score());
m_shadowNote->setVisible(false);
}
2020-06-16 10:33:18 +02:00
Ms::Score* NotationInteraction::score() const
{
2020-08-05 12:20:48 +02:00
return m_notation->score();
2020-06-16 10:33:18 +02:00
}
void NotationInteraction::startEdit()
{
m_notifyAboutDropChanged = false;
m_undoStack->prepareChanges();
}
void NotationInteraction::apply()
{
m_undoStack->commitChanges();
if (m_notifyAboutDropChanged) {
notifyAboutDropChanged();
}
}
void NotationInteraction::notifyAboutDragChanged()
{
m_dragChanged.notify();
}
void NotationInteraction::notifyAboutDropChanged()
{
m_dropChanged.notify();
}
void NotationInteraction::notifyAboutNotationChanged()
{
m_notation->notifyAboutNotationChanged();
}
void NotationInteraction::notifyAboutTextEditingStarted()
{
m_textEditingStarted.notify();
}
void NotationInteraction::notifyAboutTextEditingChanged()
{
m_textEditingChanged.notify();
}
void NotationInteraction::notifyAboutSelectionChanged()
{
m_selectionChanged.notify();
}
void NotationInteraction::paint(mu::draw::Painter* painter)
2020-06-16 10:33:18 +02:00
{
m_shadowNote->draw(painter);
2020-06-16 10:48:13 +02:00
drawAnchorLines(painter);
drawTextEditMode(painter);
drawSelectionRange(painter);
2021-01-28 12:27:36 +01:00
drawGripPoints(painter);
2021-05-25 12:37:51 +02:00
if (!m_lasso->bbox().isEmpty()) {
m_lasso->draw(painter);
}
2020-06-16 10:33:18 +02:00
}
INotationNoteInputPtr NotationInteraction::noteInput() const
2020-06-16 10:33:18 +02:00
{
return m_noteInput;
2020-06-16 10:33:18 +02:00
}
2021-06-07 19:25:41 +02:00
void NotationInteraction::showShadowNote(const PointF& pos)
2020-06-16 10:33:18 +02:00
{
const Ms::InputState& inputState = score()->inputState();
Ms::Position position;
if (!score()->getPosition(&position, pos, inputState.voice())) {
2020-06-16 10:33:18 +02:00
m_shadowNote->setVisible(false);
return;
}
Staff* staff = score()->staff(position.staffIdx);
const Ms::Instrument* instr = staff->part()->instrument();
Ms::Segment* segment = position.segment;
qreal segmentSkylineTopY = 0;
qreal segmentSkylineBottomY = 0;
Ms::Segment* shadowNoteActualSegment = position.segment->prev1enabled();
if (shadowNoteActualSegment) {
segment = shadowNoteActualSegment;
segmentSkylineTopY = shadowNoteActualSegment->elementsTopOffsetFromSkyline(position.staffIdx);
segmentSkylineBottomY = shadowNoteActualSegment->elementsBottomOffsetFromSkyline(position.staffIdx);
}
Fraction tick = segment->tick();
qreal mag = staff->staffMag(tick);
2020-06-16 10:33:18 +02:00
// in any empty measure, pos will be right next to barline
// so pad this by barNoteDistance
qreal relX = position.pos.x() - position.segment->measure()->canvasPos().x();
position.pos.rx() -= qMin(relX - score()->styleP(Ms::Sid::barNoteDistance) * mag, 0.0);
2020-06-16 10:33:18 +02:00
2020-12-15 14:56:47 +01:00
Ms::NoteHead::Group noteheadGroup = Ms::NoteHead::Group::HEAD_NORMAL;
Ms::NoteHead::Type noteHead = inputState.duration().headType();
int line = position.line;
2020-06-16 10:33:18 +02:00
if (instr->useDrumset()) {
2020-12-15 14:56:47 +01:00
const Ms::Drumset* ds = instr->drumset();
int pitch = inputState.drumNote();
2020-06-16 10:33:18 +02:00
if (pitch >= 0 && ds->isValid(pitch)) {
line = ds->line(pitch);
2020-06-16 10:33:18 +02:00
noteheadGroup = ds->noteHead(pitch);
}
}
int voice = 0;
if (inputState.drumNote() != -1 && inputState.drumset() && inputState.drumset()->isValid(inputState.drumNote())) {
voice = inputState.drumset()->voice(inputState.drumNote());
2020-06-16 10:33:18 +02:00
} else {
voice = inputState.voice();
2020-06-16 10:33:18 +02:00
}
m_shadowNote->setVisible(true);
m_shadowNote->setMag(mag);
m_shadowNote->setTick(tick);
m_shadowNote->setStaffIdx(position.staffIdx);
m_shadowNote->setVoice(voice);
m_shadowNote->setLineIndex(line);
2020-06-16 10:33:18 +02:00
Ms::SymId symNotehead;
Ms::TDuration duration(inputState.duration());
2020-12-01 15:19:42 +01:00
if (inputState.rest()) {
int yo = 0;
Ms::Rest rest(Ms::gscore, duration.type());
rest.setTicks(duration.fraction());
symNotehead = rest.getSymbol(inputState.duration().type(), 0, staff->lines(position.segment->tick()), &yo);
m_shadowNote->setState(symNotehead, duration, true, segmentSkylineTopY, segmentSkylineBottomY);
2020-06-16 10:33:18 +02:00
} else {
2020-12-15 14:56:47 +01:00
if (Ms::NoteHead::Group::HEAD_CUSTOM == noteheadGroup) {
symNotehead = instr->drumset()->noteHeads(inputState.drumNote(), noteHead);
2020-06-16 10:33:18 +02:00
} else {
symNotehead = Note::noteHead(0, noteheadGroup, noteHead);
}
m_shadowNote->setState(symNotehead, duration, false, segmentSkylineTopY, segmentSkylineBottomY);
2020-06-16 10:33:18 +02:00
}
m_shadowNote->layout();
m_shadowNote->setPos(position.pos);
2020-06-16 10:33:18 +02:00
}
void NotationInteraction::hideShadowNote()
{
m_shadowNote->setVisible(false);
}
2021-04-12 14:07:49 +02:00
void NotationInteraction::toggleVisible()
{
startEdit();
// TODO: Update `score()->cmdToggleVisible()` and call that here?
2021-04-12 14:07:49 +02:00
for (Element* el : selection()->elements()) {
if (el->isBracket()) {
continue;
}
el->undoChangeProperty(Ms::Pid::VISIBLE, !el->visible());
}
apply();
notifyAboutNotationChanged();
}
2021-06-07 19:25:41 +02:00
Element* NotationInteraction::hitElement(const PointF& pos, float width) const
2020-06-16 10:33:18 +02:00
{
QList<Ms::Element*> elements = hitElements(pos, width);
if (elements.isEmpty()) {
2020-06-16 10:33:18 +02:00
return nullptr;
}
return elements.first();
2020-06-16 10:33:18 +02:00
}
2021-06-07 19:25:41 +02:00
int NotationInteraction::hitStaffIndex(const PointF& pos) const
{
return hitMeasure(pos).staffIndex;
}
2021-06-07 19:25:41 +02:00
Ms::Page* NotationInteraction::point2page(const PointF& p) const
2020-06-16 10:48:13 +02:00
{
if (score()->layoutMode() == Ms::LayoutMode::LINE) {
return score()->pages().isEmpty() ? 0 : score()->pages().front();
}
foreach (Ms::Page* page, score()->pages()) {
if (page->bbox().translated(page->pos()).contains(p)) {
return page;
}
}
return nullptr;
}
2021-06-07 19:25:41 +02:00
QList<Element*> NotationInteraction::elementsAt(const PointF& p) const
{
QList<Element*> el;
2020-12-15 14:56:47 +01:00
Ms::Page* page = point2page(p);
if (page) {
el = page->items(p - page->pos());
std::sort(el.begin(), el.end(), NotationInteraction::elementIsLess);
}
return el;
}
2021-06-07 19:25:41 +02:00
Element* NotationInteraction::elementAt(const PointF& p) const
{
QList<Element*> el = elementsAt(p);
Element* e = el.value(0);
if (e && e->isPage()) {
e = el.value(1);
}
return e;
}
2021-06-07 19:25:41 +02:00
QList<Ms::Element*> NotationInteraction::hitElements(const PointF& p_in, float w) const
2020-06-16 10:48:13 +02:00
{
Ms::Page* page = point2page(p_in);
if (!page) {
return QList<Ms::Element*>();
}
QList<Ms::Element*> ll;
2021-06-07 19:25:41 +02:00
PointF p = p_in - page->pos();
2020-06-16 10:48:13 +02:00
2021-06-07 19:25:41 +02:00
RectF r(p.x() - w, p.y() - w, 3.0 * w, 3.0 * w);
2020-06-16 10:48:13 +02:00
2021-02-24 15:48:08 +01:00
QList<Ms::Element*> elements = page->items(r);
2020-06-16 10:48:13 +02:00
//! TODO
// for (int i = 0; i < MAX_HEADERS; i++)
// if (score()->headerText(i) != nullptr) // gives the ability to select the header
// el.push_back(score()->headerText(i));
// for (int i = 0; i < MAX_FOOTERS; i++)
// if (score()->footerText(i) != nullptr) // gives the ability to select the footer
// el.push_back(score()->footerText(i));
//! -------
2021-02-24 15:48:08 +01:00
for (Ms::Element* element : elements) {
element->itemDiscovered = 0;
if (!element->selectable() || element->isPage()) {
2020-06-16 10:48:13 +02:00
continue;
}
2021-02-24 15:48:08 +01:00
if (!element->isInteractionAvailable()) {
continue;
}
2021-02-24 15:48:08 +01:00
if (element->contains(p)) {
ll.append(element);
2020-06-16 10:48:13 +02:00
}
}
int n = ll.size();
if ((n == 0) || ((n == 1) && (ll[0]->isMeasure()))) {
//
// if no relevant element hit, look nearby
//
2021-02-24 15:48:08 +01:00
for (Ms::Element* element : elements) {
if (element->isPage() || !element->selectable()) {
2020-06-16 10:48:13 +02:00
continue;
}
2021-02-24 15:48:08 +01:00
if (!element->isInteractionAvailable()) {
continue;
}
2021-02-24 15:48:08 +01:00
if (element->intersects(r)) {
ll.append(element);
2020-06-16 10:48:13 +02:00
}
}
}
if (!ll.empty()) {
std::sort(ll.begin(), ll.end(), NotationInteraction::elementIsLess);
} else {
Ms::Measure* measure = hitMeasure(p_in).measure;
if (measure) {
ll << measure;
}
2020-06-16 10:48:13 +02:00
}
return ll;
}
2021-06-07 19:25:41 +02:00
NotationInteraction::HitMeasureData NotationInteraction::hitMeasure(const PointF& pos) const
{
int staffIndex;
2020-12-15 14:56:47 +01:00
Ms::Segment* segment;
2021-06-07 19:25:41 +02:00
PointF offset;
Measure* measure = score()->pos2measure(pos, &staffIndex, 0, &segment, &offset);
HitMeasureData result;
if (measure && measure->staffLines(staffIndex)->canvasBoundingRect().contains(pos)) {
result.measure = measure;
result.staffIndex = staffIndex;
}
return result;
}
2020-06-16 10:48:13 +02:00
bool NotationInteraction::elementIsLess(const Ms::Element* e1, const Ms::Element* e2)
{
if (!e1->selectable()) {
return false;
}
if (!e2->selectable()) {
return true;
}
if (e1->isNote() && e2->isStem()) {
return true;
}
if (e2->isNote() && e1->isStem()) {
return false;
}
if (e1->z() == e2->z()) {
// same stacking order, prefer non-hidden elements
if (e1->type() == e2->type()) {
if (e1->type() == Ms::ElementType::NOTEDOT) {
const Ms::NoteDot* n1 = static_cast<const Ms::NoteDot*>(e1);
const Ms::NoteDot* n2 = static_cast<const Ms::NoteDot*>(e2);
if (n1->note() && n1->note()->hidden()) {
return false;
} else if (n2->note() && n2->note()->hidden()) {
return true;
}
} else if (e1->type() == Ms::ElementType::NOTE) {
const Ms::Note* n1 = static_cast<const Ms::Note*>(e1);
const Ms::Note* n2 = static_cast<const Ms::Note*>(e2);
if (n1->hidden()) {
return false;
} else if (n2->hidden()) {
return true;
}
}
}
// different types, or same type but nothing hidden - use track
return e1->track() <= e2->track();
}
// default case, use stacking order
return e1->z() <= e2->z();
}
void NotationInteraction::addChordToSelection(MoveDirection d)
{
IF_ASSERT_FAILED(MoveDirection::Left == d || MoveDirection::Right == d) {
return;
}
QString cmd;
if (MoveDirection::Left == d) {
cmd = "select-prev-chord";
} else if (MoveDirection::Right == d) {
cmd = "select-next-chord";
}
score()->selectMove(cmd);
notifyAboutSelectionChanged();
}
2021-04-17 00:50:46 +02:00
void NotationInteraction::moveChordNoteSelection(MoveDirection d)
{
IF_ASSERT_FAILED(MoveDirection::Up == d || MoveDirection::Down == d) {
return;
}
Element* current = selection()->element();
if (!current || !(current->isNote() || current->isRest())) {
return;
}
Element* chordElem;
if (d == MoveDirection::Up) {
chordElem = score()->upAlt(current);
} else {
chordElem = score()->downAlt(current);
}
if (chordElem == current) {
return;
}
score()->select(chordElem, SelectType::SINGLE, chordElem->staffIdx());
notifyAboutSelectionChanged();
}
void NotationInteraction::select(const std::vector<Element*>& elements, SelectType type, int staffIndex)
2020-06-16 10:33:18 +02:00
{
if (needEndTextEditing(elements)) {
2020-11-02 20:23:13 +01:00
endEditText();
}
2021-01-28 12:27:36 +01:00
updateGripEdit(elements);
2020-11-02 20:23:13 +01:00
for (Element* element: elements) {
score()->select(element, type, staffIndex);
}
notifyAboutSelectionChanged();
2020-11-02 20:23:13 +01:00
}
2020-11-06 09:58:13 +01:00
void NotationInteraction::selectAll()
{
if (isTextEditingStarted()) {
auto textBase = toTextBase(m_textEditData.element);
textBase->selectAll(textBase->cursorFromEditData(m_textEditData));
} else {
score()->cmdSelectAll();
}
2020-11-06 09:58:13 +01:00
notifyAboutSelectionChanged();
2020-11-06 09:58:13 +01:00
}
2021-03-04 16:25:10 +01:00
void NotationInteraction::selectSection()
{
score()->cmdSelectSection();
notifyAboutSelectionChanged();
}
2021-04-17 00:51:20 +02:00
void NotationInteraction::selectFirstElement()
{
Element* element = score()->firstElement();
score()->select(element, SelectType::SINGLE, element->staffIdx());
notifyAboutSelectionChanged();
}
void NotationInteraction::selectLastElement()
{
Element* element = score()->lastElement();
score()->select(element, SelectType::SINGLE, element->staffIdx());
notifyAboutSelectionChanged();
}
INotationSelectionPtr NotationInteraction::selection() const
2020-06-16 10:33:18 +02:00
{
return m_selection;
}
void NotationInteraction::clearSelection()
{
if (selection()->isNone()) {
return;
}
score()->deselectAll();
notifyAboutSelectionChanged();
}
2020-06-16 10:33:18 +02:00
mu::async::Notification NotationInteraction::selectionChanged() const
{
return m_selectionChanged;
}
bool NotationInteraction::isDragStarted() const
{
2021-05-25 12:37:51 +02:00
return m_dragData.dragGroups.size() > 0 || !m_lasso->bbox().isEmpty();
2020-06-16 10:33:18 +02:00
}
void NotationInteraction::DragData::reset()
{
beginMove = QPointF();
elementOffset = QPointF();
ed = Ms::EditData(ed.view());
2020-06-16 10:33:18 +02:00
dragGroups.clear();
}
void NotationInteraction::startDrag(const std::vector<Element*>& elems,
2021-06-07 19:25:41 +02:00
const PointF& eoffset,
2020-06-16 10:33:18 +02:00
const IsDraggable& isDraggable)
{
m_dragData.reset();
2020-06-16 10:48:13 +02:00
m_dragData.elements = elems;
2020-06-16 10:33:18 +02:00
m_dragData.elementOffset = eoffset;
2020-06-16 10:48:13 +02:00
for (Element* e : m_dragData.elements) {
2020-06-16 10:33:18 +02:00
if (!isDraggable(e)) {
continue;
}
std::unique_ptr<Ms::ElementGroup> g = e->getDragGroup(isDraggable);
if (g && g->enabled()) {
m_dragData.dragGroups.push_back(std::move(g));
}
}
2021-02-01 18:48:18 +01:00
startEdit();
2021-01-28 12:27:36 +01:00
if (isGripEditStarted()) {
m_gripEditData.element->startEditDrag(m_gripEditData);
return;
}
for (auto& group : m_dragData.dragGroups) {
group->startDrag(m_dragData.ed);
2020-06-16 10:33:18 +02:00
}
}
2021-06-07 19:25:41 +02:00
void NotationInteraction::doDragLasso(const PointF& pt)
2021-05-25 12:37:51 +02:00
{
score()->addRefresh(m_lasso->canvasBoundingRect());
2021-06-07 19:25:41 +02:00
RectF r;
2021-05-25 12:37:51 +02:00
r.setCoords(m_dragData.beginMove.x(), m_dragData.beginMove.y(), pt.x(), pt.y());
m_lasso->setbbox(r.normalized());
score()->addRefresh(m_lasso->canvasBoundingRect());
score()->lassoSelect(m_lasso->bbox());
score()->update();
}
void NotationInteraction::endLasso()
{
score()->addRefresh(m_lasso->canvasBoundingRect());
2021-06-07 19:25:41 +02:00
m_lasso->setbbox(RectF());
2021-05-25 23:00:30 +02:00
score()->lassoSelectEnd(m_dragData.mode != DragMode::LassoList);
2021-05-25 12:37:51 +02:00
score()->update();
}
2021-06-07 19:25:41 +02:00
void NotationInteraction::drag(const PointF& fromPos, const PointF& toPos, DragMode mode)
2020-06-16 10:33:18 +02:00
{
if (m_dragData.beginMove.isNull()) {
m_dragData.beginMove = fromPos;
m_dragData.ed.pos = fromPos;
2020-06-16 10:33:18 +02:00
}
2021-05-25 23:00:30 +02:00
m_dragData.mode = mode;
2020-06-16 10:33:18 +02:00
2021-06-07 19:25:41 +02:00
PointF normalizedBegin = m_dragData.beginMove - m_dragData.elementOffset;
2020-06-16 10:33:18 +02:00
2021-06-07 19:25:41 +02:00
PointF delta = toPos - normalizedBegin;
PointF evtDelta = toPos - m_dragData.ed.pos;
2020-06-16 10:33:18 +02:00
switch (mode) {
case DragMode::BothXY:
2021-05-25 23:00:30 +02:00
case DragMode::LassoList:
2020-06-16 10:33:18 +02:00
break;
case DragMode::OnlyX:
delta.setY(m_dragData.ed.delta.y());
2020-06-16 10:33:18 +02:00
evtDelta.setY(0.0);
break;
case DragMode::OnlyY:
delta.setX(m_dragData.ed.delta.x());
2020-06-16 10:33:18 +02:00
evtDelta.setX(0.0);
break;
}
m_dragData.ed.lastPos = m_dragData.ed.pos;
m_dragData.ed.hRaster = false; //mscore->hRaster();
m_dragData.ed.vRaster = false; //mscore->vRaster();
m_dragData.ed.delta = delta;
m_dragData.ed.moveDelta = delta - m_dragData.elementOffset;
m_dragData.ed.evtDelta = evtDelta;
m_dragData.ed.pos = toPos;
2020-06-16 10:33:18 +02:00
if (isTextEditingStarted()) {
m_textEditData.pos = toPos;
toTextBase(m_textEditData.element)->dragTo(m_textEditData);
notifyAboutTextEditingChanged();
return;
}
2021-01-28 12:27:36 +01:00
if (isGripEditStarted()) {
m_dragData.ed.curGrip = m_gripEditData.curGrip;
m_dragData.ed.delta = m_dragData.ed.pos - m_dragData.ed.lastPos;
m_dragData.ed.moveDelta = m_dragData.ed.delta - m_dragData.elementOffset;
m_dragData.ed.addData(m_gripEditData.getData(m_gripEditData.element));
m_gripEditData.element->editDrag(m_dragData.ed);
} else {
for (auto& group : m_dragData.dragGroups) {
score()->addRefresh(group->drag(m_dragData.ed));
}
2020-06-16 10:33:18 +02:00
}
score()->update();
2021-06-07 19:25:41 +02:00
std::vector<LineF> anchorLines;
2020-06-16 10:48:13 +02:00
for (const Element* e : m_dragData.elements) {
2021-06-07 19:25:41 +02:00
QVector<LineF> elAnchorLines = e->dragAnchorLines();
2020-06-16 10:48:13 +02:00
const Ms::Element* page = e->findAncestor(ElementType::PAGE);
2021-06-07 19:25:41 +02:00
const PointF pageOffset((page ? page : e)->pos());
2020-06-16 10:48:13 +02:00
if (!elAnchorLines.isEmpty()) {
2021-06-07 19:25:41 +02:00
for (LineF& l : elAnchorLines) {
2020-06-16 10:48:13 +02:00
l.translate(pageOffset);
anchorLines.push_back(l);
2020-06-16 10:48:13 +02:00
}
}
}
setAnchorLines(anchorLines);
2020-06-16 10:48:13 +02:00
2021-05-25 12:37:51 +02:00
if (m_dragData.elements.size() == 0) {
doDragLasso(toPos);
}
notifyAboutDragChanged();
2020-06-16 10:33:18 +02:00
// Element* e = _score->getSelectedElement();
// if (e) {
// if (_score->playNote()) {
// mscore->play(e);
// _score->setPlayNote(false);
// }
// }
// updateGrips();
// _score->update();
}
void NotationInteraction::endDrag()
{
2021-01-28 12:27:36 +01:00
if (isGripEditStarted()) {
m_gripEditData.element->endEditDrag(m_gripEditData);
} else {
for (auto& group : m_dragData.dragGroups) {
group->endDrag(m_dragData.ed);
}
2021-05-25 12:37:51 +02:00
if (!m_lasso->bbox().isEmpty()) {
endLasso();
}
2020-06-16 10:33:18 +02:00
}
m_dragData.reset();
resetAnchorLines();
apply();
notifyAboutDragChanged();
// updateGrips();
// if (editData.element->normalModeEditBehavior() == Element::EditBehavior::Edit
// && _score->selection().element() == editData.element) {
// startEdit(/* editMode */ false);
// }
2020-06-16 10:33:18 +02:00
}
mu::async::Notification NotationInteraction::dragChanged() const
2020-06-16 10:33:18 +02:00
{
return m_dragChanged;
}
//! NOTE Copied from ScoreView::dragEnterEvent
void NotationInteraction::startDrop(const QByteArray& edata)
{
if (m_dropData.ed.dropElement) {
delete m_dropData.ed.dropElement;
m_dropData.ed.dropElement = nullptr;
}
2020-12-15 14:56:47 +01:00
Ms::XmlReader e(edata);
m_dropData.ed.dragOffset = QPointF();
Fraction duration; // dummy
ElementType type = Element::readType(e, &m_dropData.ed.dragOffset, &duration);
Element* el = Element::create(type, score());
if (el) {
if (type == ElementType::BAR_LINE || type == ElementType::ARPEGGIO || type == ElementType::BRACKET) {
double spatium = score()->spatium();
el->setHeight(spatium * 5);
}
m_dropData.ed.dropElement = el;
m_dropData.ed.dropElement->setParent(0);
m_dropData.ed.dropElement->read(e);
m_dropData.ed.dropElement->layout();
}
}
//! NOTE Copied from ScoreView::dragMoveEvent
2021-06-07 19:25:41 +02:00
bool NotationInteraction::isDropAccepted(const PointF& pos, Qt::KeyboardModifiers modifiers)
{
if (!m_dropData.ed.dropElement) {
return false;
}
m_dropData.ed.pos = pos;
m_dropData.ed.modifiers = modifiers;
switch (m_dropData.ed.dropElement->type()) {
case ElementType::VOLTA:
return dragMeasureAnchorElement(pos);
case ElementType::PEDAL:
case ElementType::LET_RING:
case ElementType::VIBRATO:
case ElementType::PALM_MUTE:
case ElementType::OTTAVA:
case ElementType::TRILL:
case ElementType::HAIRPIN:
case ElementType::TEXTLINE:
return dragTimeAnchorElement(pos);
case ElementType::IMAGE:
case ElementType::SYMBOL:
case ElementType::FSYMBOL:
case ElementType::DYNAMIC:
case ElementType::KEYSIG:
case ElementType::CLEF:
case ElementType::TIMESIG:
case ElementType::BAR_LINE:
case ElementType::ARPEGGIO:
case ElementType::BREATH:
case ElementType::GLISSANDO:
case ElementType::MEASURE_NUMBER:
case ElementType::BRACKET:
case ElementType::ARTICULATION:
case ElementType::FERMATA:
case ElementType::CHORDLINE:
case ElementType::BEND:
case ElementType::ACCIDENTAL:
case ElementType::TEXT:
case ElementType::FINGERING:
case ElementType::TEMPO_TEXT:
case ElementType::STAFF_TEXT:
case ElementType::SYSTEM_TEXT:
case ElementType::NOTEHEAD:
case ElementType::TREMOLO:
case ElementType::LAYOUT_BREAK:
case ElementType::MARKER:
case ElementType::STAFF_STATE:
case ElementType::INSTRUMENT_CHANGE:
case ElementType::REHEARSAL_MARK:
case ElementType::JUMP:
case ElementType::MEASURE_REPEAT:
case ElementType::ICON:
case ElementType::CHORD:
case ElementType::SPACER:
case ElementType::SLUR:
case ElementType::HARMONY:
case ElementType::BAGPIPE_EMBELLISHMENT:
case ElementType::AMBITUS:
case ElementType::TREMOLOBAR:
case ElementType::FIGURED_BASS:
case ElementType::LYRICS:
case ElementType::FRET_DIAGRAM:
case ElementType::STAFFTYPE_CHANGE: {
Element* e = dropTarget(m_dropData.ed);
if (e) {
if (!e->isMeasure()) {
setDropTarget(e);
}
return true;
} else {
return false;
}
}
break;
default:
break;
}
return false;
}
//! NOTE Copied from ScoreView::dropEvent
2021-06-07 19:25:41 +02:00
bool NotationInteraction::drop(const PointF& pos, Qt::KeyboardModifiers modifiers)
{
if (!m_dropData.ed.dropElement) {
return false;
}
IF_ASSERT_FAILED(m_dropData.ed.dropElement->score() == score()) {
return false;
}
bool accepted = false;
m_dropData.ed.pos = pos;
m_dropData.ed.modifiers = modifiers;
bool firstStaffOnly = false;
bool applyUserOffset = false;
//bool triggerSpannerDropApplyTour = m_dropData.ed.dropElement->isSpanner();
m_dropData.ed.dropElement->styleChanged();
startEdit();
score()->addRefresh(m_dropData.ed.dropElement->canvasBoundingRect());
switch (m_dropData.ed.dropElement->type()) {
case ElementType::TEXTLINE:
firstStaffOnly = m_dropData.ed.dropElement->systemFlag();
// fall-thru
case ElementType::VOLTA:
// voltas drop to first staff by default, or closest staff if Control is held
firstStaffOnly = firstStaffOnly || !(m_dropData.ed.modifiers & Qt::ControlModifier);
// fall-thru
case ElementType::OTTAVA:
case ElementType::TRILL:
case ElementType::PEDAL:
case ElementType::LET_RING:
case ElementType::VIBRATO:
case ElementType::PALM_MUTE:
case ElementType::HAIRPIN:
{
2020-12-15 14:56:47 +01:00
Ms::Spanner* spanner = ptr::checked_cast<Ms::Spanner>(m_dropData.ed.dropElement);
score()->cmdAddSpanner(spanner, pos, firstStaffOnly);
score()->setUpdateAll();
accepted = true;
}
break;
case ElementType::SYMBOL:
case ElementType::FSYMBOL:
case ElementType::IMAGE:
applyUserOffset = true;
// fall-thru
case ElementType::DYNAMIC:
case ElementType::FRET_DIAGRAM:
case ElementType::HARMONY:
{
Element* el = elementAt(pos);
if (el == 0 || el->type() == ElementType::STAFF_LINES) {
int staffIdx;
2020-12-15 14:56:47 +01:00
Ms::Segment* seg;
2021-06-07 19:25:41 +02:00
PointF offset;
el = score()->pos2measure(pos, &staffIdx, 0, &seg, &offset);
if (el && el->isMeasure()) {
m_dropData.ed.dropElement->setTrack(staffIdx * VOICES);
if (m_dropData.ed.dropElement->isImage()) {
m_dropData.ed.dropElement->setParent(el);
offset = pos - el->canvasPos();
} else {
m_dropData.ed.dropElement->setParent(seg);
}
if (applyUserOffset) {
m_dropData.ed.dropElement->setOffset(offset);
}
score()->undoAddElement(m_dropData.ed.dropElement);
} else {
qDebug("cannot drop here");
delete m_dropData.ed.dropElement;
m_dropData.ed.dropElement = nullptr;
}
} else {
score()->addRefresh(el->canvasBoundingRect());
score()->addRefresh(m_dropData.ed.dropElement->canvasBoundingRect());
if (!el->acceptDrop(m_dropData.ed)) {
qDebug("drop %s onto %s not accepted", m_dropData.ed.dropElement->name(), el->name());
break;
}
Element* dropElement = el->drop(m_dropData.ed);
score()->addRefresh(el->canvasBoundingRect());
if (dropElement) {
score()->select(dropElement, SelectType::SINGLE, 0);
score()->addRefresh(dropElement->canvasBoundingRect());
}
}
}
accepted = true;
break;
case ElementType::HBOX:
case ElementType::VBOX:
case ElementType::KEYSIG:
case ElementType::CLEF:
case ElementType::TIMESIG:
case ElementType::BAR_LINE:
case ElementType::ARPEGGIO:
case ElementType::BREATH:
case ElementType::GLISSANDO:
case ElementType::MEASURE_NUMBER:
case ElementType::BRACKET:
case ElementType::ARTICULATION:
case ElementType::FERMATA:
case ElementType::CHORDLINE:
case ElementType::BEND:
case ElementType::ACCIDENTAL:
case ElementType::TEXT:
case ElementType::FINGERING:
case ElementType::TEMPO_TEXT:
case ElementType::STAFF_TEXT:
case ElementType::SYSTEM_TEXT:
case ElementType::NOTEHEAD:
case ElementType::TREMOLO:
case ElementType::LAYOUT_BREAK:
case ElementType::MARKER:
case ElementType::STAFF_STATE:
case ElementType::INSTRUMENT_CHANGE:
case ElementType::REHEARSAL_MARK:
case ElementType::JUMP:
case ElementType::MEASURE_REPEAT:
case ElementType::ICON:
case ElementType::NOTE:
case ElementType::CHORD:
case ElementType::SPACER:
case ElementType::SLUR:
case ElementType::BAGPIPE_EMBELLISHMENT:
case ElementType::AMBITUS:
case ElementType::TREMOLOBAR:
case ElementType::FIGURED_BASS:
case ElementType::LYRICS:
case ElementType::STAFFTYPE_CHANGE: {
Element* el = dropTarget(m_dropData.ed);
if (!el) {
if (!dropCanvas(m_dropData.ed.dropElement)) {
qDebug("cannot drop %s(%p) to canvas", m_dropData.ed.dropElement->name(), m_dropData.ed.dropElement);
delete m_dropData.ed.dropElement;
m_dropData.ed.dropElement = nullptr;
}
break;
}
score()->addRefresh(el->canvasBoundingRect());
// TODO: HACK ALERT!
if (el->isMeasure() && m_dropData.ed.dropElement->isLayoutBreak()) {
Measure* m = toMeasure(el);
if (m->isMMRest()) {
el = m->mmRestLast();
}
}
Element* dropElement = el->drop(m_dropData.ed);
if (dropElement && dropElement->isInstrumentChange()) {
2020-12-04 14:21:54 +01:00
selectInstrument(toInstrumentChange(dropElement));
}
score()->addRefresh(el->canvasBoundingRect());
if (dropElement) {
if (!score()->noteEntryMode()) {
score()->select(dropElement, SelectType::SINGLE, 0);
}
score()->addRefresh(dropElement->canvasBoundingRect());
}
accepted = true;
}
break;
default:
delete m_dropData.ed.dropElement;
break;
}
m_dropData.ed.dropElement = nullptr;
setDropTarget(nullptr); // this also resets dropRectangle and dropAnchor
apply();
// update input cursor position (must be done after layout)
// if (noteEntryMode()) {
// moveCursor();
// }
// if (triggerSpannerDropApplyTour) {
// TourHandler::startTour("spanner-drop-apply");
// }
if (accepted) {
notifyAboutDropChanged();
}
return accepted;
}
void NotationInteraction::selectInstrument(Ms::InstrumentChange* instrumentChange)
2020-12-04 14:21:54 +01:00
{
if (!instrumentChange) {
return;
}
RetVal<Val> retVal = interactive()->open("musescore://instruments/select?canSelectMultipleInstruments=false");
if (!retVal.ret) {
return;
}
instruments::Instrument selectedIstrument = retVal.val.toQVariant().value<instruments::Instrument>();
if (!selectedIstrument.isValid()) {
return;
}
2020-12-10 16:23:12 +01:00
Ms::Instrument instrument = InstrumentsConverter::convertInstrument(selectedIstrument);
2020-12-04 14:21:54 +01:00
instrumentChange->setInit(true);
instrumentChange->setupInstrument(&instrument);
}
2020-07-09 14:31:34 +02:00
//! NOTE Copied from Palette::applyPaletteElement
bool NotationInteraction::applyPaletteElement(Ms::Element* element, Qt::KeyboardModifiers modifiers)
{
IF_ASSERT_FAILED(element) {
return false;
}
2020-12-15 14:56:47 +01:00
Ms::Score* score = this->score();
2020-07-09 14:31:34 +02:00
if (!score) {
return false;
}
2020-12-15 14:56:47 +01:00
const Ms::Selection sel = score->selection(); // make a copy of selection state before applying the operation.
2020-07-09 14:31:34 +02:00
if (sel.isNone()) {
return false;
}
//-- if (element->isSpanner()) {
//-- TourHandler::startTour("spanner-drop-apply");
//-- }
//#ifdef MSCORE_UNSTABLE
// if (ScriptRecorder* rec = adapter()->getScriptRecorder()) {
// if (modifiers == 0) {
// rec->recordPaletteElement(element);
// }
// }
//#endif
startEdit();
2020-07-09 14:31:34 +02:00
if (sel.isList()) {
ChordRest* cr1 = sel.firstChordRest();
ChordRest* cr2 = sel.lastChordRest();
bool addSingle = false; // add a single line only
if (cr1 && cr2 == cr1) {
// one chordrest selected, ok to add line
addSingle = true;
} else if (sel.elements().size() == 2 && cr1 && cr2 && cr1 != cr2) {
// two chordrests selected
// must be on same staff in order to add line, except for slur
if (element->isSlur() || cr1->staffIdx() == cr2->staffIdx()) {
addSingle = true;
}
}
auto isEntryDrumStaff = [score]() {
2020-12-23 10:26:43 +01:00
const Ms::InputState& is = score->inputState();
Ms::Staff* staff = score->staff(is.track() / VOICES);
return staff->staffType(is.tick())->group() == Ms::StaffGroup::PERCUSSION;
};
2020-07-09 14:31:34 +02:00
if (isEntryDrumStaff() && element->isChord()) {
2020-12-15 14:56:47 +01:00
Ms::InputState& is = score->inputState();
Element* e = nullptr;
if (!(modifiers & Qt::ShiftModifier)) {
// shift+double-click: add note to "chord"
// use input position rather than selection if possible
// look for a cr in the voice predefined for the drum in the palette
// back up if necessary
// TODO: refactor this with similar code in putNote()
if (is.segment()) {
2020-12-15 14:56:47 +01:00
Ms::Segment* seg = is.segment();
while (seg) {
if (seg->element(is.track())) {
break;
}
2020-12-15 14:56:47 +01:00
seg = seg->prev(Ms::SegmentType::ChordRest);
}
if (seg) {
is.setSegment(seg);
} else {
2020-12-15 14:56:47 +01:00
is.setSegment(is.segment()->measure()->first(Ms::SegmentType::ChordRest));
}
}
score->expandVoice();
e = is.cr();
}
2020-07-09 14:31:34 +02:00
if (!e) {
e = sel.elements().first();
}
if (e) {
// get note if selection was full chord
if (e->isChord()) {
e = toChord(e)->upNote();
}
2021-06-07 19:25:41 +02:00
applyDropPaletteElement(score, e, element, modifiers, PointF(), true);
// note has already been played (and what would play otherwise may be *next* input position)
score->setPlayNote(false);
score->setPlayChord(false);
2020-07-09 14:31:34 +02:00
// continue in same track
is.setTrack(e->track());
2020-07-09 14:31:34 +02:00
} else {
qDebug("nowhere to place drum note");
}
} else if (element->isLayoutBreak()) {
2020-12-15 14:56:47 +01:00
Ms::LayoutBreak* breakElement = toLayoutBreak(element);
2020-07-09 14:31:34 +02:00
score->cmdToggleLayoutBreak(breakElement->layoutBreakType());
} else if (element->isSlur() && addSingle) {
2020-11-25 19:03:54 +01:00
doAddSlur(toSlur(element));
2020-07-09 14:31:34 +02:00
} else if (element->isSLine() && !element->isGlissando() && addSingle) {
2020-12-15 14:56:47 +01:00
Ms::Segment* startSegment = cr1->segment();
Ms::Segment* endSegment = cr2->segment();
if (element->type() == Ms::ElementType::PEDAL && cr2 != cr1) {
2020-07-09 14:31:34 +02:00
endSegment = endSegment->nextCR(cr2->track());
}
// TODO - handle cross-voice selections
int idx = cr1->staffIdx();
2021-06-07 19:25:41 +02:00
QByteArray a = element->mimeData(PointF());
2020-07-09 14:31:34 +02:00
//printf("<<%s>>\n", a.data());
2020-12-15 14:56:47 +01:00
Ms::XmlReader e(a);
Ms::Fraction duration; // dummy
2021-06-07 19:25:41 +02:00
PointF dragOffset;
2020-12-15 14:56:47 +01:00
Ms::ElementType type = Ms::Element::readType(e, &dragOffset, &duration);
Ms::Spanner* spanner = static_cast<Ms::Spanner*>(Ms::Element::create(type, score));
2020-07-09 14:31:34 +02:00
spanner->read(e);
spanner->styleChanged();
score->cmdAddSpanner(spanner, idx, startSegment, endSegment);
} else {
for (Element* e : sel.elements()) {
2020-07-11 11:00:29 +02:00
applyDropPaletteElement(score, e, element, modifiers);
2020-07-09 14:31:34 +02:00
}
}
} else if (sel.isRange()) {
if (element->type() == ElementType::BAR_LINE
|| element->type() == ElementType::MARKER
|| element->type() == ElementType::JUMP
|| element->type() == ElementType::SPACER
|| element->type() == ElementType::VBOX
|| element->type() == ElementType::HBOX
|| element->type() == ElementType::TBOX
|| element->type() == ElementType::MEASURE
|| element->type() == ElementType::BRACKET
|| element->type() == ElementType::STAFFTYPE_CHANGE
|| (element->type() == ElementType::ICON
2020-12-15 14:56:47 +01:00
&& (toIcon(element)->iconType() == Ms::IconType::VFRAME
|| toIcon(element)->iconType() == Ms::IconType::HFRAME
|| toIcon(element)->iconType() == Ms::IconType::TFRAME
|| toIcon(element)->iconType() == Ms::IconType::MEASURE
|| toIcon(element)->iconType() == Ms::IconType::BRACKETS))) {
2020-07-09 14:31:34 +02:00
Measure* last = sel.endSegment() ? sel.endSegment()->measure() : nullptr;
for (Measure* m = sel.startSegment()->measure(); m; m = m->nextMeasureMM()) {
2021-06-07 19:25:41 +02:00
RectF r = m->staffabbox(sel.staffStart());
PointF pt(r.x() + r.width() * .5, r.y() + r.height() * .5);
2020-07-09 14:31:34 +02:00
pt += m->system()->page()->pos();
2020-07-11 11:00:29 +02:00
applyDropPaletteElement(score, m, element, modifiers, pt);
2020-07-09 14:31:34 +02:00
if (m == last) {
break;
}
}
} else if (element->type() == ElementType::LAYOUT_BREAK) {
2020-12-15 14:56:47 +01:00
Ms::LayoutBreak* breakElement = static_cast<Ms::LayoutBreak*>(element);
2020-07-09 14:31:34 +02:00
score->cmdToggleLayoutBreak(breakElement->layoutBreakType());
} else if (element->isClef() || element->isKeySig() || element->isTimeSig()) {
Measure* m1 = sel.startSegment()->measure();
Measure* m2 = sel.endSegment() ? sel.endSegment()->measure() : nullptr;
if (m2 == m1 && sel.startSegment()->rtick().isZero()) {
m2 = nullptr; // don't restore original if one full measure selected
} else if (m2) {
m2 = m2->nextMeasureMM();
}
// for clefs, apply to each staff separately
// otherwise just apply to top staff
int staffIdx1 = sel.staffStart();
int staffIdx2 = element->type() == ElementType::CLEF ? sel.staffEnd() : staffIdx1 + 1;
for (int i = staffIdx1; i < staffIdx2; ++i) {
// for clefs, use mid-measure changes if appropriate
Element* e1 = nullptr;
Element* e2 = nullptr;
// use mid-measure clef changes as appropriate
if (element->type() == ElementType::CLEF) {
if (sel.startSegment()->isChordRestType() && sel.startSegment()->rtick().isNotZero()) {
ChordRest* cr = static_cast<ChordRest*>(sel.startSegment()->nextChordRest(i * VOICES));
if (cr && cr->isChord()) {
2020-07-29 13:20:36 +02:00
e1 = static_cast<Ms::Chord*>(cr)->upNote();
2020-07-09 14:31:34 +02:00
} else {
e1 = cr;
}
}
2020-12-15 14:56:47 +01:00
if (sel.endSegment() && sel.endSegment()->segmentType() == Ms::SegmentType::ChordRest) {
2020-07-09 14:31:34 +02:00
ChordRest* cr = static_cast<ChordRest*>(sel.endSegment()->nextChordRest(i * VOICES));
if (cr && cr->isChord()) {
2020-07-29 13:20:36 +02:00
e2 = static_cast<Ms::Chord*>(cr)->upNote();
2020-07-09 14:31:34 +02:00
} else {
e2 = cr;
}
}
}
if (m2 || e2) {
// restore original clef/keysig/timesig
2020-12-15 14:56:47 +01:00
Ms::Staff* staff = score->staff(i);
Ms::Fraction tick1 = sel.startSegment()->tick();
Ms::Element* oelement = nullptr;
2020-07-09 14:31:34 +02:00
switch (element->type()) {
2020-12-15 14:56:47 +01:00
case Ms::ElementType::CLEF:
2020-07-09 14:31:34 +02:00
{
2020-12-15 14:56:47 +01:00
Ms::Clef* oclef = new Ms::Clef(score);
2020-07-09 14:31:34 +02:00
oclef->setClefType(staff->clef(tick1));
oelement = oclef;
break;
}
2020-12-15 14:56:47 +01:00
case Ms::ElementType::KEYSIG:
2020-07-09 14:31:34 +02:00
{
2020-12-15 14:56:47 +01:00
Ms::KeySig* okeysig = new Ms::KeySig(score);
2020-07-09 14:31:34 +02:00
okeysig->setKeySigEvent(staff->keySigEvent(tick1));
2020-12-15 14:56:47 +01:00
if (!score->styleB(Ms::Sid::concertPitch) && !okeysig->isCustom() && !okeysig->isAtonal()) {
2020-07-09 14:31:34 +02:00
Ms::Interval v = staff->part()->instrument(tick1)->transpose();
if (!v.isZero()) {
Key k = okeysig->key();
okeysig->setKey(transposeKey(k, v, okeysig->part()->preferSharpFlat()));
}
}
oelement = okeysig;
break;
}
2020-12-15 14:56:47 +01:00
case Ms::ElementType::TIMESIG:
2020-07-09 14:31:34 +02:00
{
2020-12-15 14:56:47 +01:00
Ms::TimeSig* otimesig = new Ms::TimeSig(score);
2020-07-09 14:31:34 +02:00
otimesig->setFrom(staff->timeSig(tick1));
oelement = otimesig;
break;
}
default:
break;
}
if (oelement) {
if (e2) {
2020-07-11 11:00:29 +02:00
applyDropPaletteElement(score, e2, oelement, modifiers);
2020-07-09 14:31:34 +02:00
} else {
2021-06-07 19:25:41 +02:00
RectF r = m2->staffabbox(i);
PointF pt(r.x() + r.width() * .5, r.y() + r.height() * .5);
2020-07-09 14:31:34 +02:00
pt += m2->system()->page()->pos();
2020-07-11 11:00:29 +02:00
applyDropPaletteElement(score, m2, oelement, modifiers, pt);
2020-07-09 14:31:34 +02:00
}
delete oelement;
}
}
// apply new clef/keysig/timesig
if (e1) {
2020-07-11 11:00:29 +02:00
applyDropPaletteElement(score, e1, element, modifiers);
2020-07-09 14:31:34 +02:00
} else {
2021-06-07 19:25:41 +02:00
RectF r = m1->staffabbox(i);
PointF pt(r.x() + r.width() * .5, r.y() + r.height() * .5);
2020-07-09 14:31:34 +02:00
pt += m1->system()->page()->pos();
2020-07-11 11:00:29 +02:00
applyDropPaletteElement(score, m1, element, modifiers, pt);
2020-07-09 14:31:34 +02:00
}
}
} else if (element->isSlur()) {
2020-11-25 19:03:54 +01:00
doAddSlur(toSlur(element));
2020-07-09 14:31:34 +02:00
} else if (element->isSLine() && element->type() != ElementType::GLISSANDO) {
2020-12-15 14:56:47 +01:00
Ms::Segment* startSegment = sel.startSegment();
Ms::Segment* endSegment = sel.endSegment();
2020-07-09 14:31:34 +02:00
bool firstStaffOnly = element->isVolta() && !(modifiers & Qt::ControlModifier);
int startStaff = firstStaffOnly ? 0 : sel.staffStart();
int endStaff = firstStaffOnly ? 1 : sel.staffEnd();
for (int i = startStaff; i < endStaff; ++i) {
2020-12-15 14:56:47 +01:00
Ms::Spanner* spanner = static_cast<Ms::Spanner*>(element->clone());
2020-07-09 14:31:34 +02:00
spanner->setScore(score);
spanner->styleChanged();
score->cmdAddSpanner(spanner, i, startSegment, endSegment);
}
} else {
int track1 = sel.staffStart() * VOICES;
int track2 = sel.staffEnd() * VOICES;
2020-12-15 14:56:47 +01:00
Ms::Segment* startSegment = sel.startSegment();
Ms::Segment* endSegment = sel.endSegment(); //keep it, it could change during the loop
2020-07-09 14:31:34 +02:00
2020-12-15 14:56:47 +01:00
for (Ms::Segment* s = startSegment; s && s != endSegment; s = s->next1()) {
2020-07-09 14:31:34 +02:00
for (int track = track1; track < track2; ++track) {
2020-12-15 14:56:47 +01:00
Ms::Element* e = s->element(track);
2020-07-09 14:31:34 +02:00
if (e == 0 || !score->selectionFilter().canSelect(e)
|| !score->selectionFilter().canSelectVoice(track)) {
continue;
}
if (e->isChord()) {
2020-07-29 13:20:36 +02:00
Ms::Chord* chord = toChord(e);
2020-12-15 14:56:47 +01:00
for (Ms::Note* n : chord->notes()) {
2020-07-11 11:00:29 +02:00
applyDropPaletteElement(score, n, element, modifiers);
2020-07-09 14:31:34 +02:00
if (!(element->isAccidental() || element->isNoteHead())) { // only these need to apply to every note
break;
}
}
} else {
// do not apply articulation to barline in a range selection
if (!e->isBarLine() || !element->isArticulation()) {
2020-07-11 11:00:29 +02:00
applyDropPaletteElement(score, e, element, modifiers);
2020-07-09 14:31:34 +02:00
}
}
}
if (!element->placeMultiple()) {
break;
}
}
}
} else {
qDebug("unknown selection state");
}
apply();
2020-10-23 19:30:10 +02:00
2020-07-09 14:31:34 +02:00
setDropTarget(nullptr);
return true;
}
//! NOTE Copied from Palette applyDrop
2020-07-11 11:00:29 +02:00
void NotationInteraction::applyDropPaletteElement(Ms::Score* score, Ms::Element* target, Ms::Element* e,
2020-07-09 14:31:34 +02:00
Qt::KeyboardModifiers modifiers,
2021-06-07 19:25:41 +02:00
PointF pt, bool pasteMode)
2020-07-09 14:31:34 +02:00
{
Ms::EditData dropData(&m_scoreCallbacks);
2020-07-09 14:31:34 +02:00
dropData.pos = pt.isNull() ? target->pagePos() : pt;
dropData.dragOffset = QPointF();
dropData.modifiers = modifiers;
dropData.dropElement = e;
if (target->acceptDrop(dropData)) {
// use same code path as drag&drop
2021-06-07 19:25:41 +02:00
QByteArray a = e->mimeData(PointF());
2020-07-09 14:31:34 +02:00
2020-12-15 14:56:47 +01:00
Ms::XmlReader n(a);
2020-07-09 14:31:34 +02:00
n.setPasteMode(pasteMode);
Fraction duration; // dummy
2021-06-07 19:25:41 +02:00
PointF dragOffset;
2020-07-09 14:31:34 +02:00
ElementType type = Element::readType(n, &dragOffset, &duration);
dropData.dropElement = Element::create(type, score);
dropData.dropElement->read(n);
dropData.dropElement->styleChanged(); // update to local style
2020-12-15 14:56:47 +01:00
Ms::Element* el = target->drop(dropData);
2020-07-09 14:31:34 +02:00
if (el && el->isInstrumentChange()) {
2020-12-04 14:21:54 +01:00
selectInstrument(toInstrumentChange(el));
2020-07-09 14:31:34 +02:00
}
if (el && !score->inputState().noteEntryMode()) {
2020-12-15 14:56:47 +01:00
select({ el }, Ms::SelectType::SINGLE, 0);
2020-07-09 14:31:34 +02:00
}
dropData.dropElement = 0;
m_notifyAboutDropChanged = true;
2020-07-09 14:31:34 +02:00
}
}
//! NOTE Copied from ScoreView::cmdAddSlur
2020-11-25 19:03:54 +01:00
void NotationInteraction::doAddSlur(const Ms::Slur* slurTemplate)
{
startEdit();
2020-07-09 14:31:34 +02:00
Ms::ChordRest* firstChordRest = nullptr;
Ms::ChordRest* secondChordRest = nullptr;
2020-07-09 14:31:34 +02:00
const auto& sel = score()->selection();
auto el = sel.uniqueElements();
2020-07-09 14:31:34 +02:00
if (sel.isRange()) {
int startTrack = sel.staffStart() * VOICES;
int endTrack = sel.staffEnd() * VOICES;
2020-07-09 14:31:34 +02:00
for (int track = startTrack; track < endTrack; ++track) {
firstChordRest = nullptr;
secondChordRest = nullptr;
2020-12-15 14:56:47 +01:00
for (Ms::Element* e : el) {
2020-07-09 14:31:34 +02:00
if (e->track() != track) {
continue;
}
if (e->isNote()) {
e = toNote(e)->chord();
}
if (!e->isChord()) {
continue;
}
2020-12-15 14:56:47 +01:00
Ms::ChordRest* cr = Ms::toChordRest(e);
if (!firstChordRest || firstChordRest->tick() > cr->tick()) {
firstChordRest = cr;
2020-07-09 14:31:34 +02:00
}
if (!secondChordRest || secondChordRest->tick() < cr->tick()) {
secondChordRest = cr;
2020-07-09 14:31:34 +02:00
}
}
2020-11-25 19:03:54 +01:00
if (firstChordRest && (firstChordRest != secondChordRest)) {
doAddSlur(firstChordRest, secondChordRest, slurTemplate);
2020-07-09 14:31:34 +02:00
}
}
} else {
2020-12-15 14:56:47 +01:00
for (Ms::Element* e : el) {
2020-07-09 14:31:34 +02:00
if (e->isNote()) {
2020-12-15 14:56:47 +01:00
e = Ms::toNote(e)->chord();
2020-07-09 14:31:34 +02:00
}
if (!e->isChord()) {
continue;
}
2020-12-15 14:56:47 +01:00
Ms::ChordRest* cr = Ms::toChordRest(e);
if (!firstChordRest || cr->isBefore(firstChordRest)) {
firstChordRest = cr;
2020-07-09 14:31:34 +02:00
}
if (!secondChordRest || secondChordRest->isBefore(cr)) {
secondChordRest = cr;
2020-07-09 14:31:34 +02:00
}
}
if (firstChordRest == secondChordRest) {
secondChordRest = Ms::nextChordRest(firstChordRest);
2020-07-09 14:31:34 +02:00
}
if (firstChordRest) {
doAddSlur(firstChordRest, secondChordRest, slurTemplate);
2020-07-09 14:31:34 +02:00
}
}
apply();
2020-07-09 14:31:34 +02:00
}
void NotationInteraction::doAddSlur(ChordRest* firstChordRest, ChordRest* secondChordRest, const Ms::Slur* slurTemplate)
{
Ms::Slur* slur = firstChordRest->slur(secondChordRest);
if (slur) {
score()->removeElement(slur);
return;
}
slur = score()->addSlur(firstChordRest, secondChordRest, slurTemplate);
if (m_noteInput->isNoteInputMode()) {
m_noteInput->addSlur(slur);
} else if (!secondChordRest) {
NOT_IMPLEMENTED;
//startEditMode(ss);
}
}
2020-12-08 19:37:10 +01:00
bool NotationInteraction::scoreHasMeasure() const
{
2020-12-15 14:56:47 +01:00
Ms::Page* page = score()->pages().isEmpty() ? nullptr : score()->pages().front();
const QList<Ms::System*>* systems = page ? &page->systems() : nullptr;
2020-12-08 19:37:10 +01:00
if (systems == nullptr || systems->empty() || systems->front()->measures().empty()) {
return false;
}
return true;
}
2020-12-14 16:07:33 +01:00
bool NotationInteraction::notesHaveActiculation(const std::vector<Note*>& notes, SymbolId articulationSymbolId) const
{
for (Note* note: notes) {
Chord* chord = note->chord();
std::set<SymbolId> chordArticulations = chord->articulationSymbolIds();
2020-12-14 16:07:33 +01:00
chordArticulations = Ms::flipArticulations(chordArticulations, Ms::Placement::ABOVE);
chordArticulations = Ms::splitArticulations(chordArticulations);
if (chordArticulations.find(articulationSymbolId) == chordArticulations.end()) {
return false;
}
}
return true;
}
//! NOTE Copied from ScoreView::dragLeaveEvent
void NotationInteraction::endDrop()
{
if (m_dropData.ed.dropElement) {
score()->setUpdateAll();
delete m_dropData.ed.dropElement;
m_dropData.ed.dropElement = nullptr;
score()->update();
}
setDropTarget(nullptr);
}
2020-07-10 10:46:56 +02:00
mu::async::Notification NotationInteraction::dropChanged() const
{
return m_dropChanged;
}
//! NOTE Copied from ScoreView::dropCanvas
bool NotationInteraction::dropCanvas(Element* e)
{
if (e->isIcon()) {
2020-12-15 14:56:47 +01:00
switch (Ms::toIcon(e)->iconType()) {
case Ms::IconType::VFRAME:
score()->insertMeasure(ElementType::VBOX, 0);
break;
2020-12-15 14:56:47 +01:00
case Ms::IconType::HFRAME:
score()->insertMeasure(ElementType::HBOX, 0);
break;
2020-12-15 14:56:47 +01:00
case Ms::IconType::TFRAME:
score()->insertMeasure(ElementType::TBOX, 0);
break;
2020-12-15 14:56:47 +01:00
case Ms::IconType::FFRAME:
score()->insertMeasure(ElementType::FBOX, 0);
break;
2020-12-15 14:56:47 +01:00
case Ms::IconType::MEASURE:
score()->insertMeasure(ElementType::MEASURE, 0);
break;
default:
return false;
}
delete e;
return true;
}
return false;
}
//! NOTE Copied from ScoreView::getDropTarget
Element* NotationInteraction::dropTarget(Ms::EditData& ed) const
{
QList<Element*> el = elementsAt(ed.pos);
for (Element* e : el) {
if (e->isStaffLines()) {
if (el.size() > 2) { // is not first class drop target
continue;
}
2020-12-15 14:56:47 +01:00
e = Ms::toStaffLines(e)->measure();
}
if (e->acceptDrop(ed)) {
return e;
}
}
return nullptr;
}
//! NOTE Copied from ScoreView::dragMeasureAnchorElement
2021-06-07 19:25:41 +02:00
bool NotationInteraction::dragMeasureAnchorElement(const PointF& pos)
{
int staffIdx;
2020-12-15 14:56:47 +01:00
Ms::Segment* seg;
Ms::MeasureBase* mb = score()->pos2measure(pos, &staffIdx, 0, &seg, 0);
if (!(m_dropData.ed.modifiers & Qt::ControlModifier)) {
staffIdx = 0;
}
int track = staffIdx * VOICES;
if (mb && mb->isMeasure()) {
2020-12-15 14:56:47 +01:00
Ms::Measure* m = Ms::toMeasure(mb);
Ms::System* s = m->system();
qreal y = s->staff(staffIdx)->y() + s->pos().y() + s->page()->pos().y();
2021-06-07 19:25:41 +02:00
RectF b(m->canvasBoundingRect());
if (pos.x() >= (b.x() + b.width() * .5) && m != score()->lastMeasureMM()
&& m->nextMeasure()->system() == m->system()) {
m = m->nextMeasure();
}
2021-06-07 19:25:41 +02:00
PointF anchor(m->canvasBoundingRect().x(), y);
setAnchorLines({ LineF(pos, anchor) });
m_dropData.ed.dropElement->score()->addRefresh(m_dropData.ed.dropElement->canvasBoundingRect());
m_dropData.ed.dropElement->setTrack(track);
m_dropData.ed.dropElement->score()->addRefresh(m_dropData.ed.dropElement->canvasBoundingRect());
m_notifyAboutDropChanged = true;
return true;
}
m_dropData.ed.dropElement->score()->addRefresh(m_dropData.ed.dropElement->canvasBoundingRect());
setDropTarget(nullptr);
return false;
}
//! NOTE Copied from ScoreView::dragTimeAnchorElement
2021-06-07 19:25:41 +02:00
bool NotationInteraction::dragTimeAnchorElement(const PointF& pos)
{
int staffIdx;
2020-12-15 14:56:47 +01:00
Ms::Segment* seg;
Ms::MeasureBase* mb = score()->pos2measure(pos, &staffIdx, 0, &seg, 0);
int track = staffIdx * VOICES;
if (mb && mb->isMeasure() && seg->element(track)) {
2020-12-15 14:56:47 +01:00
Ms::Measure* m = Ms::toMeasure(mb);
Ms::System* s = m->system();
qreal y = s->staff(staffIdx)->y() + s->pos().y() + s->page()->pos().y();
2021-06-07 19:25:41 +02:00
PointF anchor(seg->canvasBoundingRect().x(), y);
setAnchorLines({ LineF(pos, anchor) });
m_dropData.ed.dropElement->score()->addRefresh(m_dropData.ed.dropElement->canvasBoundingRect());
m_dropData.ed.dropElement->setTrack(track);
m_dropData.ed.dropElement->score()->addRefresh(m_dropData.ed.dropElement->canvasBoundingRect());
notifyAboutDragChanged();
return true;
}
m_dropData.ed.dropElement->score()->addRefresh(m_dropData.ed.dropElement->canvasBoundingRect());
setDropTarget(nullptr);
return false;
}
2020-07-09 14:31:34 +02:00
//! NOTE Copied from ScoreView::setDropTarget
void NotationInteraction::setDropTarget(Element* el)
{
if (m_dropData.dropTarget != el) {
if (m_dropData.dropTarget) {
m_dropData.dropTarget->setDropTarget(false);
m_dropData.dropTarget = nullptr;
}
m_dropData.dropTarget = el;
if (m_dropData.dropTarget) {
m_dropData.dropTarget->setDropTarget(true);
}
}
m_anchorLines.clear();
//! TODO
// if (dropRectangle.isValid()) {
// dropRectangle = QRectF();
// }
//! ---
notifyAboutDragChanged();
}
2021-06-07 19:25:41 +02:00
void NotationInteraction::setAnchorLines(const std::vector<LineF>& anchorList)
{
m_anchorLines = anchorList;
}
void NotationInteraction::resetAnchorLines()
2020-06-16 10:33:18 +02:00
{
m_anchorLines.clear();
2020-06-16 10:33:18 +02:00
}
void NotationInteraction::drawAnchorLines(mu::draw::Painter* painter)
2020-06-16 10:33:18 +02:00
{
if (m_anchorLines.empty()) {
2020-06-16 10:48:13 +02:00
return;
2020-06-16 10:33:18 +02:00
}
const auto dropAnchorColor = configuration()->anchorLineColor();
mu::draw::Pen pen(dropAnchorColor, 2.0 / painter->worldTransform().m11(), mu::draw::PenStyle::DotLine);
2020-06-16 10:33:18 +02:00
2021-06-07 19:25:41 +02:00
for (const LineF& anchor : m_anchorLines) {
2020-06-16 10:48:13 +02:00
painter->setPen(pen);
painter->drawLine(anchor);
2020-06-16 10:33:18 +02:00
2020-06-16 10:48:13 +02:00
qreal d = 4.0 / painter->worldTransform().m11();
2021-06-07 19:25:41 +02:00
RectF rect(-d, -d, 2 * d, 2 * d);
2020-06-16 10:33:18 +02:00
2020-06-16 10:48:13 +02:00
painter->setBrush(QBrush(dropAnchorColor));
2021-03-02 12:43:26 +01:00
painter->setNoPen();
2021-06-07 19:25:41 +02:00
rect.moveCenter(anchor.p1());
2020-06-16 10:48:13 +02:00
painter->drawEllipse(rect);
2021-06-07 19:25:41 +02:00
rect.moveCenter(anchor.p2());
2020-06-16 10:48:13 +02:00
painter->drawEllipse(rect);
2020-06-16 10:33:18 +02:00
}
}
void NotationInteraction::drawTextEditMode(draw::Painter* painter)
{
if (!isTextEditingStarted()) {
return;
}
m_textEditData.element->drawEditMode(painter, m_textEditData);
}
void NotationInteraction::drawSelectionRange(draw::Painter* painter)
2020-11-10 08:03:27 +01:00
{
2021-06-29 23:42:51 +02:00
using namespace draw;
2020-11-10 08:03:27 +01:00
if (!m_selection->isRange()) {
return;
}
painter->setBrush(Qt::NoBrush);
QColor selectionColor = configuration()->selectionColor();
qreal penWidth = 3.0 / painter->worldTransform().toAffine().m11();
2021-06-29 23:42:51 +02:00
Pen pen;
2020-11-10 08:03:27 +01:00
pen.setColor(selectionColor);
pen.setWidthF(penWidth);
pen.setStyle(PenStyle::SolidLine);
2020-11-10 08:03:27 +01:00
painter->setPen(pen);
2021-06-07 19:25:41 +02:00
std::vector<RectF> rangeArea = m_selection->range()->boundingArea();
for (const RectF& rect: rangeArea) {
2020-11-10 08:03:27 +01:00
QPainterPath path;
2021-06-07 19:25:41 +02:00
path.addRoundedRect(rect.toQRectF(), 6, 6);
2020-11-10 08:03:27 +01:00
QColor fillColor = selectionColor;
fillColor.setAlpha(10);
painter->fillPath(path, fillColor);
painter->drawPath(path);
}
}
void NotationInteraction::drawGripPoints(draw::Painter* painter)
2021-01-28 12:27:36 +01:00
{
if (!selection()->element() || !m_gripEditData.element) {
return;
}
m_gripEditData.grip.resize(m_gripEditData.grips);
constexpr qreal DEFAULT_GRIP_SIZE = 8;
qreal gripWidth = DEFAULT_GRIP_SIZE / painter->worldTransform().m11();
qreal gripHeight = DEFAULT_GRIP_SIZE / painter->worldTransform().m22();
2021-06-07 19:25:41 +02:00
RectF newRect(-gripWidth / 2, -gripHeight / 2, gripWidth, gripHeight);
2021-02-01 18:48:18 +01:00
Element* page = m_gripEditData.element->findAncestor(ElementType::PAGE);
2021-06-07 19:25:41 +02:00
PointF pageOffset = page ? page->pos() : m_gripEditData.element->pos();
2021-02-01 18:48:18 +01:00
2021-06-07 19:25:41 +02:00
for (RectF& gripRect: m_gripEditData.grip) {
2021-02-01 18:48:18 +01:00
gripRect = newRect.translated(pageOffset);
2021-01-28 12:27:36 +01:00
}
m_gripEditData.element->updateGrips(m_gripEditData);
m_gripEditData.element->drawEditMode(painter, m_gripEditData);
}
void NotationInteraction::moveSelection(MoveDirection d, MoveSelectionType type)
{
IF_ASSERT_FAILED(MoveDirection::Left == d || MoveDirection::Right == d) {
return;
}
IF_ASSERT_FAILED(MoveSelectionType::Undefined != type) {
return;
}
if (MoveSelectionType::Element == type) {
moveElementSelection(d);
return;
}
//! NOTE Previously, the `Score::move` method directly expected commands (actions)
//! Now the `Notation` provides only notation management methods,
//! and interpretation of actions is the responsibility of `NotationActionController`
auto typeToString = [](MoveSelectionType type) {
2020-12-23 10:26:43 +01:00
switch (type) {
case MoveSelectionType::Undefined: return QString();
case MoveSelectionType::Element: return QString();
case MoveSelectionType::Chord: return QString("chord");
case MoveSelectionType::Measure: return QString("measure");
case MoveSelectionType::Track: return QString("track");
}
return QString();
};
QString cmd;
if (MoveDirection::Left == d) {
cmd = "prev-";
}
if (MoveDirection::Right == d) {
cmd = "next-";
}
cmd += typeToString(type);
score()->move(cmd);
notifyAboutSelectionChanged();
}
void NotationInteraction::moveElementSelection(MoveDirection d)
{
Element* el = score()->selection().element();
if (!el && !score()->selection().elements().isEmpty()) {
el = score()->selection().elements().last();
}
if (!el) {
ChordRest* cr = score()->selection().currentCR();
if (cr) {
if (cr->isChord()) {
if (MoveDirection::Left == d) {
el = toChord(cr)->upNote();
} else {
el = toChord(cr)->downNote();
}
} else if (cr->isRest()) {
el = cr;
}
score()->select(el);
}
}
Element* toEl = nullptr;
if (el) {
toEl = (MoveDirection::Left == d) ? score()->prevElement() : score()->nextElement();
} else {
toEl = (MoveDirection::Left == d) ? score()->lastElement() : score()->firstElement();
}
if (toEl) {
score()->select(toEl, SelectType::SINGLE, 0);
if (toEl->type() == ElementType::NOTE || toEl->type() == ElementType::HARMONY) {
score()->setPlayNote(true);
}
}
notifyAboutSelectionChanged();
}
void NotationInteraction::movePitch(MoveDirection d, PitchMode mode)
{
IF_ASSERT_FAILED(MoveDirection::Up == d || MoveDirection::Down == d) {
return;
}
QList<Note*> el = score()->selection().uniqueNotes();
IF_ASSERT_FAILED(!el.isEmpty()) {
return;
}
startEdit();
bool isUp = MoveDirection::Up == d;
score()->upDown(isUp, mode);
apply();
notifyAboutDragChanged();
}
void NotationInteraction::moveText(MoveDirection d, bool quickly)
{
Element* el = score()->selection().element();
IF_ASSERT_FAILED(el && el->isTextBase()) {
return;
}
startEdit();
2020-12-15 14:56:47 +01:00
qreal step = quickly ? Ms::MScore::nudgeStep10 : Ms::MScore::nudgeStep;
step = step * el->spatium();
switch (d) {
case MoveDirection::Undefined:
IF_ASSERT_FAILED(d != MoveDirection::Undefined) {
return;
}
2020-06-26 15:09:20 +02:00
break;
case MoveDirection::Left:
2021-06-07 19:25:41 +02:00
el->undoChangeProperty(Ms::Pid::OFFSET, el->offset() - PointF(step, 0.0), Ms::PropertyFlags::UNSTYLED);
break;
case MoveDirection::Right:
2021-06-07 19:25:41 +02:00
el->undoChangeProperty(Ms::Pid::OFFSET, el->offset() + PointF(step, 0.0), Ms::PropertyFlags::UNSTYLED);
break;
case MoveDirection::Up:
2021-06-07 19:25:41 +02:00
el->undoChangeProperty(Ms::Pid::OFFSET, el->offset() - PointF(0.0, step), Ms::PropertyFlags::UNSTYLED);
break;
case MoveDirection::Down:
2021-06-07 19:25:41 +02:00
el->undoChangeProperty(Ms::Pid::OFFSET, el->offset() + PointF(0.0, step), Ms::PropertyFlags::UNSTYLED);
break;
}
apply();
notifyAboutDragChanged();
}
bool NotationInteraction::isTextEditingStarted() const
{
return m_textEditData.element && m_textEditData.element->isTextBase();
}
2021-06-07 19:25:41 +02:00
void NotationInteraction::startEditText(Element* element, const PointF& cursorPos)
{
if (!element || !element->isEditable() || !element->isTextBase()) {
qDebug("The element cannot be edited");
return;
}
m_textEditData.startMove = cursorPos;
if (isTextEditingStarted()) {
// double click on a textBase element that is being edited - select word
2020-12-15 14:56:47 +01:00
Ms::TextBase* textBase = Ms::toTextBase(m_textEditData.element);
textBase->multiClickSelect(m_textEditData, Ms::MultiClick::Double);
textBase->endHexState(m_textEditData);
textBase->setPrimed(false);
} else {
m_textEditData.clearData();
m_textEditData.element = element;
if (m_textEditData.element->isTBox()) {
m_textEditData.element = toTBox(m_textEditData.element)->text();
}
element->startEdit(m_textEditData);
}
notifyAboutTextEditingStarted();
notifyAboutTextEditingChanged();
}
void NotationInteraction::editText(QKeyEvent* event)
{
bool wasEditingText = m_textEditData.element != nullptr;
if (!wasEditingText && selection()->element()) {
m_textEditData.element = selection()->element();
}
if (!m_textEditData.element) {
return;
}
m_textEditData.key = event->key();
m_textEditData.modifiers = event->modifiers();
m_textEditData.s = event->text();
startEdit();
if (m_textEditData.element->edit(m_textEditData)) {
event->accept();
apply();
} else {
m_undoStack->rollbackChanges();
}
if (!wasEditingText) {
m_textEditData.element = nullptr;
}
if (isTextEditingStarted()) {
notifyAboutTextEditingChanged();
}
}
void NotationInteraction::endEditText()
{
IF_ASSERT_FAILED(m_textEditData.element) {
return;
}
if (!isTextEditingStarted()) {
return;
}
m_textEditData.element->endEdit(m_textEditData);
m_textEditData.element = nullptr;
m_textEditData.clearData();
notifyAboutTextEditingChanged();
}
2021-06-07 19:25:41 +02:00
void NotationInteraction::changeTextCursorPosition(const PointF& newCursorPos)
{
IF_ASSERT_FAILED(isTextEditingStarted() && m_textEditData.element) {
return;
}
m_textEditData.startMove = newCursorPos;
m_textEditData.element->mousePress(m_textEditData);
notifyAboutTextEditingChanged();
}
void NotationInteraction::undo()
{
m_undoStack->undo(&m_textEditData);
}
void NotationInteraction::redo()
{
m_undoStack->redo(&m_textEditData);
}
mu::async::Notification NotationInteraction::textEditingStarted() const
{
return m_textEditingStarted;
}
mu::async::Notification NotationInteraction::textEditingChanged() const
{
return m_textEditingChanged;
}
2020-10-13 14:06:26 +02:00
mu::async::Channel<ScoreConfigType> NotationInteraction::scoreConfigChanged() const
{
return m_scoreConfigChanged;
}
2021-01-28 12:27:36 +01:00
bool NotationInteraction::isGripEditStarted() const
{
return m_gripEditData.element && m_gripEditData.curGrip != Ms::Grip::NO_GRIP;
}
2021-06-07 19:25:41 +02:00
bool NotationInteraction::isHitGrip(const PointF& pos) const
2021-01-28 12:27:36 +01:00
{
if (!selection()->element() || m_gripEditData.grip.empty()) {
return false;
}
qreal align = m_gripEditData.grip[0].width() / 2;
for (int i = 0; i < m_gripEditData.grips; ++i) {
if (m_gripEditData.grip[i].adjusted(-align, -align, align, align).contains(pos)) {
return true;
}
}
return false;
}
2021-06-07 19:25:41 +02:00
void NotationInteraction::startEditGrip(const PointF& pos)
2021-01-28 12:27:36 +01:00
{
if (m_gripEditData.grip.size() == 0) {
return;
}
const qreal align = m_gripEditData.grip[0].width() / 2;
for (int i = 0; i < m_gripEditData.grips; ++i) {
if (!m_gripEditData.grip[i].adjusted(-align, -align, align, align).contains(pos)) {
continue;
}
m_gripEditData.curGrip = Ms::Grip(i);
2021-06-07 19:25:41 +02:00
std::vector<LineF> lines;
QVector<LineF> anchorLines = m_gripEditData.element->gripAnchorLines(m_gripEditData.curGrip);
2021-01-28 12:27:36 +01:00
Element* page = m_gripEditData.element->findAncestor(ElementType::PAGE);
2021-06-07 19:25:41 +02:00
const PointF pageOffset((page ? page : m_gripEditData.element)->pos());
2021-01-28 12:27:36 +01:00
if (!anchorLines.isEmpty()) {
2021-06-07 19:25:41 +02:00
for (LineF& line : anchorLines) {
2021-01-28 12:27:36 +01:00
line.translate(pageOffset);
2021-06-07 19:25:41 +02:00
lines.push_back(line);
2021-01-28 12:27:36 +01:00
}
}
setAnchorLines(lines);
m_gripEditData.element->startEdit(m_gripEditData);
notifyAboutNotationChanged();
return;
}
}
void NotationInteraction::endEditGrip()
{
if (!m_gripEditData.element) {
return;
}
m_gripEditData.curGrip = Ms::Grip::NO_GRIP;
resetAnchorLines();
notifyAboutNotationChanged();
}
2020-11-06 13:50:21 +01:00
void NotationInteraction::splitSelectedMeasure()
{
Element* selectedElement = m_selection->element();
if (!selectedElement) {
return;
}
if (!selectedElement->isNote() && !selectedElement->isRest()) {
return;
}
if (selectedElement->isNote()) {
selectedElement = dynamic_cast<Note*>(selectedElement)->chord();
}
ChordRest* chordRest = dynamic_cast<ChordRest*>(selectedElement);
startEdit();
2020-11-06 13:50:21 +01:00
score()->cmdSplitMeasure(chordRest);
apply();
2020-11-06 13:50:21 +01:00
notifyAboutSelectionChanged();
2020-11-06 13:50:21 +01:00
}
void NotationInteraction::joinSelectedMeasures()
{
if (!m_selection->isRange()) {
return;
}
Measure* measureStart = score()->crMeasure(m_selection->range()->startMeasureIndex() - 1);
Measure* measureEnd = score()->crMeasure(m_selection->range()->endMeasureIndex() - 1);
startEdit();
2020-11-06 13:50:21 +01:00
score()->cmdJoinMeasure(measureStart, measureEnd);
apply();
2020-11-06 13:50:21 +01:00
notifyAboutSelectionChanged();
2020-11-06 13:50:21 +01:00
}
2020-11-27 12:47:39 +01:00
void NotationInteraction::addBoxes(BoxType boxType, int count, int beforeBoxIndex)
2020-11-06 13:40:25 +01:00
{
2020-11-27 12:47:39 +01:00
auto boxTypeToElementType = [](BoxType boxType) {
2020-12-23 10:26:43 +01:00
switch (boxType) {
case BoxType::Horizontal: return Ms::ElementType::HBOX;
case BoxType::Vertical: return Ms::ElementType::VBOX;
case BoxType::Text: return Ms::ElementType::TBOX;
case BoxType::Measure: return Ms::ElementType::MEASURE;
case BoxType::Unknown: return Ms::ElementType::INVALID;
}
return ElementType::INVALID;
};
2020-11-06 13:40:25 +01:00
2020-12-15 14:56:47 +01:00
Ms::ElementType elementType = boxTypeToElementType(boxType);
Ms::MeasureBase* beforeBox = beforeBoxIndex >= 0 ? score()->measure(beforeBoxIndex) : nullptr;
2020-11-06 13:40:25 +01:00
startEdit();
2020-11-27 12:47:39 +01:00
for (int i = 0; i < count; ++i) {
constexpr bool createEmptyMeasures = false;
constexpr bool moveSignaturesClef = true;
constexpr bool needDeselectAll = false;
2020-11-06 13:40:25 +01:00
2020-11-27 12:47:39 +01:00
score()->insertMeasure(elementType, beforeBox, createEmptyMeasures, moveSignaturesClef, needDeselectAll);
2020-11-06 13:40:25 +01:00
}
apply();
2020-11-06 13:40:25 +01:00
notifyAboutNotationChanged();
2020-11-06 13:40:25 +01:00
}
2020-11-10 08:03:27 +01:00
void NotationInteraction::copySelection()
{
if (!selection()->canCopy()) {
return;
}
if (isTextEditingStarted()) {
m_textEditData.element->editCopy(m_textEditData);
} else {
QMimeData* mimeData = selection()->mimeData();
if (!mimeData) {
return;
}
QApplication::clipboard()->setMimeData(mimeData);
2020-11-10 08:03:27 +01:00
}
}
void NotationInteraction::copyLyrics()
{
QString text = score()->extractLyrics();
QApplication::clipboard()->setText(text);
}
void NotationInteraction::pasteSelection(const Fraction& scale)
2020-11-10 08:03:27 +01:00
{
startEdit();
2020-11-10 08:03:27 +01:00
if (isTextEditingStarted()) {
toTextBase(m_textEditData.element)->paste(m_textEditData);
} else {
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
score()->cmdPaste(mimeData, nullptr, scale);
}
apply();
2020-11-10 08:03:27 +01:00
notifyAboutSelectionChanged();
2020-11-10 08:03:27 +01:00
}
void NotationInteraction::swapSelection()
{
if (!selection()->canCopy()) {
return;
}
Ms::Selection& selection = score()->selection();
QString mimeType = selection.mimeType();
2020-12-15 14:56:47 +01:00
if (mimeType == Ms::mimeStaffListFormat) { // determine size of clipboard selection
const QMimeData* mimeData = this->selection()->mimeData();
2020-12-15 14:56:47 +01:00
QByteArray data = mimeData ? mimeData->data(Ms::mimeStaffListFormat) : QByteArray();
Ms::XmlReader reader(data);
reader.readNextStartElement();
Fraction tickLen = Fraction(0, 1);
int stavesCount = 0;
if (reader.name() == "StaffList") {
2020-12-15 14:56:47 +01:00
tickLen = Ms::Fraction::fromTicks(reader.intAttribute("len", 0));
stavesCount = reader.intAttribute("staves", 0);
}
2020-12-15 14:56:47 +01:00
if (tickLen > Ms::Fraction(0, 1)) { // attempt to extend selection to match clipboard size
Ms::Segment* segment = selection.startSegment();
Ms::Fraction startTick = selection.tickStart() + tickLen;
Ms::Segment* segmentAfter = score()->tick2leftSegment(startTick);
int staffIndex = selection.staffStart() + stavesCount - 1;
if (staffIndex >= score()->nstaves()) {
staffIndex = score()->nstaves() - 1;
}
startTick = selection.tickStart();
2020-12-15 14:56:47 +01:00
Ms::Fraction endTick = startTick + tickLen;
selection.extendRangeSelection(segment, segmentAfter, staffIndex, startTick, endTick);
selection.update();
}
}
QByteArray currentSelectionBackup(selection.mimeData());
pasteSelection();
QMimeData* mimeData = new QMimeData();
mimeData->setData(mimeType, currentSelectionBackup);
QApplication::clipboard()->setMimeData(mimeData);
}
2020-10-13 14:06:26 +02:00
void NotationInteraction::deleteSelection()
{
2020-12-23 10:56:45 +01:00
if (selection()->isNone()) {
return;
}
startEdit();
if (isTextEditingStarted()) {
auto textBase = toTextBase(m_textEditData.element);
if (!textBase->deleteSelectedText(m_textEditData)) {
m_textEditData.key = Qt::Key_Backspace;
m_textEditData.modifiers = {};
textBase->edit(m_textEditData);
}
} else {
score()->cmdDeleteSelection();
}
apply();
2020-10-13 14:06:26 +02:00
notifyAboutSelectionChanged();
2020-10-13 14:06:26 +02:00
}
2020-11-06 09:58:13 +01:00
2020-11-25 14:36:39 +01:00
void NotationInteraction::flipSelection()
{
2020-12-23 10:56:45 +01:00
if (selection()->isNone()) {
return;
}
startEdit();
2020-11-25 14:36:39 +01:00
score()->cmdFlip();
apply();
2020-11-25 14:36:39 +01:00
notifyAboutSelectionChanged();
2020-11-25 14:36:39 +01:00
}
2020-11-25 19:03:54 +01:00
void NotationInteraction::addTieToSelection()
{
startEdit();
score()->cmdToggleTie();
apply();
2020-11-25 19:03:54 +01:00
notifyAboutSelectionChanged();
2020-11-25 19:03:54 +01:00
}
2021-04-17 00:49:49 +02:00
void NotationInteraction::addTiedNoteToChord()
{
startEdit();
score()->cmdAddTie(true);
apply();
notifyAboutSelectionChanged();
}
2020-11-25 19:03:54 +01:00
void NotationInteraction::addSlurToSelection()
{
2020-12-23 10:56:45 +01:00
if (selection()->isNone()) {
return;
}
startEdit();
2020-11-25 19:03:54 +01:00
doAddSlur();
apply();
2020-11-25 19:03:54 +01:00
notifyAboutSelectionChanged();
2020-11-25 19:03:54 +01:00
}
2020-11-30 12:38:33 +01:00
void NotationInteraction::addOttavaToSelection(OttavaType type)
{
2020-12-23 10:56:45 +01:00
if (selection()->isNone()) {
return;
}
2020-11-30 12:38:33 +01:00
startEdit();
score()->cmdAddOttava(type);
apply();
notifyAboutSelectionChanged();
}
void NotationInteraction::addHairpinToSelection(HairpinType type)
{
2020-12-23 10:56:45 +01:00
if (selection()->isNone()) {
return;
}
2020-11-30 12:38:33 +01:00
startEdit();
score()->addHairpin(type);
apply();
notifyAboutSelectionChanged();
}
2020-12-09 11:28:49 +01:00
void NotationInteraction::addAccidentalToSelection(AccidentalType type)
{
2020-12-23 10:56:45 +01:00
if (selection()->isNone()) {
return;
}
Ms::EditData editData(&m_scoreCallbacks);
2020-12-09 11:28:49 +01:00
startEdit();
score()->toggleAccidental(type, editData);
apply();
notifyAboutSelectionChanged();
}
2021-04-17 00:50:18 +02:00
void NotationInteraction::putRestToSelection()
{
Ms::InputState& is = score()->inputState();
2021-04-17 00:51:20 +02:00
if (!is.duration().isValid() || is.duration().isZero() || is.duration().isMeasure()) {
2021-04-17 00:50:18 +02:00
is.setDuration(DurationType::V_QUARTER);
2021-04-17 00:51:20 +02:00
}
2021-04-17 00:50:18 +02:00
putRest(is.duration().type());
}
void NotationInteraction::putRest(DurationType duration)
{
if (selection()->isNone()) {
return;
}
startEdit();
score()->cmdEnterRest(Duration(duration));
apply();
notifyAboutSelectionChanged();
}
void NotationInteraction::addBracketsToSelection(BracketsType type)
{
if (selection()->isNone()) {
return;
}
startEdit();
switch (type) {
case BracketsType::Brackets:
score()->cmdAddBracket();
break;
case BracketsType::Braces:
score()->cmdAddBraces();
break;
case BracketsType::Parentheses:
score()->cmdAddParentheses();
break;
}
apply();
notifyAboutNotationChanged();
}
2020-12-01 15:19:42 +01:00
void NotationInteraction::changeSelectedNotesArticulation(SymbolId articulationSymbolId)
{
2020-12-23 10:56:45 +01:00
if (selection()->isNone()) {
return;
}
2020-12-15 14:56:47 +01:00
std::vector<Ms::Note*> notes = score()->selection().noteList();
2020-12-01 15:19:42 +01:00
2020-12-14 16:07:33 +01:00
auto updateMode = notesHaveActiculation(notes, articulationSymbolId)
? Ms::ArticulationsUpdateMode::Remove : Ms::ArticulationsUpdateMode::Insert;
2020-12-14 16:07:33 +01:00
std::set<Chord*> chords;
for (Note* note: notes) {
Chord* chord = note->chord();
if (chords.find(chord) == chords.end()) {
chords.insert(chord);
}
}
startEdit();
2020-12-14 16:07:33 +01:00
for (Chord* chord: chords) {
chord->updateArticulations({ articulationSymbolId }, updateMode);
2020-12-01 15:19:42 +01:00
}
apply();
notifyAboutSelectionChanged();
}
2021-03-03 14:11:21 +01:00
void NotationInteraction::addGraceNotesToSelectedNotes(GraceNoteType type)
2020-12-01 19:11:36 +01:00
{
2020-12-23 10:56:45 +01:00
if (selection()->isNone()) {
return;
}
int denominator = 1;
switch (type) {
case GraceNoteType::GRACE4:
case GraceNoteType::INVALID:
case GraceNoteType::NORMAL:
denominator = 1;
break;
case GraceNoteType::ACCIACCATURA:
case GraceNoteType::APPOGGIATURA:
case GraceNoteType::GRACE8_AFTER:
denominator = 2;
break;
case GraceNoteType::GRACE16:
case GraceNoteType::GRACE16_AFTER:
denominator = 4;
break;
case GraceNoteType::GRACE32:
case GraceNoteType::GRACE32_AFTER:
denominator = 8;
break;
}
startEdit();
score()->cmdAddGrace(type, Ms::MScore::division / denominator);
apply();
notifyAboutNotationChanged();
}
2021-03-03 14:11:21 +01:00
void NotationInteraction::addTupletToSelectedChordRests(const TupletOptions& options)
{
if (selection()->isNone()) {
return;
}
startEdit();
for (ChordRest* chordRest : score()->getSelectedChordRests()) {
if (!chordRest->isGrace()) {
score()->addTuplet(chordRest, options.ratio, options.numberType, options.bracketType);
}
}
apply();
notifyAboutSelectionChanged();
}
void NotationInteraction::addBeamToSelectedChordRests(BeamMode mode)
{
if (selection()->isNone()) {
return;
}
startEdit();
score()->cmdSetBeamMode(mode);
apply();
notifyAboutNotationChanged();
}
void NotationInteraction::increaseDecreaseDuration(int steps, bool stepByDots)
{
if (selection()->isNone()) {
return;
}
startEdit();
score()->cmdIncDecDuration(steps, stepByDots);
apply();
notifyAboutNotationChanged();
}
void NotationInteraction::toggleLayoutBreak(LayoutBreakType breakType)
{
startEdit();
score()->cmdToggleLayoutBreak(breakType);
apply();
notifyAboutNotationChanged();
}
2020-11-06 09:58:13 +01:00
void NotationInteraction::setBreaksSpawnInterval(BreaksSpawnIntervalType intervalType, int interval)
{
interval = intervalType == BreaksSpawnIntervalType::MeasuresInterval ? interval : 0;
bool afterEachSystem = intervalType == BreaksSpawnIntervalType::AfterEachSystem;
startEdit();
2020-11-06 09:58:13 +01:00
score()->addRemoveBreaks(interval, afterEachSystem);
apply();
2020-11-06 09:58:13 +01:00
notifyAboutNotationChanged();
2020-11-06 09:58:13 +01:00
}
bool NotationInteraction::transpose(const TransposeOptions& options)
{
startEdit();
bool ok = score()->transpose(options.mode, options.direction, options.key, options.interval,
options.needTransposeKeys, options.needTransposeChordNames, options.needTransposeDoubleSharpsFlats);
apply();
notifyAboutNotationChanged();
return ok;
}
2020-11-03 17:54:25 +01:00
void NotationInteraction::swapVoices(int voiceIndex1, int voiceIndex2)
{
2020-12-23 10:56:45 +01:00
if (selection()->isNone()) {
return;
}
2020-11-03 17:54:25 +01:00
if (voiceIndex1 == voiceIndex2) {
return;
}
if (!isVoiceIndexValid(voiceIndex1) || !isVoiceIndexValid(voiceIndex2)) {
return;
}
startEdit();
2020-11-03 17:54:25 +01:00
score()->cmdExchangeVoice(voiceIndex1, voiceIndex2);
apply();
2020-11-03 17:54:25 +01:00
notifyAboutNotationChanged();
2020-11-03 17:54:25 +01:00
}
void NotationInteraction::addIntervalToSelectedNotes(int interval)
{
if (!isNotesIntervalValid(interval)) {
return;
}
std::vector<Note*> notes;
if (score()->selection().isRange()) {
for (const ChordRest* chordRest : score()->getSelectedChordRests()) {
if (chordRest->isChord()) {
const Chord* chord = toChord(chordRest);
Note* note = interval > 0 ? chord->upNote() : chord->downNote();
notes.push_back(note);
}
}
} else {
notes = score()->selection().noteList();
}
if (notes.empty()) {
return;
}
startEdit();
score()->addInterval(interval, notes);
apply();
2021-06-22 19:57:53 +02:00
notifyAboutSelectionChanged();
}
void NotationInteraction::addFret(int fretIndex)
{
startEdit();
score()->cmdAddFret(fretIndex);
apply();
notifyAboutSelectionChanged();
}
2020-11-30 10:19:07 +01:00
void NotationInteraction::changeSelectedNotesVoice(int voiceIndex)
{
2020-12-23 10:56:45 +01:00
if (selection()->isNone()) {
return;
}
2020-11-30 10:19:07 +01:00
if (!isVoiceIndexValid(voiceIndex)) {
return;
}
startEdit();
2020-11-30 10:19:07 +01:00
score()->changeSelectedNotesVoice(voiceIndex);
apply();
2020-11-30 10:19:07 +01:00
notifyAboutSelectionChanged();
2020-11-30 10:19:07 +01:00
}
2020-11-30 12:38:33 +01:00
void NotationInteraction::addAnchoredLineToSelectedNotes()
2020-11-30 09:34:19 +01:00
{
2020-12-23 10:56:45 +01:00
if (selection()->isNone()) {
return;
}
startEdit();
2020-11-30 12:38:33 +01:00
score()->addNoteLine();
apply();
2020-11-30 09:34:19 +01:00
notifyAboutSelectionChanged();
2020-11-03 17:54:25 +01:00
}
2020-12-08 19:37:10 +01:00
void NotationInteraction::addText(TextType type)
{
if (!scoreHasMeasure()) {
LOGE() << "Need to create measure";
return;
}
if (m_noteInput->isNoteInputMode()) {
m_noteInput->endNoteInput();
}
startEdit();
2020-12-15 14:56:47 +01:00
Ms::TextBase* textBox = score()->addText(type);
2020-12-08 19:37:10 +01:00
apply();
if (textBox) {
select({ textBox }, SelectType::SINGLE);
2021-06-07 19:25:41 +02:00
startEditText(textBox, PointF());
2020-12-08 19:37:10 +01:00
}
notifyAboutSelectionChanged();
}
2021-02-10 14:24:56 +01:00
void NotationInteraction::addFiguredBass()
{
Ms::FiguredBass* figuredBass = score()->addFiguredBass();
2021-02-10 14:24:56 +01:00
if (figuredBass) {
2021-06-07 19:25:41 +02:00
startEditText(figuredBass, PointF());
2021-02-10 14:24:56 +01:00
}
notifyAboutSelectionChanged();
}
void NotationInteraction::addStretch(qreal value)
{
startEdit();
score()->cmdAddStretch(value);
apply();
notifyAboutNotationChanged();
}
2021-02-25 13:27:58 +01:00
void NotationInteraction::explodeSelectedStaff()
{
if (!selection()->isRange()) {
return;
}
startEdit();
score()->cmdExplode();
apply();
notifyAboutNotationChanged();
}
void NotationInteraction::implodeSelectedStaff()
{
if (!selection()->isRange()) {
return;
}
startEdit();
score()->cmdImplode();
apply();
notifyAboutNotationChanged();
}
void NotationInteraction::realizeSelectedChordSymbols()
{
if (selection()->isNone()) {
return;
}
startEdit();
score()->cmdRealizeChordSymbols();
apply();
notifyAboutNotationChanged();
}
2021-02-25 14:11:25 +01:00
void NotationInteraction::removeSelectedRange()
{
if (selection()->isNone()) {
return;
}
startEdit();
score()->cmdTimeDelete();
apply();
notifyAboutNotationChanged();
}
2021-02-25 15:44:49 +01:00
void NotationInteraction::removeEmptyTrailingMeasures()
{
startEdit();
score()->cmdRemoveEmptyTrailingMeasures();
apply();
notifyAboutNotationChanged();
}
void NotationInteraction::fillSelectionWithSlashes()
{
if (selection()->isNone()) {
return;
}
startEdit();
score()->cmdSlashFill();
apply();
notifyAboutNotationChanged();
}
void NotationInteraction::replaceSelectedNotesWithSlashes()
{
if (selection()->isNone()) {
return;
}
startEdit();
score()->cmdSlashRhythm();
apply();
notifyAboutNotationChanged();
}
2021-02-25 14:56:02 +01:00
void NotationInteraction::spellPitches()
{
startEdit();
score()->spell();
apply();
notifyAboutNotationChanged();
}
2021-02-25 15:07:16 +01:00
void NotationInteraction::regroupNotesAndRests()
{
startEdit();
score()->cmdResetNoteAndRestGroupings();
apply();
notifyAboutNotationChanged();
}
void NotationInteraction::resequenceRehearsalMarks()
{
startEdit();
score()->cmdResequenceRehearsalMarks();
apply();
notifyAboutNotationChanged();
}
2021-02-25 15:22:49 +01:00
void NotationInteraction::unrollRepeats()
{
if (!score()->masterScore()) {
return;
}
startEdit();
score()->masterScore()->unrollRepeats();
apply();
notifyAboutNotationChanged();
}
2021-02-12 10:11:05 +01:00
void NotationInteraction::resetToDefault(ResettableValueType type)
{
2021-02-12 10:11:05 +01:00
switch (type) {
case ResettableValueType::Stretch:
resetStretch();
break;
case ResettableValueType::BeamMode:
resetBeamMode();
break;
case ResettableValueType::ShapesAndPosition:
resetShapesAndPosition();
break;
case ResettableValueType::TextStyleOverriders:
resetTextStyleOverrides();
break;
}
}
ScoreConfig NotationInteraction::scoreConfig() const
{
ScoreConfig config;
config.isShowInvisibleElements = score()->showInvisible();
config.isShowUnprintableElements = score()->showUnprintable();
config.isShowFrames = score()->showFrames();
config.isShowPageMargins = score()->showPageborders();
config.isMarkIrregularMeasures = score()->markIrregularMeasures();
return config;
}
void NotationInteraction::setScoreConfig(ScoreConfig config)
{
startEdit();
score()->setShowInvisible(config.isShowInvisibleElements);
score()->setShowUnprintable(config.isShowUnprintableElements);
score()->setShowFrames(config.isShowFrames);
score()->setShowPageborders(config.isShowPageMargins);
score()->setMarkIrregularMeasures(config.isMarkIrregularMeasures);
Element* selectedElement = selection()->element();
2021-02-24 15:48:08 +01:00
if (selectedElement && !selectedElement->isInteractionAvailable()) {
clearSelection();
}
apply();
notifyAboutNotationChanged();
}
bool NotationInteraction::needEndTextEditing(const std::vector<Element*>& newSelectedElements) const
{
if (!isTextEditingStarted()) {
return false;
}
if (newSelectedElements.empty()) {
return false;
}
if (newSelectedElements.size() > 1) {
return true;
}
return newSelectedElements.front() != m_textEditData.element;
}
2021-01-28 12:27:36 +01:00
void NotationInteraction::updateGripEdit(const std::vector<Element*>& elements)
{
if (elements.size() > 1) {
resetGripEdit();
return;
}
Element* element = elements.front();
if (element->gripsCount() <= 0) {
resetGripEdit();
return;
}
m_gripEditData.grips = element->gripsCount();
m_gripEditData.curGrip = Ms::Grip::NO_GRIP;
m_gripEditData.element = element;
m_gripEditData.grip.resize(m_gripEditData.grips);
m_gripEditData.element->startEdit(m_gripEditData);
m_gripEditData.element->updateGrips(m_gripEditData);
resetAnchorLines();
}
void NotationInteraction::resetGripEdit()
{
m_gripEditData.grips = 0;
m_gripEditData.curGrip = Ms::Grip::NO_GRIP;
m_gripEditData.element = nullptr;
m_gripEditData.grip.clear();
resetAnchorLines();
}
2021-02-12 10:11:05 +01:00
void NotationInteraction::resetStretch()
{
startEdit();
score()->resetUserStretch();
apply();
notifyAboutNotationChanged();
}
void NotationInteraction::resetTextStyleOverrides()
{
startEdit();
score()->cmdResetTextStyleOverrides();
apply();
notifyAboutNotationChanged();
}
void NotationInteraction::resetBeamMode()
{
startEdit();
score()->cmdResetBeamMode();
apply();
notifyAboutNotationChanged();
}
void NotationInteraction::resetShapesAndPosition()
{
startEdit();
if (selection()->element()) {
clearSelection();
return;
}
for (Element* element : selection()->elements()) {
element->reset();
if (!element->isSpanner()) {
continue;
}
Ms::Spanner* spanner = toSpanner(element);
for (Ms::SpannerSegment* spannerSegment : spanner->spannerSegments()) {
spannerSegment->reset();
}
}
apply();
notifyAboutNotationChanged();
}