• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdepim
  • Sitemap
  • Contact Us
 

akregator

articlelistview.cpp

Go to the documentation of this file.
00001 /*
00002     This file is part of Akregator.
00003 
00004     Copyright (C) 2004 Stanislav Karchebny <Stanislav.Karchebny@kdemail.net>
00005                   2005-2008 Frank Osterfeld <osterfeld@kde.org>
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 "articlelistview.h"
00026 #include "actionmanager.h"
00027 #include "akregatorconfig.h"
00028 #include "article.h"
00029 #include "articlemodel.h"
00030 #include "kernel.h"
00031 #include "types.h"
00032 
00033 #include <utils/filtercolumnsproxymodel.h>
00034 
00035 #include <KIcon>
00036 #include <KLocale>
00037 #include <KUrl>
00038 #include <KMenu>
00039 
00040 #include <QApplication>
00041 #include <QContextMenuEvent>
00042 #include <QHeaderView>
00043 #include <QKeyEvent>
00044 #include <QList>
00045 #include <QMenu>
00046 #include <QPaintEvent>
00047 #include <QPalette>
00048 
00049 #include <cassert>
00050 
00051 using namespace Akregator;
00052 
00053 
00054 FilterDeletedProxyModel::FilterDeletedProxyModel( QObject* parent ) : QSortFilterProxyModel( parent )
00055 {
00056     setDynamicSortFilter( true );
00057 }
00058 
00059 bool FilterDeletedProxyModel::filterAcceptsRow( int source_row, const QModelIndex& source_parent ) const
00060 {
00061     return !sourceModel()->index( source_row, 0, source_parent ).data( ArticleModel::IsDeletedRole ).toBool();
00062 }
00063 
00064 SortColorizeProxyModel::SortColorizeProxyModel( QObject* parent ) : QSortFilterProxyModel( parent ), m_keepFlagIcon( KIcon( "mail-mark-important" ) )
00065 {
00066 }
00067 
00068 bool SortColorizeProxyModel::filterAcceptsRow ( int source_row, const QModelIndex& source_parent ) const
00069 {
00070     if ( source_parent.isValid() )
00071         return false;
00072 
00073     for ( uint i = 0; i < m_matchers.size(); ++i )
00074     {
00075         if ( !static_cast<ArticleModel*>( sourceModel() )->rowMatches( source_row, m_matchers[i] ) )
00076             return false;
00077     }
00078 
00079     return true;
00080 }
00081 
00082 void SortColorizeProxyModel::setFilters( const std::vector<boost::shared_ptr<const Filters::AbstractMatcher> >&  matchers )
00083 {
00084     if ( m_matchers == matchers )
00085         return;
00086     m_matchers = matchers;
00087     invalidateFilter();
00088 }
00089 
00090 QVariant SortColorizeProxyModel::data( const QModelIndex& idx, int role ) const
00091 {
00092     if ( !idx.isValid() || !sourceModel() )
00093         return QVariant();
00094 
00095     const QModelIndex sourceIdx = mapToSource( idx );
00096 
00097     switch ( role )
00098     {
00099         case Qt::ForegroundRole:
00100         {
00101             switch ( static_cast<ArticleStatus>( sourceIdx.data( ArticleModel::StatusRole ).toInt() ) )
00102             {
00103                 case Unread:
00104                 {
00105                     return Settings::useCustomColors() ?
00106                         Settings::colorUnreadArticles() : Qt::blue;
00107                 }
00108                 case New:
00109                 {
00110                     return Settings::useCustomColors() ?
00111                         Settings::colorNewArticles() : Qt::red;
00112                 }
00113                 case Read:
00114                 {
00115                     return QApplication::palette().color( QPalette::WindowText );
00116                 }
00117             }
00118         }
00119         break;
00120         case Qt::DecorationRole:
00121         {
00122             if ( sourceIdx.column() == ArticleModel::ItemTitleColumn )
00123             {
00124                 return sourceIdx.data( ArticleModel::IsImportantRole ).toBool() ? m_keepFlagIcon : QVariant();
00125             }
00126         }
00127         break;
00128     }
00129     return sourceIdx.data( role );
00130 }
00131 
00132 namespace {
00133 
00134     static bool isRead( const QModelIndex& idx )
00135     {
00136         if ( !idx.isValid() )
00137             return false;
00138 
00139         return static_cast<ArticleStatus>( idx.data( ArticleModel::StatusRole ).toInt() ) == Read;
00140     }
00141 }
00142 
00143 void ArticleListView::setArticleModel( ArticleModel* model )
00144 {
00145     slotClear();
00146     if ( !model )
00147         return;
00148     m_proxy = new SortColorizeProxyModel( model );
00149     m_proxy->setSourceModel( model );
00150     m_proxy->setSortRole( ArticleModel::SortRole );
00151     FilterDeletedProxyModel* const proxy2 = new FilterDeletedProxyModel( model );
00152     proxy2->setSortRole( ArticleModel::SortRole );
00153     proxy2->setSourceModel( m_proxy );
00154 
00155     FilterColumnsProxyModel* const columnsProxy = new FilterColumnsProxyModel( model );
00156     columnsProxy->setSortRole( ArticleModel::SortRole );
00157     columnsProxy->setSourceModel( proxy2 );
00158     columnsProxy->setColumnEnabled( ArticleModel::ItemTitleColumn );
00159     columnsProxy->setColumnEnabled( ArticleModel::FeedTitleColumn );
00160     columnsProxy->setColumnEnabled( ArticleModel::DateColumn );
00161     columnsProxy->setColumnEnabled( ArticleModel::AuthorColumn );
00162 
00163     setModel( columnsProxy );
00164 
00165     if ( !m_headerSetUp )
00166     {
00167         loadHeaderSettings();
00168         m_headerSetUp = true;
00169     }
00170     header()->setContextMenuPolicy( Qt::CustomContextMenu );
00171 }
00172 
00173 void ArticleListView::showHeaderMenu(const QPoint& pos)
00174 {
00175     if ( !model() )
00176         return;
00177 
00178     QPointer<KMenu> menu = new KMenu( this );
00179     connect( menu, SIGNAL( triggered( QAction* ) ),
00180             this, SLOT( headerMenuItemTriggered( QAction* ) ) );
00181     menu->addTitle( i18n( "Columns" ) );
00182     menu->setAttribute( Qt::WA_DeleteOnClose );
00183 
00184     const int colCount = model()->columnCount();
00185     for ( int i = 0; i < colCount; ++i )
00186     {
00187         QAction* act = menu->addAction( model()->headerData( i, Qt::Horizontal ).toString() );
00188         act->setCheckable( true );
00189         act->setData( i );
00190         act->setChecked( !header()->isSectionHidden( i ) );
00191     }
00192 
00193     menu->popup( header()->mapToGlobal( pos ) );
00194 }
00195 
00196 void ArticleListView::headerMenuItemTriggered( QAction* act )
00197 {
00198     assert( act );
00199     const int col = act->data().toInt();
00200     if ( act->isChecked() )
00201         header()->showSection( col );
00202     else
00203         header()->hideSection( col );
00204 }
00205 
00206 void ArticleListView::saveHeaderSettings()
00207 {
00208     //FIXME: HACK: Change back to saveState() when the Qt-bug is fixed
00209     // is it qt-bug, really? at least saveState is working, but calling the next line causes app to hang.. -Teemu
00210     //Settings::setArticlelistHeaderStates( header()->saveState().toBase64() );
00211 
00212     QByteArray s = header()->saveState();
00213     KConfigGroup conf( Settings::self()->config(), "General" );
00214     conf.writeEntry( "ArticleListHeaders", s.toBase64() );
00215 }
00216 
00217 void ArticleListView::loadHeaderSettings()
00218 {
00219     //FIXME: HACK: change back to loadState+Settings class instead of using KConfigGroup directly..
00220     //QByteArray s = QByteArray::fromBase64( Settings::feedlistHeaderStates().toAscii() ); // this fails currently, I think -Teemu
00221 
00222     KConfigGroup conf( Settings::self()->config(), "General" );
00223     QByteArray s = QByteArray::fromBase64( conf.readEntry( "ArticleListHeaders" ).toAscii() );
00224     if( !s.isNull() )
00225         header()->restoreState( s );
00226 }
00227 
00228 QItemSelectionModel* ArticleListView::articleSelectionModel() const
00229 {
00230     return selectionModel();
00231 }
00232 
00233 const QAbstractItemView* ArticleListView::itemView() const
00234 {
00235     return this;
00236 }
00237 
00238 QAbstractItemView* ArticleListView::itemView()
00239 {
00240     return this;
00241 }
00242 
00243 void ArticleListView::setGroupMode()
00244 {
00245     if ( m_columnMode == GroupMode )
00246         return;
00247     setColumnHidden( ArticleListView::FeedTitleColumn, false );
00248     m_columnMode = GroupMode;
00249 }
00250 
00251 void ArticleListView::setFeedMode()
00252 {
00253     if ( m_columnMode == FeedMode )
00254         return;
00255     setColumnHidden( ArticleListView::FeedTitleColumn, true );
00256     m_columnMode = FeedMode;
00257 }
00258 
00259 ArticleListView::~ArticleListView()
00260 {
00261     saveHeaderSettings();
00262 }
00263 
00264 void ArticleListView::setIsAggregation( bool aggregation )
00265 {
00266     if ( aggregation == m_isAggregation )
00267         return;
00268     m_isAggregation = aggregation;
00269     if ( aggregation )
00270         setGroupMode();
00271     else
00272         setFeedMode();
00273 }
00274 
00275 ArticleListView::ArticleListView( QWidget* parent )
00276     : QTreeView(parent),
00277     m_columnMode( Unspecified ),
00278     m_isAggregation( false ),
00279     m_headerSetUp( false )
00280 {
00281     setSortingEnabled( true );
00282     setAlternatingRowColors( true );
00283     setSelectionMode( QAbstractItemView::ExtendedSelection );
00284     setUniformRowHeights( true );
00285     setRootIsDecorated( false );
00286     setAllColumnsShowFocus(true);
00287 
00288     setMinimumSize( 250, 150 );
00289     setWhatsThis( i18n("<h2>Article list</h2>"
00290         "Here you can browse articles from the currently selected feed. "
00291         "You can also manage articles, as marking them as persistent (\"Keep Article\") or delete them, using the right mouse button menu."
00292         "To view the web page of the article, you can open the article internally in a tab or in an external browser window."));
00293 
00294     connect( header(), SIGNAL( customContextMenuRequested( QPoint ) ), this, SLOT( showHeaderMenu( QPoint ) ) );
00295 }
00296 
00297 void ArticleListView::mousePressEvent( QMouseEvent *ev )
00298 {
00299     // let's push the event, so we can use currentIndex() to get the newly selected article..
00300     QTreeView::mousePressEvent( ev );
00301 
00302     if( ev->button() == Qt::MidButton ) {
00303         QModelIndex idx( currentIndex() );
00304         const KUrl url = currentIndex().data( ArticleModel::LinkRole ).value<KUrl>();
00305 
00306         emit signalMouseButtonPressed( ev->button(), url );
00307     }
00308 }
00309 
00310 
00311 #if 0 // unused
00312 namespace {
00313     static QString itemIdForIndex( const QModelIndex& index )
00314     {
00315         return index.isValid() ? index.data( ArticleModel::ItemIdRole ).toString() : QString();
00316     }
00317 
00318     static QStringList itemIdsForIndexes( const QModelIndexList& indexes )
00319     {
00320         QStringList articles;
00321         Q_FOREACH ( const QModelIndex i, indexes )
00322         {
00323             articles.append( itemIdForIndex( i ) );
00324         }
00325 
00326         return articles;
00327     }
00328 }
00329 #endif
00330 
00331 void ArticleListView::contextMenuEvent( QContextMenuEvent* event )
00332 {
00333     QWidget* w = ActionManager::getInstance()->container( "article_popup" );
00334     QMenu* popup = qobject_cast<QMenu*>( w );
00335     if ( popup )
00336         popup->exec( event->globalPos() );
00337 }
00338 
00339 void ArticleListView::paintEvent( QPaintEvent* e )
00340 {
00341     QTreeView::paintEvent( e );
00342 
00343 #ifdef __GNUC__
00344 #warning The distinction between empty node and 0 items after filtering is hard to port to interview
00345 #endif
00346 #if 0
00347     QString message;
00348 
00349     if ( !model() || model()->rowCount() > 0 ) // article list is not empty
00350     {
00351         if (visibleArticles() == 0)
00352         {
00353             message = i18n("<div align=center>"
00354                             "<h3>No matches</h3>"
00355                             "Filter does not match any articles, "
00356                             "please change your criteria and try again."
00357                             "</div>");
00358         }
00359     }
00360     else if ( !model() ) // article list is empty
00361     {
00362         if (!d->node) // no node selected
00363         {
00364             message = i18n("<div align=center>"
00365                        "<h3>No feed selected</h3>"
00366                        "This area is article list. "
00367                        "Select a feed from the feed list "
00368                        "and you will see its articles here."
00369                        "</div>");
00370         }
00371     }
00372 
00373     if (!message.isNull())
00374         paintInfoBox( message, viewport(), palette() );
00375 #endif
00376 }
00377 
00378 
00379 void ArticleListView::slotClear()
00380 {
00381     QAbstractItemModel* const oldModel = model();
00382     setModel( 0L );
00383     delete oldModel;
00384 }
00385 
00386 void ArticleListView::slotPreviousArticle()
00387 {
00388     if ( !model() )
00389         return;
00390 
00391     const QModelIndex idx = currentIndex();
00392     const int newRow = qMax( 0, ( idx.isValid() ? idx.row() : model()->rowCount() ) - 1 );
00393     const QModelIndex newIdx = idx.isValid() ? idx.sibling( newRow, 0 ) : model()->index( newRow, 0 );
00394     selectIndex( newIdx );
00395 }
00396 
00397 void ArticleListView::slotNextArticle()
00398 {
00399     if ( !model() )
00400         return;
00401 
00402     const QModelIndex idx = currentIndex();
00403     const int newRow = idx.isValid() ? ( idx.row() + 1 ) : 0;
00404     const QModelIndex newIdx = model()->index( qMin( newRow, model()->rowCount() - 1 ), 0 );
00405     selectIndex( newIdx );
00406 }
00407 
00408 void ArticleListView::slotNextUnreadArticle()
00409 {
00410     if (!model())
00411         return;
00412 
00413     const int rowCount = model()->rowCount();
00414     const int startRow = qMin( rowCount - 1, ( currentIndex().isValid() ? currentIndex().row() + 1 : 0 ) );
00415 
00416     int i = startRow;
00417     bool foundUnread = false;
00418 
00419     do
00420     {
00421         if ( !::isRead( model()->index( i, 0 ) ) )
00422             foundUnread = true;
00423         else
00424             i = (i + 1) % rowCount;
00425     }
00426     while ( !foundUnread && i != startRow );
00427 
00428     if ( foundUnread )
00429     {
00430         selectIndex( model()->index( i, 0 ) );
00431     }
00432 }
00433 
00434 void ArticleListView::selectIndex( const QModelIndex& idx )
00435 {
00436     if ( !idx.isValid() )
00437         return;
00438     setCurrentIndex( idx );
00439     clearSelection();
00440     Q_ASSERT( selectionModel() );
00441     selectionModel()->select( idx, QItemSelectionModel::Select | QItemSelectionModel::Rows );
00442     scrollTo( idx, PositionAtCenter );
00443 }
00444 
00445 void ArticleListView::slotPreviousUnreadArticle()
00446 {
00447     if ( !model() )
00448         return;
00449 
00450     const int rowCount = model()->rowCount();
00451     const int startRow = qMax( 0, ( currentIndex().isValid() ? currentIndex().row() : rowCount ) - 1 );
00452 
00453     int i = startRow;
00454     bool foundUnread = false;
00455 
00456     do
00457     {
00458         if ( !::isRead( model()->index( i, 0 ) ) )
00459             foundUnread = true;
00460         else
00461             i = i > 0 ? i - 1 : rowCount - 1;
00462     }
00463     while ( !foundUnread && i != startRow );
00464 
00465     if ( foundUnread )
00466     {
00467         selectIndex( model()->index( i, 0 ) );
00468     }
00469 }
00470 
00471 
00472 void ArticleListView::setFilters( const std::vector<boost::shared_ptr<const Filters::AbstractMatcher> >& matchers )
00473 {
00474     if ( m_proxy )
00475         m_proxy->setFilters( matchers );
00476 }
00477 
00478 #include "articlelistview.moc"

akregator

Skip menu "akregator"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members

kdepim

Skip menu "kdepim"
  • akonadi
  •   clients
  •   kabc
  •   kcal
  •   kcm
  • akregator
  • console
  •   kabcclient
  •   konsolekalendar
  • kaddressbook
  • kalarm
  •   lib
  • kdgantt
  • kdgantt1
  • kjots
  • kleopatra
  • kmail
  • kmobiletools
  • knode
  • knotes
  • kontact
  • kontactinterfaces
  • korganizer
  •   korgac
  • kpilot
  • ktimetracker
  •   doc
  • libkdepim
  • libkholidays
  • libkleo
  • libkpgp
  • maildir
Generated for kdepim by doxygen 1.5.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal