From: Simon A. Eugster Date: Fri, 3 Dec 2010 17:10:22 +0000 (+0000) Subject: Made AbstractAudioScope and AbstractGfxScope inherit from AbstractScope; Done for... X-Git-Url: https://git.sesse.net/?a=commitdiff_plain;h=6cc358a050e97e22cb69838b3dd4bf81275946c9;p=kdenlive Made AbstractAudioScope and AbstractGfxScope inherit from AbstractScope; Done for audio svn path=/trunk/kdenlive/; revision=5129 --- diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 38c7a0ef..80fbab92 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -220,6 +220,7 @@ set(kdenlive_SRCS tracksconfigdialog.cpp configtrackscommand.cpp abstractscopewidget.cpp + abstractgfxscopewidget.cpp audioscopes/abstractaudioscopewidget.cpp audioscopes/audiospectrum.cpp rebuildgroupcommand.cpp diff --git a/src/abstractgfxscopewidget.cpp b/src/abstractgfxscopewidget.cpp new file mode 100644 index 00000000..48020eed --- /dev/null +++ b/src/abstractgfxscopewidget.cpp @@ -0,0 +1,459 @@ +/*************************************************************************** + * Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com) * + * This file is part of kdenlive. See www.kdenlive.org. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "qtconcurrentrun.h" + +#include "abstractgfxscopewidget.h" +#include "renderer.h" +#include "monitor.h" + +#include +#include +#include +#include +#include + +const int REALTIME_FPS = 30; + +const QColor light(250, 238, 226, 255); +const QColor dark(40, 40, 39, 255); +const QColor dark2(25, 25, 23, 255); + +const QPen AbstractGfxScopeWidget::penThick(QBrush(QColor(250, 250, 250)), 2, Qt::SolidLine); +const QPen AbstractGfxScopeWidget::penThin(QBrush(QColor(250, 250, 250)), 1, Qt::SolidLine); +const QPen AbstractGfxScopeWidget::penLight(QBrush(QColor(200, 200, 250, 150)), 1, Qt::SolidLine); +const QPen AbstractGfxScopeWidget::penLightDots(QBrush(QColor(200, 200, 250, 150)), 1, Qt::DotLine); +const QPen AbstractGfxScopeWidget::penDark(QBrush(QColor(0, 0, 20, 250)), 1, Qt::SolidLine); +const QPen AbstractGfxScopeWidget::penDarkDots(QBrush(QColor(0, 0, 20, 250)), 1, Qt::DotLine); + +AbstractGfxScopeWidget::AbstractGfxScopeWidget(Monitor *projMonitor, Monitor *clipMonitor, bool trackMouse, QWidget *parent) : + QWidget(parent), + m_projMonitor(projMonitor), + m_clipMonitor(clipMonitor), + m_mousePos(0, 0), + m_mouseWithinWidget(false), + offset(5), + m_accelFactorHUD(1), + m_accelFactorScope(1), + m_accelFactorBackground(1), + m_semaphoreHUD(1), + m_semaphoreScope(1), + m_semaphoreBackground(1), + initialDimensionUpdateDone(false), + m_requestForcedUpdate(false) + +{ + m_scopePalette = QPalette(); + m_scopePalette.setBrush(QPalette::Window, QBrush(dark2)); + m_scopePalette.setBrush(QPalette::Base, QBrush(dark)); + m_scopePalette.setBrush(QPalette::Button, QBrush(dark)); + m_scopePalette.setBrush(QPalette::Text, QBrush(light)); + m_scopePalette.setBrush(QPalette::WindowText, QBrush(light)); + m_scopePalette.setBrush(QPalette::ButtonText, QBrush(light)); + this->setPalette(m_scopePalette); + this->setAutoFillBackground(true); + + m_aAutoRefresh = new QAction(i18n("Auto Refresh"), this); + m_aAutoRefresh->setCheckable(true); + m_aRealtime = new QAction(i18n("Realtime (with precision loss)"), this); + m_aRealtime->setCheckable(true); + + m_menu = new QMenu(this); + m_menu->setPalette(m_scopePalette); + m_menu->addAction(m_aAutoRefresh); + m_menu->addAction(m_aRealtime); + + this->setContextMenuPolicy(Qt::CustomContextMenu); + + m_activeRender = (m_clipMonitor->isActive()) ? m_clipMonitor->render : m_projMonitor->render; + + bool b = true; + b &= connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(customContextMenuRequested(QPoint))); + + //b &= connect(m_activeRender, SIGNAL(rendererPosition(int)), this, SLOT(slotRenderZoneUpdated())); + b &= connect(m_activeRender, SIGNAL(frameUpdated(QImage)), this, SLOT(slotRenderZoneUpdated(QImage))); + + b &= connect(this, SIGNAL(signalHUDRenderingFinished(uint, uint)), this, SLOT(slotHUDRenderingFinished(uint, uint))); + b &= connect(this, SIGNAL(signalScopeRenderingFinished(uint, uint)), this, SLOT(slotScopeRenderingFinished(uint, uint))); + b &= connect(this, SIGNAL(signalBackgroundRenderingFinished(uint, uint)), this, SLOT(slotBackgroundRenderingFinished(uint, uint))); + b &= connect(m_aRealtime, SIGNAL(toggled(bool)), this, SLOT(slotResetRealtimeFactor(bool))); + b &= connect(m_aAutoRefresh, SIGNAL(toggled(bool)), this, SLOT(slotAutoRefreshToggled(bool))); + Q_ASSERT(b); + + // Enable mouse tracking if desired. + // Causes the mouseMoved signal to be emitted when the mouse moves inside the + // widget, even when no mouse button is pressed. + this->setMouseTracking(trackMouse); +} + +AbstractGfxScopeWidget::~AbstractGfxScopeWidget() +{ + writeConfig(); + + delete m_menu; + delete m_aAutoRefresh; + delete m_aRealtime; +} + +void AbstractGfxScopeWidget::init() +{ + m_widgetName = widgetName(); + readConfig(); +} + +void AbstractGfxScopeWidget::readConfig() +{ + KSharedConfigPtr config = KGlobal::config(); + KConfigGroup scopeConfig(config, configName()); + m_aAutoRefresh->setChecked(scopeConfig.readEntry("autoRefresh", true)); + m_aRealtime->setChecked(scopeConfig.readEntry("realtime", false)); + scopeConfig.sync(); +} + +void AbstractGfxScopeWidget::writeConfig() +{ + KSharedConfigPtr config = KGlobal::config(); + KConfigGroup scopeConfig(config, configName()); + scopeConfig.writeEntry("autoRefresh", m_aAutoRefresh->isChecked()); + scopeConfig.writeEntry("realtime", m_aRealtime->isChecked()); + scopeConfig.sync(); +} + +QString AbstractGfxScopeWidget::configName() +{ + return "Scope_" + m_widgetName; +} + +void AbstractGfxScopeWidget::prodHUDThread() +{ + if (this->visibleRegion().isEmpty()) { +// qDebug() << "Scope " << m_widgetName << " is not visible. Not calculating HUD."; + } else { + if (m_semaphoreHUD.tryAcquire(1)) { + Q_ASSERT(!m_threadHUD.isRunning()); + + m_newHUDFrames.fetchAndStoreRelaxed(0); + m_newHUDUpdates.fetchAndStoreRelaxed(0); + m_threadHUD = QtConcurrent::run(this, &AbstractGfxScopeWidget::renderHUD, m_accelFactorHUD); +// qDebug() << "HUD thread started in " << m_widgetName; + + } else { +// qDebug() << "HUD semaphore locked, not prodding in " << m_widgetName << ". Thread running: " << m_threadHUD.isRunning(); + } + } +} + +void AbstractGfxScopeWidget::prodScopeThread() +{ + // Only start a new thread if the scope is actually visible + // and not hidden by another widget on the stack and if user want the scope to update. + if (this->visibleRegion().isEmpty() || (!m_aAutoRefresh->isChecked() && !m_requestForcedUpdate)) { +// qDebug() << "Scope " << m_widgetName << " is not visible. Not calculating scope."; + } else { + // Try to acquire the semaphore. This must only succeed if m_threadScope is not running + // anymore. Therefore the semaphore must NOT be released before m_threadScope ends. + // If acquiring the semaphore fails, the thread is still running. + if (m_semaphoreScope.tryAcquire(1)) { + Q_ASSERT(!m_threadScope.isRunning()); + + m_newScopeFrames.fetchAndStoreRelaxed(0); + m_newScopeUpdates.fetchAndStoreRelaxed(0); + + Q_ASSERT(m_accelFactorScope > 0); + + // See http://doc.qt.nokia.com/latest/qtconcurrentrun.html#run about + // running member functions in a thread + m_threadScope = QtConcurrent::run(this, &AbstractGfxScopeWidget::renderScope, m_accelFactorScope, m_scopeImage); + m_requestForcedUpdate = false; + +// qDebug() << "Scope thread started in " << m_widgetName; + + } else { +// qDebug() << "Scope semaphore locked, not prodding in " << m_widgetName << ". Thread running: " << m_threadScope.isRunning(); + } + } +} +void AbstractGfxScopeWidget::prodBackgroundThread() +{ + if (this->visibleRegion().isEmpty()) { +// qDebug() << "Scope " << m_widgetName << " is not visible. Not calculating background."; + } else { + if (m_semaphoreBackground.tryAcquire(1)) { + Q_ASSERT(!m_threadBackground.isRunning()); + + m_newBackgroundFrames.fetchAndStoreRelaxed(0); + m_newBackgroundUpdates.fetchAndStoreRelaxed(0); + m_threadBackground = QtConcurrent::run(this, &AbstractGfxScopeWidget::renderBackground, m_accelFactorBackground); +// qDebug() << "Background thread started in " << m_widgetName; + + } else { +// qDebug() << "Background semaphore locked, not prodding in " << m_widgetName << ". Thread running: " << m_threadBackground.isRunning(); + } + } +} + +void AbstractGfxScopeWidget::forceUpdate(bool doUpdate) +{ +// qDebug() << "Force update called in " << widgetName() << ". Arg: " << doUpdate; + if (!doUpdate) { + return; + } + m_requestForcedUpdate = true; + m_newHUDUpdates.fetchAndAddRelaxed(1); + m_newScopeUpdates.fetchAndAddRelaxed(1); + m_newBackgroundUpdates.fetchAndAddRelaxed(1); + prodHUDThread(); + prodScopeThread(); + prodBackgroundThread(); +} +void AbstractGfxScopeWidget::forceUpdateHUD() +{ + m_newHUDUpdates.fetchAndAddRelaxed(1); + prodHUDThread(); + +} +void AbstractGfxScopeWidget::forceUpdateScope() +{ + m_newScopeUpdates.fetchAndAddRelaxed(1); + m_requestForcedUpdate = true; + prodScopeThread(); + +} +void AbstractGfxScopeWidget::forceUpdateBackground() +{ + m_newBackgroundUpdates.fetchAndAddRelaxed(1); + prodBackgroundThread(); + +} + + +///// Events ///// + +void AbstractGfxScopeWidget::mouseReleaseEvent(QMouseEvent *event) +{ + if (!m_aAutoRefresh->isChecked()) { + m_requestForcedUpdate = true; + m_activeRender->sendFrameUpdate(); + } + prodHUDThread(); + prodScopeThread(); + prodBackgroundThread(); + QWidget::mouseReleaseEvent(event); +} + +void AbstractGfxScopeWidget::resizeEvent(QResizeEvent *event) +{ + // Update the dimension of the available rect for painting + m_scopeRect = scopeRect(); + forceUpdate(); + + QWidget::resizeEvent(event); +} + +void AbstractGfxScopeWidget::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + m_scopeRect = scopeRect(); +} + +void AbstractGfxScopeWidget::paintEvent(QPaintEvent *) +{ + QPainter davinci(this); + davinci.drawImage(m_scopeRect.topLeft(), m_imgBackground); + davinci.drawImage(m_scopeRect.topLeft(), m_imgScope); + davinci.drawImage(m_scopeRect.topLeft(), m_imgHUD); +} + +void AbstractGfxScopeWidget::mouseMoveEvent(QMouseEvent *event) +{ + m_mousePos = event->pos(); + m_mouseWithinWidget = true; + emit signalMousePositionChanged(); +} +void AbstractGfxScopeWidget::leaveEvent(QEvent *) +{ + m_mouseWithinWidget = false; + emit signalMousePositionChanged(); +} + +void AbstractGfxScopeWidget::customContextMenuRequested(const QPoint &pos) +{ + m_menu->exec(this->mapToGlobal(pos)); +} + +uint AbstractGfxScopeWidget::calculateAccelFactorHUD(uint oldMseconds, uint) +{ + return ceil((float)oldMseconds*REALTIME_FPS / 1000); +} +uint AbstractGfxScopeWidget::calculateAccelFactorScope(uint oldMseconds, uint) +{ + return ceil((float)oldMseconds*REALTIME_FPS / 1000); +} +uint AbstractGfxScopeWidget::calculateAccelFactorBackground(uint oldMseconds, uint) +{ + return ceil((float)oldMseconds*REALTIME_FPS / 1000); +} + + +///// Slots ///// + +void AbstractGfxScopeWidget::slotHUDRenderingFinished(uint mseconds, uint oldFactor) +{ +// qDebug() << "HUD rendering has finished, waiting for termination in " << m_widgetName; + m_threadHUD.waitForFinished(); + m_imgHUD = m_threadHUD.result(); + + m_semaphoreHUD.release(1); + this->update(); + + int accel; + if (m_aRealtime->isChecked()) { + accel = calculateAccelFactorHUD(mseconds, oldFactor); + if (m_accelFactorHUD < 1) { + accel = 1; + } + m_accelFactorHUD = accel; + } + + if ((m_newHUDFrames > 0 && m_aAutoRefresh->isChecked()) || m_newHUDUpdates > 0) { +// qDebug() << "Trying to start a new HUD thread for " << m_widgetName +// << ". New frames/updates: " << m_newHUDFrames << "/" << m_newHUDUpdates; + prodHUDThread();; + } +} + +void AbstractGfxScopeWidget::slotScopeRenderingFinished(uint mseconds, uint oldFactor) +{ + // The signal can be received before the thread has really finished. So we + // need to wait until it has really finished before starting a new thread. +// qDebug() << "Scope rendering has finished, waiting for termination in " << m_widgetName; + m_threadScope.waitForFinished(); + m_imgScope = m_threadScope.result(); + + // The scope thread has finished. Now we can release the semaphore, allowing a new thread. + // See prodScopeThread where the semaphore is acquired again. + m_semaphoreScope.release(1); + this->update(); + + // Calculate the acceleration factor hint to get «realtime» updates. + int accel; + if (m_aRealtime->isChecked()) { + accel = calculateAccelFactorScope(mseconds, oldFactor); + if (accel < 1) { + // If mseconds happens to be 0. + accel = 1; + } + // Don't directly calculate with m_accelFactorScope as we are dealing with concurrency. + // If m_accelFactorScope is set to 0 at the wrong moment, who knows what might happen + // then :) Therefore use a local variable. + m_accelFactorScope = accel; + } + + if ((m_newScopeFrames > 0 && m_aAutoRefresh->isChecked()) || m_newScopeUpdates > 0) { +// qDebug() << "Trying to start a new scope thread for " << m_widgetName +// << ". New frames/updates: " << m_newScopeFrames << "/" << m_newScopeUpdates; + prodScopeThread(); + } +} + +void AbstractGfxScopeWidget::slotBackgroundRenderingFinished(uint mseconds, uint oldFactor) +{ +// qDebug() << "Background rendering has finished, waiting for termination in " << m_widgetName; + m_threadBackground.waitForFinished(); + m_imgBackground = m_threadBackground.result(); + + m_semaphoreBackground.release(1); + this->update(); + + int accel; + if (m_aRealtime->isChecked()) { + accel = calculateAccelFactorBackground(mseconds, oldFactor); + if (m_accelFactorBackground < 1) { + accel = 1; + } + m_accelFactorBackground = accel; + } + + if ((m_newBackgroundFrames > 0 && m_aAutoRefresh->isChecked()) || m_newBackgroundUpdates > 0) { +// qDebug() << "Trying to start a new background thread for " << m_widgetName +// << ". New frames/updates: " << m_newBackgroundFrames << "/" << m_newBackgroundUpdates; + prodBackgroundThread();; + } +} + +void AbstractGfxScopeWidget::slotActiveMonitorChanged(bool isClipMonitor) +{ +// qDebug() << "Active monitor has changed in " << m_widgetName << ". Is the clip monitor active now? " << isClipMonitor; + + bool b = m_activeRender->disconnect(this); + Q_ASSERT(b); + + m_activeRender = (isClipMonitor) ? m_clipMonitor->render : m_projMonitor->render; + + //b &= connect(m_activeRender, SIGNAL(rendererPosition(int)), this, SLOT(slotRenderZoneUpdated())); + b &= connect(m_activeRender, SIGNAL(frameUpdated(QImage)), this, SLOT(slotRenderZoneUpdated(QImage))); + Q_ASSERT(b); + + // Update the scope for the new monitor. + prodHUDThread(); + prodScopeThread(); + prodBackgroundThread(); +} + +void AbstractGfxScopeWidget::slotRenderZoneUpdated() +{ + m_newHUDFrames.fetchAndAddRelaxed(1); + m_newScopeFrames.fetchAndAddRelaxed(1); + m_newBackgroundFrames.fetchAndAddRelaxed(1); + +// qDebug() << "Monitor incoming. New frames total HUD/Scope/Background: " << m_newHUDFrames +// << "/" << m_newScopeFrames << "/" << m_newBackgroundFrames; + + if (this->visibleRegion().isEmpty()) { +// qDebug() << "Scope of widget " << m_widgetName << " is not at the top, not rendering."; + } else { + if (m_aAutoRefresh->isChecked()) { + prodHUDThread(); + prodScopeThread(); + prodBackgroundThread(); + } + } +} + +void AbstractGfxScopeWidget::slotRenderZoneUpdated(QImage frame) +{ + m_scopeImage = frame; + slotRenderZoneUpdated(); +} + +void AbstractGfxScopeWidget::slotResetRealtimeFactor(bool realtimeChecked) +{ + if (!realtimeChecked) { + m_accelFactorHUD = 1; + m_accelFactorScope = 1; + m_accelFactorBackground = 1; + } +} + +bool AbstractGfxScopeWidget::autoRefreshEnabled() +{ + return m_aAutoRefresh->isChecked(); +} + +void AbstractGfxScopeWidget::slotAutoRefreshToggled(bool autoRefresh) +{ + if (isVisible()) emit requestAutoRefresh(autoRefresh); + // TODO only if depends on input + if (autoRefresh) { + //forceUpdate(); + m_requestForcedUpdate = true; + m_activeRender->sendFrameUpdate(); + } +} diff --git a/src/abstractgfxscopewidget.h b/src/abstractgfxscopewidget.h new file mode 100644 index 00000000..05219ab0 --- /dev/null +++ b/src/abstractgfxscopewidget.h @@ -0,0 +1,272 @@ +/*************************************************************************** + * Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com) * + * This file is part of kdenlive. See www.kdenlive.org. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +/** + This abstract widget is a proof that abstract things sometimes *are* useful. + + The widget expects three layers which + * Will be painted on top of each other on each update + * Are rendered in a separate thread so that the UI is not blocked + * Are rendered only if necessary (e.g., if a layer does not depend + on input images, it will not be re-rendered for incoming frames) + + The layer order is as follows: + _____________________ + / \ + / HUD Layer \ + / \ + --------------------------- + _____________________ + / \ + / Scope Layer \ + / \ + --------------------------- + _____________________ + / \ + / Background Layer \ + / \ + --------------------------- + + Colors of Scope Widgets are defined in here (and thus don't need to be + re-defined in the implementation of the widget's .ui file). + + The custom context menu already contains entries, like for enabling auto- + refresh. It can certainly be extended in the implementation of the widget. + + Note: Widgets deriving from this class should connect slotActiveMonitorChanged + to the appropriate signal. + + If you intend to write an own widget inheriting from this one, please read + the comments on the unimplemented methods carefully. They are not only here + for optical amusement, but also contain important information. + */ + +#ifndef ABSTRACTGFXSCOPEWIDGET_H +#define ABSTRACTGFXSCOPEWIDGET_H + + +#include +#include + +class QMenu; + +class Monitor; +class Render; + +class AbstractGfxScopeWidget : public QWidget +{ + Q_OBJECT + +public: + AbstractGfxScopeWidget(Monitor *projMonitor, Monitor *clipMonitor, bool trackMouse = false, QWidget *parent = 0); + virtual ~AbstractGfxScopeWidget(); // Must be virtual because of inheritance, to avoid memory leaks + QPalette m_scopePalette; + + /** Initializes widget settings (reads configuration). + Has to be called in the implementing object. */ + void init(); + + /** Does this scope have auto-refresh enabled */ + bool autoRefreshEnabled(); + + ///// Unimplemented ///// + + virtual QString widgetName() const = 0; + + ///// Variables ///// + static const QPen penThick; + static const QPen penThin; + static const QPen penLight; + static const QPen penLightDots; + static const QPen penDark; + static const QPen penDarkDots; + +protected: + ///// Variables ///// + + Monitor *m_projMonitor; + Monitor *m_clipMonitor; + Render *m_activeRender; + + + /** The context menu. Feel free to add new entries in your implementation. */ + QMenu *m_menu; + + /** Enables auto refreshing of the scope. + This is when a new frame is shown on the active monitor. + Resize events always force a recalculation. */ + QAction *m_aAutoRefresh; + + /** Realtime rendering. Should be disabled if it is not supported. + Use the accelerationFactor variable passed to the render functions as a hint of + how many times faster the scope should be calculated. */ + QAction *m_aRealtime; + + /** The mouse position; Updated when the mouse enters the widget + AND mouse tracking has been enabled. */ + QPoint m_mousePos; + /** Knows whether the mouse currently lies within the widget or not. + Can e.g. be used for drawing a HUD only when the mouse is in the widget. */ + bool m_mouseWithinWidget; + + /** Offset from the widget's borders */ + const uchar offset; + + /** The rect on the widget we're painting in. + Can be used by the implementing widget, e.g. in the render methods. + Is updated when necessary (size changes). */ + QRect m_scopeRect; + + /** Images storing the calculated layers. Will be used on repaint events. */ + QImage m_imgHUD; + QImage m_imgScope; + QImage m_imgBackground; + + /** The acceleration factors can be accessed also by other renderer tasks, + e.g. to display the scope's acceleration factor in the HUD renderer. */ + int m_accelFactorHUD; + int m_accelFactorScope; + int m_accelFactorBackground; + + /** Reads the widget's configuration. + Can be extended in the implementing subclass (make sure to run readConfig as well). */ + virtual void readConfig(); + /** Writes the widget configuration. + Implementing widgets have to implement an own method and run it in their destructor. */ + void writeConfig(); + /** Identifier for the widget's configuration. */ + QString configName(); + + + ///// Unimplemented Methods ///// + + /** Where on the widget we can paint in. + May also update other variables that depend on the widget's size. */ + virtual QRect scopeRect() = 0; + + /** @brief HUD renderer. Must emit signalHUDRenderingFinished(). @see renderScope */ + virtual QImage renderHUD(uint accelerationFactor) = 0; + /** @brief Scope renderer. Must emit signalScopeRenderingFinished() + when calculation has finished, to allow multi-threading. + accelerationFactor hints how much faster than usual the calculation should be accomplished, if possible. */ + virtual QImage renderScope(uint accelerationFactor, const QImage) = 0; + /** @brief Background renderer. Must emit signalBackgroundRenderingFinished(). @see renderScope */ + virtual QImage renderBackground(uint accelerationFactor) = 0; + + /** Must return true if the HUD layer depends on the input monitor. + If it does not, then it does not need to be re-calculated when + a new frame from the monitor is incoming. */ + virtual bool isHUDDependingOnInput() const = 0; + /** @see isHUDDependingOnInput() */ + virtual bool isScopeDependingOnInput() const = 0; + /** @see isHUDDependingOnInput() */ + virtual bool isBackgroundDependingOnInput() const = 0; + + ///// Can be reimplemented ///// + /** Calculates the acceleration factor to be used by the render thread. + This method can be refined in the subclass if required. */ + virtual uint calculateAccelFactorHUD(uint oldMseconds, uint oldFactor); + virtual uint calculateAccelFactorScope(uint oldMseconds, uint oldFactor); + virtual uint calculateAccelFactorBackground(uint oldMseconds, uint oldFactor); + + ///// Reimplemented ///// + + void mouseMoveEvent(QMouseEvent *); + void leaveEvent(QEvent *); + void mouseReleaseEvent(QMouseEvent *); + void paintEvent(QPaintEvent *); + void resizeEvent(QResizeEvent *); + void showEvent(QShowEvent *); // Called when the widget is activated via the Menu entry + // void raise(); // Called only when manually calling the event -> useless + + +protected slots: + /** Forces an update of all layers. */ + void forceUpdate(bool doUpdate = true); + void forceUpdateHUD(); + void forceUpdateScope(); + void forceUpdateBackground(); + void slotAutoRefreshToggled(bool); + +signals: + /** mseconds represent the time taken for the calculation, + accelerationFactor is the acceleration factor that has been used for this calculation. */ + void signalHUDRenderingFinished(uint mseconds, uint accelerationFactor); + void signalScopeRenderingFinished(uint mseconds, uint accelerationFactor); + void signalBackgroundRenderingFinished(uint mseconds, uint accelerationFactor); + + /** For the mouse position itself see m_mousePos. + To check whether the mouse has leaved the widget, see m_mouseWithinWidget. */ + void signalMousePositionChanged(); + + /** Do we need the renderer to send its frames to us? */ + void requestAutoRefresh(bool); + +private: + + /** Counts the number of frames that have been rendered in the active monitor. + The frame number will be reset when the calculation starts for the current frame. */ + QAtomicInt m_newHUDFrames; + QAtomicInt m_newScopeFrames; + QAtomicInt m_newBackgroundFrames; + + /** Counts the number of updates that, unlike new frames, force a recalculation + of the scope, like for example a resize event. */ + QAtomicInt m_newHUDUpdates; + QAtomicInt m_newScopeUpdates; + QAtomicInt m_newBackgroundUpdates; + + /** The semaphores ensure that the QFutures for the HUD/Scope/Background threads cannot + be assigned a new thread while it is still running. (Could cause deadlocks and other + nasty things known from parallelism.) */ + QSemaphore m_semaphoreHUD; + QSemaphore m_semaphoreScope; + QSemaphore m_semaphoreBackground; + + QFuture m_threadHUD; + QFuture m_threadScope; + QFuture m_threadBackground; + + bool initialDimensionUpdateDone; + bool m_requestForcedUpdate; + + QImage m_scopeImage; + + QString m_widgetName; + + void prodHUDThread(); + void prodScopeThread(); + void prodBackgroundThread(); + +public slots: + /** @brief Must be called when the active monitor has shown a new frame. + This slot must be connected in the implementing class, it is *not* + done in this abstract class. */ + void slotActiveMonitorChanged(bool isClipMonitor); + +private slots: + void customContextMenuRequested(const QPoint &pos); + /** To be called when a new frame has been received. + The scope then decides whether and when it wants to recalculate the scope, depending + on whether it is currently visible and whether a calculation thread is already running. */ + void slotRenderZoneUpdated(); + void slotRenderZoneUpdated(QImage); + /** The following slots are called when rendering of a component has finished. They e.g. update + the widget and decide whether to immediately restart the calculation thread. */ + void slotHUDRenderingFinished(uint mseconds, uint accelerationFactor); + void slotScopeRenderingFinished(uint mseconds, uint accelerationFactor); + void slotBackgroundRenderingFinished(uint mseconds, uint accelerationFactor); + + /** Resets the acceleration factors to 1 when realtime rendering is disabled. */ + void slotResetRealtimeFactor(bool realtimeChecked); + +}; + +#endif // ABSTRACTGFXSCOPEWIDGET_H diff --git a/src/abstractscopewidget.cpp b/src/abstractscopewidget.cpp index 7109704c..c72e584f 100644 --- a/src/abstractscopewidget.cpp +++ b/src/abstractscopewidget.cpp @@ -170,7 +170,7 @@ void AbstractScopeWidget::prodScopeThread() // See http://doc.qt.nokia.com/latest/qtconcurrentrun.html#run about // running member functions in a thread - m_threadScope = QtConcurrent::run(this, &AbstractScopeWidget::renderScope, m_accelFactorScope, m_scopeImage); + m_threadScope = QtConcurrent::run(this, &AbstractScopeWidget::renderScope, m_accelFactorScope); m_requestForcedUpdate = false; // qDebug() << "Scope thread started in " << m_widgetName; diff --git a/src/abstractscopewidget.h b/src/abstractscopewidget.h index 0d5a597b..9e28432a 100644 --- a/src/abstractscopewidget.h +++ b/src/abstractscopewidget.h @@ -155,7 +155,7 @@ protected: /** @brief Scope renderer. Must emit signalScopeRenderingFinished() when calculation has finished, to allow multi-threading. accelerationFactor hints how much faster than usual the calculation should be accomplished, if possible. */ - virtual QImage renderScope(uint accelerationFactor, const QImage) = 0; + virtual QImage renderScope(uint accelerationFactor) = 0; /** @brief Background renderer. Must emit signalBackgroundRenderingFinished(). @see renderScope */ virtual QImage renderBackground(uint accelerationFactor) = 0; @@ -250,7 +250,7 @@ public slots: done in this abstract class. */ void slotActiveMonitorChanged(bool isClipMonitor); -private slots: +protected slots: void customContextMenuRequested(const QPoint &pos); /** To be called when a new frame has been received. The scope then decides whether and when it wants to recalculate the scope, depending diff --git a/src/audioscopes/abstractaudioscopewidget.cpp b/src/audioscopes/abstractaudioscopewidget.cpp index 2a9f1b23..52f91b9a 100644 --- a/src/audioscopes/abstractaudioscopewidget.cpp +++ b/src/audioscopes/abstractaudioscopewidget.cpp @@ -21,447 +21,24 @@ #include #include -const int REALTIME_FPS = 30; - -const QColor light(250, 238, 226, 255); -const QColor dark(40, 40, 39, 255); -const QColor dark2(25, 25, 23, 255); - -const QPen AbstractAudioScopeWidget::penThick(QBrush(QColor(250, 250, 250)), 2, Qt::SolidLine); -const QPen AbstractAudioScopeWidget::penThin(QBrush(QColor(250, 250, 250)), 1, Qt::SolidLine); -const QPen AbstractAudioScopeWidget::penLight(QBrush(QColor(200, 200, 250, 150)), 1, Qt::SolidLine); -const QPen AbstractAudioScopeWidget::penDark(QBrush(QColor(0, 0, 20, 250)), 1, Qt::SolidLine); - AbstractAudioScopeWidget::AbstractAudioScopeWidget(Monitor *projMonitor, Monitor *clipMonitor, bool trackMouse, QWidget *parent) : - QWidget(parent), - m_projMonitor(projMonitor), - m_clipMonitor(clipMonitor), - m_mousePos(0, 0), - m_mouseWithinWidget(false), - offset(5), - m_accelFactorHUD(1), - m_accelFactorScope(1), - m_accelFactorBackground(1), - m_semaphoreHUD(1), - m_semaphoreScope(1), - m_semaphoreBackground(1), - initialDimensionUpdateDone(false), - m_requestForcedUpdate(false) - -{ - m_scopePalette = QPalette(); - m_scopePalette.setBrush(QPalette::Window, QBrush(dark2)); - m_scopePalette.setBrush(QPalette::Base, QBrush(dark)); - m_scopePalette.setBrush(QPalette::Button, QBrush(dark)); - m_scopePalette.setBrush(QPalette::Text, QBrush(light)); - m_scopePalette.setBrush(QPalette::WindowText, QBrush(light)); - m_scopePalette.setBrush(QPalette::ButtonText, QBrush(light)); - this->setPalette(m_scopePalette); - this->setAutoFillBackground(true); - - m_aAutoRefresh = new QAction(i18n("Auto Refresh"), this); - m_aAutoRefresh->setCheckable(true); - m_aRealtime = new QAction(i18n("Realtime (with precision loss)"), this); - m_aRealtime->setCheckable(true); - - m_menu = new QMenu(this); - m_menu->setPalette(m_scopePalette); - m_menu->addAction(m_aAutoRefresh); - m_menu->addAction(m_aRealtime); - - this->setContextMenuPolicy(Qt::CustomContextMenu); - - m_activeRender = (m_clipMonitor->isActive()) ? m_clipMonitor->render : m_projMonitor->render; - - bool b = true; - b &= connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(customContextMenuRequested(QPoint))); - - //b &= connect(m_activeRender, SIGNAL(rendererPosition(int)), this, SLOT(slotRenderZoneUpdated())); -// b &= connect(m_activeRender, SIGNAL(frameUpdated(QImage)), this, SLOT(slotRenderZoneUpdated(QImage))); - - b &= connect(this, SIGNAL(signalHUDRenderingFinished(uint, uint)), this, SLOT(slotHUDRenderingFinished(uint, uint))); - b &= connect(this, SIGNAL(signalScopeRenderingFinished(uint, uint)), this, SLOT(slotScopeRenderingFinished(uint, uint))); - b &= connect(this, SIGNAL(signalBackgroundRenderingFinished(uint, uint)), this, SLOT(slotBackgroundRenderingFinished(uint, uint))); - b &= connect(m_aRealtime, SIGNAL(toggled(bool)), this, SLOT(slotResetRealtimeFactor(bool))); - b &= connect(m_aAutoRefresh, SIGNAL(toggled(bool)), this, SLOT(slotAutoRefreshToggled(bool))); - Q_ASSERT(b); - - // Enable mouse tracking if desired. - // Causes the mouseMoved signal to be emitted when the mouse moves inside the - // widget, even when no mouse button is pressed. - this->setMouseTracking(trackMouse); -} - -AbstractAudioScopeWidget::~AbstractAudioScopeWidget() -{ - writeConfig(); - - delete m_menu; - delete m_aAutoRefresh; - delete m_aRealtime; -} - -void AbstractAudioScopeWidget::init() -{ - m_widgetName = widgetName(); - readConfig(); -} - -void AbstractAudioScopeWidget::readConfig() -{ - KSharedConfigPtr config = KGlobal::config(); - KConfigGroup scopeConfig(config, configName()); - m_aAutoRefresh->setChecked(scopeConfig.readEntry("autoRefresh", true)); - m_aRealtime->setChecked(scopeConfig.readEntry("realtime", false)); - scopeConfig.sync(); -} - -void AbstractAudioScopeWidget::writeConfig() -{ - KSharedConfigPtr config = KGlobal::config(); - KConfigGroup scopeConfig(config, configName()); - scopeConfig.writeEntry("autoRefresh", m_aAutoRefresh->isChecked()); - scopeConfig.writeEntry("realtime", m_aRealtime->isChecked()); - scopeConfig.sync(); -} - -QString AbstractAudioScopeWidget::configName() -{ - return "Scope_" + m_widgetName; -} - -void AbstractAudioScopeWidget::prodHUDThread() -{ - if (this->visibleRegion().isEmpty()) { -// qDebug() << "Scope " << m_widgetName << " is not visible. Not calculating HUD."; - } else { - if (m_semaphoreHUD.tryAcquire(1)) { - Q_ASSERT(!m_threadHUD.isRunning()); - - m_newHUDFrames.fetchAndStoreRelaxed(0); - m_newHUDUpdates.fetchAndStoreRelaxed(0); - m_threadHUD = QtConcurrent::run(this, &AbstractAudioScopeWidget::renderHUD, m_accelFactorHUD); -// qDebug() << "HUD thread started in " << m_widgetName; - - } else { -// qDebug() << "HUD semaphore locked, not prodding in " << m_widgetName << ". Thread running: " << m_threadHUD.isRunning(); - } - } -} - -void AbstractAudioScopeWidget::prodScopeThread() -{ - // Only start a new thread if the scope is actually visible - // and not hidden by another widget on the stack and if user want the scope to update. - if (this->visibleRegion().isEmpty() || (!m_aAutoRefresh->isChecked() && !m_requestForcedUpdate)) { -// qDebug() << "Scope " << m_widgetName << " is not visible. Not calculating scope."; - } else { - // Try to acquire the semaphore. This must only succeed if m_threadScope is not running - // anymore. Therefore the semaphore must NOT be released before m_threadScope ends. - // If acquiring the semaphore fails, the thread is still running. - if (m_semaphoreScope.tryAcquire(1)) { - Q_ASSERT(!m_threadScope.isRunning()); - - m_newScopeFrames.fetchAndStoreRelaxed(0); - m_newScopeUpdates.fetchAndStoreRelaxed(0); - - Q_ASSERT(m_accelFactorScope > 0); - - // See http://doc.qt.nokia.com/latest/qtconcurrentrun.html#run about - // running member functions in a thread - m_threadScope = QtConcurrent::run(this, &AbstractAudioScopeWidget::renderScope, - m_accelFactorScope, m_audioFrame, m_freq, m_nChannels, m_nSamples); - m_requestForcedUpdate = false; - -// qDebug() << "Scope thread started in " << m_widgetName; - - } else { -// qDebug() << "Scope semaphore locked, not prodding in " << m_widgetName << ". Thread running: " << m_threadScope.isRunning(); - } - } -} -void AbstractAudioScopeWidget::prodBackgroundThread() -{ - if (this->visibleRegion().isEmpty()) { -// qDebug() << "Scope " << m_widgetName << " is not visible. Not calculating background."; - } else { - if (m_semaphoreBackground.tryAcquire(1)) { - Q_ASSERT(!m_threadBackground.isRunning()); - - m_newBackgroundFrames.fetchAndStoreRelaxed(0); - m_newBackgroundUpdates.fetchAndStoreRelaxed(0); - m_threadBackground = QtConcurrent::run(this, &AbstractAudioScopeWidget::renderBackground, m_accelFactorBackground); -// qDebug() << "Background thread started in " << m_widgetName; - - } else { -// qDebug() << "Background semaphore locked, not prodding in " << m_widgetName << ". Thread running: " << m_threadBackground.isRunning(); - } - } -} - -void AbstractAudioScopeWidget::forceUpdate(bool doUpdate) -{ -// qDebug() << "Force update called in " << widgetName() << ". Arg: " << doUpdate; - if (!doUpdate) { - return; - } - m_requestForcedUpdate = true; - m_newHUDUpdates.fetchAndAddRelaxed(1); - m_newScopeUpdates.fetchAndAddRelaxed(1); - m_newBackgroundUpdates.fetchAndAddRelaxed(1); - prodHUDThread(); - prodScopeThread(); - prodBackgroundThread(); -} -void AbstractAudioScopeWidget::forceUpdateHUD() -{ - m_newHUDUpdates.fetchAndAddRelaxed(1); - prodHUDThread(); - -} -void AbstractAudioScopeWidget::forceUpdateScope() -{ - m_newScopeUpdates.fetchAndAddRelaxed(1); - m_requestForcedUpdate = true; - prodScopeThread(); - -} -void AbstractAudioScopeWidget::forceUpdateBackground() -{ - m_newBackgroundUpdates.fetchAndAddRelaxed(1); - prodBackgroundThread(); - -} - - -///// Events ///// - -void AbstractAudioScopeWidget::mouseReleaseEvent(QMouseEvent *event) -{ - if (!m_aAutoRefresh->isChecked()) { - m_requestForcedUpdate = true; - m_activeRender->sendFrameUpdate(); - } - prodHUDThread(); - prodScopeThread(); - prodBackgroundThread(); - QWidget::mouseReleaseEvent(event); -} - -void AbstractAudioScopeWidget::resizeEvent(QResizeEvent *event) -{ - // Update the dimension of the available rect for painting - m_scopeRect = scopeRect(); - forceUpdate(); - - QWidget::resizeEvent(event); -} - -void AbstractAudioScopeWidget::showEvent(QShowEvent *event) -{ - QWidget::showEvent(event); - m_scopeRect = scopeRect(); -} - -void AbstractAudioScopeWidget::paintEvent(QPaintEvent *) -{ - QPainter davinci(this); - davinci.drawImage(m_scopeRect.topLeft(), m_imgBackground); - davinci.drawImage(m_scopeRect.topLeft(), m_imgScope); - davinci.drawImage(m_scopeRect.topLeft(), m_imgHUD); -} - -void AbstractAudioScopeWidget::mouseMoveEvent(QMouseEvent *event) -{ - m_mousePos = event->pos(); - m_mouseWithinWidget = true; - emit signalMousePositionChanged(); -} -void AbstractAudioScopeWidget::leaveEvent(QEvent *) + AbstractScopeWidget(projMonitor, clipMonitor, trackMouse, parent) { - m_mouseWithinWidget = false; - emit signalMousePositionChanged(); -} - -void AbstractAudioScopeWidget::customContextMenuRequested(const QPoint &pos) -{ - m_menu->exec(this->mapToGlobal(pos)); -} - -uint AbstractAudioScopeWidget::calculateAccelFactorHUD(uint oldMseconds, uint) -{ - return ceil((float)oldMseconds*REALTIME_FPS / 1000); -} -uint AbstractAudioScopeWidget::calculateAccelFactorScope(uint oldMseconds, uint) -{ - return ceil((float)oldMseconds*REALTIME_FPS / 1000); -} -uint AbstractAudioScopeWidget::calculateAccelFactorBackground(uint oldMseconds, uint) -{ - return ceil((float)oldMseconds*REALTIME_FPS / 1000); -} - - -///// Slots ///// - -void AbstractAudioScopeWidget::slotHUDRenderingFinished(uint mseconds, uint oldFactor) -{ -// qDebug() << "HUD rendering has finished, waiting for termination in " << m_widgetName; - m_threadHUD.waitForFinished(); - m_imgHUD = m_threadHUD.result(); - - m_semaphoreHUD.release(1); - this->update(); - - int accel; - if (m_aRealtime->isChecked()) { - accel = calculateAccelFactorHUD(mseconds, oldFactor); - if (m_accelFactorHUD < 1) { - accel = 1; - } - m_accelFactorHUD = accel; - } - - if ((m_newHUDFrames > 0 && m_aAutoRefresh->isChecked()) || m_newHUDUpdates > 0) { -// qDebug() << "Trying to start a new HUD thread for " << m_widgetName -// << ". New frames/updates: " << m_newHUDFrames << "/" << m_newHUDUpdates; - prodHUDThread();; - } -} - -void AbstractAudioScopeWidget::slotScopeRenderingFinished(uint mseconds, uint oldFactor) -{ - // The signal can be received before the thread has really finished. So we - // need to wait until it has really finished before starting a new thread. -// qDebug() << "Scope rendering has finished, waiting for termination in " << m_widgetName; - m_threadScope.waitForFinished(); - m_imgScope = m_threadScope.result(); - - // The scope thread has finished. Now we can release the semaphore, allowing a new thread. - // See prodScopeThread where the semaphore is acquired again. - m_semaphoreScope.release(1); - this->update(); - - // Calculate the acceleration factor hint to get «realtime» updates. - int accel; - if (m_aRealtime->isChecked()) { - accel = calculateAccelFactorScope(mseconds, oldFactor); - if (accel < 1) { - // If mseconds happens to be 0. - accel = 1; - } - // Don't directly calculate with m_accelFactorScope as we are dealing with concurrency. - // If m_accelFactorScope is set to 0 at the wrong moment, who knows what might happen - // then :) Therefore use a local variable. - m_accelFactorScope = accel; - } - - if ((m_newScopeFrames > 0 && m_aAutoRefresh->isChecked()) || m_newScopeUpdates > 0) { -// qDebug() << "Trying to start a new scope thread for " << m_widgetName -// << ". New frames/updates: " << m_newScopeFrames << "/" << m_newScopeUpdates; - prodScopeThread(); - } -} - -void AbstractAudioScopeWidget::slotBackgroundRenderingFinished(uint mseconds, uint oldFactor) -{ -// qDebug() << "Background rendering has finished, waiting for termination in " << m_widgetName; - m_threadBackground.waitForFinished(); - m_imgBackground = m_threadBackground.result(); - - m_semaphoreBackground.release(1); - this->update(); - - int accel; - if (m_aRealtime->isChecked()) { - accel = calculateAccelFactorBackground(mseconds, oldFactor); - if (m_accelFactorBackground < 1) { - accel = 1; - } - m_accelFactorBackground = accel; - } - - if ((m_newBackgroundFrames > 0 && m_aAutoRefresh->isChecked()) || m_newBackgroundUpdates > 0) { -// qDebug() << "Trying to start a new background thread for " << m_widgetName -// << ". New frames/updates: " << m_newBackgroundFrames << "/" << m_newBackgroundUpdates; - prodBackgroundThread();; - } -} - -//void AbstractAudioScopeWidget::slotActiveMonitorChanged(bool isClipMonitor) -//{ -//// qDebug() << "Active monitor has changed in " << m_widgetName << ". Is the clip monitor active now? " << isClipMonitor; - -// bool b = m_activeRender->disconnect(this); -// Q_ASSERT(b); - -// m_activeRender = (isClipMonitor) ? m_clipMonitor->render : m_projMonitor->render; - -// //b &= connect(m_activeRender, SIGNAL(rendererPosition(int)), this, SLOT(slotRenderZoneUpdated())); -// b &= connect(m_activeRender, SIGNAL(frameUpdated(QImage)), this, SLOT(slotRenderZoneUpdated(QImage))); -// Q_ASSERT(b); - -// // Update the scope for the new monitor. -// prodHUDThread(); -// prodScopeThread(); -// prodBackgroundThread(); -//} - -void AbstractAudioScopeWidget::slotRenderZoneUpdated() -{ - m_newHUDFrames.fetchAndAddRelaxed(1); - m_newScopeFrames.fetchAndAddRelaxed(1); - m_newBackgroundFrames.fetchAndAddRelaxed(1); - -// qDebug() << "Audio incoming. New frames total HUD/Scope/Background: " << m_newHUDFrames -// << "/" << m_newScopeFrames << "/" << m_newBackgroundFrames; - - if (this->visibleRegion().isEmpty()) { -// qDebug() << "Scope of widget " << m_widgetName << " is not at the top, not rendering."; - } else { - if (m_aAutoRefresh->isChecked()) { - prodHUDThread(); - prodScopeThread(); - prodBackgroundThread(); - } - } } void AbstractAudioScopeWidget::slotReceiveAudio(const QVector& sampleData, int freq, int num_channels, int num_samples) { - //qDebug() << "Received audio. Size is " << (int) sampleData.size() << "."; - if (sampleData.size() > 0) { - //qDebug() << "Received: " << sampleData.data()[0] << ", " << sampleData.data()[1] << ", " << sampleData.data()[2]; - } m_audioFrame = sampleData; m_freq = freq; m_nChannels = num_channels; m_nSamples = num_samples; - slotRenderZoneUpdated(); + AbstractScopeWidget::slotRenderZoneUpdated(); //TODO } -void AbstractAudioScopeWidget::slotResetRealtimeFactor(bool realtimeChecked) -{ - if (!realtimeChecked) { - m_accelFactorHUD = 1; - m_accelFactorScope = 1; - m_accelFactorBackground = 1; - } -} - -bool AbstractAudioScopeWidget::autoRefreshEnabled() -{ - return m_aAutoRefresh->isChecked(); -} +AbstractAudioScopeWidget::~AbstractAudioScopeWidget() {} -void AbstractAudioScopeWidget::slotAutoRefreshToggled(bool autoRefresh) +QImage AbstractAudioScopeWidget::renderScope(uint accelerationFactor) { - if (isVisible()) emit requestAutoRefresh(autoRefresh); - // TODO only if depends on input - if (autoRefresh) { - //forceUpdate(); - m_requestForcedUpdate = true; - m_activeRender->sendFrameUpdate(); - } + return renderAudioScope(accelerationFactor, m_audioFrame, m_freq, m_nChannels, m_nSamples); } diff --git a/src/audioscopes/abstractaudioscopewidget.h b/src/audioscopes/abstractaudioscopewidget.h index 3bc6bead..e519e2a4 100644 --- a/src/audioscopes/abstractaudioscopewidget.h +++ b/src/audioscopes/abstractaudioscopewidget.h @@ -15,220 +15,38 @@ #include #include +#include "abstractscopewidget.h" + class QMenu; class Monitor; class Render; -class AbstractAudioScopeWidget : public QWidget +class AbstractAudioScopeWidget : public AbstractScopeWidget { Q_OBJECT public: AbstractAudioScopeWidget(Monitor *projMonitor, Monitor *clipMonitor, bool trackMouse = false, QWidget *parent = 0); - virtual ~AbstractAudioScopeWidget(); // Must be virtual because of inheritance, to avoid memory leaks - QPalette m_scopePalette; - - /** Initializes widget settings (reads configuration). - Has to be called in the implementing object. */ - void init(); - - /** Does this scope have auto-refresh enabled */ - bool autoRefreshEnabled(); - - ///// Unimplemented ///// - - virtual QString widgetName() const = 0; - - ///// Variables ///// - static const QPen penThick; - static const QPen penThin; - static const QPen penLight; - static const QPen penDark; + virtual ~AbstractAudioScopeWidget(); protected: - ///// Variables ///// - - Monitor *m_projMonitor; - Monitor *m_clipMonitor; - Render *m_activeRender; - - - /** The context menu. Feel free to add new entries in your implementation. */ - QMenu *m_menu; - - /** Enables auto refreshing of the scope. - This is when a new frame is shown on the active monitor. - Resize events always force a recalculation. */ - QAction *m_aAutoRefresh; - - /** Realtime rendering. Should be disabled if it is not supported. - Use the accelerationFactor variable passed to the render functions as a hint of - how many times faster the scope should be calculated. */ - QAction *m_aRealtime; - - /** The mouse position; Updated when the mouse enters the widget - AND mouse tracking has been enabled. */ - QPoint m_mousePos; - /** Knows whether the mouse currently lies within the widget or not. - Can e.g. be used for drawing a HUD only when the mouse is in the widget. */ - bool m_mouseWithinWidget; - - /** Offset from the widget's borders */ - const uchar offset; - - /** The rect on the widget we're painting in. - Can be used by the implementing widget, e.g. in the render methods. - Is updated when necessary (size changes). */ - QRect m_scopeRect; - - /** Images storing the calculated layers. Will be used on repaint events. */ - QImage m_imgHUD; - QImage m_imgScope; - QImage m_imgBackground; - - /** The acceleration factors can be accessed also by other renderer tasks, - e.g. to display the scope's acceleration factor in the HUD renderer. */ - int m_accelFactorHUD; - int m_accelFactorScope; - int m_accelFactorBackground; - - /** Reads the widget's configuration. - Can be extended in the implementing subclass (make sure to run readConfig as well). */ - virtual void readConfig(); - /** Writes the widget configuration. - Implementing widgets have to implement an own method and run it in their destructor. */ - void writeConfig(); - /** Identifier for the widget's configuration. */ - QString configName(); - + virtual QImage renderScope(uint accelerationFactor); ///// Unimplemented Methods ///// - - /** Where on the widget we can paint in. - May also update other variables that depend on the widget's size. */ - virtual QRect scopeRect() = 0; - - /** @brief HUD renderer. Must emit signalHUDRenderingFinished(). @see renderScope */ - virtual QImage renderHUD(uint accelerationFactor) = 0; /** @brief Scope renderer. Must emit signalScopeRenderingFinished() when calculation has finished, to allow multi-threading. accelerationFactor hints how much faster than usual the calculation should be accomplished, if possible. */ - virtual QImage renderScope(uint accelerationFactor, + virtual QImage renderAudioScope(uint accelerationFactor, const QVector audioFrame, const int freq, const int num_channels, const int num_samples) = 0; - /** @brief Background renderer. Must emit signalBackgroundRenderingFinished(). @see renderScope */ - virtual QImage renderBackground(uint accelerationFactor) = 0; - - /** Must return true if the HUD layer depends on the input monitor. - If it does not, then it does not need to be re-calculated when - a new frame from the monitor is incoming. */ - virtual bool isHUDDependingOnInput() const = 0; - /** @see isHUDDependingOnInput() */ - virtual bool isScopeDependingOnInput() const = 0; - /** @see isHUDDependingOnInput() */ - virtual bool isBackgroundDependingOnInput() const = 0; - - ///// Can be reimplemented ///// - /** Calculates the acceleration factor to be used by the render thread. - This method can be refined in the subclass if required. */ - virtual uint calculateAccelFactorHUD(uint oldMseconds, uint oldFactor); - virtual uint calculateAccelFactorScope(uint oldMseconds, uint oldFactor); - virtual uint calculateAccelFactorBackground(uint oldMseconds, uint oldFactor); - - ///// Reimplemented ///// - - void mouseMoveEvent(QMouseEvent *); - void leaveEvent(QEvent *); - void mouseReleaseEvent(QMouseEvent *); - void paintEvent(QPaintEvent *); - void resizeEvent(QResizeEvent *); - void showEvent(QShowEvent *); // Called when the widget is activated via the Menu entry - // void raise(); // Called only when manually calling the event -> useless - - -protected slots: - /** Forces an update of all layers. */ - void forceUpdate(bool doUpdate = true); - void forceUpdateHUD(); - void forceUpdateScope(); - void forceUpdateBackground(); - void slotAutoRefreshToggled(bool); - -signals: - /** mseconds represent the time taken for the calculation, - accelerationFactor is the acceleration factor that has been used. */ - void signalHUDRenderingFinished(uint mseconds, uint accelerationFactor); - void signalScopeRenderingFinished(uint mseconds, uint accelerationFactor); - void signalBackgroundRenderingFinished(uint mseconds, uint accelerationFactor); - - /** For the mouse position itself see m_mousePos. - To check whether the mouse has leaved the widget, see m_mouseWithinWidget. */ - void signalMousePositionChanged(); - - /** Do we need the renderer to send its frames to us? */ - void requestAutoRefresh(bool); private: - - /** Counts the number of frames that have been rendered in the active monitor. - The frame number will be reset when the calculation starts for the current frame. */ - QAtomicInt m_newHUDFrames; - QAtomicInt m_newScopeFrames; - QAtomicInt m_newBackgroundFrames; - - /** Counts the number of updates that, unlike new frames, force a recalculation - of the scope, like for example a resize event. */ - QAtomicInt m_newHUDUpdates; - QAtomicInt m_newScopeUpdates; - QAtomicInt m_newBackgroundUpdates; - - /** The semaphores ensure that the QFutures for the HUD/Scope/Background threads cannot - be assigned a new thread while it is still running. (Could cause deadlocks and other - nasty things known from parallelism.) */ - QSemaphore m_semaphoreHUD; - QSemaphore m_semaphoreScope; - QSemaphore m_semaphoreBackground; - - QFuture m_threadHUD; - QFuture m_threadScope; - QFuture m_threadBackground; - - bool initialDimensionUpdateDone; - bool m_requestForcedUpdate; - -// QImage m_scopeImage; - QVector m_audioFrame; //NEW + QVector m_audioFrame; int m_freq; int m_nChannels; int m_nSamples; - QString m_widgetName; - - void prodHUDThread(); - void prodScopeThread(); - void prodBackgroundThread(); - -public slots: - /** @brief Must be called when the active monitor has shown a new frame. - This slot must be connected in the implementing class, it is *not* - done in this abstract class. */ -// void slotActiveMonitorChanged(bool isClipMonitor); - private slots: - void customContextMenuRequested(const QPoint &pos); - /** To be called when a new frame has been received. - The scope then decides whether and when it wants to recalculate the scope, depending - on whether it is currently visible and whether a calculation thread is already running. */ - void slotRenderZoneUpdated(); -// void slotRenderZoneUpdated(QImage);//OLD void slotReceiveAudio(const QVector& sampleData, int freq, int num_channels, int num_samples); // NEW, TODO comment - /** The following slots are called when rendering of a component has finished. They e.g. update - the widget and decide whether to immediately restart the calculation thread. */ - void slotHUDRenderingFinished(uint mseconds, uint accelerationFactor); - void slotScopeRenderingFinished(uint mseconds, uint accelerationFactor); - void slotBackgroundRenderingFinished(uint mseconds, uint accelerationFactor); - - /** Resets the acceleration factors to 1 when realtime rendering is disabled. */ - void slotResetRealtimeFactor(bool realtimeChecked); }; diff --git a/src/audioscopes/audiospectrum.cpp b/src/audioscopes/audiospectrum.cpp index 9833b3f2..b45bb9c8 100644 --- a/src/audioscopes/audiospectrum.cpp +++ b/src/audioscopes/audiospectrum.cpp @@ -1,3 +1,13 @@ +/*************************************************************************** + * Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com) * + * This file is part of kdenlive. See www.kdenlive.org. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + #include "audiospectrum.h" #include "tools/kiss_fftr.h" @@ -47,7 +57,7 @@ AudioSpectrum::AudioSpectrum(Monitor *projMonitor, Monitor *clipMonitor, QWidget b &= connect(ui->windowSize, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateCfg())); Q_ASSERT(b); - init(); + AbstractScopeWidget::init(); } AudioSpectrum::~AudioSpectrum() { @@ -59,10 +69,10 @@ AudioSpectrum::~AudioSpectrum() void AudioSpectrum::readConfig() { - AbstractAudioScopeWidget::readConfig(); + AbstractScopeWidget::readConfig(); KSharedConfigPtr config = KGlobal::config(); - KConfigGroup scopeConfig(config, configName()); + KConfigGroup scopeConfig(config, AbstractScopeWidget::configName()); QString scale = scopeConfig.readEntry("scale"); if (scale == "lin") { m_aLin->setChecked(true); @@ -75,7 +85,7 @@ void AudioSpectrum::readConfig() void AudioSpectrum::writeConfig() { KSharedConfigPtr config = KGlobal::config(); - KConfigGroup scopeConfig(config, configName()); + KConfigGroup scopeConfig(config, AbstractScopeWidget::configName()); QString scale; if (m_aLin->isChecked()) { scale = "lin"; @@ -94,7 +104,7 @@ bool AudioSpectrum::isScopeDependingOnInput() const { return true; } bool AudioSpectrum::isHUDDependingOnInput() const { return false; } QImage AudioSpectrum::renderBackground(uint) { return QImage(); } -QImage AudioSpectrum::renderScope(uint, const QVector audioFrame, const int freq, const int num_channels, const int num_samples) +QImage AudioSpectrum::renderAudioScope(uint, const QVector audioFrame, const int freq, const int num_channels, const int num_samples) { if (audioFrame.size() > 63) { m_freqMax = freq / 2; @@ -152,7 +162,6 @@ QImage AudioSpectrum::renderScope(uint, const QVector audioFrame, const val = pow(pow(fabs(freqData[i].r),2) + pow(fabs(freqData[i].i),2), .5); } freqSpectrum[i] = val; - // qDebug() << val; } @@ -248,11 +257,9 @@ QImage AudioSpectrum::renderHUD(uint) } - qDebug() << "max freq: " << m_freqMax; const uint hzDiff = ceil( ((float)minDistX)/rect.width() * m_freqMax / 1000 ) * 1000; - qDebug() << hzDiff; int x; - for (int hz = hzDiff; hz < m_freqMax; hz += hzDiff) { + for (uint hz = hzDiff; hz < m_freqMax; hz += hzDiff) { x = rect.width() * ((float)hz)/m_freqMax; davinci.drawLine(x, 0, x, rect.height()+4); davinci.drawText(x-4, rect.height() + 20, QVariant(hz/1000).toString()); diff --git a/src/audioscopes/audiospectrum.h b/src/audioscopes/audiospectrum.h index 51f6e62c..205d1202 100644 --- a/src/audioscopes/audiospectrum.h +++ b/src/audioscopes/audiospectrum.h @@ -1,3 +1,13 @@ +/*************************************************************************** + * Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com) * + * This file is part of kdenlive. See www.kdenlive.org. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + #ifndef AUDIOSPECTRUM_H #define AUDIOSPECTRUM_H @@ -23,7 +33,7 @@ protected: ///// Implemented methods ///// QRect scopeRect(); QImage renderHUD(uint accelerationFactor); - QImage renderScope(uint accelerationFactor, const QVector audioFrame, const int freq, const int num_channels, const int num_samples); + QImage renderAudioScope(uint accelerationFactor, const QVector audioFrame, const int freq, const int num_channels, const int num_samples); QImage renderBackground(uint accelerationFactor); bool isHUDDependingOnInput() const; bool isScopeDependingOnInput() const; diff --git a/src/histogram.cpp b/src/histogram.cpp index a5d1fc42..4065b163 100644 --- a/src/histogram.cpp +++ b/src/histogram.cpp @@ -15,7 +15,7 @@ #include "renderer.h" Histogram::Histogram(Monitor *projMonitor, Monitor *clipMonitor, QWidget *parent) : - AbstractScopeWidget(projMonitor, clipMonitor, false, parent) + AbstractGfxScopeWidget(projMonitor, clipMonitor, false, parent) { ui = new Ui::Histogram_UI(); ui->setupUi(this); @@ -65,7 +65,7 @@ Histogram::~Histogram() void Histogram::readConfig() { - AbstractScopeWidget::readConfig(); + AbstractGfxScopeWidget::readConfig(); KSharedConfigPtr config = KGlobal::config(); KConfigGroup scopeConfig(config, configName()); diff --git a/src/histogram.h b/src/histogram.h index af066a53..7796899e 100644 --- a/src/histogram.h +++ b/src/histogram.h @@ -11,12 +11,12 @@ #ifndef HISTOGRAM_H #define HISTOGRAM_H -#include "abstractscopewidget.h" +#include "abstractgfxscopewidget.h" #include "ui_histogram_ui.h" class HistogramGenerator; -class Histogram : public AbstractScopeWidget { +class Histogram : public AbstractGfxScopeWidget { Q_OBJECT public: diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 48242bc4..dc40c9b5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3969,7 +3969,7 @@ void MainWindow::slotMonitorRequestRenderFrame(bool request) return; } else { for (int i = 0; i < m_scopesList.count(); i++) { - if (m_scopesList.at(i)->isVisible() && tabifiedDockWidgets(m_scopesList.at(i)).isEmpty() && static_cast(m_scopesList.at(i)->widget())->autoRefreshEnabled()) { + if (m_scopesList.at(i)->isVisible() && tabifiedDockWidgets(m_scopesList.at(i)).isEmpty() && static_cast(m_scopesList.at(i)->widget())->autoRefreshEnabled()) { request = true; break; } @@ -3991,8 +3991,8 @@ void MainWindow::slotDoUpdateScopeFrameRequest() // Check scopes bool request = false; for (int i = 0; i < m_scopesList.count(); i++) { - if (!m_scopesList.at(i)->widget()->visibleRegion().isEmpty() && static_cast(m_scopesList.at(i)->widget())->autoRefreshEnabled()) { - kDebug() << "SCOPE VISIBLE: " << static_cast(m_scopesList.at(i)->widget())->widgetName(); + if (!m_scopesList.at(i)->widget()->visibleRegion().isEmpty() && static_cast(m_scopesList.at(i)->widget())->autoRefreshEnabled()) { + kDebug() << "SCOPE VISIBLE: " << static_cast(m_scopesList.at(i)->widget())->widgetName(); request = true; break; } @@ -4012,8 +4012,8 @@ void MainWindow::slotUpdateColorScopes() bool request = false; for (int i = 0; i < m_scopesList.count(); i++) { // Check if we need the renderer to send a new frame for update - if (!m_scopesList.at(i)->widget()->visibleRegion().isEmpty() && !(static_cast(m_scopesList.at(i)->widget())->autoRefreshEnabled())) request = true; - static_cast(m_scopesList.at(i)->widget())->slotActiveMonitorChanged(m_clipMonitor->isActive()); + if (!m_scopesList.at(i)->widget()->visibleRegion().isEmpty() && !(static_cast(m_scopesList.at(i)->widget())->autoRefreshEnabled())) request = true; + static_cast(m_scopesList.at(i)->widget())->slotActiveMonitorChanged(m_clipMonitor->isActive()); } if (request) { if (m_clipMonitor->isActive()) m_clipMonitor->render->sendFrameUpdate(); @@ -4029,7 +4029,7 @@ void MainWindow::slotOpenStopmotion() for (int i = 0; i < m_scopesList.count(); i++) { // Check if we need the renderer to send a new frame for update /*if (!m_scopesList.at(i)->widget()->visibleRegion().isEmpty() && !(static_cast(m_scopesList.at(i)->widget())->autoRefreshEnabled())) request = true;*/ - connect(m_stopmotion, SIGNAL(gotFrame(QImage)), static_cast(m_scopesList.at(i)->widget()), SLOT(slotRenderZoneUpdated(QImage))); + connect(m_stopmotion, SIGNAL(gotFrame(QImage)), static_cast(m_scopesList.at(i)->widget()), SLOT(slotRenderZoneUpdated(QImage))); //static_cast(m_scopesList.at(i)->widget())->slotMonitorCapture(); } } diff --git a/src/rgbparade.cpp b/src/rgbparade.cpp index 40762aa7..0ea40eea 100644 --- a/src/rgbparade.cpp +++ b/src/rgbparade.cpp @@ -17,7 +17,7 @@ #include "rgbparadegenerator.h" RGBParade::RGBParade(Monitor *projMonitor, Monitor *clipMonitor, QWidget *parent) : - AbstractScopeWidget(projMonitor, clipMonitor, true, parent) + AbstractGfxScopeWidget(projMonitor, clipMonitor, true, parent) { ui = new Ui::RGBParade_UI(); ui->setupUi(this); @@ -58,7 +58,7 @@ RGBParade::~RGBParade() void RGBParade::readConfig() { - AbstractScopeWidget::readConfig(); + AbstractGfxScopeWidget::readConfig(); KSharedConfigPtr config = KGlobal::config(); KConfigGroup scopeConfig(config, configName()); diff --git a/src/rgbparade.h b/src/rgbparade.h index 1757f707..0fd42fe6 100644 --- a/src/rgbparade.h +++ b/src/rgbparade.h @@ -12,7 +12,7 @@ #define RGBPARADE_H #include -#include "abstractscopewidget.h" +#include "abstractgfxscopewidget.h" #include "ui_rgbparade_ui.h" class Monitor; @@ -20,7 +20,7 @@ class QImage; class RGBParade_UI; class RGBParadeGenerator; -class RGBParade : public AbstractScopeWidget +class RGBParade : public AbstractGfxScopeWidget { public: RGBParade(Monitor *projMonitor, Monitor *clipMonitor, QWidget *parent = 0); diff --git a/src/vectorscope.cpp b/src/vectorscope.cpp index 860fd61c..729643e5 100644 --- a/src/vectorscope.cpp +++ b/src/vectorscope.cpp @@ -43,7 +43,7 @@ const QPointF YPbPr_Yl(-.5, .081); Vectorscope::Vectorscope(Monitor *projMonitor, Monitor *clipMonitor, QWidget *parent) : - AbstractScopeWidget(projMonitor, clipMonitor, true, parent), + AbstractGfxScopeWidget(projMonitor, clipMonitor, true, parent), m_gain(1) { ui = new Ui::Vectorscope_UI(); @@ -142,7 +142,7 @@ QString Vectorscope::widgetName() const { return QString("Vectorscope"); } void Vectorscope::readConfig() { - AbstractScopeWidget::readConfig(); + AbstractGfxScopeWidget::readConfig(); KSharedConfigPtr config = KGlobal::config(); KConfigGroup scopeConfig(config, configName()); diff --git a/src/vectorscope.h b/src/vectorscope.h index d2798cae..66c66019 100644 --- a/src/vectorscope.h +++ b/src/vectorscope.h @@ -13,7 +13,7 @@ #include #include "ui_vectorscope_ui.h" -#include "abstractscopewidget.h" +#include "abstractgfxscopewidget.h" class ColorPlaneExport; class ColorTools; @@ -24,7 +24,7 @@ class VectorscopeGenerator; enum BACKGROUND_MODE { BG_NONE = 0, BG_YUV = 1, BG_CHROMA = 2, BG_YPbPr = 3 }; -class Vectorscope : public AbstractScopeWidget { +class Vectorscope : public AbstractGfxScopeWidget { Q_OBJECT public: diff --git a/src/waveform.cpp b/src/waveform.cpp index 5bd51f19..27beee2e 100644 --- a/src/waveform.cpp +++ b/src/waveform.cpp @@ -26,7 +26,7 @@ const QSize Waveform::m_textWidth(35,0); const int Waveform::m_paddingBottom(20); Waveform::Waveform(Monitor *projMonitor, Monitor *clipMonitor, QWidget *parent) : - AbstractScopeWidget(projMonitor, clipMonitor, true, parent) + AbstractGfxScopeWidget(projMonitor, clipMonitor, true, parent) { ui = new Ui::Waveform_UI(); ui->setupUi(this); @@ -71,7 +71,7 @@ Waveform::~Waveform() void Waveform::readConfig() { - AbstractScopeWidget::readConfig(); + AbstractGfxScopeWidget::readConfig(); KSharedConfigPtr config = KGlobal::config(); KConfigGroup scopeConfig(config, configName()); diff --git a/src/waveform.h b/src/waveform.h index 5ece240f..45fde952 100644 --- a/src/waveform.h +++ b/src/waveform.h @@ -11,14 +11,14 @@ #ifndef WAVEFORM_H #define WAVEFORM_H -#include "abstractscopewidget.h" +#include "abstractgfxscopewidget.h" #include "ui_waveform_ui.h" class Waveform_UI; class WaveformGenerator; -class Waveform : public AbstractScopeWidget { +class Waveform : public AbstractGfxScopeWidget { Q_OBJECT public: