annotate widgets/LevelPanWidget.cpp @ 1269:f2894944c6b8

Make the overlays at either end translucent, so they don't completely crop out any underlying text or necessary info (e.g. selection extents)
author Chris Cannam
date Thu, 19 Apr 2018 14:35:59 +0100
parents a34a2a25907c
children e8368466fa34
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@1249 43 m_editing(false),
Chris@940 44 m_includeMute(true)
Chris@923 45 {
Chris@1191 46 setToolTip(tr("Drag vertically to adjust level, horizontally to adjust pan"));
Chris@1201 47 setLevel(1.0);
Chris@1201 48 setPan(0.0);
Chris@923 49 }
Chris@923 50
Chris@923 51 LevelPanWidget::~LevelPanWidget()
Chris@923 52 {
Chris@923 53 }
Chris@923 54
Chris@1249 55 void
Chris@1249 56 LevelPanWidget::setToDefault()
Chris@1249 57 {
Chris@1249 58 setLevel(1.0);
Chris@1249 59 setPan(0.0);
Chris@1250 60 emitLevelChanged();
Chris@1250 61 emitPanChanged();
Chris@1249 62 }
Chris@1249 63
Chris@929 64 QSize
Chris@929 65 LevelPanWidget::sizeHint() const
Chris@929 66 {
Chris@1176 67 return WidgetScale::scaleQSize(QSize(40, 40));
Chris@929 68 }
Chris@929 69
Chris@940 70 static int
Chris@940 71 db_to_level(double db)
Chris@940 72 {
Chris@940 73 // Only if !m_includeMute, otherwise AudioLevel is used.
Chris@940 74 // Levels are: +6 0 -6 -12 -20
Chris@940 75 assert(maxLevel == 4);
Chris@940 76 if (db > 3.) return 4;
Chris@940 77 else if (db > -3.) return 3;
Chris@940 78 else if (db > -9.) return 2;
Chris@940 79 else if (db > -16.) return 1;
Chris@940 80 else return 0;
Chris@940 81 }
Chris@940 82
Chris@940 83 static double
Chris@940 84 level_to_db(int level)
Chris@940 85 {
Chris@940 86 // Only if !m_includeMute, otherwise AudioLevel is used.
Chris@940 87 // Levels are: +6 0 -6 -12 -20
Chris@940 88 assert(maxLevel == 4);
Chris@940 89 if (level >= 4) return 6.;
Chris@940 90 else if (level == 3) return 0.;
Chris@940 91 else if (level == 2) return -6.;
Chris@940 92 else if (level == 1) return -12.;
Chris@940 93 else return -20.;
Chris@940 94 }
Chris@940 95
Chris@1177 96 int
Chris@1177 97 LevelPanWidget::audioLevelToLevel(float audioLevel, bool withMute)
Chris@1177 98 {
Chris@1177 99 int level;
Chris@1177 100 if (withMute) {
Chris@1177 101 level = AudioLevel::multiplier_to_fader
Chris@1177 102 (audioLevel, maxLevel, AudioLevel::ShortFader);
Chris@1177 103 } else {
Chris@1177 104 level = db_to_level(AudioLevel::multiplier_to_dB(audioLevel));
Chris@1177 105 }
Chris@1177 106 if (level < 0) level = 0;
Chris@1177 107 if (level > maxLevel) level = maxLevel;
Chris@1177 108 return level;
Chris@1177 109 }
Chris@1177 110
Chris@1177 111 float
Chris@1177 112 LevelPanWidget::levelToAudioLevel(int level, bool withMute)
Chris@1177 113 {
Chris@1177 114 if (withMute) {
Chris@1177 115 return float(AudioLevel::fader_to_multiplier
Chris@1177 116 (level, maxLevel, AudioLevel::ShortFader));
Chris@1177 117 } else {
Chris@1177 118 return float(AudioLevel::dB_to_multiplier(level_to_db(level)));
Chris@1177 119 }
Chris@1177 120 }
Chris@1177 121
Chris@923 122 void
Chris@925 123 LevelPanWidget::setLevel(float flevel)
Chris@923 124 {
Chris@1177 125 int level = audioLevelToLevel(flevel, m_includeMute);
Chris@925 126 if (level != m_level) {
Chris@1266 127 m_level = level;
Chris@1266 128 float convertsTo = getLevel();
Chris@1266 129 if (fabsf(convertsTo - flevel) > 1e-5) {
Chris@1266 130 emitLevelChanged();
Chris@1266 131 }
Chris@1266 132 update();
Chris@925 133 }
Chris@923 134 }
Chris@923 135
Chris@940 136 float
Chris@940 137 LevelPanWidget::getLevel() const
Chris@940 138 {
Chris@1201 139 float flevel = levelToAudioLevel(m_level, m_includeMute);
Chris@1201 140 return flevel;
Chris@1177 141 }
Chris@1177 142
Chris@1177 143 int
Chris@1177 144 LevelPanWidget::audioPanToPan(float audioPan)
Chris@1177 145 {
Chris@1177 146 int pan = int(round(audioPan * maxPan));
Chris@1177 147 if (pan < -maxPan) pan = -maxPan;
Chris@1177 148 if (pan > maxPan) pan = maxPan;
Chris@1177 149 return pan;
Chris@1177 150 }
Chris@1177 151
Chris@1177 152 float
Chris@1177 153 LevelPanWidget::panToAudioPan(int pan)
Chris@1177 154 {
Chris@1177 155 return float(pan) / float(maxPan);
Chris@1177 156 }
Chris@1177 157
Chris@1177 158 void
Chris@1177 159 LevelPanWidget::setPan(float fpan)
Chris@1177 160 {
Chris@1177 161 int pan = audioPanToPan(fpan);
Chris@1177 162 if (pan != m_pan) {
Chris@1177 163 m_pan = pan;
Chris@1177 164 update();
Chris@940 165 }
Chris@940 166 }
Chris@940 167
Chris@1177 168 float
Chris@1177 169 LevelPanWidget::getPan() const
Chris@1177 170 {
Chris@1177 171 return panToAudioPan(m_pan);
Chris@1177 172 }
Chris@1177 173
Chris@923 174 void
Chris@1177 175 LevelPanWidget::setMonitoringLevels(float left, float right)
Chris@923 176 {
Chris@1177 177 m_monitorLeft = left;
Chris@1177 178 m_monitorRight = right;
Chris@923 179 update();
Chris@923 180 }
Chris@923 181
Chris@940 182 bool
Chris@940 183 LevelPanWidget::isEditable() const
Chris@940 184 {
Chris@940 185 return m_editable;
Chris@940 186 }
Chris@940 187
Chris@940 188 bool
Chris@940 189 LevelPanWidget::includesMute() const
Chris@940 190 {
Chris@940 191 return m_includeMute;
Chris@940 192 }
Chris@940 193
Chris@923 194 void
Chris@923 195 LevelPanWidget::setEditable(bool editable)
Chris@923 196 {
Chris@923 197 m_editable = editable;
Chris@923 198 update();
Chris@923 199 }
Chris@923 200
Chris@940 201 void
Chris@940 202 LevelPanWidget::setIncludeMute(bool include)
Chris@923 203 {
Chris@940 204 m_includeMute = include;
Chris@940 205 emitLevelChanged();
Chris@940 206 update();
Chris@923 207 }
Chris@923 208
Chris@923 209 void
Chris@923 210 LevelPanWidget::emitLevelChanged()
Chris@923 211 {
Chris@923 212 emit levelChanged(getLevel());
Chris@923 213 }
Chris@923 214
Chris@923 215 void
Chris@923 216 LevelPanWidget::emitPanChanged()
Chris@923 217 {
Chris@923 218 emit panChanged(getPan());
Chris@923 219 }
Chris@923 220
Chris@923 221 void
Chris@923 222 LevelPanWidget::mousePressEvent(QMouseEvent *e)
Chris@923 223 {
Chris@1249 224 if (e->button() == Qt::MidButton ||
Chris@1249 225 ((e->button() == Qt::LeftButton) &&
Chris@1249 226 (e->modifiers() & Qt::ControlModifier))) {
Chris@1249 227 setToDefault();
Chris@1249 228 } else if (e->button() == Qt::LeftButton) {
Chris@1249 229 m_editing = true;
Chris@1249 230 mouseMoveEvent(e);
Chris@1249 231 }
Chris@1249 232 }
Chris@1249 233
Chris@1249 234 void
Chris@1249 235 LevelPanWidget::mouseReleaseEvent(QMouseEvent *e)
Chris@1249 236 {
Chris@923 237 mouseMoveEvent(e);
Chris@1249 238 m_editing = false;
Chris@923 239 }
Chris@923 240
Chris@923 241 void
Chris@923 242 LevelPanWidget::mouseMoveEvent(QMouseEvent *e)
Chris@923 243 {
Chris@923 244 if (!m_editable) return;
Chris@1249 245 if (!m_editing) return;
Chris@923 246
Chris@923 247 int level, pan;
Chris@929 248 toCell(rect(), e->pos(), level, pan);
Chris@923 249 if (level == m_level && pan == m_pan) {
Chris@1266 250 return;
Chris@923 251 }
Chris@923 252 if (level != m_level) {
Chris@1266 253 m_level = level;
Chris@1266 254 emitLevelChanged();
Chris@923 255 }
Chris@923 256 if (pan != m_pan) {
Chris@1266 257 m_pan = pan;
Chris@1266 258 emitPanChanged();
Chris@923 259 }
Chris@923 260 update();
Chris@923 261 }
Chris@923 262
Chris@923 263 void
Chris@923 264 LevelPanWidget::wheelEvent(QWheelEvent *e)
Chris@923 265 {
Chris@923 266 if (e->modifiers() & Qt::ControlModifier) {
Chris@1266 267 if (e->delta() > 0) {
Chris@1266 268 if (m_pan < maxPan) {
Chris@1266 269 ++m_pan;
Chris@1266 270 emitPanChanged();
Chris@1266 271 update();
Chris@1266 272 }
Chris@1266 273 } else {
Chris@1266 274 if (m_pan > -maxPan) {
Chris@1266 275 --m_pan;
Chris@1266 276 emitPanChanged();
Chris@1266 277 update();
Chris@1266 278 }
Chris@1266 279 }
Chris@923 280 } else {
Chris@1266 281 if (e->delta() > 0) {
Chris@1266 282 if (m_level < maxLevel) {
Chris@1266 283 ++m_level;
Chris@1266 284 emitLevelChanged();
Chris@1266 285 update();
Chris@1266 286 }
Chris@1266 287 } else {
Chris@1266 288 if (m_level > 0) {
Chris@1266 289 --m_level;
Chris@1266 290 emitLevelChanged();
Chris@1266 291 update();
Chris@1266 292 }
Chris@1266 293 }
Chris@923 294 }
Chris@923 295 }
Chris@923 296
Chris@923 297 void
Chris@929 298 LevelPanWidget::toCell(QRectF rect, QPointF loc, int &level, int &pan) const
Chris@923 299 {
Chris@929 300 double w = rect.width(), h = rect.height();
Chris@929 301
Chris@923 302 int npan = maxPan * 2 + 1;
Chris@924 303 int nlevel = maxLevel + 1;
Chris@929 304
Chris@924 305 double wcell = w / npan, hcell = h / nlevel;
Chris@929 306
Chris@932 307 level = int((h - (loc.y() - rect.y())) / hcell);
Chris@924 308 if (level < 0) level = 0;
Chris@923 309 if (level > maxLevel) level = maxLevel;
Chris@929 310
Chris@932 311 pan = int((loc.x() - rect.x()) / wcell) - maxPan;
Chris@923 312 if (pan < -maxPan) pan = -maxPan;
Chris@923 313 if (pan > maxPan) pan = maxPan;
Chris@923 314 }
Chris@923 315
Chris@923 316 QSizeF
Chris@929 317 LevelPanWidget::cellSize(QRectF rect) const
Chris@923 318 {
Chris@929 319 double w = rect.width(), h = rect.height();
Chris@923 320 int npan = maxPan * 2 + 1;
Chris@924 321 int nlevel = maxLevel + 1;
Chris@924 322 double wcell = w / npan, hcell = h / nlevel;
Chris@923 323 return QSizeF(wcell, hcell);
Chris@923 324 }
Chris@923 325
Chris@923 326 QPointF
Chris@929 327 LevelPanWidget::cellCentre(QRectF rect, int level, int pan) const
Chris@923 328 {
Chris@929 329 QSizeF cs = cellSize(rect);
Chris@932 330 return QPointF(rect.x() + cs.width() * (pan + maxPan) + cs.width() / 2.,
Chris@1266 331 rect.y() + rect.height() - cs.height() * (level + 1) + cs.height() / 2.);
Chris@923 332 }
Chris@923 333
Chris@923 334 QSizeF
Chris@929 335 LevelPanWidget::cellLightSize(QRectF rect) const
Chris@923 336 {
Chris@923 337 double extent = 3. / 4.;
Chris@929 338 QSizeF cs = cellSize(rect);
Chris@923 339 double m = std::min(cs.width(), cs.height());
Chris@923 340 return QSizeF(m * extent, m * extent);
Chris@923 341 }
Chris@923 342
Chris@923 343 QRectF
Chris@929 344 LevelPanWidget::cellLightRect(QRectF rect, int level, int pan) const
Chris@923 345 {
Chris@929 346 QSizeF cls = cellLightSize(rect);
Chris@929 347 QPointF cc = cellCentre(rect, level, pan);
Chris@923 348 return QRectF(cc.x() - cls.width() / 2.,
Chris@1266 349 cc.y() - cls.height() / 2.,
Chris@1266 350 cls.width(),
Chris@1266 351 cls.height());
Chris@923 352 }
Chris@923 353
Chris@923 354 double
Chris@929 355 LevelPanWidget::thinLineWidth(QRectF rect) const
Chris@923 356 {
Chris@929 357 double tw = ceil(rect.width() / (maxPan * 2. * 10.));
Chris@929 358 double th = ceil(rect.height() / (maxLevel * 10.));
Chris@923 359 return std::min(th, tw);
Chris@923 360 }
Chris@923 361
Chris@941 362 static QColor
Chris@941 363 level_to_colour(int level)
Chris@941 364 {
Chris@941 365 assert(maxLevel == 4);
Chris@941 366 if (level == 0) return Qt::black;
Chris@941 367 else if (level == 1) return QColor(80, 0, 0);
Chris@941 368 else if (level == 2) return QColor(160, 0, 0);
Chris@941 369 else if (level == 3) return QColor(255, 0, 0);
Chris@941 370 else return QColor(255, 255, 0);
Chris@941 371 }
Chris@941 372
Chris@923 373 void
Chris@929 374 LevelPanWidget::renderTo(QPaintDevice *dev, QRectF rect, bool asIfEditable) const
Chris@923 375 {
Chris@929 376 QPainter paint(dev);
Chris@923 377
Chris@923 378 paint.setRenderHint(QPainter::Antialiasing, true);
Chris@923 379
Chris@923 380 QPen pen;
Chris@923 381
Chris@929 382 double thin = thinLineWidth(rect);
Chris@938 383
Chris@923 384 pen.setColor(QColor(127, 127, 127, 127));
Chris@929 385 pen.setWidthF(cellLightSize(rect).width() + thin);
Chris@923 386 pen.setCapStyle(Qt::RoundCap);
Chris@923 387 paint.setPen(pen);
Chris@1177 388 paint.setBrush(Qt::NoBrush);
Chris@923 389
Chris@923 390 for (int pan = -maxPan; pan <= maxPan; ++pan) {
Chris@1266 391 paint.drawLine(cellCentre(rect, 0, pan), cellCentre(rect, maxLevel, pan));
Chris@924 392 }
Chris@924 393
Chris@1177 394 if (m_monitorLeft > 0.f || m_monitorRight > 0.f) {
Chris@1177 395 paint.setPen(Qt::NoPen);
Chris@1177 396 for (int pan = -maxPan; pan <= maxPan; ++pan) {
Chris@1177 397 float audioPan = panToAudioPan(pan);
Chris@1177 398 float audioLevel;
Chris@1177 399 if (audioPan < 0.f) {
Chris@1177 400 audioLevel = m_monitorLeft + m_monitorRight * (1.f + audioPan);
Chris@1177 401 } else {
Chris@1177 402 audioLevel = m_monitorRight + m_monitorLeft * (1.f - audioPan);
Chris@1177 403 }
Chris@1177 404 int levelHere = audioLevelToLevel(audioLevel, false);
Chris@1177 405 for (int level = 0; level <= levelHere; ++level) {
Chris@1177 406 paint.setBrush(level_to_colour(level));
Chris@1177 407 QRectF clr = cellLightRect(rect, level, pan);
Chris@1177 408 paint.drawEllipse(clr);
Chris@1177 409 }
Chris@1177 410 }
Chris@1177 411 paint.setPen(pen);
Chris@1177 412 paint.setBrush(Qt::NoBrush);
Chris@1177 413 }
Chris@1177 414
Chris@924 415 if (isEnabled()) {
Chris@1266 416 pen.setColor(Qt::black);
Chris@924 417 } else {
Chris@1266 418 pen.setColor(Qt::darkGray);
Chris@923 419 }
Chris@929 420
Chris@940 421 if (!asIfEditable && m_includeMute && m_level == 0) {
Chris@929 422 pen.setWidthF(thin * 2);
Chris@929 423 pen.setCapStyle(Qt::RoundCap);
Chris@929 424 paint.setPen(pen);
Chris@930 425 paint.drawLine(cellCentre(rect, 0, -maxPan),
Chris@930 426 cellCentre(rect, maxLevel, maxPan));
Chris@930 427 paint.drawLine(cellCentre(rect, maxLevel, -maxPan),
Chris@930 428 cellCentre(rect, 0, maxPan));
Chris@929 429 return;
Chris@929 430 }
Chris@923 431
Chris@923 432 pen.setWidthF(thin);
Chris@923 433 pen.setCapStyle(Qt::FlatCap);
Chris@923 434 paint.setPen(pen);
Chris@923 435
Chris@924 436 for (int level = 0; level <= m_level; ++level) {
Chris@1266 437 if (isEnabled()) {
Chris@1266 438 paint.setBrush(level_to_colour(level));
Chris@1266 439 }
Chris@1266 440 QRectF clr = cellLightRect(rect, level, m_pan);
Chris@1266 441 if (m_includeMute && m_level == 0) {
Chris@1266 442 paint.drawLine(clr.topLeft(), clr.bottomRight());
Chris@1266 443 paint.drawLine(clr.bottomLeft(), clr.topRight());
Chris@1266 444 } else {
Chris@1266 445 paint.drawEllipse(clr);
Chris@1266 446 }
Chris@923 447 }
Chris@923 448 }
Chris@923 449
Chris@929 450 void
Chris@929 451 LevelPanWidget::paintEvent(QPaintEvent *)
Chris@929 452 {
Chris@929 453 renderTo(this, rect(), m_editable);
Chris@929 454 }
Chris@923 455
Chris@1180 456 void
Chris@1180 457 LevelPanWidget::enterEvent(QEvent *e)
Chris@1180 458 {
Chris@1180 459 QWidget::enterEvent(e);
Chris@1180 460 emit mouseEntered();
Chris@1180 461 }
Chris@929 462
Chris@1180 463 void
Chris@1180 464 LevelPanWidget::leaveEvent(QEvent *e)
Chris@1180 465 {
Chris@1180 466 QWidget::enterEvent(e);
Chris@1180 467 emit mouseLeft();
Chris@1180 468 }
Chris@929 469