annotate widgets/LevelPanWidget.cpp @ 1249:8ef67917c301

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