QGIS API Documentation 3.99.0-Master (09f76ad7019)
Loading...
Searching...
No Matches
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"
16
18
19#include <QPainter>
20
21#include "moc_qgsnewsfeedmodel.cpp"
22
23//
24// QgsNewsFeedModel
25//
26
28 : QAbstractItemModel( parent )
29 , mParser( parser )
30{
31 Q_ASSERT( mParser );
32 const QList< QgsNewsFeedParser::Entry > initialEntries = mParser->entries();
33 for ( const QgsNewsFeedParser::Entry &e : initialEntries )
34 onEntryAdded( e );
35
36 connect( mParser, &QgsNewsFeedParser::entryAdded, this, &QgsNewsFeedModel::onEntryAdded );
37 connect( mParser, &QgsNewsFeedParser::entryDismissed, this, &QgsNewsFeedModel::onEntryRemoved );
38 connect( mParser, &QgsNewsFeedParser::entryUpdated, this, &QgsNewsFeedModel::onEntryUpdated );
39 connect( mParser, &QgsNewsFeedParser::imageFetched, this, &QgsNewsFeedModel::onImageFetched );
40}
41
42QVariant QgsNewsFeedModel::data( const QModelIndex &index, int role ) const
43{
44 if ( index.row() < 0 || index.row() >= rowCount( QModelIndex() ) )
45 return QVariant();
46
47 const QgsNewsFeedParser::Entry &entry = mEntries.at( index.row() );
48
49 switch ( role )
50 {
51 case Qt::DisplayRole:
52 case static_cast< int >( CustomRole::Content ):
53 return entry.content;
54
55 case Qt::ToolTipRole:
56 case static_cast< int >( CustomRole::Title ):
57 return entry.title;
58
59 case static_cast< int >( CustomRole::Key ):
60 return entry.key;
61
62 case static_cast< int >( CustomRole::ImageUrl ):
63 return entry.imageUrl;
64
65 case static_cast< int >( CustomRole::Image ):
66 return entry.image;
67
68 case static_cast< int >( CustomRole::Link ):
69 return entry.link;
70
71 case static_cast< int >( CustomRole::Sticky ):
72 return entry.sticky;
73
74 case Qt::DecorationRole:
75 if ( entry.image.isNull() )
76 return QVariant();
77 return entry.image;
78 }
79 return QVariant();
80}
81
82QHash<int, QByteArray> QgsNewsFeedModel::roleNames() const
83{
84 QHash<int, QByteArray> roles;
85 roles[static_cast< int >( CustomRole::Key )] = "Key";
86 roles[static_cast< int >( CustomRole::Title )] = "Title";
87 roles[static_cast< int >( CustomRole::Content )] = "Content";
88 roles[static_cast< int >( CustomRole::ImageUrl )] = "ImageUrl";
89 roles[static_cast< int >( CustomRole::Link )] = "Link";
90 roles[static_cast< int >( CustomRole::Sticky )] = "Sticky";
91 return roles;
92}
93
94Qt::ItemFlags QgsNewsFeedModel::flags( const QModelIndex &index ) const
95{
96 Qt::ItemFlags flags = QAbstractItemModel::flags( index );
97 return flags;
98}
99
100QModelIndex QgsNewsFeedModel::index( int row, int column, const QModelIndex &parent ) const
101{
102 if ( !hasIndex( row, column, parent ) )
103 return QModelIndex();
104
105 if ( !parent.isValid() )
106 {
107 return createIndex( row, column );
108 }
109
110 return QModelIndex();
111}
112
113QModelIndex QgsNewsFeedModel::parent( const QModelIndex & ) const
114{
115 //all items are top level for now
116 return QModelIndex();
117}
118
119int QgsNewsFeedModel::rowCount( const QModelIndex &parent ) const
120{
121 if ( !parent.isValid() )
122 {
123 return mEntries.count();
124 }
125 return 0;
126}
127
128int QgsNewsFeedModel::columnCount( const QModelIndex & ) const
129{
130 return 1;
131}
132
133void QgsNewsFeedModel::onEntryAdded( const QgsNewsFeedParser::Entry &entry )
134{
135 beginInsertRows( QModelIndex(), mEntries.count(), mEntries.count() );
136 mEntries.append( entry );
137 endInsertRows();
138}
139
140void QgsNewsFeedModel::onEntryUpdated( const QgsNewsFeedParser::Entry &entry )
141{
142 for ( int idx = 0; idx < mEntries.count(); idx++ )
143 {
144 if ( mEntries.at( idx ).key == entry.key )
145 {
146 mEntries[ idx ] = entry;
147 emit dataChanged( index( idx, 0 ), index( idx, 0 ) );
148 break;
149 }
150 }
151}
152
153void QgsNewsFeedModel::onEntryRemoved( const QgsNewsFeedParser::Entry &entry )
154{
155 // find index of entry
156 const auto findIter = std::find_if( mEntries.begin(), mEntries.end(), [entry]( const QgsNewsFeedParser::Entry & candidate )
157 {
158 return candidate.key == entry.key;
159 } );
160 if ( findIter == mEntries.end() )
161 return;
162
163 const int entryIndex = static_cast< int >( std::distance( mEntries.begin(), findIter ) );
164 beginRemoveRows( QModelIndex(), entryIndex, entryIndex );
165 mEntries.removeAt( entryIndex );
166 endRemoveRows();
167}
168
169void QgsNewsFeedModel::onImageFetched( const int key, const QPixmap &pixmap )
170{
171 // find index of entry
172 const auto findIter = std::find_if( mEntries.begin(), mEntries.end(), [key]( const QgsNewsFeedParser::Entry & candidate )
173 {
174 return candidate.key == key;
175 } );
176 if ( findIter == mEntries.end() )
177 return;
178
179 const int entryIndex = static_cast< int >( std::distance( mEntries.begin(), findIter ) );
180 mEntries[ entryIndex ].image = pixmap;
181 emit dataChanged( index( entryIndex, 0, QModelIndex() ), index( entryIndex, 0, QModelIndex() ) );
182}
183
184
185//
186// QgsNewsFeedProxyModel
187//
188
190 : QSortFilterProxyModel( parent )
191{
192 mModel = new QgsNewsFeedModel( parser, this );
193 setSortCaseSensitivity( Qt::CaseInsensitive );
194 setSourceModel( mModel );
195 setDynamicSortFilter( true );
196 sort( 0 );
197}
198
199bool QgsNewsFeedProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
200{
201 const bool leftSticky = sourceModel()->data( left, static_cast< int >( QgsNewsFeedModel::CustomRole::Sticky ) ).toBool();
202 const bool rightSticky = sourceModel()->data( right, static_cast< int >( QgsNewsFeedModel::CustomRole::Sticky ) ).toBool();
203
204 // sticky items come first
205 if ( leftSticky && !rightSticky )
206 return true;
207 if ( rightSticky && !leftSticky )
208 return false;
209
210 // else sort by descending key
211 const int leftKey = sourceModel()->data( left, static_cast< int >( QgsNewsFeedModel::CustomRole::Key ) ).toInt();
212 const int rightKey = sourceModel()->data( right, static_cast< int >( QgsNewsFeedModel::CustomRole::Key ) ).toInt();
213 return rightKey < leftKey;
214}
A model for published QGIS news feeds.
@ ImageUrl
Optional entry image URL.
@ 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.
QHash< int, QByteArray > roleNames() const override
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...
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
QgsNewsFeedProxyModel(QgsNewsFeedParser *parser, QObject *parent=nullptr)
Constructor for QgsNewsFeedProxyModel, with the specified parent object.