newsticker/summarywidget.cpp
1 /*
2  This file is part of Kontact.
3  Copyright (c) 2003 Tobias Koenig <tokoe@kde.org>
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program; if not, write to the Free Software
17  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 
19  As a special exception, permission is given to link this program
20  with any edition of TQt, and distribute the resulting executable,
21  without including the source code for TQt in the source distribution.
22 */
23 
24 #include <tqclipboard.h>
25 #include <tqeventloop.h>
26 #include <tqhbox.h>
27 #include <tqlayout.h>
28 #include <tqpixmap.h>
29 #include <tqpopupmenu.h>
30 #include <tqcursor.h>
31 
32 #include <dcopclient.h>
33 #include <tdeapplication.h>
34 #include <kcharsets.h>
35 #include <tdeconfig.h>
36 #include <kdebug.h>
37 #include <tdeglobal.h>
38 #include <kiconloader.h>
39 #include <tdelocale.h>
40 #include <kurllabel.h>
41 
42 #include "summarywidget.h"
43 
44 SummaryWidget::SummaryWidget( TQWidget *parent, const char *name )
45  : Kontact::Summary( parent, name ),
46  DCOPObject( "NewsTickerPlugin" ), mLayout( 0 ), mFeedCounter( 0 )
47 {
48  TQVBoxLayout *vlay = new TQVBoxLayout( this, 3, 3 );
49 
50  TQPixmap icon = TDEGlobal::iconLoader()->loadIcon( "kontact_news",
51  TDEIcon::Desktop, TDEIcon::SizeMedium );
52 
53  TQWidget *header = createHeader( this, icon, i18n( "News Feeds" ) );
54  vlay->addWidget( header );
55 
56  TQString error;
57  TQCString appID;
58 
59  bool dcopAvailable = true;
60  if ( !kapp->dcopClient()->isApplicationRegistered( "rssservice" ) ) {
61  if ( TDEApplication::startServiceByDesktopName( "rssservice", TQStringList(), &error, &appID ) ) {
62  TQLabel *label = new TQLabel( i18n( "No rss dcop service available.\nYou need rssservice to use this plugin." ), this );
63  vlay->addWidget( label, TQt::AlignHCenter );
64  dcopAvailable = false;
65  }
66  }
67 
68  mBaseWidget = new TQWidget( this, "baseWidget" );
69  vlay->addWidget( mBaseWidget );
70 
71  connect( &mTimer, TQ_SIGNAL( timeout() ), this, TQ_SLOT( updateDocuments() ) );
72 
73  readConfig();
74 
75  connectDCOPSignal( 0, 0, "documentUpdateError(DCOPRef,int)", "documentUpdateError(DCOPRef, int)", false );
76 
77  if ( dcopAvailable )
78  initDocuments();
79 
80  connectDCOPSignal( 0, 0, "added(TQString)", "documentAdded(TQString)", false );
81  connectDCOPSignal( 0, 0, "removed(TQString)", "documentRemoved(TQString)", false );
82 }
83 
84 int SummaryWidget::summaryHeight() const
85 {
86  return ( mFeeds.count() == 0 ? 1 : mFeeds.count() );
87 }
88 
89 void SummaryWidget::documentAdded( TQString )
90 {
91  initDocuments();
92 }
93 
94 void SummaryWidget::documentRemoved( TQString )
95 {
96  initDocuments();
97 }
98 
99 void SummaryWidget::configChanged()
100 {
101  readConfig();
102 
103  updateView();
104 }
105 
106 void SummaryWidget::readConfig()
107 {
108  TDEConfig config( "kcmkontactkntrc" );
109  config.setGroup( "General" );
110 
111  mUpdateInterval = config.readNumEntry( "UpdateInterval", 600 );
112  mArticleCount = config.readNumEntry( "ArticleCount", 4 );
113 }
114 
115 void SummaryWidget::initDocuments()
116 {
117  mFeeds.clear();
118 
119  DCOPRef dcopCall( "rssservice", "RSSService" );
120  TQStringList urls;
121  dcopCall.call( "list()" ).get( urls );
122 
123  if ( urls.isEmpty() ) { // add default
124  urls.append( "http://www.kde.org/dotkdeorg.rdf" );
125  dcopCall.send( "add(TQString)", urls[ 0 ] );
126  }
127 
128  TQStringList::Iterator it;
129  for ( it = urls.begin(); it != urls.end(); ++it ) {
130  DCOPRef feedRef = dcopCall.call( "document(TQString)", *it );
131 
132  Feed feed;
133  feed.ref = feedRef;
134  feedRef.call( "title()" ).get( feed.title );
135  feedRef.call( "link()" ).get( feed.url );
136  feedRef.call( "pixmap()" ).get( feed.logo );
137  mFeeds.append( feed );
138 
139  disconnectDCOPSignal( "rssservice", feedRef.obj(), "documentUpdated(DCOPRef)", 0 );
140  connectDCOPSignal( "rssservice", feedRef.obj(), "documentUpdated(DCOPRef)",
141  "documentUpdated(DCOPRef)", false );
142 
143  if ( tqApp )
144  tqApp->eventLoop()->processEvents( TQEventLoop::ExcludeUserInput |
145  TQEventLoop::ExcludeSocketNotifiers );
146  }
147 
148  updateDocuments();
149 }
150 
151 void SummaryWidget::updateDocuments()
152 {
153  mTimer.stop();
154 
155  FeedList::Iterator it;
156  for ( it = mFeeds.begin(); it != mFeeds.end(); ++it )
157  (*it).ref.send( "refresh()" );
158 
159  mTimer.start( 1000 * mUpdateInterval );
160 }
161 
162 void SummaryWidget::documentUpdated( DCOPRef feedRef )
163 {
164  ArticleMap map;
165 
166  int numArticles = feedRef.call( "count()" );
167  for ( int i = 0; i < numArticles; ++i ) {
168  DCOPRef artRef = feedRef.call( "article(int)", i );
169  TQString title, url;
170 
171  if ( tqApp )
172  tqApp->eventLoop()->processEvents( TQEventLoop::ExcludeUserInput |
173  TQEventLoop::ExcludeSocketNotifiers );
174 
175  artRef.call( "title()" ).get( title );
176  artRef.call( "link()" ).get( url );
177 
178  TQPair<TQString, KURL> article(title, KURL( url ));
179  map.append( article );
180  }
181 
182  FeedList::Iterator it;
183  for ( it = mFeeds.begin(); it != mFeeds.end(); ++it )
184  if ( (*it).ref.obj() == feedRef.obj() ) {
185  (*it).map = map;
186  if ( (*it).title.isEmpty() )
187  feedRef.call( "title()" ).get( (*it).title );
188  if ( (*it).url.isEmpty() )
189  feedRef.call( "link()" ).get( (*it).url );
190  if ( (*it).logo.isNull() )
191  feedRef.call( "pixmap()" ).get( (*it).logo );
192  }
193 
194  mFeedCounter++;
195  if ( mFeedCounter == mFeeds.count() ) {
196  mFeedCounter = 0;
197  updateView();
198  }
199 }
200 
201 void SummaryWidget::updateView()
202 {
203  mLabels.setAutoDelete( true );
204  mLabels.clear();
205  mLabels.setAutoDelete( false );
206 
207  delete mLayout;
208  mLayout = new TQVBoxLayout( mBaseWidget, 3 );
209 
210  TQFont boldFont;
211  boldFont.setBold( true );
212  boldFont.setPointSize( boldFont.pointSize() + 2 );
213 
214  FeedList::Iterator it;
215  for ( it = mFeeds.begin(); it != mFeeds.end(); ++it ) {
216  TQHBox *hbox = new TQHBox( mBaseWidget );
217  mLayout->addWidget( hbox );
218 
219  // icon
220  KURLLabel *urlLabel = new KURLLabel( hbox );
221  urlLabel->setURL( (*it).url );
222  urlLabel->setPixmap( (*it).logo );
223  urlLabel->setMaximumSize( urlLabel->minimumSizeHint() );
224  mLabels.append( urlLabel );
225 
226  connect( urlLabel, TQ_SIGNAL( leftClickedURL( const TQString& ) ),
227  kapp, TQ_SLOT( invokeBrowser( const TQString& ) ) );
228  connect( urlLabel, TQ_SIGNAL( rightClickedURL( const TQString& ) ),
229  this, TQ_SLOT( rmbMenu( const TQString& ) ) );
230 
231  // header
232  TQLabel *label = new TQLabel( hbox );
233  label->setText( KCharsets::resolveEntities( (*it).title ) );
234  label->setAlignment( AlignLeft|AlignVCenter );
235  label->setFont( boldFont );
236  label->setIndent( 6 );
237  label->setMaximumSize( label->minimumSizeHint() );
238  mLabels.append( label );
239 
240  hbox->setMaximumWidth( hbox->minimumSizeHint().width() );
241  hbox->show();
242 
243  // articles
244  ArticleMap articles = (*it).map;
245  ArticleMap::Iterator artIt;
246  int numArticles = 0;
247  for ( artIt = articles.begin(); artIt != articles.end() && numArticles < mArticleCount; ++artIt ) {
248  urlLabel = new KURLLabel( (*artIt).second.url(), (*artIt).first, mBaseWidget );
249  urlLabel->installEventFilter( this );
250  //TODO: RichText causes too much horizontal space between articles
251  //urlLabel->setTextFormat( RichText );
252  mLabels.append( urlLabel );
253  mLayout->addWidget( urlLabel );
254 
255  connect( urlLabel, TQ_SIGNAL( leftClickedURL( const TQString& ) ),
256  kapp, TQ_SLOT( invokeBrowser( const TQString& ) ) );
257  connect( urlLabel, TQ_SIGNAL( rightClickedURL( const TQString& ) ),
258  this, TQ_SLOT( rmbMenu( const TQString& ) ) );
259 
260 
261  numArticles++;
262  }
263  }
264 
265  for ( TQLabel *label = mLabels.first(); label; label = mLabels.next() )
266  label->show();
267 }
268 
269 void SummaryWidget::documentUpdateError( DCOPRef feedRef, int errorCode )
270 {
271  kdDebug() << " error while updating document, error code: " << errorCode << endl;
272  FeedList::Iterator it;
273  for ( it = mFeeds.begin(); it != mFeeds.end(); ++it ) {
274  if ( (*it).ref.obj() == feedRef.obj() ) {
275  mFeeds.remove( it );
276  break;
277  }
278  }
279 
280  if ( mFeedCounter == mFeeds.count() ) {
281  mFeedCounter = 0;
282  updateView();
283  }
284 
285 }
286 
287 TQStringList SummaryWidget::configModules() const
288 {
289  return "kcmkontactknt.desktop";
290 }
291 
292 void SummaryWidget::updateSummary( bool )
293 {
294  updateDocuments();
295 }
296 
297 void SummaryWidget::rmbMenu( const TQString& url )
298 {
299  TQPopupMenu menu;
300  menu.insertItem( i18n( "Copy URL to Clipboard" ) );
301  int id = menu.exec( TQCursor::pos() );
302  if ( id != -1 )
303  kapp->clipboard()->setText( url, TQClipboard::Clipboard );
304 }
305 
306 bool SummaryWidget::eventFilter( TQObject *obj, TQEvent* e )
307 {
308  if ( obj->inherits( "KURLLabel" ) ) {
309  KURLLabel* label = static_cast<KURLLabel*>( obj );
310  if ( e->type() == TQEvent::Enter )
311  emit message( label->url() );
312  if ( e->type() == TQEvent::Leave )
313  emit message( TQString() );
314  }
315 
316  return Kontact::Summary::eventFilter( obj, e );
317 }
318 
319 #include "summarywidget.moc"