Mercurial > hg > svgui
changeset 1522:d5ef91dc2ea7
Merge from branch time-frequency-boxes
author | Chris Cannam |
---|---|
date | Wed, 25 Sep 2019 13:48:04 +0100 |
parents | 872873aa6463 (current diff) d3ef60b6ae93 (diff) |
children | 4fab699536f9 |
files | |
diffstat | 16 files changed, 1494 insertions(+), 26 deletions(-) [+] |
line wrap: on
line diff
--- a/files.pri Tue Sep 17 12:50:58 2019 +0100 +++ b/files.pri Wed Sep 25 13:48:04 2019 +0100 @@ -32,6 +32,7 @@ layer/SpectrumLayer.h \ layer/TextLayer.h \ layer/TimeInstantLayer.h \ + layer/BoxLayer.h \ layer/TimeRulerLayer.h \ layer/TimeValueLayer.h \ layer/VerticalScaleLayer.h \ @@ -121,6 +122,7 @@ layer/SpectrumLayer.cpp \ layer/TextLayer.cpp \ layer/TimeInstantLayer.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 13:48:04 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 13:48:04 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/FlexiNoteLayer.cpp Tue Sep 17 12:50:58 2019 +0100 +++ b/layer/FlexiNoteLayer.cpp Wed Sep 25 13:48:04 2019 +0100 @@ -1604,13 +1604,12 @@ Event note(0); if (!getPointToDrag(v, e->x(), e->y(), note)) return false; -// Event note = *points.begin(); - ItemEditDialog *dialog = new ItemEditDialog (model->getSampleRate(), ItemEditDialog::ShowTime | ItemEditDialog::ShowDuration | ItemEditDialog::ShowValue | + ItemEditDialog::ShowLevel | ItemEditDialog::ShowText, getScaleUnits());
--- a/layer/Layer.h Tue Sep 17 12:50:58 2019 +0100 +++ b/layer/Layer.h Wed Sep 25 13:48:04 2019 +0100 @@ -488,6 +488,17 @@ } /** + * Consider using the given value extents and units for this + * layer. This may be called on a new layer when added, to prepare + * it for editing, and the extents are those of the layer + * underneath it. May not be appropriate for most layer types. + */ + virtual bool adoptExtents(double /* min */, double /* max */, + QString /* unit */) { + return false; + } + + /** * Return the value and unit at the given x coordinate in the * given view. This is for descriptive purposes using the * measurement tool. The default implementation works correctly
--- a/layer/LayerFactory.cpp Tue Sep 17 12:50:58 2019 +0100 +++ b/layer/LayerFactory.cpp Wed Sep 25 13:48:04 2019 +0100 @@ -23,6 +23,7 @@ #include "NoteLayer.h" #include "FlexiNoteLayer.h" #include "RegionLayer.h" +#include "BoxLayer.h" #include "TextLayer.h" #include "ImageLayer.h" #include "Colour3DPlotLayer.h" @@ -38,6 +39,7 @@ #include "data/model/SparseTimeValueModel.h" #include "data/model/NoteModel.h" #include "data/model/RegionModel.h" +#include "data/model/BoxModel.h" #include "data/model/TextModel.h" #include "data/model/ImageModel.h" #include "data/model/DenseThreeDimensionalModel.h" @@ -76,6 +78,7 @@ case Notes: return Layer::tr("Notes"); case FlexiNotes: return Layer::tr("Flexible Notes"); case Regions: return Layer::tr("Regions"); + 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"); @@ -173,6 +176,10 @@ types.insert(Regions); } + if (ModelById::getAs<BoxModel>(modelId)) { + types.insert(Boxes); + } + if (ModelById::getAs<TextModel>(modelId)) { types.insert(Text); } @@ -203,6 +210,7 @@ // types.insert(FlexiNotes); types.insert(Notes); types.insert(Regions); + types.insert(Boxes); types.insert(Text); types.insert(Image); //!!! and in principle Colour3DPlot -- now that's a challenge @@ -220,6 +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 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; @@ -240,6 +249,7 @@ case Notes: return "notes"; case FlexiNotes: return "flexinotes"; case Regions: return "regions"; + case Boxes: return "boxes"; case Text: return "text"; case Image: return "image"; case Colour3DPlot: return "colour3d"; @@ -266,6 +276,7 @@ case Notes: return "notes"; case FlexiNotes: return "flexinotes"; case Regions: return "regions"; + case Boxes: return "boxes"; case Text: return "text"; case Image: return "image"; case Colour3DPlot: return "colour3dplot"; @@ -291,6 +302,7 @@ if (name == "notes") return Notes; if (name == "flexinotes") return FlexiNotes; if (name == "regions") return Regions; + if (name == "boxes" || name == "timefrequencybox") return Boxes; if (name == "text") return Text; if (name == "image") return Image; if (name == "colour3dplot") return Colour3DPlot; @@ -329,6 +341,9 @@ if (trySetModel<RegionLayer, RegionModel>(layer, model)) return; + if (trySetModel<BoxLayer, BoxModel>(layer, model)) + return; + if (trySetModel<TextLayer, TextModel>(layer, model)) return; @@ -360,6 +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 == 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) { @@ -437,6 +454,10 @@ layer = new RegionLayer; break; + case Boxes: + layer = new BoxLayer; + break; + case Text: layer = new TextLayer; break;
--- a/layer/LayerFactory.h Tue Sep 17 12:50:58 2019 +0100 +++ b/layer/LayerFactory.h Wed Sep 25 13:48:04 2019 +0100 @@ -38,6 +38,7 @@ Notes, FlexiNotes, Regions, + Boxes, Text, Image, Colour3DPlot,
--- a/layer/NoteLayer.cpp Tue Sep 17 12:50:58 2019 +0100 +++ b/layer/NoteLayer.cpp Wed Sep 25 13:48:04 2019 +0100 @@ -1062,13 +1062,12 @@ Event note(0); if (!getPointToDrag(v, e->x(), e->y(), note)) return false; -// Event note = *points.begin(); - ItemEditDialog *dialog = new ItemEditDialog (model->getSampleRate(), ItemEditDialog::ShowTime | ItemEditDialog::ShowDuration | ItemEditDialog::ShowValue | + ItemEditDialog::ShowLevel | ItemEditDialog::ShowText, getScaleUnits());
--- a/layer/SliceableLayer.h Tue Sep 17 12:50:58 2019 +0100 +++ b/layer/SliceableLayer.h Wed Sep 25 13:48:04 2019 +0100 @@ -35,14 +35,6 @@ // The SliceableLayer retains ownership of the model, and will // emit sliceableModelReplaced if it is about to become invalid. virtual ModelId getSliceableModel() const = 0; -/*!!! -signals: - // Emitted when a model that was obtained through - // getSliceableModel is about to be deleted. If replacement is - // non-NULL, it may be used instead. - void sliceableModelReplaced(const Model *modelToBeReplaced, - const Model *replacement); -*/ }; #endif
--- a/layer/SpectrogramLayer.h Tue Sep 17 12:50:58 2019 +0100 +++ b/layer/SpectrogramLayer.h Wed Sep 25 13:48:04 2019 +0100 @@ -230,7 +230,7 @@ QString getError(LayerGeometryProvider *v) const override; bool getValueExtents(double &min, double &max, - bool &logarithmic, QString &unit) const override; + bool &logarithmic, QString &unit) const override; bool getDisplayExtents(double &min, double &max) const override;
--- a/layer/VerticalBinLayer.h Tue Sep 17 12:50:58 2019 +0100 +++ b/layer/VerticalBinLayer.h Wed Sep 25 13:48:04 2019 +0100 @@ -20,9 +20,10 @@ /** * Interface for layers in which the Y axis corresponds to bin number - * rather than scale value. Colour3DPlotLayer is the obvious example. - * Conceptually these are always SliceableLayers as well, and this - * subclasses from SliceableLayer to avoid a big inheritance mess. + * rather than scale value. Colour3DPlotLayer and SpectrogramLayer are + * obvious examples. Conceptually these are always SliceableLayers as + * well, and this subclasses from SliceableLayer to avoid a big + * inheritance mess. */ class VerticalBinLayer : public SliceableLayer {
--- a/view/Pane.h Tue Sep 17 12:50:58 2019 +0100 +++ b/view/Pane.h Wed Sep 25 13:48:04 2019 +0100 @@ -106,6 +106,10 @@ void mouseEnteredWidget(); void mouseLeftWidget(); + bool getTopLayerDisplayExtents(double &valueMin, double &valueMax, + double &displayMin, double &displayMax, + QString *unit = 0); + protected slots: void playbackScheduleTimerElapsed(); @@ -149,9 +153,6 @@ void updateVerticalPanner(); bool canTopLayerMoveVertical(); - bool getTopLayerDisplayExtents(double &valueMin, double &valueMax, - double &displayMin, double &displayMax, - QString *unit = 0); bool setTopLayerDisplayExtents(double displayMin, double displayMax); void dragTopLayer(QMouseEvent *e);
--- a/view/View.h Tue Sep 17 12:50:58 2019 +0100 +++ b/view/View.h Wed Sep 25 13:48:04 2019 +0100 @@ -354,7 +354,7 @@ int getTextLabelHeight(const Layer *layer, QPainter &) const override; bool getValueExtents(QString unit, double &min, double &max, - bool &log) const override; + bool &log) const override; void toXml(QTextStream &stream, QString indent = "", QString extraAttributes = "") const override;
--- a/widgets/CSVFormatDialog.cpp Tue Sep 17 12:50:58 2019 +0100 +++ b/widgets/CSVFormatDialog.cpp Wed Sep 25 13:48:04 2019 +0100 @@ -318,6 +318,9 @@ case CSVFormat::TwoDimensionalModelWithDurationAndPitch: s = f->getLayerPresentationName(LayerFactory::Notes); break; + case CSVFormat::TwoDimensionalModelWithDurationAndExtent: + s = f->getLayerPresentationName(LayerFactory::Boxes); + break; case CSVFormat::ThreeDimensionalModel: s = f->getLayerPresentationName(LayerFactory::Colour3DPlot); break; @@ -582,6 +585,8 @@ if (haveStartTime && haveDuration) { if (havePitch) { m_format.setModelType(CSVFormat::TwoDimensionalModelWithDurationAndPitch); + } else if (valueCount == 2) { + m_format.setModelType(CSVFormat::TwoDimensionalModelWithDurationAndExtent); } else { m_format.setModelType(CSVFormat::TwoDimensionalModelWithDuration); }
--- a/widgets/ItemEditDialog.cpp Tue Sep 17 12:50:58 2019 +0100 +++ b/widgets/ItemEditDialog.cpp Wed Sep 25 13:48:04 2019 +0100 @@ -28,13 +28,30 @@ #include <float.h> // for FLT_MIN/MAX +ItemEditDialog::LabelOptions::LabelOptions() : + valueLabel(tr("Value")), + levelLabel(tr("Level")) +{ +} + ItemEditDialog::ItemEditDialog(sv_samplerate_t sampleRate, int options, - QString valueUnits, QWidget *parent) : + QString scaleUnits, QWidget *parent) : + ItemEditDialog(sampleRate, options, + [] (QString units) { + ItemEditDialog::LabelOptions labelOptions; + labelOptions.valueUnits = units; + return labelOptions; + }(scaleUnits), + parent) {}; + +ItemEditDialog::ItemEditDialog(sv_samplerate_t sampleRate, int options, + LabelOptions labelOptions, QWidget *parent) : QDialog(parent), m_sampleRate(sampleRate), m_defaultFrame(0), m_defaultDuration(0), m_defaultValue(0), + m_defaultLevel(0), m_frameTimeSpinBox(nullptr), m_realTimeSecsSpinBox(nullptr), m_realTimeUSecsSpinBox(nullptr), @@ -42,6 +59,7 @@ m_realDurationSecsSpinBox(nullptr), m_realDurationUSecsSpinBox(nullptr), m_valueSpinBox(nullptr), + m_levelSpinBox(nullptr), m_textField(nullptr) { QGridLayout *grid = new QGridLayout; @@ -129,7 +147,9 @@ ++subrow; } - if ((options & ShowValue) || (options & ShowText)) { + if ((options & ShowValue) || + (options & ShowLevel) || + (options & ShowText)) { valueBox = new QGroupBox; valueBox->setTitle(tr("Properties")); @@ -145,10 +165,11 @@ if (options & ShowValue) { - subgrid->addWidget(new QLabel(tr("Value:")), subrow, 0); + subgrid->addWidget(new QLabel(tr("%1:").arg(labelOptions.valueLabel)), + subrow, 0); m_valueSpinBox = new QDoubleSpinBox; - m_valueSpinBox->setSuffix(QString(" %1").arg(valueUnits)); + m_valueSpinBox->setSuffix(QString(" %1").arg(labelOptions.valueUnits)); m_valueSpinBox->setDecimals(10); m_valueSpinBox->setMinimum(-1e10); m_valueSpinBox->setMaximum(1e10); @@ -159,6 +180,23 @@ ++subrow; } + if (options & ShowLevel) { + + subgrid->addWidget(new QLabel(tr("%1:").arg(labelOptions.levelLabel)), + subrow, 0); + + m_levelSpinBox = new QDoubleSpinBox; + m_levelSpinBox->setSuffix(QString(" %1").arg(labelOptions.levelUnits)); + m_levelSpinBox->setDecimals(10); + m_levelSpinBox->setMinimum(-1e10); + m_levelSpinBox->setMaximum(1e10); + connect(m_levelSpinBox, SIGNAL(levelChanged(double)), + this, SLOT(levelChanged(double))); + subgrid->addWidget(m_levelSpinBox, subrow, 1); + + ++subrow; + } + if (options & ShowText) { subgrid->addWidget(new QLabel(tr("Text:")), subrow, 0); @@ -175,6 +213,8 @@ m_textField->setFocus(Qt::OtherFocusReason); } else if (options & ShowValue) { m_valueSpinBox->setFocus(Qt::OtherFocusReason); + } else if (options & ShowLevel) { + m_levelSpinBox->setFocus(Qt::OtherFocusReason); } QDialogButtonBox *bb = new QDialogButtonBox(Qt::Horizontal); @@ -271,6 +311,22 @@ } void +ItemEditDialog::setLevel(float v) +{ + if (!m_levelSpinBox) return; + + m_levelSpinBox->setValue(v); + m_defaultLevel = v; + m_resetButton->setEnabled(false); +} + +float +ItemEditDialog::getLevel() const +{ + return float(m_levelSpinBox->value()); +} + +void ItemEditDialog::setText(QString text) { if (!m_textField) return; @@ -363,6 +419,12 @@ } void +ItemEditDialog::levelChanged(double) +{ + m_resetButton->setEnabled(true); +} + +void ItemEditDialog::textChanged(QString) { m_resetButton->setEnabled(true); @@ -374,6 +436,7 @@ setFrameTime(m_defaultFrame); setFrameDuration(m_defaultDuration); setValue(m_defaultValue); + setLevel(m_defaultLevel); setText(m_defaultText); m_resetButton->setEnabled(false); }
--- a/widgets/ItemEditDialog.h Tue Sep 17 12:50:58 2019 +0100 +++ b/widgets/ItemEditDialog.h Wed Sep 25 13:48:04 2019 +0100 @@ -34,10 +34,26 @@ ShowTime = 1 << 0, ShowDuration = 1 << 1, ShowValue = 1 << 2, - ShowText = 1 << 3 + ShowText = 1 << 3, + ShowLevel = 1 << 4 }; - ItemEditDialog(sv_samplerate_t sampleRate, int options, QString valueUnits = "", + struct LabelOptions { + LabelOptions(); + QString valueLabel; + QString levelLabel; + QString valueUnits; + QString levelUnits; + }; + + ItemEditDialog(sv_samplerate_t sampleRate, + int options, + LabelOptions labelOptions = {}, + QWidget *parent = 0); + + ItemEditDialog(sv_samplerate_t sampleRate, + int options, + QString scaleUnits, QWidget *parent = 0); void setFrameTime(sv_frame_t frame); @@ -55,6 +71,9 @@ void setValue(float value); float getValue() const; + void setLevel(float level); + float getLevel() const; + void setText(QString text); QString getText() const; @@ -66,6 +85,7 @@ void realDurationSecsChanged(int); void realDurationUSecsChanged(int); void valueChanged(double); + void levelChanged(double); void textChanged(QString); void reset(); @@ -74,6 +94,7 @@ sv_frame_t m_defaultFrame; sv_frame_t m_defaultDuration; float m_defaultValue; + float m_defaultLevel; QString m_defaultText; QSpinBox *m_frameTimeSpinBox; QSpinBox *m_realTimeSecsSpinBox; @@ -82,6 +103,7 @@ QSpinBox *m_realDurationSecsSpinBox; QSpinBox *m_realDurationUSecsSpinBox; QDoubleSpinBox *m_valueSpinBox; + QDoubleSpinBox *m_levelSpinBox; QLineEdit *m_textField; QPushButton *m_resetButton; };