Add a way to handle dragging elements collectively

Also draw anchor lines for multiple selected elements on dragging
This commit is contained in:
Dmitri Ovodok 2020-02-20 11:57:55 +02:00
parent ad6c8581d7
commit 55230ca86d
12 changed files with 439 additions and 25 deletions

View file

@ -62,8 +62,8 @@ add_library (
fermata.cpp articulation.cpp barline.cpp beam.cpp bend.cpp box.cpp
bracket.cpp breath.cpp bsp.cpp changeMap.cpp chord.cpp chordline.cpp
chordlist.cpp chordrest.cpp clef.cpp cleflist.cpp
drumset.cpp durationtype.cpp dynamic.cpp edit.cpp noteentry.cpp
element.cpp excerpt.cpp
drumset.cpp durationtype.cpp dynamic.cpp dynamichairpingroup.cpp edit.cpp noteentry.cpp
element.cpp elementgroup.cpp excerpt.cpp
fifo.cpp fret.cpp glissando.cpp hairpin.cpp
harmony.cpp hook.cpp image.cpp iname.cpp instrchange.cpp
instrtemplate.cpp instrument.cpp interval.cpp

View file

@ -11,6 +11,7 @@
//=============================================================================
#include "dynamic.h"
#include "dynamichairpingroup.h"
#include "xml.h"
#include "score.h"
#include "measure.h"
@ -344,6 +345,19 @@ void Dynamic::reset()
TextBase::reset();
}
//---------------------------------------------------------
// getDragGroup
//---------------------------------------------------------
std::unique_ptr<ElementGroup> Dynamic::getDragGroup(std::function<bool(const Element*)> isDragged)
{
if (auto g = HairpinWithDynamicsDragGroup::detectFor(this, isDragged))
return g;
if (auto g = DynamicNearHairpinsDragGroup::detectFor(this, isDragged))
return g;
return TextBase::getDragGroup(isDragged);
}
//---------------------------------------------------------
// drag
//---------------------------------------------------------

View file

@ -137,6 +137,8 @@ class Dynamic final : public TextBase {
Pid propertyId(const QStringRef& xmlName) const override;
QString propertyUserValue(Pid) const override;
std::unique_ptr<ElementGroup> getDragGroup(std::function<bool(const Element*)> isDragged) override;
QString accessibleInfo() const override;
QString screenReaderInfo() const override;
void doAutoplace();

View file

@ -0,0 +1,190 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA
//
// 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 "dynamichairpingroup.h"
#include "dynamic.h"
#include "hairpin.h"
#include "score.h"
#include "segment.h"
namespace Ms {
static std::pair<Hairpin*, Hairpin*> findAdjacentHairpins(Dynamic* d)
{
Score* score = d->score();
const Segment* dSeg = d->segment();
Hairpin* leftHairpin = nullptr;
Hairpin* rightHairpin = nullptr;
const Fraction tick = dSeg->tick();
const int intTick = tick.ticks();
const auto& nearSpanners = score->spannerMap().findOverlapping(intTick - 1, intTick + 1);
for (auto i : nearSpanners) {
Spanner* s = i.value;
if (s->track() == d->track() && s->isHairpin()) {
Hairpin* h = toHairpin(s);
if (h->tick() == tick)
rightHairpin = h;
else if (h->tick2() == tick)
leftHairpin = h;
}
}
return { leftHairpin, rightHairpin };
}
std::unique_ptr<ElementGroup> HairpinWithDynamicsDragGroup::detectFor(HairpinSegment* hs, std::function<bool(const Element*)> isDragged)
{
if (!hs->isSingleType())
return nullptr;
Hairpin* hairpin = hs->hairpin();
Segment* startSegment = hairpin->startSegment();
Segment* endSegment = hairpin->endSegment();
const int track = hs->track();
Dynamic* startDynamic = toDynamic(startSegment->findAnnotation(ElementType::DYNAMIC, track, track));
Dynamic* endDynamic = toDynamic(endSegment->findAnnotation(ElementType::DYNAMIC, track, track));
// Include only dragged dynamics to this group
if (!isDragged(startDynamic))
startDynamic = nullptr;
if (!isDragged(endDynamic))
endDynamic = nullptr;
if (startDynamic || endDynamic)
return std::unique_ptr<ElementGroup>(new HairpinWithDynamicsDragGroup(startDynamic, hs, endDynamic));
return nullptr;
}
std::unique_ptr<ElementGroup> HairpinWithDynamicsDragGroup::detectFor(Dynamic* d, std::function<bool(const Element*)> isDragged)
{
Hairpin* leftHairpin = nullptr;
Hairpin* rightHairpin = nullptr;
std::tie(leftHairpin, rightHairpin) = findAdjacentHairpins(d);
// Dynamic will be governed bt HairpinWithDynamicsDragGroup if any of adjacent
// hairpins is dragged, disable separate drag logic for dynamic in this case.
if (isDragged(leftHairpin))
return std::unique_ptr<ElementGroup>(new DisabledElementGroup());
if (isDragged(rightHairpin))
return std::unique_ptr<ElementGroup>(new DisabledElementGroup());
return nullptr;
}
void HairpinWithDynamicsDragGroup::startDrag(EditData& ed)
{
if (startDynamic)
startDynamic->startDrag(ed);
static_cast<Element*>(hairpinSegment)->startDrag(ed);
if (endDynamic)
endDynamic->startDrag(ed);
}
QRectF HairpinWithDynamicsDragGroup::drag(EditData& ed)
{
QRectF r;
if (startDynamic)
r |= static_cast<Element*>(startDynamic)->drag(ed);
r |= hairpinSegment->drag(ed);
if (endDynamic)
r |= static_cast<Element*>(endDynamic)->drag(ed);
Hairpin* h = hairpinSegment->hairpin();
const Fraction startTick = startDynamic ? startDynamic->segment()->tick() : h->tick();
const Fraction endTick = endDynamic ? endDynamic->segment()->tick() : h->tick2();
if (endTick > startTick) {
if (h->tick() != startTick)
h->undoChangeProperty(Pid::SPANNER_TICK, startTick);
if (h->tick2() != endTick)
h->undoChangeProperty(Pid::SPANNER_TICKS, endTick - startTick);
}
return r;
}
void HairpinWithDynamicsDragGroup::endDrag(EditData& ed)
{
if (startDynamic) {
startDynamic->endDrag(ed);
startDynamic->triggerLayout();
}
hairpinSegment->endDrag(ed);
hairpinSegment->triggerLayout();
if (endDynamic) {
endDynamic->endDrag(ed);
endDynamic->triggerLayout();
}
}
std::unique_ptr<ElementGroup> DynamicNearHairpinsDragGroup::detectFor(Dynamic* d, std::function<bool(const Element*)> isDragged)
{
Hairpin* leftHairpin = nullptr;
Hairpin* rightHairpin = nullptr;
std::tie(leftHairpin, rightHairpin) = findAdjacentHairpins(d);
// Drag hairpins according to this rule only if they are not being dragged themselves
if (isDragged(leftHairpin))
leftHairpin = nullptr;
if (isDragged(rightHairpin))
rightHairpin = nullptr;
if (leftHairpin || rightHairpin)
return std::unique_ptr<ElementGroup>(new DynamicNearHairpinsDragGroup(leftHairpin, d, rightHairpin));
return nullptr;
}
void DynamicNearHairpinsDragGroup::startDrag(EditData& ed)
{
dynamic->startDrag(ed);
}
QRectF DynamicNearHairpinsDragGroup::drag(EditData& ed)
{
QRectF r(static_cast<Element*>(dynamic)->drag(ed));
const Fraction tick = dynamic->segment()->tick();
if (leftHairpin && leftHairpin->tick2() != tick && tick > leftHairpin->tick())
leftHairpin->undoChangeProperty(Pid::SPANNER_TICKS, tick - leftHairpin->tick());
if (rightHairpin && rightHairpin->tick() != tick) {
const Fraction tick2 = rightHairpin->tick2();
if (tick < tick2) {
rightHairpin->undoChangeProperty(Pid::SPANNER_TICK, tick);
rightHairpin->undoChangeProperty(Pid::SPANNER_TICKS, tick2 - tick);
}
}
if (leftHairpin || rightHairpin)
dynamic->triggerLayout();
return r;
}
void DynamicNearHairpinsDragGroup::endDrag(EditData& ed)
{
dynamic->endDrag(ed);
dynamic->triggerLayout();
}
} // namespace Ms

View file

@ -0,0 +1,68 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA
//
// 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
//=============================================================================
#ifndef __DYNAMICHAIPRINGROUP_H__
#define __DYNAMICHAIRPINGROUP_H__
#include "elementgroup.h"
namespace Ms {
class Dynamic;
class Hairpin;
class HairpinSegment;
//-------------------------------------------------------------------
// HairpinWithDynamicsDragGroup
/// Sequence of Dynamics and Hairpins
//-------------------------------------------------------------------
class HairpinWithDynamicsDragGroup : public ElementGroup {
Dynamic* startDynamic;
HairpinSegment* hairpinSegment;
Dynamic* endDynamic;
public:
HairpinWithDynamicsDragGroup(Dynamic* start, HairpinSegment* hs, Dynamic* end)
: startDynamic(start), hairpinSegment(hs), endDynamic(end) {}
void startDrag(EditData&) override;
QRectF drag(EditData&) override;
void endDrag(EditData&) override;
static std::unique_ptr<ElementGroup> detectFor(HairpinSegment* hs, std::function<bool(const Element*)> isDragged);
static std::unique_ptr<ElementGroup> detectFor(Dynamic* d, std::function<bool(const Element*)> isDragged);
};
//-------------------------------------------------------------------
// DynamicNearHairpinsDragGroup
//-------------------------------------------------------------------
class DynamicNearHairpinsDragGroup : public ElementGroup {
Hairpin* leftHairpin;
Dynamic* dynamic;
Hairpin* rightHairpin;
public:
DynamicNearHairpinsDragGroup(Hairpin* left, Dynamic* d, Hairpin* right)
: leftHairpin(left), dynamic(d), rightHairpin(right) {}
void startDrag(EditData&) override;
QRectF drag(EditData&) override;
void endDrag(EditData&) override;
static std::unique_ptr<ElementGroup> detectFor(Dynamic* d, std::function<bool(const Element*)> isDragged);
};
} // namespace Ms
#endif

View file

@ -13,6 +13,7 @@
#ifndef __ELEMENT_H__
#define __ELEMENT_H__
#include "elementgroup.h"
#include "spatium.h"
#include "fraction.h"
#include "scoreElement.h"
@ -296,6 +297,9 @@ class Element : public ScoreElement {
virtual void write(XmlWriter&) const;
virtual void read(XmlReader&);
// virtual ElementGroup getElementGroup() { return SingleElementGroup(this); }
virtual std::unique_ptr<ElementGroup> getDragGroup(std::function<bool(const Element*)> isDragged) { Q_UNUSED(isDragged); return std::unique_ptr<ElementGroup>(new SingleElementGroup(this)); }
virtual void startDrag(EditData&);
virtual QRectF drag(EditData&);
virtual void endDrag(EditData&);

View file

@ -0,0 +1,34 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA
//
// 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 "elementgroup.h"
#include "element.h"
namespace Ms {
void SingleElementGroup::startDrag(EditData& ed)
{
e->startDrag(ed);
}
QRectF SingleElementGroup::drag(EditData& ed)
{
return e->drag(ed);
}
void SingleElementGroup::endDrag(EditData& ed)
{
e->endDrag(ed);
e->triggerLayout();
}
} // namespace Ms

68
libmscore/elementgroup.h Normal file
View file

@ -0,0 +1,68 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA
//
// 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
//=============================================================================
#ifndef __ELEMENTGROUP_H__
#define __ELEMENTGROUP_H__
namespace Ms {
class Element;
class EditData;
//-------------------------------------------------------------------
// ElementGroup
/// Base class for implementing logic to handle groups of elements
/// together in certain operations.
//-------------------------------------------------------------------
class ElementGroup {
public:
virtual ~ElementGroup() {}
virtual void startDrag(EditData&) = 0;
virtual QRectF drag(EditData&) = 0;
virtual void endDrag(EditData&) = 0;
virtual bool enabled() const { return true; }
};
//-------------------------------------------------------------------
// DisabledElementGroup
//-------------------------------------------------------------------
class DisabledElementGroup final : public ElementGroup {
public:
bool enabled() const override { return false; }
void startDrag(EditData&) override {}
QRectF drag(EditData&) override { return QRectF(); }
void endDrag(EditData&) override {}
};
//-------------------------------------------------------------------
// SingleElementGroup
/// Element group for single element.
//-------------------------------------------------------------------
class SingleElementGroup final : public ElementGroup {
Element* e;
public:
SingleElementGroup(Element* el) : e(el) {}
void startDrag(EditData& ed) override;
QRectF drag(EditData& ed) override;
void endDrag(EditData& ed) override;
};
} // namespace Ms
#endif

View file

@ -11,6 +11,7 @@
//=============================================================================
#include "hairpin.h"
#include "dynamichairpingroup.h"
#include "style.h"
#include "xml.h"
#include "utils.h"
@ -400,6 +401,17 @@ std::vector<QPointF> HairpinSegment::gripsPositions(const EditData&) const
return grips;
}
//---------------------------------------------------------
// getDragGroup
//---------------------------------------------------------
std::unique_ptr<ElementGroup> HairpinSegment::getDragGroup(std::function<bool(const Element*)> isDragged)
{
if (auto g = HairpinWithDynamicsDragGroup::detectFor(this, isDragged))
return g;
return TextLineBaseSegment::getDragGroup(isDragged);
}
//---------------------------------------------------------
// startEditDrag
//---------------------------------------------------------

View file

@ -67,6 +67,8 @@ class HairpinSegment final : public TextLineBaseSegment {
int gripsCount() const override { return 4; }
std::vector<QPointF> gripsPositions(const EditData& = EditData()) const override;
std::unique_ptr<ElementGroup> getDragGroup(std::function<bool(const Element*)> isDragged) override;
};
//---------------------------------------------------------

View file

@ -33,10 +33,27 @@ void ScoreView::startDrag()
editData.clearData();
editData.normalizedStartMove = editData.startMove - editData.element->offset();
const Selection& sel = _score->selection();
const bool filterType = sel.isRange();
const ElementType type = editData.element->type();
const auto isDragged = [filterType, type](const Element* e) {
return e && e->selected() && (!filterType || type == e->type());
};
for (Element* e : sel.elements()) {
if (!isDragged(e))
continue;
std::unique_ptr<ElementGroup> g = e->getDragGroup(isDragged);
if (g && g->enabled())
dragGroups.push_back(std::move(g));
}
_score->startCmd();
for (Element* e : _score->selection().elements())
e->startDrag(editData);
for (auto& g : dragGroups)
g->startDrag(editData);
_score->selection().lock("drag");
}
@ -72,33 +89,35 @@ void ScoreView::doDragElement(QMouseEvent* ev)
editData.pos = logicalPos;
const Selection& sel = _score->selection();
const bool filterType = sel.isRange();
const ElementType type = editData.element->type();
for (auto& g : dragGroups)
_score->addRefresh(g->drag(editData));
_score->update();
QVector<QLineF> anchorLines;
for (Element* e : sel.elements()) {
if (filterType && type != e->type())
continue;
_score->addRefresh(e->drag(editData));
QVector<QLineF> elAnchorLines = e->dragAnchorLines();
const QPointF pageOffset(e->findAncestor(ElementType::PAGE)->pos());
if (!elAnchorLines.isEmpty()) {
for (QLineF& l : elAnchorLines)
l.translate(pageOffset);
anchorLines.append(elAnchorLines);
}
}
if (anchorLines.isEmpty())
setDropTarget(0); // this also resets dropAnchor
else
setDropAnchorLines(anchorLines);
Element* e = _score->getSelectedElement();
if (e) {
if (_score->playNote()) {
mscore->play(e);
_score->setPlayNote(false);
}
_score->update();
QVector<QLineF> anchorLines = e->dragAnchorLines();
const QPointF pageOffset(e->findAncestor(ElementType::PAGE)->pos());
if (!anchorLines.isEmpty()) {
for (QLineF& l : anchorLines)
l.translate(pageOffset);
setDropAnchorLines(anchorLines);
}
else
setDropTarget(0); // this also resets dropAnchor
}
updateGrips();
_score->update();
@ -110,11 +129,10 @@ void ScoreView::doDragElement(QMouseEvent* ev)
void ScoreView::endDrag()
{
for (Element* e : _score->selection().elements()) {
e->endDrag(editData);
e->triggerLayout();
}
for (auto& g : dragGroups)
g->endDrag(editData);
dragGroups.clear();
_score->selection().unlock("drag");
setDropTarget(0); // this also resets dropAnchor
_score->endCmd();

View file

@ -15,6 +15,7 @@
#include "globals.h"
#include "libmscore/element.h"
#include "libmscore/elementgroup.h"
#include "libmscore/durationtype.h"
#include "libmscore/mscore.h"
#include "libmscore/mscoreview.h"
@ -115,6 +116,7 @@ class ScoreView : public QWidget, public MuseScoreView {
QFocusFrame* focusFrame;
EditData editData;
std::vector<std::unique_ptr<ElementGroup>> dragGroups;
//--input state:
PositionCursor* _cursor;