annotate widgets/LevelPanWidget.cpp @ 1193:54e6be7ebe11 levelpanwidget

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