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 <tdestandarddirs.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
57namespace Akregator {
58
59class 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
88class 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
130class 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
159ArticleListView::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
166ArticleListView::ArticleItem::~ArticleItem()
167{
168}
169
170Article& ArticleListView::ArticleItem::article()
171{
172 return m_article;
173}
174
175// paint ze peons
176void 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
194void 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
203int 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
216ArticleListView::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
283Article ArticleListView::currentArticle() const
284{
285 ArticleItem* ci = dynamic_cast<ArticleItem*>(TDEListView::currentItem());
286 return (ci && !selectedItems().isEmpty()) ? ci->article() : Article();
287}
288
289void 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
300void 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
338void ArticleListView::slotClear()
339{
340 if (d->node)
341 disconnectFromNode(d->node);
342
343 d->node = 0;
344 d->articleMap.clear();
345 clear();
346}
347
348void 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
372void 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
438void 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
483void 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
491void 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
499void 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
541int 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 :)
553void 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
572void 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
617TQDragObject *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
628void 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
646void 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
664void 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
699void 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
734void ArticleListView::keyPressEvent(TQKeyEvent* e)
735{
736 e->ignore();
737}
738
739void 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
753void 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
766void 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
773void 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
781void 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
788ArticleListView::~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
800TQValueList<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