Mercurial > hg > svgui
changeset 1518:2e94c268f7a0 time-frequency-boxes
Rename TimeFrequencyBoxLayer to just BoxLayer, supporting vertical scales other than Hz
author | Chris Cannam |
---|---|
date | Wed, 25 Sep 2019 09:45:42 +0100 |
parents | c5d2de8f7647 |
children | 235e08aa2d5d |
files | files.pri layer/BoxLayer.cpp layer/BoxLayer.h layer/LayerFactory.cpp layer/LayerFactory.h layer/TimeFrequencyBoxLayer.cpp layer/TimeFrequencyBoxLayer.h |
diffstat | 7 files changed, 1369 insertions(+), 1334 deletions(-) [+] |
line wrap: on
line diff
--- a/files.pri Sat Sep 21 19:13:14 2019 +0100 +++ b/files.pri Wed Sep 25 09:45:42 2019 +0100 @@ -32,7 +32,7 @@ layer/SpectrumLayer.h \ layer/TextLayer.h \ layer/TimeInstantLayer.h \ - layer/TimeFrequencyBoxLayer.h \ + layer/BoxLayer.h \ layer/TimeRulerLayer.h \ layer/TimeValueLayer.h \ layer/VerticalScaleLayer.h \ @@ -122,7 +122,7 @@ layer/SpectrumLayer.cpp \ layer/TextLayer.cpp \ layer/TimeInstantLayer.cpp \ - layer/TimeFrequencyBoxLayer.cpp \ + layer/BoxLayer.cpp \ layer/TimeRulerLayer.cpp \ layer/TimeValueLayer.cpp \ layer/WaveformLayer.cpp \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/BoxLayer.cpp Wed Sep 25 09:45:42 2019 +0100 @@ -0,0 +1,1208 @@ +/* -*- 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 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 "BoxLayer.h" + +#include "data/model/Model.h" +#include "base/RealTime.h" +#include "base/Profiler.h" +#include "base/LogRange.h" + +#include "ColourDatabase.h" +#include "ColourMapper.h" +#include "LinearNumericalScale.h" +#include "LogNumericalScale.h" +#include "PaintAssistant.h" + +#include "view/View.h" + +#include "data/model/BoxModel.h" + +#include "widgets/ItemEditDialog.h" +#include "widgets/TextAbbrev.h" + +#include <QPainter> +#include <QPainterPath> +#include <QMouseEvent> +#include <QTextStream> +#include <QMessageBox> + +#include <iostream> +#include <cmath> + +BoxLayer::BoxLayer() : + SingleColourLayer(), + m_editing(false), + m_dragPointX(0), + m_dragPointY(0), + m_dragStartX(0), + m_dragStartY(0), + m_originalPoint(0, 0.0, 0, tr("New Box")), + m_editingPoint(0, 0.0, 0, tr("New Box")), + m_editingCommand(nullptr), + m_verticalScale(AutoAlignScale) +{ + +} + +int +BoxLayer::getCompletion(LayerGeometryProvider *) const +{ + auto model = ModelById::get(m_model); + if (model) return model->getCompletion(); + else return 0; +} + +void +BoxLayer::setModel(ModelId modelId) +{ + auto oldModel = ModelById::getAs<BoxModel>(m_model); + auto newModel = ModelById::getAs<BoxModel>(modelId); + + if (!modelId.isNone() && !newModel) { + throw std::logic_error("Not a BoxModel"); + } + + if (m_model == modelId) return; + m_model = modelId; + + if (newModel) { + connectSignals(m_model); + } + + emit modelReplaced(); +} + +Layer::PropertyList +BoxLayer::getProperties() const +{ + PropertyList list = SingleColourLayer::getProperties(); + list.push_back("Vertical Scale"); + list.push_back("Scale Units"); + return list; +} + +QString +BoxLayer::getPropertyLabel(const PropertyName &name) const +{ + if (name == "Vertical Scale") return tr("Vertical Scale"); + if (name == "Scale Units") return tr("Scale Units"); + return SingleColourLayer::getPropertyLabel(name); +} + +Layer::PropertyType +BoxLayer::getPropertyType(const PropertyName &name) const +{ + if (name == "Vertical Scale") return ValueProperty; + if (name == "Scale Units") return UnitsProperty; + return SingleColourLayer::getPropertyType(name); +} + +QString +BoxLayer::getPropertyGroupName(const PropertyName &name) const +{ + if (name == "Vertical Scale" || name == "Scale Units") { + return tr("Scale"); + } + return SingleColourLayer::getPropertyGroupName(name); +} + +int +BoxLayer::getPropertyRangeAndValue(const PropertyName &name, + int *min, int *max, int *deflt) const +{ + int val = 0; + + if (name == "Vertical Scale") { + + if (min) *min = 0; + if (max) *max = 2; + if (deflt) *deflt = int(LinearScale); + + val = int(m_verticalScale); + + } else if (name == "Scale Units") { + + if (deflt) *deflt = 0; + auto model = ModelById::getAs<BoxModel>(m_model); + if (model) { + val = UnitDatabase::getInstance()->getUnitId + (model->getScaleUnits()); + } + + } else { + val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt); + } + + return val; +} + +QString +BoxLayer::getPropertyValueLabel(const PropertyName &name, + int value) const +{ + if (name == "Vertical Scale") { + switch (value) { + default: + case 0: return tr("Auto-Align"); + case 1: return tr("Linear"); + case 2: return tr("Log"); + } + } + return SingleColourLayer::getPropertyValueLabel(name, value); +} + +void +BoxLayer::setProperty(const PropertyName &name, int value) +{ + if (name == "Vertical Scale") { + setVerticalScale(VerticalScale(value)); + } else if (name == "Scale Units") { + auto model = ModelById::getAs<BoxModel>(m_model); + if (model) { + model->setScaleUnits + (UnitDatabase::getInstance()->getUnitById(value)); + emit modelChanged(m_model); + } + } else { + return SingleColourLayer::setProperty(name, value); + } +} + +void +BoxLayer::setVerticalScale(VerticalScale scale) +{ + if (m_verticalScale == scale) return; + m_verticalScale = scale; + emit layerParametersChanged(); +} + +bool +BoxLayer::isLayerScrollable(const LayerGeometryProvider *v) const +{ + QPoint discard; + return !v->shouldIlluminateLocalFeatures(this, discard); +} + +bool +BoxLayer::getValueExtents(double &min, double &max, + bool &logarithmic, QString &unit) const +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model) return false; + min = model->getValueMinimum(); + max = model->getValueMaximum(); + unit = getScaleUnits(); + + if (m_verticalScale == LogScale) logarithmic = true; + + return true; +} + +bool +BoxLayer::getDisplayExtents(double &min, double &max) const +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model || m_verticalScale == AutoAlignScale) return false; + + min = model->getValueMinimum(); + max = model->getValueMaximum(); + + return true; +} + +bool +BoxLayer::adoptExtents(double min, double max, QString unit) +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model) return false; + if (model->getScaleUnits() == "") { + model->setScaleUnits(unit); + return true; + } else { + return false; + } +} + +EventVector +BoxLayer::getLocalPoints(LayerGeometryProvider *v, int x) const +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model) return EventVector(); + + sv_frame_t frame = v->getFrameForX(x); + + EventVector local = model->getEventsCovering(frame); + if (!local.empty()) return local; + + int fuzz = ViewManager::scalePixelSize(2); + sv_frame_t start = v->getFrameForX(x - fuzz); + sv_frame_t end = v->getFrameForX(x + fuzz); + + local = model->getEventsStartingWithin(frame, end - frame); + if (!local.empty()) return local; + + local = model->getEventsSpanning(start, frame - start); + if (!local.empty()) return local; + + return {}; +} + +bool +BoxLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, + Event &point) const +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model) return false; + + sv_frame_t frame = v->getFrameForX(x); + + EventVector onPoints = model->getEventsCovering(frame); + if (onPoints.empty()) return false; + + int nearestDistance = -1; + for (const auto &p: onPoints) { + int distance = getYForValue(v, p.getValue()) - y; + if (distance < 0) distance = -distance; + if (nearestDistance == -1 || distance < nearestDistance) { + nearestDistance = distance; + point = p; + } + } + + return true; +} + +QString +BoxLayer::getLabelPreceding(sv_frame_t frame) const +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model) return ""; + EventVector points = model->getEventsStartingWithin + (model->getStartFrame(), frame - model->getStartFrame()); + if (!points.empty()) { + for (auto i = points.rbegin(); i != points.rend(); ++i) { + if (i->getLabel() != QString()) { + return i->getLabel(); + } + } + } + return QString(); +} + +QString +BoxLayer::getFeatureDescription(LayerGeometryProvider *v, + QPoint &pos) const +{ + int x = pos.x(); + + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model || !model->getSampleRate()) return ""; + + EventVector points = getLocalPoints(v, x); + + if (points.empty()) { + if (!model->isReady()) { + return tr("In progress"); + } else { + return tr("No local points"); + } + } + + Event box; + EventVector::iterator i; + + for (i = points.begin(); i != points.end(); ++i) { + + int y0 = getYForValue(v, i->getValue()); + int y1 = getYForValue(v, i->getValue() + fabsf(i->getLevel())); + + if (pos.y() >= y0 && pos.y() <= y1) { + box = *i; + break; + } + } + + if (i == points.end()) return tr("No local points"); + + RealTime rt = RealTime::frame2RealTime(box.getFrame(), + model->getSampleRate()); + RealTime rd = RealTime::frame2RealTime(box.getDuration(), + model->getSampleRate()); + + QString rangeText; + + rangeText = tr("%1 %2 - %3 %4") + .arg(box.getValue()).arg(getScaleUnits()) + .arg(box.getValue() + fabsf(box.getLevel())).arg(getScaleUnits()); + + QString text; + + if (box.getLabel() == "") { + text = QString(tr("Time:\t%1\nDuration:\t%2\nValue:\t%3\nNo label")) + .arg(rt.toText(true).c_str()) + .arg(rd.toText(true).c_str()) + .arg(rangeText); + } else { + text = QString(tr("Time:\t%1\nDuration:\t%2\nValue:\t%3\nLabel:\t%4")) + .arg(rt.toText(true).c_str()) + .arg(rd.toText(true).c_str()) + .arg(rangeText) + .arg(box.getLabel()); + } + + pos = QPoint(v->getXForFrame(box.getFrame()), + getYForValue(v, box.getValue())); + return text; +} + +bool +BoxLayer::snapToFeatureFrame(LayerGeometryProvider *v, + sv_frame_t &frame, + int &resolution, + SnapType snap) const +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model) { + return Layer::snapToFeatureFrame(v, frame, resolution, snap); + } + + // SnapLeft / SnapRight: return frame of nearest feature in that + // direction no matter how far away + // + // SnapNeighbouring: return frame of feature that would be used in + // an editing operation, i.e. closest feature in either direction + // but only if it is "close enough" + + resolution = model->getResolution(); + + if (snap == SnapNeighbouring) { + EventVector points = getLocalPoints(v, v->getXForFrame(frame)); + if (points.empty()) return false; + frame = points.begin()->getFrame(); + return true; + } + + // Normally we snap to the start frame of whichever event we + // find. However here, for SnapRight only, if the end frame of + // whichever event we would have snapped to had we been snapping + // left is closer than the start frame of the next event to the + // right, then we snap to that frame instead. Clear? + + Event left; + bool haveLeft = false; + if (model->getNearestEventMatching + (frame, [](Event) { return true; }, EventSeries::Backward, left)) { + haveLeft = true; + } + + if (snap == SnapLeft) { + frame = left.getFrame(); + return haveLeft; + } + + Event right; + bool haveRight = false; + if (model->getNearestEventMatching + (frame, [](Event) { return true; }, EventSeries::Forward, right)) { + haveRight = true; + } + + if (haveLeft) { + sv_frame_t leftEnd = left.getFrame() + left.getDuration(); + if (leftEnd > frame) { + if (haveRight) { + if (leftEnd - frame < right.getFrame() - frame) { + frame = leftEnd; + } else { + frame = right.getFrame(); + } + } else { + frame = leftEnd; + } + return true; + } + } + + if (haveRight) { + frame = right.getFrame(); + return true; + } + + return false; +} + +QString +BoxLayer::getScaleUnits() const +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (model) return model->getScaleUnits(); + else return ""; +} + +void +BoxLayer::getScaleExtents(LayerGeometryProvider *v, + double &min, double &max, + bool &log) const +{ + min = 0.0; + max = 0.0; + log = false; + + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model) return; + + QString queryUnits; + queryUnits = getScaleUnits(); + + if (m_verticalScale == AutoAlignScale) { + + if (!v->getValueExtents(queryUnits, min, max, log)) { + + min = model->getValueMinimum(); + max = model->getValueMaximum(); + +// cerr << "BoxLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; + + } else if (log) { + + LogRange::mapRange(min, max); + +// cerr << "BoxLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; + + } + + } else { + + min = model->getValueMinimum(); + max = model->getValueMaximum(); + + if (m_verticalScale == LogScale) { + LogRange::mapRange(min, max); + log = true; + } + } + + if (max == min) max = min + 1.0; +} + +int +BoxLayer::getYForValue(LayerGeometryProvider *v, double val) const +{ + double min = 0.0, max = 0.0; + bool logarithmic = false; + int h = v->getPaintHeight(); + + getScaleExtents(v, min, max, logarithmic); + +// cerr << "BoxLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl; +// cerr << "h = " << h << ", margin = " << margin << endl; + + if (logarithmic) { + val = LogRange::map(val); + } + + return int(h - ((val - min) * h) / (max - min)); +} + +double +BoxLayer::getValueForY(LayerGeometryProvider *v, int y) const +{ + double min = 0.0, max = 0.0; + bool logarithmic = false; + int h = v->getPaintHeight(); + + getScaleExtents(v, min, max, logarithmic); + + double val = min + (double(h - y) * double(max - min)) / h; + + if (logarithmic) { + val = pow(10.0, val); + } + + return val; +} + +void +BoxLayer::paint(LayerGeometryProvider *v, QPainter &paint, + QRect rect) const +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model || !model->isOK()) return; + + sv_samplerate_t sampleRate = model->getSampleRate(); + if (!sampleRate) return; + +// Profiler profiler("BoxLayer::paint", true); + + int x0 = rect.left() - 40, x1 = rect.right(); + + sv_frame_t wholeFrame0 = v->getFrameForX(0); + sv_frame_t wholeFrame1 = v->getFrameForX(v->getPaintWidth()); + + EventVector points(model->getEventsSpanning(wholeFrame0, + wholeFrame1 - wholeFrame0)); + if (points.empty()) return; + + paint.setPen(getBaseQColor()); + +// SVDEBUG << "BoxLayer::paint: resolution is " +// << model->getResolution() << " frames" << endl; + + double min = model->getValueMinimum(); + double max = model->getValueMaximum(); + if (max == min) max = min + 1.0; + + QPoint localPos; + Event illuminatePoint(0); + bool shouldIlluminate = false; + + if (v->shouldIlluminateLocalFeatures(this, localPos)) { + shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(), + illuminatePoint); + } + + paint.save(); + paint.setRenderHint(QPainter::Antialiasing, false); + + QFontMetrics fm = paint.fontMetrics(); + + for (EventVector::const_iterator i = points.begin(); + i != points.end(); ++i) { + + const Event &p(*i); + + int x = v->getXForFrame(p.getFrame()); + int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x; + int y = getYForValue(v, p.getValue()); + int h = getYForValue(v, p.getValue() + fabsf(p.getLevel())) - y; + int ex = x + w; + int gap = v->scalePixelSize(2); + + EventVector::const_iterator j = i; + ++j; + + if (j != points.end()) { + const Event &q(*j); + int nx = v->getXForFrame(q.getFrame()); + if (nx < ex) ex = nx; + } + + if (w < 1) w = 1; + + paint.setPen(getBaseQColor()); + paint.setBrush(Qt::NoBrush); + + if ((shouldIlluminate && illuminatePoint == p) || + (m_editing && m_editingPoint == p)) { + + paint.setPen(QPen(getBaseQColor(), v->scalePixelSize(2))); + + // Qt 5.13 deprecates QFontMetrics::width(), but its suggested + // replacement (horizontalAdvance) was only added in Qt 5.11 + // which is too new for us +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + + if (abs(h) > 2 * fm.height()) { + + QString y0label = QString("%1 %2") + .arg(p.getValue()) + .arg(getScaleUnits()); + + QString y1label = QString("%1 %2") + .arg(p.getValue() + fabsf(p.getLevel())) + .arg(getScaleUnits()); + + PaintAssistant::drawVisibleText + (v, paint, + x - fm.width(y0label) - gap, + y - fm.descent(), + y0label, PaintAssistant::OutlinedText); + + PaintAssistant::drawVisibleText + (v, paint, + x - fm.width(y1label) - gap, + y + h + fm.ascent(), + y1label, PaintAssistant::OutlinedText); + + } else { + + QString ylabel = QString("%1 %2 - %3 %4") + .arg(p.getValue()) + .arg(getScaleUnits()) + .arg(p.getValue() + fabsf(p.getLevel())) + .arg(getScaleUnits()); + + PaintAssistant::drawVisibleText + (v, paint, + x - fm.width(ylabel) - gap, + y - fm.descent(), + ylabel, PaintAssistant::OutlinedText); + } + + QString t0label = RealTime::frame2RealTime + (p.getFrame(), model->getSampleRate()).toText(true).c_str(); + + QString t1label = RealTime::frame2RealTime + (p.getFrame() + p.getDuration(), model->getSampleRate()) + .toText(true).c_str(); + + PaintAssistant::drawVisibleText + (v, paint, x, y + fm.ascent() + gap, + t0label, PaintAssistant::OutlinedText); + + if (w > fm.width(t0label) + fm.width(t1label) + gap * 3) { + + PaintAssistant::drawVisibleText + (v, paint, + x + w - fm.width(t1label), + y + fm.ascent() + gap, + t1label, PaintAssistant::OutlinedText); + + } else { + + PaintAssistant::drawVisibleText + (v, paint, + x + w - fm.width(t1label), + y + fm.ascent() + fm.height() + gap, + t1label, PaintAssistant::OutlinedText); + } + } + + paint.drawRect(x, y, w, h); + } + + for (EventVector::const_iterator i = points.begin(); + i != points.end(); ++i) { + + const Event &p(*i); + + QString label = p.getLabel(); + if (label == "") continue; + + if (shouldIlluminate && illuminatePoint == p) { + continue; // already handled this in illumination special case + } + + int x = v->getXForFrame(p.getFrame()); + int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x; + int y = getYForValue(v, p.getValue()); + + int labelWidth = fm.width(label); + + int gap = v->scalePixelSize(2); + + if (x + w < x0 || x - labelWidth - gap > x1) { + continue; + } + + int labelX, labelY; + + labelX = x - labelWidth - gap; + labelY = y - fm.descent(); + + PaintAssistant::drawVisibleText(v, paint, labelX, labelY, label, + PaintAssistant::OutlinedText); + } + + paint.restore(); +} + +int +BoxLayer::getVerticalScaleWidth(LayerGeometryProvider *v, + bool, QPainter &paint) const +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model || m_verticalScale == AutoAlignScale) { + return 0; + } else { + if (m_verticalScale == LogScale) { + return LogNumericalScale().getWidth(v, paint); + } else { + return LinearNumericalScale().getWidth(v, paint); + } + } +} + +void +BoxLayer::paintVerticalScale(LayerGeometryProvider *v, + bool, QPainter &paint, QRect) const +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model || model->isEmpty()) return; + + QString unit; + double min, max; + bool logarithmic; + + int w = getVerticalScaleWidth(v, false, paint); + + getScaleExtents(v, min, max, logarithmic); + + if (logarithmic) { + LogNumericalScale().paintVertical(v, this, paint, 0, min, max); + } else { + LinearNumericalScale().paintVertical(v, this, paint, 0, min, max); + } + + if (getScaleUnits() != "") { + int mw = w - 5; + paint.drawText(5, + 5 + paint.fontMetrics().ascent(), + TextAbbrev::abbreviate(getScaleUnits(), + paint.fontMetrics(), + mw)); + } +} + +void +BoxLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e) +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model) return; + + sv_frame_t frame = v->getFrameForX(e->x()); + if (frame < 0) frame = 0; + frame = frame / model->getResolution() * model->getResolution(); + + double value = getValueForY(v, e->y()); + + m_editingPoint = Event(frame, float(value), 0, ""); + m_originalPoint = m_editingPoint; + + if (m_editingCommand) finish(m_editingCommand); + m_editingCommand = new ChangeEventsCommand(m_model.untyped, + tr("Draw Box")); + m_editingCommand->add(m_editingPoint); + + m_editing = true; +} + +void +BoxLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e) +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model || !m_editing) return; + + sv_frame_t dragFrame = v->getFrameForX(e->x()); + if (dragFrame < 0) dragFrame = 0; + dragFrame = dragFrame / model->getResolution() * model->getResolution(); + + sv_frame_t eventFrame = m_originalPoint.getFrame(); + sv_frame_t eventDuration = dragFrame - eventFrame; + if (eventDuration < 0) { + eventFrame = eventFrame + eventDuration; + eventDuration = -eventDuration; + } else if (eventDuration == 0) { + eventDuration = model->getResolution(); + } + + double dragValue = getValueForY(v, e->y()); + + double eventValue = m_originalPoint.getValue(); + double eventFreqDiff = dragValue - eventValue; + if (eventFreqDiff < 0) { + eventValue = eventValue + eventFreqDiff; + eventFreqDiff = -eventFreqDiff; + } + + m_editingCommand->remove(m_editingPoint); + m_editingPoint = m_editingPoint + .withFrame(eventFrame) + .withDuration(eventDuration) + .withValue(float(eventValue)) + .withLevel(float(eventFreqDiff)); + m_editingCommand->add(m_editingPoint); +} + +void +BoxLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *) +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model || !m_editing) return; + finish(m_editingCommand); + m_editingCommand = nullptr; + m_editing = false; +} + +void +BoxLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e) +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model) return; + + if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return; + + if (m_editingCommand) { + finish(m_editingCommand); + m_editingCommand = nullptr; + } + + m_editing = true; +} + +void +BoxLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *) +{ +} + +void +BoxLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e) +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model || !m_editing) return; + + m_editing = false; + + Event p(0); + if (!getPointToDrag(v, e->x(), e->y(), p)) return; + if (p.getFrame() != m_editingPoint.getFrame() || + p.getValue() != m_editingPoint.getValue()) return; + + m_editingCommand = new ChangeEventsCommand + (m_model.untyped, tr("Erase Box")); + + m_editingCommand->remove(m_editingPoint); + + finish(m_editingCommand); + m_editingCommand = nullptr; + m_editing = false; +} + +void +BoxLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e) +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model) return; + + if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) { + return; + } + + m_dragPointX = v->getXForFrame(m_editingPoint.getFrame()); + m_dragPointY = getYForValue(v, m_editingPoint.getValue()); + + m_originalPoint = m_editingPoint; + + if (m_editingCommand) { + finish(m_editingCommand); + m_editingCommand = nullptr; + } + + m_editing = true; + m_dragStartX = e->x(); + m_dragStartY = e->y(); +} + +void +BoxLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e) +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model || !m_editing) return; + + int xdist = e->x() - m_dragStartX; + int ydist = e->y() - m_dragStartY; + int newx = m_dragPointX + xdist; + int newy = m_dragPointY + ydist; + + sv_frame_t frame = v->getFrameForX(newx); + if (frame < 0) frame = 0; + frame = frame / model->getResolution() * model->getResolution(); + + double value = getValueForY(v, newy); + + if (!m_editingCommand) { + m_editingCommand = new ChangeEventsCommand + (m_model.untyped, + tr("Drag Box")); + } + + m_editingCommand->remove(m_editingPoint); + m_editingPoint = m_editingPoint + .withFrame(frame) + .withValue(float(value)); + m_editingCommand->add(m_editingPoint); +} + +void +BoxLayer::editEnd(LayerGeometryProvider *, QMouseEvent *) +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model || !m_editing) return; + + if (m_editingCommand) { + + QString newName = m_editingCommand->getName(); + + if (m_editingPoint.getFrame() != m_originalPoint.getFrame()) { + if (m_editingPoint.getValue() != m_originalPoint.getValue()) { + newName = tr("Edit Box"); + } else { + newName = tr("Relocate Box"); + } + } else { + newName = tr("Change Point Value"); + } + + m_editingCommand->setName(newName); + finish(m_editingCommand); + } + + m_editingCommand = nullptr; + m_editing = false; +} + +bool +BoxLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e) +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model) return false; + + Event region(0); + if (!getPointToDrag(v, e->x(), e->y(), region)) return false; + + ItemEditDialog::LabelOptions labelOptions; + labelOptions.valueLabel = tr("Minimum Value"); + labelOptions.levelLabel = tr("Value Extent"); + labelOptions.valueUnits = getScaleUnits(); + labelOptions.levelUnits = getScaleUnits(); + + ItemEditDialog *dialog = new ItemEditDialog + (model->getSampleRate(), + ItemEditDialog::ShowTime | + ItemEditDialog::ShowDuration | + ItemEditDialog::ShowValue | + ItemEditDialog::ShowLevel | + ItemEditDialog::ShowText, + labelOptions); + + dialog->setFrameTime(region.getFrame()); + dialog->setValue(region.getValue()); + dialog->setLevel(region.getLevel()); + dialog->setFrameDuration(region.getDuration()); + dialog->setText(region.getLabel()); + + if (dialog->exec() == QDialog::Accepted) { + + Event newBox = region + .withFrame(dialog->getFrameTime()) + .withValue(dialog->getValue()) + .withLevel(dialog->getLevel()) + .withDuration(dialog->getFrameDuration()) + .withLabel(dialog->getText()); + + ChangeEventsCommand *command = new ChangeEventsCommand + (m_model.untyped, tr("Edit Box")); + command->remove(region); + command->add(newBox); + finish(command); + } + + delete dialog; + return true; +} + +void +BoxLayer::moveSelection(Selection s, sv_frame_t newStartFrame) +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model) return; + + ChangeEventsCommand *command = + new ChangeEventsCommand(m_model.untyped, tr("Drag Selection")); + + EventVector points = + model->getEventsStartingWithin(s.getStartFrame(), s.getDuration()); + + for (EventVector::iterator i = points.begin(); + i != points.end(); ++i) { + + Event newPoint = (*i) + .withFrame(i->getFrame() + newStartFrame - s.getStartFrame()); + command->remove(*i); + command->add(newPoint); + } + + finish(command); +} + +void +BoxLayer::resizeSelection(Selection s, Selection newSize) +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model || !s.getDuration()) return; + + ChangeEventsCommand *command = + new ChangeEventsCommand(m_model.untyped, tr("Resize Selection")); + + EventVector points = + model->getEventsStartingWithin(s.getStartFrame(), s.getDuration()); + + double ratio = double(newSize.getDuration()) / double(s.getDuration()); + double oldStart = double(s.getStartFrame()); + double newStart = double(newSize.getStartFrame()); + + for (Event p: points) { + + double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart; + double newDuration = double(p.getDuration()) * ratio; + + Event newPoint = p + .withFrame(lrint(newFrame)) + .withDuration(lrint(newDuration)); + command->remove(p); + command->add(newPoint); + } + + finish(command); +} + +void +BoxLayer::deleteSelection(Selection s) +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model) return; + + ChangeEventsCommand *command = + new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points")); + + EventVector points = + model->getEventsStartingWithin(s.getStartFrame(), s.getDuration()); + + for (EventVector::iterator i = points.begin(); + i != points.end(); ++i) { + + if (s.contains(i->getFrame())) { + command->remove(*i); + } + } + + finish(command); +} + +void +BoxLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to) +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model) return; + + EventVector points = + model->getEventsStartingWithin(s.getStartFrame(), s.getDuration()); + + for (Event p: points) { + to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame()))); + } +} + +bool +BoxLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */) +{ + auto model = ModelById::getAs<BoxModel>(m_model); + if (!model) return false; + + const EventVector &points = from.getPoints(); + + bool realign = false; + + if (clipboardHasDifferentAlignment(v, from)) { + + QMessageBox::StandardButton button = + QMessageBox::question(v->getView(), tr("Re-align pasted items?"), + tr("The items you are pasting came from a layer with different source material from this one. Do you want to re-align them in time, to match the source material for this layer?"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::Yes); + + if (button == QMessageBox::Cancel) { + return false; + } + + if (button == QMessageBox::Yes) { + realign = true; + } + } + + ChangeEventsCommand *command = + new ChangeEventsCommand(m_model.untyped, tr("Paste")); + + for (EventVector::const_iterator i = points.begin(); + i != points.end(); ++i) { + + sv_frame_t frame = 0; + + if (!realign) { + + frame = i->getFrame(); + + } else { + + if (i->hasReferenceFrame()) { + frame = i->getReferenceFrame(); + frame = alignFromReference(v, frame); + } else { + frame = i->getFrame(); + } + } + + Event p = *i; + Event newPoint = p; + if (!p.hasValue()) { + newPoint = newPoint.withValue((model->getValueMinimum() + + model->getValueMaximum()) / 2); + } + if (!p.hasDuration()) { + sv_frame_t nextFrame = frame; + EventVector::const_iterator j = i; + for (; j != points.end(); ++j) { + if (j != i) break; + } + if (j != points.end()) { + nextFrame = j->getFrame(); + } + if (nextFrame == frame) { + newPoint = newPoint.withDuration(model->getResolution()); + } else { + newPoint = newPoint.withDuration(nextFrame - frame); + } + } + + command->add(newPoint); + } + + finish(command); + return true; +} + +void +BoxLayer::toXml(QTextStream &stream, + QString indent, QString extraAttributes) const +{ + QString s; + + s += QString("verticalScale=\"%1\" ").arg(m_verticalScale); + + SingleColourLayer::toXml(stream, indent, extraAttributes + " " + s); +} + +void +BoxLayer::setProperties(const QXmlAttributes &attributes) +{ + SingleColourLayer::setProperties(attributes); + + bool ok; + VerticalScale scale = (VerticalScale) + attributes.value("verticalScale").toInt(&ok); + if (ok) setVerticalScale(scale); +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/BoxLayer.h Wed Sep 25 09:45:42 2019 +0100 @@ -0,0 +1,143 @@ +/* -*- 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 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. +*/ + +#ifndef SV_BOX_LAYER_H +#define SV_BOX_LAYER_H + +#include "SingleColourLayer.h" +#include "VerticalScaleLayer.h" + +#include "data/model/BoxModel.h" + +#include <QObject> +#include <QColor> + +#include <map> + +class View; +class QPainter; + +class BoxLayer : public SingleColourLayer, + public VerticalScaleLayer +{ + Q_OBJECT + +public: + BoxLayer(); + + void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const override; + + int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const override; + void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const override; + + QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const override; + QString getLabelPreceding(sv_frame_t) const override; + + bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, + int &resolution, + SnapType snap) const override; + + void drawStart(LayerGeometryProvider *v, QMouseEvent *) override; + void drawDrag(LayerGeometryProvider *v, QMouseEvent *) override; + void drawEnd(LayerGeometryProvider *v, QMouseEvent *) override; + + void eraseStart(LayerGeometryProvider *v, QMouseEvent *) override; + void eraseDrag(LayerGeometryProvider *v, QMouseEvent *) override; + void eraseEnd(LayerGeometryProvider *v, QMouseEvent *) override; + + void editStart(LayerGeometryProvider *v, QMouseEvent *) override; + void editDrag(LayerGeometryProvider *v, QMouseEvent *) override; + void editEnd(LayerGeometryProvider *v, QMouseEvent *) override; + + bool editOpen(LayerGeometryProvider *v, QMouseEvent *) override; + + void moveSelection(Selection s, sv_frame_t newStartFrame) override; + void resizeSelection(Selection s, Selection newSize) override; + void deleteSelection(Selection s) override; + + void copy(LayerGeometryProvider *v, Selection s, Clipboard &to) override; + bool paste(LayerGeometryProvider *v, const Clipboard &from, + sv_frame_t frameOffset, bool interactive) override; + + ModelId getModel() const override { return m_model; } + void setModel(ModelId model); // a BoxModel + + PropertyList getProperties() const override; + QString getPropertyLabel(const PropertyName &) const override; + PropertyType getPropertyType(const PropertyName &) const override; + QString getPropertyGroupName(const PropertyName &) const override; + int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max, int *deflt) const override; + QString getPropertyValueLabel(const PropertyName &, + int value) const override; + void setProperty(const PropertyName &, int value) override; + + enum VerticalScale { + AutoAlignScale, + LinearScale, + LogScale, + }; + + void setVerticalScale(VerticalScale scale); + VerticalScale getVerticalScale() const { return m_verticalScale; } + + bool isLayerScrollable(const LayerGeometryProvider *v) const override; + + bool isLayerEditable() const override { return true; } + + int getCompletion(LayerGeometryProvider *) const override; + + bool getValueExtents(double &min, double &max, + bool &log, QString &unit) const override; + + bool getDisplayExtents(double &min, double &max) const override; + + bool adoptExtents(double min, double max, QString unit) override; + + void toXml(QTextStream &stream, QString indent = "", + QString extraAttributes = "") const override; + + void setProperties(const QXmlAttributes &attributes) override; + + /// VerticalScaleLayer methods + int getYForValue(LayerGeometryProvider *v, double value) const override; + double getValueForY(LayerGeometryProvider *v, int y) const override; + QString getScaleUnits() const override; + +protected: + void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const; + + EventVector getLocalPoints(LayerGeometryProvider *v, int x) const; + + bool getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &) const; + + ModelId m_model; + bool m_editing; + int m_dragPointX; + int m_dragPointY; + int m_dragStartX; + int m_dragStartY; + Event m_originalPoint; + Event m_editingPoint; + ChangeEventsCommand *m_editingCommand; + VerticalScale m_verticalScale; + + void finish(ChangeEventsCommand *command) { + Command *c = command->finish(); + if (c) CommandHistory::getInstance()->addCommand(c, false); + } +}; + +#endif +
--- a/layer/LayerFactory.cpp Sat Sep 21 19:13:14 2019 +0100 +++ b/layer/LayerFactory.cpp Wed Sep 25 09:45:42 2019 +0100 @@ -23,7 +23,7 @@ #include "NoteLayer.h" #include "FlexiNoteLayer.h" #include "RegionLayer.h" -#include "TimeFrequencyBoxLayer.h" +#include "BoxLayer.h" #include "TextLayer.h" #include "ImageLayer.h" #include "Colour3DPlotLayer.h" @@ -39,7 +39,7 @@ #include "data/model/SparseTimeValueModel.h" #include "data/model/NoteModel.h" #include "data/model/RegionModel.h" -#include "data/model/TimeFrequencyBoxModel.h" +#include "data/model/BoxModel.h" #include "data/model/TextModel.h" #include "data/model/ImageModel.h" #include "data/model/DenseThreeDimensionalModel.h" @@ -78,7 +78,7 @@ case Notes: return Layer::tr("Notes"); case FlexiNotes: return Layer::tr("Flexible Notes"); case Regions: return Layer::tr("Regions"); - case TimeFrequencyBox: return Layer::tr("Time-Frequency Box"); + case Boxes: return Layer::tr("Boxes"); case Text: return Layer::tr("Text"); case Image: return Layer::tr("Images"); case Colour3DPlot: return Layer::tr("Colour 3D Plot"); @@ -176,8 +176,8 @@ types.insert(Regions); } - if (ModelById::getAs<TimeFrequencyBoxModel>(modelId)) { - types.insert(TimeFrequencyBox); + if (ModelById::getAs<BoxModel>(modelId)) { + types.insert(Boxes); } if (ModelById::getAs<TextModel>(modelId)) { @@ -210,7 +210,7 @@ // types.insert(FlexiNotes); types.insert(Notes); types.insert(Regions); - types.insert(TimeFrequencyBox); + types.insert(Boxes); types.insert(Text); types.insert(Image); //!!! and in principle Colour3DPlot -- now that's a challenge @@ -228,7 +228,7 @@ if (dynamic_cast<const FlexiNoteLayer *>(layer)) return FlexiNotes; if (dynamic_cast<const NoteLayer *>(layer)) return Notes; if (dynamic_cast<const RegionLayer *>(layer)) return Regions; - if (dynamic_cast<const TimeFrequencyBoxLayer *>(layer)) return TimeFrequencyBox; + if (dynamic_cast<const BoxLayer *>(layer)) return Boxes; if (dynamic_cast<const TextLayer *>(layer)) return Text; if (dynamic_cast<const ImageLayer *>(layer)) return Image; if (dynamic_cast<const Colour3DPlotLayer *>(layer)) return Colour3DPlot; @@ -249,7 +249,7 @@ case Notes: return "notes"; case FlexiNotes: return "flexinotes"; case Regions: return "regions"; - case TimeFrequencyBox: return "timefreq"; + case Boxes: return "boxes"; case Text: return "text"; case Image: return "image"; case Colour3DPlot: return "colour3d"; @@ -276,7 +276,7 @@ case Notes: return "notes"; case FlexiNotes: return "flexinotes"; case Regions: return "regions"; - case TimeFrequencyBox: return "timefrequencybox"; + case Boxes: return "boxes"; case Text: return "text"; case Image: return "image"; case Colour3DPlot: return "colour3dplot"; @@ -302,7 +302,7 @@ if (name == "notes") return Notes; if (name == "flexinotes") return FlexiNotes; if (name == "regions") return Regions; - if (name == "timefrequencybox") return TimeFrequencyBox; + if (name == "boxes" || name == "timefrequencybox") return Boxes; if (name == "text") return Text; if (name == "image") return Image; if (name == "colour3dplot") return Colour3DPlot; @@ -341,7 +341,7 @@ if (trySetModel<RegionLayer, RegionModel>(layer, model)) return; - if (trySetModel<TimeFrequencyBoxLayer, TimeFrequencyBoxModel>(layer, model)) + if (trySetModel<BoxLayer, BoxModel>(layer, model)) return; if (trySetModel<TextLayer, TextModel>(layer, model)) @@ -375,8 +375,8 @@ return std::make_shared<NoteModel>(rate, 1, true); } else if (layerType == Regions) { return std::make_shared<RegionModel>(rate, 1, true); - } else if (layerType == TimeFrequencyBox) { - return std::make_shared<TimeFrequencyBoxModel>(rate, 1, true); + } else if (layerType == Boxes) { + return std::make_shared<BoxModel>(rate, 1, true); } else if (layerType == Text) { return std::make_shared<TextModel>(rate, 1, true); } else if (layerType == Image) { @@ -454,8 +454,8 @@ layer = new RegionLayer; break; - case TimeFrequencyBox: - layer = new TimeFrequencyBoxLayer; + case Boxes: + layer = new BoxLayer; break; case Text:
--- a/layer/LayerFactory.h Sat Sep 21 19:13:14 2019 +0100 +++ b/layer/LayerFactory.h Wed Sep 25 09:45:42 2019 +0100 @@ -38,7 +38,7 @@ Notes, FlexiNotes, Regions, - TimeFrequencyBox, + Boxes, Text, Image, Colour3DPlot,
--- a/layer/TimeFrequencyBoxLayer.cpp Sat Sep 21 19:13:14 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1175 +0,0 @@ -/* -*- 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 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 "TimeFrequencyBoxLayer.h" - -#include "data/model/Model.h" -#include "base/RealTime.h" -#include "base/Profiler.h" -#include "base/LogRange.h" - -#include "ColourDatabase.h" -#include "ColourMapper.h" -#include "LinearNumericalScale.h" -#include "LogNumericalScale.h" -#include "PaintAssistant.h" - -#include "view/View.h" - -#include "data/model/TimeFrequencyBoxModel.h" - -#include "widgets/ItemEditDialog.h" -#include "widgets/TextAbbrev.h" - -#include <QPainter> -#include <QPainterPath> -#include <QMouseEvent> -#include <QTextStream> -#include <QMessageBox> - -#include <iostream> -#include <cmath> - -TimeFrequencyBoxLayer::TimeFrequencyBoxLayer() : - SingleColourLayer(), - m_editing(false), - m_dragPointX(0), - m_dragPointY(0), - m_dragStartX(0), - m_dragStartY(0), - m_originalPoint(0, 0.0, 0, tr("New Box")), - m_editingPoint(0, 0.0, 0, tr("New Box")), - m_editingCommand(nullptr) -{ - -} - -int -TimeFrequencyBoxLayer::getCompletion(LayerGeometryProvider *) const -{ - auto model = ModelById::get(m_model); - if (model) return model->getCompletion(); - else return 0; -} - -void -TimeFrequencyBoxLayer::setModel(ModelId modelId) -{ - auto oldModel = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - auto newModel = ModelById::getAs<TimeFrequencyBoxModel>(modelId); - - if (!modelId.isNone() && !newModel) { - throw std::logic_error("Not a TimeFrequencyBoxModel"); - } - - if (m_model == modelId) return; - m_model = modelId; - - if (newModel) { - connectSignals(m_model); - } - - emit modelReplaced(); -} - -Layer::PropertyList -TimeFrequencyBoxLayer::getProperties() const -{ - PropertyList list = SingleColourLayer::getProperties(); - list.push_back("Vertical Scale"); - return list; -} - -QString -TimeFrequencyBoxLayer::getPropertyLabel(const PropertyName &name) const -{ - if (name == "Vertical Scale") return tr("Vertical Scale"); - return SingleColourLayer::getPropertyLabel(name); -} - -Layer::PropertyType -TimeFrequencyBoxLayer::getPropertyType(const PropertyName &name) const -{ - if (name == "Vertical Scale") return ValueProperty; - return SingleColourLayer::getPropertyType(name); -} - -QString -TimeFrequencyBoxLayer::getPropertyGroupName(const PropertyName &name) const -{ - if (name == "Vertical Scale") { - return tr("Scale"); - } - return SingleColourLayer::getPropertyGroupName(name); -} - -int -TimeFrequencyBoxLayer::getPropertyRangeAndValue(const PropertyName &name, - int *min, int *max, int *deflt) const -{ - int val = 0; - - if (name == "Vertical Scale") { - - if (min) *min = 0; - if (max) *max = 2; - if (deflt) *deflt = int(LinearScale); - - val = int(m_verticalScale); - - } else { - val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt); - } - - return val; -} - -QString -TimeFrequencyBoxLayer::getPropertyValueLabel(const PropertyName &name, - int value) const -{ - if (name == "Vertical Scale") { - switch (value) { - default: - case 0: return tr("Auto-Align"); - case 1: return tr("Linear"); - case 2: return tr("Log"); - } - } - return SingleColourLayer::getPropertyValueLabel(name, value); -} - -void -TimeFrequencyBoxLayer::setProperty(const PropertyName &name, int value) -{ - if (name == "Vertical Scale") { - setVerticalScale(VerticalScale(value)); - } else { - return SingleColourLayer::setProperty(name, value); - } -} - -void -TimeFrequencyBoxLayer::setVerticalScale(VerticalScale scale) -{ - if (m_verticalScale == scale) return; - m_verticalScale = scale; - emit layerParametersChanged(); -} - -bool -TimeFrequencyBoxLayer::isLayerScrollable(const LayerGeometryProvider *v) const -{ - QPoint discard; - return !v->shouldIlluminateLocalFeatures(this, discard); -} - -bool -TimeFrequencyBoxLayer::getValueExtents(double &min, double &max, - bool &logarithmic, QString &unit) const -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model) return false; - min = model->getFrequencyMinimum(); - max = model->getFrequencyMaximum(); - unit = getScaleUnits(); - - if (m_verticalScale == LogScale) logarithmic = true; - - return true; -} - -bool -TimeFrequencyBoxLayer::getDisplayExtents(double &min, double &max) const -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model || m_verticalScale == AutoAlignScale) return false; - - min = model->getFrequencyMinimum(); - max = model->getFrequencyMaximum(); - - return true; -} - -EventVector -TimeFrequencyBoxLayer::getLocalPoints(LayerGeometryProvider *v, int x) const -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model) return EventVector(); - - sv_frame_t frame = v->getFrameForX(x); - - EventVector local = model->getEventsCovering(frame); - if (!local.empty()) return local; - - int fuzz = ViewManager::scalePixelSize(2); - sv_frame_t start = v->getFrameForX(x - fuzz); - sv_frame_t end = v->getFrameForX(x + fuzz); - - local = model->getEventsStartingWithin(frame, end - frame); - if (!local.empty()) return local; - - local = model->getEventsSpanning(start, frame - start); - if (!local.empty()) return local; - - return {}; -} - -bool -TimeFrequencyBoxLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, - Event &point) const -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model) return false; - - sv_frame_t frame = v->getFrameForX(x); - - EventVector onPoints = model->getEventsCovering(frame); - if (onPoints.empty()) return false; - - int nearestDistance = -1; - for (const auto &p: onPoints) { - int distance = getYForValue(v, p.getValue()) - y; - if (distance < 0) distance = -distance; - if (nearestDistance == -1 || distance < nearestDistance) { - nearestDistance = distance; - point = p; - } - } - - return true; -} - -QString -TimeFrequencyBoxLayer::getLabelPreceding(sv_frame_t frame) const -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model) return ""; - EventVector points = model->getEventsStartingWithin - (model->getStartFrame(), frame - model->getStartFrame()); - if (!points.empty()) { - for (auto i = points.rbegin(); i != points.rend(); ++i) { - if (i->getLabel() != QString()) { - return i->getLabel(); - } - } - } - return QString(); -} - -QString -TimeFrequencyBoxLayer::getFeatureDescription(LayerGeometryProvider *v, - QPoint &pos) const -{ - int x = pos.x(); - - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model || !model->getSampleRate()) return ""; - - EventVector points = getLocalPoints(v, x); - - if (points.empty()) { - if (!model->isReady()) { - return tr("In progress"); - } else { - return tr("No local points"); - } - } - - Event box; - EventVector::iterator i; - - for (i = points.begin(); i != points.end(); ++i) { - - int y0 = getYForValue(v, i->getValue()); - int y1 = getYForValue(v, i->getValue() + fabsf(i->getLevel())); - - if (pos.y() >= y0 && pos.y() <= y1) { - box = *i; - break; - } - } - - if (i == points.end()) return tr("No local points"); - - RealTime rt = RealTime::frame2RealTime(box.getFrame(), - model->getSampleRate()); - RealTime rd = RealTime::frame2RealTime(box.getDuration(), - model->getSampleRate()); - - QString rangeText; - - rangeText = tr("%1 %2 - %3 %4") - .arg(box.getValue()).arg(getScaleUnits()) - .arg(box.getValue() + fabsf(box.getLevel())).arg(getScaleUnits()); - - QString text; - - if (box.getLabel() == "") { - text = QString(tr("Time:\t%1\nDuration:\t%2\nFrequency:\t%3\nNo label")) - .arg(rt.toText(true).c_str()) - .arg(rd.toText(true).c_str()) - .arg(rangeText); - } else { - text = QString(tr("Time:\t%1\nDuration:\t%2\nFrequency:\t%3\nLabel:\t%4")) - .arg(rt.toText(true).c_str()) - .arg(rd.toText(true).c_str()) - .arg(rangeText) - .arg(box.getLabel()); - } - - pos = QPoint(v->getXForFrame(box.getFrame()), - getYForValue(v, box.getValue())); - return text; -} - -bool -TimeFrequencyBoxLayer::snapToFeatureFrame(LayerGeometryProvider *v, - sv_frame_t &frame, - int &resolution, - SnapType snap) const -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model) { - return Layer::snapToFeatureFrame(v, frame, resolution, snap); - } - - // SnapLeft / SnapRight: return frame of nearest feature in that - // direction no matter how far away - // - // SnapNeighbouring: return frame of feature that would be used in - // an editing operation, i.e. closest feature in either direction - // but only if it is "close enough" - - resolution = model->getResolution(); - - if (snap == SnapNeighbouring) { - EventVector points = getLocalPoints(v, v->getXForFrame(frame)); - if (points.empty()) return false; - frame = points.begin()->getFrame(); - return true; - } - - // Normally we snap to the start frame of whichever event we - // find. However here, for SnapRight only, if the end frame of - // whichever event we would have snapped to had we been snapping - // left is closer than the start frame of the next event to the - // right, then we snap to that frame instead. Clear? - - Event left; - bool haveLeft = false; - if (model->getNearestEventMatching - (frame, [](Event) { return true; }, EventSeries::Backward, left)) { - haveLeft = true; - } - - if (snap == SnapLeft) { - frame = left.getFrame(); - return haveLeft; - } - - Event right; - bool haveRight = false; - if (model->getNearestEventMatching - (frame, [](Event) { return true; }, EventSeries::Forward, right)) { - haveRight = true; - } - - if (haveLeft) { - sv_frame_t leftEnd = left.getFrame() + left.getDuration(); - if (leftEnd > frame) { - if (haveRight) { - if (leftEnd - frame < right.getFrame() - frame) { - frame = leftEnd; - } else { - frame = right.getFrame(); - } - } else { - frame = leftEnd; - } - return true; - } - } - - if (haveRight) { - frame = right.getFrame(); - return true; - } - - return false; -} - -QString -TimeFrequencyBoxLayer::getScaleUnits() const -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (model) return model->getScaleUnits(); - else return ""; -} - -void -TimeFrequencyBoxLayer::getScaleExtents(LayerGeometryProvider *v, - double &min, double &max, - bool &log) const -{ - min = 0.0; - max = 0.0; - log = false; - - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model) return; - - QString queryUnits; - queryUnits = getScaleUnits(); - - if (m_verticalScale == AutoAlignScale) { - - if (!v->getValueExtents(queryUnits, min, max, log)) { - - min = model->getFrequencyMinimum(); - max = model->getFrequencyMaximum(); - -// cerr << "TimeFrequencyBoxLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; - - } else if (log) { - - LogRange::mapRange(min, max); - -// cerr << "TimeFrequencyBoxLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; - - } - - } else { - - min = model->getFrequencyMinimum(); - max = model->getFrequencyMaximum(); - - if (m_verticalScale == LogScale) { - LogRange::mapRange(min, max); - log = true; - } - } - - if (max == min) max = min + 1.0; -} - -int -TimeFrequencyBoxLayer::getYForValue(LayerGeometryProvider *v, double val) const -{ - double min = 0.0, max = 0.0; - bool logarithmic = false; - int h = v->getPaintHeight(); - - getScaleExtents(v, min, max, logarithmic); - -// cerr << "TimeFrequencyBoxLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl; -// cerr << "h = " << h << ", margin = " << margin << endl; - - if (logarithmic) { - val = LogRange::map(val); - } - - return int(h - ((val - min) * h) / (max - min)); -} - -double -TimeFrequencyBoxLayer::getValueForY(LayerGeometryProvider *v, int y) const -{ - double min = 0.0, max = 0.0; - bool logarithmic = false; - int h = v->getPaintHeight(); - - getScaleExtents(v, min, max, logarithmic); - - double val = min + (double(h - y) * double(max - min)) / h; - - if (logarithmic) { - val = pow(10.0, val); - } - - return val; -} - -void -TimeFrequencyBoxLayer::paint(LayerGeometryProvider *v, QPainter &paint, - QRect rect) const -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model || !model->isOK()) return; - - sv_samplerate_t sampleRate = model->getSampleRate(); - if (!sampleRate) return; - -// Profiler profiler("TimeFrequencyBoxLayer::paint", true); - - int x0 = rect.left() - 40, x1 = rect.right(); - - sv_frame_t wholeFrame0 = v->getFrameForX(0); - sv_frame_t wholeFrame1 = v->getFrameForX(v->getPaintWidth()); - - EventVector points(model->getEventsSpanning(wholeFrame0, - wholeFrame1 - wholeFrame0)); - if (points.empty()) return; - - paint.setPen(getBaseQColor()); - -// SVDEBUG << "TimeFrequencyBoxLayer::paint: resolution is " -// << model->getResolution() << " frames" << endl; - - double min = model->getFrequencyMinimum(); - double max = model->getFrequencyMaximum(); - if (max == min) max = min + 1.0; - - QPoint localPos; - Event illuminatePoint(0); - bool shouldIlluminate = false; - - if (v->shouldIlluminateLocalFeatures(this, localPos)) { - shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(), - illuminatePoint); - } - - paint.save(); - paint.setRenderHint(QPainter::Antialiasing, false); - - QFontMetrics fm = paint.fontMetrics(); - - for (EventVector::const_iterator i = points.begin(); - i != points.end(); ++i) { - - const Event &p(*i); - - int x = v->getXForFrame(p.getFrame()); - int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x; - int y = getYForValue(v, p.getValue()); - int h = getYForValue(v, p.getValue() + fabsf(p.getLevel())) - y; - int ex = x + w; - int gap = v->scalePixelSize(2); - - EventVector::const_iterator j = i; - ++j; - - if (j != points.end()) { - const Event &q(*j); - int nx = v->getXForFrame(q.getFrame()); - if (nx < ex) ex = nx; - } - - if (w < 1) w = 1; - - paint.setPen(getBaseQColor()); - paint.setBrush(Qt::NoBrush); - - if ((shouldIlluminate && illuminatePoint == p) || - (m_editing && m_editingPoint == p)) { - - paint.setPen(QPen(getBaseQColor(), v->scalePixelSize(2))); - - // Qt 5.13 deprecates QFontMetrics::width(), but its suggested - // replacement (horizontalAdvance) was only added in Qt 5.11 - // which is too new for us -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - - if (abs(h) > 2 * fm.height()) { - - QString y0label = QString("%1 %2") - .arg(p.getValue()) - .arg(getScaleUnits()); - - QString y1label = QString("%1 %2") - .arg(p.getValue() + fabsf(p.getLevel())) - .arg(getScaleUnits()); - - PaintAssistant::drawVisibleText - (v, paint, - x - fm.width(y0label) - gap, - y - fm.descent(), - y0label, PaintAssistant::OutlinedText); - - PaintAssistant::drawVisibleText - (v, paint, - x - fm.width(y1label) - gap, - y + h + fm.ascent(), - y1label, PaintAssistant::OutlinedText); - - } else { - - QString ylabel = QString("%1 %2 - %3 %4") - .arg(p.getValue()) - .arg(getScaleUnits()) - .arg(p.getValue() + fabsf(p.getLevel())) - .arg(getScaleUnits()); - - PaintAssistant::drawVisibleText - (v, paint, - x - fm.width(ylabel) - gap, - y - fm.descent(), - ylabel, PaintAssistant::OutlinedText); - } - - QString t0label = RealTime::frame2RealTime - (p.getFrame(), model->getSampleRate()).toText(true).c_str(); - - QString t1label = RealTime::frame2RealTime - (p.getFrame() + p.getDuration(), model->getSampleRate()) - .toText(true).c_str(); - - PaintAssistant::drawVisibleText - (v, paint, x, y + fm.ascent() + gap, - t0label, PaintAssistant::OutlinedText); - - if (w > fm.width(t0label) + fm.width(t1label) + gap * 3) { - - PaintAssistant::drawVisibleText - (v, paint, - x + w - fm.width(t1label), - y + fm.ascent() + gap, - t1label, PaintAssistant::OutlinedText); - - } else { - - PaintAssistant::drawVisibleText - (v, paint, - x + w - fm.width(t1label), - y + fm.ascent() + fm.height() + gap, - t1label, PaintAssistant::OutlinedText); - } - } - - paint.drawRect(x, y, w, h); - } - - for (EventVector::const_iterator i = points.begin(); - i != points.end(); ++i) { - - const Event &p(*i); - - QString label = p.getLabel(); - if (label == "") continue; - - if (shouldIlluminate && illuminatePoint == p) { - continue; // already handled this in illumination special case - } - - int x = v->getXForFrame(p.getFrame()); - int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x; - int y = getYForValue(v, p.getValue()); - - int labelWidth = fm.width(label); - - int gap = v->scalePixelSize(2); - - if (x + w < x0 || x - labelWidth - gap > x1) { - continue; - } - - int labelX, labelY; - - labelX = x - labelWidth - gap; - labelY = y - fm.descent(); - - PaintAssistant::drawVisibleText(v, paint, labelX, labelY, label, - PaintAssistant::OutlinedText); - } - - paint.restore(); -} - -int -TimeFrequencyBoxLayer::getVerticalScaleWidth(LayerGeometryProvider *v, - bool, QPainter &paint) const -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model || m_verticalScale == AutoAlignScale) { - return 0; - } else { - if (m_verticalScale == LogScale) { - return LogNumericalScale().getWidth(v, paint); - } else { - return LinearNumericalScale().getWidth(v, paint); - } - } -} - -void -TimeFrequencyBoxLayer::paintVerticalScale(LayerGeometryProvider *v, - bool, QPainter &paint, QRect) const -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model || model->isEmpty()) return; - - QString unit; - double min, max; - bool logarithmic; - - int w = getVerticalScaleWidth(v, false, paint); - - getScaleExtents(v, min, max, logarithmic); - - if (logarithmic) { - LogNumericalScale().paintVertical(v, this, paint, 0, min, max); - } else { - LinearNumericalScale().paintVertical(v, this, paint, 0, min, max); - } - - if (getScaleUnits() != "") { - int mw = w - 5; - paint.drawText(5, - 5 + paint.fontMetrics().ascent(), - TextAbbrev::abbreviate(getScaleUnits(), - paint.fontMetrics(), - mw)); - } -} - -void -TimeFrequencyBoxLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e) -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model) return; - - sv_frame_t frame = v->getFrameForX(e->x()); - if (frame < 0) frame = 0; - frame = frame / model->getResolution() * model->getResolution(); - - double frequency = getValueForY(v, e->y()); - - m_editingPoint = Event(frame, float(frequency), 0, ""); - m_originalPoint = m_editingPoint; - - if (m_editingCommand) finish(m_editingCommand); - m_editingCommand = new ChangeEventsCommand(m_model.untyped, - tr("Draw Time-Frequency Box")); - m_editingCommand->add(m_editingPoint); - - m_editing = true; -} - -void -TimeFrequencyBoxLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e) -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model || !m_editing) return; - - sv_frame_t dragFrame = v->getFrameForX(e->x()); - if (dragFrame < 0) dragFrame = 0; - dragFrame = dragFrame / model->getResolution() * model->getResolution(); - - sv_frame_t eventFrame = m_originalPoint.getFrame(); - sv_frame_t eventDuration = dragFrame - eventFrame; - if (eventDuration < 0) { - eventFrame = eventFrame + eventDuration; - eventDuration = -eventDuration; - } else if (eventDuration == 0) { - eventDuration = model->getResolution(); - } - - double dragFrequency = getValueForY(v, e->y()); - - double eventFrequency = m_originalPoint.getValue(); - double eventFreqDiff = dragFrequency - eventFrequency; - if (eventFreqDiff < 0) { - eventFrequency = eventFrequency + eventFreqDiff; - eventFreqDiff = -eventFreqDiff; - } - - m_editingCommand->remove(m_editingPoint); - m_editingPoint = m_editingPoint - .withFrame(eventFrame) - .withDuration(eventDuration) - .withValue(float(eventFrequency)) - .withLevel(float(eventFreqDiff)); - m_editingCommand->add(m_editingPoint); -} - -void -TimeFrequencyBoxLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *) -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model || !m_editing) return; - finish(m_editingCommand); - m_editingCommand = nullptr; - m_editing = false; -} - -void -TimeFrequencyBoxLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e) -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model) return; - - if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return; - - if (m_editingCommand) { - finish(m_editingCommand); - m_editingCommand = nullptr; - } - - m_editing = true; -} - -void -TimeFrequencyBoxLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *) -{ -} - -void -TimeFrequencyBoxLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e) -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model || !m_editing) return; - - m_editing = false; - - Event p(0); - if (!getPointToDrag(v, e->x(), e->y(), p)) return; - if (p.getFrame() != m_editingPoint.getFrame() || - p.getValue() != m_editingPoint.getValue()) return; - - m_editingCommand = new ChangeEventsCommand - (m_model.untyped, tr("Erase Time-Frequency Box")); - - m_editingCommand->remove(m_editingPoint); - - finish(m_editingCommand); - m_editingCommand = nullptr; - m_editing = false; -} - -void -TimeFrequencyBoxLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e) -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model) return; - - if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) { - return; - } - - m_dragPointX = v->getXForFrame(m_editingPoint.getFrame()); - m_dragPointY = getYForValue(v, m_editingPoint.getValue()); - - m_originalPoint = m_editingPoint; - - if (m_editingCommand) { - finish(m_editingCommand); - m_editingCommand = nullptr; - } - - m_editing = true; - m_dragStartX = e->x(); - m_dragStartY = e->y(); -} - -void -TimeFrequencyBoxLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e) -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model || !m_editing) return; - - int xdist = e->x() - m_dragStartX; - int ydist = e->y() - m_dragStartY; - int newx = m_dragPointX + xdist; - int newy = m_dragPointY + ydist; - - sv_frame_t frame = v->getFrameForX(newx); - if (frame < 0) frame = 0; - frame = frame / model->getResolution() * model->getResolution(); - - double value = getValueForY(v, newy); - - if (!m_editingCommand) { - m_editingCommand = new ChangeEventsCommand - (m_model.untyped, - tr("Drag Time-Frequency Box")); - } - - m_editingCommand->remove(m_editingPoint); - m_editingPoint = m_editingPoint - .withFrame(frame) - .withValue(float(value)); - m_editingCommand->add(m_editingPoint); -} - -void -TimeFrequencyBoxLayer::editEnd(LayerGeometryProvider *, QMouseEvent *) -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model || !m_editing) return; - - if (m_editingCommand) { - - QString newName = m_editingCommand->getName(); - - if (m_editingPoint.getFrame() != m_originalPoint.getFrame()) { - if (m_editingPoint.getValue() != m_originalPoint.getValue()) { - newName = tr("Edit Time-Frequency Box"); - } else { - newName = tr("Relocate Time-Frequency Box"); - } - } else { - newName = tr("Change Point Value"); - } - - m_editingCommand->setName(newName); - finish(m_editingCommand); - } - - m_editingCommand = nullptr; - m_editing = false; -} - -bool -TimeFrequencyBoxLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e) -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model) return false; - - Event region(0); - if (!getPointToDrag(v, e->x(), e->y(), region)) return false; - - ItemEditDialog::LabelOptions labelOptions; - labelOptions.valueLabel = tr("Minimum Frequency"); - labelOptions.levelLabel = tr("Frequency Extent"); - labelOptions.valueUnits = getScaleUnits(); - labelOptions.levelUnits = getScaleUnits(); - - ItemEditDialog *dialog = new ItemEditDialog - (model->getSampleRate(), - ItemEditDialog::ShowTime | - ItemEditDialog::ShowDuration | - ItemEditDialog::ShowValue | - ItemEditDialog::ShowLevel | - ItemEditDialog::ShowText, - labelOptions); - - dialog->setFrameTime(region.getFrame()); - dialog->setValue(region.getValue()); - dialog->setLevel(region.getLevel()); - dialog->setFrameDuration(region.getDuration()); - dialog->setText(region.getLabel()); - - if (dialog->exec() == QDialog::Accepted) { - - Event newTimeFrequencyBox = region - .withFrame(dialog->getFrameTime()) - .withValue(dialog->getValue()) - .withLevel(dialog->getLevel()) - .withDuration(dialog->getFrameDuration()) - .withLabel(dialog->getText()); - - ChangeEventsCommand *command = new ChangeEventsCommand - (m_model.untyped, tr("Edit Time-Frequency Box")); - command->remove(region); - command->add(newTimeFrequencyBox); - finish(command); - } - - delete dialog; - return true; -} - -void -TimeFrequencyBoxLayer::moveSelection(Selection s, sv_frame_t newStartFrame) -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model) return; - - ChangeEventsCommand *command = - new ChangeEventsCommand(m_model.untyped, tr("Drag Selection")); - - EventVector points = - model->getEventsStartingWithin(s.getStartFrame(), s.getDuration()); - - for (EventVector::iterator i = points.begin(); - i != points.end(); ++i) { - - Event newPoint = (*i) - .withFrame(i->getFrame() + newStartFrame - s.getStartFrame()); - command->remove(*i); - command->add(newPoint); - } - - finish(command); -} - -void -TimeFrequencyBoxLayer::resizeSelection(Selection s, Selection newSize) -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model || !s.getDuration()) return; - - ChangeEventsCommand *command = - new ChangeEventsCommand(m_model.untyped, tr("Resize Selection")); - - EventVector points = - model->getEventsStartingWithin(s.getStartFrame(), s.getDuration()); - - double ratio = double(newSize.getDuration()) / double(s.getDuration()); - double oldStart = double(s.getStartFrame()); - double newStart = double(newSize.getStartFrame()); - - for (Event p: points) { - - double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart; - double newDuration = double(p.getDuration()) * ratio; - - Event newPoint = p - .withFrame(lrint(newFrame)) - .withDuration(lrint(newDuration)); - command->remove(p); - command->add(newPoint); - } - - finish(command); -} - -void -TimeFrequencyBoxLayer::deleteSelection(Selection s) -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model) return; - - ChangeEventsCommand *command = - new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points")); - - EventVector points = - model->getEventsStartingWithin(s.getStartFrame(), s.getDuration()); - - for (EventVector::iterator i = points.begin(); - i != points.end(); ++i) { - - if (s.contains(i->getFrame())) { - command->remove(*i); - } - } - - finish(command); -} - -void -TimeFrequencyBoxLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to) -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model) return; - - EventVector points = - model->getEventsStartingWithin(s.getStartFrame(), s.getDuration()); - - for (Event p: points) { - to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame()))); - } -} - -bool -TimeFrequencyBoxLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */) -{ - auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); - if (!model) return false; - - const EventVector &points = from.getPoints(); - - bool realign = false; - - if (clipboardHasDifferentAlignment(v, from)) { - - QMessageBox::StandardButton button = - QMessageBox::question(v->getView(), tr("Re-align pasted items?"), - tr("The items you are pasting came from a layer with different source material from this one. Do you want to re-align them in time, to match the source material for this layer?"), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, - QMessageBox::Yes); - - if (button == QMessageBox::Cancel) { - return false; - } - - if (button == QMessageBox::Yes) { - realign = true; - } - } - - ChangeEventsCommand *command = - new ChangeEventsCommand(m_model.untyped, tr("Paste")); - - for (EventVector::const_iterator i = points.begin(); - i != points.end(); ++i) { - - sv_frame_t frame = 0; - - if (!realign) { - - frame = i->getFrame(); - - } else { - - if (i->hasReferenceFrame()) { - frame = i->getReferenceFrame(); - frame = alignFromReference(v, frame); - } else { - frame = i->getFrame(); - } - } - - Event p = *i; - Event newPoint = p; - if (!p.hasValue()) { - newPoint = newPoint.withValue((model->getFrequencyMinimum() + - model->getFrequencyMaximum()) / 2); - } - if (!p.hasDuration()) { - sv_frame_t nextFrame = frame; - EventVector::const_iterator j = i; - for (; j != points.end(); ++j) { - if (j != i) break; - } - if (j != points.end()) { - nextFrame = j->getFrame(); - } - if (nextFrame == frame) { - newPoint = newPoint.withDuration(model->getResolution()); - } else { - newPoint = newPoint.withDuration(nextFrame - frame); - } - } - - command->add(newPoint); - } - - finish(command); - return true; -} - -void -TimeFrequencyBoxLayer::toXml(QTextStream &stream, - QString indent, QString extraAttributes) const -{ - QString s; - - s += QString("verticalScale=\"%1\" ").arg(m_verticalScale); - - SingleColourLayer::toXml(stream, indent, extraAttributes + " " + s); -} - -void -TimeFrequencyBoxLayer::setProperties(const QXmlAttributes &attributes) -{ - SingleColourLayer::setProperties(attributes); - - bool ok; - VerticalScale scale = (VerticalScale) - attributes.value("verticalScale").toInt(&ok); - if (ok) setVerticalScale(scale); -} - -
--- a/layer/TimeFrequencyBoxLayer.h Sat Sep 21 19:13:14 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,141 +0,0 @@ -/* -*- 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 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. -*/ - -#ifndef SV_TIME_FREQUENCY_BOX_LAYER_H -#define SV_TIME_FREQUENCY_BOX_LAYER_H - -#include "SingleColourLayer.h" -#include "VerticalScaleLayer.h" - -#include "data/model/TimeFrequencyBoxModel.h" - -#include <QObject> -#include <QColor> - -#include <map> - -class View; -class QPainter; - -class TimeFrequencyBoxLayer : public SingleColourLayer, - public VerticalScaleLayer -{ - Q_OBJECT - -public: - TimeFrequencyBoxLayer(); - - void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const override; - - int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const override; - void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const override; - - QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const override; - QString getLabelPreceding(sv_frame_t) const override; - - bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, - int &resolution, - SnapType snap) const override; - - void drawStart(LayerGeometryProvider *v, QMouseEvent *) override; - void drawDrag(LayerGeometryProvider *v, QMouseEvent *) override; - void drawEnd(LayerGeometryProvider *v, QMouseEvent *) override; - - void eraseStart(LayerGeometryProvider *v, QMouseEvent *) override; - void eraseDrag(LayerGeometryProvider *v, QMouseEvent *) override; - void eraseEnd(LayerGeometryProvider *v, QMouseEvent *) override; - - void editStart(LayerGeometryProvider *v, QMouseEvent *) override; - void editDrag(LayerGeometryProvider *v, QMouseEvent *) override; - void editEnd(LayerGeometryProvider *v, QMouseEvent *) override; - - bool editOpen(LayerGeometryProvider *v, QMouseEvent *) override; - - void moveSelection(Selection s, sv_frame_t newStartFrame) override; - void resizeSelection(Selection s, Selection newSize) override; - void deleteSelection(Selection s) override; - - void copy(LayerGeometryProvider *v, Selection s, Clipboard &to) override; - bool paste(LayerGeometryProvider *v, const Clipboard &from, - sv_frame_t frameOffset, bool interactive) override; - - ModelId getModel() const override { return m_model; } - void setModel(ModelId model); // a TimeFrequencyBoxModel - - PropertyList getProperties() const override; - QString getPropertyLabel(const PropertyName &) const override; - PropertyType getPropertyType(const PropertyName &) const override; - QString getPropertyGroupName(const PropertyName &) const override; - int getPropertyRangeAndValue(const PropertyName &, - int *min, int *max, int *deflt) const override; - QString getPropertyValueLabel(const PropertyName &, - int value) const override; - void setProperty(const PropertyName &, int value) override; - - enum VerticalScale { - AutoAlignScale, - LinearScale, - LogScale, - }; - - void setVerticalScale(VerticalScale scale); - VerticalScale getVerticalScale() const { return m_verticalScale; } - - bool isLayerScrollable(const LayerGeometryProvider *v) const override; - - bool isLayerEditable() const override { return true; } - - int getCompletion(LayerGeometryProvider *) const override; - - bool getValueExtents(double &min, double &max, - bool &log, QString &unit) const override; - - bool getDisplayExtents(double &min, double &max) const override; - - void toXml(QTextStream &stream, QString indent = "", - QString extraAttributes = "") const override; - - void setProperties(const QXmlAttributes &attributes) override; - - /// VerticalScaleLayer methods - int getYForValue(LayerGeometryProvider *v, double value) const override; - double getValueForY(LayerGeometryProvider *v, int y) const override; - QString getScaleUnits() const override; - -protected: - void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const; - - EventVector getLocalPoints(LayerGeometryProvider *v, int x) const; - - bool getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &) const; - - ModelId m_model; - bool m_editing; - int m_dragPointX; - int m_dragPointY; - int m_dragStartX; - int m_dragStartY; - Event m_originalPoint; - Event m_editingPoint; - ChangeEventsCommand *m_editingCommand; - VerticalScale m_verticalScale; - - void finish(ChangeEventsCommand *command) { - Command *c = command->finish(); - if (c) CommandHistory::getInstance()->addCommand(c, false); - } -}; - -#endif -