# HG changeset patch # User Chris Cannam # Date 1429517935 -3600 # Node ID bb80983c9e6187288f1ffe3fc13f4ee04aa7507a # Parent e39d5d2734eda8ab634b385e845ac0a0e4529725# Parent 78c152e4db958ac5a36e0fba3102cb6fdc52dd62 Merge from default branch diff -r e39d5d2734ed -r bb80983c9e61 configure --- a/configure Wed Mar 18 15:10:36 2015 +0000 +++ b/configure Mon Apr 20 09:18:55 2015 +0100 @@ -4343,8 +4343,8 @@ CXXFLAGS_MINIMAL="$AUTOCONF_CXXFLAGS" if test "x$GCC" = "xyes"; then - CXXFLAGS_ANY="-Wall -Wextra -Werror -Woverloaded-virtual -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -Wconversion -pipe" - CXXFLAGS_DEBUG="$CXXFLAGS_ANY -g" + CXXFLAGS_ANY="-Wall -Wextra -Woverloaded-virtual -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -Wconversion -pipe" + CXXFLAGS_DEBUG="$CXXFLAGS_ANY -Werror -g" CXXFLAGS_RELEASE="$CXXFLAGS_ANY -g0 -O2" CXXFLAGS_MINIMAL="$CXXFLAGS_ANY -g0 -O0" fi diff -r e39d5d2734ed -r bb80983c9e61 configure.ac --- a/configure.ac Wed Mar 18 15:10:36 2015 +0000 +++ b/configure.ac Mon Apr 20 09:18:55 2015 +0100 @@ -53,8 +53,8 @@ CXXFLAGS_MINIMAL="$AUTOCONF_CXXFLAGS" if test "x$GCC" = "xyes"; then - CXXFLAGS_ANY="-Wall -Wextra -Werror -Woverloaded-virtual -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -Wconversion -pipe" - CXXFLAGS_DEBUG="$CXXFLAGS_ANY -g" + CXXFLAGS_ANY="-Wall -Wextra -Woverloaded-virtual -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -Wconversion -pipe" + CXXFLAGS_DEBUG="$CXXFLAGS_ANY -Werror -g" CXXFLAGS_RELEASE="$CXXFLAGS_ANY -g0 -O2" CXXFLAGS_MINIMAL="$CXXFLAGS_ANY -g0 -O0" fi diff -r e39d5d2734ed -r bb80983c9e61 layer/FlexiNoteLayer.cpp --- a/layer/FlexiNoteLayer.cpp Wed Mar 18 15:10:36 2015 +0000 +++ b/layer/FlexiNoteLayer.cpp Mon Apr 20 09:18:55 2015 +0100 @@ -1135,7 +1135,8 @@ m_editingCommand->deletePoint(m_editingPoint); - std::cerr << "edit mode: " << m_editMode << std::endl; + std::cerr << "edit mode: " << m_editMode << " intelligent actions = " + << m_intelligentActions << std::endl; switch (m_editMode) { case LeftBoundary : { @@ -1164,16 +1165,28 @@ dragFrame = m_smallestRightNeighbourFrame - m_originalPoint.duration; } m_editingPoint.frame = dragFrame; + m_editingPoint.value = float(value); + + // Re-analyse region within +/- 1 semitone of the dragged value + float cents = 0; + int midiPitch = Pitch::getPitchForFrequency(m_editingPoint.value, ¢s); + double lower = Pitch::getFrequencyForPitch(midiPitch - 1, cents); + double higher = Pitch::getFrequencyForPitch(midiPitch + 1, cents); + + emit reAnalyseRegion(m_editingPoint.frame, + m_editingPoint.frame + m_editingPoint.duration, + float(lower), float(higher)); break; } case SplitNote: // nothing break; } - updateNoteValue(v, m_editingPoint); + +// updateNoteValueFromPitchCurve(v, m_editingPoint); m_editingCommand->addPoint(m_editingPoint); + std::cerr << "added new point(" << m_editingPoint.frame << "," << m_editingPoint.duration << ")" << std::endl; - } void @@ -1188,6 +1201,15 @@ QString newName = m_editingCommand->getName(); + if (m_editMode == DragNote) { + //!!! command nesting is wrong? + emit materialiseReAnalysis(); + } + + m_editingCommand->deletePoint(m_editingPoint); + updateNoteValueFromPitchCurve(v, m_editingPoint); + m_editingCommand->addPoint(m_editingPoint); + if (m_editingPoint.frame != m_originalPoint.frame) { if (m_editingPoint.value != m_originalPoint.value) { newName = tr("Edit Point"); @@ -1210,7 +1232,7 @@ FlexiNoteLayer::splitStart(LayerGeometryProvider *v, QMouseEvent *e) { // GF: note splitting starts (!! remove printing soon) - std::cerr << "splitStart" << std::endl; + std::cerr << "splitStart (n.b. editStart will be called later, if the user drags the mouse)" << std::endl; if (!m_model) return; if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return; @@ -1227,7 +1249,6 @@ m_editing = true; m_dragStartX = e->x(); m_dragStartY = e->y(); - } void @@ -1280,10 +1301,10 @@ note.level, note.label); if (m_intelligentActions) { - if (updateNoteValue(v, newNote1)) { + if (updateNoteValueFromPitchCurve(v, newNote1)) { command->addPoint(newNote1); } - if (updateNoteValue(v, newNote2)) { + if (updateNoteValueFromPitchCurve(v, newNote2)) { command->addPoint(newNote2); } } else { @@ -1339,22 +1360,23 @@ { // Better than we used to do, but still not very satisfactory - cerr << "FlexiNoteLayer::getAssociatedPitchModel()" << endl; +// cerr << "FlexiNoteLayer::getAssociatedPitchModel()" << endl; for (int i = 0; i < v->getView()->getLayerCount(); ++i) { Layer *layer = v->getView()->getLayer(i); if (layer && layer->getLayerPresentationName() != "candidate") { - cerr << "FlexiNoteLayer::getAssociatedPitchModel: looks like our layer is " << layer << endl; +// cerr << "FlexiNoteLayer::getAssociatedPitchModel: looks like our layer is " << layer << endl; SparseTimeValueModel *model = qobject_cast (layer->getModel()); - cerr << "FlexiNoteLayer::getAssociatedPitchModel: and its model is " << model << endl; +// cerr << "FlexiNoteLayer::getAssociatedPitchModel: and its model is " << model << endl; if (model && model->getScaleUnits() == "Hz") { cerr << "FlexiNoteLayer::getAssociatedPitchModel: it's good, returning " << model << endl; return model; } } } + cerr << "FlexiNoteLayer::getAssociatedPitchModel: failed to find a model" << endl; return 0; } @@ -1388,7 +1410,7 @@ command->deletePoint(note); - if (updateNoteValue(v, newNote)) { + if (updateNoteValueFromPitchCurve(v, newNote)) { command->addPoint(newNote); } } @@ -1434,13 +1456,13 @@ ++i; } - updateNoteValue(v, newNote); + updateNoteValueFromPitchCurve(v, newNote); command->addPoint(newNote); finish(command); } bool -FlexiNoteLayer::updateNoteValue(LayerGeometryProvider *v, FlexiNoteModel::Point ¬e) const +FlexiNoteLayer::updateNoteValueFromPitchCurve(LayerGeometryProvider *v, FlexiNoteModel::Point ¬e) const { SparseTimeValueModel *model = getAssociatedPitchModel(v); if (!model) return false; @@ -1475,6 +1497,8 @@ } else { median = pitchValues[size/2]; } + + std::cerr << "updateNoteValueFromPitchCurve: corrected from " << note.value << " to median " << median << std::endl; note.value = float(median); @@ -1492,21 +1516,31 @@ 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->getView()->setCursor(Qt::SizeVerCursor); + bool closeToLeft = false, closeToRight = false, + closeToTop = false, closeToBottom = false; + getRelativeMousePosition(v, note, e->x(), e->y(), + closeToLeft, closeToRight, + closeToTop, closeToBottom); - if (closeToLeft) { v->getView()->setCursor(Qt::SizeHorCursor); m_editMode = LeftBoundary; return; } - if (closeToRight) { v->getView()->setCursor(Qt::SizeHorCursor); m_editMode = RightBoundary; return; } - if (closeToTop) { v->getView()->setCursor(Qt::CrossCursor); m_editMode = DragNote; return; } - if (closeToBottom) { v->getView()->setCursor(Qt::UpArrowCursor); m_editMode = SplitNote; return; } - - v->getView()->setCursor(Qt::ArrowCursor); - - std::cerr << "Mouse moved in edit mode over FlexiNoteLayer" << std::endl; - // v->getView()->setCursor(Qt::SizeHorCursor); - + if (closeToLeft) { + v->getView()->setCursor(Qt::SizeHorCursor); + m_editMode = LeftBoundary; + cerr << "edit mode -> LeftBoundary" << endl; + } else if (closeToRight) { + v->getView()->setCursor(Qt::SizeHorCursor); + m_editMode = RightBoundary; + cerr << "edit mode -> RightBoundary" << endl; + } else if (closeToTop) { + v->getView()->setCursor(Qt::CrossCursor); + m_editMode = DragNote; + cerr << "edit mode -> DragNote" << endl; + } else if (closeToBottom) { + v->getView()->setCursor(Qt::UpArrowCursor); + m_editMode = SplitNote; + cerr << "edit mode -> SplitNote" << endl; + } else { + v->getView()->setCursor(Qt::ArrowCursor); + } } void diff -r e39d5d2734ed -r bb80983c9e61 layer/FlexiNoteLayer.h --- a/layer/FlexiNoteLayer.h Wed Mar 18 15:10:36 2015 +0000 +++ b/layer/FlexiNoteLayer.h Mon Apr 20 09:18:55 2015 +0100 @@ -163,6 +163,10 @@ virtual double getValueForY(LayerGeometryProvider *v, int y) const; virtual QString getScaleUnits() const; +signals: + void reAnalyseRegion(sv_frame_t, sv_frame_t, float, float); + void materialiseReAnalysis(); + protected: void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const; bool shouldConvertMIDIToHz() const; @@ -175,7 +179,7 @@ bool getNoteToEdit(LayerGeometryProvider *v, int x, int y, FlexiNoteModel::Point &) const; void getRelativeMousePosition(LayerGeometryProvider *v, FlexiNoteModel::Point ¬e, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const; SparseTimeValueModel *getAssociatedPitchModel(LayerGeometryProvider *v) const; - bool updateNoteValue(LayerGeometryProvider *v, FlexiNoteModel::Point ¬e) const; + bool updateNoteValueFromPitchCurve(LayerGeometryProvider *v, FlexiNoteModel::Point ¬e) const; void splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame, QMouseEvent *e); FlexiNoteModel *m_model; diff -r e39d5d2734ed -r bb80983c9e61 svgui.pro --- a/svgui.pro Wed Mar 18 15:10:36 2015 +0000 +++ b/svgui.pro Mon Apr 20 09:18:55 2015 +0100 @@ -117,6 +117,8 @@ widgets/LayerTree.h \ widgets/LayerTreeDialog.h \ widgets/LEDButton.h \ + widgets/LevelPanToolButton.h \ + widgets/LevelPanWidget.h \ widgets/ListInputDialog.h \ widgets/MIDIFileImportDialog.h \ widgets/ModelDataTableDialog.h \ @@ -155,6 +157,8 @@ widgets/LayerTree.cpp \ widgets/LayerTreeDialog.cpp \ widgets/LEDButton.cpp \ + widgets/LevelPanToolButton.cpp \ + widgets/LevelPanWidget.cpp \ widgets/ListInputDialog.cpp \ widgets/MIDIFileImportDialog.cpp \ widgets/ModelDataTableDialog.cpp \ diff -r e39d5d2734ed -r bb80983c9e61 view/Overview.cpp --- a/view/Overview.cpp Wed Mar 18 15:10:36 2015 +0000 +++ b/view/Overview.cpp Mon Apr 20 09:18:55 2015 +0100 @@ -144,6 +144,20 @@ if (changed) update(); } +QColor +Overview::getFillWithin() const +{ + return Qt::transparent; +} + +QColor +Overview::getFillWithout() const +{ + QColor c = palette().window().color(); + c.setAlpha(100); + return c; +} + void Overview::paintEvent(QPaintEvent *e) { @@ -184,21 +198,23 @@ QPainter paint; paint.begin(this); - + paint.setClipRegion(e->region()); + paint.setRenderHints(QPainter::Antialiasing); + QRect r(rect()); - if (e) { - r = e->rect(); - paint.setClipRect(r); - } + // We paint a rounded rect for each distinct set of view extents, + // and we colour in the inside and outside of the rect that + // corresponds to the current view. (One small caveat -- we don't + // know which rect that is yet. We'll have to figure it out + // somehow...) - paint.setPen(getForeground()); + std::set > extents; + std::vector rects; + QRect primary; int y = 0; - sv_frame_t prevx0 = -10; - sv_frame_t prevx1 = -10; - for (ViewSet::iterator i = m_views.begin(); i != m_views.end(); ++i) { if (!*i) continue; @@ -219,15 +235,36 @@ int x0 = getXForFrame(f0); int x1 = getXForFrame(f1); - if (x0 != prevx0 || x1 != prevx1) { - y += height() / 10 + 1; - prevx0 = x0; - prevx1 = x1; - } if (x1 <= x0) x1 = x0 + 1; - - paint.drawRect(x0, y, x1 - x0, height() - 2 * y); + + std::pair extent(x0, x1); + + if (extents.find(extent) == extents.end()) { + + y += height() / 10 + 1; + extents.insert(extent); + + QRect vr(x0, y, x1 - x0, height() - 2 * y); + rects.push_back(vr); + primary = vr; //!!! for now + } + } + + QPainterPath without; + without.addRoundedRect(primary, 4, 4); + without.addRect(rect()); + paint.setPen(Qt::NoPen); + paint.setBrush(getFillWithout()); + paint.drawPath(without); + + paint.setBrush(getFillWithin()); + paint.drawRoundedRect(primary, 4, 4); + + foreach (QRect vr, rects) { + paint.setBrush(Qt::NoBrush); + paint.setPen(QPen(Qt::gray, 2)); + paint.drawRoundedRect(vr, 4, 4); } paint.end(); diff -r e39d5d2734ed -r bb80983c9e61 view/Overview.h --- a/view/Overview.h Wed Mar 18 15:10:36 2015 +0000 +++ b/view/Overview.h Mon Apr 20 09:18:55 2015 +0100 @@ -59,6 +59,9 @@ virtual void leaveEvent(QEvent *); virtual bool shouldLabelSelections() const { return false; } + QColor getFillWithin() const; + QColor getFillWithout() const; + QPoint m_clickPos; QPoint m_mousePos; bool m_clickedInRange; diff -r e39d5d2734ed -r bb80983c9e61 view/Pane.cpp --- a/view/Pane.cpp Wed Mar 18 15:10:36 2015 +0000 +++ b/view/Pane.cpp Mon Apr 20 09:18:55 2015 +0100 @@ -24,6 +24,7 @@ #include "widgets/TextAbbrev.h" #include "base/Preferences.h" #include "layer/WaveformLayer.h" +#include "layer/TimeRulerLayer.h" // GF: added so we can propagate the mouse move event to the note layer for context handling. #include "layer/LayerFactory.h" @@ -1367,7 +1368,8 @@ sv_frame_t snapFrame = mouseFrame; Layer *layer = getInteractionLayer(); - if (layer && !m_shiftPressed) { + if (layer && !m_shiftPressed && + !qobject_cast(layer)) { // don't snap to secs layer->snapToFeatureFrame(this, snapFrame, resolution, Layer::SnapLeft); } @@ -1740,9 +1742,9 @@ if (!editSelectionDrag(e)) { - Layer *layer = getInteractionLayer(); - - if (layer && layer->isLayerEditable()) { + Layer *layer = getTopFlexiNoteLayer(); + + if (layer) { int x = e->x(); int y = e->y(); @@ -2083,7 +2085,8 @@ sv_frame_t snapFrameRight = mouseFrame; Layer *layer = getInteractionLayer(); - if (layer && !m_shiftPressed) { + if (layer && !m_shiftPressed && + !qobject_cast(layer)) { // don't snap to secs layer->snapToFeatureFrame(this, snapFrameLeft, resolution, Layer::SnapLeft); layer->snapToFeatureFrame(this, snapFrameRight, diff -r e39d5d2734ed -r bb80983c9e61 widgets/ItemEditDialog.cpp --- a/widgets/ItemEditDialog.cpp Wed Mar 18 15:10:36 2015 +0000 +++ b/widgets/ItemEditDialog.cpp Mon Apr 20 09:18:55 2015 +0100 @@ -76,7 +76,7 @@ m_frameTimeSpinBox->setSuffix(tr(" frames")); subgrid->addWidget(m_frameTimeSpinBox, subrow, 1, 1, 2); connect(m_frameTimeSpinBox, SIGNAL(valueChanged(int)), - this, SLOT(frameTimeChanged(sv_frame_t))); + this, SLOT(frameTimeChanged(int))); ++subrow; @@ -107,7 +107,7 @@ m_frameDurationSpinBox->setSuffix(tr(" frames")); subgrid->addWidget(m_frameDurationSpinBox, subrow, 1, 1, 2); connect(m_frameDurationSpinBox, SIGNAL(valueChanged(int)), - this, SLOT(frameDurationChanged(sv_frame_t))); + this, SLOT(frameDurationChanged(int))); ++subrow; @@ -287,7 +287,7 @@ } void -ItemEditDialog::frameTimeChanged(sv_frame_t i) +ItemEditDialog::frameTimeChanged(int i) { m_realTimeSecsSpinBox->blockSignals(true); m_realTimeUSecsSpinBox->blockSignals(true); @@ -322,7 +322,7 @@ } void -ItemEditDialog::frameDurationChanged(sv_frame_t i) +ItemEditDialog::frameDurationChanged(int i) { m_realDurationSecsSpinBox->blockSignals(true); m_realDurationUSecsSpinBox->blockSignals(true); diff -r e39d5d2734ed -r bb80983c9e61 widgets/ItemEditDialog.h --- a/widgets/ItemEditDialog.h Wed Mar 18 15:10:36 2015 +0000 +++ b/widgets/ItemEditDialog.h Mon Apr 20 09:18:55 2015 +0100 @@ -59,10 +59,10 @@ QString getText() const; protected slots: - void frameTimeChanged(sv_frame_t); + void frameTimeChanged(int); // must be int as invoked from int signal void realTimeSecsChanged(int); void realTimeUSecsChanged(int); - void frameDurationChanged(sv_frame_t); + void frameDurationChanged(int); // must be int as invoked from int signal void realDurationSecsChanged(int); void realDurationUSecsChanged(int); void valueChanged(double); diff -r e39d5d2734ed -r bb80983c9e61 widgets/LevelPanToolButton.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/LevelPanToolButton.cpp Mon Apr 20 09:18:55 2015 +0100 @@ -0,0 +1,173 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "LevelPanToolButton.h" +#include "LevelPanWidget.h" + +#include +#include +#include +#include +#include + +#include +using std::cerr; +using std::endl; + +LevelPanToolButton::LevelPanToolButton(QWidget *parent) : + QToolButton(parent), + m_pixels(32), + m_pixelsBig(32 * 3), + m_muted(false), + m_savedLevel(1.f) +{ + m_lpw = new LevelPanWidget(); + + connect(m_lpw, SIGNAL(levelChanged(float)), this, SIGNAL(levelChanged(float))); + connect(m_lpw, SIGNAL(levelChanged(float)), this, SLOT(selfLevelChanged(float))); + + connect(m_lpw, SIGNAL(panChanged(float)), this, SIGNAL(panChanged(float))); + connect(m_lpw, SIGNAL(panChanged(float)), this, SLOT(update())); + + connect(this, SIGNAL(clicked(bool)), this, SLOT(selfClicked())); + + QMenu *menu = new QMenu(); + QWidgetAction *wa = new QWidgetAction(menu); + wa->setDefaultWidget(m_lpw); + menu->addAction(wa); + + setPopupMode(InstantPopup); + setMenu(menu); + + setImageSize(m_pixels); + setBigImageSize(m_pixelsBig); +} + +LevelPanToolButton::~LevelPanToolButton() +{ +} + +float +LevelPanToolButton::getLevel() const +{ + return m_lpw->getLevel(); +} + +float +LevelPanToolButton::getPan() const +{ + return m_lpw->getPan(); +} + +bool +LevelPanToolButton::includesMute() const +{ + return m_lpw->includesMute(); +} + +void +LevelPanToolButton::setImageSize(int pixels) +{ + m_pixels = pixels; + + QPixmap px(m_pixels, m_pixels); + px.fill(Qt::transparent); + setIcon(px); +} + +void +LevelPanToolButton::setBigImageSize(int pixels) +{ + m_pixelsBig = pixels; + + m_lpw->setFixedWidth(m_pixelsBig); + m_lpw->setFixedHeight(m_pixelsBig); +} + +void +LevelPanToolButton::setLevel(float level) +{ + m_lpw->setLevel(level); + update(); +} + +void +LevelPanToolButton::setPan(float pan) +{ + m_lpw->setPan(pan); + update(); +} + +void +LevelPanToolButton::setIncludeMute(bool include) +{ + m_lpw->setIncludeMute(include); + update(); +} + +void +LevelPanToolButton::setEnabled(bool enabled) +{ + m_lpw->setEnabled(enabled); + QToolButton::setEnabled(enabled); +} + +void +LevelPanToolButton::selfLevelChanged(float level) +{ + if (level > 0.f) { + m_muted = false; + } else { + m_muted = true; + m_savedLevel = 1.f; + } + update(); +} + +void +LevelPanToolButton::selfClicked() +{ + cerr << "selfClicked" << endl; + + if (m_muted) { + m_muted = false; + m_lpw->setLevel(m_savedLevel); + emit levelChanged(m_savedLevel); + } else { + m_savedLevel = m_lpw->getLevel(); + m_muted = true; + m_lpw->setLevel(0.f); + emit levelChanged(0.f); + } + update(); +} + +void +LevelPanToolButton::paintEvent(QPaintEvent *) +{ + QStylePainter p(this); + QStyleOptionToolButton opt; + initStyleOption(&opt); + opt.features &= (~QStyleOptionToolButton::HasMenu); + p.drawComplexControl(QStyle::CC_ToolButton, opt); + + if (m_pixels >= height()) { + setImageSize(height()-1); + } + + double margin = (double(height()) - m_pixels) / 2.0; + m_lpw->renderTo(this, QRectF(margin, margin, m_pixels, m_pixels), false); +} + + diff -r e39d5d2734ed -r bb80983c9e61 widgets/LevelPanToolButton.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/LevelPanToolButton.h Mon Apr 20 09:18:55 2015 +0100 @@ -0,0 +1,73 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef LEVEL_PAN_TOOLBUTTON_H +#define LEVEL_PAN_TOOLBUTTON_H + +#include + +class LevelPanWidget; + +class LevelPanToolButton : public QToolButton +{ + Q_OBJECT + +public: + LevelPanToolButton(QWidget *parent = 0); + ~LevelPanToolButton(); + + /// Return level as a gain value in the range [0,1] + float getLevel() const; + + /// Return pan as a value in the range [-1,1] + float getPan() const; + + /// Discover whether the level range includes muting or not + bool includesMute() const; + + void setImageSize(int pixels); + + void setBigImageSize(int pixels); + +public slots: + /// Set level in the range [0,1] -- will be rounded + void setLevel(float); + + /// Set pan in the range [-1,1] -- will be rounded + void setPan(float); + + /// Specify whether the level range should include muting or not + void setIncludeMute(bool); + + void setEnabled(bool enabled); + +signals: + void levelChanged(float); + void panChanged(float); + +private slots: + void selfLevelChanged(float); + void selfClicked(); + +protected: + void paintEvent(QPaintEvent *); + + LevelPanWidget *m_lpw; + int m_pixels; + int m_pixelsBig; + bool m_muted; + float m_savedLevel; +}; + +#endif diff -r e39d5d2734ed -r bb80983c9e61 widgets/LevelPanWidget.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/LevelPanWidget.cpp Mon Apr 20 09:18:55 2015 +0100 @@ -0,0 +1,388 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "LevelPanWidget.h" + +#include +#include +#include + +#include "layer/ColourMapper.h" +#include "base/AudioLevel.h" + +#include +#include +#include + +using std::cerr; +using std::endl; + +static const int maxLevel = 4; // min is 0, may be mute or not depending on m_includeMute +static const int maxPan = 2; // range is -maxPan to maxPan + +LevelPanWidget::LevelPanWidget(QWidget *parent) : + QWidget(parent), + m_level(maxLevel), + m_pan(0), + m_editable(true), + m_includeMute(true) +{ +} + +LevelPanWidget::~LevelPanWidget() +{ +} + +QSize +LevelPanWidget::sizeHint() const +{ + static double ratio = 0.0; + if (ratio == 0.0) { + double baseEm; +#ifdef Q_OS_MAC + baseEm = 17.0; +#else + baseEm = 15.0; +#endif + double em = QFontMetrics(QFont()).height(); + ratio = em / baseEm; + } + + int pixels = 40; + int scaled = int(pixels * ratio + 0.5); + if (pixels != 0 && scaled == 0) scaled = 1; + return QSize(scaled, scaled); +} + +static int +db_to_level(double db) +{ + // Only if !m_includeMute, otherwise AudioLevel is used. + // Levels are: +6 0 -6 -12 -20 + assert(maxLevel == 4); + if (db > 3.) return 4; + else if (db > -3.) return 3; + else if (db > -9.) return 2; + else if (db > -16.) return 1; + else return 0; +} + +static double +level_to_db(int level) +{ + // Only if !m_includeMute, otherwise AudioLevel is used. + // Levels are: +6 0 -6 -12 -20 + assert(maxLevel == 4); + if (level >= 4) return 6.; + else if (level == 3) return 0.; + else if (level == 2) return -6.; + else if (level == 1) return -12.; + else return -20.; +} + +void +LevelPanWidget::setLevel(float flevel) +{ + int level; + if (m_includeMute) { + level = AudioLevel::multiplier_to_fader + (flevel, maxLevel, AudioLevel::ShortFader); + } else { + level = db_to_level(AudioLevel::multiplier_to_dB(flevel)); + } + if (level < 0) level = 0; + if (level > maxLevel) level = maxLevel; + if (level != m_level) { + m_level = level; + float convertsTo = getLevel(); + if (fabsf(convertsTo - flevel) > 1e-5) { + emitLevelChanged(); + } + update(); + } +} + +float +LevelPanWidget::getLevel() const +{ + if (m_includeMute) { + return float(AudioLevel::fader_to_multiplier + (m_level, maxLevel, AudioLevel::ShortFader)); + } else { + return float(AudioLevel::dB_to_multiplier(level_to_db(m_level))); + } +} + +void +LevelPanWidget::setPan(float pan) +{ + m_pan = int(round(pan * maxPan)); + if (m_pan < -maxPan) m_pan = -maxPan; + if (m_pan > maxPan) m_pan = maxPan; + update(); +} + +bool +LevelPanWidget::isEditable() const +{ + return m_editable; +} + +bool +LevelPanWidget::includesMute() const +{ + return m_includeMute; +} + +void +LevelPanWidget::setEditable(bool editable) +{ + m_editable = editable; + update(); +} + +void +LevelPanWidget::setIncludeMute(bool include) +{ + m_includeMute = include; + emitLevelChanged(); + update(); +} + +float +LevelPanWidget::getPan() const +{ + return float(m_pan) / float(maxPan); +} + +void +LevelPanWidget::emitLevelChanged() +{ + cerr << "emitting levelChanged(" << getLevel() << ")" << endl; + emit levelChanged(getLevel()); +} + +void +LevelPanWidget::emitPanChanged() +{ + cerr << "emitting panChanged(" << getPan() << ")" << endl; + emit panChanged(getPan()); +} + +void +LevelPanWidget::mousePressEvent(QMouseEvent *e) +{ + mouseMoveEvent(e); +} + +void +LevelPanWidget::mouseMoveEvent(QMouseEvent *e) +{ + if (!m_editable) return; + + int level, pan; + toCell(rect(), e->pos(), level, pan); + if (level == m_level && pan == m_pan) { + return; + } + if (level != m_level) { + m_level = level; + emitLevelChanged(); + } + if (pan != m_pan) { + m_pan = pan; + emitPanChanged(); + } + update(); +} + +void +LevelPanWidget::mouseReleaseEvent(QMouseEvent *e) +{ + mouseMoveEvent(e); +} + +void +LevelPanWidget::wheelEvent(QWheelEvent *e) +{ + if (e->modifiers() & Qt::ControlModifier) { + if (e->delta() > 0) { + if (m_pan < maxPan) { + ++m_pan; + emitPanChanged(); + update(); + } + } else { + if (m_pan > -maxPan) { + --m_pan; + emitPanChanged(); + update(); + } + } + } else { + if (e->delta() > 0) { + if (m_level < maxLevel) { + ++m_level; + emitLevelChanged(); + update(); + } + } else { + if (m_level > 0) { + --m_level; + emitLevelChanged(); + update(); + } + } + } +} + +void +LevelPanWidget::toCell(QRectF rect, QPointF loc, int &level, int &pan) const +{ + double w = rect.width(), h = rect.height(); + + int npan = maxPan * 2 + 1; + int nlevel = maxLevel + 1; + + double wcell = w / npan, hcell = h / nlevel; + + level = int((h - (loc.y() - rect.y())) / hcell); + if (level < 0) level = 0; + if (level > maxLevel) level = maxLevel; + + pan = int((loc.x() - rect.x()) / wcell) - maxPan; + if (pan < -maxPan) pan = -maxPan; + if (pan > maxPan) pan = maxPan; +} + +QSizeF +LevelPanWidget::cellSize(QRectF rect) const +{ + double w = rect.width(), h = rect.height(); + int npan = maxPan * 2 + 1; + int nlevel = maxLevel + 1; + double wcell = w / npan, hcell = h / nlevel; + return QSizeF(wcell, hcell); +} + +QPointF +LevelPanWidget::cellCentre(QRectF rect, int level, int pan) const +{ + QSizeF cs = cellSize(rect); + return QPointF(rect.x() + cs.width() * (pan + maxPan) + cs.width() / 2., + rect.y() + rect.height() - cs.height() * (level + 1) + cs.height() / 2.); +} + +QSizeF +LevelPanWidget::cellLightSize(QRectF rect) const +{ + double extent = 3. / 4.; + QSizeF cs = cellSize(rect); + double m = std::min(cs.width(), cs.height()); + return QSizeF(m * extent, m * extent); +} + +QRectF +LevelPanWidget::cellLightRect(QRectF rect, int level, int pan) const +{ + QSizeF cls = cellLightSize(rect); + QPointF cc = cellCentre(rect, level, pan); + return QRectF(cc.x() - cls.width() / 2., + cc.y() - cls.height() / 2., + cls.width(), + cls.height()); +} + +double +LevelPanWidget::thinLineWidth(QRectF rect) const +{ + double tw = ceil(rect.width() / (maxPan * 2. * 10.)); + double th = ceil(rect.height() / (maxLevel * 10.)); + return std::min(th, tw); +} + +static QColor +level_to_colour(int level) +{ + assert(maxLevel == 4); + if (level == 0) return Qt::black; + else if (level == 1) return QColor(80, 0, 0); + else if (level == 2) return QColor(160, 0, 0); + else if (level == 3) return QColor(255, 0, 0); + else return QColor(255, 255, 0); +} + +void +LevelPanWidget::renderTo(QPaintDevice *dev, QRectF rect, bool asIfEditable) const +{ + QPainter paint(dev); + + paint.setRenderHint(QPainter::Antialiasing, true); + + QPen pen; + + double thin = thinLineWidth(rect); + + pen.setColor(QColor(127, 127, 127, 127)); + pen.setWidthF(cellLightSize(rect).width() + thin); + pen.setCapStyle(Qt::RoundCap); + paint.setPen(pen); + + for (int pan = -maxPan; pan <= maxPan; ++pan) { + paint.drawLine(cellCentre(rect, 0, pan), cellCentre(rect, maxLevel, pan)); + } + + if (isEnabled()) { + pen.setColor(Qt::black); + } else { + pen.setColor(Qt::darkGray); + } + + if (!asIfEditable && m_includeMute && m_level == 0) { + pen.setWidthF(thin * 2); + pen.setCapStyle(Qt::RoundCap); + paint.setPen(pen); + paint.drawLine(cellCentre(rect, 0, -maxPan), + cellCentre(rect, maxLevel, maxPan)); + paint.drawLine(cellCentre(rect, maxLevel, -maxPan), + cellCentre(rect, 0, maxPan)); + return; + } + + pen.setWidthF(thin); + pen.setCapStyle(Qt::FlatCap); + paint.setPen(pen); + + for (int level = 0; level <= m_level; ++level) { + if (isEnabled()) { + paint.setBrush(level_to_colour(level)); + } + QRectF clr = cellLightRect(rect, level, m_pan); + if (m_includeMute && m_level == 0) { + paint.drawLine(clr.topLeft(), clr.bottomRight()); + paint.drawLine(clr.bottomLeft(), clr.topRight()); + } else { + paint.drawEllipse(clr); + } + } +} + +void +LevelPanWidget::paintEvent(QPaintEvent *) +{ + renderTo(this, rect(), m_editable); +} + + + diff -r e39d5d2734ed -r bb80983c9e61 widgets/LevelPanWidget.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/LevelPanWidget.h Mon Apr 20 09:18:55 2015 +0100 @@ -0,0 +1,89 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef LEVEL_PAN_WIDGET_H +#define LEVEL_PAN_WIDGET_H + +#include + +/** + * A simple widget for coarse level and pan control. + */ + +class LevelPanWidget : public QWidget +{ + Q_OBJECT + +public: + LevelPanWidget(QWidget *parent = 0); + ~LevelPanWidget(); + + /// Return level as a gain value in the range [0,1] + float getLevel() const; + + /// Return pan as a value in the range [-1,1] + float getPan() const; + + /// Find out whether the widget is editable + bool isEditable() const; + + /// Discover whether the level range includes muting or not + bool includesMute() const; + + /// Draw a suitably sized copy of the widget's contents to the given device + void renderTo(QPaintDevice *, QRectF, bool asIfEditable) const; + + QSize sizeHint() const; + +public slots: + /// Set level in the range [0,1] -- will be rounded + void setLevel(float); + + /// Set pan in the range [-1,1] -- will be rounded + void setPan(float); + + /// Specify whether the widget is editable or read-only (default editable) + void setEditable(bool); + + /// Specify whether the level range should include muting or not + void setIncludeMute(bool); + +signals: + void levelChanged(float); + void panChanged(float); + +protected: + virtual void mousePressEvent(QMouseEvent *ev); + virtual void mouseMoveEvent(QMouseEvent *ev); + virtual void mouseReleaseEvent(QMouseEvent *ev); + virtual void wheelEvent(QWheelEvent *ev); + virtual void paintEvent(QPaintEvent *ev); + + void emitLevelChanged(); + void emitPanChanged(); + + int m_level; + int m_pan; + bool m_editable; + bool m_includeMute; + + QSizeF cellSize(QRectF) const; + QPointF cellCentre(QRectF, int level, int pan) const; + QSizeF cellLightSize(QRectF) const; + QRectF cellLightRect(QRectF, int level, int pan) const; + double thinLineWidth(QRectF) const; + void toCell(QRectF, QPointF loc, int &level, int &pan) const; +}; + +#endif diff -r e39d5d2734ed -r bb80983c9e61 widgets/PropertyStack.cpp --- a/widgets/PropertyStack.cpp Wed Mar 18 15:10:36 2015 +0000 +++ b/widgets/PropertyStack.cpp Mon Apr 20 09:18:55 2015 +0100 @@ -30,7 +30,7 @@ #include -#define DEBUG_PROPERTY_STACK 1 +//#define DEBUG_PROPERTY_STACK 1 PropertyStack::PropertyStack(QWidget *parent, View *client) : QTabWidget(parent),