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.
531 lines
17 KiB
531 lines
17 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 "SegmentSelector.h"
|
|
|
|
#include "base/Event.h"
|
|
#include <tdelocale.h>
|
|
#include "misc/Debug.h"
|
|
#include "base/Composition.h"
|
|
#include "base/RealTime.h"
|
|
#include "base/SnapGrid.h"
|
|
#include "base/Selection.h"
|
|
#include "base/Track.h"
|
|
#include "commands/segment/SegmentQuickCopyCommand.h"
|
|
#include "commands/segment/SegmentReconfigureCommand.h"
|
|
#include "CompositionItemHelper.h"
|
|
#include "CompositionModel.h"
|
|
#include "CompositionView.h"
|
|
#include "document/RosegardenGUIDoc.h"
|
|
#include "document/ConfigGroups.h"
|
|
#include "gui/general/BaseTool.h"
|
|
#include "gui/general/RosegardenCanvasView.h"
|
|
#include "SegmentPencil.h"
|
|
#include "SegmentResizer.h"
|
|
#include "SegmentTool.h"
|
|
#include "SegmentToolBox.h"
|
|
#include <tdeapplication.h>
|
|
#include <tdeconfig.h>
|
|
#include <tqcursor.h>
|
|
#include <tqevent.h>
|
|
#include <tqpoint.h>
|
|
#include <tqrect.h>
|
|
#include <tqstring.h>
|
|
|
|
|
|
namespace Rosegarden
|
|
{
|
|
|
|
SegmentSelector::SegmentSelector(CompositionView *c, RosegardenGUIDoc *d)
|
|
: SegmentTool(c, d),
|
|
m_segmentAddMode(false),
|
|
m_segmentCopyMode(false),
|
|
m_segmentQuickCopyDone(false),
|
|
m_buttonPressed(false),
|
|
m_selectionMoveStarted(false),
|
|
m_dispatchTool(0)
|
|
{
|
|
RG_DEBUG << "SegmentSelector()\n";
|
|
}
|
|
|
|
SegmentSelector::~SegmentSelector()
|
|
{}
|
|
|
|
void SegmentSelector::ready()
|
|
{
|
|
m_canvas->viewport()->setCursor(TQt::arrowCursor);
|
|
connect(m_canvas, TQ_SIGNAL(contentsMoving (int, int)),
|
|
this, TQ_SLOT(slotCanvasScrolled(int, int)));
|
|
setContextHelp(i18n("Click and drag to select segments"));
|
|
}
|
|
|
|
void SegmentSelector::stow()
|
|
{}
|
|
|
|
void SegmentSelector::slotCanvasScrolled(int newX, int newY)
|
|
{
|
|
TQMouseEvent tmpEvent(TQEvent::MouseMove,
|
|
m_canvas->viewport()->mapFromGlobal(TQCursor::pos()) + TQPoint(newX, newY),
|
|
TQt::NoButton, TQt::NoButton);
|
|
handleMouseMove(&tmpEvent);
|
|
}
|
|
|
|
void
|
|
SegmentSelector::handleMouseButtonPress(TQMouseEvent *e)
|
|
{
|
|
RG_DEBUG << "SegmentSelector::handleMouseButtonPress\n";
|
|
m_buttonPressed = true;
|
|
|
|
CompositionItem item = m_canvas->getFirstItemAt(e->pos());
|
|
|
|
// If we're in segmentAddMode or not clicking on an item then we don't
|
|
// clear the selection vector. If we're clicking on an item and it's
|
|
// not in the selection - then also clear the selection.
|
|
//
|
|
if ((!m_segmentAddMode && !item) ||
|
|
(!m_segmentAddMode && !(m_canvas->getModel()->isSelected(item)))) {
|
|
m_canvas->getModel()->clearSelected();
|
|
}
|
|
|
|
if (item) {
|
|
|
|
// Fifteen percent of the width of the SegmentItem, up to 10px
|
|
//
|
|
int threshold = int(float(item->rect().width()) * 0.15);
|
|
if (threshold == 0) threshold = 1;
|
|
if (threshold > 10) threshold = 10;
|
|
|
|
bool start = false;
|
|
|
|
// Resize if we're dragging from the edge, provided we aren't
|
|
// in segment-add mode with at least one segment already
|
|
// selected -- as we aren't able to resize multiple segments
|
|
// at once, we should assume the segment-add aspect takes
|
|
// priority
|
|
|
|
if ((!m_segmentAddMode ||
|
|
!m_canvas->getModel()->haveSelection()) &&
|
|
SegmentResizer::cursorIsCloseEnoughToEdge(item, e->pos(), threshold, start)) {
|
|
|
|
SegmentResizer* resizer =
|
|
dynamic_cast<SegmentResizer*>(getToolBox()->getTool(SegmentResizer::ToolName));
|
|
|
|
resizer->setEdgeThreshold(threshold);
|
|
|
|
// For the moment we only allow resizing of a single segment
|
|
// at a time.
|
|
//
|
|
m_canvas->getModel()->clearSelected();
|
|
|
|
m_dispatchTool = resizer;
|
|
|
|
m_dispatchTool->ready(); // set mouse cursor
|
|
m_dispatchTool->handleMouseButtonPress(e);
|
|
return ;
|
|
}
|
|
|
|
bool selecting = true;
|
|
|
|
if (m_segmentAddMode && m_canvas->getModel()->isSelected(item)) {
|
|
selecting = false;
|
|
} else {
|
|
// put the segment in 'move' mode only if it's being selected
|
|
m_canvas->getModel()->startChange(item, CompositionModel::ChangeMove);
|
|
}
|
|
|
|
m_canvas->getModel()->setSelected(item, selecting);
|
|
|
|
// Moving
|
|
//
|
|
// RG_DEBUG << "SegmentSelector::handleMouseButtonPress - m_currentItem = " << item << endl;
|
|
m_currentItem = item;
|
|
m_clickPoint = e->pos();
|
|
|
|
int guideX = item->rect().x();
|
|
int guideY = item->rect().y();
|
|
|
|
m_canvas->setGuidesPos(guideX, guideY);
|
|
|
|
m_canvas->setDrawGuides(true);
|
|
|
|
} else {
|
|
|
|
// Add on middle button or ctrl+left - bounding box on rest
|
|
//
|
|
if (e->button() == TQt::MidButton ||
|
|
(e->button() == TQt::LeftButton && (e->state() & ControlButton))) {
|
|
|
|
m_dispatchTool = getToolBox()->getTool(SegmentPencil::ToolName);
|
|
|
|
if (m_dispatchTool) {
|
|
m_dispatchTool->ready(); // set mouse cursor
|
|
m_dispatchTool->handleMouseButtonPress(e);
|
|
}
|
|
|
|
return ;
|
|
|
|
} else {
|
|
|
|
m_canvas->setSelectionRectPos(e->pos());
|
|
m_canvas->setDrawSelectionRect(true);
|
|
if (!m_segmentAddMode)
|
|
m_canvas->getModel()->clearSelected();
|
|
|
|
}
|
|
}
|
|
|
|
// Tell the RosegardenGUIView that we've selected some new Segments -
|
|
// when the list is empty we're just unselecting.
|
|
//
|
|
m_canvas->getModel()->signalSelection();
|
|
|
|
m_passedInertiaEdge = false;
|
|
}
|
|
|
|
void
|
|
SegmentSelector::handleMouseButtonRelease(TQMouseEvent *e)
|
|
{
|
|
m_buttonPressed = false;
|
|
|
|
// Hide guides and stuff
|
|
//
|
|
m_canvas->setDrawGuides(false);
|
|
m_canvas->hideTextFloat();
|
|
|
|
if (m_dispatchTool) {
|
|
m_dispatchTool->handleMouseButtonRelease(e);
|
|
m_dispatchTool = 0;
|
|
m_canvas->viewport()->setCursor(TQt::arrowCursor);
|
|
return ;
|
|
}
|
|
|
|
int startDragTrackPos = m_canvas->grid().getYBin(m_clickPoint.y());
|
|
int currentTrackPos = m_canvas->grid().getYBin(e->pos().y());
|
|
int trackDiff = currentTrackPos - startDragTrackPos;
|
|
|
|
if (!m_currentItem) {
|
|
m_canvas->setDrawSelectionRect(false);
|
|
m_canvas->getModel()->finalizeSelectionRect();
|
|
m_canvas->getModel()->signalSelection();
|
|
return ;
|
|
}
|
|
|
|
m_canvas->viewport()->setCursor(TQt::arrowCursor);
|
|
|
|
Composition &comp = m_doc->getComposition();
|
|
|
|
if (m_canvas->getModel()->isSelected(m_currentItem)) {
|
|
|
|
CompositionModel::itemcontainer& changingItems = m_canvas->getModel()->getChangingItems();
|
|
CompositionModel::itemcontainer::iterator it;
|
|
|
|
if (changeMade()) {
|
|
|
|
SegmentReconfigureCommand *command =
|
|
new SegmentReconfigureCommand
|
|
(m_selectedItems.size() == 1 ? i18n("Move Segment") :
|
|
i18n("Move Segments"));
|
|
|
|
for (it = changingItems.begin();
|
|
it != changingItems.end();
|
|
it++) {
|
|
|
|
CompositionItem item = *it;
|
|
|
|
Segment* segment = CompositionItemHelper::getSegment(item);
|
|
|
|
TrackId origTrackId = segment->getTrack();
|
|
int trackPos = comp.getTrackPositionById(origTrackId);
|
|
trackPos += trackDiff;
|
|
|
|
if (trackPos < 0) {
|
|
trackPos = 0;
|
|
} else if (trackPos >= comp.getNbTracks()) {
|
|
trackPos = comp.getNbTracks() - 1;
|
|
}
|
|
|
|
Track *newTrack = comp.getTrackByPosition(trackPos);
|
|
int newTrackId = origTrackId;
|
|
if (newTrack) newTrackId = newTrack->getId();
|
|
|
|
timeT itemStartTime = CompositionItemHelper::getStartTime
|
|
(item, m_canvas->grid());
|
|
|
|
// We absolutely don't want to snap the end time to
|
|
// the grid. We want it to remain exactly the same as
|
|
// it was, but relative to the new start time.
|
|
timeT itemEndTime = itemStartTime + segment->getEndMarkerTime()
|
|
- segment->getStartTime();
|
|
|
|
// std::cerr << "releasing segment " << segment << ": mouse started at track " << startDragTrackPos << ", is now at " << currentTrackPos << ", diff is " << trackDiff << ", moving from track pos " << comp.getTrackPositionById(origTrackId) << " to " << trackPos << ", id " << origTrackId << " to " << newTrackId << std::endl;
|
|
|
|
command->addSegment(segment,
|
|
itemStartTime,
|
|
itemEndTime,
|
|
newTrackId);
|
|
}
|
|
|
|
addCommandToHistory(command);
|
|
}
|
|
|
|
m_canvas->getModel()->endChange();
|
|
m_canvas->slotUpdateSegmentsDrawBuffer();
|
|
}
|
|
|
|
// if we've just finished a quick copy then drop the Z level back
|
|
if (m_segmentQuickCopyDone) {
|
|
m_segmentQuickCopyDone = false;
|
|
// m_currentItem->setZ(2); // see SegmentItem::setSelected --??
|
|
}
|
|
|
|
setChangeMade(false);
|
|
|
|
m_selectionMoveStarted = false;
|
|
|
|
m_currentItem = CompositionItem();
|
|
|
|
setContextHelpFor(e->pos());
|
|
}
|
|
|
|
int
|
|
SegmentSelector::handleMouseMove(TQMouseEvent *e)
|
|
{
|
|
if (!m_buttonPressed) {
|
|
setContextHelpFor(e->pos(), (e->state() & TQt::ControlButton));
|
|
return RosegardenCanvasView::NoFollow;
|
|
}
|
|
|
|
if (m_dispatchTool) {
|
|
return m_dispatchTool->handleMouseMove(e);
|
|
}
|
|
|
|
Composition &comp = m_doc->getComposition();
|
|
|
|
if (!m_currentItem) {
|
|
|
|
// RG_DEBUG << "SegmentSelector::handleMouseMove: no current item\n";
|
|
|
|
// do a bounding box
|
|
TQRect selectionRect = m_canvas->getSelectionRect();
|
|
|
|
m_canvas->setDrawSelectionRect(true);
|
|
|
|
// same as for notation view
|
|
int w = int(e->pos().x() - selectionRect.x());
|
|
int h = int(e->pos().y() - selectionRect.y());
|
|
if (w > 0)
|
|
++w;
|
|
else
|
|
--w;
|
|
if (h > 0)
|
|
++h;
|
|
else
|
|
--h;
|
|
|
|
// Translate these points
|
|
//
|
|
m_canvas->setSelectionRectSize(w, h);
|
|
|
|
m_canvas->getModel()->signalSelection();
|
|
return RosegardenCanvasView::FollowHorizontal | RosegardenCanvasView::FollowVertical;
|
|
}
|
|
|
|
m_canvas->viewport()->setCursor(TQt::sizeAllCursor);
|
|
|
|
if (m_segmentCopyMode && !m_segmentQuickCopyDone) {
|
|
KMacroCommand *mcommand = new KMacroCommand
|
|
(SegmentQuickCopyCommand::getGlobalName());
|
|
|
|
SegmentSelection selectedItems = m_canvas->getSelectedSegments();
|
|
SegmentSelection::iterator it;
|
|
for (it = selectedItems.begin();
|
|
it != selectedItems.end();
|
|
it++) {
|
|
SegmentQuickCopyCommand *command =
|
|
new SegmentQuickCopyCommand(*it);
|
|
|
|
mcommand->addCommand(command);
|
|
}
|
|
|
|
addCommandToHistory(mcommand);
|
|
|
|
// generate SegmentItem
|
|
//
|
|
m_canvas->updateContents();
|
|
m_segmentQuickCopyDone = true;
|
|
}
|
|
|
|
m_canvas->setSnapGrain(true);
|
|
|
|
int startDragTrackPos = m_canvas->grid().getYBin(m_clickPoint.y());
|
|
int currentTrackPos = m_canvas->grid().getYBin(e->pos().y());
|
|
int trackDiff = currentTrackPos - startDragTrackPos;
|
|
|
|
if (m_canvas->getModel()->isSelected(m_currentItem)) {
|
|
|
|
if (!m_canvas->isFineGrain()) {
|
|
setContextHelp(i18n("Hold Shift to avoid snapping to beat grid"));
|
|
} else {
|
|
clearContextHelp();
|
|
}
|
|
|
|
// RG_DEBUG << "SegmentSelector::handleMouseMove: current item is selected\n";
|
|
|
|
if (!m_selectionMoveStarted) { // start move on selected items only once
|
|
m_canvas->getModel()->startChangeSelection(CompositionModel::ChangeMove);
|
|
m_selectionMoveStarted = true;
|
|
}
|
|
|
|
CompositionModel::itemcontainer& changingItems = m_canvas->getModel()->getChangingItems();
|
|
setCurrentItem(CompositionItemHelper::findSiblingCompositionItem(changingItems, m_currentItem));
|
|
|
|
CompositionModel::itemcontainer::iterator it;
|
|
int guideX = 0;
|
|
int guideY = 0;
|
|
|
|
for (it = changingItems.begin();
|
|
it != changingItems.end();
|
|
++it) {
|
|
|
|
// RG_DEBUG << "SegmentSelector::handleMouseMove() : movingItem at "
|
|
// << (*it)->rect().x() << "," << (*it)->rect().y() << endl;
|
|
|
|
int dx = e->pos().x() - m_clickPoint.x(),
|
|
dy = e->pos().y() - m_clickPoint.y();
|
|
|
|
const int inertiaDistance = m_canvas->grid().getYSnap() / 3;
|
|
if (!m_passedInertiaEdge &&
|
|
(dx < inertiaDistance && dx > -inertiaDistance) &&
|
|
(dy < inertiaDistance && dy > -inertiaDistance)) {
|
|
return RosegardenCanvasView::NoFollow;
|
|
} else {
|
|
m_passedInertiaEdge = true;
|
|
}
|
|
|
|
timeT newStartTime = m_canvas->grid().snapX((*it)->savedRect().x() + dx);
|
|
|
|
int newX = int(m_canvas->grid().getRulerScale()->getXForTime(newStartTime));
|
|
|
|
int trackPos = m_canvas->grid().getYBin((*it)->savedRect().y());
|
|
|
|
// std::cerr << "segment " << *it << ": mouse started at track " << startDragTrackPos << ", is now at " << currentTrackPos << ", trackPos from " << trackPos << " to ";
|
|
|
|
trackPos += trackDiff;
|
|
|
|
// std::cerr << trackPos << std::endl;
|
|
|
|
if (trackPos < 0) {
|
|
trackPos = 0;
|
|
} else if (trackPos >= comp.getNbTracks()) {
|
|
trackPos = comp.getNbTracks() - 1;
|
|
}
|
|
|
|
int newY = m_canvas->grid().getYBinCoordinate(trackPos);
|
|
|
|
(*it)->moveTo(newX, newY);
|
|
setChangeMade(true);
|
|
}
|
|
|
|
if (changeMade())
|
|
m_canvas->getModel()->signalContentChange();
|
|
|
|
guideX = m_currentItem->rect().x();
|
|
guideY = m_currentItem->rect().y();
|
|
|
|
m_canvas->setGuidesPos(guideX, guideY);
|
|
|
|
timeT currentItemStartTime = m_canvas->grid().snapX(m_currentItem->rect().x());
|
|
|
|
RealTime time = comp.getElapsedRealTime(currentItemStartTime);
|
|
TQString ms;
|
|
ms.sprintf("%03d", time.msec());
|
|
|
|
int bar, beat, fraction, remainder;
|
|
comp.getMusicalTimeForAbsoluteTime(currentItemStartTime, bar, beat, fraction, remainder);
|
|
|
|
TQString posString = TQString("%1.%2s (%3, %4, %5)")
|
|
.arg(time.sec).arg(ms)
|
|
.arg(bar + 1).arg(beat).arg(fraction);
|
|
|
|
m_canvas->setTextFloat(guideX + 10, guideY - 30, posString);
|
|
m_canvas->updateContents();
|
|
|
|
} else {
|
|
// RG_DEBUG << "SegmentSelector::handleMouseMove: current item not selected\n";
|
|
}
|
|
|
|
return RosegardenCanvasView::FollowHorizontal | RosegardenCanvasView::FollowVertical;
|
|
}
|
|
|
|
void SegmentSelector::setContextHelpFor(TQPoint p, bool ctrlPressed)
|
|
{
|
|
kapp->config()->setGroup(GeneralOptionsConfigGroup);
|
|
if (!kapp->config()->readBoolEntry("toolcontexthelp", true)) return;
|
|
|
|
CompositionItem item = m_canvas->getFirstItemAt(p);
|
|
|
|
if (!item) {
|
|
setContextHelp(i18n("Click and drag to select segments; middle-click and drag to draw an empty segment"));
|
|
|
|
} else {
|
|
|
|
// Same logic as in handleMouseButtonPress to establish
|
|
// whether we'd be moving or resizing
|
|
|
|
int threshold = int(float(item->rect().width()) * 0.15);
|
|
if (threshold == 0) threshold = 1;
|
|
if (threshold > 10) threshold = 10;
|
|
bool start = false;
|
|
|
|
if ((!m_segmentAddMode ||
|
|
!m_canvas->getModel()->haveSelection()) &&
|
|
SegmentResizer::cursorIsCloseEnoughToEdge(item, p,
|
|
threshold, start)) {
|
|
if (!ctrlPressed) {
|
|
setContextHelp(i18n("Click and drag to resize a segment; hold Ctrl as well to rescale its contents"));
|
|
} else {
|
|
setContextHelp(i18n("Click and drag to rescale segment"));
|
|
}
|
|
} else {
|
|
if (m_canvas->getModel()->haveMultipleSelection()) {
|
|
if (!ctrlPressed) {
|
|
setContextHelp(i18n("Click and drag to move segments; hold Ctrl as well to copy them"));
|
|
} else {
|
|
setContextHelp(i18n("Click and drag to copy segments"));
|
|
}
|
|
} else {
|
|
if (!ctrlPressed) {
|
|
setContextHelp(i18n("Click and drag to move segment; hold Ctrl as well to copy it; double-click to edit"));
|
|
} else {
|
|
setContextHelp(i18n("Click and drag to copy segment"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const TQString SegmentSelector::ToolName = "segmentselector";
|
|
|
|
}
|
|
#include "SegmentSelector.moc"
|