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.
rosegarden/src/gui/rulers/MarkerRuler.cpp

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"