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     else
00428     {
00429         d->noneSelected = true;
00430     }
00431     
00432 
00433     setUpdatesEnabled(true);
00434     triggerUpdate();
00435 }
00436 
00437 void ArticleListView::slotArticlesRemoved(TreeNode* /*node*/, const QValueList<Article>& list)
00438 {
00439     // if only one item is selected and this selected item
00440     // is deleted, we will select the next item in the list
00441     bool singleSelected = selectedArticles().count() == 1;
00442 
00443     QListViewItem* next = 0; // the item to select if a selected item is deleted
00444     
00445     setUpdatesEnabled(false);
00446     
00447     for (QValueList<Article>::ConstIterator it = list.begin(); it != list.end(); ++it)
00448     {
00449         if (d->articleMap.contains(*it))
00450         {
00451             ArticleItem* ali = d->articleMap[*it];
00452             d->articleMap.remove(*it);
00453             
00454             if (singleSelected && ali->isSelected())
00455             {
00456                 if (ali->itemBelow())
00457                     next = ali->itemBelow();
00458                 else if (ali->itemAbove())
00459                     next = ali->itemAbove();
00460             }
00461             
00462             delete ali;
00463         }
00464     }
00465     
00466     // if the only selected item was deleted, select
00467     // an item next to it
00468     if (singleSelected && next != 0)
00469     {
00470         setSelected(next, true);
00471         setCurrentItem(next);
00472     }
00473     else
00474     {
00475         d->noneSelected = true;
00476     }
00477      
00478     setUpdatesEnabled(true);
00479     triggerUpdate();
00480 }
00481             
00482 void ArticleListView::connectToNode(TreeNode* node)
00483 {
00484     connect(node, SIGNAL(signalDestroyed(TreeNode*)), this, SLOT(slotClear()) );
00485     connect(node, SIGNAL(signalArticlesAdded(TreeNode*, const QValueList<Article>&)), this, SLOT(slotArticlesAdded(TreeNode*, const QValueList<Article>&)) );
00486     connect(node, SIGNAL(signalArticlesUpdated(TreeNode*, const QValueList<Article>&)), this, SLOT(slotArticlesUpdated(TreeNode*, const QValueList<Article>&)) );
00487     connect(node, SIGNAL(signalArticlesRemoved(TreeNode*, const QValueList<Article>&)), this, SLOT(slotArticlesRemoved(TreeNode*, const QValueList<Article>&)) );
00488 }
00489 
00490 void ArticleListView::disconnectFromNode(TreeNode* node)
00491 {
00492     disconnect(node, SIGNAL(signalDestroyed(TreeNode*)), this, SLOT(slotClear()) );
00493     disconnect(node, SIGNAL(signalArticlesAdded(TreeNode*, const QValueList<Article>&)), this, SLOT(slotArticlesAdded(TreeNode*, const QValueList<Article>&)) );
00494     disconnect(node, SIGNAL(signalArticlesUpdated(TreeNode*, const QValueList<Article>&)), this, SLOT(slotArticlesUpdated(TreeNode*, const QValueList<Article>&)) );
00495     disconnect(node, SIGNAL(signalArticlesRemoved(TreeNode*, const QValueList<Article>&)), this, SLOT(slotArticlesRemoved(TreeNode*, const QValueList<Article>&)) );
00496 }
00497 
00498 void ArticleListView::applyFilters()
00499 {
00500     bool statusActive = !(d->statusFilter.matchesAll());
00501     bool textActive = !(d->textFilter.matchesAll());
00502     
00503     ArticleItem* ali = 0;
00504     
00505     if (!statusActive && !textActive)
00506     {
00507         for (QListViewItemIterator it(this); it.current(); ++it)
00508         {
00509             (static_cast<ArticleItem*> (it.current()))->setVisible(true);
00510         }
00511     }
00512     else if (statusActive && !textActive)
00513     {
00514         for (QListViewItemIterator it(this); it.current(); ++it)
00515         {
00516             ali = static_cast<ArticleItem*> (it.current());
00517             ali->setVisible( d->statusFilter.matches( ali->article()) );
00518         }
00519     }
00520     else if (!statusActive && textActive)
00521     {
00522         for (QListViewItemIterator it(this); it.current(); ++it)
00523         {
00524             ali = static_cast<ArticleItem*> (it.current());
00525             ali->setVisible( d->textFilter.matches( ali->article()) );
00526         }
00527     }
00528     else // both true
00529     {
00530         for (QListViewItemIterator it(this); it.current(); ++it)
00531         {
00532             ali = static_cast<ArticleItem*> (it.current());
00533             ali->setVisible( d->statusFilter.matches( ali->article()) 
00534                     && d->textFilter.matches( ali->article()) );
00535         }
00536     }
00537 
00538 }
00539 
00540 int ArticleListView::visibleArticles()
00541 {
00542     int visible = 0;
00543     ArticleItem* ali = 0;
00544     for (QListViewItemIterator it(this); it.current(); ++it) {
00545         ali = static_cast<ArticleItem*> (it.current());
00546         visible += ali->isVisible() ? 1 : 0;
00547     }
00548     return visible;
00549 }
00550 
00551 // from amarok :)
00552 void ArticleListView::paintInfoBox(const QString &message)
00553 {
00554     QPainter p( viewport() );
00555     QSimpleRichText t( message, QApplication::font() );
00556 
00557     if ( t.width()+30 >= viewport()->width() || t.height()+30 >= viewport()->height() )
00558             //too big, giving up
00559         return;
00560 
00561     const uint w = t.width();
00562     const uint h = t.height();
00563     const uint x = (viewport()->width() - w - 30) / 2 ;
00564     const uint y = (viewport()->height() - h - 30) / 2 ;
00565 
00566     p.setBrush( colorGroup().background() );
00567     p.drawRoundRect( x, y, w+30, h+30, (8*200)/w, (8*200)/h );
00568     t.draw( &p, x+15, y+15, QRect(), colorGroup() );
00569 }
00570 
00571 void ArticleListView::viewportPaintEvent(QPaintEvent *e)
00572 {
00573 
00574     KListView::viewportPaintEvent(e);
00575     
00576     if(!e)
00577         return;
00578         
00579     QString message = QString::null;
00580     
00581     //kdDebug() << "visible articles: " << visibleArticles() << endl;
00582     
00583     if(childCount() != 0) // article list is not empty
00584     {
00585         if (visibleArticles() == 0)
00586         {
00587         message = i18n("<div align=center>"
00588                         "<h3>No matches</h3>"
00589                         "Filter does not match any articles, "
00590                         "please change your criteria and try again."
00591                         "</div>");
00592         }
00593         
00594     }
00595     else // article list is empty
00596     {
00597         if (!d->node) // no node selected
00598         {
00599             message = i18n("<div align=center>"
00600                        "<h3>No feed selected</h3>"
00601                        "This area is article list. "
00602                        "Select a feed from the feed list "
00603                        "and you will see its articles here."
00604                        "</div>");
00605         }
00606         else // empty node
00607         {
00608             // TODO: we could display message like "empty node, choose "fetch" to update it" 
00609         }
00610     }
00611     
00612     if (!message.isNull())
00613         paintInfoBox(message);
00614 }
00615 
00616 QDragObject *ArticleListView::dragObject()
00617 {
00618     QDragObject* d = 0;
00619     QValueList<Article> articles = selectedArticles();
00620     if (!articles.isEmpty())
00621     {
00622         d = new ArticleDrag(articles, this);
00623     }
00624     return d;
00625 }
00626 
00627 void ArticleListView::slotPreviousArticle()
00628 {
00629     ArticleItem* ali = 0;
00630     if (!currentItem() || selectedItems().isEmpty())
00631         ali = dynamic_cast<ArticleItem*>(lastChild());
00632     else
00633         ali = dynamic_cast<ArticleItem*>(currentItem()->itemAbove());
00634     
00635     if (ali)
00636     {
00637         Article a = ali->article();
00638         setCurrentItem(d->articleMap[a]);
00639         clearSelection();
00640         setSelected(d->articleMap[a], true);
00641         d->ensureCurrentItemVisible();
00642     }
00643 }
00644 
00645 void ArticleListView::slotNextArticle()
00646 {
00647     ArticleItem* ali = 0;
00648     if (!currentItem() || selectedItems().isEmpty())
00649         ali = dynamic_cast<ArticleItem*>(firstChild());
00650     else
00651         ali = dynamic_cast<ArticleItem*>(currentItem()->itemBelow());
00652     
00653     if (ali)
00654     {
00655         Article a = ali->article();
00656         setCurrentItem(d->articleMap[a]);
00657         clearSelection();
00658         setSelected(d->articleMap[a], true);
00659         d->ensureCurrentItemVisible();
00660     }
00661 }
00662 
00663 void ArticleListView::slotNextUnreadArticle()
00664 {
00665     ArticleItem* start = 0L;
00666     if (!currentItem() || selectedItems().isEmpty())
00667         start = dynamic_cast<ArticleItem*>(firstChild());
00668     else
00669         start = dynamic_cast<ArticleItem*>(currentItem()->itemBelow() ? currentItem()->itemBelow() : firstChild());
00670 
00671     ArticleItem* i = start;
00672     ArticleItem* unread = 0L;
00673     
00674     do
00675     {
00676         if (i == 0L)
00677             i = static_cast<ArticleItem*>(firstChild());
00678         else
00679         {
00680             if (i->article().status() != Article::Read)
00681                 unread = i;
00682             else 
00683                 i = static_cast<ArticleItem*>(i && i->itemBelow() ? i->itemBelow() : firstChild());
00684         }
00685     }
00686     while (!unread && i != start);
00687 
00688     if (unread)
00689     {
00690         Article a = unread->article();
00691         setCurrentItem(d->articleMap[a]);
00692         clearSelection();
00693         setSelected(d->articleMap[a], true);
00694         d->ensureCurrentItemVisible();
00695     }
00696 }
00697 
00698 void ArticleListView::slotPreviousUnreadArticle()
00699 {
00700     ArticleItem* start = 0L;
00701     if (!currentItem() || selectedItems().isEmpty())
00702         start = dynamic_cast<ArticleItem*>(lastChild());
00703     else
00704         start = dynamic_cast<ArticleItem*>(currentItem()->itemAbove() ? currentItem()->itemAbove() : firstChild());
00705 
00706     ArticleItem* i = start;
00707     ArticleItem* unread = 0L;
00708     
00709     do
00710     {
00711         if (i == 0L)
00712             i = static_cast<ArticleItem*>(lastChild());
00713         else
00714         {
00715             if (i->article().status() != Article::Read)
00716                 unread = i;
00717             else 
00718                 i = static_cast<ArticleItem*>(i->itemAbove() ? i->itemAbove() : lastChild());
00719         }
00720     }
00721     while ( !(unread != 0L || i == start) );
00722 
00723     if (unread)
00724     {
00725         Article a = unread->article();
00726         setCurrentItem(d->articleMap[a]);
00727         clearSelection();
00728         setSelected(d->articleMap[a], true);
00729         d->ensureCurrentItemVisible();
00730     }
00731 }
00732 
00733 void ArticleListView::keyPressEvent(QKeyEvent* e)
00734 {
00735     e->ignore();
00736 }
00737 
00738 void ArticleListView::slotSelectionChanged()
00739 {
00740     // if there is only one article in the list, currentItem is set initially to 
00741     // that article item, although the user hasn't selected it. If the user selects
00742     // the article, selection changes, but currentItem does not.
00743     // executed. So we have to handle this case by observing selection changes.
00744     
00745     if (d->noneSelected)
00746     {
00747         d->noneSelected = false;
00748         slotCurrentChanged(currentItem());
00749     }
00750 }
00751 
00752 void ArticleListView::slotCurrentChanged(QListViewItem* item)
00753 {
00754     ArticleItem* ai = dynamic_cast<ArticleItem*> (item);
00755     if (ai)
00756         emit signalArticleChosen( ai->article() );
00757     else
00758     {
00759         d->noneSelected = true;
00760         emit signalArticleChosen( Article() );
00761     }
00762 } 
00763 
00764 
00765 void ArticleListView::slotDoubleClicked(QListViewItem* item, const QPoint& p, int i)
00766 {
00767     ArticleItem* ali = dynamic_cast<ArticleItem*>(item);
00768     if (ali)
00769         emit signalDoubleClicked(ali->article(), p, i);
00770 }
00771 
00772 void ArticleListView::slotContextMenu(KListView* /*list*/, QListViewItem* /*item*/, const QPoint& p)
00773 {
00774     QWidget* w = ActionManager::getInstance()->container("article_popup");
00775     QPopupMenu* popup = static_cast<QPopupMenu *>(w);
00776     if (popup)
00777         popup->exec(p);
00778 }
00779 
00780 void ArticleListView::slotMouseButtonPressed(int button, QListViewItem* item, const QPoint& p, int column)
00781 {
00782     ArticleItem* ali = dynamic_cast<ArticleItem*>(item);
00783     if (ali)
00784         emit signalMouseButtonPressed(button, ali->article(), p, column);
00785 }
00786 
00787 ArticleListView::~ArticleListView()
00788 {
00789     Settings::setTitleWidth(columnWidth(0));
00790     Settings::setFeedWidth(columnWidth(1) > 0 ? columnWidth(1) : d->feedWidth);
00791     Settings::setSortColumn(sortColumn());
00792     Settings::setSortAscending(sortOrder() == Ascending);
00793     Settings::writeConfig();
00794     delete d->columnLayoutVisitor;
00795     delete d;
00796     d = 0;
00797 }
00798 
00799 QValueList<Article> ArticleListView::selectedArticles() const
00800 {
00801     QValueList<Article> ret;
00802     QPtrList<QListViewItem> items = selectedItems(false);
00803     for (QListViewItem* i = items.first(); i; i = items.next() )
00804         ret.append((static_cast<ArticleItem*>(i))->article());
00805     return ret;
00806 }
00807 
00808 } // namespace Akregator
00809 
00810 #include "articlelistview.moc"
00811 // vim: ts=4 sw=4 et
KDE Home | KDE Accessibility Home | Description of Access Keys