Chris@1511: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@1511: Chris@1511: /* Chris@1511: Sonic Visualiser Chris@1511: An audio file viewer and annotation editor. Chris@1511: Centre for Digital Music, Queen Mary, University of London. Chris@1511: Chris@1511: This program is free software; you can redistribute it and/or Chris@1511: modify it under the terms of the GNU General Public License as Chris@1511: published by the Free Software Foundation; either version 2 of the Chris@1511: License, or (at your option) any later version. See the file Chris@1511: COPYING included with this distribution for more information. Chris@1511: */ Chris@1511: Chris@1518: #include "BoxLayer.h" Chris@1511: Chris@1511: #include "data/model/Model.h" Chris@1511: #include "base/RealTime.h" Chris@1511: #include "base/Profiler.h" Chris@1511: #include "base/LogRange.h" Chris@1511: Chris@1511: #include "ColourDatabase.h" Chris@1511: #include "ColourMapper.h" Chris@1511: #include "LinearNumericalScale.h" Chris@1511: #include "LogNumericalScale.h" Chris@1511: #include "PaintAssistant.h" Chris@1511: Chris@1511: #include "view/View.h" Chris@1511: Chris@1518: #include "data/model/BoxModel.h" Chris@1511: Chris@1511: #include "widgets/ItemEditDialog.h" Chris@1511: #include "widgets/TextAbbrev.h" Chris@1511: Chris@1511: #include Chris@1511: #include Chris@1511: #include Chris@1511: #include Chris@1511: #include Chris@1511: Chris@1511: #include Chris@1511: #include Chris@1511: Chris@1518: BoxLayer::BoxLayer() : Chris@1511: SingleColourLayer(), Chris@1511: m_editing(false), Chris@1511: m_dragPointX(0), Chris@1511: m_dragPointY(0), Chris@1511: m_dragStartX(0), Chris@1511: m_dragStartY(0), Chris@1511: m_originalPoint(0, 0.0, 0, tr("New Box")), Chris@1511: m_editingPoint(0, 0.0, 0, tr("New Box")), Chris@1518: m_editingCommand(nullptr), Chris@1518: m_verticalScale(AutoAlignScale) Chris@1511: { Chris@1511: Chris@1511: } Chris@1511: Chris@1511: int Chris@1518: BoxLayer::getCompletion(LayerGeometryProvider *) const Chris@1511: { Chris@1511: auto model = ModelById::get(m_model); Chris@1511: if (model) return model->getCompletion(); Chris@1511: else return 0; Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::setModel(ModelId modelId) Chris@1511: { Chris@1518: auto oldModel = ModelById::getAs(m_model); Chris@1518: auto newModel = ModelById::getAs(modelId); Chris@1511: Chris@1511: if (!modelId.isNone() && !newModel) { Chris@1518: throw std::logic_error("Not a BoxModel"); Chris@1511: } Chris@1511: Chris@1511: if (m_model == modelId) return; Chris@1511: m_model = modelId; Chris@1511: Chris@1511: if (newModel) { Chris@1511: connectSignals(m_model); Chris@1511: } Chris@1511: Chris@1511: emit modelReplaced(); Chris@1511: } Chris@1511: Chris@1511: Layer::PropertyList Chris@1518: BoxLayer::getProperties() const Chris@1511: { Chris@1511: PropertyList list = SingleColourLayer::getProperties(); Chris@1511: list.push_back("Vertical Scale"); Chris@1518: list.push_back("Scale Units"); Chris@1511: return list; Chris@1511: } Chris@1511: Chris@1511: QString Chris@1518: BoxLayer::getPropertyLabel(const PropertyName &name) const Chris@1511: { Chris@1511: if (name == "Vertical Scale") return tr("Vertical Scale"); Chris@1518: if (name == "Scale Units") return tr("Scale Units"); Chris@1511: return SingleColourLayer::getPropertyLabel(name); Chris@1511: } Chris@1511: Chris@1511: Layer::PropertyType Chris@1518: BoxLayer::getPropertyType(const PropertyName &name) const Chris@1511: { Chris@1511: if (name == "Vertical Scale") return ValueProperty; Chris@1518: if (name == "Scale Units") return UnitsProperty; Chris@1511: return SingleColourLayer::getPropertyType(name); Chris@1511: } Chris@1511: Chris@1511: QString Chris@1518: BoxLayer::getPropertyGroupName(const PropertyName &name) const Chris@1511: { Chris@1518: if (name == "Vertical Scale" || name == "Scale Units") { Chris@1511: return tr("Scale"); Chris@1511: } Chris@1511: return SingleColourLayer::getPropertyGroupName(name); Chris@1511: } Chris@1511: Chris@1511: int Chris@1518: BoxLayer::getPropertyRangeAndValue(const PropertyName &name, Chris@1511: int *min, int *max, int *deflt) const Chris@1511: { Chris@1511: int val = 0; Chris@1511: Chris@1511: if (name == "Vertical Scale") { Chris@1511: Chris@1511: if (min) *min = 0; Chris@1511: if (max) *max = 2; Chris@1511: if (deflt) *deflt = int(LinearScale); Chris@1511: Chris@1511: val = int(m_verticalScale); Chris@1511: Chris@1518: } else if (name == "Scale Units") { Chris@1518: Chris@1518: if (deflt) *deflt = 0; Chris@1518: auto model = ModelById::getAs(m_model); Chris@1518: if (model) { Chris@1518: val = UnitDatabase::getInstance()->getUnitId Chris@1518: (model->getScaleUnits()); Chris@1518: } Chris@1518: Chris@1511: } else { Chris@1511: val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt); Chris@1511: } Chris@1511: Chris@1511: return val; Chris@1511: } Chris@1511: Chris@1511: QString Chris@1518: BoxLayer::getPropertyValueLabel(const PropertyName &name, Chris@1511: int value) const Chris@1511: { Chris@1511: if (name == "Vertical Scale") { Chris@1511: switch (value) { Chris@1511: default: Chris@1511: case 0: return tr("Auto-Align"); Chris@1511: case 1: return tr("Linear"); Chris@1511: case 2: return tr("Log"); Chris@1511: } Chris@1511: } Chris@1511: return SingleColourLayer::getPropertyValueLabel(name, value); Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::setProperty(const PropertyName &name, int value) Chris@1511: { Chris@1511: if (name == "Vertical Scale") { Chris@1511: setVerticalScale(VerticalScale(value)); Chris@1518: } else if (name == "Scale Units") { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1518: if (model) { Chris@1518: model->setScaleUnits Chris@1518: (UnitDatabase::getInstance()->getUnitById(value)); Chris@1518: emit modelChanged(m_model); Chris@1518: } Chris@1511: } else { Chris@1511: return SingleColourLayer::setProperty(name, value); Chris@1511: } Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::setVerticalScale(VerticalScale scale) Chris@1511: { Chris@1511: if (m_verticalScale == scale) return; Chris@1511: m_verticalScale = scale; Chris@1511: emit layerParametersChanged(); Chris@1511: } Chris@1511: Chris@1511: bool Chris@1518: BoxLayer::isLayerScrollable(const LayerGeometryProvider *v) const Chris@1511: { Chris@1511: QPoint discard; Chris@1511: return !v->shouldIlluminateLocalFeatures(this, discard); Chris@1511: } Chris@1511: Chris@1511: bool Chris@1518: BoxLayer::getValueExtents(double &min, double &max, Chris@1518: bool &logarithmic, QString &unit) const Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model) return false; Chris@1518: min = model->getValueMinimum(); Chris@1518: max = model->getValueMaximum(); Chris@1511: unit = getScaleUnits(); Chris@1511: Chris@1511: if (m_verticalScale == LogScale) logarithmic = true; Chris@1511: Chris@1511: return true; Chris@1511: } Chris@1511: Chris@1511: bool Chris@1518: BoxLayer::getDisplayExtents(double &min, double &max) const Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model || m_verticalScale == AutoAlignScale) return false; Chris@1511: Chris@1518: min = model->getValueMinimum(); Chris@1518: max = model->getValueMaximum(); Chris@1511: Chris@1511: return true; Chris@1511: } Chris@1511: Chris@1518: bool Chris@1518: BoxLayer::adoptExtents(double min, double max, QString unit) Chris@1518: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1518: if (!model) return false; Chris@1518: if (model->getScaleUnits() == "") { Chris@1518: model->setScaleUnits(unit); Chris@1518: return true; Chris@1518: } else { Chris@1518: return false; Chris@1518: } Chris@1518: } Chris@1518: Chris@1511: EventVector Chris@1518: BoxLayer::getLocalPoints(LayerGeometryProvider *v, int x) const Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model) return EventVector(); Chris@1511: Chris@1511: sv_frame_t frame = v->getFrameForX(x); Chris@1511: Chris@1511: EventVector local = model->getEventsCovering(frame); Chris@1511: if (!local.empty()) return local; Chris@1511: Chris@1511: int fuzz = ViewManager::scalePixelSize(2); Chris@1511: sv_frame_t start = v->getFrameForX(x - fuzz); Chris@1511: sv_frame_t end = v->getFrameForX(x + fuzz); Chris@1511: Chris@1511: local = model->getEventsStartingWithin(frame, end - frame); Chris@1511: if (!local.empty()) return local; Chris@1511: Chris@1511: local = model->getEventsSpanning(start, frame - start); Chris@1511: if (!local.empty()) return local; Chris@1511: Chris@1511: return {}; Chris@1511: } Chris@1511: Chris@1511: bool Chris@1518: BoxLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, Chris@1511: Event &point) const Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model) return false; Chris@1511: Chris@1511: sv_frame_t frame = v->getFrameForX(x); Chris@1511: Chris@1511: EventVector onPoints = model->getEventsCovering(frame); Chris@1511: if (onPoints.empty()) return false; Chris@1511: Chris@1511: int nearestDistance = -1; Chris@1511: for (const auto &p: onPoints) { Chris@1511: int distance = getYForValue(v, p.getValue()) - y; Chris@1511: if (distance < 0) distance = -distance; Chris@1511: if (nearestDistance == -1 || distance < nearestDistance) { Chris@1511: nearestDistance = distance; Chris@1511: point = p; Chris@1511: } Chris@1511: } Chris@1511: Chris@1511: return true; Chris@1511: } Chris@1511: Chris@1511: QString Chris@1518: BoxLayer::getLabelPreceding(sv_frame_t frame) const Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model) return ""; Chris@1511: EventVector points = model->getEventsStartingWithin Chris@1511: (model->getStartFrame(), frame - model->getStartFrame()); Chris@1511: if (!points.empty()) { Chris@1511: for (auto i = points.rbegin(); i != points.rend(); ++i) { Chris@1511: if (i->getLabel() != QString()) { Chris@1511: return i->getLabel(); Chris@1511: } Chris@1511: } Chris@1511: } Chris@1511: return QString(); Chris@1511: } Chris@1511: Chris@1511: QString Chris@1518: BoxLayer::getFeatureDescription(LayerGeometryProvider *v, Chris@1511: QPoint &pos) const Chris@1511: { Chris@1511: int x = pos.x(); Chris@1511: Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model || !model->getSampleRate()) return ""; Chris@1511: Chris@1511: EventVector points = getLocalPoints(v, x); Chris@1511: Chris@1511: if (points.empty()) { Chris@1511: if (!model->isReady()) { Chris@1511: return tr("In progress"); Chris@1511: } else { Chris@1511: return tr("No local points"); Chris@1511: } Chris@1511: } Chris@1511: Chris@1511: Event box; Chris@1511: EventVector::iterator i; Chris@1511: Chris@1511: for (i = points.begin(); i != points.end(); ++i) { Chris@1511: Chris@1511: int y0 = getYForValue(v, i->getValue()); Chris@1511: int y1 = getYForValue(v, i->getValue() + fabsf(i->getLevel())); Chris@1511: Chris@1511: if (pos.y() >= y0 && pos.y() <= y1) { Chris@1511: box = *i; Chris@1511: break; Chris@1511: } Chris@1511: } Chris@1511: Chris@1511: if (i == points.end()) return tr("No local points"); Chris@1511: Chris@1511: RealTime rt = RealTime::frame2RealTime(box.getFrame(), Chris@1511: model->getSampleRate()); Chris@1511: RealTime rd = RealTime::frame2RealTime(box.getDuration(), Chris@1511: model->getSampleRate()); Chris@1511: Chris@1511: QString rangeText; Chris@1511: Chris@1511: rangeText = tr("%1 %2 - %3 %4") Chris@1511: .arg(box.getValue()).arg(getScaleUnits()) Chris@1511: .arg(box.getValue() + fabsf(box.getLevel())).arg(getScaleUnits()); Chris@1511: Chris@1511: QString text; Chris@1511: Chris@1511: if (box.getLabel() == "") { Chris@1518: text = QString(tr("Time:\t%1\nDuration:\t%2\nValue:\t%3\nNo label")) Chris@1511: .arg(rt.toText(true).c_str()) Chris@1511: .arg(rd.toText(true).c_str()) Chris@1511: .arg(rangeText); Chris@1511: } else { Chris@1518: text = QString(tr("Time:\t%1\nDuration:\t%2\nValue:\t%3\nLabel:\t%4")) Chris@1511: .arg(rt.toText(true).c_str()) Chris@1511: .arg(rd.toText(true).c_str()) Chris@1511: .arg(rangeText) Chris@1511: .arg(box.getLabel()); Chris@1511: } Chris@1511: Chris@1511: pos = QPoint(v->getXForFrame(box.getFrame()), Chris@1511: getYForValue(v, box.getValue())); Chris@1511: return text; Chris@1511: } Chris@1511: Chris@1511: bool Chris@1518: BoxLayer::snapToFeatureFrame(LayerGeometryProvider *v, Chris@1511: sv_frame_t &frame, Chris@1511: int &resolution, Chris@1511: SnapType snap) const Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model) { Chris@1511: return Layer::snapToFeatureFrame(v, frame, resolution, snap); Chris@1511: } Chris@1511: Chris@1511: // SnapLeft / SnapRight: return frame of nearest feature in that Chris@1511: // direction no matter how far away Chris@1511: // Chris@1511: // SnapNeighbouring: return frame of feature that would be used in Chris@1511: // an editing operation, i.e. closest feature in either direction Chris@1511: // but only if it is "close enough" Chris@1511: Chris@1511: resolution = model->getResolution(); Chris@1511: Chris@1511: if (snap == SnapNeighbouring) { Chris@1511: EventVector points = getLocalPoints(v, v->getXForFrame(frame)); Chris@1511: if (points.empty()) return false; Chris@1511: frame = points.begin()->getFrame(); Chris@1511: return true; Chris@1511: } Chris@1511: Chris@1511: // Normally we snap to the start frame of whichever event we Chris@1511: // find. However here, for SnapRight only, if the end frame of Chris@1511: // whichever event we would have snapped to had we been snapping Chris@1511: // left is closer than the start frame of the next event to the Chris@1511: // right, then we snap to that frame instead. Clear? Chris@1511: Chris@1511: Event left; Chris@1511: bool haveLeft = false; Chris@1511: if (model->getNearestEventMatching Chris@1511: (frame, [](Event) { return true; }, EventSeries::Backward, left)) { Chris@1511: haveLeft = true; Chris@1511: } Chris@1511: Chris@1511: if (snap == SnapLeft) { Chris@1511: frame = left.getFrame(); Chris@1511: return haveLeft; Chris@1511: } Chris@1511: Chris@1511: Event right; Chris@1511: bool haveRight = false; Chris@1511: if (model->getNearestEventMatching Chris@1511: (frame, [](Event) { return true; }, EventSeries::Forward, right)) { Chris@1511: haveRight = true; Chris@1511: } Chris@1511: Chris@1511: if (haveLeft) { Chris@1511: sv_frame_t leftEnd = left.getFrame() + left.getDuration(); Chris@1511: if (leftEnd > frame) { Chris@1511: if (haveRight) { Chris@1511: if (leftEnd - frame < right.getFrame() - frame) { Chris@1511: frame = leftEnd; Chris@1511: } else { Chris@1511: frame = right.getFrame(); Chris@1511: } Chris@1511: } else { Chris@1511: frame = leftEnd; Chris@1511: } Chris@1511: return true; Chris@1511: } Chris@1511: } Chris@1511: Chris@1511: if (haveRight) { Chris@1511: frame = right.getFrame(); Chris@1511: return true; Chris@1511: } Chris@1511: Chris@1511: return false; Chris@1511: } Chris@1511: Chris@1511: QString Chris@1518: BoxLayer::getScaleUnits() const Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (model) return model->getScaleUnits(); Chris@1511: else return ""; Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::getScaleExtents(LayerGeometryProvider *v, Chris@1511: double &min, double &max, Chris@1511: bool &log) const Chris@1511: { Chris@1511: min = 0.0; Chris@1511: max = 0.0; Chris@1511: log = false; Chris@1511: Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model) return; Chris@1511: Chris@1511: QString queryUnits; Chris@1511: queryUnits = getScaleUnits(); Chris@1511: Chris@1511: if (m_verticalScale == AutoAlignScale) { Chris@1511: Chris@1511: if (!v->getValueExtents(queryUnits, min, max, log)) { Chris@1511: Chris@1518: min = model->getValueMinimum(); Chris@1518: max = model->getValueMaximum(); Chris@1511: Chris@1518: // cerr << "BoxLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; Chris@1511: Chris@1511: } else if (log) { Chris@1511: Chris@1511: LogRange::mapRange(min, max); Chris@1511: Chris@1518: // cerr << "BoxLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; Chris@1511: Chris@1511: } Chris@1511: Chris@1511: } else { Chris@1511: Chris@1518: min = model->getValueMinimum(); Chris@1518: max = model->getValueMaximum(); Chris@1511: Chris@1511: if (m_verticalScale == LogScale) { Chris@1511: LogRange::mapRange(min, max); Chris@1511: log = true; Chris@1511: } Chris@1511: } Chris@1511: Chris@1511: if (max == min) max = min + 1.0; Chris@1511: } Chris@1511: Chris@1511: int Chris@1518: BoxLayer::getYForValue(LayerGeometryProvider *v, double val) const Chris@1511: { Chris@1511: double min = 0.0, max = 0.0; Chris@1511: bool logarithmic = false; Chris@1511: int h = v->getPaintHeight(); Chris@1511: Chris@1511: getScaleExtents(v, min, max, logarithmic); Chris@1511: Chris@1518: // cerr << "BoxLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl; Chris@1511: // cerr << "h = " << h << ", margin = " << margin << endl; Chris@1511: Chris@1511: if (logarithmic) { Chris@1511: val = LogRange::map(val); Chris@1511: } Chris@1511: Chris@1511: return int(h - ((val - min) * h) / (max - min)); Chris@1511: } Chris@1511: Chris@1511: double Chris@1518: BoxLayer::getValueForY(LayerGeometryProvider *v, int y) const Chris@1511: { Chris@1511: double min = 0.0, max = 0.0; Chris@1511: bool logarithmic = false; Chris@1511: int h = v->getPaintHeight(); Chris@1511: Chris@1511: getScaleExtents(v, min, max, logarithmic); Chris@1511: Chris@1511: double val = min + (double(h - y) * double(max - min)) / h; Chris@1511: Chris@1511: if (logarithmic) { Chris@1511: val = pow(10.0, val); Chris@1511: } Chris@1511: Chris@1511: return val; Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::paint(LayerGeometryProvider *v, QPainter &paint, Chris@1511: QRect rect) const Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model || !model->isOK()) return; Chris@1511: Chris@1511: sv_samplerate_t sampleRate = model->getSampleRate(); Chris@1511: if (!sampleRate) return; Chris@1511: Chris@1518: // Profiler profiler("BoxLayer::paint", true); Chris@1511: Chris@1511: int x0 = rect.left() - 40, x1 = rect.right(); Chris@1511: Chris@1511: sv_frame_t wholeFrame0 = v->getFrameForX(0); Chris@1511: sv_frame_t wholeFrame1 = v->getFrameForX(v->getPaintWidth()); Chris@1511: Chris@1511: EventVector points(model->getEventsSpanning(wholeFrame0, Chris@1511: wholeFrame1 - wholeFrame0)); Chris@1511: if (points.empty()) return; Chris@1511: Chris@1511: paint.setPen(getBaseQColor()); Chris@1511: Chris@1518: // SVDEBUG << "BoxLayer::paint: resolution is " Chris@1511: // << model->getResolution() << " frames" << endl; Chris@1511: Chris@1518: double min = model->getValueMinimum(); Chris@1518: double max = model->getValueMaximum(); Chris@1511: if (max == min) max = min + 1.0; Chris@1511: Chris@1511: QPoint localPos; Chris@1511: Event illuminatePoint(0); Chris@1511: bool shouldIlluminate = false; Chris@1511: Chris@1511: if (v->shouldIlluminateLocalFeatures(this, localPos)) { Chris@1511: shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(), Chris@1511: illuminatePoint); Chris@1511: } Chris@1511: Chris@1511: paint.save(); Chris@1511: paint.setRenderHint(QPainter::Antialiasing, false); Chris@1511: Chris@1514: QFontMetrics fm = paint.fontMetrics(); Chris@1514: Chris@1511: for (EventVector::const_iterator i = points.begin(); Chris@1511: i != points.end(); ++i) { Chris@1511: Chris@1511: const Event &p(*i); Chris@1511: Chris@1511: int x = v->getXForFrame(p.getFrame()); Chris@1511: int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x; Chris@1511: int y = getYForValue(v, p.getValue()); Chris@1514: int h = getYForValue(v, p.getValue() + fabsf(p.getLevel())) - y; Chris@1511: int ex = x + w; Chris@1514: int gap = v->scalePixelSize(2); Chris@1511: Chris@1511: EventVector::const_iterator j = i; Chris@1511: ++j; Chris@1511: Chris@1511: if (j != points.end()) { Chris@1511: const Event &q(*j); Chris@1511: int nx = v->getXForFrame(q.getFrame()); Chris@1511: if (nx < ex) ex = nx; Chris@1511: } Chris@1511: Chris@1511: if (w < 1) w = 1; Chris@1511: Chris@1511: paint.setPen(getBaseQColor()); Chris@1514: paint.setBrush(Qt::NoBrush); Chris@1511: Chris@1514: if ((shouldIlluminate && illuminatePoint == p) || Chris@1514: (m_editing && m_editingPoint == p)) { Chris@1511: Chris@1514: paint.setPen(QPen(getBaseQColor(), v->scalePixelSize(2))); Chris@1511: Chris@1511: // Qt 5.13 deprecates QFontMetrics::width(), but its suggested Chris@1511: // replacement (horizontalAdvance) was only added in Qt 5.11 Chris@1511: // which is too new for us Chris@1511: #pragma GCC diagnostic ignored "-Wdeprecated-declarations" Chris@1511: Chris@1514: if (abs(h) > 2 * fm.height()) { Chris@1514: Chris@1514: QString y0label = QString("%1 %2") Chris@1514: .arg(p.getValue()) Chris@1514: .arg(getScaleUnits()); Chris@1514: Chris@1514: QString y1label = QString("%1 %2") Chris@1514: .arg(p.getValue() + fabsf(p.getLevel())) Chris@1514: .arg(getScaleUnits()); Chris@1514: Chris@1514: PaintAssistant::drawVisibleText Chris@1514: (v, paint, Chris@1514: x - fm.width(y0label) - gap, Chris@1514: y - fm.descent(), Chris@1514: y0label, PaintAssistant::OutlinedText); Chris@1514: Chris@1514: PaintAssistant::drawVisibleText Chris@1514: (v, paint, Chris@1514: x - fm.width(y1label) - gap, Chris@1514: y + h + fm.ascent(), Chris@1514: y1label, PaintAssistant::OutlinedText); Chris@1514: Chris@1514: } else { Chris@1514: Chris@1514: QString ylabel = QString("%1 %2 - %3 %4") Chris@1514: .arg(p.getValue()) Chris@1514: .arg(getScaleUnits()) Chris@1514: .arg(p.getValue() + fabsf(p.getLevel())) Chris@1514: .arg(getScaleUnits()); Chris@1514: Chris@1514: PaintAssistant::drawVisibleText Chris@1514: (v, paint, Chris@1514: x - fm.width(ylabel) - gap, Chris@1514: y - fm.descent(), Chris@1514: ylabel, PaintAssistant::OutlinedText); Chris@1514: } Chris@1514: Chris@1514: QString t0label = RealTime::frame2RealTime Chris@1514: (p.getFrame(), model->getSampleRate()).toText(true).c_str(); Chris@1514: Chris@1514: QString t1label = RealTime::frame2RealTime Chris@1514: (p.getFrame() + p.getDuration(), model->getSampleRate()) Chris@1514: .toText(true).c_str(); Chris@1514: Chris@1511: PaintAssistant::drawVisibleText Chris@1514: (v, paint, x, y + fm.ascent() + gap, Chris@1514: t0label, PaintAssistant::OutlinedText); Chris@1514: Chris@1514: if (w > fm.width(t0label) + fm.width(t1label) + gap * 3) { Chris@1514: Chris@1514: PaintAssistant::drawVisibleText Chris@1514: (v, paint, Chris@1514: x + w - fm.width(t1label), Chris@1514: y + fm.ascent() + gap, Chris@1514: t1label, PaintAssistant::OutlinedText); Chris@1514: Chris@1514: } else { Chris@1514: Chris@1514: PaintAssistant::drawVisibleText Chris@1514: (v, paint, Chris@1514: x + w - fm.width(t1label), Chris@1514: y + fm.ascent() + fm.height() + gap, Chris@1514: t1label, PaintAssistant::OutlinedText); Chris@1514: } Chris@1511: } Chris@1511: Chris@1511: paint.drawRect(x, y, w, h); Chris@1511: } Chris@1511: Chris@1511: for (EventVector::const_iterator i = points.begin(); Chris@1511: i != points.end(); ++i) { Chris@1511: Chris@1511: const Event &p(*i); Chris@1511: Chris@1514: QString label = p.getLabel(); Chris@1514: if (label == "") continue; Chris@1514: Chris@1514: if (shouldIlluminate && illuminatePoint == p) { Chris@1514: continue; // already handled this in illumination special case Chris@1514: } Chris@1514: Chris@1511: int x = v->getXForFrame(p.getFrame()); Chris@1511: int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x; Chris@1511: int y = getYForValue(v, p.getValue()); Chris@1511: Chris@1514: int labelWidth = fm.width(label); Chris@1511: Chris@1511: int gap = v->scalePixelSize(2); Chris@1511: Chris@1511: if (x + w < x0 || x - labelWidth - gap > x1) { Chris@1511: continue; Chris@1511: } Chris@1511: Chris@1514: int labelX, labelY; Chris@1511: Chris@1514: labelX = x - labelWidth - gap; Chris@1514: labelY = y - fm.descent(); Chris@1511: Chris@1514: PaintAssistant::drawVisibleText(v, paint, labelX, labelY, label, Chris@1514: PaintAssistant::OutlinedText); Chris@1511: } Chris@1511: Chris@1511: paint.restore(); Chris@1511: } Chris@1511: Chris@1511: int Chris@1518: BoxLayer::getVerticalScaleWidth(LayerGeometryProvider *v, Chris@1511: bool, QPainter &paint) const Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model || m_verticalScale == AutoAlignScale) { Chris@1511: return 0; Chris@1511: } else { Chris@1511: if (m_verticalScale == LogScale) { Chris@1511: return LogNumericalScale().getWidth(v, paint); Chris@1511: } else { Chris@1511: return LinearNumericalScale().getWidth(v, paint); Chris@1511: } Chris@1511: } Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::paintVerticalScale(LayerGeometryProvider *v, Chris@1511: bool, QPainter &paint, QRect) const Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model || model->isEmpty()) return; Chris@1511: Chris@1511: QString unit; Chris@1511: double min, max; Chris@1511: bool logarithmic; Chris@1511: Chris@1511: int w = getVerticalScaleWidth(v, false, paint); Chris@1511: Chris@1511: getScaleExtents(v, min, max, logarithmic); Chris@1511: Chris@1511: if (logarithmic) { Chris@1511: LogNumericalScale().paintVertical(v, this, paint, 0, min, max); Chris@1511: } else { Chris@1511: LinearNumericalScale().paintVertical(v, this, paint, 0, min, max); Chris@1511: } Chris@1511: Chris@1511: if (getScaleUnits() != "") { Chris@1511: int mw = w - 5; Chris@1511: paint.drawText(5, Chris@1511: 5 + paint.fontMetrics().ascent(), Chris@1511: TextAbbrev::abbreviate(getScaleUnits(), Chris@1511: paint.fontMetrics(), Chris@1511: mw)); Chris@1511: } Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e) Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model) return; Chris@1511: Chris@1511: sv_frame_t frame = v->getFrameForX(e->x()); Chris@1511: if (frame < 0) frame = 0; Chris@1511: frame = frame / model->getResolution() * model->getResolution(); Chris@1511: Chris@1518: double value = getValueForY(v, e->y()); Chris@1511: Chris@1518: m_editingPoint = Event(frame, float(value), 0, ""); Chris@1511: m_originalPoint = m_editingPoint; Chris@1511: Chris@1511: if (m_editingCommand) finish(m_editingCommand); Chris@1511: m_editingCommand = new ChangeEventsCommand(m_model.untyped, Chris@1518: tr("Draw Box")); Chris@1511: m_editingCommand->add(m_editingPoint); Chris@1511: Chris@1511: m_editing = true; Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e) Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model || !m_editing) return; Chris@1511: Chris@1514: sv_frame_t dragFrame = v->getFrameForX(e->x()); Chris@1514: if (dragFrame < 0) dragFrame = 0; Chris@1514: dragFrame = dragFrame / model->getResolution() * model->getResolution(); Chris@1511: Chris@1514: sv_frame_t eventFrame = m_originalPoint.getFrame(); Chris@1514: sv_frame_t eventDuration = dragFrame - eventFrame; Chris@1514: if (eventDuration < 0) { Chris@1514: eventFrame = eventFrame + eventDuration; Chris@1514: eventDuration = -eventDuration; Chris@1514: } else if (eventDuration == 0) { Chris@1514: eventDuration = model->getResolution(); Chris@1514: } Chris@1511: Chris@1518: double dragValue = getValueForY(v, e->y()); Chris@1514: Chris@1518: double eventValue = m_originalPoint.getValue(); Chris@1518: double eventFreqDiff = dragValue - eventValue; Chris@1514: if (eventFreqDiff < 0) { Chris@1518: eventValue = eventValue + eventFreqDiff; Chris@1514: eventFreqDiff = -eventFreqDiff; Chris@1511: } Chris@1511: Chris@1511: m_editingCommand->remove(m_editingPoint); Chris@1511: m_editingPoint = m_editingPoint Chris@1514: .withFrame(eventFrame) Chris@1514: .withDuration(eventDuration) Chris@1518: .withValue(float(eventValue)) Chris@1514: .withLevel(float(eventFreqDiff)); Chris@1511: m_editingCommand->add(m_editingPoint); Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *) Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model || !m_editing) return; Chris@1511: finish(m_editingCommand); Chris@1511: m_editingCommand = nullptr; Chris@1511: m_editing = false; Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e) Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model) return; Chris@1511: Chris@1511: if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return; Chris@1511: Chris@1511: if (m_editingCommand) { Chris@1511: finish(m_editingCommand); Chris@1511: m_editingCommand = nullptr; Chris@1511: } Chris@1511: Chris@1511: m_editing = true; Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *) Chris@1511: { Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e) Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model || !m_editing) return; Chris@1511: Chris@1511: m_editing = false; Chris@1511: Chris@1511: Event p(0); Chris@1511: if (!getPointToDrag(v, e->x(), e->y(), p)) return; Chris@1511: if (p.getFrame() != m_editingPoint.getFrame() || Chris@1511: p.getValue() != m_editingPoint.getValue()) return; Chris@1511: Chris@1511: m_editingCommand = new ChangeEventsCommand Chris@1518: (m_model.untyped, tr("Erase Box")); Chris@1511: Chris@1511: m_editingCommand->remove(m_editingPoint); Chris@1511: Chris@1511: finish(m_editingCommand); Chris@1511: m_editingCommand = nullptr; Chris@1511: m_editing = false; Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e) Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model) return; Chris@1511: Chris@1511: if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) { Chris@1511: return; Chris@1511: } Chris@1511: Chris@1511: m_dragPointX = v->getXForFrame(m_editingPoint.getFrame()); Chris@1511: m_dragPointY = getYForValue(v, m_editingPoint.getValue()); Chris@1511: Chris@1511: m_originalPoint = m_editingPoint; Chris@1511: Chris@1511: if (m_editingCommand) { Chris@1511: finish(m_editingCommand); Chris@1511: m_editingCommand = nullptr; Chris@1511: } Chris@1511: Chris@1511: m_editing = true; Chris@1511: m_dragStartX = e->x(); Chris@1511: m_dragStartY = e->y(); Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e) Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model || !m_editing) return; Chris@1511: Chris@1511: int xdist = e->x() - m_dragStartX; Chris@1511: int ydist = e->y() - m_dragStartY; Chris@1511: int newx = m_dragPointX + xdist; Chris@1511: int newy = m_dragPointY + ydist; Chris@1511: Chris@1511: sv_frame_t frame = v->getFrameForX(newx); Chris@1511: if (frame < 0) frame = 0; Chris@1511: frame = frame / model->getResolution() * model->getResolution(); Chris@1511: Chris@1511: double value = getValueForY(v, newy); Chris@1511: Chris@1511: if (!m_editingCommand) { Chris@1511: m_editingCommand = new ChangeEventsCommand Chris@1511: (m_model.untyped, Chris@1518: tr("Drag Box")); Chris@1511: } Chris@1511: Chris@1511: m_editingCommand->remove(m_editingPoint); Chris@1511: m_editingPoint = m_editingPoint Chris@1511: .withFrame(frame) Chris@1511: .withValue(float(value)); Chris@1511: m_editingCommand->add(m_editingPoint); Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::editEnd(LayerGeometryProvider *, QMouseEvent *) Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model || !m_editing) return; Chris@1511: Chris@1511: if (m_editingCommand) { Chris@1511: Chris@1511: QString newName = m_editingCommand->getName(); Chris@1511: Chris@1511: if (m_editingPoint.getFrame() != m_originalPoint.getFrame()) { Chris@1511: if (m_editingPoint.getValue() != m_originalPoint.getValue()) { Chris@1518: newName = tr("Edit Box"); Chris@1511: } else { Chris@1518: newName = tr("Relocate Box"); Chris@1511: } Chris@1511: } else { Chris@1511: newName = tr("Change Point Value"); Chris@1511: } Chris@1511: Chris@1511: m_editingCommand->setName(newName); Chris@1511: finish(m_editingCommand); Chris@1511: } Chris@1511: Chris@1511: m_editingCommand = nullptr; Chris@1511: m_editing = false; Chris@1511: } Chris@1511: Chris@1511: bool Chris@1518: BoxLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e) Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model) return false; Chris@1511: Chris@1511: Event region(0); Chris@1511: if (!getPointToDrag(v, e->x(), e->y(), region)) return false; Chris@1511: Chris@1515: ItemEditDialog::LabelOptions labelOptions; Chris@1518: labelOptions.valueLabel = tr("Minimum Value"); Chris@1518: labelOptions.levelLabel = tr("Value Extent"); Chris@1515: labelOptions.valueUnits = getScaleUnits(); Chris@1515: labelOptions.levelUnits = getScaleUnits(); Chris@1515: Chris@1511: ItemEditDialog *dialog = new ItemEditDialog Chris@1511: (model->getSampleRate(), Chris@1511: ItemEditDialog::ShowTime | Chris@1511: ItemEditDialog::ShowDuration | Chris@1511: ItemEditDialog::ShowValue | Chris@1515: ItemEditDialog::ShowLevel | Chris@1511: ItemEditDialog::ShowText, Chris@1515: labelOptions); Chris@1511: Chris@1511: dialog->setFrameTime(region.getFrame()); Chris@1511: dialog->setValue(region.getValue()); Chris@1515: dialog->setLevel(region.getLevel()); Chris@1511: dialog->setFrameDuration(region.getDuration()); Chris@1511: dialog->setText(region.getLabel()); Chris@1511: Chris@1511: if (dialog->exec() == QDialog::Accepted) { Chris@1511: Chris@1518: Event newBox = region Chris@1511: .withFrame(dialog->getFrameTime()) Chris@1511: .withValue(dialog->getValue()) Chris@1515: .withLevel(dialog->getLevel()) Chris@1511: .withDuration(dialog->getFrameDuration()) Chris@1511: .withLabel(dialog->getText()); Chris@1511: Chris@1511: ChangeEventsCommand *command = new ChangeEventsCommand Chris@1518: (m_model.untyped, tr("Edit Box")); Chris@1511: command->remove(region); Chris@1518: command->add(newBox); Chris@1511: finish(command); Chris@1511: } Chris@1511: Chris@1511: delete dialog; Chris@1511: return true; Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::moveSelection(Selection s, sv_frame_t newStartFrame) Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model) return; Chris@1511: Chris@1511: ChangeEventsCommand *command = Chris@1511: new ChangeEventsCommand(m_model.untyped, tr("Drag Selection")); Chris@1511: Chris@1511: EventVector points = Chris@1511: model->getEventsStartingWithin(s.getStartFrame(), s.getDuration()); Chris@1511: Chris@1511: for (EventVector::iterator i = points.begin(); Chris@1511: i != points.end(); ++i) { Chris@1511: Chris@1511: Event newPoint = (*i) Chris@1511: .withFrame(i->getFrame() + newStartFrame - s.getStartFrame()); Chris@1511: command->remove(*i); Chris@1511: command->add(newPoint); Chris@1511: } Chris@1511: Chris@1511: finish(command); Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::resizeSelection(Selection s, Selection newSize) Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model || !s.getDuration()) return; Chris@1511: Chris@1511: ChangeEventsCommand *command = Chris@1511: new ChangeEventsCommand(m_model.untyped, tr("Resize Selection")); Chris@1511: Chris@1511: EventVector points = Chris@1511: model->getEventsStartingWithin(s.getStartFrame(), s.getDuration()); Chris@1511: Chris@1511: double ratio = double(newSize.getDuration()) / double(s.getDuration()); Chris@1511: double oldStart = double(s.getStartFrame()); Chris@1511: double newStart = double(newSize.getStartFrame()); Chris@1511: Chris@1511: for (Event p: points) { Chris@1511: Chris@1511: double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart; Chris@1511: double newDuration = double(p.getDuration()) * ratio; Chris@1511: Chris@1511: Event newPoint = p Chris@1511: .withFrame(lrint(newFrame)) Chris@1511: .withDuration(lrint(newDuration)); Chris@1511: command->remove(p); Chris@1511: command->add(newPoint); Chris@1511: } Chris@1511: Chris@1511: finish(command); Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::deleteSelection(Selection s) Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model) return; Chris@1511: Chris@1511: ChangeEventsCommand *command = Chris@1511: new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points")); Chris@1511: Chris@1511: EventVector points = Chris@1511: model->getEventsStartingWithin(s.getStartFrame(), s.getDuration()); Chris@1511: Chris@1511: for (EventVector::iterator i = points.begin(); Chris@1511: i != points.end(); ++i) { Chris@1511: Chris@1511: if (s.contains(i->getFrame())) { Chris@1511: command->remove(*i); Chris@1511: } Chris@1511: } Chris@1511: Chris@1511: finish(command); Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to) Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model) return; Chris@1511: Chris@1511: EventVector points = Chris@1511: model->getEventsStartingWithin(s.getStartFrame(), s.getDuration()); Chris@1511: Chris@1511: for (Event p: points) { Chris@1511: to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame()))); Chris@1511: } Chris@1511: } Chris@1511: Chris@1511: bool Chris@1518: BoxLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */) Chris@1511: { Chris@1518: auto model = ModelById::getAs(m_model); Chris@1511: if (!model) return false; Chris@1511: Chris@1511: const EventVector &points = from.getPoints(); Chris@1511: Chris@1511: bool realign = false; Chris@1511: Chris@1511: if (clipboardHasDifferentAlignment(v, from)) { Chris@1511: Chris@1511: QMessageBox::StandardButton button = Chris@1511: QMessageBox::question(v->getView(), tr("Re-align pasted items?"), Chris@1511: 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?"), Chris@1511: QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, Chris@1511: QMessageBox::Yes); Chris@1511: Chris@1511: if (button == QMessageBox::Cancel) { Chris@1511: return false; Chris@1511: } Chris@1511: Chris@1511: if (button == QMessageBox::Yes) { Chris@1511: realign = true; Chris@1511: } Chris@1511: } Chris@1511: Chris@1511: ChangeEventsCommand *command = Chris@1511: new ChangeEventsCommand(m_model.untyped, tr("Paste")); Chris@1511: Chris@1511: for (EventVector::const_iterator i = points.begin(); Chris@1511: i != points.end(); ++i) { Chris@1511: Chris@1511: sv_frame_t frame = 0; Chris@1511: Chris@1511: if (!realign) { Chris@1511: Chris@1511: frame = i->getFrame(); Chris@1511: Chris@1511: } else { Chris@1511: Chris@1511: if (i->hasReferenceFrame()) { Chris@1511: frame = i->getReferenceFrame(); Chris@1511: frame = alignFromReference(v, frame); Chris@1511: } else { Chris@1511: frame = i->getFrame(); Chris@1511: } Chris@1511: } Chris@1511: Chris@1511: Event p = *i; Chris@1511: Event newPoint = p; Chris@1511: if (!p.hasValue()) { Chris@1518: newPoint = newPoint.withValue((model->getValueMinimum() + Chris@1518: model->getValueMaximum()) / 2); Chris@1511: } Chris@1511: if (!p.hasDuration()) { Chris@1511: sv_frame_t nextFrame = frame; Chris@1511: EventVector::const_iterator j = i; Chris@1511: for (; j != points.end(); ++j) { Chris@1511: if (j != i) break; Chris@1511: } Chris@1511: if (j != points.end()) { Chris@1511: nextFrame = j->getFrame(); Chris@1511: } Chris@1511: if (nextFrame == frame) { Chris@1511: newPoint = newPoint.withDuration(model->getResolution()); Chris@1511: } else { Chris@1511: newPoint = newPoint.withDuration(nextFrame - frame); Chris@1511: } Chris@1511: } Chris@1511: Chris@1511: command->add(newPoint); Chris@1511: } Chris@1511: Chris@1511: finish(command); Chris@1511: return true; Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::toXml(QTextStream &stream, Chris@1515: QString indent, QString extraAttributes) const Chris@1511: { Chris@1511: QString s; Chris@1511: Chris@1515: s += QString("verticalScale=\"%1\" ").arg(m_verticalScale); Chris@1511: Chris@1511: SingleColourLayer::toXml(stream, indent, extraAttributes + " " + s); Chris@1511: } Chris@1511: Chris@1511: void Chris@1518: BoxLayer::setProperties(const QXmlAttributes &attributes) Chris@1511: { Chris@1511: SingleColourLayer::setProperties(attributes); Chris@1511: Chris@1511: bool ok; Chris@1511: VerticalScale scale = (VerticalScale) Chris@1511: attributes.value("verticalScale").toInt(&ok); Chris@1511: if (ok) setVerticalScale(scale); Chris@1511: } Chris@1511: Chris@1511: