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.
480 lines
16 KiB
480 lines
16 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 "MatrixMover.h"
|
|
|
|
#include "base/BaseProperties.h"
|
|
#include <tdelocale.h>
|
|
#include <kstddirs.h>
|
|
#include "base/Event.h"
|
|
#include "base/Segment.h"
|
|
#include "base/Selection.h"
|
|
#include "base/SnapGrid.h"
|
|
#include "base/ViewElement.h"
|
|
#include "commands/matrix/MatrixModifyCommand.h"
|
|
#include "commands/matrix/MatrixInsertionCommand.h"
|
|
#include "commands/notation/NormalizeRestsCommand.h"
|
|
#include "gui/general/EditTool.h"
|
|
#include "gui/general/RosegardenCanvasView.h"
|
|
#include "MatrixElement.h"
|
|
#include "MatrixStaff.h"
|
|
#include "MatrixTool.h"
|
|
#include "MatrixView.h"
|
|
#include "MatrixVLayout.h"
|
|
#include <tdeaction.h>
|
|
#include <tdeglobal.h>
|
|
#include <tqiconset.h>
|
|
#include <tqpoint.h>
|
|
#include <tqstring.h>
|
|
#include "misc/Debug.h"
|
|
|
|
|
|
namespace Rosegarden
|
|
{
|
|
|
|
MatrixMover::MatrixMover(MatrixView* parent) :
|
|
MatrixTool("MatrixMover", parent),
|
|
m_currentElement(0),
|
|
m_currentStaff(0),
|
|
m_lastPlayedPitch(-1)
|
|
{
|
|
TQString pixmapDir = TDEGlobal::dirs()->findResource("appdata", "pixmaps/");
|
|
TQCanvasPixmap pixmap(pixmapDir + "/toolbar/select.xpm");
|
|
TQIconSet icon = TQIconSet(pixmap);
|
|
|
|
new TDEAction(i18n("Switch to Select Tool"), icon, 0, this,
|
|
TQ_SLOT(slotSelectSelected()), actionCollection(),
|
|
"select");
|
|
|
|
new TDEAction(i18n("Switch to Draw Tool"), "pencil", 0, this,
|
|
TQ_SLOT(slotDrawSelected()), actionCollection(),
|
|
"draw");
|
|
|
|
new TDEAction(i18n("Switch to Erase Tool"), "eraser", 0, this,
|
|
TQ_SLOT(slotEraseSelected()), actionCollection(),
|
|
"erase");
|
|
|
|
pixmap.load(pixmapDir + "/toolbar/resize.xpm");
|
|
icon = TQIconSet(pixmap);
|
|
new TDEAction(i18n("Switch to Resize Tool"), icon, 0, this,
|
|
TQ_SLOT(slotResizeSelected()), actionCollection(),
|
|
"resize");
|
|
|
|
createMenu("matrixmover.rc");
|
|
}
|
|
|
|
void MatrixMover::handleEventRemoved(Event *event)
|
|
{
|
|
if (m_currentElement && m_currentElement->event() == event) {
|
|
m_currentElement = 0;
|
|
}
|
|
}
|
|
|
|
void MatrixMover::handleLeftButtonPress(timeT time,
|
|
int pitch,
|
|
int staffNo,
|
|
TQMouseEvent* e,
|
|
ViewElement* el)
|
|
{
|
|
MATRIX_DEBUG << "MatrixMover::handleLeftButtonPress() : time = " << time << ", el = " << el << endl;
|
|
if (!el) return;
|
|
|
|
m_quickCopy = (e->state() & TQt::ControlButton);
|
|
|
|
if (!m_duplicateElements.empty()) {
|
|
for (size_t i = 0; i < m_duplicateElements.size(); ++i) {
|
|
delete m_duplicateElements[i]->event();
|
|
delete m_duplicateElements[i];
|
|
}
|
|
m_duplicateElements.clear();
|
|
}
|
|
|
|
m_currentElement = dynamic_cast<MatrixElement*>(el);
|
|
m_currentStaff = m_mParentView->getStaff(staffNo);
|
|
|
|
if (m_currentElement) {
|
|
|
|
// Add this element and allow movement
|
|
//
|
|
EventSelection* selection = m_mParentView->getCurrentSelection();
|
|
|
|
if (selection) {
|
|
EventSelection *newSelection;
|
|
|
|
if ((e->state() & TQt::ShiftButton) ||
|
|
selection->contains(m_currentElement->event()))
|
|
newSelection = new EventSelection(*selection);
|
|
else
|
|
newSelection = new EventSelection(m_currentStaff->getSegment());
|
|
|
|
// if the selection already contains the event, remove it from the
|
|
// selection if shift is pressed
|
|
if (selection->contains(m_currentElement->event())){
|
|
if (e->state() & TQt::ShiftButton)
|
|
newSelection->removeEvent(m_currentElement->event());
|
|
} else {
|
|
newSelection->addEvent(m_currentElement->event());
|
|
}
|
|
m_mParentView->setCurrentSelection(newSelection, true, true);
|
|
m_mParentView->canvas()->update();
|
|
selection = newSelection;
|
|
} else {
|
|
m_mParentView->setSingleSelectedEvent(m_currentStaff->getSegment(),
|
|
m_currentElement->event(),
|
|
true);
|
|
m_mParentView->canvas()->update();
|
|
}
|
|
|
|
long velocity = m_mParentView->getCurrentVelocity();
|
|
m_currentElement->event()->get<Int>(BaseProperties::VELOCITY, velocity);
|
|
m_mParentView->playNote(m_currentStaff->getSegment(), pitch, velocity);
|
|
m_lastPlayedPitch = pitch;
|
|
|
|
if (m_quickCopy && selection) {
|
|
for (EventSelection::eventcontainer::iterator i =
|
|
selection->getSegmentEvents().begin();
|
|
i != selection->getSegmentEvents().end(); ++i) {
|
|
|
|
MatrixElement *element = m_currentStaff->getElement(*i);
|
|
if (!element) continue;
|
|
|
|
MatrixElement *duplicate = new MatrixElement
|
|
(new Event(**i), m_mParentView->isDrumMode());
|
|
duplicate->setLayoutY(element->getLayoutY());
|
|
duplicate->setLayoutX(element->getLayoutX());
|
|
duplicate->setWidth(element->getWidth());
|
|
duplicate->setHeight(element->getHeight());
|
|
duplicate->setCanvasZ(-1);
|
|
m_currentStaff->positionElement(duplicate);
|
|
m_duplicateElements.push_back(duplicate);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_clickX = m_mParentView->inverseMapPoint(e->pos()).x();
|
|
}
|
|
|
|
timeT
|
|
MatrixMover::getDragTime(TQMouseEvent *e, timeT candidate)
|
|
{
|
|
int x = m_mParentView->inverseMapPoint(e->pos()).x();
|
|
int xdiff = x - m_clickX;
|
|
|
|
const SnapGrid &grid = getSnapGrid();
|
|
const RulerScale &scale = *grid.getRulerScale();
|
|
|
|
timeT eventTime = m_currentElement->getViewAbsoluteTime();
|
|
int eventX = scale.getXForTime(eventTime);
|
|
timeT preSnapTarget = scale.getTimeForX(eventX + xdiff);
|
|
|
|
candidate = grid.snapTime(preSnapTarget, SnapGrid::SnapEither);
|
|
|
|
if (xdiff == 0 ||
|
|
(abs(eventTime - preSnapTarget) < abs(candidate - preSnapTarget))) {
|
|
candidate = eventTime;
|
|
}
|
|
|
|
return candidate;
|
|
}
|
|
|
|
int MatrixMover::handleMouseMove(timeT newTime,
|
|
int newPitch,
|
|
TQMouseEvent *e)
|
|
{
|
|
MATRIX_DEBUG << "MatrixMover::handleMouseMove() time = "
|
|
<< newTime << endl;
|
|
|
|
if (e) {
|
|
setBasicContextHelp(e->state() & TQt::ControlButton);
|
|
}
|
|
|
|
if (!m_currentElement || !m_currentStaff)
|
|
return RosegardenCanvasView::NoFollow;
|
|
|
|
if (getSnapGrid().getSnapSetting() != SnapGrid::NoSnap) {
|
|
setContextHelp(i18n("Hold Shift to avoid snapping to beat grid"));
|
|
} else {
|
|
clearContextHelp();
|
|
}
|
|
|
|
if (e) newTime = getDragTime(e, newTime);
|
|
|
|
emit hoveredOverNoteChanged(newPitch, true, newTime);
|
|
|
|
using BaseProperties::PITCH;
|
|
int diffPitch = 0;
|
|
if (m_currentElement->event()->has(PITCH)) {
|
|
diffPitch = newPitch - m_currentElement->event()->get<Int>(PITCH);
|
|
}
|
|
|
|
int diffY =
|
|
int(((m_currentStaff->getLayoutYForHeight(newPitch) -
|
|
m_currentStaff->getElementHeight() / 2) -
|
|
m_currentElement->getLayoutY()));
|
|
|
|
EventSelection* selection = m_mParentView->getCurrentSelection();
|
|
EventSelection::eventcontainer::iterator it =
|
|
selection->getSegmentEvents().begin();
|
|
|
|
MatrixElement *element = 0;
|
|
int maxY = m_currentStaff->getCanvasYForHeight(0);
|
|
|
|
for (; it != selection->getSegmentEvents().end(); it++) {
|
|
element = m_currentStaff->getElement(*it);
|
|
|
|
if (element) {
|
|
|
|
timeT diffTime = element->getViewAbsoluteTime() -
|
|
m_currentElement->getViewAbsoluteTime();
|
|
|
|
int newX = getSnapGrid().getRulerScale()->
|
|
getXForTime(newTime + diffTime);
|
|
|
|
if (newX < 0) newX = 0;
|
|
|
|
int newY = int(element->getLayoutY() + diffY);
|
|
|
|
if (newY < 0) newY = 0;
|
|
if (newY > maxY) newY = maxY;
|
|
|
|
element->setLayoutX(newX);
|
|
element->setLayoutY(newY);
|
|
|
|
m_currentStaff->positionElement(element);
|
|
}
|
|
}
|
|
|
|
if (newPitch != m_lastPlayedPitch) {
|
|
long velocity = m_mParentView->getCurrentVelocity();
|
|
m_currentElement->event()->get<Int>(BaseProperties::VELOCITY, velocity);
|
|
m_mParentView->playNote(m_currentStaff->getSegment(), newPitch, velocity);
|
|
m_lastPlayedPitch = newPitch;
|
|
}
|
|
|
|
m_mParentView->canvas()->update();
|
|
return RosegardenCanvasView::FollowHorizontal |
|
|
RosegardenCanvasView::FollowVertical;
|
|
}
|
|
|
|
void MatrixMover::handleMouseRelease(timeT newTime,
|
|
int newPitch,
|
|
TQMouseEvent *e)
|
|
{
|
|
MATRIX_DEBUG << "MatrixMover::handleMouseRelease() - newPitch = "
|
|
<< newPitch << endl;
|
|
|
|
if (!m_currentElement || !m_currentStaff)
|
|
return;
|
|
|
|
if (newPitch > MatrixVLayout::maxMIDIPitch)
|
|
newPitch = MatrixVLayout::maxMIDIPitch;
|
|
if (newPitch < 0)
|
|
newPitch = 0;
|
|
|
|
if (e) newTime = getDragTime(e, newTime);
|
|
|
|
using BaseProperties::PITCH;
|
|
timeT diffTime = newTime - m_currentElement->getViewAbsoluteTime();
|
|
int diffPitch = 0;
|
|
if (m_currentElement->event()->has(PITCH)) {
|
|
diffPitch = newPitch - m_currentElement->event()->get<Int>(PITCH);
|
|
}
|
|
|
|
EventSelection *selection = m_mParentView->getCurrentSelection();
|
|
|
|
if ((diffTime == 0 && diffPitch == 0) || selection->getAddedEvents() == 0) {
|
|
for (size_t i = 0; i < m_duplicateElements.size(); ++i) {
|
|
delete m_duplicateElements[i]->event();
|
|
delete m_duplicateElements[i];
|
|
}
|
|
m_duplicateElements.clear();
|
|
m_mParentView->canvas()->update();
|
|
m_currentElement = 0;
|
|
return;
|
|
}
|
|
|
|
if (newPitch != m_lastPlayedPitch) {
|
|
long velocity = m_mParentView->getCurrentVelocity();
|
|
m_currentElement->event()->get<Int>(BaseProperties::VELOCITY, velocity);
|
|
m_mParentView->playNote(m_currentStaff->getSegment(), newPitch, velocity);
|
|
m_lastPlayedPitch = newPitch;
|
|
}
|
|
|
|
TQString commandLabel;
|
|
if (m_quickCopy) {
|
|
if (selection->getAddedEvents() < 2) {
|
|
commandLabel = i18n("Copy and Move Event");
|
|
} else {
|
|
commandLabel = i18n("Copy and Move Events");
|
|
}
|
|
} else {
|
|
if (selection->getAddedEvents() < 2) {
|
|
commandLabel = i18n("Move Event");
|
|
} else {
|
|
commandLabel = i18n("Move Events");
|
|
}
|
|
}
|
|
|
|
KMacroCommand *macro = new KMacroCommand(commandLabel);
|
|
|
|
EventSelection::eventcontainer::iterator it =
|
|
selection->getSegmentEvents().begin();
|
|
|
|
Segment &segment = m_currentStaff->getSegment();
|
|
|
|
EventSelection *newSelection = new EventSelection(segment);
|
|
|
|
timeT normalizeStart = selection->getStartTime();
|
|
timeT normalizeEnd = selection->getEndTime();
|
|
|
|
if (m_quickCopy) {
|
|
for (size_t i = 0; i < m_duplicateElements.size(); ++i) {
|
|
timeT time = m_duplicateElements[i]->getViewAbsoluteTime();
|
|
timeT endTime = time + m_duplicateElements[i]->getViewDuration();
|
|
if (time < normalizeStart) normalizeStart = time;
|
|
if (endTime > normalizeEnd) normalizeEnd = endTime;
|
|
macro->addCommand(new MatrixInsertionCommand
|
|
(segment, time, endTime,
|
|
m_duplicateElements[i]->event()));
|
|
delete m_duplicateElements[i]->event();
|
|
delete m_duplicateElements[i];
|
|
}
|
|
m_duplicateElements.clear();
|
|
m_quickCopy = false;
|
|
}
|
|
|
|
for (; it != selection->getSegmentEvents().end(); it++) {
|
|
|
|
timeT newTime = (*it)->getAbsoluteTime() + diffTime;
|
|
|
|
int newPitch = 60;
|
|
if ((*it)->has(PITCH)) {
|
|
newPitch = (*it)->get<Int>(PITCH) + diffPitch;
|
|
}
|
|
|
|
Event *newEvent = 0;
|
|
|
|
if (newTime < segment.getStartTime()) {
|
|
newTime = segment.getStartTime();
|
|
}
|
|
|
|
if (newTime + (*it)->getDuration() >= segment.getEndMarkerTime()) {
|
|
timeT limit = getSnapGrid().snapTime
|
|
(segment.getEndMarkerTime() - 1, SnapGrid::SnapLeft);
|
|
if (newTime > limit) newTime = limit;
|
|
timeT newDuration = std::min
|
|
((*it)->getDuration(), segment.getEndMarkerTime() - newTime);
|
|
newEvent = new Event(**it, newTime, newDuration);
|
|
} else {
|
|
newEvent = new Event(**it, newTime);
|
|
}
|
|
|
|
newEvent->set<Int>(BaseProperties::PITCH, newPitch);
|
|
|
|
macro->addCommand(new MatrixModifyCommand(segment,
|
|
(*it),
|
|
newEvent,
|
|
true,
|
|
false));
|
|
newSelection->addEvent(newEvent);
|
|
}
|
|
|
|
normalizeStart = std::min(normalizeStart, newSelection->getStartTime());
|
|
normalizeEnd = std::max(normalizeEnd, newSelection->getEndTime());
|
|
|
|
macro->addCommand(new NormalizeRestsCommand(segment,
|
|
normalizeStart,
|
|
normalizeEnd));
|
|
|
|
m_mParentView->setCurrentSelection(0, false, false);
|
|
m_mParentView->addCommandToHistory(macro);
|
|
m_mParentView->setCurrentSelection(newSelection, false, false);
|
|
|
|
m_mParentView->canvas()->update();
|
|
m_currentElement = 0;
|
|
|
|
setBasicContextHelp();
|
|
}
|
|
|
|
void MatrixMover::ready()
|
|
{
|
|
connect(m_parentView->getCanvasView(), TQ_SIGNAL(contentsMoving (int, int)),
|
|
this, TQ_SLOT(slotMatrixScrolled(int, int)));
|
|
connect(this, TQ_SIGNAL(hoveredOverNoteChanged(int, bool, timeT)),
|
|
m_mParentView, TQ_SLOT(slotHoveredOverNoteChanged(int, bool, timeT)));
|
|
m_mParentView->setCanvasCursor(TQt::sizeAllCursor);
|
|
setBasicContextHelp();
|
|
}
|
|
|
|
void MatrixMover::stow()
|
|
{
|
|
disconnect(m_parentView->getCanvasView(), TQ_SIGNAL(contentsMoving (int, int)),
|
|
this, TQ_SLOT(slotMatrixScrolled(int, int)));
|
|
disconnect(this, TQ_SIGNAL(hoveredOverNoteChanged(int, bool, timeT)),
|
|
m_mParentView, TQ_SLOT(slotHoveredOverNoteChanged(int, bool, timeT)));
|
|
}
|
|
|
|
void MatrixMover::slotMatrixScrolled(int newX, int newY)
|
|
{
|
|
if (!m_currentElement)
|
|
return ;
|
|
|
|
TQPoint newP1(newX, newY), oldP1(m_parentView->getCanvasView()->contentsX(),
|
|
m_parentView->getCanvasView()->contentsY());
|
|
|
|
TQPoint offset = newP1 - oldP1;
|
|
|
|
offset = m_mParentView->inverseMapPoint(offset);
|
|
|
|
TQPoint p(m_currentElement->getCanvasX(), m_currentElement->getCanvasY());
|
|
p += offset;
|
|
|
|
timeT newTime = getSnapGrid().snapX(p.x());
|
|
int newPitch = m_currentStaff->getHeightAtCanvasCoords(p.x(), p.y());
|
|
|
|
handleMouseMove(newTime, newPitch, 0);
|
|
}
|
|
|
|
void MatrixMover::setBasicContextHelp(bool ctrlPressed)
|
|
{
|
|
EventSelection *selection = m_mParentView->getCurrentSelection();
|
|
if (!selection || selection->getAddedEvents() < 2) {
|
|
if (!ctrlPressed) {
|
|
setContextHelp(i18n("Click and drag to move a note; hold Ctrl as well to copy it"));
|
|
} else {
|
|
setContextHelp(i18n("Click and drag to copy a note"));
|
|
}
|
|
} else {
|
|
if (!ctrlPressed) {
|
|
setContextHelp(i18n("Click and drag to move selected notes; hold Ctrl as well to copy"));
|
|
} else {
|
|
setContextHelp(i18n("Click and drag to copy selected notes"));
|
|
}
|
|
}
|
|
}
|
|
|
|
const TQString MatrixMover::ToolName = "mover";
|
|
|
|
}
|
|
#include "MatrixMover.moc"
|