/******************************************************************** 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