[krita] krita: Implemented a "Drop Frames" mode for Krita and made it default option
Dmitry Kazakov
dimula73 at gmail.com
Fri Dec 18 11:42:51 UTC 2015
Git commit 9fda1f749e9392a644b4b71c56e5cf76ae4832bb by Dmitry Kazakov.
Committed on 18/12/2015 at 11:42.
Pushed by dkazakov into branch 'master'.
Implemented a "Drop Frames" mode for Krita and made it default option
Now you can switch on the "Drop Frames" mode in the Animation Docker
to ensure your animation is playing with the requested frame rate,
even when the GPU cannot handle this amount of data to be shown.
TODO: we still need an icon for this feature! Atm it uses "Auto Frame" icon!
BUG:356245
CC:kimageshop at kde.org
M +56 -6 krita/plugins/extensions/dockers/animation/animation_docker.cpp
M +3 -0 krita/plugins/extensions/dockers/animation/animation_docker.h
M +1 -0 krita/plugins/extensions/dockers/animation/kis_animation_utils.cpp
M +1 -0 krita/plugins/extensions/dockers/animation/kis_animation_utils.h
M +10 -0 krita/plugins/extensions/dockers/animation/wdg_animation.ui
M +122 -31 krita/ui/canvas/kis_animation_player.cpp
M +2 -2 krita/ui/canvas/kis_animation_player.h
M +16 -1 krita/ui/kis_config.cc
M +4 -1 krita/ui/kis_config.h
M +14 -1 krita/ui/kis_config_notifier.cpp
M +8 -1 krita/ui/kis_config_notifier.h
http://commits.kde.org/krita/9fda1f749e9392a644b4b71c56e5cf76ae4832bb
diff --git a/krita/plugins/extensions/dockers/animation/animation_docker.cpp b/krita/plugins/extensions/dockers/animation/animation_docker.cpp
index 09bc075..8238f67 100644
--- a/krita/plugins/extensions/dockers/animation/animation_docker.cpp
+++ b/krita/plugins/extensions/dockers/animation/animation_docker.cpp
@@ -34,11 +34,25 @@
#include "kis_animation_utils.h"
#include "krita_utils.h"
#include "kis_image_config.h"
+#include "kis_config.h"
#include "kis_signals_blocker.h"
#include "ui_wdg_animation.h"
+void setupActionButton(const QString &text,
+ KisAction::ActivationFlags flags,
+ bool defaultValue,
+ QToolButton *button,
+ KisAction **action)
+{
+ *action = new KisAction(text, button);
+ (*action)->setActivationFlags(flags);
+ (*action)->setCheckable(true);
+ (*action)->setChecked(defaultValue);
+ button->setDefaultAction(*action);
+}
+
AnimationDocker::AnimationDocker()
: QDockWidget(i18n("Animation"))
, m_canvas(0)
@@ -91,16 +105,23 @@ AnimationDocker::AnimationDocker()
m_deleteKeyframeAction->setActivationFlags(KisAction::ACTIVE_LAYER);
m_animationWidget->btnDeleteKeyframe->setDefaultAction(m_deleteKeyframeAction);
- m_lazyFrameAction = new KisAction(KisAnimationUtils::lazyFrameCreationActionName, m_animationWidget->btnLazyFrame);
- m_lazyFrameAction->setActivationFlags(KisAction::ACTIVE_IMAGE);
- m_lazyFrameAction->setCheckable(true);
-
{
KisImageConfig cfg;
- m_lazyFrameAction->setChecked(cfg.lazyFrameCreationEnabled());
+ setupActionButton(KisAnimationUtils::lazyFrameCreationActionName,
+ KisAction::ACTIVE_IMAGE,
+ cfg.lazyFrameCreationEnabled(),
+ m_animationWidget->btnLazyFrame,
+ &m_lazyFrameAction);
}
- m_animationWidget->btnLazyFrame->setDefaultAction(m_lazyFrameAction);
+ {
+ KisConfig cfg;
+ setupActionButton(KisAnimationUtils::dropFramesActionName,
+ KisAction::ACTIVE_IMAGE,
+ cfg.animationDropFrames(),
+ m_animationWidget->btnDropFrames,
+ &m_dropFramesAction);
+ }
QFont font;
font.setPointSize(1.7 * font.pointSize());
@@ -122,6 +143,7 @@ AnimationDocker::AnimationDocker()
connect(m_addDuplicateFrameAction, SIGNAL(triggered()), this, SLOT(slotAddDuplicateFrame()));
connect(m_deleteKeyframeAction, SIGNAL(triggered()), this, SLOT(slotDeleteKeyframe()));
connect(m_lazyFrameAction, SIGNAL(toggled(bool)), this, SLOT(slotLazyFrameChanged(bool)));
+ connect(m_dropFramesAction, SIGNAL(toggled(bool)), this, SLOT(slotDropFramesChanged(bool)));
m_animationWidget->btnOnionSkinOptions->setToolTip(i18n("Onion Skins"));
connect(m_animationWidget->btnOnionSkinOptions, SIGNAL(clicked()), this, SLOT(slotOnionSkinOptions()));
@@ -193,6 +215,7 @@ void AnimationDocker::setMainWindow(KisViewManager *view)
actionManager->addAction("last_frame", m_lastFrameAction);
actionManager->addAction("lazy_frame", m_lazyFrameAction);
+ actionManager->addAction("drop_frames", m_dropFramesAction);
actionManager->addAction("toggle_playback", m_playPauseAction);
actionManager->addAction("add_blank_frame", m_addBlankFrameAction);
@@ -431,6 +454,21 @@ void AnimationDocker::updateLazyFrameIcon()
.arg(KritaUtils::toLocalizedOnOff(value)));
}
+void AnimationDocker::updateDropFramesIcon()
+{
+ KisConfig cfg;
+
+ const bool value = cfg.animationDropFrames();
+
+ m_dropFramesAction->setIcon(value ?
+ KisIconUtils::loadIcon("lazyframeOn") :
+ KisIconUtils::loadIcon("lazyframeOff"));
+
+ m_dropFramesAction->setText(QString("%1 (%2)")
+ .arg(KisAnimationUtils::dropFramesActionName)
+ .arg(KritaUtils::toLocalizedOnOff(value)));
+}
+
void AnimationDocker::slotUpdateIcons()
{
m_previousFrameAction->setIcon(KisIconUtils::loadIcon("prevframe"));
@@ -448,6 +486,7 @@ void AnimationDocker::slotUpdateIcons()
m_deleteKeyframeAction->setIcon(KisIconUtils::loadIcon("deletekeyframe"));
updateLazyFrameIcon();
+ updateDropFramesIcon();
m_animationWidget->btnOnionSkinOptions->setIcon(KisIconUtils::loadIcon("onion_skin_options"));
m_animationWidget->btnOnionSkinOptions->setIconSize(QSize(22, 22));
@@ -465,6 +504,7 @@ void AnimationDocker::slotUpdateIcons()
m_animationWidget->btnAddDuplicateFrame->setIconSize(QSize(22, 22));
m_animationWidget->btnDeleteKeyframe->setIconSize(QSize(22, 22));
m_animationWidget->btnLazyFrame->setIconSize(QSize(22, 22));
+ m_animationWidget->btnDropFrames->setIconSize(QSize(22, 22));
}
void AnimationDocker::slotLazyFrameChanged(bool value)
@@ -477,4 +517,14 @@ void AnimationDocker::slotLazyFrameChanged(bool value)
}
}
+void AnimationDocker::slotDropFramesChanged(bool value)
+{
+ KisConfig cfg;
+
+ if (value != cfg.animationDropFrames()) {
+ cfg.setAnimationDropFrames(value);
+ updateDropFramesIcon();
+ }
+}
+
#include "animation_docker.moc"
diff --git a/krita/plugins/extensions/dockers/animation/animation_docker.h b/krita/plugins/extensions/dockers/animation/animation_docker.h
index ec8860b..e584623 100644
--- a/krita/plugins/extensions/dockers/animation/animation_docker.h
+++ b/krita/plugins/extensions/dockers/animation/animation_docker.h
@@ -68,8 +68,10 @@ private Q_SLOTS:
void updatePlayPauseIcon();
void updateLazyFrameIcon();
+ void updateDropFramesIcon();
void slotLazyFrameChanged(bool value);
+ void slotDropFramesChanged(bool value);
private:
@@ -91,6 +93,7 @@ private:
KisAction *m_addDuplicateFrameAction;
KisAction *m_deleteKeyframeAction;
KisAction *m_lazyFrameAction;
+ KisAction *m_dropFramesAction;
KisMainWindow *m_mainWindow;
};
diff --git a/krita/plugins/extensions/dockers/animation/kis_animation_utils.cpp b/krita/plugins/extensions/dockers/animation/kis_animation_utils.cpp
index 67df50e..5f9ca1d 100644
--- a/krita/plugins/extensions/dockers/animation/kis_animation_utils.cpp
+++ b/krita/plugins/extensions/dockers/animation/kis_animation_utils.cpp
@@ -34,6 +34,7 @@ namespace KisAnimationUtils {
const QString removeFrameActionName = i18n("Remove Frame");
const QString removeFramesActionName = i18n("Remove Frames");
const QString lazyFrameCreationActionName = i18n("Auto Frame Mode");
+ const QString dropFramesActionName = i18n("Drop Frames");
const QString showLayerActionName = i18n("Show in Timeline");
diff --git a/krita/plugins/extensions/dockers/animation/kis_animation_utils.h b/krita/plugins/extensions/dockers/animation/kis_animation_utils.h
index 4a42bd8..63e3fb6 100644
--- a/krita/plugins/extensions/dockers/animation/kis_animation_utils.h
+++ b/krita/plugins/extensions/dockers/animation/kis_animation_utils.h
@@ -52,6 +52,7 @@ namespace KisAnimationUtils
extern const QString removeFrameActionName;
extern const QString removeFramesActionName;
extern const QString lazyFrameCreationActionName;
+ extern const QString dropFramesActionName;
extern const QString showLayerActionName;
};
diff --git a/krita/plugins/extensions/dockers/animation/wdg_animation.ui b/krita/plugins/extensions/dockers/animation/wdg_animation.ui
index 83f0590..fced8f1 100644
--- a/krita/plugins/extensions/dockers/animation/wdg_animation.ui
+++ b/krita/plugins/extensions/dockers/animation/wdg_animation.ui
@@ -442,6 +442,16 @@
</property>
</widget>
</item>
+ <item>
+ <widget class="QToolButton" name="btnDropFrames">
+ <property name="text">
+ <string>...</string>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
</layout>
diff --git a/krita/ui/canvas/kis_animation_player.cpp b/krita/ui/canvas/kis_animation_player.cpp
index f14022c..6fd2601 100644
--- a/krita/ui/canvas/kis_animation_player.cpp
+++ b/krita/ui/canvas/kis_animation_player.cpp
@@ -18,17 +18,16 @@
#include "kis_animation_player.h"
+#include <QElapsedTimer>
#include <QTimer>
+#include <QtMath>
-//#define DEBUG_FRAMERATE
-
-#ifdef DEBUG_FRAMERATE
-#include <QTime>
-#endif /* DEBUG_FRAMERATE */
+//#define PLAYER_DEBUG_FRAMERATE
#include "kis_global.h"
-
+#include "kis_config.h"
+#include "kis_config_notifier.h"
#include "kis_image.h"
#include "kis_canvas2.h"
#include "kis_animation_frame_cache.h"
@@ -36,19 +35,38 @@
#include "kis_image_animation_interface.h"
#include "kis_time_range.h"
+#ifdef PLAYER_DEBUG_FRAMERATE
+#include <boost/accumulators/accumulators.hpp>
+#include <boost/accumulators/statistics/stats.hpp>
+#include <boost/accumulators/statistics/rolling_mean.hpp>
+
+using namespace boost::accumulators;
+typedef accumulator_set<qreal, stats<tag::rolling_mean> > FpsAccumulator;
+#endif /* PLAYER_DEBUG_FRAMERATE */
+
struct KisAnimationPlayer::Private
{
public:
- Private(KisAnimationPlayer *_q) : q(_q) {}
+ Private(KisAnimationPlayer *_q)
+ : q(_q),
+#ifdef PLAYER_DEBUG_FRAMERATE
+ realFpsAccumulator(tag::rolling_window::window_size = 12),
+ droppedFpsAccumulator(tag::rolling_window::window_size = 12),
+#endif /* PLAYER_DEBUG_FRAMERATE */
+ dropFramesMode(true),
+ nextFrameExpectedTime(0),
+ expectedInterval(0),
+ expectedFrame(0),
+ lastTimerInterval(0),
+ lastPaintedFrame(0)
+ {}
KisAnimationPlayer *q;
bool useFastFrameUpload;
bool playing;
- int currentFrame;
QTimer *timer;
- QImage frame;
int firstFrame;
int lastFrame;
@@ -59,11 +77,30 @@ public:
KisSignalAutoConnectionsStore cancelStrokeConnections;
-#ifdef DEBUG_FRAMERATE
- QTime frameRateTimer;
-#endif /* DEBUG_FRAMERATE */
+#ifdef PLAYER_DEBUG_FRAMERATE
+ QElapsedTimer realFpsTimer;
+ FpsAccumulator realFpsAccumulator;
+ FpsAccumulator droppedFpsAccumulator;
+#endif /* PLAYER_DEBUG_FRAMERATE */
+
+ bool dropFramesMode;
+
+ QElapsedTimer playbackTime;
+ int nextFrameExpectedTime;
+ int expectedInterval;
+ int expectedFrame;
+ int lastTimerInterval;
+ int lastPaintedFrame;
void stopImpl(bool doUpdates);
+
+ int incFrame(int frame, int inc) {
+ frame += inc;
+ if (frame > lastFrame) {
+ frame = firstFrame + frame - lastFrame - 1;
+ }
+ return frame;
+ }
};
KisAnimationPlayer::KisAnimationPlayer(KisCanvas2 *canvas)
@@ -77,11 +114,23 @@ KisAnimationPlayer::KisAnimationPlayer(KisCanvas2 *canvas)
m_d->timer = new QTimer(this);
connect(m_d->timer, SIGNAL(timeout()), this, SLOT(slotUpdate()));
+ m_d->timer->setSingleShot(true);
+
+ connect(KisConfigNotifier::instance(),
+ SIGNAL(dropFramesModeChanged()),
+ SLOT(slotUpdateDropFramesMode()));
+ slotUpdateDropFramesMode();
}
KisAnimationPlayer::~KisAnimationPlayer()
{}
+void KisAnimationPlayer::slotUpdateDropFramesMode()
+{
+ KisConfig cfg;
+ m_d->dropFramesMode = cfg.animationDropFrames();
+}
+
void KisAnimationPlayer::connectCancelSignals()
{
m_d->cancelStrokeConnections.addConnection(
@@ -125,9 +174,19 @@ void KisAnimationPlayer::slotUpdatePlaybackTimer()
m_d->fps = animation->framerate();
m_d->firstFrame = range.start();
m_d->lastFrame = range.end();
- m_d->currentFrame = qBound(m_d->firstFrame, m_d->currentFrame, m_d->lastFrame);
+ m_d->expectedFrame = qBound(m_d->firstFrame, m_d->expectedFrame, m_d->lastFrame);
+
+ m_d->expectedInterval = qreal(1000) / m_d->fps / m_d->playbackSpeed;
+ m_d->lastTimerInterval = m_d->expectedInterval;
+ m_d->timer->start(m_d->expectedInterval);
+
+ if (m_d->playbackTime.isValid()) {
+ m_d->playbackTime.restart();
+ } else {
+ m_d->playbackTime.start();
+ }
- m_d->timer->start(qreal(1000) / m_d->fps / m_d->playbackSpeed);
+ m_d->nextFrameExpectedTime = m_d->playbackTime.elapsed() + m_d->expectedInterval;
}
void KisAnimationPlayer::play()
@@ -135,7 +194,8 @@ void KisAnimationPlayer::play()
m_d->playing = true;
slotUpdatePlaybackTimer();
- m_d->currentFrame = m_d->firstFrame;
+ m_d->expectedFrame = m_d->firstFrame;
+ m_d->lastPaintedFrame = m_d->firstFrame;
connectCancelSignals();
}
@@ -171,7 +231,7 @@ bool KisAnimationPlayer::isPlaying()
int KisAnimationPlayer::currentTime()
{
- return m_d->currentFrame;
+ return m_d->lastPaintedFrame;
}
void KisAnimationPlayer::displayFrame(int time)
@@ -181,19 +241,35 @@ void KisAnimationPlayer::displayFrame(int time)
void KisAnimationPlayer::slotUpdate()
{
- m_d->currentFrame++;
- if (m_d->currentFrame > m_d->lastFrame) m_d->currentFrame = m_d->firstFrame;
-
- uploadFrame(m_d->currentFrame);
+ uploadFrame(-1);
}
-#ifdef DEBUG_FRAMERATE
-#include "../../sdk/tests/testutil.h"
-static TestUtil::MeasureAvgPortion C(25);
-#endif /* DEBUG_FRAMERATE */
-
void KisAnimationPlayer::uploadFrame(int frame)
{
+ if (frame < 0) {
+ const int currentTime = m_d->playbackTime.elapsed();
+ const int framesDiff = currentTime - m_d->nextFrameExpectedTime;
+ const qreal framesDiffNorm = qreal(framesDiff) / m_d->expectedInterval;
+
+ // qDebug() << ppVar(framesDiff)
+ // << ppVar(m_d->expectedFrame)
+ // << ppVar(framesDiffNorm)
+ // << ppVar(m_d->lastTimerInterval);
+
+ if (m_d->dropFramesMode) {
+ frame = m_d->incFrame(m_d->expectedFrame, qMax(0, qRound(framesDiffNorm)));
+ } else {
+ frame = m_d->expectedFrame;
+ }
+
+ m_d->nextFrameExpectedTime = currentTime + m_d->expectedInterval;
+
+ m_d->lastTimerInterval = qMax(0.0, m_d->lastTimerInterval - 0.5 * framesDiff);
+ m_d->expectedFrame = m_d->incFrame(frame, 1);
+
+ m_d->timer->start(m_d->lastTimerInterval);
+ }
+
if (m_d->canvas->frameCache()) {
if (m_d->canvas->frameCache()->uploadFrame(frame)) {
m_d->canvas->updateCanvas();
@@ -209,14 +285,29 @@ void KisAnimationPlayer::uploadFrame(int frame)
emit sigFrameChanged();
}
-#ifdef DEBUG_FRAMERATE
- if (!m_d->frameRateTimer.isValid()) {
- m_d->frameRateTimer.start();
+#ifdef PLAYER_DEBUG_FRAMERATE
+ if (!m_d->realFpsTimer.isValid()) {
+ m_d->realFpsTimer.start();
} else {
- const int elapsed = m_d->frameRateTimer.restart();
- C.addTotal(1000 / elapsed);
+ const int elapsed = m_d->realFpsTimer.restart();
+ m_d->realFpsAccumulator(elapsed);
+
+ int numFrames = frame - m_d->lastPaintedFrame;
+ if (numFrames < 0) {
+ numFrames += m_d->lastFrame - m_d->firstFrame + 1;
+ }
+
+ if (numFrames > 0) {
+ m_d->droppedFpsAccumulator(qreal(elapsed) / numFrames);
+ }
+
+ qDebug() << " RFPS:" << 1000.0 / rolling_mean(m_d->realFpsAccumulator)
+ << "DFPS:" << 1000.0 / rolling_mean(m_d->droppedFpsAccumulator) << ppVar(numFrames);
}
-#endif /* DEBUG_FRAMERATE */
+#endif /* PLAYER_DEBUG_FRAMERATE */
+
+ m_d->lastPaintedFrame = frame;
+
}
void KisAnimationPlayer::slotCancelPlayback()
diff --git a/krita/ui/canvas/kis_animation_player.h b/krita/ui/canvas/kis_animation_player.h
index 52903b3..b056cfd 100644
--- a/krita/ui/canvas/kis_animation_player.h
+++ b/krita/ui/canvas/kis_animation_player.h
@@ -52,7 +52,7 @@ public Q_SLOTS:
void slotCancelPlaybackSafe();
void slotUpdatePlaybackSpeed(double value);
void slotUpdatePlaybackTimer();
-
+ void slotUpdateDropFramesMode();
Q_SIGNALS:
void sigFrameChanged();
void sigPlaybackStopped();
@@ -60,7 +60,7 @@ Q_SIGNALS:
private:
void connectCancelSignals();
void disconnectCancelSignals();
- void uploadFrame(int frame);
+ void uploadFrame(int time);
private:
struct Private;
diff --git a/krita/ui/kis_config.cc b/krita/ui/kis_config.cc
index ef0cbd2..7faa51b 100644
--- a/krita/ui/kis_config.cc
+++ b/krita/ui/kis_config.cc
@@ -1626,7 +1626,7 @@ void KisConfig::setEnableOpenGLDebugging(bool value) const
m_cfg.writeEntry("enableOpenGLDebugging", value);
}
-void KisConfig::setEnableAmdVectorizationWorkaround(bool value) const
+void KisConfig::setEnableAmdVectorizationWorkaround(bool value)
{
m_cfg.writeEntry("amdDisableVectorWorkaround", value);
}
@@ -1635,3 +1635,18 @@ bool KisConfig::enableAmdVectorizationWorkaround(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("amdDisableVectorWorkaround", false));
}
+
+void KisConfig::setAnimationDropFrames(bool value)
+{
+ bool oldValue = animationDropFrames();
+
+ if (value == oldValue) return;
+
+ m_cfg.writeEntry("animationDropFrames", value);
+ KisConfigNotifier::instance()->notifyDropFramesModeChanged();
+}
+
+bool KisConfig::animationDropFrames(bool defaultValue) const
+{
+ return (defaultValue ? true : m_cfg.readEntry("animationDropFrames", true));
+}
diff --git a/krita/ui/kis_config.h b/krita/ui/kis_config.h
index 060c5a7..de7c6fe 100644
--- a/krita/ui/kis_config.h
+++ b/krita/ui/kis_config.h
@@ -465,9 +465,12 @@ public:
void setEnableOpenGLDebugging(bool value) const;
bool enableOpenGLDebugging(bool defaultValue = false) const;
- void setEnableAmdVectorizationWorkaround(bool value) const;
+ void setEnableAmdVectorizationWorkaround(bool value);
bool enableAmdVectorizationWorkaround(bool defaultValue = false) const;
+ bool animationDropFrames(bool defaultValue = false) const;
+ void setAnimationDropFrames(bool value);
+
template<class T>
void writeEntry(const QString& name, const T& value) {
m_cfg.writeEntry(name, value);
diff --git a/krita/ui/kis_config_notifier.cpp b/krita/ui/kis_config_notifier.cpp
index cd4ccc6..0fe6511 100644
--- a/krita/ui/kis_config_notifier.cpp
+++ b/krita/ui/kis_config_notifier.cpp
@@ -20,11 +20,21 @@
#include <QGlobalStatic>
#include <kis_debug.h>
+#include "kis_signal_compressor.h"
Q_GLOBAL_STATIC(KisConfigNotifier, s_instance)
+struct KisConfigNotifier::Private
+{
+ Private() : dropFramesModeCompressor(300, KisSignalCompressor::FIRST_ACTIVE) {}
+
+ KisSignalCompressor dropFramesModeCompressor;
+};
+
KisConfigNotifier::KisConfigNotifier()
+ : m_d(new Private)
{
+ connect(&m_d->dropFramesModeCompressor, SIGNAL(timeout()), SIGNAL(dropFramesModeChanged()));
}
KisConfigNotifier::~KisConfigNotifier()
@@ -42,4 +52,7 @@ void KisConfigNotifier::notifyConfigChanged(void)
emit configChanged();
}
-
+void KisConfigNotifier::notifyDropFramesModeChanged()
+{
+ m_d->dropFramesModeCompressor.start();
+}
diff --git a/krita/ui/kis_config_notifier.h b/krita/ui/kis_config_notifier.h
index b822281..6234669 100644
--- a/krita/ui/kis_config_notifier.h
+++ b/krita/ui/kis_config_notifier.h
@@ -19,6 +19,7 @@
#define KIS_CONFIG_NOTIFIER_H_
#include <QObject>
+#include <QScopedPointer>
#include "kritaui_export.h"
@@ -44,15 +45,21 @@ public:
*/
void notifyConfigChanged(void);
+ void notifyDropFramesModeChanged();
+
Q_SIGNALS:
/**
* This signal is emitted whenever notifyConfigChanged() is called.
*/
void configChanged(void);
-
+ void dropFramesModeChanged();
private:
KisConfigNotifier(const KisConfigNotifier&);
KisConfigNotifier operator=(const KisConfigNotifier&);
+
+private:
+ struct Private;
+ const QScopedPointer<Private> m_d;
};
#endif // KIS_CONFIG_NOTIFIER_H_
More information about the kimageshop
mailing list