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.
489 lines
15 KiB
489 lines
15 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 "MarkerRuler.h"
|
|
|
|
#include "misc/Debug.h"
|
|
#include "misc/Strings.h"
|
|
#include "base/Composition.h"
|
|
#include "base/RulerScale.h"
|
|
#include "document/RosegardenGUIDoc.h"
|
|
#include "gui/general/GUIPalette.h"
|
|
#include "gui/general/HZoomable.h"
|
|
#include "gui/dialogs/MarkerModifyDialog.h"
|
|
#include "commands/edit/ModifyMarkerCommand.h"
|
|
#include "document/MultiViewCommandHistory.h"
|
|
#include <kxmlguifactory.h>
|
|
#include <tqbrush.h>
|
|
#include <tqcursor.h>
|
|
#include <tqfont.h>
|
|
#include <tqfontmetrics.h>
|
|
#include <tqpainter.h>
|
|
#include <tqpen.h>
|
|
#include <tqpoint.h>
|
|
#include <tqpopupmenu.h>
|
|
#include <tqrect.h>
|
|
#include <tqsize.h>
|
|
#include <tqstring.h>
|
|
#include <tqwidget.h>
|
|
#include <tdelocale.h>
|
|
#include <tdeaction.h>
|
|
#include <kstddirs.h>
|
|
#include <tqtooltip.h>
|
|
|
|
|
|
namespace Rosegarden
|
|
{
|
|
|
|
MarkerRuler::MarkerRuler(RosegardenGUIDoc *doc,
|
|
RulerScale *rulerScale,
|
|
int barHeight,
|
|
double xorigin,
|
|
TQWidget* parent,
|
|
const char* name,
|
|
WFlags f)
|
|
: TQWidget(parent, name, f),
|
|
m_barHeight(barHeight),
|
|
m_xorigin(xorigin),
|
|
m_currentXOffset(0),
|
|
m_width(-1),
|
|
m_clickX(0),
|
|
m_menu(0),
|
|
m_doc(doc),
|
|
m_rulerScale(rulerScale),
|
|
m_parentMainWindow(dynamic_cast<TDEMainWindow*>(doc->parent()))
|
|
{
|
|
// If the parent window has a main window above it, we need to use
|
|
// that as the parent main window, not the document's parent.
|
|
// Otherwise we'll end up adding all actions to the same
|
|
// (document-level) action collection regardless of which window
|
|
// we're in.
|
|
TQObject *probe = parent;
|
|
while (probe && !dynamic_cast<TDEMainWindow *>(probe)) probe = probe->parent();
|
|
if (probe) m_parentMainWindow = dynamic_cast<TDEMainWindow *>(probe);
|
|
|
|
// m_barFont = new TQFont("helvetica", 12);
|
|
// m_barFont->setPixelSize(12);
|
|
m_barFont = new TQFont();
|
|
m_barFont->setPointSize(10);
|
|
|
|
TQString pixmapDir = TDEGlobal::dirs()->findResource("appdata", "pixmaps/");
|
|
TQIconSet icon;
|
|
|
|
// Use the event insert, delete, edit icons because they are
|
|
// actually generic enough to serve for anything. Let's hope they
|
|
// don't become more event-specific in future...
|
|
|
|
icon = TQIconSet(TQPixmap(pixmapDir + "/toolbar/event-insert.png"));
|
|
new TDEAction(i18n("Insert Marker"), icon, 0, this,
|
|
TQ_SLOT(slotInsertMarkerHere()), actionCollection(),
|
|
"insert_marker_here");
|
|
|
|
new TDEAction(i18n("Insert Marker at Playback Position"), 0, this,
|
|
TQ_SLOT(slotInsertMarkerAtPointer()), actionCollection(),
|
|
"insert_marker_at_pointer");
|
|
|
|
icon = TQIconSet(TQPixmap(pixmapDir + "/toolbar/event-delete.png"));
|
|
new TDEAction(i18n("Delete Marker"), icon, 0, this,
|
|
TQ_SLOT(slotDeleteMarker()), actionCollection(),
|
|
"delete_marker");
|
|
|
|
icon = TQIconSet(TQPixmap(pixmapDir + "/toolbar/event-edit.png"));
|
|
new TDEAction(i18n("Edit Marker..."), icon, 0, this,
|
|
TQ_SLOT(slotEditMarker()), actionCollection(),
|
|
"edit_marker");
|
|
|
|
TQToolTip::add
|
|
(this, i18n("Click on a marker to move the playback pointer.\nShift-click to set a range between markers.\nDouble-click to open the marker editor."));
|
|
}
|
|
|
|
MarkerRuler::~MarkerRuler()
|
|
{
|
|
delete m_barFont;
|
|
// we have to do this so that the menu is re-created properly
|
|
// when the main window is itself recreated (on a File->New for instance)
|
|
KXMLGUIFactory* factory = m_parentMainWindow->factory();
|
|
if (factory)
|
|
factory->removeClient(this);
|
|
}
|
|
|
|
void
|
|
MarkerRuler::createMenu()
|
|
{
|
|
setXMLFile("markerruler.rc");
|
|
|
|
KXMLGUIFactory* factory = m_parentMainWindow->factory();
|
|
factory->addClient(this);
|
|
|
|
TQWidget* tmp = factory->container("marker_ruler_menu", this);
|
|
|
|
// if (!tmp) {
|
|
// RG_DEBUG << "MarkerRuler::createMenu() menu not found\n"
|
|
// << domDocument().toString(4) << endl;
|
|
// }
|
|
|
|
m_menu = dynamic_cast<TQPopupMenu*>(tmp);
|
|
|
|
if (!m_menu) {
|
|
RG_DEBUG << "MarkerRuler::createMenu() failed\n";
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
MarkerRuler::scrollHoriz(int x)
|
|
{
|
|
m_currentXOffset = static_cast<int>( -x / getHScaleFactor());
|
|
repaint();
|
|
}
|
|
|
|
TQSize
|
|
MarkerRuler::sizeHint() const
|
|
{
|
|
int lastBar =
|
|
m_rulerScale->getLastVisibleBar();
|
|
double width =
|
|
m_rulerScale->getBarPosition(lastBar) +
|
|
m_rulerScale->getBarWidth(lastBar) + m_xorigin;
|
|
|
|
return TQSize(std::max(int(width), m_width), m_barHeight);
|
|
}
|
|
|
|
TQSize
|
|
MarkerRuler::minimumSizeHint() const
|
|
{
|
|
double firstBarWidth = m_rulerScale->getBarWidth(0) + m_xorigin;
|
|
|
|
return TQSize(static_cast<int>(firstBarWidth), m_barHeight);
|
|
}
|
|
|
|
void
|
|
MarkerRuler::slotInsertMarkerHere()
|
|
{
|
|
emit addMarker(getClickPosition());
|
|
}
|
|
|
|
void
|
|
MarkerRuler::slotInsertMarkerAtPointer()
|
|
{
|
|
emit addMarker(m_doc->getComposition().getPosition());
|
|
}
|
|
|
|
void
|
|
MarkerRuler::slotDeleteMarker()
|
|
{
|
|
RG_DEBUG << "MarkerRuler::slotDeleteMarker()\n";
|
|
|
|
Rosegarden::Marker* marker = getMarkerAtClickPosition();
|
|
|
|
if (marker)
|
|
emit deleteMarker(marker->getID(),
|
|
marker->getTime(),
|
|
marker->getName().c_str(),
|
|
marker->getDescription().c_str());
|
|
}
|
|
|
|
void
|
|
MarkerRuler::slotEditMarker()
|
|
{
|
|
Rosegarden::Marker* marker = getMarkerAtClickPosition();
|
|
|
|
if (!marker) return;
|
|
|
|
// I think the ruler should be doing all this stuff itself, or
|
|
// emitting signals connected to a dedicated marker model object,
|
|
// not just relying on the app object. Same goes for practically
|
|
// everything else we do. Hey ho. Having this here is
|
|
// inconsistent with the other methods, so if anyone wants to move
|
|
// it, be my guest.
|
|
|
|
MarkerModifyDialog dialog(this, &m_doc->getComposition(), marker);
|
|
if (dialog.exec() == TQDialog::Accepted) {
|
|
ModifyMarkerCommand *command =
|
|
new ModifyMarkerCommand(&m_doc->getComposition(),
|
|
marker->getID(),
|
|
dialog.getOriginalTime(),
|
|
dialog.getTime(),
|
|
qstrtostr(dialog.getName()),
|
|
qstrtostr(dialog.getDescription()));
|
|
m_doc->getCommandHistory()->addCommand(command);
|
|
}
|
|
}
|
|
|
|
timeT
|
|
MarkerRuler::getClickPosition()
|
|
{
|
|
timeT t = m_rulerScale->getTimeForX
|
|
(m_clickX - m_xorigin - m_currentXOffset);
|
|
|
|
return t;
|
|
}
|
|
|
|
Rosegarden::Marker*
|
|
MarkerRuler::getMarkerAtClickPosition()
|
|
{
|
|
TQRect clipRect = visibleRect();
|
|
|
|
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();
|
|
}
|
|
|
|
Composition &comp = m_doc->getComposition();
|
|
Composition::markercontainer markers = comp.getMarkers();
|
|
|
|
timeT start = comp.getBarStart(firstBar);
|
|
timeT end = comp.getBarEnd(lastBar);
|
|
|
|
// need these to calculate the visible extents of a marker tag
|
|
TQPainter painter(this);
|
|
painter.setFont(*m_barFont);
|
|
TQFontMetrics metrics = painter.fontMetrics();
|
|
|
|
for (Composition::markerconstiterator i = markers.begin();
|
|
i != markers.end(); ++i) {
|
|
|
|
if ((*i)->getTime() >= start && (*i)->getTime() < end) {
|
|
|
|
TQString name(strtoqstr((*i)->getName()));
|
|
|
|
int x = m_rulerScale->getXForTime((*i)->getTime())
|
|
+ m_xorigin + m_currentXOffset;
|
|
|
|
int width = metrics.width(name) + 5;
|
|
|
|
int nextX = -1;
|
|
Composition::markerconstiterator j = i;
|
|
++j;
|
|
if (j != markers.end()) {
|
|
nextX = m_rulerScale->getXForTime((*j)->getTime())
|
|
+ m_xorigin + m_currentXOffset;
|
|
}
|
|
|
|
if (m_clickX >= x && m_clickX <= x + width) {
|
|
|
|
if (nextX < x || m_clickX <= nextX) {
|
|
|
|
return *i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0L;
|
|
}
|
|
|
|
void
|
|
MarkerRuler::paintEvent(TQPaintEvent*)
|
|
{
|
|
TQPainter painter(this);
|
|
painter.setFont(*m_barFont);
|
|
|
|
if (getHScaleFactor() != 1.0)
|
|
painter.scale(getHScaleFactor(), 1.0);
|
|
|
|
TQRect clipRect = visibleRect();
|
|
|
|
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();
|
|
}
|
|
|
|
painter.drawLine(m_currentXOffset, 0, static_cast<int>(visibleRect().width() / getHScaleFactor()), 0);
|
|
|
|
float minimumWidth = 25.0;
|
|
float testSize = ((float)(m_rulerScale->getBarPosition(firstBar + 1) -
|
|
m_rulerScale->getBarPosition(firstBar)))
|
|
/ minimumWidth;
|
|
|
|
int every = 0;
|
|
int count = 0;
|
|
|
|
if (testSize < 1.0) {
|
|
every = (int(1.0 / testSize));
|
|
|
|
if (every % 2 == 0)
|
|
every++;
|
|
}
|
|
|
|
for (int i = firstBar; i <= lastBar; ++i) {
|
|
|
|
double x = m_rulerScale->getBarPosition(i) + m_xorigin + m_currentXOffset;
|
|
|
|
if ((x * getHScaleFactor()) > clipRect.x() + clipRect.width())
|
|
break;
|
|
|
|
// always the first bar number
|
|
if (every && i != firstBar) {
|
|
if (count < every) {
|
|
count++;
|
|
continue;
|
|
}
|
|
|
|
// reset count if we passed
|
|
count = 0;
|
|
}
|
|
|
|
// adjust count for first bar line
|
|
if (every == firstBar)
|
|
count++;
|
|
|
|
if (i != lastBar) {
|
|
painter.drawLine(static_cast<int>(x), 0, static_cast<int>(x), m_barHeight);
|
|
|
|
// disable worldXForm for text
|
|
TQPoint textDrawPoint = painter.xForm(TQPoint(static_cast<int>(x + 4), 12));
|
|
|
|
bool enableXForm = painter.hasWorldXForm();
|
|
painter.setWorldXForm(false);
|
|
|
|
if (i >= 0)
|
|
painter.drawText(textDrawPoint, TQString("%1").arg(i + 1));
|
|
|
|
painter.setWorldXForm(enableXForm);
|
|
} else {
|
|
const TQPen normalPen = painter.pen();
|
|
;
|
|
TQPen endPen(black, 2);
|
|
painter.setPen(endPen);
|
|
painter.drawLine(static_cast<int>(x), 0, static_cast<int>(x), m_barHeight);
|
|
painter.setPen(normalPen);
|
|
}
|
|
}
|
|
|
|
if (m_doc) {
|
|
Composition &comp = m_doc->getComposition();
|
|
Composition::markercontainer markers = comp.getMarkers();
|
|
Composition::markerconstiterator it;
|
|
|
|
timeT start = comp.getBarStart(firstBar);
|
|
timeT end = comp.getBarEnd(lastBar);
|
|
|
|
TQFontMetrics metrics = painter.fontMetrics();
|
|
|
|
for (it = markers.begin(); it != markers.end(); ++it) {
|
|
if ((*it)->getTime() >= start && (*it)->getTime() < end) {
|
|
TQString name(strtoqstr((*it)->getName()));
|
|
|
|
double x = m_rulerScale->getXForTime((*it)->getTime())
|
|
+ m_xorigin + m_currentXOffset;
|
|
|
|
painter.fillRect(static_cast<int>(x), 1,
|
|
static_cast<int>(metrics.width(name) + 5),
|
|
m_barHeight - 2,
|
|
TQBrush(GUIPalette::getColour(GUIPalette::MarkerBackground)));
|
|
|
|
painter.drawLine(int(x), 1, int(x), m_barHeight - 2);
|
|
painter.drawLine(int(x) + 1, 1, int(x) + 1, m_barHeight - 2);
|
|
|
|
TQPoint textDrawPoint = painter.xForm
|
|
(TQPoint(static_cast<int>(x + 3), m_barHeight - 4));
|
|
|
|
// disable worldXForm for text
|
|
bool enableXForm = painter.hasWorldXForm();
|
|
painter.setWorldXForm(false);
|
|
|
|
painter.drawText(textDrawPoint, name);
|
|
|
|
painter.setWorldXForm(enableXForm);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
MarkerRuler::mousePressEvent(TQMouseEvent *e)
|
|
{
|
|
RG_DEBUG << "MarkerRuler::mousePressEvent: x = " << e->x() << endl;
|
|
|
|
if (!m_doc || !e)
|
|
return;
|
|
|
|
m_clickX = e->x();
|
|
Rosegarden::Marker* clickedMarker = getMarkerAtClickPosition();
|
|
|
|
// if right-click, show popup menu
|
|
//
|
|
if (e->button() == TQt::RightButton) {
|
|
if (!m_menu)
|
|
createMenu();
|
|
if (m_menu) {
|
|
actionCollection()->action("delete_marker")->setEnabled(clickedMarker != 0);
|
|
actionCollection()->action("edit_marker")->setEnabled(clickedMarker != 0);
|
|
m_menu->exec(TQCursor::pos());
|
|
}
|
|
return;
|
|
}
|
|
|
|
bool shiftPressed = ((e->state() & TQt::ShiftButton) != 0);
|
|
|
|
Composition &comp = m_doc->getComposition();
|
|
Composition::markercontainer markers = comp.getMarkers();
|
|
|
|
if (shiftPressed) { // set loop
|
|
|
|
timeT t = m_rulerScale->getTimeForX
|
|
(e->x() - m_xorigin - m_currentXOffset);
|
|
|
|
timeT prev = 0;
|
|
|
|
for (Composition::markerconstiterator i = markers.begin();
|
|
i != markers.end(); ++i) {
|
|
|
|
timeT cur = (*i)->getTime();
|
|
|
|
if (cur >= t) {
|
|
emit setLoop(prev, cur);
|
|
return ;
|
|
}
|
|
|
|
prev = cur;
|
|
}
|
|
|
|
if (prev > 0)
|
|
emit setLoop(prev, comp.getEndMarker());
|
|
|
|
} else { // set pointer to clicked marker
|
|
|
|
if (clickedMarker)
|
|
emit setPointerPosition(clickedMarker->getTime());
|
|
}
|
|
}
|
|
|
|
void
|
|
MarkerRuler::mouseDoubleClickEvent(TQMouseEvent *)
|
|
{
|
|
RG_DEBUG << "MarkerRuler::mouseDoubleClickEvent" << endl;
|
|
|
|
emit editMarkers();
|
|
}
|
|
|
|
}
|
|
#include "MarkerRuler.moc"
|