QGIS API Documentation 3.37.0-Master (fa0c88a90b0)
qgsnewsfeedmodel.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsnewsfeedmodel.cpp
3 -------------------
4 begin : July 2019
5 copyright : (C) 2019 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15#include "qgsnewsfeedmodel.h"
17#include <QPainter>
18
19//
20// QgsNewsFeedModel
21//
22
24 : QAbstractItemModel( parent )
25 , mParser( parser )
26{
27 Q_ASSERT( mParser );
28 const QList< QgsNewsFeedParser::Entry > initialEntries = mParser->entries();
29 for ( const QgsNewsFeedParser::Entry &e : initialEntries )
30 onEntryAdded( e );
31
32 connect( mParser, &QgsNewsFeedParser::entryAdded, this, &QgsNewsFeedModel::onEntryAdded );
33 connect( mParser, &QgsNewsFeedParser::entryDismissed, this, &QgsNewsFeedModel::onEntryRemoved );
34 connect( mParser, &QgsNewsFeedParser::entryUpdated, this, &QgsNewsFeedModel::onEntryUpdated );
35 connect( mParser, &QgsNewsFeedParser::imageFetched, this, &QgsNewsFeedModel::onImageFetched );
36}
37
38QVariant QgsNewsFeedModel::data( const QModelIndex &index, int role ) const
39{
40 if ( index.row() < 0 || index.row() >= rowCount( QModelIndex() ) )
41 return QVariant();
42
43 const QgsNewsFeedParser::Entry &entry = mEntries.at( index.row() );
44
45 switch ( role )
46 {
47 case Qt::DisplayRole:
48 case static_cast< int >( CustomRole::Content ):
49 return entry.content;
50
51 case Qt::ToolTipRole:
52 case static_cast< int >( CustomRole::Title ):
53 return entry.title;
54
55 case static_cast< int >( CustomRole::Key ):
56 return entry.key;
57
58 case static_cast< int >( CustomRole::ImageUrl ):
59 return entry.imageUrl;
60
61 case static_cast< int >( CustomRole::Image ):
62 return entry.image;
63
64 case static_cast< int >( CustomRole::Link ):
65 return entry.link;
66
67 case static_cast< int >( CustomRole::Sticky ):
68 return entry.sticky;
69
70 case Qt::DecorationRole:
71 if ( entry.image.isNull() )
72 return QVariant();
73 return entry.image;
74 }
75 return QVariant();
76}
77
78Qt::ItemFlags QgsNewsFeedModel::flags( const QModelIndex &index ) const
79{
80 Qt::ItemFlags flags = QAbstractItemModel::flags( index );
81 return flags;
82}
83
84QModelIndex QgsNewsFeedModel::index( int row, int column, const QModelIndex &parent ) const
85{
86 if ( !hasIndex( row, column, parent ) )
87 return QModelIndex();
88
89 if ( !parent.isValid() )
90 {
91 return createIndex( row, column );
92 }
93
94 return QModelIndex();
95}
96
97QModelIndex QgsNewsFeedModel::parent( const QModelIndex & ) const
98{
99 //all items are top level for now
100 return QModelIndex();
101}
102
103int QgsNewsFeedModel::rowCount( const QModelIndex &parent ) const
104{
105 if ( !parent.isValid() )
106 {
107 return mEntries.count();
108 }
109 return 0;
110}
111
112int QgsNewsFeedModel::columnCount( const QModelIndex & ) const
113{
114 return 1;
115}
116
117void QgsNewsFeedModel::onEntryAdded( const QgsNewsFeedParser::Entry &entry )
118{
119 beginInsertRows( QModelIndex(), mEntries.count(), mEntries.count() );
120 mEntries.append( entry );
121 endInsertRows();
122}
123
124void QgsNewsFeedModel::onEntryUpdated( const QgsNewsFeedParser::Entry &entry )
125{
126 for ( int idx = 0; idx < mEntries.count(); idx++ )
127 {
128 if ( mEntries.at( idx ).key == entry.key )
129 {
130 mEntries[ idx ] = entry;
131 emit dataChanged( index( idx, 0 ), index( idx, 0 ) );
132 break;
133 }
134 }
135}
136
137void QgsNewsFeedModel::onEntryRemoved( const QgsNewsFeedParser::Entry &entry )
138{
139 // find index of entry
140 const auto findIter = std::find_if( mEntries.begin(), mEntries.end(), [entry]( const QgsNewsFeedParser::Entry & candidate )
141 {
142 return candidate.key == entry.key;
143 } );
144 if ( findIter == mEntries.end() )
145 return;
146
147 const int entryIndex = static_cast< int >( std::distance( mEntries.begin(), findIter ) );
148 beginRemoveRows( QModelIndex(), entryIndex, entryIndex );
149 mEntries.removeAt( entryIndex );
150 endRemoveRows();
151}
152
153void QgsNewsFeedModel::onImageFetched( const int key, const QPixmap &pixmap )
154{
155 // find index of entry
156 const auto findIter = std::find_if( mEntries.begin(), mEntries.end(), [key]( const QgsNewsFeedParser::Entry & candidate )
157 {
158 return candidate.key == key;
159 } );
160 if ( findIter == mEntries.end() )
161 return;
162
163 const int entryIndex = static_cast< int >( std::distance( mEntries.begin(), findIter ) );
164 mEntries[ entryIndex ].image = pixmap;
165 emit dataChanged( index( entryIndex, 0, QModelIndex() ), index( entryIndex, 0, QModelIndex() ) );
166}
167
168
169//
170// QgsNewsFeedProxyModel
171//
172
174 : QSortFilterProxyModel( parent )
175{
176 mModel = new QgsNewsFeedModel( parser, this );
177 setSortCaseSensitivity( Qt::CaseInsensitive );
178 setSourceModel( mModel );
179 setDynamicSortFilter( true );
180 sort( 0 );
181}
182
183bool QgsNewsFeedProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
184{
185 const bool leftSticky = sourceModel()->data( left, static_cast< int >( QgsNewsFeedModel::CustomRole::Sticky ) ).toBool();
186 const bool rightSticky = sourceModel()->data( right, static_cast< int >( QgsNewsFeedModel::CustomRole::Sticky ) ).toBool();
187
188 // sticky items come first
189 if ( leftSticky && !rightSticky )
190 return true;
191 if ( rightSticky && !leftSticky )
192 return false;
193
194 // else sort by descending key
195 const int leftKey = sourceModel()->data( left, static_cast< int >( QgsNewsFeedModel::CustomRole::Key ) ).toInt();
196 const int rightKey = sourceModel()->data( right, static_cast< int >( QgsNewsFeedModel::CustomRole::Key ) ).toInt();
197 return rightKey < leftKey;
198}
A model for published QGIS news feeds.
@ ImageUrl
Optional entry image URL.
@ Key
Entry unique key.
@ Link
Optional entry URL link.
@ Image
Optional entry image.
@ Sticky
Whether entry is sticky.
int columnCount(const QModelIndex &parent=QModelIndex()) const override
QVariant data(const QModelIndex &index, int role) const override
Qt::ItemFlags flags(const QModelIndex &index) const override
QgsNewsFeedModel(QgsNewsFeedParser *parser, QObject *parent=nullptr)
Constructor for QgsNewsFeedModel, with the specified parent object.
QModelIndex parent(const QModelIndex &index) const override
int rowCount(const QModelIndex &parent=QModelIndex()) const override
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
Represents a single entry from a news feed.
QString content
HTML content of news entry.
bool sticky
true if entry is "sticky" and should always be shown at the top
QUrl link
Optional URL link for entry.
QString imageUrl
Optional URL for image associated with entry.
QPixmap image
Optional image data.
int key
Unique entry identifier.
QString title
Entry title.
Parser for published QGIS news feeds.
void entryDismissed(const QgsNewsFeedParser::Entry &entry)
Emitted whenever an entry is dismissed (as a result of a call to dismissEntry()).
void entryUpdated(const QgsNewsFeedParser::Entry &entry)
Emitted whenever an existing entry is available from the feed (as a result of a call to fetch()).
void entryAdded(const QgsNewsFeedParser::Entry &entry)
Emitted whenever a new entry is available from the feed (as a result of a call to fetch()).
void imageFetched(int key, const QPixmap &pixmap)
Emitted when the image attached to the entry with the specified key has been fetched and is now avail...
QList< QgsNewsFeedParser::Entry > entries() const
Returns a list of existing entries in the feed.
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
QgsNewsFeedProxyModel(QgsNewsFeedParser *parser, QObject *parent=nullptr)
Constructor for QgsNewsFeedProxyModel, with the specified parent object.