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 TQt, and distribute the resulting executable, 00022 without including the source code for TQt 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 <tdeglobal.h> 00038 #include <kiconloader.h> 00039 #include <tdelocale.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 virtual ~ColumnLayoutVisitor() {} 00093 00094 virtual bool visitTagNode(TagNode* /*node*/) 00095 { 00096 if (m_view->d->columnMode == ArticleListViewPrivate::feedMode) 00097 { 00098 m_view->setColumnWidth(1, m_view->d->feedWidth); 00099 m_view->d->columnMode = ArticleListViewPrivate::groupMode; 00100 } 00101 return true; 00102 } 00103 00104 virtual bool visitFolder(Folder* /*node*/) 00105 { 00106 if (m_view->d->columnMode == ArticleListViewPrivate::feedMode) 00107 { 00108 m_view->setColumnWidth(1, m_view->d->feedWidth); 00109 m_view->d->columnMode = ArticleListViewPrivate::groupMode; 00110 } 00111 return true; 00112 } 00113 00114 virtual bool visitFeed(Feed* /*node*/) 00115 { 00116 if (m_view->d->columnMode == ArticleListViewPrivate::groupMode) 00117 { 00118 m_view->d->feedWidth = m_view->columnWidth(1); 00119 m_view->hideColumn(1); 00120 m_view->d->columnMode = ArticleListViewPrivate::feedMode; 00121 } 00122 return true; 00123 } 00124 private: 00125 00126 ArticleListView* m_view; 00127 00128 }; 00129 00130 class ArticleListView::ArticleItem : public TDEListViewItem 00131 { 00132 friend class ArticleListView; 00133 00134 public: 00135 ArticleItem( TQListView *parent, const Article& a); 00136 ~ArticleItem(); 00137 00138 Article& article(); 00139 00140 void paintCell ( TQPainter * p, const TQColorGroup & cg, int column, int width, int align ); 00141 virtual int compare(TQListViewItem *i, int col, bool ascending) const; 00142 00143 void updateItem(const Article& article); 00144 00145 virtual ArticleItem* itemAbove() { return static_cast<ArticleItem*>(TDEListViewItem::itemAbove()); } 00146 00147 virtual ArticleItem* nextSibling() { return static_cast<ArticleItem*>(TDEListViewItem::nextSibling()); } 00148 00149 private: 00150 Article m_article; 00151 time_t m_pubDate; 00152 static TQPixmap keepFlag() { 00153 static TQPixmap s_keepFlag = TQPixmap(locate("data", "akregator/pics/akregator_flag.png")); 00154 return s_keepFlag; 00155 } 00156 }; 00157 00158 // FIXME: Remove resolveEntities for KDE 4.0, it's now done in the parser 00159 ArticleListView::ArticleItem::ArticleItem( TQListView *parent, const Article& a) 00160 : 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()) 00161 { 00162 if (a.keep()) 00163 setPixmap(0, keepFlag()); 00164 } 00165 00166 ArticleListView::ArticleItem::~ArticleItem() 00167 { 00168 } 00169 00170 Article& ArticleListView::ArticleItem::article() 00171 { 00172 return m_article; 00173 } 00174 00175 // paint ze peons 00176 void ArticleListView::ArticleItem::paintCell ( TQPainter * p, const TQColorGroup & cg, int column, int width, int align ) 00177 { 00178 if (article().status() == Article::Read) 00179 TDEListViewItem::paintCell( p, cg, column, width, align ); 00180 else 00181 { 00182 TQColorGroup cg2(cg); 00183 00184 if (article().status() == Article::Unread) 00185 cg2.setColor(TQColorGroup::Text, Settings::unreadTextColor()); 00186 else // New 00187 cg2.setColor(TQColorGroup::Text, Settings::readTextColor()); 00188 00189 TDEListViewItem::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, TDEGlobal::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 TDEListViewItem::compare(i, col, ascending); 00212 } 00213 00214 /* ==================================================================================== */ 00215 00216 ArticleListView::ArticleListView(TQWidget *parent, const char *name) 00217 : TDEListView(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(TDEListView*, TQListViewItem*, const TQPoint&)), 00278 this, TQT_SLOT(slotContextMenu(TDEListView*, 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*>(TDEListView::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 TDEListView::viewportPaintEvent(e); 00576 00577 if(!e) 00578 return; 00579 00580 TQString message = TQString(); 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(TDEListView* /*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