akregator/src

articlelistview.cpp

00001 /*
00002     This file is part of Akregator.
00003 
00004     Copyright (C) 2004 Stanislav Karchebny <Stanislav.Karchebny@kdemail.net>
00005                   2005 Frank Osterfeld <frank.osterfeld at kdemail.net>
00006     This program is free software; you can redistribute it and/or modify
00007     it under the terms of the GNU General Public License as published by
00008     the Free Software Foundation; either version 2 of the License, or
00009     (at your option) any later version.
00010 
00011     This program is distributed in the hope that it will be useful,
00012     but WITHOUT ANY WARRANTY; without even the implied warranty of
00013     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00014     GNU General Public License for more details.
00015 
00016     You should have received a copy of the GNU General Public License
00017     along with this program; if not, write to the Free Software
00018     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00019 
00020     As a special exception, permission is given to link this program
00021     with any edition of Qt, and distribute the resulting executable,
00022     without including the source code for Qt in the source distribution.
00023 */
00024 
00025 #include "akregatorconfig.h"
00026 #include "actionmanager.h"
00027 #include "articlelistview.h"
00028 #include "article.h"
00029 #include "articlefilter.h"
00030 #include "dragobjects.h"
00031 #include "feed.h"
00032 #include "treenode.h"
00033 #include "treenodevisitor.h"
00034 
00035 #include <kstandarddirs.h>
00036 #include <kdebug.h>
00037 #include <kglobal.h>
00038 #include <kiconloader.h>
00039 #include <klocale.h>
00040 #include <kcharsets.h>
00041 #include <kurl.h>
00042 
00043 #include <qdatetime.h>
00044 #include <qpixmap.h>
00045 #include <qpopupmenu.h>
00046 #include <qptrlist.h>
00047 #include <qvaluelist.h>
00048 #include <qwhatsthis.h>
00049 #include <qheader.h>
00050 #include <qdragobject.h>
00051 #include <qsimplerichtext.h>
00052 #include <qpainter.h>
00053 #include <qapplication.h>
00054 
00055 #include <ctime>
00056 
00057 namespace Akregator {
00058 
00059 class ArticleListView::ArticleListViewPrivate
00060 {
00061     public:
00062 
00063     ArticleListViewPrivate(ArticleListView* parent) : m_parent(parent) { }
00064 
00065     void ensureCurrentItemVisible()
00066     {
00067         if (m_parent->currentItem())
00068         {
00069             m_parent->center( m_parent->contentsX(), m_parent->itemPos(m_parent->currentItem()), 0, 9.0 );
00070         }
00071     }
00072 
00073     ArticleListView* m_parent;
00074 
00076     QMap<Article, ArticleItem*> articleMap;
00077     TreeNode* node;
00078     Akregator::Filters::ArticleMatcher textFilter;
00079     Akregator::Filters::ArticleMatcher statusFilter;
00080     enum ColumnMode { groupMode, feedMode };
00081     ColumnMode columnMode;
00082     int feedWidth;
00083     bool noneSelected;
00084     
00085     ColumnLayoutVisitor* columnLayoutVisitor;
00086 };
00087   
00088 class ArticleListView::ColumnLayoutVisitor : public TreeNodeVisitor
00089 {
00090     public:
00091         ColumnLayoutVisitor(ArticleListView* view) : m_view(view) {}
00092 
00093         virtual bool visitTagNode(TagNode* /*node*/)
00094         {
00095             if (m_view->d->columnMode == ArticleListViewPrivate::feedMode)
00096             {
00097                 m_view->setColumnWidth(1, m_view->d->feedWidth);
00098                 m_view->d->columnMode = ArticleListViewPrivate::groupMode;
00099             }
00100             return true;
00101         }
00102         
00103         virtual bool visitFolder(Folder* /*node*/)
00104         {
00105             if (m_view->d->columnMode == ArticleListViewPrivate::feedMode)
00106             {
00107                 m_view->setColumnWidth(1, m_view->d->feedWidth);
00108                 m_view->d->columnMode = ArticleListViewPrivate::groupMode;
00109             }
00110             return true;
00111         }
00112         
00113         virtual bool visitFeed(Feed* /*node*/)
00114         {
00115             if (m_view->d->columnMode == ArticleListViewPrivate::groupMode)
00116             {    
00117                 m_view->d->feedWidth = m_view->columnWidth(1);
00118                 m_view->hideColumn(1);
00119                 m_view->d->columnMode = ArticleListViewPrivate::feedMode;
00120             }
00121             return true;
00122         }
00123     private:
00124 
00125         ArticleListView* m_view;
00126     
00127 };
00128 
00129 class ArticleListView::ArticleItem : public KListViewItem
00130     {
00131         friend class ArticleListView;
00132 
00133         public:
00134             ArticleItem( QListView *parent, const Article& a);
00135             ~ArticleItem();
00136 
00137             Article& article();
00138 
00139             void paintCell ( QPainter * p, const QColorGroup & cg, int column, int width, int align );
00140             virtual int compare(QListViewItem *i, int col, bool ascending) const;
00141 
00142             void updateItem(const Article& article);
00143 
00144             virtual ArticleItem* itemAbove() { return static_cast<ArticleItem*>(KListViewItem::itemAbove()); }
00145             
00146             virtual ArticleItem* nextSibling() { return static_cast<ArticleItem*>(KListViewItem::nextSibling()); }
00147 
00148         private:
00149             Article m_article;
00150             time_t m_pubDate;
00151             static QPixmap m_keepFlag;
00152 };
00153 
00154 // FIXME: Remove resolveEntities for KDE 4.0, it's now done in the parser
00155 ArticleListView::ArticleItem::ArticleItem( QListView *parent, const Article& a)
00156     : KListViewItem( parent, KCharsets::resolveEntities(a.title()), a.feed()->title(), KGlobal::locale()->formatDateTime(a.pubDate(), true, false) ), m_article(a), m_pubDate(a.pubDate().toTime_t())
00157 {
00158     if (a.keep())
00159         setPixmap(0, m_keepFlag);
00160 }
00161  
00162 ArticleListView::ArticleItem::~ArticleItem()
00163 {
00164 }
00165 
00166 Article& ArticleListView::ArticleItem::article()
00167 {
00168     return m_article;
00169 }
00170 
00171 QPixmap ArticleListView::ArticleItem::m_keepFlag = QPixmap(locate("data", "akregator/pics/akregator_flag.png"));
00172 
00173 // paint ze peons
00174 void ArticleListView::ArticleItem::paintCell ( QPainter * p, const QColorGroup & cg, int column, int width, int align )
00175 {
00176     if (article().status() == Article::Read)
00177         KListViewItem::paintCell( p, cg, column, width, align );
00178     else
00179     {
00180         // if article status is unread or new, we change the color: FIXME: make colors configurable
00181         QColorGroup cg2(cg);
00182     
00183         if (article().status() == Article::Unread)
00184             cg2.setColor(QColorGroup::Text, Qt::blue);
00185         else // New
00186             cg2.setColor(QColorGroup::Text, Qt::red);
00187     
00188         KListViewItem::paintCell( p, cg2, column, width, align );
00189     }
00190 
00191 }
00192 
00193 void ArticleListView::ArticleItem::updateItem(const Article& article)
00194 {
00195     m_article = article;
00196     setPixmap(0, m_article.keep() ? m_keepFlag : QPixmap());
00197     setText(0, KCharsets::resolveEntities(m_article.title()));
00198     setText(1, m_article.feed()->title());
00199     setText(2, KGlobal::locale()->formatDateTime(m_article.pubDate(), true, false));
00200 }
00201 
00202 int ArticleListView::ArticleItem::compare(QListViewItem *i, int col, bool ascending) const {
00203     if (col == 2)
00204     {
00205         ArticleItem* item = static_cast<ArticleItem*>(i);
00206         if (m_pubDate == item->m_pubDate)
00207             return 0;
00208         return (m_pubDate > item->m_pubDate) ? 1 : -1;
00209     }
00210     return KListViewItem::compare(i, col, ascending);
00211 }
00212 
00213 /* ==================================================================================== */
00214 
00215 ArticleListView::ArticleListView(QWidget *parent, const char *name)
00216     : KListView(parent, name)
00217 {
00218     d = new ArticleListViewPrivate(this);
00219     d->noneSelected = true;
00220     d->node = 0;
00221     d->columnMode = ArticleListViewPrivate::feedMode;
00222 
00223     d->columnLayoutVisitor = new ColumnLayoutVisitor(this);
00224     setMinimumSize(250, 150);
00225     addColumn(i18n("Article"));
00226     addColumn(i18n("Feed"));
00227     addColumn(i18n("Date"));
00228     setSelectionMode(QListView::Extended);
00229     setColumnWidthMode(2, QListView::Maximum);
00230     setColumnWidthMode(1, QListView::Manual);
00231     setColumnWidthMode(0, QListView::Manual);
00232     setRootIsDecorated(false);
00233     setItemsRenameable(false);
00234     setItemsMovable(false);
00235     setAllColumnsShowFocus(true);
00236     setDragEnabled(true); // FIXME before we implement dragging between archived feeds??
00237     setAcceptDrops(false); // FIXME before we implement dragging between archived feeds??
00238     setFullWidth(false);
00239     
00240     setShowSortIndicator(true);
00241     setDragAutoScroll(true);
00242     setDropHighlighter(false);
00243 
00244     int c = Settings::sortColumn();
00245     setSorting((c >= 0 && c <= 2) ? c : 2, Settings::sortAscending());
00246 
00247     int w;
00248     w = Settings::titleWidth();
00249     if (w > 0) {
00250         setColumnWidth(0, w);
00251     }
00252     
00253     w = Settings::feedWidth();
00254     if (w > 0) {
00255         setColumnWidth(1, w);
00256     }
00257     
00258     w = Settings::dateWidth();
00259     if (w > 0) {
00260         setColumnWidth(2, w);
00261     }
00262     
00263     d->feedWidth = columnWidth(1);
00264     hideColumn(1);
00265 
00266     header()->setStretchEnabled(true, 0);
00267 
00268     QWhatsThis::add(this, i18n("<h2>Article list</h2>"
00269         "Here you can browse articles from the currently selected feed. "
00270         "You can also manage articles, as marking them as persistent (\"Keep Article\") or delete them, using the right mouse button menu."
00271         "To view the web page of the article, you can open the article internally in a tab or in an external browser window."));
00272 
00273     connect(this, SIGNAL(currentChanged(QListViewItem*)), this, SLOT(slotCurrentChanged(QListViewItem* )));
00274     connect(this, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged()));
00275     connect(this, SIGNAL(doubleClicked(QListViewItem*, const QPoint&, int)),  this, SLOT(slotDoubleClicked(QListViewItem*, const QPoint&, int)) );
00276     connect(this, SIGNAL(contextMenu(KListView*, QListViewItem*, const QPoint&)),
00277             this, SLOT(slotContextMenu(KListView*, QListViewItem*, const QPoint&)));
00278 
00279     connect(this, SIGNAL(mouseButtonPressed(int, QListViewItem *, const QPoint &, int)), this, SLOT(slotMouseButtonPressed(int, QListViewItem *, const QPoint &, int)));
00280 }
00281 
00282 Article ArticleListView::currentArticle() const
00283 {
00284     ArticleItem* ci = dynamic_cast<ArticleItem*>(KListView::currentItem());
00285     return (ci && !selectedItems().isEmpty()) ? ci->article() : Article();
00286 }
00287 
00288 void ArticleListView::slotSetFilter(const Akregator::Filters::ArticleMatcher& textFilter, const Akregator::Filters::ArticleMatcher& statusFilter)
00289 {
00290     if ( (textFilter != d->textFilter) || (statusFilter != d->statusFilter) )
00291     {
00292         d->textFilter = textFilter;
00293         d->statusFilter = statusFilter;
00294                
00295         applyFilters();
00296     }
00297 }
00298 
00299 void ArticleListView::slotShowNode(TreeNode* node)
00300 {
00301     if (node == d->node)
00302         return;
00303 
00304     slotClear();
00305 
00306     if (!node)
00307         return;
00308 
00309     d->node = node;
00310     connectToNode(node);
00311 
00312     d->columnLayoutVisitor->visit(node);
00313 
00314     setUpdatesEnabled(false);
00315 
00316     QValueList<Article> articles = d->node->articles();
00317 
00318     QValueList<Article>::ConstIterator end = articles.end();
00319     QValueList<Article>::ConstIterator it = articles.begin();
00320     
00321     for (; it != end; ++it)
00322     {
00323         if (!(*it).isNull() && !(*it).isDeleted())
00324         {
00325             ArticleItem* ali = new ArticleItem(this, *it);
00326             d->articleMap.insert(*it, ali);
00327         }
00328     }
00329 
00330     sort();
00331     applyFilters();
00332     d->noneSelected = true;
00333     setUpdatesEnabled(true);
00334     triggerUpdate();
00335 }
00336 
00337 void ArticleListView::slotClear()
00338 {
00339     if (d->node)
00340         disconnectFromNode(d->node);
00341         
00342     d->node = 0;
00343     d->articleMap.clear();
00344     clear();
00345 }
00346 
00347 void ArticleListView::slotArticlesAdded(TreeNode* /*node*/, const QValueList<Article>& list)
00348 {
00349     setUpdatesEnabled(false);
00350     
00351     bool statusActive = !(d->statusFilter.matchesAll());
00352     bool textActive = !(d->textFilter.matchesAll());
00353     
00354     for (QValueList<Article>::ConstIterator it = list.begin(); it != list.end(); ++it)
00355     {
00356         if (!d->articleMap.contains(*it))
00357         {
00358             if (!(*it).isNull() && !(*it).isDeleted())
00359             {
00360                 ArticleItem* ali = new ArticleItem(this, *it);
00361                 ali->setVisible( (!statusActive ||  d->statusFilter.matches( ali->article()))
00362                         && (!textActive || d->textFilter.matches( ali->article())) );
00363                 d->articleMap.insert(*it, ali);
00364             }
00365         }
00366     }
00367     setUpdatesEnabled(true);
00368     triggerUpdate();
00369 }
00370 
00371 void ArticleListView::slotArticlesUpdated(TreeNode* /*node*/, const QValueList<Article>& list)
00372 {
00373     setUpdatesEnabled(false);
00374 
00375     // if only one item is selected and this selected item
00376     // is deleted, we will select the next item in the list
00377     bool singleSelected = selectedArticles().count() == 1;
00378     
00379     bool statusActive = !(d->statusFilter.matchesAll());
00380     bool textActive = !(d->textFilter.matchesAll());
00381     
00382     QListViewItem* next = 0; // the item to select if a selected item is deleted
00383     
00384     for (QValueList<Article>::ConstIterator it = list.begin(); it != list.end(); ++it)
00385     {
00386         
00387         if (!(*it).isNull() && d->articleMap.contains(*it))
00388         {
00389             ArticleItem* ali = d->articleMap[*it];
00390 
00391             if (ali)
00392             {
00393                 if ((*it).isDeleted()) // if article was set to deleted, delete item
00394                 {
00395                     if (singleSelected && ali->isSelected())
00396                     {
00397                         if (ali->itemBelow())
00398                             next = ali->itemBelow();
00399                         else if (ali->itemAbove())
00400                             next = ali->itemAbove();
00401                     }
00402                     
00403                     d->articleMap.remove(*it);
00404                     delete ali;
00405                 }
00406                 else
00407                 {
00408                     ali->updateItem(*it);
00409                     // if the updated article matches the filters after the update,
00410                     // make visible. If it matched them before but not after update,
00411                     // they should stay visible (to not confuse users)
00412                     if ((!statusActive || d->statusFilter.matches(ali->article()))
00413                         && (!textActive || d->textFilter.matches( ali->article())) )
00414                         ali->setVisible(true);
00415                 }
00416             } // if ali
00417         }
00418     }
00419 
00420     // if the only selected item was deleted, select
00421     // an item next to it
00422     if (singleSelected && next != 0)
00423     {
00424         setSelected(next, true);
00425         setCurrentItem(next);
00426     }
00427 
00428     setUpdatesEnabled(true);
00429     triggerUpdate();
00430 }
00431 
00432 void ArticleListView::slotArticlesRemoved(TreeNode* /*node*/, const QValueList<Article>& list)
00433 {
00434     // if only one item is selected and this selected item
00435     // is deleted, we will select the next item in the list
00436     bool singleSelected = selectedArticles().count() == 1;
00437 
00438     QListViewItem* next = 0; // the item to select if a selected item is deleted
00439     
00440     setUpdatesEnabled(false);
00441     
00442     for (QValueList<Article>::ConstIterator it = list.begin(); it != list.end(); ++it)
00443     {
00444         if (d->articleMap.contains(*it))
00445         {
00446             ArticleItem* ali = d->articleMap[*it];
00447             d->articleMap.remove(*it);
00448             
00449             if (singleSelected && ali->isSelected())
00450             {
00451                 if (ali->itemBelow())
00452                     next = ali->itemBelow();
00453                 else if (ali->itemAbove())
00454                     next = ali->itemAbove();
00455             }
00456             
00457             delete ali;
00458         }
00459     }
00460     
00461     // if the only selected item was deleted, select
00462     // an item next to it
00463     if (singleSelected && next != 0)
00464     {
00465         setSelected(next, true);
00466         setCurrentItem(next);
00467     }
00468 
00469     setUpdatesEnabled(true);
00470     triggerUpdate();
00471 }
00472             
00473 void ArticleListView::connectToNode(TreeNode* node)
00474 {
00475     connect(node, SIGNAL(signalDestroyed(TreeNode*)), this, SLOT(slotClear()) );
00476     connect(node, SIGNAL(signalArticlesAdded(TreeNode*, const QValueList<Article>&)), this, SLOT(slotArticlesAdded(TreeNode*, const QValueList<Article>&)) );
00477     connect(node, SIGNAL(signalArticlesUpdated(TreeNode*, const QValueList<Article>&)), this, SLOT(slotArticlesUpdated(TreeNode*, const QValueList<Article>&)) );
00478     connect(node, SIGNAL(signalArticlesRemoved(TreeNode*, const QValueList<Article>&)), this, SLOT(slotArticlesRemoved(TreeNode*, const QValueList<Article>&)) );
00479 }
00480 
00481 void ArticleListView::disconnectFromNode(TreeNode* node)
00482 {
00483     disconnect(node, SIGNAL(signalDestroyed(TreeNode*)), this, SLOT(slotClear()) );
00484     disconnect(node, SIGNAL(signalArticlesAdded(TreeNode*, const QValueList<Article>&)), this, SLOT(slotArticlesAdded(TreeNode*, const QValueList<Article>&)) );
00485     disconnect(node, SIGNAL(signalArticlesUpdated(TreeNode*, const QValueList<Article>&)), this, SLOT(slotArticlesUpdated(TreeNode*, const QValueList<Article>&)) );
00486     disconnect(node, SIGNAL(signalArticlesRemoved(TreeNode*, const QValueList<Article>&)), this, SLOT(slotArticlesRemoved(TreeNode*, const QValueList<Article>&)) );
00487 }
00488 
00489 void ArticleListView::applyFilters()
00490 {
00491     bool statusActive = !(d->statusFilter.matchesAll());
00492     bool textActive = !(d->textFilter.matchesAll());
00493     
00494     ArticleItem* ali = 0;
00495     
00496     if (!statusActive && !textActive)
00497     {
00498         for (QListViewItemIterator it(this); it.current(); ++it)
00499         {
00500             (static_cast<ArticleItem*> (it.current()))->setVisible(true);
00501         }
00502     }
00503     else if (statusActive && !textActive)
00504     {
00505         for (QListViewItemIterator it(this); it.current(); ++it)
00506         {
00507             ali = static_cast<ArticleItem*> (it.current());
00508             ali->setVisible( d->statusFilter.matches( ali->article()) );
00509         }
00510     }
00511     else if (!statusActive && textActive)
00512     {
00513         for (QListViewItemIterator it(this); it.current(); ++it)
00514         {
00515             ali = static_cast<ArticleItem*> (it.current());
00516             ali->setVisible( d->textFilter.matches( ali->article()) );
00517         }
00518     }
00519     else // both true
00520     {
00521         for (QListViewItemIterator it(this); it.current(); ++it)
00522         {
00523             ali = static_cast<ArticleItem*> (it.current());
00524             ali->setVisible( d->statusFilter.matches( ali->article()) 
00525                     && d->textFilter.matches( ali->article()) );
00526         }
00527     }
00528 
00529 }
00530 
00531 int ArticleListView::visibleArticles()
00532 {
00533     int visible = 0;
00534     ArticleItem* ali = 0;
00535     for (QListViewItemIterator it(this); it.current(); ++it) {
00536         ali = static_cast<ArticleItem*> (it.current());
00537         visible += ali->isVisible() ? 1 : 0;
00538     }
00539     return visible;
00540 }
00541 
00542 // from amarok :)
00543 void ArticleListView::paintInfoBox(const QString &message)
00544 {
00545     QPainter p( viewport() );
00546     QSimpleRichText t( message, QApplication::font() );
00547 
00548     if ( t.width()+30 >= viewport()->width() || t.height()+30 >= viewport()->height() )
00549             //too big, giving up
00550         return;
00551 
00552     const uint w = t.width();
00553     const uint h = t.height();
00554     const uint x = (viewport()->width() - w - 30) / 2 ;
00555     const uint y = (viewport()->height() - h - 30) / 2 ;
00556 
00557     p.setBrush( colorGroup().background() );
00558     p.drawRoundRect( x, y, w+30, h+30, (8*200)/w, (8*200)/h );
00559     t.draw( &p, x+15, y+15, QRect(), colorGroup() );
00560 }
00561 
00562 void ArticleListView::viewportPaintEvent(QPaintEvent *e)
00563 {
00564 
00565     KListView::viewportPaintEvent(e);
00566     
00567     if(!e)
00568         return;
00569         
00570     QString message = QString::null;
00571     
00572     //kdDebug() << "visible articles: " << visibleArticles() << endl;
00573     
00574     if(childCount() != 0) // article list is not empty
00575     {
00576         if (visibleArticles() == 0)
00577         {
00578         message = i18n("<div align=center>"
00579                         "<h3>No matches</h3>"
00580                         "Filter does not match any articles, "
00581                         "please change your criteria and try again."
00582                         "</div>");
00583         }
00584         
00585     }
00586     else // article list is empty
00587     {
00588         if (!d->node) // no node selected
00589         {
00590             message = i18n("<div align=center>"
00591                        "<h3>No feed selected</h3>"
00592                        "This area is article list. "
00593                        "Select a feed from the feed list "
00594                        "and you will see its articles here."
00595                        "</div>");
00596         }
00597         else // empty node
00598         {
00599             // TODO: we could display message like "empty node, choose "fetch" to update it" 
00600         }
00601     }
00602     
00603     if (!message.isNull())
00604         paintInfoBox(message);
00605 }
00606 
00607 QDragObject *ArticleListView::dragObject()
00608 {
00609     QDragObject* d = 0;
00610     QValueList<Article> articles = selectedArticles();
00611     if (!articles.isEmpty())
00612     {
00613         d = new ArticleDrag(articles, this);
00614     }
00615     return d;
00616 }
00617 
00618 void ArticleListView::slotPreviousArticle()
00619 {
00620     ArticleItem* ali = 0;
00621     if (!currentItem() || selectedItems().isEmpty())
00622         ali = dynamic_cast<ArticleItem*>(lastChild());
00623     else
00624         ali = dynamic_cast<ArticleItem*>(currentItem()->itemAbove());
00625     
00626     if (ali)
00627     {
00628         Article a = ali->article();
00629         clearSelection();
00630         setSelected(d->articleMap[a], true);
00631         setCurrentItem(d->articleMap[a]);
00632         d->ensureCurrentItemVisible();
00633     }
00634 }
00635 
00636 void ArticleListView::slotNextArticle()
00637 {
00638     ArticleItem* ali = 0;
00639     if (!currentItem() || selectedItems().isEmpty())
00640         ali = dynamic_cast<ArticleItem*>(firstChild());
00641     else
00642         ali = dynamic_cast<ArticleItem*>(currentItem()->itemBelow());
00643     
00644     if (ali)
00645     {
00646         Article a = ali->article();
00647         clearSelection();
00648         setSelected(d->articleMap[a], true);
00649         setCurrentItem(d->articleMap[a]);
00650         d->ensureCurrentItemVisible();
00651     }
00652 }
00653 
00654 void ArticleListView::slotNextUnreadArticle()
00655 {
00656     ArticleItem* start = 0L;
00657     if (!currentItem() || selectedItems().isEmpty())
00658         start = dynamic_cast<ArticleItem*>(firstChild());
00659     else
00660         start = dynamic_cast<ArticleItem*>(currentItem()->itemBelow() ? currentItem()->itemBelow() : firstChild());
00661 
00662     ArticleItem* i = start;
00663     ArticleItem* unread = 0L;
00664     
00665     do
00666     {
00667         if (i == 0L)
00668             i = static_cast<ArticleItem*>(firstChild());
00669         else
00670         {
00671             if (i->article().status() != Article::Read)
00672                 unread = i;
00673             else 
00674                 i = static_cast<ArticleItem*>(i && i->itemBelow() ? i->itemBelow() : firstChild());
00675         }
00676     }
00677     while (!unread && i != start);
00678 
00679     if (unread)
00680     {
00681         Article a = unread->article();
00682         setCurrentItem(d->articleMap[a]);
00683         clearSelection();
00684         setSelected(d->articleMap[a], true);
00685         d->ensureCurrentItemVisible();
00686     }
00687 }
00688 
00689 void ArticleListView::slotPreviousUnreadArticle()
00690 {
00691     ArticleItem* start = 0L;
00692     if (!currentItem() || selectedItems().isEmpty())
00693         start = dynamic_cast<ArticleItem*>(lastChild());
00694     else
00695         start = dynamic_cast<ArticleItem*>(currentItem()->itemAbove() ? currentItem()->itemAbove() : firstChild());
00696 
00697     ArticleItem* i = start;
00698     ArticleItem* unread = 0L;
00699     
00700     do
00701     {
00702         if (i == 0L)
00703             i = static_cast<ArticleItem*>(lastChild());
00704         else
00705         {
00706             if (i->article().status() != Article::Read)
00707                 unread = i;
00708             else 
00709                 i = static_cast<ArticleItem*>(i->itemAbove() ? i->itemAbove() : lastChild());
00710         }
00711     }
00712     while ( !(unread != 0L || i == start) );
00713 
00714     if (unread)
00715     {
00716         Article a = unread->article();
00717         setCurrentItem(d->articleMap[a]);
00718         clearSelection();
00719         setSelected(d->articleMap[a], true);
00720         d->ensureCurrentItemVisible();
00721     }
00722 }
00723 
00724 void ArticleListView::keyPressEvent(QKeyEvent* e)
00725 {
00726     e->ignore();
00727 }
00728 
00729 void ArticleListView::slotSelectionChanged()
00730 {
00731     // if there is only one article in the list, currentItem is set initially to 
00732     // that article item, although the user hasn't selected it. If the user selects
00733     // the article, selection changes, but currentItem does not.
00734     // executed. So we have to handle this case by observing selection changes.
00735     
00736     if (d->noneSelected)
00737     {
00738         d->noneSelected = false;
00739         slotCurrentChanged(currentItem());
00740     }
00741 }
00742 
00743 void ArticleListView::slotCurrentChanged(QListViewItem* item)
00744 {
00745     ArticleItem* ai = dynamic_cast<ArticleItem*> (item);
00746     if (ai)
00747         emit signalArticleChosen( ai->article() );
00748     else 
00749         emit signalArticleChosen( Article() );
00750 } 
00751 
00752 
00753 void ArticleListView::slotDoubleClicked(QListViewItem* item, const QPoint& p, int i)
00754 {
00755     ArticleItem* ali = dynamic_cast<ArticleItem*>(item);
00756     if (ali)
00757         emit signalDoubleClicked(ali->article(), p, i);
00758 }
00759 
00760 void ArticleListView::slotContextMenu(KListView* /*list*/, QListViewItem* /*item*/, const QPoint& p)
00761 {
00762     QWidget* w = ActionManager::getInstance()->container("article_popup");
00763     QPopupMenu* popup = static_cast<QPopupMenu *>(w);
00764     if (popup)
00765         popup->exec(p);
00766 }
00767 
00768 void ArticleListView::slotMouseButtonPressed(int button, QListViewItem* item, const QPoint& p, int column)
00769 {
00770     ArticleItem* ali = dynamic_cast<ArticleItem*>(item);
00771     if (ali)
00772         emit signalMouseButtonPressed(button, ali->article(), p, column);
00773 }
00774 
00775 ArticleListView::~ArticleListView()
00776 {
00777     Settings::setTitleWidth(columnWidth(0));
00778     Settings::setFeedWidth(columnWidth(1) > 0 ? columnWidth(1) : d->feedWidth);
00779     Settings::setSortColumn(sortColumn());
00780     Settings::setSortAscending(sortOrder() == Ascending);
00781     Settings::writeConfig();
00782     delete d->columnLayoutVisitor;
00783     delete d;
00784     d = 0;
00785 }
00786 
00787 QValueList<Article> ArticleListView::selectedArticles() const
00788 {
00789     QValueList<Article> ret;
00790     QPtrList<QListViewItem> items = selectedItems(false);
00791     for (QListViewItem* i = items.first(); i; i = items.next() )
00792         ret.append((static_cast<ArticleItem*>(i))->article());
00793     return ret;
00794 }
00795 
00796 } // namespace Akregator
00797 
00798 #include "articlelistview.moc"
00799 // vim: ts=4 sw=4 et
KDE Home | KDE Accessibility Home | Description of Access Keys