diff --git a/effects/_test/CMakeLists.txt b/effects/_test/CMakeLists.txt index 019b586b18..e4a1a5fc8c 100644 --- a/effects/_test/CMakeLists.txt +++ b/effects/_test/CMakeLists.txt @@ -1,7 +1,7 @@ # Adds effect plugin with given name. Sources are given after the name macro(KWIN4_ADD_EFFECT name) kde4_add_plugin(kwin4_effect_${name} ${ARGN}) - target_link_libraries(kwin4_effect_${name} kwineffects ${KDE4_KDEUI_LIBS} ${X11_LIBRARIES} kephal) + target_link_libraries(kwin4_effect_${name} kwineffects ${KDE4_KDEUI_LIBS} ${X11_LIBRARIES} ${QT_QTWEBKIT_LIBRARY} kephal) install(TARGETS kwin4_effect_${name} DESTINATION ${PLUGIN_INSTALL_DIR}) endmacro(KWIN4_ADD_EFFECT) @@ -25,6 +25,7 @@ SET(kwin4_effect_tests_sources howto.cpp test_input.cpp test_thumbnail.cpp + kicker/kicker.cpp ) install( FILES demo_shakymove.desktop @@ -34,6 +35,7 @@ install( FILES howto.desktop test_input.desktop test_thumbnail.desktop + kicker/kicker.desktop DESTINATION ${SERVICES_INSTALL_DIR}/kwin ) if(OPENGL_FOUND) diff --git a/effects/_test/kicker/CMakeLists.txt b/effects/_test/kicker/CMakeLists.txt new file mode 100644 index 0000000000..5fac700c8a --- /dev/null +++ b/effects/_test/kicker/CMakeLists.txt @@ -0,0 +1,29 @@ +####################################### +# Effect + +# Source files +set( kwin4_effect_builtins_sources ${kwin4_effect_builtins_sources} + kicker/kicker.cpp + ) + +# .desktop files +install( FILES + kicker/kicker.desktop + DESTINATION ${SERVICES_INSTALL_DIR}/kwin ) + +####################################### +# Config + +# Source files +#set( kwin4_effect_builtins_config_sources ${kwin4_effect_builtins_config_sources} + #cube/cube_config.cpp + #cube/cube_config.ui + #cube/cubeslide_config.cpp + #cube/cubeslide_config.ui + #) + +# .desktop files +#install( FILES + # cube/cube_config.desktop + # cube/cubeslide_config.desktop + # DESTINATION ${SERVICES_INSTALL_DIR}/kwin ) diff --git a/effects/_test/kicker/kicker.cpp b/effects/_test/kicker/kicker.cpp new file mode 100644 index 0000000000..f8966fe9b9 --- /dev/null +++ b/effects/_test/kicker/kicker.cpp @@ -0,0 +1,258 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + + Copyright (C) 2010 Martin Gräßlin + +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 program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "kicker.h" + +#include +#include +#include +#include +#include +#include + +namespace KWin +{ + +KWIN_EFFECT( kicker, KickerEffect ) + +KickerEffect::KickerEffect() + : m_page( new QWebPage( this ) ) + , m_timer( new QTimer( this ) ) + , m_goalTimer( new QTimer( this ) ) + , m_goalActive( false ) + , m_ascending( false ) + { + connect(m_page, SIGNAL(loadFinished(bool)), this, SLOT(slotLoadFinished(bool))); + connect(m_timer, SIGNAL(timeout()), this, SLOT(timeout())); + connect(m_goalTimer, SIGNAL(timeout()), this, SLOT(goalTimeout())); + + reconfigure( ReconfigureAll ); + // reload the webpage each half a minute + m_timer->start(30*1000); + // goal animation should end after seven seconds + m_goalTimer->setInterval( 7 * 1000 ); + m_goalTimer->setSingleShot( true ); + // the animations should have a duration of half a second with an + // ease in out curve + m_timeLine.setCurveShape( TimeLine::EaseInOutCurve ); + m_timeLine.setDuration( 500 ); + // let's download the webpage immediatelly + timeout(); + } + +KickerEffect::~KickerEffect() + { + while( !m_frames.isEmpty() ) + { + delete m_frames.first(); + m_frames.removeFirst(); + } + while( !m_goalFrames.isEmpty() ) + { + delete m_goalFrames.first(); + m_goalFrames.removeFirst(); + } + } + +void KickerEffect::reconfigure( ReconfigureFlags ) + { + } + +void KickerEffect::prePaintScreen( ScreenPrePaintData& data, int time ) + { + // the goal animation uses a KWin::TimeLine to modify the opacity values + // as long as the goal timer has not timed out m_goalActive is true + // after the timeout an animation might still be running, so we continue + // till progress reaches 0.0 again + if( m_goalActive || m_timeLine.progress() != 0.0 ) + { + // the animation might either be ascending (increasing opacity) or + // descending (decreasing opacity). In case of ascending we add time + // to the timeline till progress reaches 1.0. There we switch direction + // to descending. In descending case of course vice versa. + if( m_ascending ) + { + m_timeLine.addTime( time ); + if( m_timeLine.progress() == 1.0 ) + { + m_ascending = false; + } + } + else + { + m_timeLine.removeTime( time ); + // it is possible that this is the last animation. Therefore the + // anding with goalActive. If it has been the last animation we + // do not need to keep the goal EffectFrame around any more, so + // we delete them. + if( m_timeLine.progress() == 0.0 && m_goalActive ) + { + m_ascending = true; + } + else if( m_timeLine.progress() == 0.0 ) + { + // goal animation finshed, let's delete the EffectFrames + while( !m_goalFrames.isEmpty() ) + { + delete m_goalFrames.first(); + m_goalFrames.removeFirst(); + } + m_goalFrames.clear(); + } + } + } + effects->prePaintScreen( data, time ); + } + +void KickerEffect::paintScreen( int mask, QRegion region, ScreenPaintData &data ) + { + effects->paintScreen( mask, region, data ); + + if( m_size.isValid() ) + { + // let's paint the score EffectFrame with unmodified opacity. As it + // uses Plasma styled textures it's still translucent, but the text + // is fully opaque. + foreach( EffectFrame* frame, m_frames ) + frame->render( region, 1.0 ); + } + if( m_goalActive || m_timeLine.progress() != 0.0 ) + { + // the goal animation changes the opacity. Therefore we modify the + // opacity by multiplying with the timeline's progress. + // The text should be fully opaque (1.0), while the background should + // be translucent (0.75) + foreach( EffectFrame* frame, m_goalFrames ) + frame->render( region, 1.0 * m_timeLine.progress(), 0.75 * m_timeLine.progress() ); + // we are animating: need a new frame + effects->addRepaintFull(); + } + } + +void KickerEffect::slotLoadFinished( bool ok ) + { + // if connection failed let's keep the last score + if( !ok ) + return; + + QWebElement doc = m_page->mainFrame()->documentElement(); + // CSS selector for the teams + QWebElementCollection teams = doc.findAll("td.lttabver h1 a"); + if( teams.count() != 2 ) + return; + QString firstTeam = teams[0].toPlainText(); + QString secondTeam = teams[1].toPlainText(); + + // CSS selector for the current score + QString result = doc.findFirst("td.lttaberg h1").toPlainText(); + if( m_score != result ) + { + // the score changed, a goal might have been scored + bool activate = true; + // but not if the web page has been loaded for the first time + if( m_score.isNull() ) + activate = false; + // not if we entered extra time + if( !m_score.contains("i.V.") && result.contains("i.V.") ) + activate = false; + // not if extra time ended + if( !m_score.contains("n.V.") && result.contains("n.V.") ) + activate = false; + // not if penality shootout begins + if( !m_score.contains("i.E.") && result.contains("i.E.") ) + activate = false; + // not if first or second half starts. + if( m_score.count( '-' ) > result.count( '-' ) ) + activate = false; + // yeah it's a goal - generate the EffectFrame and start the animation + if( activate ) + { + generateGoalImage(); + m_goalActive = true; + m_ascending = true; + m_goalTimer->start(); + } + m_score = result; + } + + QString text = firstTeam + ' ' + result + ' ' + secondTeam; + QFont font; + font.setBold( true ); + QFontMetrics fm(font); + m_size = fm.boundingRect(text).adjusted(-10, -10, 10, 10).size(); + // we don't want to reposition the EffectFrames, therefore we delete the + // old ones. Normally you wouldn't do that, but as we only update once in + // half a minute and it's easier to code... + while( !m_frames.isEmpty() ) + { + delete m_frames.first(); + m_frames.removeFirst(); + } + m_frames.clear(); + // and properly position the frame on each screen + for( int i = 0; i < effects->numScreens(); i++ ) + { + QRect area = effects->clientArea( ScreenArea, i, effects->currentDesktop()); + QRect geometry = QRect( area.x() + area.width() - m_size.width() - 20, area.y() + 20, m_size.width(), m_size.height() ); + EffectFrame *frame = new EffectFrame( EffectFrame::Styled ); + frame->setText( text ); + frame->setFont( font ); + frame->setGeometry( geometry ); + m_frames.append(frame); + } + // effect frame changed and animation might have started - we need a full repaint + effects->addRepaintFull(); + } + +void KickerEffect::timeout() + { + // hard coded URL to the liveticker of the match Argentina vs Germany at World Cup 2010. + // If this URL is not valid anymore you can find newer examples on the referrenced web site. + m_page->mainFrame()->load(QUrl("http://www.kicker.de/news/fussball/wm/spielplan/weltmeisterschaft/2010/5/833353/livematch_argentinien_deutschland.html")); + } + +void KickerEffect::generateGoalImage() + { + QFont font( "FreeMono", 172 ); + QString goal( "GOAL" ); + QFontMetrics fm( font ); + QSize size = fm.boundingRect( goal ).adjusted(-10, -10, 10, 10).size(); + for( int i = 0; i < effects->numScreens(); i++ ) + { + // place one frame on the center of each screen + QRect area = effects->clientArea( ScreenArea, i, effects->currentDesktop()); + QRect geometry = QRect( area.x() + (area.width() - size.width())/2, + area.y() + (area.height() - size.height())/2, + size.width(), size.height()); + EffectFrame *frame = new EffectFrame( EffectFrame::Unstyled, false ); + frame->setText( goal ); + frame->setFont( font ); + frame->setGeometry( geometry ); + m_goalFrames.append(frame); + } + } + +void KickerEffect::goalTimeout() + { + // stop the animation + m_goalActive = false; + effects->addRepaintFull(); + } + +} // namespace diff --git a/effects/_test/kicker/kicker.desktop b/effects/_test/kicker/kicker.desktop new file mode 100644 index 0000000000..2dc4d987b2 --- /dev/null +++ b/effects/_test/kicker/kicker.desktop @@ -0,0 +1,17 @@ +[Desktop Entry] +name=Kicker Liveticker Demo Effect +Icon=preferences-system-windows-effect-kicker +Comment=Demonstration effect for using EffectFrames + +Type=Service +X-KDE-ServiceTypes=KWin/Effect +X-KDE-PluginInfo-Author=Martin Gräßlin +X-KDE-PluginInfo-Email=kde@martin-graesslin.com +X-KDE-PluginInfo-Name=kwin4_effect_kicker +X-KDE-PluginInfo-Version=0.1.0 +X-KDE-PluginInfo-Category=Candy +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +X-KDE-Library=kwin4_effect_builtins +X-KDE-Ordering=50 \ No newline at end of file diff --git a/effects/_test/kicker/kicker.h b/effects/_test/kicker/kicker.h new file mode 100644 index 0000000000..f5ef430f46 --- /dev/null +++ b/effects/_test/kicker/kicker.h @@ -0,0 +1,84 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + + Copyright (C) 2010 Martin Gräßlin + +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 program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ + +#ifndef KWIN_KICKER_H +#define KWIN_KICKER_H + +#include +#include + +class QTimer; +class QWebPage; + +namespace KWin +{ + +/** + * This effect was written for a talk at Akademy 2010 to have the current + * score of the 2010 World Cup Quarterfinal Argentina vs Germany on screen + * during the presentation. + * + * The effect connects to the German football website "kicker.de" and parses + * the returned website. This explains the name of the effect - it is unrelated + * to the panel of KDE 3. + * + * The effect is a nice illustration on how to use EffectFrames and animations. + * Therefore it was imported into the test directory in SVN to provide a small + * example. + * + * The effect consists of two parts: + * displaying the current score and showing a goal animation when a goal is + * scored. The first uses a Plasma styled EffectFrame, while the latter uses + * an unstyled effect frame. + * + * @author Martin Gräßlin + */ +class KickerEffect : public QObject, public Effect + { + Q_OBJECT + public: + KickerEffect(); + ~KickerEffect(); + virtual void reconfigure( ReconfigureFlags ); + virtual void prePaintScreen( ScreenPrePaintData& data, int time ); + virtual void paintScreen( int mask, QRegion region, ScreenPaintData& data ); + + private Q_SLOTS: + void slotLoadFinished(bool ok); + void timeout(); + void goalTimeout(); + + private: + void generateGoalImage(); + QWebPage *m_page; + QTimer *m_timer; + QTimer *m_goalTimer; + QSize m_size; + QList m_frames; + QList m_goalFrames; + bool m_goalActive; + QString m_score; + TimeLine m_timeLine; + bool m_ascending; + }; + +} + +#endif // KWIN_KICKER_H