akregator/src

articlelistview.cpp
1 /*
2  This file is part of Akregator.
3 
4  Copyright (C) 2004 Stanislav Karchebny <Stanislav.Karchebny@kdemail.net>
5  2005 Frank Osterfeld <frank.osterfeld at kdemail.net>
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10 
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with this program; if not, write to the Free Software
18  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 
20  As a special exception, permission is given to link this program
21  with any edition of TQt, and distribute the resulting executable,
22  without including the source code for TQt in the source distribution.
23 */
24 
25 #include "akregatorconfig.h"
26 #include "actionmanager.h"
27 #include "articlelistview.h"
28 #include "article.h"
29 #include "articlefilter.h"
30 #include "dragobjects.h"
31 #include "feed.h"
32 #include "treenode.h"
33 #include "treenodevisitor.h"
34 
35 #include <kstandarddirs.h>
36 #include <kdebug.h>
37 #include <tdeglobal.h>
38 #include <kiconloader.h>
39 #include <tdelocale.h>
40 #include <kcharsets.h>
41 #include <kurl.h>
42 
43 #include <tqdatetime.h>
44 #include <tqpixmap.h>
45 #include <tqpopupmenu.h>
46 #include <tqptrlist.h>
47 #include <tqvaluelist.h>
48 #include <tqwhatsthis.h>
49 #include <tqheader.h>
50 #include <tqdragobject.h>
51 #include <tqsimplerichtext.h>
52 #include <tqpainter.h>
53 #include <tqapplication.h>
54 
55 #include <ctime>
56 
57 namespace Akregator {
58 
59 class ArticleListView::ArticleListViewPrivate
60 {
61  public:
62 
63  ArticleListViewPrivate(ArticleListView* parent) : m_parent(parent) { }
64 
65  void ensureCurrentItemVisible()
66  {
67  if (m_parent->currentItem())
68  {
69  m_parent->center( m_parent->contentsX(), m_parent->itemPos(m_parent->currentItem()), 0, 9.0 );
70  }
71  }
72 
73  ArticleListView* m_parent;
74 
76  TQMap<Article, ArticleItem*> articleMap;
77  TreeNode* node;
80  enum ColumnMode { groupMode, feedMode };
81  ColumnMode columnMode;
82  int feedWidth;
83  bool noneSelected;
84 
85  ColumnLayoutVisitor* columnLayoutVisitor;
86 };
87 
88 class ArticleListView::ColumnLayoutVisitor : public TreeNodeVisitor
89 {
90  public:
91  ColumnLayoutVisitor(ArticleListView* view) : m_view(view) {}
92  virtual ~ColumnLayoutVisitor() {}
93 
94  virtual bool visitTagNode(TagNode* /*node*/)
95  {
96  if (m_view->d->columnMode == ArticleListViewPrivate::feedMode)
97  {
98  m_view->setColumnWidth(1, m_view->d->feedWidth);
99  m_view->d->columnMode = ArticleListViewPrivate::groupMode;
100  }
101  return true;
102  }
103 
104  virtual bool visitFolder(Folder* /*node*/)
105  {
106  if (m_view->d->columnMode == ArticleListViewPrivate::feedMode)
107  {
108  m_view->setColumnWidth(1, m_view->d->feedWidth);
109  m_view->d->columnMode = ArticleListViewPrivate::groupMode;
110  }
111  return true;
112  }
113 
114  virtual bool visitFeed(Feed* /*node*/)
115  {
116  if (m_view->d->columnMode == ArticleListViewPrivate::groupMode)
117  {
118  m_view->d->feedWidth = m_view->columnWidth(1);
119  m_view->hideColumn(1);
120  m_view->d->columnMode = ArticleListViewPrivate::feedMode;
121  }
122  return true;
123  }
124  private:
125 
126  ArticleListView* m_view;
127 
128 };
129 
130 class ArticleListView::ArticleItem : public TDEListViewItem
131  {
132  friend class ArticleListView;
133 
134  public:
135  ArticleItem( TQListView *parent, const Article& a);
136  ~ArticleItem();
137 
138  Article& article();
139 
140  void paintCell ( TQPainter * p, const TQColorGroup & cg, int column, int width, int align );
141  virtual int compare(TQListViewItem *i, int col, bool ascending) const;
142 
143  void updateItem(const Article& article);
144 
145  virtual ArticleItem* itemAbove() { return static_cast<ArticleItem*>(TDEListViewItem::itemAbove()); }
146 
147  virtual ArticleItem* nextSibling() { return static_cast<ArticleItem*>(TDEListViewItem::nextSibling()); }
148 
149  private:
150  Article m_article;
151  time_t m_pubDate;
152  static TQPixmap keepFlag() {
153  static TQPixmap s_keepFlag = TQPixmap(locate("data", "akregator/pics/akregator_flag.png"));
154  return s_keepFlag;
155  }
156 };
157 
158 // FIXME: Remove resolveEntities for KDE 4.0, it's now done in the parser
159 ArticleListView::ArticleItem::ArticleItem( TQListView *parent, const Article& a)
160  : TDEListViewItem( parent, KCharsets::resolveEntities(a.title()), a.feed()->title(), TDEGlobal::locale()->formatDateTime(a.pubDate(), true, false) ), m_article(a), m_pubDate(a.pubDate().toTime_t())
161 {
162  if (a.keep())
163  setPixmap(0, keepFlag());
164 }
165 
166 ArticleListView::ArticleItem::~ArticleItem()
167 {
168 }
169 
170 Article& ArticleListView::ArticleItem::article()
171 {
172  return m_article;
173 }
174 
175 // paint ze peons
176 void ArticleListView::ArticleItem::paintCell ( TQPainter * p, const TQColorGroup & cg, int column, int width, int align )
177 {
178  if (article().status() == Article::Read)
179  TDEListViewItem::paintCell( p, cg, column, width, align );
180  else
181  {
182  TQColorGroup cg2(cg);
183 
184  if (article().status() == Article::Unread)
185  cg2.setColor(TQColorGroup::Text, Settings::unreadTextColor());
186  else // New
187  cg2.setColor(TQColorGroup::Text, Settings::readTextColor());
188 
189  TDEListViewItem::paintCell( p, cg2, column, width, align );
190  }
191 
192 }
193 
194 void ArticleListView::ArticleItem::updateItem(const Article& article)
195 {
196  m_article = article;
197  setPixmap(0, m_article.keep() ? keepFlag() : TQPixmap());
198  setText(0, KCharsets::resolveEntities(m_article.title()));
199  setText(1, m_article.feed()->title());
200  setText(2, TDEGlobal::locale()->formatDateTime(m_article.pubDate(), true, false));
201 }
202 
203 int ArticleListView::ArticleItem::compare(TQListViewItem *i, int col, bool ascending) const {
204  if (col == 2)
205  {
206  ArticleItem* item = static_cast<ArticleItem*>(i);
207  if (m_pubDate == item->m_pubDate)
208  return 0;
209  return (m_pubDate > item->m_pubDate) ? 1 : -1;
210  }
211  return TDEListViewItem::compare(i, col, ascending);
212 }
213 
214 /* ==================================================================================== */
215 
216 ArticleListView::ArticleListView(TQWidget *parent, const char *name)
217  : TDEListView(parent, name)
218 {
219  d = new ArticleListViewPrivate(this);
220  d->noneSelected = true;
221  d->node = 0;
222  d->columnMode = ArticleListViewPrivate::feedMode;
223 
224  d->columnLayoutVisitor = new ColumnLayoutVisitor(this);
225  setMinimumSize(250, 150);
226  addColumn(i18n("Article"));
227  addColumn(i18n("Feed"));
228  addColumn(i18n("Date"));
229  setSelectionMode(TQListView::Extended);
230  setColumnWidthMode(2, TQListView::Maximum);
231  setColumnWidthMode(1, TQListView::Manual);
232  setColumnWidthMode(0, TQListView::Manual);
233  setRootIsDecorated(false);
234  setItemsRenameable(false);
235  setItemsMovable(false);
236  setAllColumnsShowFocus(true);
237  setDragEnabled(true); // FIXME before we implement dragging between archived feeds??
238  setAcceptDrops(false); // FIXME before we implement dragging between archived feeds??
239  setFullWidth(false);
240 
241  setShowSortIndicator(true);
242  setDragAutoScroll(true);
243  setDropHighlighter(false);
244 
245  int c = Settings::sortColumn();
246  setSorting((c >= 0 && c <= 2) ? c : 2, Settings::sortAscending());
247 
248  int w;
249  w = Settings::titleWidth();
250  if (w > 0) {
251  setColumnWidth(0, w);
252  }
253 
254  w = Settings::feedWidth();
255  if (w > 0) {
256  setColumnWidth(1, w);
257  }
258 
259  w = Settings::dateWidth();
260  if (w > 0) {
261  setColumnWidth(2, w);
262  }
263 
264  d->feedWidth = columnWidth(1);
265  hideColumn(1);
266 
267  header()->setStretchEnabled(true, 0);
268 
269  TQWhatsThis::add(this, i18n("<h2>Article list</h2>"
270  "Here you can browse articles from the currently selected feed. "
271  "You can also manage articles, as marking them as persistent (\"Keep Article\") or delete them, using the right mouse button menu."
272  "To view the web page of the article, you can open the article internally in a tab or in an external browser window."));
273 
274  connect(this, TQ_SIGNAL(currentChanged(TQListViewItem*)), this, TQ_SLOT(slotCurrentChanged(TQListViewItem* )));
275  connect(this, TQ_SIGNAL(selectionChanged()), this, TQ_SLOT(slotSelectionChanged()));
276  connect(this, TQ_SIGNAL(doubleClicked(TQListViewItem*, const TQPoint&, int)), this, TQ_SLOT(slotDoubleClicked(TQListViewItem*, const TQPoint&, int)) );
277  connect(this, TQ_SIGNAL(contextMenu(TDEListView*, TQListViewItem*, const TQPoint&)),
278  this, TQ_SLOT(slotContextMenu(TDEListView*, TQListViewItem*, const TQPoint&)));
279 
280  connect(this, TQ_SIGNAL(mouseButtonPressed(int, TQListViewItem *, const TQPoint &, int)), this, TQ_SLOT(slotMouseButtonPressed(int, TQListViewItem *, const TQPoint &, int)));
281 }
282 
283 Article ArticleListView::currentArticle() const
284 {
285  ArticleItem* ci = dynamic_cast<ArticleItem*>(TDEListView::currentItem());
286  return (ci && !selectedItems().isEmpty()) ? ci->article() : Article();
287 }
288 
289 void ArticleListView::slotSetFilter(const Akregator::Filters::ArticleMatcher& textFilter, const Akregator::Filters::ArticleMatcher& statusFilter)
290 {
291  if ( (textFilter != d->textFilter) || (statusFilter != d->statusFilter) )
292  {
293  d->textFilter = textFilter;
294  d->statusFilter = statusFilter;
295 
296  applyFilters();
297  }
298 }
299 
300 void ArticleListView::slotShowNode(TreeNode* node)
301 {
302  if (node == d->node)
303  return;
304 
305  slotClear();
306 
307  if (!node)
308  return;
309 
310  d->node = node;
311  connectToNode(node);
312 
313  d->columnLayoutVisitor->visit(node);
314 
315  setUpdatesEnabled(false);
316 
317  TQValueList<Article> articles = d->node->articles();
318 
319  TQValueList<Article>::ConstIterator end = articles.end();
320  TQValueList<Article>::ConstIterator it = articles.begin();
321 
322  for (; it != end; ++it)
323  {
324  if (!(*it).isNull() && !(*it).isDeleted())
325  {
326  ArticleItem* ali = new ArticleItem(this, *it);
327  d->articleMap.insert(*it, ali);
328  }
329  }
330 
331  sort();
332  applyFilters();
333  d->noneSelected = true;
334  setUpdatesEnabled(true);
335  triggerUpdate();
336 }
337 
338 void ArticleListView::slotClear()
339 {
340  if (d->node)
341  disconnectFromNode(d->node);
342 
343  d->node = 0;
344  d->articleMap.clear();
345  clear();
346 }
347 
348 void ArticleListView::slotArticlesAdded(TreeNode* /*node*/, const TQValueList<Article>& list)
349 {
350  setUpdatesEnabled(false);
351 
352  bool statusActive = !(d->statusFilter.matchesAll());
353  bool textActive = !(d->textFilter.matchesAll());
354 
355  for (TQValueList<Article>::ConstIterator it = list.begin(); it != list.end(); ++it)
356  {
357  if (!d->articleMap.contains(*it))
358  {
359  if (!(*it).isNull() && !(*it).isDeleted())
360  {
361  ArticleItem* ali = new ArticleItem(this, *it);
362  ali->setVisible( (!statusActive || d->statusFilter.matches( ali->article()))
363  && (!textActive || d->textFilter.matches( ali->article())) );
364  d->articleMap.insert(*it, ali);
365  }
366  }
367  }
368  setUpdatesEnabled(true);
369  triggerUpdate();
370 }
371 
372 void ArticleListView::slotArticlesUpdated(TreeNode* /*node*/, const TQValueList<Article>& list)
373 {
374  setUpdatesEnabled(false);
375 
376  // if only one item is selected and this selected item
377  // is deleted, we will select the next item in the list
378  bool singleSelected = selectedArticles().count() == 1;
379 
380  bool statusActive = !(d->statusFilter.matchesAll());
381  bool textActive = !(d->textFilter.matchesAll());
382 
383  TQListViewItem* next = 0; // the item to select if a selected item is deleted
384 
385  for (TQValueList<Article>::ConstIterator it = list.begin(); it != list.end(); ++it)
386  {
387 
388  if (!(*it).isNull() && d->articleMap.contains(*it))
389  {
390  ArticleItem* ali = d->articleMap[*it];
391 
392  if (ali)
393  {
394  if ((*it).isDeleted()) // if article was set to deleted, delete item
395  {
396  if (singleSelected && ali->isSelected())
397  {
398  if (ali->itemBelow())
399  next = ali->itemBelow();
400  else if (ali->itemAbove())
401  next = ali->itemAbove();
402  }
403 
404  d->articleMap.remove(*it);
405  delete ali;
406  }
407  else
408  {
409  ali->updateItem(*it);
410  // if the updated article matches the filters after the update,
411  // make visible. If it matched them before but not after update,
412  // they should stay visible (to not confuse users)
413  if ((!statusActive || d->statusFilter.matches(ali->article()))
414  && (!textActive || d->textFilter.matches( ali->article())) )
415  ali->setVisible(true);
416  }
417  } // if ali
418  }
419  }
420 
421  // if the only selected item was deleted, select
422  // an item next to it
423  if (singleSelected && next != 0)
424  {
425  setSelected(next, true);
426  setCurrentItem(next);
427  }
428  else
429  {
430  d->noneSelected = true;
431  }
432 
433 
434  setUpdatesEnabled(true);
435  triggerUpdate();
436 }
437 
438 void ArticleListView::slotArticlesRemoved(TreeNode* /*node*/, const TQValueList<Article>& list)
439 {
440  // if only one item is selected and this selected item
441  // is deleted, we will select the next item in the list
442  bool singleSelected = selectedArticles().count() == 1;
443 
444  TQListViewItem* next = 0; // the item to select if a selected item is deleted
445 
446  setUpdatesEnabled(false);
447 
448  for (TQValueList<Article>::ConstIterator it = list.begin(); it != list.end(); ++it)
449  {
450  if (d->articleMap.contains(*it))
451  {
452  ArticleItem* ali = d->articleMap[*it];
453  d->articleMap.remove(*it);
454 
455  if (singleSelected && ali->isSelected())
456  {
457  if (ali->itemBelow())
458  next = ali->itemBelow();
459  else if (ali->itemAbove())
460  next = ali->itemAbove();
461  }
462 
463  delete ali;
464  }
465  }
466 
467  // if the only selected item was deleted, select
468  // an item next to it
469  if (singleSelected && next != 0)
470  {
471  setSelected(next, true);
472  setCurrentItem(next);
473  }
474  else
475  {
476  d->noneSelected = true;
477  }
478 
479  setUpdatesEnabled(true);
480  triggerUpdate();
481 }
482 
483 void ArticleListView::connectToNode(TreeNode* node)
484 {
485  connect(node, TQ_SIGNAL(signalDestroyed(TreeNode*)), this, TQ_SLOT(slotClear()) );
486  connect(node, TQ_SIGNAL(signalArticlesAdded(TreeNode*, const TQValueList<Article>&)), this, TQ_SLOT(slotArticlesAdded(TreeNode*, const TQValueList<Article>&)) );
487  connect(node, TQ_SIGNAL(signalArticlesUpdated(TreeNode*, const TQValueList<Article>&)), this, TQ_SLOT(slotArticlesUpdated(TreeNode*, const TQValueList<Article>&)) );
488  connect(node, TQ_SIGNAL(signalArticlesRemoved(TreeNode*, const TQValueList<Article>&)), this, TQ_SLOT(slotArticlesRemoved(TreeNode*, const TQValueList<Article>&)) );
489 }
490 
491 void ArticleListView::disconnectFromNode(TreeNode* node)
492 {
493  disconnect(node, TQ_SIGNAL(signalDestroyed(TreeNode*)), this, TQ_SLOT(slotClear()) );
494  disconnect(node, TQ_SIGNAL(signalArticlesAdded(TreeNode*, const TQValueList<Article>&)), this, TQ_SLOT(slotArticlesAdded(TreeNode*, const TQValueList<Article>&)) );
495  disconnect(node, TQ_SIGNAL(signalArticlesUpdated(TreeNode*, const TQValueList<Article>&)), this, TQ_SLOT(slotArticlesUpdated(TreeNode*, const TQValueList<Article>&)) );
496  disconnect(node, TQ_SIGNAL(signalArticlesRemoved(TreeNode*, const TQValueList<Article>&)), this, TQ_SLOT(slotArticlesRemoved(TreeNode*, const TQValueList<Article>&)) );
497 }
498 
499 void ArticleListView::applyFilters()
500 {
501  bool statusActive = !(d->statusFilter.matchesAll());
502  bool textActive = !(d->textFilter.matchesAll());
503 
504  ArticleItem* ali = 0;
505 
506  if (!statusActive && !textActive)
507  {
508  for (TQListViewItemIterator it(this); it.current(); ++it)
509  {
510  (static_cast<ArticleItem*> (it.current()))->setVisible(true);
511  }
512  }
513  else if (statusActive && !textActive)
514  {
515  for (TQListViewItemIterator it(this); it.current(); ++it)
516  {
517  ali = static_cast<ArticleItem*> (it.current());
518  ali->setVisible( d->statusFilter.matches( ali->article()) );
519  }
520  }
521  else if (!statusActive && textActive)
522  {
523  for (TQListViewItemIterator it(this); it.current(); ++it)
524  {
525  ali = static_cast<ArticleItem*> (it.current());
526  ali->setVisible( d->textFilter.matches( ali->article()) );
527  }
528  }
529  else // both true
530  {
531  for (TQListViewItemIterator it(this); it.current(); ++it)
532  {
533  ali = static_cast<ArticleItem*> (it.current());
534  ali->setVisible( d->statusFilter.matches( ali->article())
535  && d->textFilter.matches( ali->article()) );
536  }
537  }
538 
539 }
540 
541 int ArticleListView::visibleArticles()
542 {
543  int visible = 0;
544  ArticleItem* ali = 0;
545  for (TQListViewItemIterator it(this); it.current(); ++it) {
546  ali = static_cast<ArticleItem*> (it.current());
547  visible += ali->isVisible() ? 1 : 0;
548  }
549  return visible;
550 }
551 
552 // from amarok :)
553 void ArticleListView::paintInfoBox(const TQString &message)
554 {
555  TQPainter p( viewport() );
556  TQSimpleRichText t( message, TQApplication::font() );
557 
558  if ( t.width()+30 >= viewport()->width() || t.height()+30 >= viewport()->height() )
559  //too big, giving up
560  return;
561 
562  const uint w = t.width();
563  const uint h = t.height();
564  const uint x = (viewport()->width() - w - 30) / 2 ;
565  const uint y = (viewport()->height() - h - 30) / 2 ;
566 
567  p.setBrush( colorGroup().background() );
568  p.drawRoundRect( x, y, w+30, h+30, (8*200)/w, (8*200)/h );
569  t.draw( &p, x+15, y+15, TQRect(), colorGroup() );
570 }
571 
572 void ArticleListView::viewportPaintEvent(TQPaintEvent *e)
573 {
574 
575  TDEListView::viewportPaintEvent(e);
576 
577  if(!e)
578  return;
579 
580  TQString message = TQString();
581 
582  //kdDebug() << "visible articles: " << visibleArticles() << endl;
583 
584  if(childCount() != 0) // article list is not empty
585  {
586  if (visibleArticles() == 0)
587  {
588  message = i18n("<div align=center>"
589  "<h3>No matches</h3>"
590  "Filter does not match any articles, "
591  "please change your criteria and try again."
592  "</div>");
593  }
594 
595  }
596  else // article list is empty
597  {
598  if (!d->node) // no node selected
599  {
600  message = i18n("<div align=center>"
601  "<h3>No feed selected</h3>"
602  "This area is article list. "
603  "Select a feed from the feed list "
604  "and you will see its articles here."
605  "</div>");
606  }
607  else // empty node
608  {
609  // TODO: we could display message like "empty node, choose "fetch" to update it"
610  }
611  }
612 
613  if (!message.isNull())
614  paintInfoBox(message);
615 }
616 
617 TQDragObject *ArticleListView::dragObject()
618 {
619  TQDragObject* d = 0;
620  TQValueList<Article> articles = selectedArticles();
621  if (!articles.isEmpty())
622  {
623  d = new ArticleDrag(articles, this);
624  }
625  return d;
626 }
627 
628 void ArticleListView::slotPreviousArticle()
629 {
630  ArticleItem* ali = 0;
631  if (!currentItem() || selectedItems().isEmpty())
632  ali = dynamic_cast<ArticleItem*>(lastChild());
633  else
634  ali = dynamic_cast<ArticleItem*>(currentItem()->itemAbove());
635 
636  if (ali)
637  {
638  Article a = ali->article();
639  setCurrentItem(d->articleMap[a]);
640  clearSelection();
641  setSelected(d->articleMap[a], true);
642  d->ensureCurrentItemVisible();
643  }
644 }
645 
646 void ArticleListView::slotNextArticle()
647 {
648  ArticleItem* ali = 0;
649  if (!currentItem() || selectedItems().isEmpty())
650  ali = dynamic_cast<ArticleItem*>(firstChild());
651  else
652  ali = dynamic_cast<ArticleItem*>(currentItem()->itemBelow());
653 
654  if (ali)
655  {
656  Article a = ali->article();
657  setCurrentItem(d->articleMap[a]);
658  clearSelection();
659  setSelected(d->articleMap[a], true);
660  d->ensureCurrentItemVisible();
661  }
662 }
663 
664 void ArticleListView::slotNextUnreadArticle()
665 {
666  ArticleItem* start = 0L;
667  if (!currentItem() || selectedItems().isEmpty())
668  start = dynamic_cast<ArticleItem*>(firstChild());
669  else
670  start = dynamic_cast<ArticleItem*>(currentItem()->itemBelow() ? currentItem()->itemBelow() : firstChild());
671 
672  ArticleItem* i = start;
673  ArticleItem* unread = 0L;
674 
675  do
676  {
677  if (i == 0L)
678  i = static_cast<ArticleItem*>(firstChild());
679  else
680  {
681  if (i->article().status() != Article::Read)
682  unread = i;
683  else
684  i = static_cast<ArticleItem*>(i && i->itemBelow() ? i->itemBelow() : firstChild());
685  }
686  }
687  while (!unread && i != start);
688 
689  if (unread)
690  {
691  Article a = unread->article();
692  setCurrentItem(d->articleMap[a]);
693  clearSelection();
694  setSelected(d->articleMap[a], true);
695  d->ensureCurrentItemVisible();
696  }
697 }
698 
699 void ArticleListView::slotPreviousUnreadArticle()
700 {
701  ArticleItem* start = 0L;
702  if (!currentItem() || selectedItems().isEmpty())
703  start = dynamic_cast<ArticleItem*>(lastChild());
704  else
705  start = dynamic_cast<ArticleItem*>(currentItem()->itemAbove() ? currentItem()->itemAbove() : firstChild());
706 
707  ArticleItem* i = start;
708  ArticleItem* unread = 0L;
709 
710  do
711  {
712  if (i == 0L)
713  i = static_cast<ArticleItem*>(lastChild());
714  else
715  {
716  if (i->article().status() != Article::Read)
717  unread = i;
718  else
719  i = static_cast<ArticleItem*>(i->itemAbove() ? i->itemAbove() : lastChild());
720  }
721  }
722  while ( !(unread != 0L || i == start) );
723 
724  if (unread)
725  {
726  Article a = unread->article();
727  setCurrentItem(d->articleMap[a]);
728  clearSelection();
729  setSelected(d->articleMap[a], true);
730  d->ensureCurrentItemVisible();
731  }
732 }
733 
734 void ArticleListView::keyPressEvent(TQKeyEvent* e)
735 {
736  e->ignore();
737 }
738 
739 void ArticleListView::slotSelectionChanged()
740 {
741  // if there is only one article in the list, currentItem is set initially to
742  // that article item, although the user hasn't selected it. If the user selects
743  // the article, selection changes, but currentItem does not.
744  // executed. So we have to handle this case by observing selection changes.
745 
746  if (d->noneSelected)
747  {
748  d->noneSelected = false;
749  slotCurrentChanged(currentItem());
750  }
751 }
752 
753 void ArticleListView::slotCurrentChanged(TQListViewItem* item)
754 {
755  ArticleItem* ai = dynamic_cast<ArticleItem*> (item);
756  if (ai)
757  emit signalArticleChosen( ai->article() );
758  else
759  {
760  d->noneSelected = true;
761  emit signalArticleChosen( Article() );
762  }
763 }
764 
765 
766 void ArticleListView::slotDoubleClicked(TQListViewItem* item, const TQPoint& p, int i)
767 {
768  ArticleItem* ali = dynamic_cast<ArticleItem*>(item);
769  if (ali)
770  emit signalDoubleClicked(ali->article(), p, i);
771 }
772 
773 void ArticleListView::slotContextMenu(TDEListView* /*list*/, TQListViewItem* /*item*/, const TQPoint& p)
774 {
775  TQWidget* w = ActionManager::getInstance()->container("article_popup");
776  TQPopupMenu* popup = static_cast<TQPopupMenu *>(w);
777  if (popup)
778  popup->exec(p);
779 }
780 
781 void ArticleListView::slotMouseButtonPressed(int button, TQListViewItem* item, const TQPoint& p, int column)
782 {
783  ArticleItem* ali = dynamic_cast<ArticleItem*>(item);
784  if (ali)
785  emit signalMouseButtonPressed(button, ali->article(), p, column);
786 }
787 
788 ArticleListView::~ArticleListView()
789 {
790  Settings::setTitleWidth(columnWidth(0));
791  Settings::setFeedWidth(columnWidth(1) > 0 ? columnWidth(1) : d->feedWidth);
792  Settings::setSortColumn(sortColumn());
793  Settings::setSortAscending(sortOrder() == Ascending);
794  Settings::writeConfig();
795  delete d->columnLayoutVisitor;
796  delete d;
797  d = 0;
798 }
799 
800 TQValueList<Article> ArticleListView::selectedArticles() const
801 {
802  TQValueList<Article> ret;
803  TQPtrList<TQListViewItem> items = selectedItems(false);
804  for (TQListViewItem* i = items.first(); i; i = items.next() )
805  ret.append((static_cast<ArticleItem*>(i))->article());
806  return ret;
807 }
808 
809 } // namespace Akregator
810 
811 #include "articlelistview.moc"
a powerful matcher supporting multiple criterions, which can be combined via logical OR or AND