diff layer/TimeInstantLayer.cpp @ 0:fc9323a41f5a

start base : Sonic Visualiser sv1-1.0rc1
author lbajardsilogic
date Fri, 11 May 2007 09:08:14 +0000
parents
children d8e6709e9075
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/TimeInstantLayer.cpp	Fri May 11 09:08:14 2007 +0000
@@ -0,0 +1,782 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam.
+    
+    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 "TimeInstantLayer.h"
+
+#include "data/model/Model.h"
+#include "base/RealTime.h"
+#include "view/View.h"
+#include "base/Profiler.h"
+#include "base/Clipboard.h"
+
+#include "data/model/SparseOneDimensionalModel.h"
+
+#include "widgets/ItemEditDialog.h"
+
+#include <QPainter>
+#include <QMouseEvent>
+
+#include <iostream>
+#include <cmath>
+
+TimeInstantLayer::TimeInstantLayer() :
+    Layer(),
+    m_model(0),
+    m_editing(false),
+    m_editingPoint(0, tr("New Point")),
+    m_editingCommand(0),
+    m_colour(QColor(200, 50, 255)),
+    m_plotStyle(PlotInstants)
+{
+    
+}
+
+void
+TimeInstantLayer::setModel(SparseOneDimensionalModel *model)
+{
+    if (m_model == model) return;
+    m_model = model;
+
+    connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged()));
+    connect(m_model, SIGNAL(modelChanged(size_t, size_t)),
+	    this, SIGNAL(modelChanged(size_t, size_t)));
+
+    connect(m_model, SIGNAL(completionChanged()),
+	    this, SIGNAL(modelCompletionChanged()));
+
+    std::cerr << "TimeInstantLayer::setModel(" << model << ")" << std::endl;
+
+    emit modelReplaced();
+}
+
+Layer::PropertyList
+TimeInstantLayer::getProperties() const
+{
+    PropertyList list;
+    list.push_back("Colour");
+    list.push_back("Plot Type");
+    return list;
+}
+
+QString
+TimeInstantLayer::getPropertyLabel(const PropertyName &name) const
+{
+    if (name == "Colour") return tr("Colour");
+    if (name == "Plot Type") return tr("Plot Type");
+    return "";
+}
+
+Layer::PropertyType
+TimeInstantLayer::getPropertyType(const PropertyName &) const
+{
+    return ValueProperty;
+}
+
+int
+TimeInstantLayer::getPropertyRangeAndValue(const PropertyName &name,
+                                           int *min, int *max, int *deflt) const
+{
+    int val = 0;
+
+    if (name == "Colour") {
+
+	if (min) *min = 0;
+	if (max) *max = 5;
+        if (deflt) *deflt = 0;
+
+	if (m_colour == Qt::black) val = 0;
+	else if (m_colour == Qt::darkRed) val = 1;
+	else if (m_colour == Qt::darkBlue) val = 2;
+	else if (m_colour == Qt::darkGreen) val = 3;
+	else if (m_colour == QColor(200, 50, 255)) val = 4;
+	else if (m_colour == QColor(255, 150, 50)) val = 5;
+
+    } else if (name == "Plot Type") {
+	
+	if (min) *min = 0;
+	if (max) *max = 1;
+        if (deflt) *deflt = 0;
+	
+	val = int(m_plotStyle);
+
+    } else {
+	
+	val = Layer::getPropertyRangeAndValue(name, min, max, deflt);
+    }
+
+    return val;
+}
+
+QString
+TimeInstantLayer::getPropertyValueLabel(const PropertyName &name,
+				    int value) const
+{
+    if (name == "Colour") {
+	switch (value) {
+	default:
+	case 0: return tr("Black");
+	case 1: return tr("Red");
+	case 2: return tr("Blue");
+	case 3: return tr("Green");
+	case 4: return tr("Purple");
+	case 5: return tr("Orange");
+	}
+    } else if (name == "Plot Type") {
+	switch (value) {
+	default:
+	case 0: return tr("Instants");
+	case 1: return tr("Segmentation");
+	}
+    }
+    return tr("<unknown>");
+}
+
+void
+TimeInstantLayer::setProperty(const PropertyName &name, int value)
+{
+    if (name == "Colour") {
+	switch (value) {
+	default:
+	case 0:	setBaseColour(Qt::black); break;
+	case 1: setBaseColour(Qt::darkRed); break;
+	case 2: setBaseColour(Qt::darkBlue); break;
+	case 3: setBaseColour(Qt::darkGreen); break;
+	case 4: setBaseColour(QColor(200, 50, 255)); break;
+	case 5: setBaseColour(QColor(255, 150, 50)); break;
+	}
+    } else if (name == "Plot Type") {
+	setPlotStyle(PlotStyle(value));
+    }
+}
+
+void
+TimeInstantLayer::setBaseColour(QColor colour)
+{
+    if (m_colour == colour) return;
+    m_colour = colour;
+    emit layerParametersChanged();
+}
+
+void
+TimeInstantLayer::setPlotStyle(PlotStyle style)
+{
+    if (m_plotStyle == style) return;
+    m_plotStyle = style;
+    emit layerParametersChanged();
+}
+
+bool
+TimeInstantLayer::isLayerScrollable(const View *v) const
+{
+    QPoint discard;
+    return !v->shouldIlluminateLocalFeatures(this, discard);
+}
+
+SparseOneDimensionalModel::PointList
+TimeInstantLayer::getLocalPoints(View *v, int x) const
+{
+    // Return a set of points that all have the same frame number, the
+    // nearest to the given x coordinate, and that are within a
+    // certain fuzz distance of that x coordinate.
+
+    if (!m_model) return SparseOneDimensionalModel::PointList();
+
+    long frame = v->getFrameForX(x);
+
+    SparseOneDimensionalModel::PointList onPoints =
+	m_model->getPoints(frame);
+
+    if (!onPoints.empty()) {
+	return onPoints;
+    }
+
+    SparseOneDimensionalModel::PointList prevPoints =
+	m_model->getPreviousPoints(frame);
+    SparseOneDimensionalModel::PointList nextPoints =
+	m_model->getNextPoints(frame);
+
+    SparseOneDimensionalModel::PointList usePoints = prevPoints;
+
+    if (prevPoints.empty()) {
+	usePoints = nextPoints;
+    } else if (long(prevPoints.begin()->frame) < v->getStartFrame() &&
+	       !(nextPoints.begin()->frame > v->getEndFrame())) {
+	usePoints = nextPoints;
+    } else if (nextPoints.begin()->frame - frame <
+	       frame - prevPoints.begin()->frame) {
+	usePoints = nextPoints;
+    }
+
+    if (!usePoints.empty()) {
+	int fuzz = 2;
+	int px = v->getXForFrame(usePoints.begin()->frame);
+	if ((px > x && px - x > fuzz) ||
+	    (px < x && x - px > fuzz + 1)) {
+	    usePoints.clear();
+	}
+    }
+
+    return usePoints;
+}
+
+QString
+TimeInstantLayer::getFeatureDescription(View *v, QPoint &pos) const
+{
+    int x = pos.x();
+
+    if (!m_model || !m_model->getSampleRate()) return "";
+
+    SparseOneDimensionalModel::PointList points = getLocalPoints(v, x);
+
+    if (points.empty()) {
+	if (!m_model->isReady()) {
+	    return tr("In progress");
+	} else {
+	    return tr("No local points");
+	}
+    }
+
+    long useFrame = points.begin()->frame;
+
+    RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
+    
+    QString text;
+
+    if (points.begin()->label == "") {
+	text = QString(tr("Time:\t%1\nNo label"))
+	    .arg(rt.toText(true).c_str());
+    } else {
+	text = QString(tr("Time:\t%1\nLabel:\t%2"))
+	    .arg(rt.toText(true).c_str())
+	    .arg(points.begin()->label);
+    }
+
+    pos = QPoint(v->getXForFrame(useFrame), pos.y());
+    return text;
+}
+
+bool
+TimeInstantLayer::snapToFeatureFrame(View *v, int &frame,
+				     size_t &resolution,
+				     SnapType snap) const
+{
+    if (!m_model) {
+	return Layer::snapToFeatureFrame(v, frame, resolution, snap);
+    }
+
+    resolution = m_model->getResolution();
+    SparseOneDimensionalModel::PointList points;
+
+    if (snap == SnapNeighbouring) {
+	
+	points = getLocalPoints(v, v->getXForFrame(frame));
+	if (points.empty()) return false;
+	frame = points.begin()->frame;
+	return true;
+    }    
+
+    points = m_model->getPoints(frame, frame);
+    int snapped = frame;
+    bool found = false;
+
+    for (SparseOneDimensionalModel::PointList::const_iterator i = points.begin();
+	 i != points.end(); ++i) {
+
+	if (snap == SnapRight) {
+
+	    if (i->frame >= frame) {
+		snapped = i->frame;
+		found = true;
+		break;
+	    }
+
+	} else if (snap == SnapLeft) {
+
+	    if (i->frame <= frame) {
+		snapped = i->frame;
+		found = true; // don't break, as the next may be better
+	    } else {
+		break;
+	    }
+
+	} else { // nearest
+
+	    SparseOneDimensionalModel::PointList::const_iterator j = i;
+	    ++j;
+
+	    if (j == points.end()) {
+
+		snapped = i->frame;
+		found = true;
+		break;
+
+	    } else if (j->frame >= frame) {
+
+		if (j->frame - frame < frame - i->frame) {
+		    snapped = j->frame;
+		} else {
+		    snapped = i->frame;
+		}
+		found = true;
+		break;
+	    }
+	}
+    }
+
+    frame = snapped;
+    return found;
+}
+
+void
+TimeInstantLayer::paint(View *v, QPainter &paint, QRect rect) const
+{
+    if (!m_model || !m_model->isOK()) return;
+
+//    Profiler profiler("TimeInstantLayer::paint", true);
+
+    int x0 = rect.left(), x1 = rect.right();
+
+    long frame0 = v->getFrameForX(x0);
+    long frame1 = v->getFrameForX(x1);
+
+    SparseOneDimensionalModel::PointList points(m_model->getPoints
+						(frame0, frame1));
+
+    bool odd = false;
+    if (m_plotStyle == PlotSegmentation && !points.empty()) {
+	int index = m_model->getIndexOf(*points.begin());
+	odd = ((index % 2) == 1);
+    }
+
+    paint.setPen(m_colour);
+
+    QColor brushColour(m_colour);
+    brushColour.setAlpha(100);
+    paint.setBrush(brushColour);
+
+    QColor oddBrushColour(brushColour);
+    if (m_plotStyle == PlotSegmentation) {
+	if (m_colour == Qt::black) {
+	    oddBrushColour = Qt::gray;
+	} else if (m_colour == Qt::darkRed) {
+	    oddBrushColour = Qt::red;
+	} else if (m_colour == Qt::darkBlue) {
+	    oddBrushColour = Qt::blue;
+	} else if (m_colour == Qt::darkGreen) {
+	    oddBrushColour = Qt::green;
+	} else {
+	    oddBrushColour = oddBrushColour.light(150);
+	}
+	oddBrushColour.setAlpha(100);
+    }
+
+//    std::cerr << "TimeInstantLayer::paint: resolution is "
+//	      << m_model->getResolution() << " frames" << std::endl;
+
+    QPoint localPos;
+    long illuminateFrame = -1;
+
+    if (v->shouldIlluminateLocalFeatures(this, localPos)) {
+	SparseOneDimensionalModel::PointList localPoints =
+	    getLocalPoints(v, localPos.x());
+	if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame;
+    }
+	
+    int prevX = -1;
+    int textY = v->getTextLabelHeight(this, paint);
+    
+    for (SparseOneDimensionalModel::PointList::const_iterator i = points.begin();
+	 i != points.end(); ++i) {
+
+	const SparseOneDimensionalModel::Point &p(*i);
+	SparseOneDimensionalModel::PointList::const_iterator j = i;
+	++j;
+
+	int x = v->getXForFrame(p.frame);
+	if (x == prevX && p.frame != illuminateFrame) continue;
+
+	int iw = v->getXForFrame(p.frame + m_model->getResolution()) - x;
+	if (iw < 2) {
+	    if (iw < 1) {
+		iw = 2;
+		if (j != points.end()) {
+		    int nx = v->getXForFrame(j->frame);
+		    if (nx < x + 3) iw = 1;
+		}
+	    } else {
+		iw = 2;
+	    }
+	}
+		
+	if (p.frame == illuminateFrame) {
+	    paint.setPen(Qt::black); //!!!
+	} else {
+	    paint.setPen(brushColour);
+	}
+
+	if (m_plotStyle == PlotInstants) {
+	    if (iw > 1) {
+		paint.drawRect(x, 0, iw - 1, v->height() - 1);
+	    } else {
+		paint.drawLine(x, 0, x, v->height() - 1);
+	    }
+	} else {
+
+	    if (odd) paint.setBrush(oddBrushColour);
+	    else paint.setBrush(brushColour);
+	    
+	    int nx;
+	    
+	    if (j != points.end()) {
+		const SparseOneDimensionalModel::Point &q(*j);
+		nx = v->getXForFrame(q.frame);
+	    } else {
+		nx = v->getXForFrame(m_model->getEndFrame());
+	    }
+
+	    if (nx >= x) {
+		
+		if (illuminateFrame != p.frame &&
+		    (nx < x + 5 || x >= v->width() - 1)) {
+		    paint.setPen(Qt::NoPen);
+		}
+
+		paint.drawRect(x, -1, nx - x, v->height() + 1);
+	    }
+
+	    odd = !odd;
+	}
+
+	paint.setPen(m_colour);
+	
+	if (p.label != "") {
+
+	    // only draw if there's enough room from here to the next point
+
+	    int lw = paint.fontMetrics().width(p.label);
+	    bool good = true;
+
+	    if (j != points.end()) {
+		int nx = v->getXForFrame(j->frame);
+		if (nx >= x && nx - x - iw - 3 <= lw) good = false;
+	    }
+
+	    if (good) {
+		paint.drawText(x + iw + 2, textY, p.label);
+	    }
+	}
+
+	prevX = x;
+    }
+}
+
+void
+TimeInstantLayer::drawStart(View *v, QMouseEvent *e)
+{
+    std::cerr << "TimeInstantLayer::drawStart(" << e->x() << ")" << std::endl;
+
+    if (!m_model) return;
+
+    long frame = v->getFrameForX(e->x());
+    if (frame < 0) frame = 0;
+    frame = frame / m_model->getResolution() * m_model->getResolution();
+
+    m_editingPoint = SparseOneDimensionalModel::Point(frame, tr("New Point"));
+
+    if (m_editingCommand) m_editingCommand->finish();
+    m_editingCommand = new SparseOneDimensionalModel::EditCommand(m_model,
+								  tr("Draw Point"));
+    m_editingCommand->addPoint(m_editingPoint);
+
+    m_editing = true;
+}
+
+void
+TimeInstantLayer::drawDrag(View *v, QMouseEvent *e)
+{
+    std::cerr << "TimeInstantLayer::drawDrag(" << e->x() << ")" << std::endl;
+
+    if (!m_model || !m_editing) return;
+
+    long frame = v->getFrameForX(e->x());
+    if (frame < 0) frame = 0;
+    frame = frame / m_model->getResolution() * m_model->getResolution();
+    m_editingCommand->deletePoint(m_editingPoint);
+    m_editingPoint.frame = frame;
+    m_editingCommand->addPoint(m_editingPoint);
+}
+
+void
+TimeInstantLayer::drawEnd(View *, QMouseEvent *e)
+{
+    std::cerr << "TimeInstantLayer::drawEnd(" << e->x() << ")" << std::endl;
+    if (!m_model || !m_editing) return;
+    QString newName = tr("Add Point at %1 s")
+	.arg(RealTime::frame2RealTime(m_editingPoint.frame,
+				      m_model->getSampleRate())
+	     .toText(false).c_str());
+    m_editingCommand->setName(newName);
+    m_editingCommand->finish();
+    m_editingCommand = 0;
+    m_editing = false;
+}
+
+void
+TimeInstantLayer::editStart(View *v, QMouseEvent *e)
+{
+    std::cerr << "TimeInstantLayer::editStart(" << e->x() << ")" << std::endl;
+
+    if (!m_model) return;
+
+    SparseOneDimensionalModel::PointList points = getLocalPoints(v, e->x());
+    if (points.empty()) return;
+
+    m_editingPoint = *points.begin();
+
+    if (m_editingCommand) {
+	m_editingCommand->finish();
+	m_editingCommand = 0;
+    }
+
+    m_editing = true;
+}
+
+void
+TimeInstantLayer::editDrag(View *v, QMouseEvent *e)
+{
+    std::cerr << "TimeInstantLayer::editDrag(" << e->x() << ")" << std::endl;
+
+    if (!m_model || !m_editing) return;
+
+    long frame = v->getFrameForX(e->x());
+    if (frame < 0) frame = 0;
+    frame = frame / m_model->getResolution() * m_model->getResolution();
+
+    if (!m_editingCommand) {
+	m_editingCommand = new SparseOneDimensionalModel::EditCommand(m_model,
+								      tr("Drag Point"));
+    }
+
+    m_editingCommand->deletePoint(m_editingPoint);
+    m_editingPoint.frame = frame;
+    m_editingCommand->addPoint(m_editingPoint);
+}
+
+void
+TimeInstantLayer::editEnd(View *, QMouseEvent *e)
+{
+    std::cerr << "TimeInstantLayer::editEnd(" << e->x() << ")" << std::endl;
+    if (!m_model || !m_editing) return;
+    if (m_editingCommand) {
+	QString newName = tr("Move Point to %1 s")
+	    .arg(RealTime::frame2RealTime(m_editingPoint.frame,
+					  m_model->getSampleRate())
+		 .toText(false).c_str());
+	m_editingCommand->setName(newName);
+	m_editingCommand->finish();
+    }
+    m_editingCommand = 0;
+    m_editing = false;
+}
+
+void
+TimeInstantLayer::editOpen(View *v, QMouseEvent *e)
+{
+    if (!m_model) return;
+
+    SparseOneDimensionalModel::PointList points = getLocalPoints(v, e->x());
+    if (points.empty()) return;
+
+    SparseOneDimensionalModel::Point point = *points.begin();
+
+    ItemEditDialog *dialog = new ItemEditDialog
+        (m_model->getSampleRate(),
+         ItemEditDialog::ShowTime |
+         ItemEditDialog::ShowText);
+
+    dialog->setFrameTime(point.frame);
+    dialog->setText(point.label);
+
+    if (dialog->exec() == QDialog::Accepted) {
+
+        SparseOneDimensionalModel::Point newPoint = point;
+        newPoint.frame = dialog->getFrameTime();
+        newPoint.label = dialog->getText();
+        
+        SparseOneDimensionalModel::EditCommand *command =
+            new SparseOneDimensionalModel::EditCommand(m_model, tr("Edit Point"));
+        command->deletePoint(point);
+        command->addPoint(newPoint);
+        command->finish();
+    }
+
+    delete dialog;
+}
+
+void
+TimeInstantLayer::moveSelection(Selection s, size_t newStartFrame)
+{
+    if (!m_model) return;
+
+    SparseOneDimensionalModel::EditCommand *command =
+	new SparseOneDimensionalModel::EditCommand(m_model,
+						   tr("Drag Selection"));
+
+    SparseOneDimensionalModel::PointList points =
+	m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+
+    for (SparseOneDimensionalModel::PointList::iterator i = points.begin();
+	 i != points.end(); ++i) {
+
+	if (s.contains(i->frame)) {
+	    SparseOneDimensionalModel::Point newPoint(*i);
+	    newPoint.frame = i->frame + newStartFrame - s.getStartFrame();
+	    command->deletePoint(*i);
+	    command->addPoint(newPoint);
+	}
+    }
+
+    command->finish();
+}
+
+void
+TimeInstantLayer::resizeSelection(Selection s, Selection newSize)
+{
+    if (!m_model) return;
+
+    SparseOneDimensionalModel::EditCommand *command =
+	new SparseOneDimensionalModel::EditCommand(m_model,
+						   tr("Resize Selection"));
+
+    SparseOneDimensionalModel::PointList points =
+	m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+
+    double ratio =
+	double(newSize.getEndFrame() - newSize.getStartFrame()) /
+	double(s.getEndFrame() - s.getStartFrame());
+
+    for (SparseOneDimensionalModel::PointList::iterator i = points.begin();
+	 i != points.end(); ++i) {
+
+	if (s.contains(i->frame)) {
+
+	    double target = i->frame;
+	    target = newSize.getStartFrame() + 
+		double(target - s.getStartFrame()) * ratio;
+
+	    SparseOneDimensionalModel::Point newPoint(*i);
+	    newPoint.frame = lrint(target);
+	    command->deletePoint(*i);
+	    command->addPoint(newPoint);
+	}
+    }
+
+    command->finish();
+}
+
+void
+TimeInstantLayer::deleteSelection(Selection s)
+{
+    if (!m_model) return;
+
+    SparseOneDimensionalModel::EditCommand *command =
+	new SparseOneDimensionalModel::EditCommand(m_model,
+						   tr("Delete Selection"));
+
+    SparseOneDimensionalModel::PointList points =
+	m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+
+    for (SparseOneDimensionalModel::PointList::iterator i = points.begin();
+	 i != points.end(); ++i) {
+	if (s.contains(i->frame)) command->deletePoint(*i);
+    }
+
+    command->finish();
+}
+
+void
+TimeInstantLayer::copy(Selection s, Clipboard &to)
+{
+    if (!m_model) return;
+
+    SparseOneDimensionalModel::PointList points =
+	m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+
+    for (SparseOneDimensionalModel::PointList::iterator i = points.begin();
+	 i != points.end(); ++i) {
+	if (s.contains(i->frame)) {
+            Clipboard::Point point(i->frame, i->label);
+            to.addPoint(point);
+        }
+    }
+}
+
+bool
+TimeInstantLayer::paste(const Clipboard &from, int frameOffset, bool)
+{
+    if (!m_model) return false;
+
+    const Clipboard::PointList &points = from.getPoints();
+
+    SparseOneDimensionalModel::EditCommand *command =
+	new SparseOneDimensionalModel::EditCommand(m_model, tr("Paste"));
+
+    for (Clipboard::PointList::const_iterator i = points.begin();
+         i != points.end(); ++i) {
+        
+        if (!i->haveFrame()) continue;
+        size_t frame = 0;
+        if (frameOffset > 0 || -frameOffset < i->getFrame()) {
+            frame = i->getFrame() + frameOffset;
+        }
+        SparseOneDimensionalModel::Point newPoint(frame);
+        if (i->haveLabel()) {
+            newPoint.label = i->getLabel();
+        } else if (i->haveValue()) {
+            newPoint.label = QString("%1").arg(i->getValue());
+        }
+        
+        command->addPoint(newPoint);
+    }
+
+    command->finish();
+    return true;
+}
+
+QString
+TimeInstantLayer::toXmlString(QString indent, QString extraAttributes) const
+{
+    return Layer::toXmlString(indent, extraAttributes +
+			      QString(" colour=\"%1\" plotStyle=\"%2\"")
+			      .arg(encodeColour(m_colour)).arg(m_plotStyle));
+}
+
+void
+TimeInstantLayer::setProperties(const QXmlAttributes &attributes)
+{
+    QString colourSpec = attributes.value("colour");
+    if (colourSpec != "") {
+	QColor colour(colourSpec);
+	if (colour.isValid()) {
+	    setBaseColour(QColor(colourSpec));
+	}
+    }
+
+    bool ok;
+    PlotStyle style = (PlotStyle)
+	attributes.value("plotStyle").toInt(&ok);
+    if (ok) setPlotStyle(style);
+}
+