25 #include "akregatorconfig.h"
26 #include "actionmanager.h"
27 #include "articlelistview.h"
29 #include "articlefilter.h"
30 #include "dragobjects.h"
33 #include "treenodevisitor.h"
35 #include <kstandarddirs.h>
37 #include <tdeglobal.h>
38 #include <kiconloader.h>
39 #include <tdelocale.h>
40 #include <kcharsets.h>
43 #include <tqdatetime.h>
45 #include <tqpopupmenu.h>
46 #include <tqptrlist.h>
47 #include <tqvaluelist.h>
48 #include <tqwhatsthis.h>
50 #include <tqdragobject.h>
51 #include <tqsimplerichtext.h>
52 #include <tqpainter.h>
53 #include <tqapplication.h>
59 class ArticleListView::ArticleListViewPrivate
63 ArticleListViewPrivate(ArticleListView* parent) : m_parent(parent) { }
65 void ensureCurrentItemVisible()
67 if (m_parent->currentItem())
69 m_parent->center( m_parent->contentsX(), m_parent->itemPos(m_parent->currentItem()), 0, 9.0 );
73 ArticleListView* m_parent;
76 TQMap<Article, ArticleItem*> articleMap;
80 enum ColumnMode { groupMode, feedMode };
81 ColumnMode columnMode;
85 ColumnLayoutVisitor* columnLayoutVisitor;
88 class ArticleListView::ColumnLayoutVisitor :
public TreeNodeVisitor
91 ColumnLayoutVisitor(ArticleListView* view) : m_view(view) {}
92 virtual ~ColumnLayoutVisitor() {}
94 virtual bool visitTagNode(TagNode* )
96 if (m_view->d->columnMode == ArticleListViewPrivate::feedMode)
98 m_view->setColumnWidth(1, m_view->d->feedWidth);
99 m_view->d->columnMode = ArticleListViewPrivate::groupMode;
104 virtual bool visitFolder(Folder* )
106 if (m_view->d->columnMode == ArticleListViewPrivate::feedMode)
108 m_view->setColumnWidth(1, m_view->d->feedWidth);
109 m_view->d->columnMode = ArticleListViewPrivate::groupMode;
114 virtual bool visitFeed(Feed* )
116 if (m_view->d->columnMode == ArticleListViewPrivate::groupMode)
118 m_view->d->feedWidth = m_view->columnWidth(1);
119 m_view->hideColumn(1);
120 m_view->d->columnMode = ArticleListViewPrivate::feedMode;
126 ArticleListView* m_view;
130 class ArticleListView::ArticleItem :
public TDEListViewItem
132 friend class ArticleListView;
135 ArticleItem( TQListView *parent,
const Article& a);
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;
143 void updateItem(
const Article& article);
145 virtual ArticleItem* itemAbove() {
return static_cast<ArticleItem*
>(TDEListViewItem::itemAbove()); }
147 virtual ArticleItem* nextSibling() {
return static_cast<ArticleItem*
>(TDEListViewItem::nextSibling()); }
152 static TQPixmap keepFlag() {
153 static TQPixmap s_keepFlag = TQPixmap(locate(
"data",
"akregator/pics/akregator_flag.png"));
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())
163 setPixmap(0, keepFlag());
166 ArticleListView::ArticleItem::~ArticleItem()
170 Article& ArticleListView::ArticleItem::article()
176 void ArticleListView::ArticleItem::paintCell ( TQPainter * p,
const TQColorGroup & cg,
int column,
int width,
int align )
178 if (article().status() == Article::Read)
179 TDEListViewItem::paintCell( p, cg, column, width, align );
182 TQColorGroup cg2(cg);
184 if (article().status() == Article::Unread)
185 cg2.setColor(TQColorGroup::Text, Settings::unreadTextColor());
187 cg2.setColor(TQColorGroup::Text, Settings::readTextColor());
189 TDEListViewItem::paintCell( p, cg2, column, width, align );
194 void ArticleListView::ArticleItem::updateItem(
const 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));
203 int ArticleListView::ArticleItem::compare(TQListViewItem *i,
int col,
bool ascending)
const {
206 ArticleItem* item =
static_cast<ArticleItem*
>(i);
207 if (m_pubDate == item->m_pubDate)
209 return (m_pubDate > item->m_pubDate) ? 1 : -1;
211 return TDEListViewItem::compare(i, col, ascending);
216 ArticleListView::ArticleListView(TQWidget *parent,
const char *name)
217 : TDEListView(parent, name)
219 d =
new ArticleListViewPrivate(
this);
220 d->noneSelected =
true;
222 d->columnMode = ArticleListViewPrivate::feedMode;
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);
238 setAcceptDrops(
false);
241 setShowSortIndicator(
true);
242 setDragAutoScroll(
true);
243 setDropHighlighter(
false);
245 int c = Settings::sortColumn();
246 setSorting((c >= 0 && c <= 2) ? c : 2, Settings::sortAscending());
249 w = Settings::titleWidth();
251 setColumnWidth(0, w);
254 w = Settings::feedWidth();
256 setColumnWidth(1, w);
259 w = Settings::dateWidth();
261 setColumnWidth(2, w);
264 d->feedWidth = columnWidth(1);
267 header()->setStretchEnabled(
true, 0);
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."));
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&)));
280 connect(
this, TQ_SIGNAL(mouseButtonPressed(
int, TQListViewItem *,
const TQPoint &,
int)),
this, TQ_SLOT(slotMouseButtonPressed(
int, TQListViewItem *,
const TQPoint &,
int)));
283 Article ArticleListView::currentArticle()
const
285 ArticleItem* ci =
dynamic_cast<ArticleItem*
>(TDEListView::currentItem());
286 return (ci && !selectedItems().isEmpty()) ? ci->article() : Article();
291 if ( (textFilter != d->textFilter) || (statusFilter != d->statusFilter) )
293 d->textFilter = textFilter;
294 d->statusFilter = statusFilter;
300 void ArticleListView::slotShowNode(TreeNode* node)
313 d->columnLayoutVisitor->visit(node);
315 setUpdatesEnabled(
false);
317 TQValueList<Article> articles = d->node->articles();
319 TQValueList<Article>::ConstIterator end = articles.end();
320 TQValueList<Article>::ConstIterator it = articles.begin();
322 for (; it != end; ++it)
324 if (!(*it).isNull() && !(*it).isDeleted())
326 ArticleItem* ali =
new ArticleItem(
this, *it);
327 d->articleMap.insert(*it, ali);
333 d->noneSelected =
true;
334 setUpdatesEnabled(
true);
338 void ArticleListView::slotClear()
341 disconnectFromNode(d->node);
344 d->articleMap.clear();
348 void ArticleListView::slotArticlesAdded(TreeNode* ,
const TQValueList<Article>& list)
350 setUpdatesEnabled(
false);
352 bool statusActive = !(d->statusFilter.matchesAll());
353 bool textActive = !(d->textFilter.matchesAll());
355 for (TQValueList<Article>::ConstIterator it = list.begin(); it != list.end(); ++it)
357 if (!d->articleMap.contains(*it))
359 if (!(*it).isNull() && !(*it).isDeleted())
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);
368 setUpdatesEnabled(
true);
372 void ArticleListView::slotArticlesUpdated(TreeNode* ,
const TQValueList<Article>& list)
374 setUpdatesEnabled(
false);
378 bool singleSelected = selectedArticles().count() == 1;
380 bool statusActive = !(d->statusFilter.matchesAll());
381 bool textActive = !(d->textFilter.matchesAll());
383 TQListViewItem* next = 0;
385 for (TQValueList<Article>::ConstIterator it = list.begin(); it != list.end(); ++it)
388 if (!(*it).isNull() && d->articleMap.contains(*it))
390 ArticleItem* ali = d->articleMap[*it];
394 if ((*it).isDeleted())
396 if (singleSelected && ali->isSelected())
398 if (ali->itemBelow())
399 next = ali->itemBelow();
400 else if (ali->itemAbove())
401 next = ali->itemAbove();
404 d->articleMap.remove(*it);
409 ali->updateItem(*it);
413 if ((!statusActive || d->statusFilter.matches(ali->article()))
414 && (!textActive || d->textFilter.matches( ali->article())) )
415 ali->setVisible(
true);
423 if (singleSelected && next != 0)
425 setSelected(next,
true);
426 setCurrentItem(next);
430 d->noneSelected =
true;
434 setUpdatesEnabled(
true);
438 void ArticleListView::slotArticlesRemoved(TreeNode* ,
const TQValueList<Article>& list)
442 bool singleSelected = selectedArticles().count() == 1;
444 TQListViewItem* next = 0;
446 setUpdatesEnabled(
false);
448 for (TQValueList<Article>::ConstIterator it = list.begin(); it != list.end(); ++it)
450 if (d->articleMap.contains(*it))
452 ArticleItem* ali = d->articleMap[*it];
453 d->articleMap.remove(*it);
455 if (singleSelected && ali->isSelected())
457 if (ali->itemBelow())
458 next = ali->itemBelow();
459 else if (ali->itemAbove())
460 next = ali->itemAbove();
469 if (singleSelected && next != 0)
471 setSelected(next,
true);
472 setCurrentItem(next);
476 d->noneSelected =
true;
479 setUpdatesEnabled(
true);
483 void ArticleListView::connectToNode(TreeNode* node)
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>&)) );
491 void ArticleListView::disconnectFromNode(TreeNode* node)
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>&)) );
499 void ArticleListView::applyFilters()
501 bool statusActive = !(d->statusFilter.matchesAll());
502 bool textActive = !(d->textFilter.matchesAll());
504 ArticleItem* ali = 0;
506 if (!statusActive && !textActive)
508 for (TQListViewItemIterator it(
this); it.current(); ++it)
510 (
static_cast<ArticleItem*
> (it.current()))->setVisible(
true);
513 else if (statusActive && !textActive)
515 for (TQListViewItemIterator it(
this); it.current(); ++it)
517 ali =
static_cast<ArticleItem*
> (it.current());
518 ali->setVisible( d->statusFilter.matches( ali->article()) );
521 else if (!statusActive && textActive)
523 for (TQListViewItemIterator it(
this); it.current(); ++it)
525 ali =
static_cast<ArticleItem*
> (it.current());
526 ali->setVisible( d->textFilter.matches( ali->article()) );
531 for (TQListViewItemIterator it(
this); it.current(); ++it)
533 ali =
static_cast<ArticleItem*
> (it.current());
534 ali->setVisible( d->statusFilter.matches( ali->article())
535 && d->textFilter.matches( ali->article()) );
541 int ArticleListView::visibleArticles()
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;
553 void ArticleListView::paintInfoBox(
const TQString &message)
555 TQPainter p( viewport() );
556 TQSimpleRichText t( message, TQApplication::font() );
558 if ( t.width()+30 >= viewport()->width() || t.height()+30 >= viewport()->height() )
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 ;
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() );
572 void ArticleListView::viewportPaintEvent(TQPaintEvent *e)
575 TDEListView::viewportPaintEvent(e);
580 TQString message = TQString();
584 if(childCount() != 0)
586 if (visibleArticles() == 0)
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."
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."
613 if (!message.isNull())
614 paintInfoBox(message);
617 TQDragObject *ArticleListView::dragObject()
620 TQValueList<Article> articles = selectedArticles();
621 if (!articles.isEmpty())
623 d =
new ArticleDrag(articles,
this);
628 void ArticleListView::slotPreviousArticle()
630 ArticleItem* ali = 0;
631 if (!currentItem() || selectedItems().isEmpty())
632 ali =
dynamic_cast<ArticleItem*
>(lastChild());
634 ali =
dynamic_cast<ArticleItem*
>(currentItem()->itemAbove());
638 Article a = ali->article();
639 setCurrentItem(d->articleMap[a]);
641 setSelected(d->articleMap[a],
true);
642 d->ensureCurrentItemVisible();
646 void ArticleListView::slotNextArticle()
648 ArticleItem* ali = 0;
649 if (!currentItem() || selectedItems().isEmpty())
650 ali =
dynamic_cast<ArticleItem*
>(firstChild());
652 ali =
dynamic_cast<ArticleItem*
>(currentItem()->itemBelow());
656 Article a = ali->article();
657 setCurrentItem(d->articleMap[a]);
659 setSelected(d->articleMap[a],
true);
660 d->ensureCurrentItemVisible();
664 void ArticleListView::slotNextUnreadArticle()
666 ArticleItem* start = 0L;
667 if (!currentItem() || selectedItems().isEmpty())
668 start =
dynamic_cast<ArticleItem*
>(firstChild());
670 start =
dynamic_cast<ArticleItem*
>(currentItem()->itemBelow() ? currentItem()->itemBelow() : firstChild());
672 ArticleItem* i = start;
673 ArticleItem* unread = 0L;
678 i =
static_cast<ArticleItem*
>(firstChild());
681 if (i->article().status() != Article::Read)
684 i =
static_cast<ArticleItem*
>(i && i->itemBelow() ? i->itemBelow() : firstChild());
687 while (!unread && i != start);
691 Article a = unread->article();
692 setCurrentItem(d->articleMap[a]);
694 setSelected(d->articleMap[a],
true);
695 d->ensureCurrentItemVisible();
699 void ArticleListView::slotPreviousUnreadArticle()
701 ArticleItem* start = 0L;
702 if (!currentItem() || selectedItems().isEmpty())
703 start =
dynamic_cast<ArticleItem*
>(lastChild());
705 start =
dynamic_cast<ArticleItem*
>(currentItem()->itemAbove() ? currentItem()->itemAbove() : firstChild());
707 ArticleItem* i = start;
708 ArticleItem* unread = 0L;
713 i =
static_cast<ArticleItem*
>(lastChild());
716 if (i->article().status() != Article::Read)
719 i =
static_cast<ArticleItem*
>(i->itemAbove() ? i->itemAbove() : lastChild());
722 while ( !(unread != 0L || i == start) );
726 Article a = unread->article();
727 setCurrentItem(d->articleMap[a]);
729 setSelected(d->articleMap[a],
true);
730 d->ensureCurrentItemVisible();
734 void ArticleListView::keyPressEvent(TQKeyEvent* e)
739 void ArticleListView::slotSelectionChanged()
748 d->noneSelected =
false;
749 slotCurrentChanged(currentItem());
753 void ArticleListView::slotCurrentChanged(TQListViewItem* item)
755 ArticleItem* ai =
dynamic_cast<ArticleItem*
> (item);
757 emit signalArticleChosen( ai->article() );
760 d->noneSelected =
true;
761 emit signalArticleChosen( Article() );
766 void ArticleListView::slotDoubleClicked(TQListViewItem* item,
const TQPoint& p,
int i)
768 ArticleItem* ali =
dynamic_cast<ArticleItem*
>(item);
770 emit signalDoubleClicked(ali->article(), p, i);
773 void ArticleListView::slotContextMenu(TDEListView* , TQListViewItem* ,
const TQPoint& p)
775 TQWidget* w = ActionManager::getInstance()->container(
"article_popup");
776 TQPopupMenu* popup =
static_cast<TQPopupMenu *
>(w);
781 void ArticleListView::slotMouseButtonPressed(
int button, TQListViewItem* item,
const TQPoint& p,
int column)
783 ArticleItem* ali =
dynamic_cast<ArticleItem*
>(item);
785 emit signalMouseButtonPressed(button, ali->article(), p, column);
788 ArticleListView::~ArticleListView()
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;
800 TQValueList<Article> ArticleListView::selectedArticles()
const
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());
811 #include "articlelistview.moc"
a powerful matcher supporting multiple criterions, which can be combined via logical OR or AND