# HG changeset patch # User Chris Cannam # Date 1386093520 0 # Node ID ad12e428785b4d13306e20b749bc08a207f63694 # Parent 212644efa523f61316450bc6ee92f3f5158f68f3# Parent 81c4c44ecc103ac4ca8fdb61de0d8fd4e31a3456 Merge from default branch diff -r 81c4c44ecc10 -r ad12e428785b layer/FlexiNoteLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/FlexiNoteLayer.cpp Tue Dec 03 17:58:40 2013 +0000 @@ -0,0 +1,1647 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "FlexiNoteLayer.h" + +#include "data/model/Model.h" +#include "data/model/SparseTimeValueModel.h" +#include "base/RealTime.h" +#include "base/Profiler.h" +#include "base/Pitch.h" +#include "base/LogRange.h" +#include "base/RangeMapper.h" +#include "ColourDatabase.h" +#include "view/View.h" +#include "PianoScale.h" + +#include "data/model/FlexiNoteModel.h" + +#include "widgets/ItemEditDialog.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include // GF: included to compile std::numerical_limits on linux +#include + + +FlexiNoteLayer::FlexiNoteLayer() : + SingleColourLayer(), + + // m_model(0), + // m_editing(false), + // m_originalPoint(0, 0.0, 0, 1.f, tr("New Point")), + // m_editingPoint(0, 0.0, 0, 1.f, tr("New Point")), + // m_editingCommand(0), + // m_verticalScale(AutoAlignScale), + // m_scaleMinimum(0), + // m_scaleMaximum(0) + + m_model(0), + m_editing(false), + m_originalPoint(0, 0.0, 0, 1.f, tr("New Point")), + m_editingPoint(0, 0.0, 0, 1.f, tr("New Point")), + m_editingCommand(0), + m_verticalScale(AutoAlignScale), + m_editMode(DragNote), + m_scaleMinimum(34), + m_scaleMaximum(77), + m_intelligentActions(true) +{ +} + +void +FlexiNoteLayer::setModel(FlexiNoteModel *model) +{ + if (m_model == model) return; + m_model = model; + + connectSignals(m_model); + + // m_scaleMinimum = 0; + // m_scaleMaximum = 0; + + emit modelReplaced(); +} + +Layer::PropertyList +FlexiNoteLayer::getProperties() const +{ + PropertyList list = SingleColourLayer::getProperties(); + list.push_back("Vertical Scale"); + list.push_back("Scale Units"); + return list; +} + +QString +FlexiNoteLayer::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 +FlexiNoteLayer::getPropertyType(const PropertyName &name) const +{ + if (name == "Scale Units") return UnitsProperty; + if (name == "Vertical Scale") return ValueProperty; + return SingleColourLayer::getPropertyType(name); +} + +QString +FlexiNoteLayer::getPropertyGroupName(const PropertyName &name) const +{ + if (name == "Vertical Scale" || name == "Scale Units") { + return tr("Scale"); + } + return SingleColourLayer::getPropertyGroupName(name); +} + +int +FlexiNoteLayer::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 = 3; + if (deflt) *deflt = int(AutoAlignScale); + + val = int(m_verticalScale); + + } else if (name == "Scale Units") { + + if (deflt) *deflt = 0; + if (m_model) { + val = UnitDatabase::getInstance()->getUnitId + (m_model->getScaleUnits()); + } + + } else { + + val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt); + } + + return val; +} + +QString +FlexiNoteLayer::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"); + case 3: return tr("MIDI Notes"); + } + } + return SingleColourLayer::getPropertyValueLabel(name, value); +} + +void +FlexiNoteLayer::setProperty(const PropertyName &name, int value) +{ + if (name == "Vertical Scale") { + setVerticalScale(VerticalScale(value)); + } else if (name == "Scale Units") { + if (m_model) { + m_model->setScaleUnits + (UnitDatabase::getInstance()->getUnitById(value)); + emit modelChanged(); + } + } else { + return SingleColourLayer::setProperty(name, value); + } +} + +void +FlexiNoteLayer::setVerticalScale(VerticalScale scale) +{ + if (m_verticalScale == scale) return; + m_verticalScale = scale; + emit layerParametersChanged(); +} + +bool +FlexiNoteLayer::isLayerScrollable(const View *v) const +{ + QPoint discard; + return !v->shouldIlluminateLocalFeatures(this, discard); +} + +bool +FlexiNoteLayer::shouldConvertMIDIToHz() const +{ + QString unit = m_model->getScaleUnits(); + return (unit != "Hz"); +// if (unit == "" || +// unit.startsWith("MIDI") || +// unit.startsWith("midi")) return true; +// return false; +} + +bool +FlexiNoteLayer::getValueExtents(float &min, float &max, + bool &logarithmic, QString &unit) const +{ + if (!m_model) return false; + min = m_model->getValueMinimum(); + max = m_model->getValueMaximum(); + + if (shouldConvertMIDIToHz()) { + unit = "Hz"; + min = Pitch::getFrequencyForPitch(lrintf(min)); + max = Pitch::getFrequencyForPitch(lrintf(max + 1)); + } else unit = m_model->getScaleUnits(); + + if (m_verticalScale == MIDIRangeScale || + m_verticalScale == LogScale) logarithmic = true; + + return true; +} + +bool +FlexiNoteLayer::getDisplayExtents(float &min, float &max) const +{ + if (!m_model || shouldAutoAlign()) { + std::cerr << "No model or shouldAutoAlign()" << std::endl; + return false; + } + + if (m_verticalScale == MIDIRangeScale) { + min = Pitch::getFrequencyForPitch(0); + max = Pitch::getFrequencyForPitch(127); + return true; + } + + if (m_scaleMinimum == m_scaleMaximum) { + min = m_model->getValueMinimum(); + max = m_model->getValueMaximum(); + } else { + min = m_scaleMinimum; + max = m_scaleMaximum; + } + + if (shouldConvertMIDIToHz()) { + min = Pitch::getFrequencyForPitch(lrintf(min)); + max = Pitch::getFrequencyForPitch(lrintf(max + 1)); + } + +#ifdef DEBUG_NOTE_LAYER + cerr << "NoteLayer::getDisplayExtents: min = " << min << ", max = " << max << " (m_scaleMinimum = " << m_scaleMinimum << ", m_scaleMaximum = " << m_scaleMaximum << ")" << endl; +#endif + + return true; +} + +bool +FlexiNoteLayer::setDisplayExtents(float min, float max) +{ + if (!m_model) return false; + + if (min == max) { + if (min == 0.f) { + max = 1.f; + } else { + max = min * 1.0001; + } + } + + m_scaleMinimum = min; + m_scaleMaximum = max; + +#ifdef DEBUG_NOTE_LAYER + cerr << "FlexiNoteLayer::setDisplayExtents: min = " << min << ", max = " << max << endl; +#endif + + emit layerParametersChanged(); + return true; +} + +int +FlexiNoteLayer::getVerticalZoomSteps(int &defaultStep) const +{ + if (shouldAutoAlign()) return 0; + if (!m_model) return 0; + + defaultStep = 0; + return 100; +} + +int +FlexiNoteLayer::getCurrentVerticalZoomStep() const +{ + if (shouldAutoAlign()) return 0; + if (!m_model) return 0; + + RangeMapper *mapper = getNewVerticalZoomRangeMapper(); + if (!mapper) return 0; + + float dmin, dmax; + getDisplayExtents(dmin, dmax); + + int nr = mapper->getPositionForValue(dmax - dmin); + + delete mapper; + + return 100 - nr; +} + +//!!! lots of duplication with TimeValueLayer + +void +FlexiNoteLayer::setVerticalZoomStep(int step) +{ + if (shouldAutoAlign()) return; + if (!m_model) return; + + RangeMapper *mapper = getNewVerticalZoomRangeMapper(); + if (!mapper) return; + + float min, max; + bool logarithmic; + QString unit; + getValueExtents(min, max, logarithmic, unit); + + float dmin, dmax; + getDisplayExtents(dmin, dmax); + + float newdist = mapper->getValueForPosition(100 - step); + + float newmin, newmax; + + if (logarithmic) { + + // see SpectrogramLayer::setVerticalZoomStep + + newmax = (newdist + sqrtf(newdist*newdist + 4*dmin*dmax)) / 2; + newmin = newmax - newdist; + +// cerr << "newmin = " << newmin << ", newmax = " << newmax << endl; + + } else { + float dmid = (dmax + dmin) / 2; + newmin = dmid - newdist / 2; + newmax = dmid + newdist / 2; + } + + if (newmin < min) { + newmax += (min - newmin); + newmin = min; + } + if (newmax > max) { + newmax = max; + } + +#ifdef DEBUG_NOTE_LAYER + cerr << "FlexiNoteLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl; +#endif + + setDisplayExtents(newmin, newmax); +} + +RangeMapper * +FlexiNoteLayer::getNewVerticalZoomRangeMapper() const +{ + if (!m_model) return 0; + + RangeMapper *mapper; + + float min, max; + bool logarithmic; + QString unit; + getValueExtents(min, max, logarithmic, unit); + + if (min == max) return 0; + + if (logarithmic) { + mapper = new LogRangeMapper(0, 100, min, max, unit); + } else { + mapper = new LinearRangeMapper(0, 100, min, max, unit); + } + + return mapper; +} + +FlexiNoteModel::PointList +FlexiNoteLayer::getLocalPoints(View *v, int x) const +{ + if (!m_model) return FlexiNoteModel::PointList(); + + long frame = v->getFrameForX(x); + + FlexiNoteModel::PointList onPoints = + m_model->getPoints(frame); + + if (!onPoints.empty()) { + return onPoints; + } + + FlexiNoteModel::PointList prevPoints = + m_model->getPreviousPoints(frame); + FlexiNoteModel::PointList nextPoints = + m_model->getNextPoints(frame); + + FlexiNoteModel::PointList usePoints = prevPoints; + + if (prevPoints.empty()) { + usePoints = nextPoints; + } else if (long(prevPoints.begin()->frame) < v->getStartFrame() && + !(nextPoints.begin()->frame > v->getEndFrame())) { + usePoints = nextPoints; + } else if (long(nextPoints.begin()->frame) - frame < + frame - long(prevPoints.begin()->frame)) { + usePoints = nextPoints; + } + + if (!usePoints.empty()) { + int fuzz = 2; + int px = v->getXForFrame(usePoints.begin()->frame); + if ((px > x && px - x > fuzz) || + (px < x && x - px > fuzz + 1)) { + usePoints.clear(); + } + } + + return usePoints; +} + +bool +FlexiNoteLayer::getPointToDrag(View *v, int x, int y, FlexiNoteModel::Point &p) const +{ + if (!m_model) return false; + + long frame = v->getFrameForX(x); + + FlexiNoteModel::PointList onPoints = m_model->getPoints(frame); + if (onPoints.empty()) return false; + +// cerr << "frame " << frame << ": " << onPoints.size() << " candidate points" << endl; + + int nearestDistance = -1; + + for (FlexiNoteModel::PointList::const_iterator i = onPoints.begin(); + i != onPoints.end(); ++i) { + + int distance = getYForValue(v, (*i).value) - y; + if (distance < 0) distance = -distance; + if (nearestDistance == -1 || distance < nearestDistance) { + nearestDistance = distance; + p = *i; + } + } + + return true; +} + +bool +FlexiNoteLayer::getNoteToEdit(View *v, int x, int y, FlexiNoteModel::Point &p) const +{ + // GF: find the note that is closest to the cursor + if (!m_model) return false; + + long frame = v->getFrameForX(x); + + FlexiNoteModel::PointList onPoints = m_model->getPoints(frame); + if (onPoints.empty()) return false; + +// std::cerr << "frame " << frame << ": " << onPoints.size() << " candidate points" << std::endl; + + int nearestDistance = -1; + + for (FlexiNoteModel::PointList::const_iterator i = onPoints.begin(); + i != onPoints.end(); ++i) { + + int distance = getYForValue(v, (*i).value) - y; + if (distance < 0) distance = -distance; + if (nearestDistance == -1 || distance < nearestDistance) { + nearestDistance = distance; + p = *i; + } + } + + return true; +} + +QString +FlexiNoteLayer::getFeatureDescription(View *v, QPoint &pos) const +{ + int x = pos.x(); + + if (!m_model || !m_model->getSampleRate()) return ""; + + FlexiNoteModel::PointList points = getLocalPoints(v, x); + + if (points.empty()) { + if (!m_model->isReady()) { + return tr("In progress"); + } else { + return tr("No local points"); + } + } + + FlexiNote note(0); + FlexiNoteModel::PointList::iterator i; + + for (i = points.begin(); i != points.end(); ++i) { + + int y = getYForValue(v, i->value); + int h = NOTE_HEIGHT; // GF: larger notes + + if (m_model->getValueQuantization() != 0.0) { + h = y - getYForValue(v, i->value + m_model->getValueQuantization()); + if (h < NOTE_HEIGHT) h = NOTE_HEIGHT; + } + + // GF: this is not quite correct + if (pos.y() >= y - 4 && pos.y() <= y + h) { + note = *i; + break; + } + } + + if (i == points.end()) return tr("No local points"); + + RealTime rt = RealTime::frame2RealTime(note.frame, + m_model->getSampleRate()); + RealTime rd = RealTime::frame2RealTime(note.duration, + m_model->getSampleRate()); + + QString pitchText; + + if (shouldConvertMIDIToHz()) { + + int mnote = lrintf(note.value); + int cents = lrintf((note.value - mnote) * 100); + float freq = Pitch::getFrequencyForPitch(mnote, cents); + pitchText = tr("%1 (%2, %3 Hz)") + .arg(Pitch::getPitchLabel(mnote, cents)) + .arg(mnote) + .arg(freq); + + } else if (m_model->getScaleUnits() == "Hz") { + + pitchText = tr("%1 Hz (%2, %3)") + .arg(note.value) + .arg(Pitch::getPitchLabelForFrequency(note.value)) + .arg(Pitch::getPitchForFrequency(note.value)); + + } else { + pitchText = tr("%1 %2") + .arg(note.value).arg(m_model->getScaleUnits()); + } + + QString text; + + if (note.label == "") { + text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nNo label")) + .arg(rt.toText(true).c_str()) + .arg(pitchText) + .arg(rd.toText(true).c_str()); + } else { + text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nLabel:\t%4")) + .arg(rt.toText(true).c_str()) + .arg(pitchText) + .arg(rd.toText(true).c_str()) + .arg(note.label); + } + + pos = QPoint(v->getXForFrame(note.frame), + getYForValue(v, note.value)); + return text; +} + +bool +FlexiNoteLayer::snapToFeatureFrame(View *v, int &frame, + size_t &resolution, + SnapType snap) const +{ + if (!m_model) { + return Layer::snapToFeatureFrame(v, frame, resolution, snap); + } + + resolution = m_model->getResolution(); + FlexiNoteModel::PointList points; + + if (snap == SnapNeighbouring) { + + points = getLocalPoints(v, v->getXForFrame(frame)); + if (points.empty()) return false; + frame = points.begin()->frame; + return true; + } + + points = m_model->getPoints(frame, frame); + int snapped = frame; + bool found = false; + + for (FlexiNoteModel::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + + if (snap == SnapRight) { + + if (i->frame > frame) { + snapped = i->frame; + found = true; + break; + } + + } else if (snap == SnapLeft) { + + if (i->frame <= frame) { + snapped = i->frame; + found = true; // don't break, as the next may be better + } else { + break; + } + + } else { // nearest + + FlexiNoteModel::PointList::const_iterator j = i; + ++j; + + if (j == points.end()) { + + snapped = i->frame; + found = true; + break; + + } else if (j->frame >= frame) { + + if (j->frame - frame < frame - i->frame) { + snapped = j->frame; + } else { + snapped = i->frame; + } + found = true; + break; + } + } + } + + frame = snapped; + return found; +} + +void +FlexiNoteLayer::getScaleExtents(View *v, float &min, float &max, bool &log) const +{ + min = 0.0; + max = 0.0; + log = false; + + QString queryUnits; + if (shouldConvertMIDIToHz()) queryUnits = "Hz"; + else queryUnits = m_model->getScaleUnits(); + + if (shouldAutoAlign()) { + + if (!v->getValueExtents(queryUnits, min, max, log)) { + + min = m_model->getValueMinimum(); + max = m_model->getValueMaximum(); + + if (shouldConvertMIDIToHz()) { + min = Pitch::getFrequencyForPitch(lrintf(min)); + max = Pitch::getFrequencyForPitch(lrintf(max + 1)); + } + +#ifdef DEBUG_NOTE_LAYER + cerr << "FlexiNoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; +#endif + + } else if (log) { + + LogRange::mapRange(min, max); + +#ifdef DEBUG_NOTE_LAYER + cerr << "FlexiNoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; +#endif + } + + } else { + + getDisplayExtents(min, max); + + if (m_verticalScale == MIDIRangeScale) { + min = Pitch::getFrequencyForPitch(0); + max = Pitch::getFrequencyForPitch(70); + } else if (shouldConvertMIDIToHz()) { + min = Pitch::getFrequencyForPitch(lrintf(min)); + max = Pitch::getFrequencyForPitch(lrintf(max + 1)); + } + + if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) { + LogRange::mapRange(min, max); + log = true; + } + } + + if (max == min) max = min + 1.0; +} + +int +FlexiNoteLayer::getYForValue(View *v, float val) const +{ + float min = 0.0, max = 0.0; + bool logarithmic = false; + int h = v->height(); + + getScaleExtents(v, min, max, logarithmic); + +#ifdef DEBUG_NOTE_LAYER + cerr << "FlexiNoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl; +#endif + + if (shouldConvertMIDIToHz()) { + val = Pitch::getFrequencyForPitch(lrintf(val), + lrintf((val - lrintf(val)) * 100)); +#ifdef DEBUG_NOTE_LAYER + cerr << "shouldConvertMIDIToHz true, val now = " << val << endl; +#endif + } + + if (logarithmic) { + val = LogRange::map(val); +#ifdef DEBUG_NOTE_LAYER + cerr << "logarithmic true, val now = " << val << endl; +#endif + } + + int y = int(h - ((val - min) * h) / (max - min)) - 1; +#ifdef DEBUG_NOTE_LAYER + cerr << "y = " << y << endl; +#endif + return y; +} + +float +FlexiNoteLayer::getValueForY(View *v, int y) const +{ + float min = 0.0, max = 0.0; + bool logarithmic = false; + int h = v->height(); + + getScaleExtents(v, min, max, logarithmic); + + float val = min + (float(h - y) * float(max - min)) / h; + + if (logarithmic) { + val = powf(10.f, val); + } + + if (shouldConvertMIDIToHz()) { + val = Pitch::getPitchForFrequency(val); + } + + return val; +} + +bool +FlexiNoteLayer::shouldAutoAlign() const +{ + if (!m_model) return false; + return (m_verticalScale == AutoAlignScale); +} + +void +FlexiNoteLayer::paint(View *v, QPainter &paint, QRect rect) const +{ + if (!m_model || !m_model->isOK()) return; + + int sampleRate = m_model->getSampleRate(); + if (!sampleRate) return; + +// Profiler profiler("FlexiNoteLayer::paint", true); + + int x0 = rect.left(), x1 = rect.right(); + long frame0 = v->getFrameForX(x0); + long frame1 = v->getFrameForX(x1); + + FlexiNoteModel::PointList points(m_model->getPoints(frame0, frame1)); + if (points.empty()) return; + + paint.setPen(getBaseQColor()); + + QColor brushColour(getBaseQColor()); + brushColour.setAlpha(80); + +// SVDEBUG << "FlexiNoteLayer::paint: resolution is " +// << m_model->getResolution() << " frames" << endl; + + float min = m_model->getValueMinimum(); + float max = m_model->getValueMaximum(); + if (max == min) max = min + 1.0; + + QPoint localPos; + FlexiNoteModel::Point 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); + + for (FlexiNoteModel::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + + const FlexiNoteModel::Point &p(*i); + + int x = v->getXForFrame(p.frame); + int y = getYForValue(v, p.value); + int w = v->getXForFrame(p.frame + p.duration) - x; + int h = NOTE_HEIGHT; //GF: larger notes + + if (m_model->getValueQuantization() != 0.0) { + h = y - getYForValue(v, p.value + m_model->getValueQuantization()); + if (h < NOTE_HEIGHT) h = NOTE_HEIGHT; //GF: larger notes + } + + if (w < 1) w = 1; + paint.setPen(getBaseQColor()); + paint.setBrush(brushColour); + + // if (shouldIlluminate && + // // "illuminatePoint == p" + // !FlexiNoteModel::Point::Comparator()(illuminatePoint, p) && + // !FlexiNoteModel::Point::Comparator()(p, illuminatePoint)) { + // + // paint.setPen(v->getForeground()); + // paint.setBrush(v->getForeground()); + // + // QString vlabel = QString("%1%2").arg(p.value).arg(m_model->getScaleUnits()); + // v->drawVisibleText(paint, + // x - paint.fontMetrics().width(vlabel) - 2, + // y + paint.fontMetrics().height()/2 + // - paint.fontMetrics().descent(), + // vlabel, View::OutlinedText); + // + // QString hlabel = RealTime::frame2RealTime + // (p.frame, m_model->getSampleRate()).toText(true).c_str(); + // v->drawVisibleText(paint, + // x, + // y - h/2 - paint.fontMetrics().descent() - 2, + // hlabel, View::OutlinedText); + // } + + paint.drawRect(x, y - h/2, w, h); + } + + paint.restore(); +} + +int +NoteLayer::getVerticalScaleWidth(View *, bool, QPainter &paint) const +{ + return 10; +} + +void +NoteLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect) const +{ + float fmin, fmax; + getDisplayExtents(fmin, fmax); + PianoScale().paintPianoVertical + (v, paint, QRect(0, 0, 10, v->height()), fmin, fmax); + paint.drawLine(10, 0, 10, v->height()); +} + +void +FlexiNoteLayer::drawStart(View *v, QMouseEvent *e) +{ +// SVDEBUG << "FlexiNoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl; + + if (!m_model) return; + + long frame = v->getFrameForX(e->x()); + if (frame < 0) frame = 0; + frame = frame / m_model->getResolution() * m_model->getResolution(); + + float value = getValueForY(v, e->y()); + + m_editingPoint = FlexiNoteModel::Point(frame, value, 0, 0.8, tr("New Point")); + m_originalPoint = m_editingPoint; + + if (m_editingCommand) finish(m_editingCommand); + m_editingCommand = new FlexiNoteModel::EditCommand(m_model, + tr("Draw Point")); + m_editingCommand->addPoint(m_editingPoint); + + m_editing = true; +} + +void +FlexiNoteLayer::drawDrag(View *v, QMouseEvent *e) +{ +// SVDEBUG << "FlexiNoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl; + + if (!m_model || !m_editing) return; + + long frame = v->getFrameForX(e->x()); + if (frame < 0) frame = 0; + frame = frame / m_model->getResolution() * m_model->getResolution(); + + float newValue = getValueForY(v, e->y()); + + long newFrame = m_editingPoint.frame; + long newDuration = frame - newFrame; + if (newDuration < 0) { + newFrame = frame; + newDuration = -newDuration; + } else if (newDuration == 0) { + newDuration = 1; + } + + m_editingCommand->deletePoint(m_editingPoint); + m_editingPoint.frame = newFrame; + m_editingPoint.value = newValue; + m_editingPoint.duration = newDuration; + m_editingCommand->addPoint(m_editingPoint); +} + +void +FlexiNoteLayer::drawEnd(View *, QMouseEvent *) +{ +// SVDEBUG << "FlexiNoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl; + if (!m_model || !m_editing) return; + finish(m_editingCommand); + m_editingCommand = 0; + m_editing = false; +} + +void +FlexiNoteLayer::eraseStart(View *v, QMouseEvent *e) +{ + if (!m_model) return; + + if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return; + + if (m_editingCommand) { + finish(m_editingCommand); + m_editingCommand = 0; + } + + m_editing = true; +} + +void +FlexiNoteLayer::eraseDrag(View *v, QMouseEvent *e) +{ +} + +void +FlexiNoteLayer::eraseEnd(View *v, QMouseEvent *e) +{ + if (!m_model || !m_editing) return; + + m_editing = false; + + FlexiNoteModel::Point p(0); + if (!getPointToDrag(v, e->x(), e->y(), p)) return; + if (p.frame != m_editingPoint.frame || p.value != m_editingPoint.value) return; + + m_editingCommand = new FlexiNoteModel::EditCommand(m_model, tr("Erase Point")); + + m_editingCommand->deletePoint(m_editingPoint); + + finish(m_editingCommand); + m_editingCommand = 0; + m_editing = false; +} + +void +FlexiNoteLayer::editStart(View *v, QMouseEvent *e) +{ +// SVDEBUG << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl; + std::cerr << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl; + + if (!m_model) return; + + if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return; + m_originalPoint = FlexiNote(m_editingPoint); + + if (m_editMode == RightBoundary) { + m_dragPointX = v->getXForFrame(m_editingPoint.frame + m_editingPoint.duration); + } else { + m_dragPointX = v->getXForFrame(m_editingPoint.frame); + } + m_dragPointY = getYForValue(v, m_editingPoint.value); + + if (m_editingCommand) { + finish(m_editingCommand); + m_editingCommand = 0; + } + + m_editing = true; + m_dragStartX = e->x(); + m_dragStartY = e->y(); + + long onset = m_originalPoint.frame; + long offset = m_originalPoint.frame + m_originalPoint.duration - 1; + + m_greatestLeftNeighbourFrame = -1; + m_smallestRightNeighbourFrame = std::numeric_limits::max(); + + for (FlexiNoteModel::PointList::const_iterator i = m_model->getPoints().begin(); + i != m_model->getPoints().end(); ++i) { + FlexiNote currentNote = *i; + + // left boundary + if (currentNote.frame + currentNote.duration - 1 < onset) { + m_greatestLeftNeighbourFrame = currentNote.frame + currentNote.duration - 1; + } + + // right boundary + if (currentNote.frame > offset) { + m_smallestRightNeighbourFrame = currentNote.frame; + break; + } + } + std::cerr << "note frame: " << onset << ", left boundary: " << m_greatestLeftNeighbourFrame << ", right boundary: " << m_smallestRightNeighbourFrame << std::endl; +} + +void +FlexiNoteLayer::editDrag(View *v, QMouseEvent *e) +{ +// SVDEBUG << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl; + std::cerr << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << std::endl; + + if (!m_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; + + long dragFrame = v->getFrameForX(newx); + if (dragFrame < 0) dragFrame = 0; + dragFrame = dragFrame / m_model->getResolution() * m_model->getResolution(); + + float value = getValueForY(v, newy); + + if (!m_editingCommand) { + m_editingCommand = new FlexiNoteModel::EditCommand(m_model, + tr("Drag Point")); + } + + m_editingCommand->deletePoint(m_editingPoint); + + std::cerr << "edit mode: " << m_editMode << std::endl; + + switch (m_editMode) { + case LeftBoundary : { + // left + if (m_intelligentActions && dragFrame <= m_greatestLeftNeighbourFrame) dragFrame = m_greatestLeftNeighbourFrame + 1; + // right + if (m_intelligentActions && dragFrame >= m_originalPoint.frame + m_originalPoint.duration) { + dragFrame = m_originalPoint.frame + m_originalPoint.duration - 1; + } + m_editingPoint.frame = dragFrame; + m_editingPoint.duration = m_originalPoint.frame - dragFrame + m_originalPoint.duration; + break; + } + case RightBoundary : { + // left + if (m_intelligentActions && dragFrame <= m_greatestLeftNeighbourFrame) dragFrame = m_greatestLeftNeighbourFrame + 1; + if (m_intelligentActions && dragFrame >= m_smallestRightNeighbourFrame) dragFrame = m_smallestRightNeighbourFrame - 1; + m_editingPoint.duration = dragFrame - m_originalPoint.frame + 1; + break; + } + case DragNote : { + // left + if (m_intelligentActions && dragFrame <= m_greatestLeftNeighbourFrame) dragFrame = m_greatestLeftNeighbourFrame + 1; + // right + if (m_intelligentActions && dragFrame + m_originalPoint.duration >= m_smallestRightNeighbourFrame) { + dragFrame = m_smallestRightNeighbourFrame - m_originalPoint.duration; + } + m_editingPoint.frame = dragFrame; + m_editingPoint.value = value; + break; + } + } + m_editingCommand->addPoint(m_editingPoint); + std::cerr << "added new point(" << m_editingPoint.frame << "," << m_editingPoint.duration << ")" << std::endl; + +} + +void +FlexiNoteLayer::editEnd(View *v, QMouseEvent *e) +{ +// SVDEBUG << "FlexiNoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl; + std::cerr << "FlexiNoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl; + + if (!m_model || !m_editing) return; + + if (m_editingCommand) { + + QString newName = m_editingCommand->getName(); + + if (m_editingPoint.frame != m_originalPoint.frame) { + if (m_editingPoint.value != m_originalPoint.value) { + newName = tr("Edit Point"); + } else { + newName = tr("Relocate Point"); + } + } else { + newName = tr("Change Point Value"); + } + + m_editingCommand->setName(newName); + finish(m_editingCommand); + } + + m_editingCommand = 0; + m_editing = false; +} + +void +FlexiNoteLayer::splitStart(View *v, QMouseEvent *e) +{ + // GF: note splitting starts (!! remove printing soon) + std::cerr << "splitStart" << std::endl; + if (!m_model) return; + + if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return; + // m_originalPoint = m_editingPoint; + // + // m_dragPointX = v->getXForFrame(m_editingPoint.frame); + // m_dragPointY = getYForValue(v, m_editingPoint.value); + + if (m_editingCommand) { + finish(m_editingCommand); + m_editingCommand = 0; + } + + m_editing = true; + m_dragStartX = e->x(); + m_dragStartY = e->y(); + +} + +void +FlexiNoteLayer::splitEnd(View *v, QMouseEvent *e) +{ + // GF: note splitting ends. (!! remove printing soon) + std::cerr << "splitEnd" << std::endl; + if (!m_model || !m_editing || m_editMode != SplitNote) return; + + int xdist = e->x() - m_dragStartX; + int ydist = e->y() - m_dragStartY; + if (xdist != 0 || ydist != 0) { + std::cerr << "mouse moved" << std::endl; + return; + } + + // MM: simpler declaration + FlexiNote note(0); + if (!getPointToDrag(v, e->x(), e->y(), note)) return; + + long frame = v->getFrameForX(e->x()); + + int gap = 0; // MM: I prefer a gap of 0, but we can decide later + + // MM: changed this a bit, to make it slightly clearer (// GF: nice changes!) + FlexiNote newNote1(note.frame, note.value, + frame - note.frame - gap, + note.level, note.label); + + FlexiNote newNote2(frame, note.value, + note.duration - newNote1.duration, + note.level, note.label); + + if (m_intelligentActions) { + updateNoteValue(v,newNote1); + updateNoteValue(v,newNote2); + } + + FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand + (m_model, tr("Edit Point")); + command->deletePoint(note); + if ((e->modifiers() & Qt::ShiftModifier)) { + finish(command); + return; + } + command->addPoint(newNote1); + command->addPoint(newNote2); + finish(command); + +} + +void +FlexiNoteLayer::addNote(View *v, QMouseEvent *e) +{ + std::cerr << "addNote" << std::endl; + if (!m_model) return; + + long duration = 10000; + + long frame = v->getFrameForX(e->x()); + float value = getValueForY(v, e->y()); + + if (m_intelligentActions) { + long smallestRightNeighbourFrame = 0; + for (FlexiNoteModel::PointList::const_iterator i = m_model->getPoints().begin(); + i != m_model->getPoints().end(); ++i) { + FlexiNote currentNote = *i; + if (currentNote.frame > frame) { + smallestRightNeighbourFrame = currentNote.frame; + break; + } + } + + duration = std::min(smallestRightNeighbourFrame - frame + 1, duration); + duration = (duration > 0) ? duration : 0; + } + + if (!m_intelligentActions || + m_model->getPoints(frame).empty() && duration > 0) + { + FlexiNote newNote(frame, value, duration, 100, "new note"); + FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand + (m_model, tr("Add Point")); + command->addPoint(newNote); + finish(command); + } +} + + +void +FlexiNoteLayer::updateNoteValue(View *v, FlexiNoteModel::Point ¬e) const +{ + //GF: update the note value conforming the median of pitch values in the underlying note layer + Layer *layer = v->getLayer(1); // GF: !!! gross assumption about correct layer order + SparseTimeValueModel *model = 0; + if (layer && layer->getModel()) + model = dynamic_cast(layer->getModel()); + + if (!model) return; + + std::cerr << model->getTypeName() << std::endl; + + SparseModel::PointList dataPoints = model->getPoints(note.frame, note.frame + note.duration); + if (dataPoints.empty()) return; + + // std::cerr << "frame " << note.frame << ": " << dataPoints.size() << " candidate points" << std::endl; + + std::vector pitchValues; + + for (SparseModel::PointList::const_iterator i = dataPoints.begin(); + i != dataPoints.end(); ++i) { + pitchValues.push_back((*i).value); + } + sort(pitchValues.begin(), pitchValues.end()); + size_t size = pitchValues.size(); + double median; + + if (size % 2 == 0) { + median = (pitchValues[size/2 - 1] + pitchValues[size/2]) / 2; + } else { + median = pitchValues[size/2]; + } + + note.value = median; +} + +void +FlexiNoteLayer::mouseMoveEvent(View *v, QMouseEvent *e) +{ + // GF: context sensitive cursors + // v->setCursor(Qt::ArrowCursor); + FlexiNoteModel::Point note(0); + if (!getNoteToEdit(v, e->x(), e->y(), note)) { + // v->setCursor(Qt::UpArrowCursor); + return; + } + + bool closeToLeft = false, closeToRight = false, closeToTop = false, closeToBottom = false; + getRelativeMousePosition(v, note, e->x(), e->y(), closeToLeft, closeToRight, closeToTop, closeToBottom); + // if (!closeToLeft) return; + // if (closeToTop) v->setCursor(Qt::SizeVerCursor); + + if (closeToLeft) { v->setCursor(Qt::SizeHorCursor); m_editMode = LeftBoundary; return; } + if (closeToRight) { v->setCursor(Qt::SizeHorCursor); m_editMode = RightBoundary; return; } + if (closeToTop) { v->setCursor(Qt::CrossCursor); m_editMode = DragNote; return; } + if (closeToBottom) { v->setCursor(Qt::UpArrowCursor); m_editMode = SplitNote; return; } + + v->setCursor(Qt::ArrowCursor); + + // std::cerr << "Mouse moved in edit mode over FlexiNoteLayer" << std::endl; + // v->setCursor(Qt::SizeHorCursor); + +} + +void +FlexiNoteLayer::getRelativeMousePosition(View *v, FlexiNoteModel::Point ¬e, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const +{ + // GF: TODO: consoloidate the tolerance values + if (!m_model) return; + + int ctol = 0; + int noteStartX = v->getXForFrame(note.frame); + int noteEndX = v->getXForFrame(note.frame + note.duration); + int noteValueY = getYForValue(v,note.value); + int noteStartY = noteValueY - (NOTE_HEIGHT / 2); + int noteEndY = noteValueY + (NOTE_HEIGHT / 2); + + bool closeToNote = false; + + if (y >= noteStartY-ctol && y <= noteEndY+ctol && x >= noteStartX-ctol && x <= noteEndX+ctol) closeToNote = true; + if (!closeToNote) return; + + int tol = NOTE_HEIGHT / 2; + + if (x >= noteStartX - tol && x <= noteStartX + tol) closeToLeft = true; + if (x >= noteEndX - tol && x <= noteEndX + tol) closeToRight = true; + if (y >= noteStartY - tol && y <= noteStartY + tol) closeToTop = true; + if (y >= noteEndY - tol && y <= noteEndY + tol) closeToBottom = true; + +// cerr << "FlexiNoteLayer::getRelativeMousePosition: close to: left " << closeToLeft << " right " << closeToRight << " top " << closeToTop << " bottom " << closeToBottom << endl; +} + + +bool +FlexiNoteLayer::editOpen(View *v, QMouseEvent *e) +{ + std::cerr << "Opening note editor dialog" << std::endl; + if (!m_model) return false; + + FlexiNoteModel::Point note(0); + if (!getPointToDrag(v, e->x(), e->y(), note)) return false; + +// FlexiNoteModel::Point note = *points.begin(); + + ItemEditDialog *dialog = new ItemEditDialog + (m_model->getSampleRate(), + ItemEditDialog::ShowTime | + ItemEditDialog::ShowDuration | + ItemEditDialog::ShowValue | + ItemEditDialog::ShowText, + m_model->getScaleUnits()); + + dialog->setFrameTime(note.frame); + dialog->setValue(note.value); + dialog->setFrameDuration(note.duration); + dialog->setText(note.label); + + if (dialog->exec() == QDialog::Accepted) { + + FlexiNoteModel::Point newNote = note; + newNote.frame = dialog->getFrameTime(); + newNote.value = dialog->getValue(); + newNote.duration = dialog->getFrameDuration(); + newNote.label = dialog->getText(); + + FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand + (m_model, tr("Edit Point")); + command->deletePoint(note); + command->addPoint(newNote); + finish(command); + } + + delete dialog; + return true; +} + +void +FlexiNoteLayer::moveSelection(Selection s, size_t newStartFrame) +{ + if (!m_model) return; + + FlexiNoteModel::EditCommand *command = + new FlexiNoteModel::EditCommand(m_model, tr("Drag Selection")); + + FlexiNoteModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + for (FlexiNoteModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + + if (s.contains(i->frame)) { + FlexiNoteModel::Point newPoint(*i); + newPoint.frame = i->frame + newStartFrame - s.getStartFrame(); + command->deletePoint(*i); + command->addPoint(newPoint); + } + } + + finish(command); +} + +void +FlexiNoteLayer::resizeSelection(Selection s, Selection newSize) +{ + if (!m_model) return; + + FlexiNoteModel::EditCommand *command = + new FlexiNoteModel::EditCommand(m_model, tr("Resize Selection")); + + FlexiNoteModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + double ratio = + double(newSize.getEndFrame() - newSize.getStartFrame()) / + double(s.getEndFrame() - s.getStartFrame()); + + for (FlexiNoteModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + + if (s.contains(i->frame)) { + + double targetStart = i->frame; + targetStart = newSize.getStartFrame() + + double(targetStart - s.getStartFrame()) * ratio; + + double targetEnd = i->frame + i->duration; + targetEnd = newSize.getStartFrame() + + double(targetEnd - s.getStartFrame()) * ratio; + + FlexiNoteModel::Point newPoint(*i); + newPoint.frame = lrint(targetStart); + newPoint.duration = lrint(targetEnd - targetStart); + command->deletePoint(*i); + command->addPoint(newPoint); + } + } + + finish(command); +} + +void +FlexiNoteLayer::deleteSelection(Selection s) +{ + if (!m_model) return; + + FlexiNoteModel::EditCommand *command = + new FlexiNoteModel::EditCommand(m_model, tr("Delete Selected Points")); + + FlexiNoteModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + for (FlexiNoteModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + + if (s.contains(i->frame)) { + command->deletePoint(*i); + } + } + + finish(command); +} + +void +FlexiNoteLayer::copy(View *v, Selection s, Clipboard &to) +{ + if (!m_model) return; + + FlexiNoteModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + for (FlexiNoteModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + if (s.contains(i->frame)) { + Clipboard::Point point(i->frame, i->value, i->duration, i->level, i->label); + point.setReferenceFrame(alignToReference(v, i->frame)); + to.addPoint(point); + } + } +} + +bool +FlexiNoteLayer::paste(View *v, const Clipboard &from, int frameOffset, bool /* interactive */) +{ + if (!m_model) return false; + + const Clipboard::PointList &points = from.getPoints(); + + bool realign = false; + + if (clipboardHasDifferentAlignment(v, from)) { + + QMessageBox::StandardButton button = + QMessageBox::question(v, 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; + } + } + + FlexiNoteModel::EditCommand *command = + new FlexiNoteModel::EditCommand(m_model, tr("Paste")); + + for (Clipboard::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + + if (!i->haveFrame()) continue; + size_t frame = 0; + + if (!realign) { + + frame = i->getFrame(); + + } else { + + if (i->haveReferenceFrame()) { + frame = i->getReferenceFrame(); + frame = alignFromReference(v, frame); + } else { + frame = i->getFrame(); + } + } + + FlexiNoteModel::Point newPoint(frame); + + if (i->haveLabel()) newPoint.label = i->getLabel(); + if (i->haveValue()) newPoint.value = i->getValue(); + else newPoint.value = (m_model->getValueMinimum() + + m_model->getValueMaximum()) / 2; + if (i->haveLevel()) newPoint.level = i->getLevel(); + if (i->haveDuration()) newPoint.duration = i->getDuration(); + else { + size_t nextFrame = frame; + Clipboard::PointList::const_iterator j = i; + for (; j != points.end(); ++j) { + if (!j->haveFrame()) continue; + if (j != i) break; + } + if (j != points.end()) { + nextFrame = j->getFrame(); + } + if (nextFrame == frame) { + newPoint.duration = m_model->getResolution(); + } else { + newPoint.duration = nextFrame - frame; + } + } + + command->addPoint(newPoint); + } + + finish(command); + return true; +} + +void +FlexiNoteLayer::addNoteOn(long frame, int pitch, int velocity) +{ + m_pendingNoteOns.insert(FlexiNote(frame, pitch, 0, float(velocity) / 127.0, "")); +} + +void +FlexiNoteLayer::addNoteOff(long frame, int pitch) +{ + for (FlexiNoteSet::iterator i = m_pendingNoteOns.begin(); + i != m_pendingNoteOns.end(); ++i) { + if (lrintf((*i).value) == pitch) { + FlexiNote note(*i); + m_pendingNoteOns.erase(i); + note.duration = frame - note.frame; + if (m_model) { + FlexiNoteModel::AddPointCommand *c = new FlexiNoteModel::AddPointCommand + (m_model, note, tr("Record FlexiNote")); + // execute and bundle: + CommandHistory::getInstance()->addCommand(c, true, true); + } + break; + } + } +} + +void +FlexiNoteLayer::abandonNoteOns() +{ + m_pendingNoteOns.clear(); +} + +int +FlexiNoteLayer::getDefaultColourHint(bool darkbg, bool &impose) +{ + impose = false; + return ColourDatabase::getInstance()->getColourIndex + (QString(darkbg ? "White" : "Black")); +} + +void +FlexiNoteLayer::toXml(QTextStream &stream, + QString indent, QString extraAttributes) const +{ + SingleColourLayer::toXml(stream, indent, extraAttributes + + QString(" verticalScale=\"%1\" scaleMinimum=\"%2\" scaleMaximum=\"%3\" ") + .arg(m_verticalScale) + .arg(m_scaleMinimum) + .arg(m_scaleMaximum)); +} + +void +FlexiNoteLayer::setProperties(const QXmlAttributes &attributes) +{ + SingleColourLayer::setProperties(attributes); + + bool ok, alsoOk; + VerticalScale scale = (VerticalScale) + attributes.value("verticalScale").toInt(&ok); + if (ok) setVerticalScale(scale); + + float min = attributes.value("scaleMinimum").toFloat(&ok); + float max = attributes.value("scaleMaximum").toFloat(&alsoOk); +// if (ok && alsoOk && min != max) setDisplayExtents(min, max); +} + +void +FlexiNoteLayer::setVerticalRangeToNoteRange(View *v) +{ + float minf = std::numeric_limits::max(); + float maxf = 0; + bool hasNotes = 0; + for (FlexiNoteModel::PointList::const_iterator i = m_model->getPoints().begin(); + i != m_model->getPoints().end(); ++i) { + hasNotes = 1; + FlexiNote note = *i; + if (note.value < minf) minf = note.value; + if (note.value > maxf) maxf = note.value; + } + + std::cerr << "min frequency:" << minf << ", max frequency: " << maxf << std::endl; + + if (hasNotes) { + v->getLayer(1)->setDisplayExtents(minf*0.66,maxf*1.5); + // MM: this is a hack because we rely on + // * this layer being automatically aligned to layer 1 + // * layer one is a log frequency layer. + } +} + + diff -r 81c4c44ecc10 -r ad12e428785b layer/FlexiNoteLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/FlexiNoteLayer.h Tue Dec 03 17:58:40 2013 +0000 @@ -0,0 +1,197 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _FLEXINOTE_LAYER_H_ +#define _FLEXINOTE_LAYER_H_ + +#define NOTE_HEIGHT 16 + +#include "SingleColourLayer.h" +#include "data/model/FlexiNoteModel.h" + +#include +#include + +class View; +class QPainter; + +class FlexiNoteLayer : public SingleColourLayer +{ + Q_OBJECT + +public: + FlexiNoteLayer(); + + virtual void paint(View *v, QPainter &paint, QRect rect) const; + + virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const; + virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const; + + virtual QString getFeatureDescription(View *v, QPoint &) const; + + virtual bool snapToFeatureFrame(View *v, int &frame, + size_t &resolution, + SnapType snap) const; + + virtual void drawStart(View *v, QMouseEvent *); + virtual void drawDrag(View *v, QMouseEvent *); + virtual void drawEnd(View *v, QMouseEvent *); + + virtual void eraseStart(View *v, QMouseEvent *); + virtual void eraseDrag(View *v, QMouseEvent *); + virtual void eraseEnd(View *v, QMouseEvent *); + + virtual void editStart(View *v, QMouseEvent *); + virtual void editDrag(View *v, QMouseEvent *); + virtual void editEnd(View *v, QMouseEvent *); + + virtual void splitStart(View *v, QMouseEvent *); + virtual void splitEnd(View *v, QMouseEvent *); + + virtual void addNote(View *v, QMouseEvent *e); + + virtual void mouseMoveEvent(View *v, QMouseEvent *); + + virtual bool editOpen(View *v, QMouseEvent *); + + virtual void moveSelection(Selection s, size_t newStartFrame); + virtual void resizeSelection(Selection s, Selection newSize); + virtual void deleteSelection(Selection s); + + virtual void copy(View *v, Selection s, Clipboard &to); + virtual bool paste(View *v, const Clipboard &from, int frameOffset, + bool interactive); + + virtual const Model *getModel() const { return m_model; } + void setModel(FlexiNoteModel *model); + + virtual PropertyList getProperties() const; + virtual QString getPropertyLabel(const PropertyName &) const; + virtual PropertyType getPropertyType(const PropertyName &) const; + virtual QString getPropertyGroupName(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max, int *deflt) const; + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + virtual void setProperty(const PropertyName &, int value); + + enum VerticalScale { + AutoAlignScale, + LinearScale, + LogScale, + MIDIRangeScale + }; + + //GF: Tonioni: context sensitive note edit actions (denoted clockwise from top). + enum EditMode { + DragNote, + RightBoundary, + SplitNote, + LeftBoundary + }; + + void setIntelligentActions(bool on) { m_intelligentActions=on; } + + void setVerticalScale(VerticalScale scale); + VerticalScale getVerticalScale() const { return m_verticalScale; } + + virtual bool isLayerScrollable(const View *v) const; + + virtual bool isLayerEditable() const { return true; } + + virtual int getCompletion(View *) const { return m_model->getCompletion(); } + + virtual bool getValueExtents(float &min, float &max, + bool &log, QString &unit) const; + + virtual bool getDisplayExtents(float &min, float &max) const; + virtual bool setDisplayExtents(float min, float max); + + virtual int getVerticalZoomSteps(int &defaultStep) const; + virtual int getCurrentVerticalZoomStep() const; + virtual void setVerticalZoomStep(int); + virtual RangeMapper *getNewVerticalZoomRangeMapper() const; + + /** + * Add a note-on. Used when recording MIDI "live". The note will + * not be finally added to the layer until the corresponding + * note-off. + */ + void addNoteOn(long frame, int pitch, int velocity); + + /** + * Add a note-off. This will cause a note to appear, if and only + * if there is a matching pending note-on. + */ + void addNoteOff(long frame, int pitch); + + /** + * Abandon all pending note-on events. + */ + void abandonNoteOns(); + + virtual void toXml(QTextStream &stream, QString indent = "", + QString extraAttributes = "") const; + + void setProperties(const QXmlAttributes &attributes); + + void setVerticalRangeToNoteRange(View *v); + +protected: + void getScaleExtents(View *, float &min, float &max, bool &log) const; + int getYForValue(View *v, float value) const; + float getValueForY(View *v, int y) const; + bool shouldConvertMIDIToHz() const; + + virtual int getDefaultColourHint(bool dark, bool &impose); + + FlexiNoteModel::PointList getLocalPoints(View *v, int) const; + + bool getPointToDrag(View *v, int x, int y, FlexiNoteModel::Point &) const; + bool getNoteToEdit(View *v, int x, int y, FlexiNoteModel::Point &) const; + void getRelativeMousePosition(View *v, FlexiNoteModel::Point ¬e, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const; + void updateNoteValue(View *v, FlexiNoteModel::Point ¬e) const; + + FlexiNoteModel *m_model; + bool m_editing; + bool m_intelligentActions; + int m_dragPointX; + int m_dragPointY; + int m_dragStartX; + int m_dragStartY; + FlexiNoteModel::Point m_originalPoint; + FlexiNoteModel::Point m_editingPoint; + long m_greatestLeftNeighbourFrame; + long m_smallestRightNeighbourFrame; + FlexiNoteModel::EditCommand *m_editingCommand; + VerticalScale m_verticalScale; + EditMode m_editMode; + + typedef std::set FlexiNoteSet; + FlexiNoteSet m_pendingNoteOns; + + mutable float m_scaleMinimum; + mutable float m_scaleMaximum; + + bool shouldAutoAlign() const; + + void finish(FlexiNoteModel::EditCommand *command) { + Command *c = command->finish(); + if (c) CommandHistory::getInstance()->addCommand(c, false); + } +}; + +#endif + diff -r 81c4c44ecc10 -r ad12e428785b layer/Layer.h --- a/layer/Layer.h Tue Dec 03 17:56:38 2013 +0000 +++ b/layer/Layer.h Tue Dec 03 17:58:40 2013 +0000 @@ -229,6 +229,10 @@ virtual void editDrag(View *, QMouseEvent *) { } virtual void editEnd(View *, QMouseEvent *) { } + virtual void splitStart(View *, QMouseEvent *) { } + virtual void splitEnd(View *, QMouseEvent *) { } + virtual void addNote(View *v, QMouseEvent *e) { }; + // Measurement rectangle (or equivalent). Unlike draw and edit, // the base Layer class can provide working implementations of // these for most situations. diff -r 81c4c44ecc10 -r ad12e428785b layer/LayerFactory.cpp --- a/layer/LayerFactory.cpp Tue Dec 03 17:56:38 2013 +0000 +++ b/layer/LayerFactory.cpp Tue Dec 03 17:58:40 2013 +0000 @@ -21,6 +21,7 @@ #include "TimeInstantLayer.h" #include "TimeValueLayer.h" #include "NoteLayer.h" +#include "FlexiNoteLayer.h" #include "RegionLayer.h" #include "TextLayer.h" #include "ImageLayer.h" @@ -36,6 +37,7 @@ #include "data/model/SparseOneDimensionalModel.h" #include "data/model/SparseTimeValueModel.h" #include "data/model/NoteModel.h" +#include "data/model/FlexiNoteModel.h" #include "data/model/RegionModel.h" #include "data/model/TextModel.h" #include "data/model/ImageModel.h" @@ -73,6 +75,7 @@ case TimeInstants: return Layer::tr("Time Instants"); case TimeValues: return Layer::tr("Time Values"); case Notes: return Layer::tr("Notes"); + case FlexiNotes: return Layer::tr("Flexible Notes"); case Regions: return Layer::tr("Regions"); case Text: return Layer::tr("Text"); case Image: return Layer::tr("Images"); @@ -161,6 +164,11 @@ types.insert(Notes); } + // NOTE: GF: types is a set, so order of insertion does not matter + if (dynamic_cast(model)) { + types.insert(FlexiNotes); + } + if (dynamic_cast(model)) { types.insert(Regions); } @@ -189,6 +197,7 @@ LayerTypeSet types; types.insert(TimeInstants); types.insert(TimeValues); + types.insert(FlexiNotes); types.insert(Notes); types.insert(Regions); types.insert(Text); @@ -205,6 +214,7 @@ if (dynamic_cast(layer)) return TimeRuler; if (dynamic_cast(layer)) return TimeInstants; if (dynamic_cast(layer)) return TimeValues; + if (dynamic_cast(layer)) return FlexiNotes; if (dynamic_cast(layer)) return Notes; if (dynamic_cast(layer)) return Regions; if (dynamic_cast(layer)) return Text; @@ -225,6 +235,7 @@ case TimeInstants: return "instants"; case TimeValues: return "values"; case Notes: return "notes"; + case FlexiNotes: return "flexible notes"; case Regions: return "regions"; case Text: return "text"; case Image: return "image"; @@ -247,6 +258,7 @@ case TimeInstants: return "timeinstants"; case TimeValues: return "timevalues"; case Notes: return "notes"; + case FlexiNotes: return "flexinotes"; case Regions: return "regions"; case Text: return "text"; case Image: return "image"; @@ -267,7 +279,7 @@ if (name == "timeruler") return TimeRuler; if (name == "timeinstants") return TimeInstants; if (name == "timevalues") return TimeValues; - if (name == "notes") return Notes; + if (name == "flexinotes") return FlexiNotes; if (name == "regions") return Regions; if (name == "text") return Text; if (name == "image") return Image; @@ -282,7 +294,7 @@ { // if (trySetModel(layer, model)) // return; - + if (trySetModel(layer, model)) return; @@ -301,9 +313,13 @@ if (trySetModel(layer, model)) return; - if (trySetModel(layer, model)) - return; + if (trySetModel(layer, model)) + return; + // GF: added FlexiNoteLayer + if (trySetModel(layer, model)) + return; + if (trySetModel(layer, model)) return; @@ -333,6 +349,8 @@ return new SparseOneDimensionalModel(baseModel->getSampleRate(), 1); } else if (layerType == TimeValues) { return new SparseTimeValueModel(baseModel->getSampleRate(), 1, true); + } else if (layerType == FlexiNotes) { + return new FlexiNoteModel(baseModel->getSampleRate(), 1, true); } else if (layerType == Notes) { return new NoteModel(baseModel->getSampleRate(), 1, true); } else if (layerType == Regions) { @@ -402,6 +420,10 @@ layer = new TimeValueLayer; break; + case FlexiNotes: + layer = new FlexiNoteLayer; + break; + case Notes: layer = new NoteLayer; break; diff -r 81c4c44ecc10 -r ad12e428785b layer/LayerFactory.h --- a/layer/LayerFactory.h Tue Dec 03 17:56:38 2013 +0000 +++ b/layer/LayerFactory.h Tue Dec 03 17:58:40 2013 +0000 @@ -35,6 +35,7 @@ TimeInstants, TimeValues, Notes, + FlexiNotes, Regions, Text, Image, diff -r 81c4c44ecc10 -r ad12e428785b layer/NoteLayer.cpp --- a/layer/NoteLayer.cpp Tue Dec 03 17:56:38 2013 +0000 +++ b/layer/NoteLayer.cpp Tue Dec 03 17:58:40 2013 +0000 @@ -52,12 +52,12 @@ m_scaleMinimum(0), m_scaleMaximum(0) { - + SVDEBUG << "constructed NoteLayer" << endl; } void NoteLayer::setModel(NoteModel *model) -{ +{ if (m_model == model) return; m_model = model; diff -r 81c4c44ecc10 -r ad12e428785b layer/layer.pro --- a/layer/layer.pro Tue Dec 03 17:56:38 2013 +0000 +++ b/layer/layer.pro Tue Dec 03 17:58:40 2013 +0000 @@ -22,6 +22,7 @@ Layer.h \ LayerFactory.h \ NoteLayer.h \ + FlexiNoteLayer.h \ PaintAssistant.h \ RegionLayer.h \ SingleColourLayer.h \ @@ -42,6 +43,7 @@ Layer.cpp \ LayerFactory.cpp \ NoteLayer.cpp \ + FlexiNoteLayer.cpp \ PaintAssistant.cpp \ RegionLayer.cpp \ SingleColourLayer.cpp \ diff -r 81c4c44ecc10 -r ad12e428785b svgui.pro --- a/svgui.pro Tue Dec 03 17:56:38 2013 +0000 +++ b/svgui.pro Tue Dec 03 17:58:40 2013 +0000 @@ -35,6 +35,7 @@ layer/Layer.h \ layer/LayerFactory.h \ layer/NoteLayer.h \ + layer/FlexiNoteLayer.h \ layer/PaintAssistant.h \ layer/PianoScale.h \ layer/RegionLayer.h \ @@ -56,6 +57,7 @@ layer/Layer.cpp \ layer/LayerFactory.cpp \ layer/NoteLayer.cpp \ + layer/FlexiNoteLayer.cpp \ layer/PaintAssistant.cpp \ layer/PianoScale.cpp \ layer/RegionLayer.cpp \ diff -r 81c4c44ecc10 -r ad12e428785b view/Pane.cpp --- a/view/Pane.cpp Tue Dec 03 17:56:38 2013 +0000 +++ b/view/Pane.cpp Tue Dec 03 17:58:40 2013 +0000 @@ -25,6 +25,11 @@ #include "base/Preferences.h" #include "layer/WaveformLayer.h" +// GF: added so we can propagate the mouse move event to the note layer for context handling. +#include "layer/LayerFactory.h" +#include "layer/FlexiNoteLayer.h" + + //!!! ugh #include "data/model/WaveFileModel.h" @@ -168,7 +173,7 @@ } m_reset = new NotifyingPushButton; - m_reset->setFlat(true); + m_reset->setFlat(true); m_reset->setCursor(Qt::ArrowCursor); m_reset->setFixedHeight(16); m_reset->setFixedWidth(16); @@ -331,16 +336,16 @@ if (m_manager && m_manager->getToolMode() == ViewManager::MeasureMode) { return false; } - + if (m_manager && !m_manager->shouldIlluminateLocalFeatures()) { return false; } if (layer == getSelectedLayer() && - !shouldIlluminateLocalSelection(discard, b0, b1)) { - - pos = m_identifyPoint; - return m_identifyFeatures; + !shouldIlluminateLocalSelection(discard, b0, b1)) { + + pos = m_identifyPoint; + return m_identifyFeatures; } return false; @@ -348,25 +353,25 @@ bool Pane::shouldIlluminateLocalSelection(QPoint &pos, - bool &closeToLeft, - bool &closeToRight) const + bool &closeToLeft, + bool &closeToRight) const { if (m_identifyFeatures && - m_manager && - m_manager->getToolMode() == ViewManager::EditMode && - !m_manager->getSelections().empty() && - !selectionIsBeingEdited()) { - - Selection s(getSelectionAt(m_identifyPoint.x(), - closeToLeft, closeToRight)); - - if (!s.isEmpty()) { - if (getSelectedLayer() && getSelectedLayer()->isLayerEditable()) { - - pos = m_identifyPoint; - return true; - } - } + m_manager && + m_manager->getToolMode() == ViewManager::EditMode && + !m_manager->getSelections().empty() && + !selectionIsBeingEdited()) { + + Selection s(getSelectionAt(m_identifyPoint.x(), + closeToLeft, closeToRight)); + + if (!s.isEmpty()) { + if (getSelectedLayer() && getSelectedLayer()->isLayerEditable()) { + + pos = m_identifyPoint; + return true; + } + } } return false; @@ -376,10 +381,10 @@ Pane::selectionIsBeingEdited() const { if (!m_editingSelection.isEmpty()) { - if (m_mousePos != m_clickPos && - getFrameForX(m_mousePos.x()) != getFrameForX(m_clickPos.x())) { - return true; - } + if (m_mousePos != m_clickPos && + getFrameForX(m_mousePos.x()) != getFrameForX(m_clickPos.x())) { + return true; + } } return false; } @@ -626,9 +631,9 @@ if (m_scaleWidth > 0 && r.left() < m_scaleWidth) { -// Profiler profiler("Pane::paintEvent - painting vertical scale", true); - -// SVDEBUG << "Pane::paintEvent: calling paint.save() in vertical scale block" << endl; +// Profiler profiler("Pane::paintEvent - painting vertical scale", true); + +// SVDEBUG << "Pane::paintEvent: calling paint.save() in vertical scale block" << endl; paint.save(); paint.setPen(getForeground()); @@ -649,7 +654,7 @@ { QPoint pos = m_identifyPoint; QString desc = topLayer->getFeatureDescription(this, pos); - + if (desc != "") { paint.save(); @@ -726,7 +731,7 @@ LayerList::iterator vi = m_layers.end(); if (vi != m_layers.begin()) { - + switch ((*--vi)->getPreferredFrameCountPosition()) { case Layer::PositionTop: @@ -893,7 +898,7 @@ } if (r.x() + r.width() >= llx - fontAscent - 3) { - + for (size_t i = 0; i < texts.size(); ++i) { // cerr << "Pane "<< this << ": text " << i << ": " << texts[i] << endl; @@ -1147,8 +1152,8 @@ long testFrame = getFrameForX(x - 5); if (testFrame < 0) { - testFrame = getFrameForX(x); - if (testFrame < 0) return Selection(); + testFrame = getFrameForX(x); + if (testFrame < 0) return Selection(); } Selection selection = m_manager->getContainingSelection(testFrame, true); @@ -1303,12 +1308,12 @@ (mode == ViewManager::MeasureMode && (e->buttons() & Qt::LeftButton) && m_shiftPressed)) { - if (mode != ViewManager::NavigateMode) { - setCursor(Qt::PointingHandCursor); - } - - m_navigating = true; - m_dragCentreFrame = m_centreFrame; + if (mode != ViewManager::NavigateMode) { + setCursor(Qt::PointingHandCursor); + } + + m_navigating = true; + m_dragCentreFrame = m_centreFrame; m_dragStartMinValue = 0; float vmin, vmax, dmin, dmax; @@ -1320,61 +1325,70 @@ if (!hasTopLayerTimeXAxis()) return; - bool closeToLeft = false, closeToRight = false; - Selection selection = getSelectionAt(e->x(), closeToLeft, closeToRight); - - if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) { - - m_manager->removeSelection(selection); - - if (closeToLeft) { - m_selectionStartFrame = selection.getEndFrame(); - } else { - m_selectionStartFrame = selection.getStartFrame(); - } - - m_manager->setInProgressSelection(selection, false); - m_resizing = true; - - } else { - - int mouseFrame = getFrameForX(e->x()); - size_t resolution = 1; - int snapFrame = mouseFrame; - - Layer *layer = getSelectedLayer(); - if (layer && !m_shiftPressed) { - layer->snapToFeatureFrame(this, snapFrame, - resolution, Layer::SnapLeft); - } - - if (snapFrame < 0) snapFrame = 0; - m_selectionStartFrame = snapFrame; - if (m_manager) { - m_manager->setInProgressSelection + bool closeToLeft = false, closeToRight = false; + Selection selection = getSelectionAt(e->x(), closeToLeft, closeToRight); + + if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) { + + m_manager->removeSelection(selection); + + if (closeToLeft) { + m_selectionStartFrame = selection.getEndFrame(); + } else { + m_selectionStartFrame = selection.getStartFrame(); + } + + m_manager->setInProgressSelection(selection, false); + m_resizing = true; + + } else { + + int mouseFrame = getFrameForX(e->x()); + size_t resolution = 1; + int snapFrame = mouseFrame; + + Layer *layer = getSelectedLayer(); + if (layer && !m_shiftPressed) { + layer->snapToFeatureFrame(this, snapFrame, + resolution, Layer::SnapLeft); + } + + if (snapFrame < 0) snapFrame = 0; + m_selectionStartFrame = snapFrame; + if (m_manager) { + m_manager->setInProgressSelection (Selection(alignToReference(snapFrame), alignToReference(snapFrame + resolution)), !m_ctrlPressed); - } - - m_resizing = false; - } - - update(); + } + + m_resizing = false; + } + + update(); } else if (mode == ViewManager::DrawMode) { - Layer *layer = getSelectedLayer(); - if (layer && layer->isLayerEditable()) { - layer->drawStart(this, e); - } + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + layer->drawStart(this, e); + } } else if (mode == ViewManager::EraseMode) { - Layer *layer = getSelectedLayer(); - if (layer && layer->isLayerEditable()) { - layer->eraseStart(this, e); - } + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + layer->eraseStart(this, e); + } + + // GF: handle mouse press for NoteEditMode + } else if (mode == ViewManager::NoteEditMode) { + + std::cerr << "mouse pressed in note edit mode" << std::endl; + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + layer->splitStart(this, e); + } } else if (mode == ViewManager::EditMode) { @@ -1406,28 +1420,28 @@ m_releasing = true; if (m_clickedInRange) { - mouseMoveEvent(e); + mouseMoveEvent(e); } if (m_navigating || mode == ViewManager::NavigateMode) { - m_navigating = false; - - if (mode != ViewManager::NavigateMode) { - // restore cursor - toolModeChanged(); - } - - if (m_shiftPressed) { - - int x0 = std::min(m_clickPos.x(), m_mousePos.x()); - int x1 = std::max(m_clickPos.x(), m_mousePos.x()); - - int y0 = std::min(m_clickPos.y(), m_mousePos.y()); - int y1 = std::max(m_clickPos.y(), m_mousePos.y()); + m_navigating = false; + + if (mode != ViewManager::NavigateMode) { + // restore cursor + toolModeChanged(); + } + + if (m_shiftPressed) { + + int x0 = std::min(m_clickPos.x(), m_mousePos.x()); + int x1 = std::max(m_clickPos.x(), m_mousePos.x()); + + int y0 = std::min(m_clickPos.y(), m_mousePos.y()); + int y1 = std::max(m_clickPos.y(), m_mousePos.y()); zoomToRegion(x0, y0, x1, y1); - } + } } else if (mode == ViewManager::SelectMode) { @@ -1436,43 +1450,49 @@ return; } - if (m_manager && m_manager->haveInProgressSelection()) { - - bool exclusive; - Selection selection = m_manager->getInProgressSelection(exclusive); - - if (selection.getEndFrame() < selection.getStartFrame() + 2) { - selection = Selection(); - } - - m_manager->clearInProgressSelection(); - - if (exclusive) { - m_manager->setSelection(selection); - } else { - m_manager->addSelection(selection); - } - } - - update(); + if (m_manager && m_manager->haveInProgressSelection()) { + + bool exclusive; + Selection selection = m_manager->getInProgressSelection(exclusive); + + if (selection.getEndFrame() < selection.getStartFrame() + 2) { + selection = Selection(); + } + + m_manager->clearInProgressSelection(); + + if (exclusive) { + m_manager->setSelection(selection); + } else { + m_manager->addSelection(selection); + } + } + + update(); } else if (mode == ViewManager::DrawMode) { - Layer *layer = getSelectedLayer(); - if (layer && layer->isLayerEditable()) { - layer->drawEnd(this, e); - update(); - } + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + layer->drawEnd(this, e); + update(); + } } else if (mode == ViewManager::EraseMode) { - Layer *layer = getSelectedLayer(); - if (layer && layer->isLayerEditable()) { - layer->eraseEnd(this, e); - update(); - } - - } else if (mode == ViewManager::EditMode) { + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + layer->eraseEnd(this, e); + update(); + } + + } else if (mode == ViewManager::NoteEditMode) { + + //GF: handle mouse release for NoteEditMode (note: works but will need to re-think this a bit later) + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + layer->splitEnd(this, e); + update(); } if (m_editing) { if (!editSelectionEnd(e)) { @@ -1482,7 +1502,20 @@ update(); } } - } + } + + } else if (mode == ViewManager::EditMode) { + + // GF: edited this previously, but restored to original state + if (m_editing) { + if (!editSelectionEnd(e)) { + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + layer->editEnd(this, e); + update(); + } + } + } } else if (mode == ViewManager::MeasureMode) { @@ -1531,16 +1564,24 @@ m_identifyPoint = e->pos(); if (!m_clickedInRange) { - - if (mode == ViewManager::SelectMode && hasTopLayerTimeXAxis()) { - bool closeToLeft = false, closeToRight = false; - getSelectionAt(e->x(), closeToLeft, closeToRight); - if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) { - setCursor(Qt::SizeHorCursor); - } else { - setCursor(Qt::ArrowCursor); - } - } + + // GF: handle mouse move for context sensitive cursor switching in NoteEditMode. + // GF: Propagate the event to FlexiNoteLayer. I somehow feel it's best handeled there rather than here, but perhaps not if this will be needed elsewhere too. + if (mode == ViewManager::NoteEditMode && LayerFactory::getInstance()->getLayerType(getTopLayer()) == LayerFactory::FlexiNotes) { + + dynamic_cast(getTopLayer())->mouseMoveEvent(this, e); + + } + + if (mode == ViewManager::SelectMode && hasTopLayerTimeXAxis()) { + bool closeToLeft = false, closeToRight = false; + getSelectionAt(e->x(), closeToLeft, closeToRight); + if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) { + setCursor(Qt::SizeHorCursor); + } else { + setCursor(Qt::ArrowCursor); + } + } if (!m_manager->isPlaying()) { @@ -1570,40 +1611,105 @@ } } - return; + return; } if (m_navigating || mode == ViewManager::NavigateMode) { - if (m_shiftPressed) { - - m_mousePos = e->pos(); - update(); - - } else { + if (m_shiftPressed) { + + m_mousePos = e->pos(); + update(); + + } else { dragTopLayer(e); } } else if (mode == ViewManager::SelectMode) { - if (!hasTopLayerTimeXAxis()) return; - - dragExtendSelection(e); + if (!hasTopLayerTimeXAxis()) return; + + dragExtendSelection(e); } else if (mode == ViewManager::DrawMode) { - Layer *layer = getSelectedLayer(); - if (layer && layer->isLayerEditable()) { - layer->drawDrag(this, e); - } + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + layer->drawDrag(this, e); + } } else if (mode == ViewManager::EraseMode) { - Layer *layer = getSelectedLayer(); - if (layer && layer->isLayerEditable()) { - layer->eraseDrag(this, e); - } + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + layer->eraseDrag(this, e); + } + + // GF: handling NoteEditMode dragging and boundary actions for mouseMoveEvent + } else if (mode == ViewManager::NoteEditMode) { + + bool resist = true; + + if ((e->modifiers() & Qt::ShiftModifier)) { + m_shiftPressed = true; + } + + if (m_shiftPressed) resist = false; + + m_dragMode = updateDragMode + (m_dragMode, + m_clickPos, + e->pos(), + true, // can move horiz + true, // can move vert + resist, // resist horiz + resist); // resist vert + + if (!m_editing) { + + if (m_dragMode != UnresolvedDrag) { + + m_editing = true; + + QMouseEvent clickEvent(QEvent::MouseButtonPress, + m_clickPos, + Qt::NoButton, + e->buttons(), + e->modifiers()); + + if (!editSelectionStart(&clickEvent)) { + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + std::cerr << "calling edit start" << std::endl; + layer->editStart(this, &clickEvent); + } + } + } + + } else { + + if (!editSelectionDrag(e)) { + + Layer *layer = getSelectedLayer(); + + if (layer && layer->isLayerEditable()) { + + int x = e->x(); + int y = e->y(); + if (m_dragMode == VerticalDrag) x = m_clickPos.x(); + else if (m_dragMode == HorizontalDrag) y = m_clickPos.y(); + + QMouseEvent moveEvent(QEvent::MouseMove, + QPoint(x, y), + Qt::NoButton, + e->buttons(), + e->modifiers()); + std::cerr << "calling editDrag" << std::endl; + layer->editDrag(this, &moveEvent); + } + } + } } else if (mode == ViewManager::EditMode) { @@ -1689,18 +1795,18 @@ Pane::zoomToRegion(int x0, int y0, int x1, int y1) { int w = x1 - x0; - + long newStartFrame = getFrameForX(x0); - + long visibleFrames = getEndFrame() - getStartFrame(); if (newStartFrame <= -visibleFrames) { newStartFrame = -visibleFrames + 1; } - + if (newStartFrame >= long(getModelsEndFrame())) { newStartFrame = getModelsEndFrame() - 1; } - + float ratio = float(w) / float(width()); // cerr << "ratio: " << ratio << endl; size_t newZoomLevel = (size_t)nearbyint(m_zoomLevel * ratio); @@ -1785,7 +1891,7 @@ long frameOff = getFrameForX(e->x()) - getFrameForX(m_clickPos.x()); size_t newCentreFrame = m_dragCentreFrame; - + if (frameOff < 0) { newCentreFrame -= frameOff; } else if (newCentreFrame >= size_t(frameOff)) { @@ -1794,7 +1900,7 @@ newCentreFrame = 0; } -#ifdef DEBUG_PANE +#ifdef DEBUG_PANE SVDEBUG << "Pane::dragTopLayer: newCentreFrame = " << newCentreFrame << ", models end frame = " << getModelsEndFrame() << endl; #endif @@ -1913,7 +2019,7 @@ size_t resolution = 1; int snapFrameLeft = mouseFrame; int snapFrameRight = mouseFrame; - + Layer *layer = getSelectedLayer(); if (layer && !m_shiftPressed) { layer->snapToFeatureFrame(this, snapFrameLeft, @@ -1926,9 +2032,9 @@ if (snapFrameLeft < 0) snapFrameLeft = 0; if (snapFrameRight < 0) snapFrameRight = 0; - + size_t min, max; - + if (m_selectionStartFrame > size_t(snapFrameLeft)) { min = snapFrameLeft; max = m_selectionStartFrame; @@ -2002,10 +2108,10 @@ if (mode == ViewManager::NavigateMode || mode == ViewManager::EditMode) { - Layer *layer = getSelectedLayer(); - if (layer && layer->isLayerEditable()) { - if (layer->editOpen(this, e)) relocate = false; - } + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + if (layer->editOpen(this, e)) relocate = false; + } } else if (mode == ViewManager::MeasureMode) { @@ -2029,6 +2135,14 @@ m_dragStartMinValue = dmin; } } + + if (mode == ViewManager::NoteEditMode) { + std::cerr << "double click in note edit mode" << std::endl; + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + layer->addNote(this, e); + } + } m_clickedInRange = false; // in case mouseReleaseEvent is not properly called } @@ -2063,31 +2177,31 @@ int count = e->delta(); if (count > 0) { - if (count >= 120) count /= 120; - else count = 1; + if (count >= 120) count /= 120; + else count = 1; } if (count < 0) { - if (count <= -120) count /= 120; - else count = -1; + if (count <= -120) count /= 120; + else count = -1; } if (e->modifiers() & Qt::ControlModifier) { - // Scroll left or right, rapidly - - if (getStartFrame() < 0 && - getEndFrame() >= getModelsEndFrame()) return; - - long delta = ((width() / 2) * count * m_zoomLevel); - - if (int(m_centreFrame) < delta) { - setCentreFrame(0); - } else if (int(m_centreFrame) - delta >= int(getModelsEndFrame())) { - setCentreFrame(getModelsEndFrame()); - } else { - setCentreFrame(m_centreFrame - delta); - } + // Scroll left or right, rapidly + + if (getStartFrame() < 0 && + getEndFrame() >= getModelsEndFrame()) return; + + long delta = ((width() / 2) * count * m_zoomLevel); + + if (int(m_centreFrame) < delta) { + setCentreFrame(0); + } else if (int(m_centreFrame) - delta >= int(getModelsEndFrame())) { + setCentreFrame(getModelsEndFrame()); + } else { + setCentreFrame(m_centreFrame - delta); + } } else if (e->modifiers() & Qt::ShiftModifier) { @@ -2107,29 +2221,29 @@ } else { - // Zoom in or out - - int newZoomLevel = m_zoomLevel; + // Zoom in or out + + int newZoomLevel = m_zoomLevel; - while (count > 0) { - if (newZoomLevel <= 2) { - newZoomLevel = 1; - break; - } - newZoomLevel = getZoomConstraintBlockSize(newZoomLevel - 1, - ZoomConstraint::RoundDown); - --count; - } - - while (count < 0) { - newZoomLevel = getZoomConstraintBlockSize(newZoomLevel + 1, - ZoomConstraint::RoundUp); - ++count; - } - - if (newZoomLevel != m_zoomLevel) { - setZoomLevel(newZoomLevel); - } + while (count > 0) { + if (newZoomLevel <= 2) { + newZoomLevel = 1; + break; + } + newZoomLevel = getZoomConstraintBlockSize(newZoomLevel - 1, + ZoomConstraint::RoundDown); + --count; + } + + while (count < 0) { + newZoomLevel = getZoomConstraintBlockSize(newZoomLevel + 1, + ZoomConstraint::RoundUp); + ++count; + } + + if (newZoomLevel != m_zoomLevel) { + setZoomLevel(newZoomLevel); + } } emit paneInteractedWith(); @@ -2307,9 +2421,9 @@ Pane::editSelectionStart(QMouseEvent *e) { if (!m_identifyFeatures || - !m_manager || - m_manager->getToolMode() != ViewManager::EditMode) { - return false; + !m_manager || + m_manager->getToolMode() != ViewManager::EditMode) { + return false; } bool closeToLeft, closeToRight; @@ -2339,8 +2453,8 @@ Layer *layer = getSelectedLayer(); if (offset == 0 || !layer) { - m_editingSelection = Selection(); - return true; + m_editingSelection = Selection(); + return true; } int p0 = getXForFrame(m_editingSelection.getStartFrame()) + offset; @@ -2352,25 +2466,25 @@ Selection newSelection(f0, f1); if (m_editingSelectionEdge == 0) { - + CommandHistory::getInstance()->startCompoundOperation (tr("Drag Selection"), true); - layer->moveSelection(m_editingSelection, f0); - + layer->moveSelection(m_editingSelection, f0); + } else { - + CommandHistory::getInstance()->startCompoundOperation (tr("Resize Selection"), true); - if (m_editingSelectionEdge < 0) { - f1 = m_editingSelection.getEndFrame(); - } else { - f0 = m_editingSelection.getStartFrame(); - } - - newSelection = Selection(f0, f1); - layer->resizeSelection(m_editingSelection, newSelection); + if (m_editingSelectionEdge < 0) { + f1 = m_editingSelection.getEndFrame(); + } else { + f0 = m_editingSelection.getStartFrame(); + } + + newSelection = Selection(f0, f1); + layer->resizeSelection(m_editingSelection, newSelection); } m_manager->removeSelection(m_editingSelection); @@ -2400,33 +2514,38 @@ switch (mode) { case ViewManager::NavigateMode: - setCursor(Qt::PointingHandCursor); - break; - + setCursor(Qt::PointingHandCursor); + break; + case ViewManager::SelectMode: - setCursor(Qt::ArrowCursor); - break; - + setCursor(Qt::ArrowCursor); + break; + case ViewManager::EditMode: - setCursor(Qt::UpArrowCursor); - break; - + setCursor(Qt::UpArrowCursor); + break; + case ViewManager::DrawMode: - setCursor(Qt::CrossCursor); - break; - + setCursor(Qt::CrossCursor); + break; + case ViewManager::EraseMode: - setCursor(Qt::CrossCursor); - break; + setCursor(Qt::CrossCursor); + break; case ViewManager::MeasureMode: if (m_measureCursor1) setCursor(*m_measureCursor1); - break; - -/* + break; + + // GF: NoteEditMode uses the same default cursor as EditMode, but it will change in a context sensitive manner. + case ViewManager::NoteEditMode: + setCursor(Qt::UpArrowCursor); + break; + +/* case ViewManager::TextMode: - setCursor(Qt::IBeamCursor); - break; + setCursor(Qt::IBeamCursor); + break; */ } } @@ -2562,21 +2681,21 @@ } else if (mode == ViewManager::DrawMode) { //!!! could call through to a layer function to find out exact meaning - if (editable) { + if (editable) { help = tr("Click to add a new item in the active layer"); } } else if (mode == ViewManager::EraseMode) { //!!! could call through to a layer function to find out exact meaning - if (editable) { + if (editable) { help = tr("Click to erase an item from the active layer"); } } else if (mode == ViewManager::EditMode) { //!!! could call through to layer - if (editable) { + if (editable) { help = tr("Click and drag an item in the active layer to move it; hold Shift to override initial resistance"); if (pos) { bool closeToLeft = false, closeToRight = false; @@ -2620,8 +2739,8 @@ { View::toXml (stream, indent, - QString("type=\"pane\" centreLineVisible=\"%1\" height=\"%2\" %3") - .arg(m_centreLineVisible).arg(height()).arg(extraAttributes)); + QString("type=\"pane\" centreLineVisible=\"%1\" height=\"%2\" %3") + .arg(m_centreLineVisible).arg(height()).arg(extraAttributes)); } diff -r 81c4c44ecc10 -r ad12e428785b view/PaneStack.cpp --- a/view/PaneStack.cpp Tue Dec 03 17:56:38 2013 +0000 +++ b/view/PaneStack.cpp Tue Dec 03 17:58:40 2013 +0000 @@ -580,24 +580,44 @@ int count = sizes.size(); - int total = 0; + int fixed = 0, variable = 0, total = 0; + int varicount = 0; + for (int i = 0; i < count; ++i) { total += sizes[i]; } + variable = total; + + for (int i = 0; i < count; ++i) { + int minh = m_panes[i].pane->minimumSize().height(); + if (minh == m_panes[i].pane->maximumSize().height()) { + fixed += minh; + variable -= minh; + } else { + varicount++; + } + } + if (total == 0) return; sizes.clear(); - int each = total / count; + int each = (varicount > 0 ? (variable / varicount) : 0); int remaining = total; for (int i = 0; i < count; ++i) { if (i == count - 1) { sizes.push_back(remaining); } else { - sizes.push_back(each); - remaining -= each; + int minh = m_panes[i].pane->minimumSize().height(); + if (minh == m_panes[i].pane->maximumSize().height()) { + sizes.push_back(minh); + remaining -= minh; + } else { + sizes.push_back(each); + remaining -= each; + } } } @@ -612,4 +632,3 @@ m_splitter->setSizes(sizes); } - diff -r 81c4c44ecc10 -r ad12e428785b view/ViewManager.cpp --- a/view/ViewManager.cpp Tue Dec 03 17:56:38 2013 +0000 +++ b/view/ViewManager.cpp Tue Dec 03 17:58:40 2013 +0000 @@ -360,6 +360,7 @@ case DrawMode: emit activity(tr("Enter Draw mode")); break; case EraseMode: emit activity(tr("Enter Erase mode")); break; case MeasureMode: emit activity(tr("Enter Measure mode")); break; + case NoteEditMode: emit activity(tr("Enter NoteEdit mode")); break; // GF: NoteEditMode activity (I'm not yet certain why we need to emit this.) }; } diff -r 81c4c44ecc10 -r ad12e428785b view/ViewManager.h --- a/view/ViewManager.h Tue Dec 03 17:56:38 2013 +0000 +++ b/view/ViewManager.h Tue Dec 03 17:58:40 2013 +0000 @@ -98,10 +98,11 @@ enum ToolMode { NavigateMode, SelectMode, - EditMode, + EditMode, DrawMode, EraseMode, - MeasureMode + MeasureMode, + NoteEditMode //GF: Tonioni: this tool mode will be context sensitive. }; ToolMode getToolMode() const { return m_toolMode; } void setToolMode(ToolMode mode);