File indexing completed on 2024-04-14 04:47:02

0001 /*
0002     SPDX-FileCopyrightText: 2010 Simon Andreas Eugster <simon.eu@gmail.com>
0003     This file is part of kdenlive. See www.kdenlive.org.
0004 
0005 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "abstractscopewidget.h"
0009 
0010 #include "monitor/monitor.h"
0011 
0012 #include <QColor>
0013 #include <QMouseEvent>
0014 #include <QPainter>
0015 #include <QtConcurrent>
0016 
0017 #include "klocalizedstring.h"
0018 #include <KConfigGroup>
0019 #include <KSharedConfig>
0020 #include <cmath>
0021 // Uncomment for Scope debugging.
0022 //#define DEBUG_ASW
0023 
0024 #ifdef DEBUG_ASW
0025 #include "kdenlive_debug.h"
0026 #endif
0027 
0028 const int REALTIME_FPS = 30;
0029 
0030 const QColor light(250, 238, 226, 255);
0031 const QColor dark(40, 40, 39, 255);
0032 const QColor dark2(25, 25, 23, 255);
0033 const QColor AbstractScopeWidget::colHighlightLight(18, 130, 255, 255);
0034 const QColor AbstractScopeWidget::colHighlightDark(255, 64, 19, 255);
0035 const QColor AbstractScopeWidget::colDarkWhite(250, 250, 250);
0036 
0037 const QPen AbstractScopeWidget::penThick(QBrush(AbstractScopeWidget::colDarkWhite.rgb()), 2, Qt::SolidLine);
0038 const QPen AbstractScopeWidget::penThin(QBrush(AbstractScopeWidget::colDarkWhite.rgb()), 1, Qt::SolidLine);
0039 const QPen AbstractScopeWidget::penLight(QBrush(QColor(200, 200, 250, 150)), 1, Qt::SolidLine);
0040 const QPen AbstractScopeWidget::penLightDots(QBrush(QColor(200, 200, 250, 150)), 1, Qt::DotLine);
0041 const QPen AbstractScopeWidget::penLighter(QBrush(QColor(225, 225, 250, 225)), 1, Qt::SolidLine);
0042 const QPen AbstractScopeWidget::penDark(QBrush(QColor(0, 0, 20, 250)), 1, Qt::SolidLine);
0043 const QPen AbstractScopeWidget::penDarkDots(QBrush(QColor(0, 0, 20, 250)), 1, Qt::DotLine);
0044 const QPen AbstractScopeWidget::penBackground(QBrush(dark2), 1, Qt::SolidLine);
0045 
0046 const QString AbstractScopeWidget::directions[] = {QStringLiteral("North"), QStringLiteral("Northeast"), QStringLiteral("East"), QStringLiteral("Southeast")};
0047 
0048 AbstractScopeWidget::AbstractScopeWidget(bool trackMouse, QWidget *parent)
0049     : QWidget(parent)
0050     , m_scopePalette(QPalette())
0051     , m_mousePos(0, 0)
0052     , m_semaphoreHUD(1)
0053     , m_semaphoreScope(1)
0054     , m_semaphoreBackground(1)
0055 
0056 {
0057     m_scopePalette.setBrush(QPalette::Window, QBrush(dark2));
0058     m_scopePalette.setBrush(QPalette::Base, QBrush(dark));
0059     m_scopePalette.setBrush(QPalette::Button, QBrush(dark));
0060     m_scopePalette.setBrush(QPalette::Text, QBrush(light));
0061     m_scopePalette.setBrush(QPalette::WindowText, QBrush(light));
0062     m_scopePalette.setBrush(QPalette::ButtonText, QBrush(light));
0063     setPalette(m_scopePalette);
0064     setAutoFillBackground(true);
0065 
0066     m_aAutoRefresh = new QAction(i18n("Auto Refresh"), this);
0067     m_aAutoRefresh->setCheckable(true);
0068     m_aRealtime = new QAction(i18n("Realtime (with precision loss)"), this);
0069     m_aRealtime->setCheckable(true);
0070 
0071     m_menu = new QMenu();
0072     // Disabled dark palette on menus since it breaks up with some themes: kdenlive issue #2950
0073     // m_menu->setPalette(m_scopePalette);
0074     m_menu->addAction(m_aAutoRefresh);
0075     m_menu->addAction(m_aRealtime);
0076 
0077     setContextMenuPolicy(Qt::CustomContextMenu);
0078 
0079     connect(this, &AbstractScopeWidget::customContextMenuRequested, this, &AbstractScopeWidget::slotContextMenuRequested);
0080 
0081     connect(this, &AbstractScopeWidget::signalHUDRenderingFinished, this, &AbstractScopeWidget::slotHUDRenderingFinished);
0082     connect(this, &AbstractScopeWidget::signalScopeRenderingFinished, this, &AbstractScopeWidget::slotScopeRenderingFinished);
0083     connect(this, &AbstractScopeWidget::signalBackgroundRenderingFinished, this, &AbstractScopeWidget::slotBackgroundRenderingFinished);
0084     connect(m_aRealtime, &QAction::toggled, this, &AbstractScopeWidget::slotResetRealtimeFactor);
0085     connect(m_aAutoRefresh, &QAction::toggled, this, &AbstractScopeWidget::slotAutoRefreshToggled);
0086 
0087     // Enable mouse tracking if desired.
0088     // Causes the mouseMoved signal to be emitted when the mouse moves inside the
0089     // widget, even when no mouse button is pressed.
0090     this->setMouseTracking(trackMouse);
0091 }
0092 
0093 AbstractScopeWidget::~AbstractScopeWidget()
0094 {
0095     writeConfig();
0096 
0097     delete m_menu;
0098     delete m_aAutoRefresh;
0099     delete m_aRealtime;
0100 }
0101 
0102 void AbstractScopeWidget::init()
0103 {
0104     m_widgetName = widgetName();
0105     readConfig();
0106 }
0107 
0108 void AbstractScopeWidget::readConfig()
0109 {
0110     KSharedConfigPtr config = KSharedConfig::openConfig();
0111     KConfigGroup scopeConfig(config, configName());
0112     m_aAutoRefresh->setChecked(scopeConfig.readEntry("autoRefresh", true));
0113     m_aRealtime->setChecked(scopeConfig.readEntry("realtime", false));
0114     scopeConfig.sync();
0115 }
0116 
0117 void AbstractScopeWidget::writeConfig()
0118 {
0119     KSharedConfigPtr config = KSharedConfig::openConfig();
0120     KConfigGroup scopeConfig(config, configName());
0121     scopeConfig.writeEntry("autoRefresh", m_aAutoRefresh->isChecked());
0122     scopeConfig.writeEntry("realtime", m_aRealtime->isChecked());
0123     scopeConfig.sync();
0124 }
0125 
0126 QString AbstractScopeWidget::configName()
0127 {
0128     return "Scope_" + m_widgetName;
0129 }
0130 
0131 void AbstractScopeWidget::prodHUDThread()
0132 {
0133     if (this->visibleRegion().isEmpty()) {
0134 #ifdef DEBUG_ASW
0135         qCDebug(KDENLIVE_LOG) << "Scope " << m_widgetName << " is not visible. Not calculating HUD.";
0136 #endif
0137     } else {
0138         if (m_semaphoreHUD.tryAcquire(1)) {
0139             Q_ASSERT(!m_threadHUD.isRunning());
0140 
0141             m_newHUDFrames.fetchAndStoreRelaxed(0);
0142             m_newHUDUpdates.fetchAndStoreRelaxed(0);
0143 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0144             m_threadHUD = QtConcurrent::run(this, &AbstractScopeWidget::renderHUD, m_accelFactorHUD);
0145 #else
0146             m_threadHUD = QtConcurrent::run(&AbstractScopeWidget::renderHUD, this, m_accelFactorHUD);
0147 #endif
0148 #ifdef DEBUG_ASW
0149             qCDebug(KDENLIVE_LOG) << "HUD thread started in " << m_widgetName;
0150 #endif
0151 
0152         }
0153 #ifdef DEBUG_ASW
0154         else {
0155             qCDebug(KDENLIVE_LOG) << "HUD semaphore locked, not prodding in " << m_widgetName << ". Thread running: " << m_threadHUD.isRunning();
0156         }
0157 #endif
0158     }
0159 }
0160 
0161 void AbstractScopeWidget::prodScopeThread()
0162 {
0163     // Only start a new thread if the scope is actually visible
0164     // and not hidden by another widget on the stack and if user want the scope to update.
0165     if (this->visibleRegion().isEmpty() || (!m_aAutoRefresh->isChecked() && !m_requestForcedUpdate)) {
0166 #ifdef DEBUG_ASW
0167         qCDebug(KDENLIVE_LOG) << "Scope " << m_widgetName << " is not visible. Not calculating scope.";
0168 #endif
0169     } else {
0170         // Try to acquire the semaphore. This must only succeed if m_threadScope is not running
0171         // anymore. Therefore the semaphore must NOT be released before m_threadScope ends.
0172         // If acquiring the semaphore fails, the thread is still running.
0173         if (m_semaphoreScope.tryAcquire(1)) {
0174             Q_ASSERT(!m_threadScope.isRunning());
0175 
0176             m_newScopeFrames.fetchAndStoreRelaxed(0);
0177             m_newScopeUpdates.fetchAndStoreRelaxed(0);
0178 
0179             Q_ASSERT(m_accelFactorScope > 0);
0180 
0181             // See https://doc.qt.io/qt-5/qtconcurrentrun.html about
0182             // running member functions in a thread
0183 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0184             m_threadScope = QtConcurrent::run(this, &AbstractScopeWidget::renderScope, m_accelFactorScope);
0185 #else
0186             m_threadScope = QtConcurrent::run(&AbstractScopeWidget::renderScope, this, m_accelFactorScope);
0187 #endif
0188             m_requestForcedUpdate = false;
0189 
0190 #ifdef DEBUG_ASW
0191             qCDebug(KDENLIVE_LOG) << "Scope thread started in " << m_widgetName;
0192 #endif
0193 
0194         } else {
0195 #ifdef DEBUG_ASW
0196             qCDebug(KDENLIVE_LOG) << "Scope semaphore locked, not prodding in " << m_widgetName << ". Thread running: " << m_threadScope.isRunning();
0197 #endif
0198         }
0199     }
0200 }
0201 void AbstractScopeWidget::prodBackgroundThread()
0202 {
0203     if (this->visibleRegion().isEmpty()) {
0204 #ifdef DEBUG_ASW
0205         qCDebug(KDENLIVE_LOG) << "Scope " << m_widgetName << " is not visible. Not calculating background.";
0206 #endif
0207     } else {
0208         if (m_semaphoreBackground.tryAcquire(1)) {
0209             Q_ASSERT(!m_threadBackground.isRunning());
0210 
0211             m_newBackgroundFrames.fetchAndStoreRelaxed(0);
0212             m_newBackgroundUpdates.fetchAndStoreRelaxed(0);
0213 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0214             m_threadBackground = QtConcurrent::run(this, &AbstractScopeWidget::renderBackground, m_accelFactorBackground);
0215 #else
0216             m_threadBackground = QtConcurrent::run(&AbstractScopeWidget::renderBackground, this, m_accelFactorBackground);
0217 #endif
0218 
0219 #ifdef DEBUG_ASW
0220             qCDebug(KDENLIVE_LOG) << "Background thread started in " << m_widgetName;
0221 #endif
0222 
0223         } else {
0224 #ifdef DEBUG_ASW
0225             qCDebug(KDENLIVE_LOG) << "Background semaphore locked, not prodding in " << m_widgetName << ". Thread running: " << m_threadBackground.isRunning();
0226 #endif
0227         }
0228     }
0229 }
0230 
0231 void AbstractScopeWidget::forceUpdate(bool doUpdate)
0232 {
0233 #ifdef DEBUG_ASW
0234     qCDebug(KDENLIVE_LOG) << "Forced update called in " << widgetName() << ". Arg: " << doUpdate;
0235 #endif
0236 
0237     if (!doUpdate) {
0238         return;
0239     }
0240     m_requestForcedUpdate = true;
0241     m_newHUDUpdates.fetchAndAddRelaxed(1);
0242     m_newScopeUpdates.fetchAndAddRelaxed(1);
0243     m_newBackgroundUpdates.fetchAndAddRelaxed(1);
0244     prodHUDThread();
0245     prodScopeThread();
0246     prodBackgroundThread();
0247 }
0248 void AbstractScopeWidget::forceUpdateHUD()
0249 {
0250     m_newHUDUpdates.fetchAndAddRelaxed(1);
0251     prodHUDThread();
0252 }
0253 void AbstractScopeWidget::forceUpdateScope()
0254 {
0255     m_newScopeUpdates.fetchAndAddRelaxed(1);
0256     m_requestForcedUpdate = true;
0257     prodScopeThread();
0258 }
0259 void AbstractScopeWidget::forceUpdateBackground()
0260 {
0261     m_newBackgroundUpdates.fetchAndAddRelaxed(1);
0262     prodBackgroundThread();
0263 }
0264 
0265 ///// Events /////
0266 
0267 void AbstractScopeWidget::resizeEvent(QResizeEvent *event)
0268 {
0269     // Update the dimension of the available rect for painting
0270     m_scopeRect = scopeRect();
0271     forceUpdate();
0272 
0273     QWidget::resizeEvent(event);
0274 }
0275 
0276 void AbstractScopeWidget::showEvent(QShowEvent *event)
0277 {
0278     QWidget::showEvent(event);
0279     m_scopeRect = scopeRect();
0280 }
0281 
0282 void AbstractScopeWidget::paintEvent(QPaintEvent *)
0283 {
0284     QPainter davinci;
0285     bool ok = davinci.begin(this);
0286     if (!ok) {
0287         if (!m_scopeWarningPrinted) {
0288             qDebug() << "Warning: Could not initialise painter for drawing scope.";
0289             m_scopeWarningPrinted = true;
0290         }
0291         return;
0292     }
0293     davinci.drawImage(m_scopeRect.topLeft(), m_imgBackground);
0294     davinci.drawImage(m_scopeRect.topLeft(), m_imgScope);
0295     davinci.drawImage(m_scopeRect.topLeft(), m_imgHUD);
0296 }
0297 
0298 void AbstractScopeWidget::mousePressEvent(QMouseEvent *event)
0299 {
0300     if (event->button() == Qt::LeftButton) {
0301         // Rescaling mode starts
0302         m_rescaleActive = true;
0303         m_rescalePropertiesLocked = false;
0304         m_rescaleFirstRescaleDone = false;
0305         m_rescaleStartPoint = event->pos();
0306         m_rescaleModifiers = event->modifiers();
0307     }
0308 }
0309 void AbstractScopeWidget::mouseReleaseEvent(QMouseEvent *event)
0310 {
0311     m_rescaleActive = false;
0312     m_rescalePropertiesLocked = false;
0313 
0314     if (!m_aAutoRefresh->isChecked()) {
0315         m_requestForcedUpdate = true;
0316     }
0317     prodHUDThread();
0318     prodScopeThread();
0319     prodBackgroundThread();
0320     QWidget::mouseReleaseEvent(event);
0321 }
0322 void AbstractScopeWidget::mouseMoveEvent(QMouseEvent *event)
0323 {
0324     m_mousePos = event->pos();
0325     m_mouseWithinWidget = true;
0326     Q_EMIT signalMousePositionChanged();
0327 
0328     QPoint movement = event->pos() - m_rescaleStartPoint;
0329 
0330     if (m_rescaleActive) {
0331         if (m_rescalePropertiesLocked) {
0332             // Direction is known, now adjust parameters
0333 
0334             // Reset the starting point to make the next moveEvent relative to the current one
0335             m_rescaleStartPoint = event->pos();
0336 
0337             if (!m_rescaleFirstRescaleDone) {
0338                 // We have just learned the desired direction; Normalize the movement to one pixel
0339                 // to avoid a jump by m_rescaleMinDist
0340 
0341                 if (movement.x() != 0) {
0342                     movement.setX(movement.x() / abs(movement.x()));
0343                 }
0344                 if (movement.y() != 0) {
0345                     movement.setY(movement.y() / abs(movement.y()));
0346                 }
0347 
0348                 m_rescaleFirstRescaleDone = true;
0349             }
0350 
0351             handleMouseDrag(movement, m_rescaleDirection, m_rescaleModifiers);
0352 
0353         } else {
0354             // Detect the movement direction here.
0355             // This algorithm relies on the aspect ratio of dy/dx (size and signum).
0356             if (movement.manhattanLength() > m_rescaleMinDist) {
0357                 float diff = float(movement.y()) / movement.x();
0358 
0359                 if (std::fabs(diff) > m_rescaleVerticalThreshold || movement.x() == 0) {
0360                     m_rescaleDirection = North;
0361                 } else if (std::fabs(diff) < 1 / m_rescaleVerticalThreshold) {
0362                     m_rescaleDirection = East;
0363                 } else if (diff < 0) {
0364                     m_rescaleDirection = Northeast;
0365                 } else {
0366                     m_rescaleDirection = Southeast;
0367                 }
0368 #ifdef DEBUG_ASW
0369                 qCDebug(KDENLIVE_LOG) << "Diff is " << diff << "; chose " << directions[m_rescaleDirection] << " as direction";
0370 #endif
0371                 m_rescalePropertiesLocked = true;
0372             }
0373         }
0374     }
0375 }
0376 
0377 void AbstractScopeWidget::leaveEvent(QEvent *)
0378 {
0379     m_mouseWithinWidget = false;
0380     Q_EMIT signalMousePositionChanged();
0381 }
0382 
0383 void AbstractScopeWidget::slotContextMenuRequested(const QPoint &pos)
0384 {
0385     m_menu->exec(this->mapToGlobal(pos));
0386 }
0387 
0388 uint AbstractScopeWidget::calculateAccelFactorHUD(uint oldMseconds, uint)
0389 {
0390     return uint(std::ceil(float(oldMseconds) * REALTIME_FPS / 1000.f));
0391 }
0392 uint AbstractScopeWidget::calculateAccelFactorScope(uint oldMseconds, uint)
0393 {
0394     return uint(std::ceil(float(oldMseconds) * REALTIME_FPS / 1000.f));
0395 }
0396 uint AbstractScopeWidget::calculateAccelFactorBackground(uint oldMseconds, uint)
0397 {
0398     return uint(std::ceil(float(oldMseconds) * REALTIME_FPS / 1000.f));
0399 }
0400 
0401 ///// Slots /////
0402 
0403 void AbstractScopeWidget::slotHUDRenderingFinished(uint mseconds, uint oldFactor)
0404 {
0405 #ifdef DEBUG_ASW
0406     qCDebug(KDENLIVE_LOG) << "HUD rendering has finished in " << mseconds << " ms, waiting for termination in " << m_widgetName;
0407 #endif
0408     m_threadHUD.waitForFinished();
0409     m_imgHUD = m_threadHUD.result();
0410 
0411     m_semaphoreHUD.release(1);
0412     this->update();
0413 
0414     if (m_aRealtime->isChecked()) {
0415         int accel;
0416         accel = int(calculateAccelFactorHUD(mseconds, oldFactor));
0417         if (m_accelFactorHUD < 1) {
0418             accel = 1;
0419         }
0420         m_accelFactorHUD = accel;
0421     }
0422 
0423     if ((m_newHUDFrames > 0 && m_aAutoRefresh->isChecked()) || m_newHUDUpdates > 0) {
0424 #ifdef DEBUG_ASW
0425         qCDebug(KDENLIVE_LOG) << "Trying to start a new HUD thread for " << m_widgetName << ". New frames/updates: " << m_newHUDFrames << '/'
0426                               << m_newHUDUpdates;
0427 #endif
0428         prodHUDThread();
0429     }
0430 }
0431 
0432 void AbstractScopeWidget::slotScopeRenderingFinished(uint mseconds, uint oldFactor)
0433 {
0434 // The signal can be received before the thread has really finished. So we
0435 // need to wait until it has really finished before starting a new thread.
0436 #ifdef DEBUG_ASW
0437     qCDebug(KDENLIVE_LOG) << "Scope rendering has finished in " << mseconds << " ms, waiting for termination in " << m_widgetName;
0438 #endif
0439     m_threadScope.waitForFinished();
0440     m_imgScope = m_threadScope.result();
0441 
0442     // The scope thread has finished. Now we can release the semaphore, allowing a new thread.
0443     // See prodScopeThread where the semaphore is acquired again.
0444     m_semaphoreScope.release(1);
0445     this->update();
0446 
0447     // Calculate the acceleration factor hint to get «realtime» updates.
0448     if (m_aRealtime->isChecked()) {
0449         int accel;
0450         accel = int(calculateAccelFactorScope(mseconds, oldFactor));
0451         if (accel < 1) {
0452             // If mseconds happens to be 0.
0453             accel = 1;
0454         }
0455         // Don't directly calculate with m_accelFactorScope as we are dealing with concurrency.
0456         // If m_accelFactorScope is set to 0 at the wrong moment, who knows what might happen
0457         // then :) Therefore use a local variable.
0458         m_accelFactorScope = accel;
0459     }
0460 
0461     if ((m_newScopeFrames > 0 && m_aAutoRefresh->isChecked()) || m_newScopeUpdates > 0) {
0462 #ifdef DEBUG_ASW
0463         qCDebug(KDENLIVE_LOG) << "Trying to start a new scope thread for " << m_widgetName << ". New frames/updates: " << m_newScopeFrames << '/'
0464                               << m_newScopeUpdates;
0465 #endif
0466         prodScopeThread();
0467     }
0468 }
0469 
0470 void AbstractScopeWidget::slotBackgroundRenderingFinished(uint mseconds, uint oldFactor)
0471 {
0472 #ifdef DEBUG_ASW
0473     qCDebug(KDENLIVE_LOG) << "Background rendering has finished in " << mseconds << " ms, waiting for termination in " << m_widgetName;
0474 #endif
0475     m_threadBackground.waitForFinished();
0476     m_imgBackground = m_threadBackground.result();
0477 
0478     m_semaphoreBackground.release(1);
0479     this->update();
0480 
0481     if (m_aRealtime->isChecked()) {
0482         int accel;
0483         accel = int(calculateAccelFactorBackground(mseconds, oldFactor));
0484         if (m_accelFactorBackground < 1) {
0485             accel = 1;
0486         }
0487         m_accelFactorBackground = accel;
0488     }
0489 
0490     if ((m_newBackgroundFrames > 0 && m_aAutoRefresh->isChecked()) || m_newBackgroundUpdates > 0) {
0491 #ifdef DEBUG_ASW
0492         qCDebug(KDENLIVE_LOG) << "Trying to start a new background thread for " << m_widgetName << ". New frames/updates: " << m_newBackgroundFrames << '/'
0493                               << m_newBackgroundUpdates;
0494 #endif
0495         prodBackgroundThread();
0496     }
0497 }
0498 
0499 void AbstractScopeWidget::slotRenderZoneUpdated()
0500 {
0501     m_newHUDFrames.fetchAndAddRelaxed(1);
0502     m_newScopeFrames.fetchAndAddRelaxed(1);
0503     m_newBackgroundFrames.fetchAndAddRelaxed(1);
0504 
0505 #ifdef DEBUG_ASW
0506     qCDebug(KDENLIVE_LOG) << "Data incoming at " << widgetName() << ". New frames total HUD/Scope/Background: " << m_newHUDFrames << '/' << m_newScopeFrames
0507                           << '/' << m_newBackgroundFrames;
0508 #endif
0509 
0510     if (this->visibleRegion().isEmpty()) {
0511 #ifdef DEBUG_ASW
0512         qCDebug(KDENLIVE_LOG) << "Scope of widget " << m_widgetName << " is not at the top, not rendering.";
0513 #endif
0514     } else {
0515         if (m_aAutoRefresh->isChecked()) {
0516             prodHUDThread();
0517             prodScopeThread();
0518             prodBackgroundThread();
0519         }
0520     }
0521 }
0522 
0523 void AbstractScopeWidget::slotResetRealtimeFactor(bool realtimeChecked)
0524 {
0525     if (!realtimeChecked) {
0526         m_accelFactorHUD = 1;
0527         m_accelFactorScope = 1;
0528         m_accelFactorBackground = 1;
0529     }
0530 }
0531 
0532 bool AbstractScopeWidget::autoRefreshEnabled() const
0533 {
0534     return m_aAutoRefresh->isChecked();
0535 }
0536 
0537 void AbstractScopeWidget::slotAutoRefreshToggled(bool autoRefresh)
0538 {
0539 #ifdef DEBUG_ASW
0540     qCDebug(KDENLIVE_LOG) << "Auto-refresh switched to " << autoRefresh << " in " << widgetName() << " (Visible: " << isVisible() << '/'
0541                           << this->visibleRegion().isEmpty() << ')';
0542 #endif
0543     if (isVisible()) {
0544         // Notify listeners whether we accept new frames now
0545         Q_EMIT requestAutoRefresh(autoRefresh);
0546     }
0547     // TODO only if depends on input
0548     if (autoRefresh) {
0549         // forceUpdate();
0550         m_requestForcedUpdate = true;
0551     }
0552 }
0553 
0554 void AbstractScopeWidget::handleMouseDrag(const QPoint &, const RescaleDirection, const Qt::KeyboardModifiers) {}
0555 
0556 #ifdef DEBUG_ASW
0557 #undef DEBUG_ASW
0558 #endif