You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
956 lines
31 KiB
956 lines
31 KiB
/*
|
|
Rosegarden
|
|
A MIDI and audio sequencer and musical notation editor.
|
|
|
|
This program is Copyright 2000-2008
|
|
Guillaume Laurent <glaurent@telegraph-road.org>,
|
|
Chris Cannam <cannam@all-day-breakfast.com>,
|
|
Richard Bown <richard.bown@ferventsoftware.com>
|
|
|
|
The moral rights of Guillaume Laurent, Chris Cannam, and Richard
|
|
Bown to claim authorship of this work have been asserted.
|
|
|
|
Other copyrights also apply to some parts of this work. Please
|
|
see the AUTHORS file and individual file headers for details.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the
|
|
License, or (at your option) any later version. See the file
|
|
COPYING included with this distribution for more information.
|
|
*/
|
|
|
|
|
|
#include "NotationSelector.h"
|
|
#include "misc/Debug.h"
|
|
|
|
#include <tdelocale.h>
|
|
#include "base/Event.h"
|
|
#include "base/NotationTypes.h"
|
|
#include "base/PropertyName.h"
|
|
#include "base/Selection.h"
|
|
#include "base/ViewElement.h"
|
|
#include "base/BaseProperties.h"
|
|
#include "commands/edit/MoveAcrossSegmentsCommand.h"
|
|
#include "commands/edit/MoveCommand.h"
|
|
#include "commands/edit/TransposeCommand.h"
|
|
#include "commands/notation/IncrementDisplacementsCommand.h"
|
|
#include "gui/general/EditTool.h"
|
|
#include "gui/general/GUIPalette.h"
|
|
#include "gui/general/LinedStaff.h"
|
|
#include "gui/general/RosegardenCanvasView.h"
|
|
#include "gui/kdeext/QCanvasSimpleSprite.h"
|
|
#include "NotationElement.h"
|
|
#include "NotationProperties.h"
|
|
#include "NotationStaff.h"
|
|
#include "NotationTool.h"
|
|
#include "NotationView.h"
|
|
#include "NotePixmapFactory.h"
|
|
#include <tdeaction.h>
|
|
#include <tqapplication.h>
|
|
#include <tqiconset.h>
|
|
#include <tqrect.h>
|
|
#include <tqstring.h>
|
|
#include <tqtimer.h>
|
|
|
|
|
|
namespace Rosegarden
|
|
{
|
|
|
|
using namespace BaseProperties;
|
|
|
|
NotationSelector::NotationSelector(NotationView* view)
|
|
: NotationTool("NotationSelector", view),
|
|
m_selectionRect(0),
|
|
m_updateRect(false),
|
|
m_selectedStaff(0),
|
|
m_clickedElement(0),
|
|
m_selectionToMerge(0),
|
|
m_justSelectedBar(false),
|
|
m_wholeStaffSelectionComplete(false)
|
|
{
|
|
connect(m_parentView, TQ_SIGNAL(usedSelection()),
|
|
this, TQ_SLOT(slotHideSelection()));
|
|
|
|
connect(this, TQ_SIGNAL(editElement(NotationStaff *, NotationElement *, bool)),
|
|
m_parentView, TQ_SLOT(slotEditElement(NotationStaff *, NotationElement *, bool)));
|
|
|
|
TQIconSet icon
|
|
(NotePixmapFactory::toTQPixmap(NotePixmapFactory::
|
|
makeToolbarPixmap("crotchet")));
|
|
new TDEToggleAction(i18n("Switch to Insert Tool"), icon, 0, this,
|
|
TQ_SLOT(slotInsertSelected()), actionCollection(),
|
|
"insert");
|
|
|
|
new TDEAction(i18n("Switch to Erase Tool"), "eraser", 0, this,
|
|
TQ_SLOT(slotEraseSelected()), actionCollection(),
|
|
"erase");
|
|
|
|
// (this crashed, and it might be superfluous with ^N anyway, so I'm
|
|
// commenting it out, but leaving it here in case I change my mind about
|
|
// fooling with it.) (DMM)
|
|
// new TDEAction(i18n("Normalize Rests"), 0, 0, this,
|
|
// TQ_SLOT(slotCollapseRests()), actionCollection(),
|
|
// "collapse_rests");
|
|
|
|
new TDEAction(i18n("Collapse Rests"), 0, 0, this,
|
|
TQ_SLOT(slotCollapseRestsHard()), actionCollection(),
|
|
"collapse_rests_aggressively");
|
|
|
|
new TDEAction(i18n("Respell as Flat"), 0, 0, this,
|
|
TQ_SLOT(slotRespellFlat()), actionCollection(),
|
|
"respell_flat");
|
|
|
|
new TDEAction(i18n("Respell as Sharp"), 0, 0, this,
|
|
TQ_SLOT(slotRespellSharp()), actionCollection(),
|
|
"respell_sharp");
|
|
|
|
new TDEAction(i18n("Respell as Natural"), 0, 0, this,
|
|
TQ_SLOT(slotRespellNatural()), actionCollection(),
|
|
"respell_natural");
|
|
|
|
new TDEAction(i18n("Collapse Notes"), 0, 0, this,
|
|
TQ_SLOT(slotCollapseNotes()), actionCollection(),
|
|
"collapse_notes");
|
|
|
|
new TDEAction(i18n("Interpret"), 0, 0, this,
|
|
TQ_SLOT(slotInterpret()), actionCollection(),
|
|
"interpret");
|
|
|
|
new TDEAction(i18n("Move to Staff Above"), 0, 0, this,
|
|
TQ_SLOT(slotStaffAbove()), actionCollection(),
|
|
"move_events_up_staff");
|
|
|
|
new TDEAction(i18n("Move to Staff Below"), 0, 0, this,
|
|
TQ_SLOT(slotStaffBelow()), actionCollection(),
|
|
"move_events_down_staff");
|
|
|
|
new TDEAction(i18n("Make Invisible"), 0, 0, this,
|
|
TQ_SLOT(slotMakeInvisible()), actionCollection(),
|
|
"make_invisible");
|
|
|
|
new TDEAction(i18n("Make Visible"), 0, 0, this,
|
|
TQ_SLOT(slotMakeVisible()), actionCollection(),
|
|
"make_visible");
|
|
|
|
createMenu("notationselector.rc");
|
|
}
|
|
|
|
NotationSelector::~NotationSelector()
|
|
{
|
|
delete m_selectionToMerge;
|
|
}
|
|
|
|
void NotationSelector::handleLeftButtonPress(timeT t,
|
|
int height,
|
|
int staffNo,
|
|
TQMouseEvent* e,
|
|
ViewElement *element)
|
|
{
|
|
std::cerr << "NotationSelector::handleMousePress: time is " << t << ", staffNo is "
|
|
<< staffNo << ", e and element are " << e << " and " << element << std::endl;
|
|
|
|
if (m_justSelectedBar) {
|
|
handleMouseTripleClick(t, height, staffNo, e, element);
|
|
m_justSelectedBar = false;
|
|
return ;
|
|
}
|
|
|
|
m_wholeStaffSelectionComplete = false;
|
|
|
|
delete m_selectionToMerge;
|
|
const EventSelection *selectionToMerge = 0;
|
|
if (e->state() & ShiftButton) {
|
|
m_clickedShift = true;
|
|
selectionToMerge = m_nParentView->getCurrentSelection();
|
|
} else {
|
|
m_clickedShift = false;
|
|
}
|
|
m_selectionToMerge =
|
|
(selectionToMerge ? new EventSelection(*selectionToMerge) : 0);
|
|
|
|
m_clickedElement = dynamic_cast<NotationElement*>(element);
|
|
if (m_clickedElement) {
|
|
m_selectedStaff = getStaffForElement(m_clickedElement);
|
|
m_lastDragPitch = -400;
|
|
m_lastDragTime = m_clickedElement->event()->getNotationAbsoluteTime();
|
|
} else {
|
|
m_selectedStaff = 0; // don't know yet; wait until we have an element
|
|
}
|
|
|
|
m_selectionRect->setX(e->x());
|
|
m_selectionRect->setY(e->y());
|
|
m_selectionRect->setSize(0, 0);
|
|
|
|
m_selectionRect->show();
|
|
m_updateRect = true;
|
|
m_startedFineDrag = false;
|
|
|
|
//m_parentView->setCursorPosition(p.x());
|
|
}
|
|
|
|
void NotationSelector::handleRightButtonPress(timeT t,
|
|
int height,
|
|
int staffNo,
|
|
TQMouseEvent* e,
|
|
ViewElement *element)
|
|
{
|
|
std::cerr << "NotationSelector::handleRightButtonPress" << std::endl;
|
|
|
|
const EventSelection *sel = m_nParentView->getCurrentSelection();
|
|
|
|
if (!sel || sel->getSegmentEvents().empty()) {
|
|
|
|
// if nothing selected, permit the possibility of selecting
|
|
// something before showing the menu
|
|
|
|
if (element) {
|
|
m_clickedElement = dynamic_cast<NotationElement*>(element);
|
|
m_selectedStaff = getStaffForElement(m_clickedElement);
|
|
m_nParentView->setSingleSelectedEvent
|
|
(m_selectedStaff->getId(), m_clickedElement->event(),
|
|
true, true);
|
|
}
|
|
|
|
handleLeftButtonPress(t, height, staffNo, e, element);
|
|
}
|
|
|
|
EditTool::handleRightButtonPress(t, height, staffNo, e, element);
|
|
}
|
|
|
|
void NotationSelector::slotClickTimeout()
|
|
{
|
|
m_justSelectedBar = false;
|
|
}
|
|
|
|
void NotationSelector::handleMouseDoubleClick(timeT,
|
|
int,
|
|
int staffNo,
|
|
TQMouseEvent* e,
|
|
ViewElement *element)
|
|
{
|
|
NOTATION_DEBUG << "NotationSelector::handleMouseDoubleClick" << endl;
|
|
m_clickedElement = dynamic_cast<NotationElement*>(element);
|
|
|
|
NotationStaff *staff = m_nParentView->getNotationStaff(staffNo);
|
|
if (!staff)
|
|
return ;
|
|
m_selectedStaff = staff;
|
|
|
|
bool advanced = (e->state() & ShiftButton);
|
|
|
|
if (m_clickedElement) {
|
|
|
|
emit editElement(staff, m_clickedElement, advanced);
|
|
|
|
} else {
|
|
|
|
TQRect rect = staff->getBarExtents(e->x(), e->y());
|
|
|
|
m_selectionRect->setX(rect.x() + 1);
|
|
m_selectionRect->setY(rect.y());
|
|
m_selectionRect->setSize(rect.width() - 1, rect.height());
|
|
|
|
m_selectionRect->show();
|
|
m_updateRect = false;
|
|
|
|
m_justSelectedBar = true;
|
|
TQTimer::singleShot(TQApplication::doubleClickInterval(), this,
|
|
TQ_SLOT(slotClickTimeout()));
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
void NotationSelector::handleMouseTripleClick(timeT t,
|
|
int height,
|
|
int staffNo,
|
|
TQMouseEvent* e,
|
|
ViewElement *element)
|
|
{
|
|
if (!m_justSelectedBar)
|
|
return ;
|
|
m_justSelectedBar = false;
|
|
|
|
NOTATION_DEBUG << "NotationSelector::handleMouseTripleClick" << endl;
|
|
m_clickedElement = dynamic_cast<NotationElement*>(element);
|
|
|
|
NotationStaff *staff = m_nParentView->getNotationStaff(staffNo);
|
|
if (!staff)
|
|
return ;
|
|
m_selectedStaff = staff;
|
|
|
|
if (m_clickedElement) {
|
|
|
|
// should be safe, as we've already set m_justSelectedBar false
|
|
handleLeftButtonPress(t, height, staffNo, e, element);
|
|
return ;
|
|
|
|
} else {
|
|
|
|
m_selectionRect->setX(staff->getX());
|
|
m_selectionRect->setY(staff->getY());
|
|
m_selectionRect->setSize(int(staff->getTotalWidth()) - 1,
|
|
staff->getTotalHeight() - 1);
|
|
|
|
m_selectionRect->show();
|
|
m_updateRect = false;
|
|
}
|
|
|
|
m_wholeStaffSelectionComplete = true;
|
|
|
|
return ;
|
|
}
|
|
|
|
int NotationSelector::handleMouseMove(timeT, int,
|
|
TQMouseEvent* e)
|
|
{
|
|
if (!m_updateRect)
|
|
return RosegardenCanvasView::NoFollow;
|
|
|
|
int w = int(e->x() - m_selectionRect->x());
|
|
int h = int(e->y() - m_selectionRect->y());
|
|
|
|
if (m_clickedElement /* && !m_clickedElement->isRest() */) {
|
|
|
|
if (m_startedFineDrag) {
|
|
dragFine(e->x(), e->y(), false);
|
|
} else if (m_clickedShift) {
|
|
if (w > 2 || w < -2 || h > 2 || h < -2) {
|
|
dragFine(e->x(), e->y(), false);
|
|
}
|
|
} else if (w > 3 || w < -3 || h > 3 || h < -3) {
|
|
drag(e->x(), e->y(), false);
|
|
}
|
|
|
|
} else {
|
|
|
|
// TQt rectangle dimensions appear to be 1-based
|
|
if (w > 0)
|
|
++w;
|
|
else
|
|
--w;
|
|
if (h > 0)
|
|
++h;
|
|
else
|
|
--h;
|
|
|
|
m_selectionRect->setSize(w, h);
|
|
setViewCurrentSelection(true);
|
|
m_nParentView->canvas()->update();
|
|
}
|
|
|
|
return RosegardenCanvasView::FollowHorizontal | RosegardenCanvasView::FollowVertical;
|
|
}
|
|
|
|
void NotationSelector::handleMouseRelease(timeT, int, TQMouseEvent *e)
|
|
{
|
|
NOTATION_DEBUG << "NotationSelector::handleMouseRelease" << endl;
|
|
m_updateRect = false;
|
|
|
|
NOTATION_DEBUG << "selectionRect width, height: " << m_selectionRect->width()
|
|
<< ", " << m_selectionRect->height() << endl;
|
|
|
|
// Test how far we've moved from the original click position -- not
|
|
// how big the rectangle is (if we were dragging an event, the
|
|
// rectangle size will still be zero).
|
|
|
|
if (((e->x() - m_selectionRect->x()) > -3 &&
|
|
(e->x() - m_selectionRect->x()) < 3 &&
|
|
(e->y() - m_selectionRect->y()) > -3 &&
|
|
(e->y() - m_selectionRect->y()) < 3) &&
|
|
!m_startedFineDrag) {
|
|
|
|
if (m_clickedElement != 0 && m_selectedStaff) {
|
|
|
|
// If we didn't drag out a meaningful area, but _did_
|
|
// click on an individual event, then select just that
|
|
// event
|
|
|
|
if (m_selectionToMerge &&
|
|
m_selectionToMerge->getSegment() ==
|
|
m_selectedStaff->getSegment()) {
|
|
|
|
// if the event was already part of the selection, we want to
|
|
// remove it
|
|
if (m_selectionToMerge->contains(m_clickedElement->event())) {
|
|
m_selectionToMerge->removeEvent(m_clickedElement->event());
|
|
} else {
|
|
m_selectionToMerge->addEvent(m_clickedElement->event());
|
|
}
|
|
|
|
m_nParentView->setCurrentSelection(m_selectionToMerge,
|
|
true, true);
|
|
m_selectionToMerge = 0;
|
|
|
|
} else {
|
|
|
|
m_nParentView->setSingleSelectedEvent
|
|
(m_selectedStaff->getId(), m_clickedElement->event(),
|
|
true, true);
|
|
}
|
|
/*
|
|
} else if (m_selectedStaff) {
|
|
|
|
// If we clicked on no event but on a staff, move the
|
|
// insertion cursor to the point where we clicked.
|
|
// Actually we only really want this to happen if
|
|
// we aren't double-clicking -- consider using a timer
|
|
// to establish whether a double-click is going to happen
|
|
|
|
m_nParentView->slotSetInsertCursorPosition(e->x(), (int)e->y());
|
|
*/
|
|
} else {
|
|
setViewCurrentSelection(false);
|
|
}
|
|
|
|
} else {
|
|
|
|
if (m_startedFineDrag) {
|
|
dragFine(e->x(), e->y(), true);
|
|
} else if (m_clickedElement /* && !m_clickedElement->isRest() */) {
|
|
drag(e->x(), e->y(), true);
|
|
} else {
|
|
setViewCurrentSelection(false);
|
|
}
|
|
}
|
|
|
|
m_clickedElement = 0;
|
|
m_selectionRect->hide();
|
|
m_wholeStaffSelectionComplete = false;
|
|
m_nParentView->canvas()->update();
|
|
}
|
|
|
|
void NotationSelector::drag(int x, int y, bool final)
|
|
{
|
|
NOTATION_DEBUG << "NotationSelector::drag " << x << ", " << y << endl;
|
|
|
|
if (!m_clickedElement || !m_selectedStaff)
|
|
return ;
|
|
|
|
EventSelection *selection = m_nParentView->getCurrentSelection();
|
|
if (!selection || !selection->contains(m_clickedElement->event())) {
|
|
selection = new EventSelection(m_selectedStaff->getSegment());
|
|
selection->addEvent(m_clickedElement->event());
|
|
}
|
|
m_nParentView->setCurrentSelection(selection);
|
|
|
|
LinedStaff *targetStaff = m_nParentView->getStaffForCanvasCoords(x, y);
|
|
if (!targetStaff)
|
|
targetStaff = m_selectedStaff;
|
|
|
|
// Calculate time and height
|
|
|
|
timeT clickedTime = m_clickedElement->event()->getNotationAbsoluteTime();
|
|
|
|
Accidental clickedAccidental = Accidentals::NoAccidental;
|
|
(void)m_clickedElement->event()->get<String>(ACCIDENTAL, clickedAccidental);
|
|
|
|
long clickedPitch = 0;
|
|
(void)m_clickedElement->event()->get<Int>(PITCH, clickedPitch);
|
|
|
|
long clickedHeight = 0;
|
|
(void)m_clickedElement->event()->get<Int>
|
|
(NotationProperties::HEIGHT_ON_STAFF, clickedHeight);
|
|
|
|
Event *clefEvt = 0, *keyEvt = 0;
|
|
Clef clef;
|
|
::Rosegarden::Key key;
|
|
|
|
timeT dragTime = clickedTime;
|
|
double layoutX = m_clickedElement->getLayoutX();
|
|
timeT duration = m_clickedElement->getViewDuration();
|
|
|
|
NotationElementList::iterator itr =
|
|
targetStaff->getElementUnderCanvasCoords(x, y, clefEvt, keyEvt);
|
|
|
|
if (itr != targetStaff->getViewElementList()->end()) {
|
|
|
|
NotationElement *elt = dynamic_cast<NotationElement *>(*itr);
|
|
dragTime = elt->getViewAbsoluteTime();
|
|
layoutX = elt->getLayoutX();
|
|
|
|
if (elt->isRest() && duration > 0 && elt->getCanvasItem()) {
|
|
|
|
double restX = 0, restWidth = 0;
|
|
elt->getCanvasAirspace(restX, restWidth);
|
|
|
|
timeT restDuration = elt->getViewDuration();
|
|
|
|
if (restWidth > 0 &&
|
|
restDuration >= duration * 2) {
|
|
|
|
int parts = restDuration / duration;
|
|
double encroachment = x - restX;
|
|
NOTATION_DEBUG << "encroachment is " << encroachment << ", restWidth is " << restWidth << endl;
|
|
int part = (int)((encroachment / restWidth) * parts);
|
|
if (part >= parts)
|
|
part = parts - 1;
|
|
|
|
dragTime += part * restDuration / parts;
|
|
layoutX += part * restWidth / parts +
|
|
(restX - elt->getCanvasX());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (clefEvt)
|
|
clef = Clef(*clefEvt);
|
|
if (keyEvt)
|
|
key = ::Rosegarden::Key(*keyEvt);
|
|
|
|
int height = targetStaff->getHeightAtCanvasCoords(x, y);
|
|
int pitch = clickedPitch;
|
|
|
|
if (height != clickedHeight)
|
|
pitch =
|
|
Pitch
|
|
(height, clef, key, clickedAccidental).getPerformancePitch();
|
|
|
|
if (pitch < clickedPitch) {
|
|
if (height < -10) {
|
|
height = -10;
|
|
pitch = Pitch
|
|
(height, clef, key, clickedAccidental).getPerformancePitch();
|
|
}
|
|
} else if (pitch > clickedPitch) {
|
|
if (height > 18) {
|
|
height = 18;
|
|
pitch = Pitch
|
|
(height, clef, key, clickedAccidental).getPerformancePitch();
|
|
}
|
|
}
|
|
|
|
bool singleNonNotePreview = !m_clickedElement->isNote() &&
|
|
selection->getSegmentEvents().size() == 1;
|
|
|
|
if (!final && !singleNonNotePreview) {
|
|
|
|
if ((pitch != m_lastDragPitch || dragTime != m_lastDragTime) &&
|
|
m_clickedElement->isNote()) {
|
|
|
|
m_nParentView->showPreviewNote(targetStaff->getId(),
|
|
layoutX, pitch, height,
|
|
Note::getNearestNote(duration),
|
|
m_clickedElement->isGrace());
|
|
m_lastDragPitch = pitch;
|
|
m_lastDragTime = dragTime;
|
|
}
|
|
|
|
} else {
|
|
|
|
m_nParentView->clearPreviewNote();
|
|
|
|
KMacroCommand *command = new KMacroCommand(MoveCommand::getGlobalName());
|
|
bool haveSomething = false;
|
|
|
|
MoveCommand *mc = 0;
|
|
Event *lastInsertedEvent = 0;
|
|
|
|
if (pitch != clickedPitch && m_clickedElement->isNote()) {
|
|
command->addCommand(new TransposeCommand(pitch - clickedPitch,
|
|
*selection));
|
|
haveSomething = true;
|
|
}
|
|
|
|
if (targetStaff != m_selectedStaff) {
|
|
command->addCommand(new MoveAcrossSegmentsCommand
|
|
(m_selectedStaff->getSegment(),
|
|
targetStaff->getSegment(),
|
|
dragTime - clickedTime + selection->getStartTime(),
|
|
true,
|
|
*selection));
|
|
haveSomething = true;
|
|
} else {
|
|
if (dragTime != clickedTime) {
|
|
mc = new MoveCommand
|
|
(m_selectedStaff->getSegment(), //!!!sort
|
|
dragTime - clickedTime, true, *selection);
|
|
command->addCommand(mc);
|
|
haveSomething = true;
|
|
}
|
|
}
|
|
|
|
if (haveSomething) {
|
|
|
|
m_nParentView->addCommandToHistory(command);
|
|
|
|
if (mc && singleNonNotePreview) {
|
|
|
|
lastInsertedEvent = mc->getLastInsertedEvent();
|
|
|
|
if (lastInsertedEvent) {
|
|
m_nParentView->setSingleSelectedEvent(targetStaff->getId(),
|
|
lastInsertedEvent);
|
|
|
|
ViewElementList::iterator vli =
|
|
targetStaff->findEvent(lastInsertedEvent);
|
|
|
|
if (vli != targetStaff->getViewElementList()->end()) {
|
|
m_clickedElement = dynamic_cast<NotationElement *>(*vli);
|
|
} else {
|
|
m_clickedElement = 0;
|
|
}
|
|
|
|
m_selectionRect->setX(x);
|
|
m_selectionRect->setY(y);
|
|
}
|
|
}
|
|
} else {
|
|
delete command;
|
|
}
|
|
}
|
|
}
|
|
|
|
void NotationSelector::dragFine(int x, int y, bool final)
|
|
{
|
|
NOTATION_DEBUG << "NotationSelector::drag " << x << ", " << y << endl;
|
|
|
|
if (!m_clickedElement || !m_selectedStaff)
|
|
return ;
|
|
|
|
EventSelection *selection = m_nParentView->getCurrentSelection();
|
|
if (!selection)
|
|
selection = new EventSelection(m_selectedStaff->getSegment());
|
|
if (!selection->contains(m_clickedElement->event()))
|
|
selection->addEvent(m_clickedElement->event());
|
|
m_nParentView->setCurrentSelection(selection);
|
|
|
|
// Fine drag modifies the DISPLACED_X and DISPLACED_Y properties on
|
|
// each event. The modifications have to be relative to the previous
|
|
// values of these properties, not to zero, so for each event we need
|
|
// to store the previous value at the time the drag starts.
|
|
|
|
static PropertyName xProperty("temporary-displaced-x");
|
|
static PropertyName yProperty("temporary-displaced-y");
|
|
|
|
if (!m_startedFineDrag) {
|
|
// back up original properties
|
|
|
|
for (EventSelection::eventcontainer::iterator i =
|
|
selection->getSegmentEvents().begin();
|
|
i != selection->getSegmentEvents().end(); ++i) {
|
|
long prevX = 0, prevY = 0;
|
|
(*i)->get
|
|
<Int>(DISPLACED_X, prevX);
|
|
(*i)->get
|
|
<Int>(DISPLACED_Y, prevY);
|
|
(*i)->setMaybe<Int>(xProperty, prevX);
|
|
(*i)->setMaybe<Int>(yProperty, prevY);
|
|
}
|
|
|
|
m_startedFineDrag = true;
|
|
}
|
|
|
|
// We want the displacements in 1/1000ths of a staff space
|
|
|
|
double dx = x - m_selectionRect->x();
|
|
double dy = y - m_selectionRect->y();
|
|
|
|
double noteBodyWidth = m_nParentView->getNotePixmapFactory()->getNoteBodyWidth();
|
|
double lineSpacing = m_nParentView->getNotePixmapFactory()->getLineSpacing();
|
|
dx = (1000.0 * dx) / noteBodyWidth;
|
|
dy = (1000.0 * dy) / lineSpacing;
|
|
|
|
if (final) {
|
|
|
|
// reset original values (and remove backup values) before
|
|
// applying command
|
|
|
|
for (EventSelection::eventcontainer::iterator i =
|
|
selection->getSegmentEvents().begin();
|
|
i != selection->getSegmentEvents().end(); ++i) {
|
|
long prevX = 0, prevY = 0;
|
|
(*i)->get
|
|
<Int>(xProperty, prevX);
|
|
(*i)->get
|
|
<Int>(yProperty, prevY);
|
|
(*i)->setMaybe<Int>(DISPLACED_X, prevX);
|
|
(*i)->setMaybe<Int>(DISPLACED_Y, prevY);
|
|
(*i)->unset(xProperty);
|
|
(*i)->unset(yProperty);
|
|
}
|
|
|
|
IncrementDisplacementsCommand *command = new IncrementDisplacementsCommand
|
|
(*selection, long(dx), long(dy));
|
|
m_nParentView->addCommandToHistory(command);
|
|
|
|
} else {
|
|
|
|
timeT startTime = 0, endTime = 0;
|
|
|
|
for (EventSelection::eventcontainer::iterator i =
|
|
selection->getSegmentEvents().begin();
|
|
i != selection->getSegmentEvents().end(); ++i) {
|
|
long prevX = 0, prevY = 0;
|
|
(*i)->get
|
|
<Int>(xProperty, prevX);
|
|
(*i)->get
|
|
<Int>(yProperty, prevY);
|
|
(*i)->setMaybe<Int>(DISPLACED_X, prevX + long(dx));
|
|
(*i)->setMaybe<Int>(DISPLACED_Y, prevY + long(dy));
|
|
if (i == selection->getSegmentEvents().begin()) {
|
|
startTime = (*i)->getAbsoluteTime();
|
|
}
|
|
endTime = (*i)->getAbsoluteTime() + (*i)->getDuration();
|
|
}
|
|
|
|
if (startTime == endTime)
|
|
++endTime;
|
|
selection->getSegment().updateRefreshStatuses(startTime, endTime);
|
|
m_nParentView->update();
|
|
}
|
|
}
|
|
|
|
void NotationSelector::ready()
|
|
{
|
|
m_selectionRect = new TQCanvasRectangle(m_nParentView->canvas());
|
|
|
|
m_selectionRect->hide();
|
|
m_selectionRect->setPen(GUIPalette::getColour(GUIPalette::SelectionRectangle));
|
|
|
|
m_nParentView->setCanvasCursor(TQt::arrowCursor);
|
|
m_nParentView->setHeightTracking(false);
|
|
}
|
|
|
|
void NotationSelector::stow()
|
|
{
|
|
delete m_selectionRect;
|
|
m_selectionRect = 0;
|
|
m_nParentView->canvas()->update();
|
|
}
|
|
|
|
void NotationSelector::slotHideSelection()
|
|
{
|
|
if (!m_selectionRect)
|
|
return ;
|
|
m_selectionRect->hide();
|
|
m_selectionRect->setSize(0, 0);
|
|
m_nParentView->canvas()->update();
|
|
}
|
|
|
|
void NotationSelector::slotInsertSelected()
|
|
{
|
|
m_nParentView->slotLastNoteAction();
|
|
}
|
|
|
|
void NotationSelector::slotEraseSelected()
|
|
{
|
|
m_parentView->actionCollection()->action("erase")->activate();
|
|
}
|
|
|
|
void NotationSelector::slotCollapseRestsHard()
|
|
{
|
|
m_parentView->actionCollection()->action("collapse_rests_aggressively")->activate();
|
|
}
|
|
|
|
void NotationSelector::slotRespellFlat()
|
|
{
|
|
m_parentView->actionCollection()->action("respell_flat")->activate();
|
|
}
|
|
|
|
void NotationSelector::slotRespellSharp()
|
|
{
|
|
m_parentView->actionCollection()->action("respell_sharp")->activate();
|
|
}
|
|
|
|
void NotationSelector::slotRespellNatural()
|
|
{
|
|
m_parentView->actionCollection()->action("respell_natural")->activate();
|
|
}
|
|
|
|
void NotationSelector::slotCollapseNotes()
|
|
{
|
|
m_parentView->actionCollection()->action("collapse_notes")->activate();
|
|
}
|
|
|
|
void NotationSelector::slotInterpret()
|
|
{
|
|
m_parentView->actionCollection()->action("interpret")->activate();
|
|
}
|
|
|
|
void NotationSelector::slotStaffAbove()
|
|
{
|
|
m_parentView->actionCollection()->action("move_events_up_staff")->activate();
|
|
}
|
|
|
|
void NotationSelector::slotStaffBelow()
|
|
{
|
|
m_parentView->actionCollection()->action("move_events_down_staff")->activate();
|
|
}
|
|
|
|
void NotationSelector::slotMakeInvisible()
|
|
{
|
|
m_parentView->actionCollection()->action("make_invisible")->activate();
|
|
}
|
|
|
|
void NotationSelector::slotMakeVisible()
|
|
{
|
|
m_parentView->actionCollection()->action("make_visible")->activate();
|
|
}
|
|
|
|
void NotationSelector::setViewCurrentSelection(bool preview)
|
|
{
|
|
EventSelection *selection = getSelection();
|
|
|
|
if (m_selectionToMerge) {
|
|
if (selection &&
|
|
m_selectionToMerge->getSegment() == selection->getSegment()) {
|
|
selection->addFromSelection(m_selectionToMerge);
|
|
} else {
|
|
return ;
|
|
}
|
|
}
|
|
|
|
m_nParentView->setCurrentSelection(selection, preview, true);
|
|
}
|
|
|
|
NotationStaff *
|
|
NotationSelector::getStaffForElement(NotationElement *elt)
|
|
{
|
|
for (int i = 0; i < m_nParentView->getStaffCount(); ++i) {
|
|
NotationStaff *staff = m_nParentView->getNotationStaff(i);
|
|
if (staff->getSegment().findSingle(elt->event()) !=
|
|
staff->getSegment().end())
|
|
return staff;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
EventSelection* NotationSelector::getSelection()
|
|
{
|
|
// If selection rect is not visible or too small,
|
|
// return 0
|
|
//
|
|
if (!m_selectionRect->isVisible()) return 0;
|
|
|
|
// NOTATION_DEBUG << "Selection x,y: " << m_selectionRect->x() << ","
|
|
// << m_selectionRect->y() << "; w,h: " << m_selectionRect->width() << "," << m_selectionRect->height() << endl;
|
|
|
|
if (m_selectionRect->width() > -3 &&
|
|
m_selectionRect->width() < 3 &&
|
|
m_selectionRect->height() > -3 &&
|
|
m_selectionRect->height() < 3) return 0;
|
|
|
|
TQCanvasItemList itemList = m_selectionRect->collisions(false);
|
|
TQCanvasItemList::Iterator it;
|
|
|
|
TQRect rect = m_selectionRect->rect().normalize();
|
|
TQCanvasNotationSprite *sprite = 0;
|
|
|
|
if (!m_selectedStaff) {
|
|
|
|
// Scan the list of collisions, looking for a valid notation
|
|
// element; if we find one, initialise m_selectedStaff from it.
|
|
// If we don't find one, we have no selection. This is a little
|
|
// inefficient but we only do it for the first event in the
|
|
// selection.
|
|
|
|
for (it = itemList.begin(); it != itemList.end(); ++it) {
|
|
|
|
if ((sprite = dynamic_cast<TQCanvasNotationSprite*>(*it))) {
|
|
|
|
NotationElement &el = sprite->getNotationElement();
|
|
|
|
NotationStaff *staff = getStaffForElement(&el);
|
|
if (!staff) continue;
|
|
|
|
int x = (int)(*it)->x();
|
|
bool shifted = false;
|
|
int nbw = staff->getNotePixmapFactory(false).getNoteBodyWidth();
|
|
|
|
|
|
// #957364 (Notation: Hard to select upper note in
|
|
// chords of seconds) -- adjust x-coord for shifted
|
|
// note head
|
|
if (el.event()->get<Rosegarden::Bool>
|
|
(staff->getProperties().NOTE_HEAD_SHIFTED, shifted) && shifted) {
|
|
x += nbw;
|
|
}
|
|
|
|
if (!rect.contains(x, int((*it)->y()), true)) {
|
|
// #988217 (Notation: Special column of pixels
|
|
// prevents sweep selection) -- for notes, test
|
|
// again with centred x-coord
|
|
if (!el.isNote() || !rect.contains(x + nbw/2, int((*it)->y()), true)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
m_selectedStaff = staff;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!m_selectedStaff) return 0;
|
|
Segment& originalSegment = m_selectedStaff->getSegment();
|
|
|
|
// If we selected the whole staff, force that to happen explicitly
|
|
// rather than relying on collisions with the rectangle -- because
|
|
// events way off the currently visible area might not even have
|
|
// been drawn yet, and so will not appear in the collision list.
|
|
// (We did still need the collision list to determine which staff
|
|
// to use though.)
|
|
|
|
if (m_wholeStaffSelectionComplete) {
|
|
EventSelection *selection = new EventSelection(originalSegment,
|
|
originalSegment.getStartTime(),
|
|
originalSegment.getEndMarkerTime());
|
|
return selection;
|
|
}
|
|
|
|
EventSelection* selection = new EventSelection(originalSegment);
|
|
|
|
for (it = itemList.begin(); it != itemList.end(); ++it) {
|
|
|
|
if ((sprite = dynamic_cast<TQCanvasNotationSprite*>(*it))) {
|
|
|
|
NotationElement &el = sprite->getNotationElement();
|
|
|
|
int x = (int)(*it)->x();
|
|
bool shifted = false;
|
|
int nbw = m_selectedStaff->getNotePixmapFactory(false).getNoteBodyWidth();
|
|
|
|
// #957364 (Notation: Hard to select upper note in chords
|
|
// of seconds) -- adjust x-coord for shifted note head
|
|
if (el.event()->get<Rosegarden::Bool>
|
|
(m_selectedStaff->getProperties().NOTE_HEAD_SHIFTED, shifted)
|
|
&& shifted) {
|
|
x += nbw;
|
|
}
|
|
|
|
// check if the element's rect
|
|
// is actually included in the selection rect.
|
|
//
|
|
if (!rect.contains(x, int((*it)->y()), true)) {
|
|
// #988217 (Notation: Special column of pixels
|
|
// prevents sweep selection) -- for notes, test again
|
|
// with centred x-coord
|
|
if (!el.isNote() || !rect.contains(x + nbw/2, int((*it)->y()), true)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// must be in the same segment as we first started on,
|
|
// we can't select events across multiple segments
|
|
if (selection->getSegment().findSingle(el.event()) !=
|
|
selection->getSegment().end()) {
|
|
selection->addEvent(el.event());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (selection->getAddedEvents() > 0) {
|
|
return selection;
|
|
} else {
|
|
delete selection;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
const TQString NotationSelector::ToolName = "notationselector";
|
|
|
|
}
|
|
#include "NotationSelector.moc"
|