annotate widgets/LevelPanWidget.cpp @ 1204:d421df27e184 3.0-integration

Further PropertyBox layout overhaul: avoid crash (/ assertion failure) when property type changes from e.g. colour to colourmap, by replacing the existing widget within the layout rather than trying to repopulate it
author Chris Cannam
date Tue, 20 Dec 2016 10:49:24 +0000
parents a68c25ba1153
children 8ef67917c301
rev   line source
Chris@923 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@923 2
Chris@923 3 /*
Chris@923 4 Sonic Visualiser
Chris@923 5 An audio file viewer and annotation editor.
Chris@923 6 Centre for Digital Music, Queen Mary, University of London.
Chris@923 7
Chris@923 8 This program is free software; you can redistribute it and/or
Chris@923 9 modify it under the terms of the GNU General Public License as
Chris@923 10 published by the Free Software Foundation; either version 2 of the
Chris@923 11 License, or (at your option) any later version. See the file
Chris@923 12 COPYING included with this distribution for more information.
Chris@923 13 */
Chris@923 14
Chris@923 15 #include "LevelPanWidget.h"
Chris@923 16
Chris@923 17 #include <QPainter>
Chris@923 18 #include <QMouseEvent>
Chris@923 19 #include <QWheelEvent>
Chris@923 20
Chris@923 21 #include "layer/ColourMapper.h"
Chris@925 22 #include "base/AudioLevel.h"
Chris@923 23
Chris@1176 24 #include "WidgetScale.h"
Chris@1176 25
Chris@923 26 #include <iostream>
Chris@926 27 #include <cmath>
Chris@940 28 #include <cassert>
Chris@923 29
Chris@923 30 using std::cerr;
Chris@923 31 using std::endl;
Chris@923 32
Chris@940 33 static const int maxLevel = 4; // min is 0, may be mute or not depending on m_includeMute
Chris@923 34 static const int maxPan = 2; // range is -maxPan to maxPan
Chris@923 35
Chris@923 36 LevelPanWidget::LevelPanWidget(QWidget *parent) :
Chris@923 37 QWidget(parent),
Chris@923 38 m_level(maxLevel),
Chris@923 39 m_pan(0),
Chris@1177 40 m_monitorLeft(-1),
Chris@1177 41 m_monitorRight(-1),
Chris@940 42 m_editable(true),
Chris@940 43 m_includeMute(true)
Chris@923 44 {
Chris@1191 45 setToolTip(tr("Drag vertically to adjust level, horizontally to adjust pan"));
Chris@1201 46 setLevel(1.0);
Chris@1201 47 setPan(0.0);
Chris@923 48 }
Chris@923 49
Chris@923 50 LevelPanWidget::~LevelPanWidget()
Chris@923 51 {
Chris@923 52 }
Chris@923 53
Chris@929 54 QSize
Chris@929 55 LevelPanWidget::sizeHint() const
Chris@929 56 {
Chris@1176 57 return WidgetScale::scaleQSize(QSize(40, 40));
Chris@929 58 }
Chris@929 59
Chris@940 60 static int
Chris@940 61 db_to_level(double db)
Chris@940 62 {
Chris@940 63 // Only if !m_includeMute, otherwise AudioLevel is used.
Chris@940 64 // Levels are: +6 0 -6 -12 -20
Chris@940 65 assert(maxLevel == 4);
Chris@940 66 if (db > 3.) return 4;
Chris@940 67 else if (db > -3.) return 3;
Chris@940 68 else if (db > -9.) return 2;
Chris@940 69 else if (db > -16.) return 1;
Chris@940 70 else return 0;
Chris@940 71 }
Chris@940 72
Chris@940 73 static double
Chris@940 74 level_to_db(int level)
Chris@940 75 {
Chris@940 76 // Only if !m_includeMute, otherwise AudioLevel is used.
Chris@940 77 // Levels are: +6 0 -6 -12 -20
Chris@940 78 assert(maxLevel == 4);
Chris@940 79 if (level >= 4) return 6.;
Chris@940 80 else if (level == 3) return 0.;
Chris@940 81 else if (level == 2) return -6.;
Chris@940 82 else if (level == 1) return -12.;
Chris@940 83 else return -20.;
Chris@940 84 }
Chris@940 85
Chris@1177 86 int
Chris@1177 87 LevelPanWidget::audioLevelToLevel(float audioLevel, bool withMute)
Chris@1177 88 {
Chris@1177 89 int level;
Chris@1177 90 if (withMute) {
Chris@1177 91 level = AudioLevel::multiplier_to_fader
Chris@1177 92 (audioLevel, maxLevel, AudioLevel::ShortFader);
Chris@1177 93 } else {
Chris@1177 94 level = db_to_level(AudioLevel::multiplier_to_dB(audioLevel));
Chris@1177 95 }
Chris@1177 96 if (level < 0) level = 0;
Chris@1177 97 if (level > maxLevel) level = maxLevel;
Chris@1177 98 return level;
Chris@1177 99 }
Chris@1177 100
Chris@1177 101 float
Chris@1177 102 LevelPanWidget::levelToAudioLevel(int level, bool withMute)
Chris@1177 103 {
Chris@1177 104 if (withMute) {
Chris@1177 105 return float(AudioLevel::fader_to_multiplier
Chris@1177 106 (level, maxLevel, AudioLevel::ShortFader));
Chris@1177 107 } else {
Chris@1177 108 return float(AudioLevel::dB_to_multiplier(level_to_db(level)));
Chris@1177 109 }
Chris@1177 110 }
Chris@1177 111
Chris@923 112 void
Chris@925 113 LevelPanWidget::setLevel(float flevel)
Chris@923 114 {
Chris@1177 115 int level = audioLevelToLevel(flevel, m_includeMute);
Chris@925 116 if (level != m_level) {
Chris@925 117 m_level = level;
Chris@925 118 float convertsTo = getLevel();
Chris@925 119 if (fabsf(convertsTo - flevel) > 1e-5) {
Chris@925 120 emitLevelChanged();
Chris@925 121 }
Chris@925 122 update();
Chris@925 123 }
Chris@923 124 }
Chris@923 125
Chris@940 126 float
Chris@940 127 LevelPanWidget::getLevel() const
Chris@940 128 {
Chris@1201 129 float flevel = levelToAudioLevel(m_level, m_includeMute);
Chris@1201 130 return flevel;
Chris@1177 131 }
Chris@1177 132
Chris@1177 133 int
Chris@1177 134 LevelPanWidget::audioPanToPan(float audioPan)
Chris@1177 135 {
Chris@1177 136 int pan = int(round(audioPan * maxPan));
Chris@1177 137 if (pan < -maxPan) pan = -maxPan;
Chris@1177 138 if (pan > maxPan) pan = maxPan;
Chris@1177 139 return pan;
Chris@1177 140 }
Chris@1177 141
Chris@1177 142 float
Chris@1177 143 LevelPanWidget::panToAudioPan(int pan)
Chris@1177 144 {
Chris@1177 145 return float(pan) / float(maxPan);
Chris@1177 146 }
Chris@1177 147
Chris@1177 148 void
Chris@1177 149 LevelPanWidget::setPan(float fpan)
Chris@1177 150 {
Chris@1177 151 int pan = audioPanToPan(fpan);
Chris@1177 152 if (pan != m_pan) {
Chris@1177 153 m_pan = pan;
Chris@1177 154 update();
Chris@940 155 }
Chris@940 156 }
Chris@940 157
Chris@1177 158 float
Chris@1177 159 LevelPanWidget::getPan() const
Chris@1177 160 {
Chris@1177 161 return panToAudioPan(m_pan);
Chris@1177 162 }
Chris@1177 163
Chris@923 164 void
Chris@1177 165 LevelPanWidget::setMonitoringLevels(float left, float right)
Chris@923 166 {
Chris@1177 167 m_monitorLeft = left;
Chris@1177 168 m_monitorRight = right;
Chris@923 169 update();
Chris@923 170 }
Chris@923 171
Chris@940 172 bool
Chris@940 173 LevelPanWidget::isEditable() const
Chris@940 174 {
Chris@940 175 return m_editable;
Chris@940 176 }
Chris@940 177
Chris@940 178 bool
Chris@940 179 LevelPanWidget::includesMute() const
Chris@940 180 {
Chris@940 181 return m_includeMute;
Chris@940 182 }
Chris@940 183
Chris@923 184 void
Chris@923 185 LevelPanWidget::setEditable(bool editable)
Chris@923 186 {
Chris@923 187 m_editable = editable;
Chris@923 188 update();
Chris@923 189 }
Chris@923 190
Chris@940 191 void
Chris@940 192 LevelPanWidget::setIncludeMute(bool include)
Chris@923 193 {
Chris@940 194 m_includeMute = include;
Chris@940 195 emitLevelChanged();
Chris@940 196 update();
Chris@923 197 }
Chris@923 198
Chris@923 199 void
Chris@923 200 LevelPanWidget::emitLevelChanged()
Chris@923 201 {
Chris@923 202 emit levelChanged(getLevel());
Chris@923 203 }
Chris@923 204
Chris@923 205 void
Chris@923 206 LevelPanWidget::emitPanChanged()
Chris@923 207 {
Chris@923 208 emit panChanged(getPan());
Chris@923 209 }
Chris@923 210
Chris@923 211 void
Chris@923 212 LevelPanWidget::mousePressEvent(QMouseEvent *e)
Chris@923 213 {
Chris@923 214 mouseMoveEvent(e);
Chris@923 215 }
Chris@923 216
Chris@923 217 void
Chris@923 218 LevelPanWidget::mouseMoveEvent(QMouseEvent *e)
Chris@923 219 {
Chris@923 220 if (!m_editable) return;
Chris@923 221
Chris@923 222 int level, pan;
Chris@929 223 toCell(rect(), e->pos(), level, pan);
Chris@923 224 if (level == m_level && pan == m_pan) {
Chris@923 225 return;
Chris@923 226 }
Chris@923 227 if (level != m_level) {
Chris@923 228 m_level = level;
Chris@923 229 emitLevelChanged();
Chris@923 230 }
Chris@923 231 if (pan != m_pan) {
Chris@923 232 m_pan = pan;
Chris@923 233 emitPanChanged();
Chris@923 234 }
Chris@923 235 update();
Chris@923 236 }
Chris@923 237
Chris@923 238 void
Chris@923 239 LevelPanWidget::mouseReleaseEvent(QMouseEvent *e)
Chris@923 240 {
Chris@923 241 mouseMoveEvent(e);
Chris@923 242 }
Chris@923 243
Chris@923 244 void
Chris@923 245 LevelPanWidget::wheelEvent(QWheelEvent *e)
Chris@923 246 {
Chris@923 247 if (e->modifiers() & Qt::ControlModifier) {
Chris@923 248 if (e->delta() > 0) {
Chris@923 249 if (m_pan < maxPan) {
Chris@923 250 ++m_pan;
Chris@923 251 emitPanChanged();
Chris@923 252 update();
Chris@923 253 }
Chris@923 254 } else {
Chris@923 255 if (m_pan > -maxPan) {
Chris@923 256 --m_pan;
Chris@923 257 emitPanChanged();
Chris@923 258 update();
Chris@923 259 }
Chris@923 260 }
Chris@923 261 } else {
Chris@923 262 if (e->delta() > 0) {
Chris@923 263 if (m_level < maxLevel) {
Chris@923 264 ++m_level;
Chris@923 265 emitLevelChanged();
Chris@923 266 update();
Chris@923 267 }
Chris@923 268 } else {
Chris@923 269 if (m_level > 0) {
Chris@923 270 --m_level;
Chris@923 271 emitLevelChanged();
Chris@923 272 update();
Chris@923 273 }
Chris@923 274 }
Chris@923 275 }
Chris@923 276 }
Chris@923 277
Chris@923 278 void
Chris@929 279 LevelPanWidget::toCell(QRectF rect, QPointF loc, int &level, int &pan) const
Chris@923 280 {
Chris@929 281 double w = rect.width(), h = rect.height();
Chris@929 282
Chris@923 283 int npan = maxPan * 2 + 1;
Chris@924 284 int nlevel = maxLevel + 1;
Chris@929 285
Chris@924 286 double wcell = w / npan, hcell = h / nlevel;
Chris@929 287
Chris@932 288 level = int((h - (loc.y() - rect.y())) / hcell);
Chris@924 289 if (level < 0) level = 0;
Chris@923 290 if (level > maxLevel) level = maxLevel;
Chris@929 291
Chris@932 292 pan = int((loc.x() - rect.x()) / wcell) - maxPan;
Chris@923 293 if (pan < -maxPan) pan = -maxPan;
Chris@923 294 if (pan > maxPan) pan = maxPan;
Chris@923 295 }
Chris@923 296
Chris@923 297 QSizeF
Chris@929 298 LevelPanWidget::cellSize(QRectF rect) const
Chris@923 299 {
Chris@929 300 double w = rect.width(), h = rect.height();
Chris@923 301 int npan = maxPan * 2 + 1;
Chris@924 302 int nlevel = maxLevel + 1;
Chris@924 303 double wcell = w / npan, hcell = h / nlevel;
Chris@923 304 return QSizeF(wcell, hcell);
Chris@923 305 }
Chris@923 306
Chris@923 307 QPointF
Chris@929 308 LevelPanWidget::cellCentre(QRectF rect, int level, int pan) const
Chris@923 309 {
Chris@929 310 QSizeF cs = cellSize(rect);
Chris@932 311 return QPointF(rect.x() + cs.width() * (pan + maxPan) + cs.width() / 2.,
Chris@932 312 rect.y() + rect.height() - cs.height() * (level + 1) + cs.height() / 2.);
Chris@923 313 }
Chris@923 314
Chris@923 315 QSizeF
Chris@929 316 LevelPanWidget::cellLightSize(QRectF rect) const
Chris@923 317 {
Chris@923 318 double extent = 3. / 4.;
Chris@929 319 QSizeF cs = cellSize(rect);
Chris@923 320 double m = std::min(cs.width(), cs.height());
Chris@923 321 return QSizeF(m * extent, m * extent);
Chris@923 322 }
Chris@923 323
Chris@923 324 QRectF
Chris@929 325 LevelPanWidget::cellLightRect(QRectF rect, int level, int pan) const
Chris@923 326 {
Chris@929 327 QSizeF cls = cellLightSize(rect);
Chris@929 328 QPointF cc = cellCentre(rect, level, pan);
Chris@923 329 return QRectF(cc.x() - cls.width() / 2.,
Chris@923 330 cc.y() - cls.height() / 2.,
Chris@923 331 cls.width(),
Chris@923 332 cls.height());
Chris@923 333 }
Chris@923 334
Chris@923 335 double
Chris@929 336 LevelPanWidget::thinLineWidth(QRectF rect) const
Chris@923 337 {
Chris@929 338 double tw = ceil(rect.width() / (maxPan * 2. * 10.));
Chris@929 339 double th = ceil(rect.height() / (maxLevel * 10.));
Chris@923 340 return std::min(th, tw);
Chris@923 341 }
Chris@923 342
Chris@941 343 static QColor
Chris@941 344 level_to_colour(int level)
Chris@941 345 {
Chris@941 346 assert(maxLevel == 4);
Chris@941 347 if (level == 0) return Qt::black;
Chris@941 348 else if (level == 1) return QColor(80, 0, 0);
Chris@941 349 else if (level == 2) return QColor(160, 0, 0);
Chris@941 350 else if (level == 3) return QColor(255, 0, 0);
Chris@941 351 else return QColor(255, 255, 0);
Chris@941 352 }
Chris@941 353
Chris@923 354 void
Chris@929 355 LevelPanWidget::renderTo(QPaintDevice *dev, QRectF rect, bool asIfEditable) const
Chris@923 356 {
Chris@929 357 QPainter paint(dev);
Chris@923 358
Chris@923 359 paint.setRenderHint(QPainter::Antialiasing, true);
Chris@923 360
Chris@923 361 QPen pen;
Chris@923 362
Chris@929 363 double thin = thinLineWidth(rect);
Chris@938 364
Chris@923 365 pen.setColor(QColor(127, 127, 127, 127));
Chris@929 366 pen.setWidthF(cellLightSize(rect).width() + thin);
Chris@923 367 pen.setCapStyle(Qt::RoundCap);
Chris@923 368 paint.setPen(pen);
Chris@1177 369 paint.setBrush(Qt::NoBrush);
Chris@923 370
Chris@923 371 for (int pan = -maxPan; pan <= maxPan; ++pan) {
Chris@929 372 paint.drawLine(cellCentre(rect, 0, pan), cellCentre(rect, maxLevel, pan));
Chris@924 373 }
Chris@924 374
Chris@1177 375 if (m_monitorLeft > 0.f || m_monitorRight > 0.f) {
Chris@1177 376 paint.setPen(Qt::NoPen);
Chris@1177 377 for (int pan = -maxPan; pan <= maxPan; ++pan) {
Chris@1177 378 float audioPan = panToAudioPan(pan);
Chris@1177 379 float audioLevel;
Chris@1177 380 if (audioPan < 0.f) {
Chris@1177 381 audioLevel = m_monitorLeft + m_monitorRight * (1.f + audioPan);
Chris@1177 382 } else {
Chris@1177 383 audioLevel = m_monitorRight + m_monitorLeft * (1.f - audioPan);
Chris@1177 384 }
Chris@1177 385 int levelHere = audioLevelToLevel(audioLevel, false);
Chris@1177 386 for (int level = 0; level <= levelHere; ++level) {
Chris@1177 387 paint.setBrush(level_to_colour(level));
Chris@1177 388 QRectF clr = cellLightRect(rect, level, pan);
Chris@1177 389 paint.drawEllipse(clr);
Chris@1177 390 }
Chris@1177 391 }
Chris@1177 392 paint.setPen(pen);
Chris@1177 393 paint.setBrush(Qt::NoBrush);
Chris@1177 394 }
Chris@1177 395
Chris@924 396 if (isEnabled()) {
Chris@924 397 pen.setColor(Qt::black);
Chris@924 398 } else {
Chris@924 399 pen.setColor(Qt::darkGray);
Chris@923 400 }
Chris@929 401
Chris@940 402 if (!asIfEditable && m_includeMute && m_level == 0) {
Chris@929 403 pen.setWidthF(thin * 2);
Chris@929 404 pen.setCapStyle(Qt::RoundCap);
Chris@929 405 paint.setPen(pen);
Chris@930 406 paint.drawLine(cellCentre(rect, 0, -maxPan),
Chris@930 407 cellCentre(rect, maxLevel, maxPan));
Chris@930 408 paint.drawLine(cellCentre(rect, maxLevel, -maxPan),
Chris@930 409 cellCentre(rect, 0, maxPan));
Chris@929 410 return;
Chris@929 411 }
Chris@923 412
Chris@923 413 pen.setWidthF(thin);
Chris@923 414 pen.setCapStyle(Qt::FlatCap);
Chris@923 415 paint.setPen(pen);
Chris@923 416
Chris@924 417 for (int level = 0; level <= m_level; ++level) {
Chris@924 418 if (isEnabled()) {
Chris@941 419 paint.setBrush(level_to_colour(level));
Chris@924 420 }
Chris@929 421 QRectF clr = cellLightRect(rect, level, m_pan);
Chris@940 422 if (m_includeMute && m_level == 0) {
Chris@924 423 paint.drawLine(clr.topLeft(), clr.bottomRight());
Chris@924 424 paint.drawLine(clr.bottomLeft(), clr.topRight());
Chris@924 425 } else {
Chris@924 426 paint.drawEllipse(clr);
Chris@924 427 }
Chris@923 428 }
Chris@923 429 }
Chris@923 430
Chris@929 431 void
Chris@929 432 LevelPanWidget::paintEvent(QPaintEvent *)
Chris@929 433 {
Chris@929 434 renderTo(this, rect(), m_editable);
Chris@929 435 }
Chris@923 436
Chris@1180 437 void
Chris@1180 438 LevelPanWidget::enterEvent(QEvent *e)
Chris@1180 439 {
Chris@1180 440 QWidget::enterEvent(e);
Chris@1180 441 emit mouseEntered();
Chris@1180 442 }
Chris@929 443
Chris@1180 444 void
Chris@1180 445 LevelPanWidget::leaveEvent(QEvent *e)
Chris@1180 446 {
Chris@1180 447 QWidget::enterEvent(e);
Chris@1180 448 emit mouseLeft();
Chris@1180 449 }
Chris@929 450