/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Rosegarden A MIDI and audio sequencer and musical notation editor. Copyright 2000-2011 the Rosegarden development team. 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 "LoopRuler.h" #include "misc/Debug.h" #include "base/RulerScale.h" #include "base/SnapGrid.h" #include "gui/general/GUIPalette.h" #include "gui/general/HZoomable.h" #include "gui/general/RosegardenScrollView.h" #include "document/RosegardenDocument.h" #include #include #include #include #include #include #include #include #include #include namespace Rosegarden { LoopRuler::LoopRuler(RosegardenDocument *doc, RulerScale *rulerScale, int height, double xorigin, bool invert, QWidget *parent, const char *name) : QWidget(parent, name), m_height(height), m_xorigin(xorigin), m_invert(invert), m_currentXOffset(0), m_width( -1), m_activeMousePress(false), m_doc(doc), m_rulerScale(rulerScale), m_defaultGrid(rulerScale), m_loopGrid(new SnapGrid(rulerScale)), m_grid(&m_defaultGrid), m_quickMarkerPen(QPen(GUIPalette::getColour(GUIPalette::QuickMarker), 4)), m_loopingMode(false), m_startLoop(0), m_endLoop(0) { /* * I need to understand if this ruler is being built for the main * window (Track Editor) or for any of the segment editors. Apparently * the name parameter is supplied (non-NULL) only for the main window. * I can't find of any other (more decent) way to do this. Sorry. */ m_mainWindow = (name != 0 && std::string(name).length() != 0); //setAutoFillBackground(true); //setBackgroundColor(GUIPalette::getColour(GUIPalette::LoopRulerBackground)); // QString localStyle=("background: #787878; color: #FFFFFF;"); // setStyleSheet(localStyle); // Always snap loop extents to beats; by default apply no snap to // pointer position // m_defaultGrid.setSnapTime(SnapGrid::NoSnap); m_loopGrid->setSnapTime(SnapGrid::SnapToBeat); setToolTip(tr("

Click and drag to move the playback pointer.

Shift-click and drag to set a range for looping or editing.

Shift-click to clear the loop or range.

Ctrl-click and drag to move the playback pointer with snap to beat.

Double-click to start playback.

")); } LoopRuler::~LoopRuler() { delete m_loopGrid; } void LoopRuler::setSnapGrid(const SnapGrid *grid) { delete m_loopGrid; if (grid == 0) { m_grid = &m_defaultGrid; m_loopGrid = new SnapGrid(m_defaultGrid); } else { m_grid = grid; m_loopGrid = new SnapGrid(*grid); } m_loopGrid->setSnapTime(SnapGrid::SnapToBeat); } void LoopRuler::scrollHoriz(int x) { // int w = width(); //, h = height(); // int dx = x - ( -m_currentXOffset); if (getHScaleFactor() != 1.0) { m_currentXOffset = static_cast( -x / getHScaleFactor()); } else { m_currentXOffset = -x; } // if (dx > w*3 / 4 || dx < -w*3 / 4) { // update(); // return ; // } /*### These bitBlts are not working RG_DEBUG << "LoopRuler::scrollHoriz > Dodgy bitBlt start?" << endl; if (dx > 0) { // moving right, so the existing stuff moves left bitBlt(this, 0, 0, this, dx, 0, w - dx, h); repaint(w - dx, 0, dx, h); } else { // moving left, so the existing stuff moves right bitBlt(this, -dx, 0, this, 0, 0, w + dx, h); repaint(0, 0, -dx, h); } RG_DEBUG << "LoopRuler::scrollHoriz > Dodgy bitBlt end?" << endl; */ update(); } QSize LoopRuler::sizeHint() const { double width = m_rulerScale->getBarPosition(m_rulerScale->getLastVisibleBar()) + m_rulerScale->getBarWidth(m_rulerScale->getLastVisibleBar()) + m_xorigin; QSize res(std::max(int(width), m_width), m_height); return res; } QSize LoopRuler::minimumSizeHint() const { double firstBarWidth = m_rulerScale->getBarWidth(0) + m_xorigin; QSize res = QSize(int(firstBarWidth), m_height); return res; } void LoopRuler::paintEvent(QPaintEvent* e) { // RG_DEBUG << "LoopRuler::paintEvent" << endl; QPainter paint(this); if (getHScaleFactor() != 1.0) paint.scale(getHScaleFactor(), 1.0); paint.setClipRegion(e->region()); paint.setClipRect(e->rect().normalize()); // In a stylesheet world, we have to draw the ruler backgrounds. Hopefully // this won't be too flickery. (Seems OK, and best of all it actually // worked!) QBrush bg = QBrush(GUIPalette::getColour(GUIPalette::LoopRulerBackground)); paint.fillRect(e->rect(), bg); paint.setBrush(palette().foreground()); drawBarSections(&paint); drawLoopMarker(&paint); if (m_mainWindow) { timeT tQM = m_doc->getQuickMarkerTime(); if (tQM >= 0) { // draw quick marker double xQM = m_rulerScale->getXForTime(tQM) + m_xorigin + m_currentXOffset; paint.setPen(m_quickMarkerPen); // looks necessary to compensate for shift in the CompositionView (cursor) paint.translate(1, 0); // draw red segment paint.drawLine(int(xQM), 1, int(xQM), m_height-1); } } } void LoopRuler::drawBarSections(QPainter* paint) { QRect clipRect = paint->clipRegion().boundingRect(); int firstBar = m_rulerScale->getBarForX(clipRect.x() - m_currentXOffset - m_xorigin); int lastBar = m_rulerScale->getLastVisibleBar(); if (firstBar < m_rulerScale->getFirstVisibleBar()) { firstBar = m_rulerScale->getFirstVisibleBar(); } paint->setPen(GUIPalette::getColour(GUIPalette::LoopRulerForeground)); for (int i = firstBar; i < lastBar; ++i) { double x = m_rulerScale->getBarPosition(i) + m_currentXOffset + m_xorigin; if ((x * getHScaleFactor()) > clipRect.x() + clipRect.width()) break; double width = m_rulerScale->getBarWidth(i); if (width == 0) continue; if (x + width < clipRect.x()) continue; if (m_invert) { paint->drawLine(int(x), 0, int(x), 5*m_height / 7); } else { paint->drawLine(int(x), 2*m_height / 7, int(x), m_height); } double beatAccumulator = m_rulerScale->getBeatWidth(i); double inc = beatAccumulator; if (inc == 0) continue; for (; beatAccumulator < width; beatAccumulator += inc) { if (m_invert) { paint->drawLine(int(x + beatAccumulator), 0, int(x + beatAccumulator), 2 * m_height / 7); } else { paint->drawLine(int(x + beatAccumulator), 5*m_height / 7, int(x + beatAccumulator), m_height); } } } } void LoopRuler::drawLoopMarker(QPainter* paint) { double x1 = (int)m_rulerScale->getXForTime(m_startLoop); double x2 = (int)m_rulerScale->getXForTime(m_endLoop); if (x1 > x2) { double tmp = x2; x2 = x1; x1 = tmp; } x1 += m_currentXOffset + m_xorigin; x2 += m_currentXOffset + m_xorigin; paint->save(); paint->setBrush(GUIPalette::getColour(GUIPalette::LoopHighlight)); paint->setPen(GUIPalette::getColour(GUIPalette::LoopHighlight)); paint->drawRect(static_cast(x1), 0, static_cast(x2 - x1), m_height); paint->restore(); } double LoopRuler::mouseEventToSceneX(QMouseEvent *mE) { double x = mE->pos().x() / getHScaleFactor() - m_currentXOffset - m_xorigin; return x; } void LoopRuler::mousePressEvent(QMouseEvent *mE) { RG_DEBUG << "LoopRuler::mousePressEvent: x = " << mE->x() << endl; Qt::ButtonState bs = mE->state(); setLoopingMode((bs & Qt::ShiftModifier) != 0); if (mE->button() == Qt::LeftButton) { double x = mouseEventToSceneX(mE); if (m_loopingMode) { m_endLoop = m_startLoop = m_loopGrid->snapX(x); } else { // If we are still using the default grid, that means we are being // used by the TrackEditor (instead of the MatrixEditor). if (m_grid == &m_defaultGrid) { // If the ctrl key is pressed, enable snap to beat if ((bs & Qt::ControlModifier) != 0) m_defaultGrid.setSnapTime(SnapGrid::SnapToBeat); else m_defaultGrid.setSnapTime(SnapGrid::NoSnap); } // No -- now that we're emitting when the button is // released, we _don't_ want to emit here as well -- // otherwise we get an irritating stutter when simply // clicking on the ruler during playback //RG_DEBUG << "emitting setPointerPosition(" << // m_rulerScale->getTimeForX(x) << ")" << endl; // emit setPointerPosition(m_rulerScale->getTimeForX(x)); // But we want to see the pointer under the mouse as soon as the // button is pressed, before we begin to drag it. emit dragPointerToPosition(m_grid->snapX(x)); m_lastMouseXPos = x; } m_activeMousePress = true; emit startMouseMove(RosegardenScrollView::FollowHorizontal); } } void LoopRuler::mouseReleaseEvent(QMouseEvent *mE) { if (mE->button() == Qt::LeftButton) { if (m_loopingMode) { // Cancel the loop if there was no drag // if (m_endLoop == m_startLoop) { m_endLoop = m_startLoop = 0; // to clear any other loop rulers emit setLoop(m_startLoop, m_endLoop); update(); } // emit with the args around the right way // if (m_endLoop < m_startLoop) emit setLoop(m_endLoop, m_startLoop); else emit setLoop(m_startLoop, m_endLoop); } else { // we need to re-emit this signal so that when the user releases the button // after dragging the pointer, the pointer's position is updated again in the // other views (typically, in the seg. canvas while the user has dragged the pointer // in an edit view) // emit setPointerPosition(m_grid->snapX(m_lastMouseXPos)); } emit stopMouseMove(); m_activeMousePress = false; } } void LoopRuler::mouseDoubleClickEvent(QMouseEvent *mE) { double x = mouseEventToSceneX(mE); if (x < 0) x = 0; RG_DEBUG << "LoopRuler::mouseDoubleClickEvent: x = " << x << ", looping = " << m_loopingMode << endl; if (mE->button() == Qt::LeftButton && !m_loopingMode) emit setPlayPosition(m_grid->snapX(x)); } void LoopRuler::mouseMoveEvent(QMouseEvent *mE) { // If we are still using the default grid, that means we are being // used by the TrackEditor (instead of the MatrixEditor). if (m_grid == &m_defaultGrid) { Qt::ButtonState bs = mE->state(); // If the ctrl key is pressed, enable snap to beat if ((bs & Qt::ControlModifier) != 0) m_defaultGrid.setSnapTime(SnapGrid::SnapToBeat); else m_defaultGrid.setSnapTime(SnapGrid::NoSnap); } double x = mouseEventToSceneX(mE); if (x < 0) x = 0; if (m_loopingMode) { if (m_loopGrid->snapX(x) != m_endLoop) { m_endLoop = m_loopGrid->snapX(x); emit dragLoopToPosition(m_endLoop); update(); } } else { emit dragPointerToPosition(m_grid->snapX(x)); m_lastMouseXPos = x; } emit mouseMove(); } void LoopRuler::slotSetLoopMarker(timeT startLoop, timeT endLoop) { m_startLoop = startLoop; m_endLoop = endLoop; update(); } } #include "LoopRuler.moc"