annotate widgets/LevelPanWidget.cpp @ 1302:f3d3fab250ac

Proper handling for wheel rotation distance
author Chris Cannam
date Fri, 22 Jun 2018 13:41:54 +0100
parents e8368466fa34
children 13f5f84fbfad
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@1301 33 /**
Chris@1301 34 * Gain and pan scales:
Chris@1301 35 *
Chris@1301 36 * Gain: we have 5 circles vertically in the display, each of which
Chris@1301 37 * has half-circle and full-circle versions, and we also have "no
Chris@1301 38 * circles", so there are in total 11 distinct levels, which we refer
Chris@1301 39 * to as "notches" and number 0-10. (We use "notch" because "level" is
Chris@1301 40 * used by the external API to refer to audio gain.)
Chris@1301 41 *
Chris@1301 42 * i.e. the levels are represented by these (schematic, rotated to
Chris@1301 43 * horizontal) displays:
Chris@1301 44 *
Chris@1301 45 * 0 X
Chris@1301 46 * 1 [
Chris@1301 47 * 2 []
Chris@1301 48 * 3 [][
Chris@1301 49 * ...
Chris@1301 50 * 9 [][][][][
Chris@1301 51 * 10 [][][][][]
Chris@1301 52 *
Chris@1301 53 * If we have mute enabled, then we map the range 0-10 to gain using
Chris@1301 54 * AudioLevel::fader_to_* with the ShortFader type, which treats fader
Chris@1301 55 * 0 as muted. If mute is disabled, then we map the range 1-10.
Chris@1301 56 *
Chris@1301 57 * We can also disable half-circles, which leaves the range unchanged
Chris@1301 58 * but limits the notches to even values.
Chris@1301 59 *
Chris@1301 60 * Pan: we have 5 columns with no finer resolution, so we only have 2
Chris@1301 61 * possible pan values on each side of centre.
Chris@1301 62 */
Chris@1301 63
Chris@923 64 static const int maxPan = 2; // range is -maxPan to maxPan
Chris@923 65
Chris@923 66 LevelPanWidget::LevelPanWidget(QWidget *parent) :
Chris@923 67 QWidget(parent),
Chris@1301 68 m_minNotch(0),
Chris@1301 69 m_maxNotch(10),
Chris@1301 70 m_notch(m_maxNotch),
Chris@923 71 m_pan(0),
Chris@1177 72 m_monitorLeft(-1),
Chris@1177 73 m_monitorRight(-1),
Chris@940 74 m_editable(true),
Chris@1249 75 m_editing(false),
Chris@1301 76 m_includeMute(true),
Chris@1302 77 m_includeHalfSteps(true),
Chris@1302 78 m_pendingWheelAngle(0)
Chris@923 79 {
Chris@1191 80 setToolTip(tr("Drag vertically to adjust level, horizontally to adjust pan"));
Chris@1201 81 setLevel(1.0);
Chris@1201 82 setPan(0.0);
Chris@923 83 }
Chris@923 84
Chris@923 85 LevelPanWidget::~LevelPanWidget()
Chris@923 86 {
Chris@923 87 }
Chris@923 88
Chris@1249 89 void
Chris@1249 90 LevelPanWidget::setToDefault()
Chris@1249 91 {
Chris@1249 92 setLevel(1.0);
Chris@1249 93 setPan(0.0);
Chris@1250 94 emitLevelChanged();
Chris@1250 95 emitPanChanged();
Chris@1249 96 }
Chris@1249 97
Chris@929 98 QSize
Chris@929 99 LevelPanWidget::sizeHint() const
Chris@929 100 {
Chris@1176 101 return WidgetScale::scaleQSize(QSize(40, 40));
Chris@929 102 }
Chris@929 103
Chris@1301 104 int
Chris@1301 105 LevelPanWidget::clampNotch(int notch) const
Chris@940 106 {
Chris@1301 107 if (notch < m_minNotch) notch = m_minNotch;
Chris@1301 108 if (notch > m_maxNotch) notch = m_maxNotch;
Chris@1301 109 if (!m_includeHalfSteps) {
Chris@1301 110 notch = (notch / 2) * 2;
Chris@1301 111 }
Chris@1301 112 return notch;
Chris@940 113 }
Chris@940 114
Chris@1177 115 int
Chris@1302 116 LevelPanWidget::clampPan(int pan) const
Chris@1302 117 {
Chris@1302 118 if (pan < -maxPan) pan = -maxPan;
Chris@1302 119 if (pan > maxPan) pan = maxPan;
Chris@1302 120 return pan;
Chris@1302 121 }
Chris@1302 122
Chris@1302 123 int
Chris@1301 124 LevelPanWidget::audioLevelToNotch(float audioLevel) const
Chris@1177 125 {
Chris@1301 126 int notch = AudioLevel::multiplier_to_fader
Chris@1301 127 (audioLevel, m_maxNotch, AudioLevel::ShortFader);
Chris@1301 128 return clampNotch(notch);
Chris@1177 129 }
Chris@1177 130
Chris@1177 131 float
Chris@1301 132 LevelPanWidget::notchToAudioLevel(int notch) const
Chris@1177 133 {
Chris@1301 134 return float(AudioLevel::fader_to_multiplier
Chris@1301 135 (notch, m_maxNotch, AudioLevel::ShortFader));
Chris@1177 136 }
Chris@1177 137
Chris@923 138 void
Chris@1301 139 LevelPanWidget::setLevel(float level)
Chris@923 140 {
Chris@1301 141 int notch = audioLevelToNotch(level);
Chris@1301 142 if (notch != m_notch) {
Chris@1301 143 m_notch = notch;
Chris@1266 144 float convertsTo = getLevel();
Chris@1301 145 if (fabsf(convertsTo - level) > 1e-5) {
Chris@1266 146 emitLevelChanged();
Chris@1266 147 }
Chris@1266 148 update();
Chris@925 149 }
Chris@1301 150 SVCERR << "setLevel: level " << level << " -> notch " << m_notch << " (which converts back to level " << getLevel() << ")" << endl;
Chris@923 151 }
Chris@923 152
Chris@940 153 float
Chris@940 154 LevelPanWidget::getLevel() const
Chris@940 155 {
Chris@1301 156 return notchToAudioLevel(m_notch);
Chris@1177 157 }
Chris@1177 158
Chris@1177 159 int
Chris@1301 160 LevelPanWidget::audioPanToPan(float audioPan) const
Chris@1177 161 {
Chris@1177 162 int pan = int(round(audioPan * maxPan));
Chris@1302 163 pan = clampPan(pan);
Chris@1177 164 return pan;
Chris@1177 165 }
Chris@1177 166
Chris@1177 167 float
Chris@1301 168 LevelPanWidget::panToAudioPan(int pan) const
Chris@1177 169 {
Chris@1177 170 return float(pan) / float(maxPan);
Chris@1177 171 }
Chris@1177 172
Chris@1177 173 void
Chris@1177 174 LevelPanWidget::setPan(float fpan)
Chris@1177 175 {
Chris@1177 176 int pan = audioPanToPan(fpan);
Chris@1177 177 if (pan != m_pan) {
Chris@1177 178 m_pan = pan;
Chris@1177 179 update();
Chris@940 180 }
Chris@940 181 }
Chris@940 182
Chris@1177 183 float
Chris@1177 184 LevelPanWidget::getPan() const
Chris@1177 185 {
Chris@1177 186 return panToAudioPan(m_pan);
Chris@1177 187 }
Chris@1177 188
Chris@923 189 void
Chris@1177 190 LevelPanWidget::setMonitoringLevels(float left, float right)
Chris@923 191 {
Chris@1177 192 m_monitorLeft = left;
Chris@1177 193 m_monitorRight = right;
Chris@923 194 update();
Chris@923 195 }
Chris@923 196
Chris@940 197 bool
Chris@940 198 LevelPanWidget::isEditable() const
Chris@940 199 {
Chris@940 200 return m_editable;
Chris@940 201 }
Chris@940 202
Chris@940 203 bool
Chris@940 204 LevelPanWidget::includesMute() const
Chris@940 205 {
Chris@940 206 return m_includeMute;
Chris@940 207 }
Chris@940 208
Chris@923 209 void
Chris@923 210 LevelPanWidget::setEditable(bool editable)
Chris@923 211 {
Chris@923 212 m_editable = editable;
Chris@923 213 update();
Chris@923 214 }
Chris@923 215
Chris@940 216 void
Chris@940 217 LevelPanWidget::setIncludeMute(bool include)
Chris@923 218 {
Chris@940 219 m_includeMute = include;
Chris@1301 220 if (m_includeMute) {
Chris@1301 221 m_minNotch = 0;
Chris@1301 222 } else {
Chris@1301 223 m_minNotch = 1;
Chris@1301 224 }
Chris@940 225 emitLevelChanged();
Chris@940 226 update();
Chris@923 227 }
Chris@923 228
Chris@923 229 void
Chris@923 230 LevelPanWidget::emitLevelChanged()
Chris@923 231 {
Chris@923 232 emit levelChanged(getLevel());
Chris@923 233 }
Chris@923 234
Chris@923 235 void
Chris@923 236 LevelPanWidget::emitPanChanged()
Chris@923 237 {
Chris@923 238 emit panChanged(getPan());
Chris@923 239 }
Chris@923 240
Chris@923 241 void
Chris@923 242 LevelPanWidget::mousePressEvent(QMouseEvent *e)
Chris@923 243 {
Chris@1249 244 if (e->button() == Qt::MidButton ||
Chris@1249 245 ((e->button() == Qt::LeftButton) &&
Chris@1249 246 (e->modifiers() & Qt::ControlModifier))) {
Chris@1249 247 setToDefault();
Chris@1249 248 } else if (e->button() == Qt::LeftButton) {
Chris@1249 249 m_editing = true;
Chris@1249 250 mouseMoveEvent(e);
Chris@1249 251 }
Chris@1249 252 }
Chris@1249 253
Chris@1249 254 void
Chris@1249 255 LevelPanWidget::mouseReleaseEvent(QMouseEvent *e)
Chris@1249 256 {
Chris@923 257 mouseMoveEvent(e);
Chris@1249 258 m_editing = false;
Chris@923 259 }
Chris@923 260
Chris@923 261 void
Chris@923 262 LevelPanWidget::mouseMoveEvent(QMouseEvent *e)
Chris@923 263 {
Chris@923 264 if (!m_editable) return;
Chris@1249 265 if (!m_editing) return;
Chris@923 266
Chris@1301 267 int notch = coordsToNotch(rect(), e->pos());
Chris@1301 268 int pan = coordsToPan(rect(), e->pos());
Chris@1301 269
Chris@1301 270 if (notch == m_notch && pan == m_pan) {
Chris@1266 271 return;
Chris@923 272 }
Chris@1301 273 if (notch != m_notch) {
Chris@1301 274 m_notch = notch;
Chris@1266 275 emitLevelChanged();
Chris@923 276 }
Chris@923 277 if (pan != m_pan) {
Chris@1266 278 m_pan = pan;
Chris@1266 279 emitPanChanged();
Chris@923 280 }
Chris@923 281 update();
Chris@923 282 }
Chris@923 283
Chris@923 284 void
Chris@923 285 LevelPanWidget::wheelEvent(QWheelEvent *e)
Chris@923 286 {
Chris@1302 287 e->accept();
Chris@1302 288
Chris@1302 289 int dy = e->angleDelta().y();
Chris@1302 290 if (dy == 0) {
Chris@1302 291 return;
Chris@1302 292 }
Chris@1302 293
Chris@1302 294 if (e->phase() == Qt::ScrollBegin ||
Chris@1302 295 std::abs(dy) >= 120 ||
Chris@1302 296 (dy > 0 && m_pendingWheelAngle < 0) ||
Chris@1302 297 (dy < 0 && m_pendingWheelAngle > 0)) {
Chris@1302 298 m_pendingWheelAngle = dy;
Chris@1302 299 } else {
Chris@1302 300 m_pendingWheelAngle += dy;
Chris@1302 301 }
Chris@1302 302
Chris@1302 303 if (abs(m_pendingWheelAngle) >= 600) {
Chris@1302 304 // discard absurd results
Chris@1302 305 m_pendingWheelAngle = 0;
Chris@1302 306 return;
Chris@1302 307 }
Chris@1302 308
Chris@1302 309 while (abs(m_pendingWheelAngle) >= 120) {
Chris@1302 310
Chris@1302 311 int sign = (m_pendingWheelAngle < 0 ? -1 : 1);
Chris@1302 312
Chris@1302 313 if (e->modifiers() & Qt::ControlModifier) {
Chris@1302 314 m_pan += sign;
Chris@1302 315 m_pan = clampPan(m_pan);
Chris@1302 316 emitPanChanged();
Chris@1302 317 update();
Chris@1266 318 } else {
Chris@1302 319 m_notch += sign;
Chris@1302 320 m_notch = clampNotch(m_notch);
Chris@1302 321 emitLevelChanged();
Chris@1302 322 update();
Chris@1266 323 }
Chris@1302 324
Chris@1302 325 m_pendingWheelAngle -= sign * 120;
Chris@923 326 }
Chris@923 327 }
Chris@923 328
Chris@1301 329 int
Chris@1301 330 LevelPanWidget::coordsToNotch(QRectF rect, QPointF loc) const
Chris@923 331 {
Chris@1301 332 double h = rect.height();
Chris@1301 333
Chris@1301 334 int nnotch = m_maxNotch + 1;
Chris@1301 335 double cell = h / nnotch;
Chris@1301 336
Chris@1301 337 int notch = int((h - (loc.y() - rect.y())) / cell);
Chris@1301 338 notch = clampNotch(notch);
Chris@1301 339
Chris@1301 340 return notch;
Chris@1301 341 }
Chris@1301 342
Chris@1301 343 int
Chris@1301 344 LevelPanWidget::coordsToPan(QRectF rect, QPointF loc) const
Chris@1301 345 {
Chris@1301 346 double w = rect.width();
Chris@929 347
Chris@923 348 int npan = maxPan * 2 + 1;
Chris@1301 349 double cell = w / npan;
Chris@929 350
Chris@1301 351 int pan = int((loc.x() - rect.x()) / cell) - maxPan;
Chris@1302 352 pan = clampPan(pan);
Chris@1301 353
Chris@1301 354 return pan;
Chris@923 355 }
Chris@923 356
Chris@923 357 QSizeF
Chris@929 358 LevelPanWidget::cellSize(QRectF rect) const
Chris@923 359 {
Chris@929 360 double w = rect.width(), h = rect.height();
Chris@1301 361 int ncol = maxPan * 2 + 1;
Chris@1301 362 int nrow = m_maxNotch/2;
Chris@1301 363 double wcell = w / ncol, hcell = h / nrow;
Chris@923 364 return QSizeF(wcell, hcell);
Chris@923 365 }
Chris@923 366
Chris@923 367 QPointF
Chris@1301 368 LevelPanWidget::cellCentre(QRectF rect, int row, int col) const
Chris@923 369 {
Chris@929 370 QSizeF cs = cellSize(rect);
Chris@1301 371 return QPointF(rect.x() +
Chris@1301 372 cs.width() * (col + maxPan) + cs.width() / 2.,
Chris@1301 373 rect.y() + rect.height() -
Chris@1301 374 cs.height() * (row + 1) + cs.height() / 2.);
Chris@923 375 }
Chris@923 376
Chris@923 377 QSizeF
Chris@929 378 LevelPanWidget::cellLightSize(QRectF rect) const
Chris@923 379 {
Chris@923 380 double extent = 3. / 4.;
Chris@929 381 QSizeF cs = cellSize(rect);
Chris@923 382 double m = std::min(cs.width(), cs.height());
Chris@923 383 return QSizeF(m * extent, m * extent);
Chris@923 384 }
Chris@923 385
Chris@923 386 QRectF
Chris@1301 387 LevelPanWidget::cellLightRect(QRectF rect, int row, int col) const
Chris@923 388 {
Chris@929 389 QSizeF cls = cellLightSize(rect);
Chris@1301 390 QPointF cc = cellCentre(rect, row, col);
Chris@923 391 return QRectF(cc.x() - cls.width() / 2.,
Chris@1266 392 cc.y() - cls.height() / 2.,
Chris@1266 393 cls.width(),
Chris@1266 394 cls.height());
Chris@923 395 }
Chris@923 396
Chris@923 397 double
Chris@929 398 LevelPanWidget::thinLineWidth(QRectF rect) const
Chris@923 399 {
Chris@929 400 double tw = ceil(rect.width() / (maxPan * 2. * 10.));
Chris@1301 401 double th = ceil(rect.height() / (m_maxNotch/2 * 10.));
Chris@923 402 return std::min(th, tw);
Chris@923 403 }
Chris@923 404
Chris@1301 405 QRectF
Chris@1301 406 LevelPanWidget::cellOutlineRect(QRectF rect, int row, int col) const
Chris@941 407 {
Chris@1301 408 QRectF clr = cellLightRect(rect, row, col);
Chris@1301 409 double adj = thinLineWidth(rect)/2;
Chris@1301 410 return clr.adjusted(-adj, -adj, adj, adj);
Chris@1301 411 }
Chris@1301 412
Chris@1301 413 QColor
Chris@1301 414 LevelPanWidget::notchToColour(int notch) const
Chris@1301 415 {
Chris@1301 416 if (notch < 3) return Qt::black;
Chris@1301 417 if (notch < 5) return QColor(80, 0, 0);
Chris@1301 418 if (notch < 7) return QColor(160, 0, 0);
Chris@1301 419 if (notch < 9) return QColor(255, 0, 0);
Chris@1301 420 return QColor(255, 255, 0);
Chris@941 421 }
Chris@941 422
Chris@923 423 void
Chris@929 424 LevelPanWidget::renderTo(QPaintDevice *dev, QRectF rect, bool asIfEditable) const
Chris@923 425 {
Chris@929 426 QPainter paint(dev);
Chris@923 427
Chris@923 428 paint.setRenderHint(QPainter::Antialiasing, true);
Chris@923 429
Chris@923 430 QPen pen;
Chris@923 431
Chris@929 432 double thin = thinLineWidth(rect);
Chris@938 433
Chris@1301 434 QColor columnBackground = QColor(180, 180, 180);
Chris@1301 435 pen.setColor(columnBackground);
Chris@929 436 pen.setWidthF(cellLightSize(rect).width() + thin);
Chris@923 437 pen.setCapStyle(Qt::RoundCap);
Chris@923 438 paint.setPen(pen);
Chris@1177 439 paint.setBrush(Qt::NoBrush);
Chris@923 440
Chris@923 441 for (int pan = -maxPan; pan <= maxPan; ++pan) {
Chris@1301 442 paint.drawLine(cellCentre(rect, 0, pan),
Chris@1301 443 cellCentre(rect, m_maxNotch/2 - 1, pan));
Chris@924 444 }
Chris@924 445
Chris@1301 446 bool monitoring = (m_monitorLeft > 0.f || m_monitorRight > 0.f);
Chris@1301 447
Chris@1301 448 if (isEnabled()) {
Chris@1301 449 pen.setColor(Qt::black);
Chris@1301 450 } else {
Chris@1301 451 pen.setColor(Qt::darkGray);
Chris@1301 452 }
Chris@1301 453
Chris@1301 454 if (!asIfEditable && m_includeMute && m_notch == 0) {
Chris@1301 455 // The X for mute takes up the whole display when we're not
Chris@1301 456 // being rendered in editable style
Chris@1301 457 pen.setWidthF(thin * 2);
Chris@1301 458 pen.setCapStyle(Qt::RoundCap);
Chris@1301 459 paint.setPen(pen);
Chris@1301 460 paint.drawLine(cellCentre(rect, 0, -maxPan),
Chris@1301 461 cellCentre(rect, m_maxNotch/2 - 1, maxPan));
Chris@1301 462 paint.drawLine(cellCentre(rect, m_maxNotch/2 - 1, -maxPan),
Chris@1301 463 cellCentre(rect, 0, maxPan));
Chris@1301 464 } else {
Chris@1301 465 // the normal case
Chris@1301 466
Chris@1301 467 // pen a bit less thin than in theory, so that we can erase
Chris@1301 468 // semi-circles later without leaving a faint edge
Chris@1301 469 pen.setWidthF(thin * 0.8);
Chris@1301 470 pen.setCapStyle(Qt::FlatCap);
Chris@1301 471 paint.setPen(pen);
Chris@1301 472
Chris@1301 473 if (m_includeMute && m_notch == 0) {
Chris@1301 474 QRectF clr = cellLightRect(rect, 0, m_pan);
Chris@1301 475 paint.drawLine(clr.topLeft(), clr.bottomRight());
Chris@1301 476 paint.drawLine(clr.bottomLeft(), clr.topRight());
Chris@1301 477 } else {
Chris@1301 478 for (int notch = 1; notch <= m_notch; notch += 2) {
Chris@1301 479 if (isEnabled() && !monitoring) {
Chris@1301 480 paint.setBrush(notchToColour(notch));
Chris@1301 481 }
Chris@1301 482 QRectF clr = cellLightRect(rect, notch/2, m_pan);
Chris@1301 483 paint.drawEllipse(clr);
Chris@1301 484 }
Chris@1301 485 if (m_notch % 2 != 0) {
Chris@1301 486 QRectF clr = cellOutlineRect(rect, (m_notch-1)/2, m_pan);
Chris@1301 487 paint.save();
Chris@1301 488 paint.setPen(Qt::NoPen);
Chris@1301 489 paint.setBrush(columnBackground);
Chris@1301 490 paint.drawPie(clr, 0, 180 * 16);
Chris@1301 491 paint.restore();
Chris@1301 492 }
Chris@1301 493 }
Chris@1301 494 }
Chris@1301 495
Chris@1301 496 if (monitoring) {
Chris@1177 497 paint.setPen(Qt::NoPen);
Chris@1177 498 for (int pan = -maxPan; pan <= maxPan; ++pan) {
Chris@1177 499 float audioPan = panToAudioPan(pan);
Chris@1177 500 float audioLevel;
Chris@1177 501 if (audioPan < 0.f) {
Chris@1177 502 audioLevel = m_monitorLeft + m_monitorRight * (1.f + audioPan);
Chris@1177 503 } else {
Chris@1177 504 audioLevel = m_monitorRight + m_monitorLeft * (1.f - audioPan);
Chris@1177 505 }
Chris@1301 506 int notchHere = audioLevelToNotch(audioLevel);
Chris@1301 507 for (int notch = 1; notch <= notchHere; notch += 2) {
Chris@1301 508 paint.setBrush(notchToColour(notch));
Chris@1301 509 QRectF clr = cellLightRect(rect, (notch-1)/2, pan);
Chris@1301 510 double adj = thinLineWidth(rect)/2;
Chris@1301 511 clr = clr.adjusted(adj, adj, -adj, -adj);
Chris@1301 512 if (notch + 2 > notchHere && notchHere % 2 != 0) {
Chris@1301 513 paint.drawPie(clr, 180 * 16, 180 * 16);
Chris@1301 514 } else {
Chris@1301 515 paint.drawEllipse(clr);
Chris@1301 516 }
Chris@1177 517 }
Chris@1177 518 }
Chris@1177 519 paint.setPen(pen);
Chris@1177 520 paint.setBrush(Qt::NoBrush);
Chris@1177 521 }
Chris@923 522 }
Chris@923 523
Chris@929 524 void
Chris@929 525 LevelPanWidget::paintEvent(QPaintEvent *)
Chris@929 526 {
Chris@929 527 renderTo(this, rect(), m_editable);
Chris@929 528 }
Chris@923 529
Chris@1180 530 void
Chris@1180 531 LevelPanWidget::enterEvent(QEvent *e)
Chris@1180 532 {
Chris@1180 533 QWidget::enterEvent(e);
Chris@1180 534 emit mouseEntered();
Chris@1180 535 }
Chris@929 536
Chris@1180 537 void
Chris@1180 538 LevelPanWidget::leaveEvent(QEvent *e)
Chris@1180 539 {
Chris@1180 540 QWidget::enterEvent(e);
Chris@1180 541 emit mouseLeft();
Chris@1180 542 }
Chris@929 543