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 <tqdatetime.h>
00044 #include <tqpixmap.h>
00045 #include <tqpopupmenu.h>
00046 #include <tqptrlist.h>
00047 #include <tqvaluelist.h>
00048 #include <tqwhatsthis.h>
00049 #include <tqheader.h>
00050 #include <tqdragobject.h>
00051 #include <tqsimplerichtext.h>
00052 #include <tqpainter.h>
00053 #include <tqapplication.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     TQMap<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( TQListView *parent, const Article& a);
00135             ~ArticleItem();
00136 
00137             Article& article();
00138 
00139             void paintCell ( TQPainter * p, const TQColorGroup & cg, int column, int width, int align );
00140             virtual int compare(TQListViewItem *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 TQPixmap keepFlag() {
00152                    static TQPixmap s_keepFlag = TQPixmap(locate("data", "akregator/pics/akregator_flag.png"));
00153                    return s_keepFlag;
00154         }
00155 };
00156 
00157 // FIXME: Remove resolveEntities for KDE 4.0, it's now done in the parser
00158 ArticleListView::ArticleItem::ArticleItem( TQListView *parent, const Article& a)
00159     : 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())
00160 {
00161     if (a.keep())
00162         setPixmap(0, keepFlag());
00163 }
00164  
00165 ArticleListView::ArticleItem::~ArticleItem()
00166 {
00167 }
00168 
00169 Article& ArticleListView::ArticleItem::article()
00170 {
00171     return m_article;
00172 }
00173 
00174 // paint ze peons
00175 void ArticleListView::ArticleItem::paintCell ( TQPainter * p, const TQColorGroup & cg, int column, int width, int align )
00176 {
00177     if (article().status() == Article::Read)
00178         KListViewItem::paintCell( p, cg, column, width, align );
00179     else
00180     {
00181         // if article status is unread or new, we change the color: FIXME: make colors configurable
00182         TQColorGroup cg2(cg);
00183     
00184         if (article().status() == Article::Unread)
00185             cg2.setColor(TQColorGroup::Text, Qt::blue);
00186         else // New
00187             cg2.setColor(TQColorGroup::Text, Qt::red);
00188     
00189         KListViewItem::paintCell( p, cg2, column, width, align );
00190     }
00191 
00192 }
00193 
00194 void ArticleListView::ArticleItem::updateItem(const Article& article)
00195 {
00196     m_article = article;
00197     setPixmap(0, m_article.keep() ? keepFlag() : TQPixmap());
00198     setText(0, KCharsets::resolveEntities(m_article.title()));
00199     setText(1, m_article.feed()->title());
00200     setText(2, KGlobal::locale()->formatDateTime(m_article.pubDate(), true, false));
00201 }
00202 
00203 int ArticleListView::ArticleItem::compare(TQListViewItem *i, int col, bool ascending) const {
00204     if (col == 2)
00205     {
00206         ArticleItem* item = static_cast<ArticleItem*>(i);
00207         if (m_pubDate == item->m_pubDate)
00208             return 0;
00209         return (m_pubDate > item->m_pubDate) ? 1 : -1;
00210     }
00211     return KListViewItem::compare(i, col, ascending);
00212 }
00213 
00214 /* ==================================================================================== */
00215 
00216 ArticleListView::ArticleListView(TQWidget *parent, const char *name)
00217     : KListView(parent, name)
00218 {
00219     d = new ArticleListViewPrivate(this);
00220     d->noneSelected = true;
00221     d->node = 0;
00222     d->columnMode = ArticleListViewPrivate::feedMode;
00223 
00224     d->columnLayoutVisitor = new ColumnLayoutVisitor(this);
00225     setMinimumSize(250, 150);
00226     addColumn(i18n("Article"));
00227     addColumn(i18n("Feed"));
00228     addColumn(i18n("Date"));
00229     setSelectionMode(TQListView::Extended);
00230     setColumnWidthMode(2, TQListView::Maximum);
00231     setColumnWidthMode(1, TQListView::Manual);
00232     setColumnWidthMode(0, TQListView::Manual);
00233     setRootIsDecorated(false);
00234     setItemsRenameable(false);
00235     setItemsMovable(false);
00236     setAllColumnsShowFocus(true);
00237     setDragEnabled(true); // FIXME before we implement dragging between archived feeds??
00238     setAcceptDrops(false); // FIXME before we implement dragging between archived feeds??
00239     setFullWidth(false);
00240     
00241     setShowSortIndicator(true);
00242     setDragAutoScroll(true);
00243     setDropHighlighter(false);
00244 
00245     int c = Settings::sortColumn();
00246     setSorting((c >= 0 && c <= 2) ? c : 2, Settings::sortAscending());
00247 
00248     int w;
00249     w = Settings::titleWidth();
00250     if (w > 0) {
00251         setColumnWidth(0, w);
00252     }
00253     
00254     w = Settings::feedWidth();
00255     if (w > 0) {
00256         setColumnWidth(1, w);
00257     }
00258     
00259     w = Settings::dateWidth();
00260     if (w > 0) {
00261         setColumnWidth(2, w);
00262     }
00263     
00264     d->feedWidth = columnWidth(1);
00265     hideColumn(1);
00266 
00267     header()->setStretchEnabled(true, 0);
00268 
00269     TQWhatsThis::add(this, i18n("<h2>Article list</h2>"
00270         "Here you can browse articles from the currently selected feed. "
00271         "You can also manage articles, as marking them as persistent (\"Keep Article\") or delete them, using the right mouse button menu."
00272         "To view the web page of the article, you can open the article internally in a tab or in an external browser window."));
00273 
00274     connect(this, TQT_SIGNAL(currentChanged(TQListViewItem*)), this, TQT_SLOT(slotCurrentChanged(TQListViewItem* )));
00275     connect(this, TQT_SIGNAL(selectionChanged()), this, TQT_SLOT(slotSelectionChanged()));
00276     connect(this, TQT_SIGNAL(doubleClicked(TQListViewItem*, const TQPoint&, int)),  this, TQT_SLOT(slotDoubleClicked(TQListViewItem*, const TQPoint&, int)) );
00277     connect(this, TQT_SIGNAL(contextMenu(KListView*, TQListViewItem*, const TQPoint&)),
00278             this, TQT_SLOT(slotContextMenu(KListView*, TQListViewItem*, const TQPoint&)));
00279 
00280     connect(this, TQT_SIGNAL(mouseButtonPressed(int, TQListViewItem *, const TQPoint &, int)), this, TQT_SLOT(slotMouseButtonPressed(int, TQListViewItem *, const TQPoint &, int)));
00281 }
00282 
00283 Article ArticleListView::currentArticle() const
00284 {
00285     ArticleItem* ci = dynamic_cast<ArticleItem*>(KListView::currentItem());
00286     return (ci && !selectedItems().isEmpty()) ? ci->article() : Article();
00287 }
00288 
00289 void ArticleListView::slotSetFilter(const Akregator::Filters::ArticleMatcher& textFilter, const Akregator::Filters::ArticleMatcher& statusFilter)
00290 {
00291     if ( (textFilter != d->textFilter) || (statusFilter != d->statusFilter) )
00292     {
00293         d->textFilter = textFilter;
00294         d->statusFilter = statusFilter;
00295                
00296         applyFilters();
00297     }
00298 }
00299 
00300 void ArticleListView::slotShowNode(TreeNode* node)
00301 {
00302     if (node == d->node)
00303         return;
00304 
00305     slotClear();
00306 
00307     if (!node)
00308         return;
00309 
00310     d->node = node;
00311     connectToNode(node);
00312 
00313     d->columnLayoutVisitor->visit(node);
00314 
00315     setUpdatesEnabled(false);
00316 
00317     TQValueList<Article> articles = d->node->articles();
00318 
00319     TQValueList<Article>::ConstIterator end = articles.end();
00320     TQValueList<Article>::ConstIterator it = articles.begin();
00321     
00322     for (; it != end; ++it)
00323     {
00324         if (!(*it).isNull() && !(*it).isDeleted())
00325         {
00326             ArticleItem* ali = new ArticleItem(this, *it);
00327             d->articleMap.insert(*it, ali);
00328         }
00329     }
00330 
00331     sort();
00332     applyFilters();
00333     d->noneSelected = true;
00334     setUpdatesEnabled(true);
00335     triggerUpdate();
00336 }
00337 
00338 void ArticleListView::slotClear()
00339 {
00340     if (d->node)
00341         disconnectFromNode(d->node);
00342         
00343     d->node = 0;
00344     d->articleMap.clear();
00345     clear();
00346 }
00347 
00348 void ArticleListView::slotArticlesAdded(TreeNode* /*node*/, const TQValueList<Article>& list)
00349 {
00350     setUpdatesEnabled(false);
00351     
00352     bool statusActive = !(d->statusFilter.matchesAll());
00353     bool textActive = !(d->textFilter.matchesAll());
00354     
00355     for (TQValueList<Article>::ConstIterator it = list.begin(); it != list.end(); ++it)
00356     {
00357         if (!d->articleMap.contains(*it))
00358         {
00359             if (!(*it).isNull() && !(*it).isDeleted())
00360             {
00361                 ArticleItem* ali = new ArticleItem(this, *it);
00362                 ali->setVisible( (!statusActive ||  d->statusFilter.matches( ali->article()))
00363                         && (!textActive || d->textFilter.matches( ali->article())) );
00364                 d->articleMap.insert(*it, ali);
00365             }
00366         }
00367     }
00368     setUpdatesEnabled(true);
00369     triggerUpdate();
00370 }
00371 
00372 void ArticleListView::slotArticlesUpdated(TreeNode* /*node*/, const TQValueList<Article>& list)
00373 {
00374     setUpdatesEnabled(false);
00375 
00376     // if only one item is selected and this selected item
00377     // is deleted, we will select the next item in the list
00378     bool singleSelected = selectedArticles().count() == 1;
00379     
00380     bool statusActive = !(d->statusFilter.matchesAll());
00381     bool textActive = !(d->textFilter.matchesAll());
00382     
00383     TQListViewItem* next = 0; // the item to select if a selected item is deleted
00384     
00385     for (TQValueList<Article>::ConstIterator it = list.begin(); it != list.end(); ++it)
00386     {
00387         
00388         if (!(*it).isNull() && d->articleMap.contains(*it))
00389         {
00390             ArticleItem* ali = d->articleMap[*it];
00391 
00392             if (ali)
00393             {
00394                 if ((*it).isDeleted()) // if article was set to deleted, delete item
00395                 {
00396                     if (singleSelected && ali->isSelected())
00397                     {
00398                         if (ali->itemBelow())
00399                             next = ali->itemBelow();
00400                         else if (ali->itemAbove())
00401                             next = ali->itemAbove();
00402                     }
00403                     
00404                     d->articleMap.remove(*it);
00405                     delete ali;
00406                 }
00407                 else
00408                 {
00409                     ali->updateItem(*it);
00410                     // if the updated article matches the filters after the update,
00411                     // make visible. If it matched them before but not after update,
00412                     // they should stay visible (to not confuse users)
00413                     if ((!statusActive || d->statusFilter.matches(ali->article()))
00414                         && (!textActive || d->textFilter.matches( ali->article())) )
00415                         ali->setVisible(true);
00416                 }
00417             } // if ali
00418         }
00419     }
00420 
00421     // if the only selected item was deleted, select
00422     // an item next to it
00423     if (singleSelected && next != 0)
00424     {
00425         setSelected(next, true);
00426         setCurrentItem(next);
00427     }
00428     else
00429     {
00430         d->noneSelected = true;
00431     }
00432     
00433 
00434     setUpdatesEnabled(true);
00435     triggerUpdate();
00436 }
00437 
00438 void ArticleListView::slotArticlesRemoved(TreeNode* /*node*/, const TQValueList<Article>& list)
00439 {
00440     // if only one item is selected and this selected item
00441     // is deleted, we will select the next item in the list
00442     bool singleSelected = selectedArticles().count() == 1;
00443 
00444     TQListViewItem* next = 0; // the item to select if a selected item is deleted
00445     
00446     setUpdatesEnabled(false);
00447     
00448     for (TQValueList<Article>::ConstIterator it = list.begin(); it != list.end(); ++it)
00449     {
00450         if (d->articleMap.contains(*it))
00451         {
00452             ArticleItem* ali = d->articleMap[*it];
00453             d->articleMap.remove(*it);
00454             
00455             if (singleSelected && ali->isSelected())
00456             {
00457                 if (ali->itemBelow())
00458                     next = ali->itemBelow();
00459                 else if (ali->itemAbove())
00460                     next = ali->itemAbove();
00461             }
00462             
00463             delete ali;
00464         }
00465     }
00466     
00467     // if the only selected item was deleted, select
00468     // an item next to it
00469     if (singleSelected && next != 0)
00470     {
00471         setSelected(next, true);
00472         setCurrentItem(next);
00473     }
00474     else
00475     {
00476         d->noneSelected = true;
00477     }
00478      
00479     setUpdatesEnabled(true);
00480     triggerUpdate();
00481 }
00482             
00483 void ArticleListView::connectToNode(TreeNode* node)
00484 {
00485     connect(node, TQT_SIGNAL(signalDestroyed(TreeNode*)), this, TQT_SLOT(slotClear()) );
00486     connect(node, TQT_SIGNAL(signalArticlesAdded(TreeNode*, const TQValueList<Article>&)), this, TQT_SLOT(slotArticlesAdded(TreeNode*, const TQValueList<Article>&)) );
00487     connect(node, TQT_SIGNAL(signalArticlesUpdated(TreeNode*, const TQValueList<Article>&)), this, TQT_SLOT(slotArticlesUpdated(TreeNode*, const TQValueList<Article>&)) );
00488     connect(node, TQT_SIGNAL(signalArticlesRemoved(TreeNode*, const TQValueList<Article>&)), this, TQT_SLOT(slotArticlesRemoved(TreeNode*, const TQValueList<Article>&)) );
00489 }
00490 
00491 void ArticleListView::disconnectFromNode(TreeNode* node)
00492 {
00493     disconnect(node, TQT_SIGNAL(signalDestroyed(TreeNode*)), this, TQT_SLOT(slotClear()) );
00494     disconnect(node, TQT_SIGNAL(signalArticlesAdded(TreeNode*, const TQValueList<Article>&)), this, TQT_SLOT(slotArticlesAdded(TreeNode*, const TQValueList<Article>&)) );
00495     disconnect(node, TQT_SIGNAL(signalArticlesUpdated(TreeNode*, const TQValueList<Article>&)), this, TQT_SLOT(slotArticlesUpdated(TreeNode*, const TQValueList<Article>&)) );
00496     disconnect(node, TQT_SIGNAL(signalArticlesRemoved(TreeNode*, const TQValueList<Article>&)), this, TQT_SLOT(slotArticlesRemoved(TreeNode*, const TQValueList<Article>&)) );
00497 }
00498 
00499 void ArticleListView::applyFilters()
00500 {
00501     bool statusActive = !(d->statusFilter.matchesAll());
00502     bool textActive = !(d->textFilter.matchesAll());
00503     
00504     ArticleItem* ali = 0;
00505     
00506     if (!statusActive && !textActive)
00507     {
00508         for (TQListViewItemIterator it(this); it.current(); ++it)
00509         {
00510             (static_cast<ArticleItem*> (it.current()))->setVisible(true);
00511         }
00512     }
00513     else if (statusActive && !textActive)
00514     {
00515         for (TQListViewItemIterator it(this); it.current(); ++it)
00516         {
00517             ali = static_cast<ArticleItem*> (it.current());
00518             ali->setVisible( d->statusFilter.matches( ali->article()) );
00519         }
00520     }
00521     else if (!statusActive && textActive)
00522     {
00523         for (TQListViewItemIterator it(this); it.current(); ++it)
00524         {
00525             ali = static_cast<ArticleItem*> (it.current());
00526             ali->setVisible( d->textFilter.matches( ali->article()) );
00527         }
00528     }
00529     else // both true
00530     {
00531         for (TQListViewItemIterator it(this); it.current(); ++it)
00532         {
00533             ali = static_cast<ArticleItem*> (it.current());
00534             ali->setVisible( d->statusFilter.matches( ali->article()) 
00535                     && d->textFilter.matches( ali->article()) );
00536         }
00537     }
00538 
00539 }
00540 
00541 int ArticleListView::visibleArticles()
00542 {
00543     int visible = 0;
00544     ArticleItem* ali = 0;
00545     for (TQListViewItemIterator it(this); it.current(); ++it) {
00546         ali = static_cast<ArticleItem*> (it.current());
00547         visible += ali->isVisible() ? 1 : 0;
00548     }
00549     return visible;
00550 }
00551 
00552 // from amarok :)
00553 void ArticleListView::paintInfoBox(const TQString &message)
00554 {
00555     TQPainter p( viewport() );
00556     TQSimpleRichText t( message, TQApplication::font() );
00557 
00558     if ( t.width()+30 >= viewport()->width() || t.height()+30 >= viewport()->height() )
00559             //too big, giving up
00560         return;
00561 
00562     const uint w = t.width();
00563     const uint h = t.height();
00564     const uint x = (viewport()->width() - w - 30) / 2 ;
00565     const uint y = (viewport()->height() - h - 30) / 2 ;
00566 
00567     p.setBrush( colorGroup().background() );
00568     p.drawRoundRect( x, y, w+30, h+30, (8*200)/w, (8*200)/h );
00569     t.draw( &p, x+15, y+15, TQRect(), colorGroup() );
00570 }
00571 
00572 void ArticleListView::viewportPaintEvent(TQPaintEvent *e)
00573 {
00574 
00575     KListView::viewportPaintEvent(e);
00576     
00577     if(!e)
00578         return;
00579         
00580     TQString message = TQString::null;
00581     
00582     //kdDebug() << "visible articles: " << visibleArticles() << endl;
00583     
00584     if(childCount() != 0) // article list is not empty
00585     {
00586         if (visibleArticles() == 0)
00587         {
00588         message = i18n("<div align=center>"
00589                         "<h3>No matches</h3>"
00590                         "Filter does not match any articles, "
00591                         "please change your criteria and try again."
00592                         "</div>");
00593         }
00594         
00595     }
00596     else // article list is empty
00597     {
00598         if (!d->node) // no node selected
00599         {
00600             message = i18n("<div align=center>"
00601                        "<h3>No feed selected</h3>"
00602                        "This area is article list. "
00603                        "Select a feed from the feed list "
00604                        "and you will see its articles here."
00605                        "</div>");
00606         }
00607         else // empty node
00608         {
00609             // TODO: we could display message like "empty node, choose "fetch" to update it" 
00610         }
00611     }
00612     
00613     if (!message.isNull())
00614         paintInfoBox(message);
00615 }
00616 
00617 TQDragObject *ArticleListView::dragObject()
00618 {
00619     TQDragObject* d = 0;
00620     TQValueList<Article> articles = selectedArticles();
00621     if (!articles.isEmpty())
00622     {
00623         d = new ArticleDrag(articles, this);
00624     }
00625     return d;
00626 }
00627 
00628 void ArticleListView::slotPreviousArticle()
00629 {
00630     ArticleItem* ali = 0;
00631     if (!currentItem() || selectedItems().isEmpty())
00632         ali = dynamic_cast<ArticleItem*>(lastChild());
00633     else
00634         ali = dynamic_cast<ArticleItem*>(currentItem()->itemAbove());
00635     
00636     if (ali)
00637     {
00638         Article a = ali->article();
00639         setCurrentItem(d->articleMap[a]);
00640         clearSelection();
00641         setSelected(d->articleMap[a], true);
00642         d->ensureCurrentItemVisible();
00643     }
00644 }
00645 
00646 void ArticleListView::slotNextArticle()
00647 {
00648     ArticleItem* ali = 0;
00649     if (!currentItem() || selectedItems().isEmpty())
00650         ali = dynamic_cast<ArticleItem*>(firstChild());
00651     else
00652         ali = dynamic_cast<ArticleItem*>(currentItem()->itemBelow());
00653     
00654     if (ali)
00655     {
00656         Article a = ali->article();
00657         setCurrentItem(d->articleMap[a]);
00658         clearSelection();
00659         setSelected(d->articleMap[a], true);
00660         d->ensureCurrentItemVisible();
00661     }
00662 }
00663 
00664 void ArticleListView::slotNextUnreadArticle()
00665 {
00666     ArticleItem* start = 0L;
00667     if (!currentItem() || selectedItems().isEmpty())
00668         start = dynamic_cast<ArticleItem*>(firstChild());
00669     else
00670         start = dynamic_cast<ArticleItem*>(currentItem()->itemBelow() ? currentItem()->itemBelow() : firstChild());
00671 
00672     ArticleItem* i = start;
00673     ArticleItem* unread = 0L;
00674     
00675     do
00676     {
00677         if (i == 0L)
00678             i = static_cast<ArticleItem*>(firstChild());
00679         else
00680         {
00681             if (i->article().status() != Article::Read)
00682                 unread = i;
00683             else 
00684                 i = static_cast<ArticleItem*>(i && i->itemBelow() ? i->itemBelow() : firstChild());
00685         }
00686     }
00687     while (!unread && i != start);
00688 
00689     if (unread)
00690     {
00691         Article a = unread->article();
00692         setCurrentItem(d->articleMap[a]);
00693         clearSelection();
00694         setSelected(d->articleMap[a], true);
00695         d->ensureCurrentItemVisible();
00696     }
00697 }
00698 
00699 void ArticleListView::slotPreviousUnreadArticle()
00700 {
00701     ArticleItem* start = 0L;
00702     if (!currentItem() || selectedItems().isEmpty())
00703         start = dynamic_cast<ArticleItem*>(lastChild());
00704     else
00705         start = dynamic_cast<ArticleItem*>(currentItem()->itemAbove() ? currentItem()->itemAbove() : firstChild());
00706 
00707     ArticleItem* i = start;
00708     ArticleItem* unread = 0L;
00709     
00710     do
00711     {
00712         if (i == 0L)
00713             i = static_cast<ArticleItem*>(lastChild());
00714         else
00715         {
00716             if (i->article().status() != Article::Read)
00717                 unread = i;
00718             else 
00719                 i = static_cast<ArticleItem*>(i->itemAbove() ? i->itemAbove() : lastChild());
00720         }
00721     }
00722     while ( !(unread != 0L || i == start) );
00723 
00724     if (unread)
00725     {
00726         Article a = unread->article();
00727         setCurrentItem(d->articleMap[a]);
00728         clearSelection();
00729         setSelected(d->articleMap[a], true);
00730         d->ensureCurrentItemVisible();
00731     }
00732 }
00733 
00734 void ArticleListView::keyPressEvent(TQKeyEvent* e)
00735 {
00736     e->ignore();
00737 }
00738 
00739 void ArticleListView::slotSelectionChanged()
00740 {
00741     // if there is only one article in the list, currentItem is set initially to 
00742     // that article item, although the user hasn't selected it. If the user selects
00743     // the article, selection changes, but currentItem does not.
00744     // executed. So we have to handle this case by observing selection changes.
00745     
00746     if (d->noneSelected)
00747     {
00748         d->noneSelected = false;
00749         slotCurrentChanged(currentItem());
00750     }
00751 }
00752 
00753 void ArticleListView::slotCurrentChanged(TQListViewItem* item)
00754 {
00755     ArticleItem* ai = dynamic_cast<ArticleItem*> (item);
00756     if (ai)
00757         emit signalArticleChosen( ai->article() );
00758     else
00759     {
00760         d->noneSelected = true;
00761         emit signalArticleChosen( Article() );
00762     }
00763 } 
00764 
00765 
00766 void ArticleListView::slotDoubleClicked(TQListViewItem* item, const TQPoint& p, int i)
00767 {
00768     ArticleItem* ali = dynamic_cast<ArticleItem*>(item);
00769     if (ali)
00770         emit signalDoubleClicked(ali->article(), p, i);
00771 }
00772 
00773 void ArticleListView::slotContextMenu(KListView* /*list*/, TQListViewItem* /*item*/, const TQPoint& p)
00774 {
00775     TQWidget* w = ActionManager::getInstance()->container("article_popup");
00776     TQPopupMenu* popup = static_cast<TQPopupMenu *>(w);
00777     if (popup)
00778         popup->exec(p);
00779 }
00780 
00781 void ArticleListView::slotMouseButtonPressed(int button, TQListViewItem* item, const TQPoint& p, int column)
00782 {
00783     ArticleItem* ali = dynamic_cast<ArticleItem*>(item);
00784     if (ali)
00785         emit signalMouseButtonPressed(button, ali->article(), p, column);
00786 }
00787 
00788 ArticleListView::~ArticleListView()
00789 {
00790     Settings::setTitleWidth(columnWidth(0));
00791     Settings::setFeedWidth(columnWidth(1) > 0 ? columnWidth(1) : d->feedWidth);
00792     Settings::setSortColumn(sortColumn());
00793     Settings::setSortAscending(sortOrder() == Ascending);
00794     Settings::writeConfig();
00795     delete d->columnLayoutVisitor;
00796     delete d;
00797     d = 0;
00798 }
00799 
00800 TQValueList<Article> ArticleListView::selectedArticles() const
00801 {
00802     TQValueList<Article> ret;
00803     TQPtrList<TQListViewItem> items = selectedItems(false);
00804     for (TQListViewItem* i = items.first(); i; i = items.next() )
00805         ret.append((static_cast<ArticleItem*>(i))->article());
00806     return ret;
00807 }
00808 
00809 } // namespace Akregator
00810 
00811 #include "articlelistview.moc"
00812 // vim: ts=4 sw=4 et