File indexing completed on 2024-05-19 04:48:41
0001 /**************************************************************************************** 0002 * Copyright (c) 2007-2010 Bart Cerneels <bart.cerneels@kde.org> * 0003 * * 0004 * This program is free software; you can redistribute it and/or modify it under * 0005 * the terms of the GNU General Public License as published by the Free Software * 0006 * Foundation; either version 2 of the License, or (at your option) any later * 0007 * version. * 0008 * * 0009 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0010 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0011 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0012 * * 0013 * You should have received a copy of the GNU General Public License along with * 0014 * this program. If not, see <http://www.gnu.org/licenses/>. * 0015 ****************************************************************************************/ 0016 0017 #include "PodcastModel.h" 0018 0019 #include "AmarokMimeData.h" 0020 // #include "context/popupdropper/libpud/PopupDropper.h" 0021 // #include "context/popupdropper/libpud/PopupDropperItem.h" 0022 #include "core/podcasts/PodcastImageFetcher.h" 0023 #include "core/podcasts/PodcastMeta.h" 0024 #include "core/support/Debug.h" 0025 #include "playlistmanager/PlaylistManager.h" 0026 #include "playlistmanager/SyncedPodcast.h" 0027 #include "PodcastCategory.h" 0028 #include "SvgHandler.h" 0029 #include "widgets/PrettyTreeRoles.h" 0030 0031 #include <QAction> 0032 #include <QIcon> 0033 #include <QInputDialog> 0034 #include <QListIterator> 0035 #include <QPainter> 0036 0037 #include <KIconEngine> 0038 0039 using namespace Podcasts; 0040 0041 namespace The 0042 { 0043 PlaylistBrowserNS::PodcastModel* podcastModel() 0044 { 0045 return PlaylistBrowserNS::PodcastModel::instance(); 0046 } 0047 } 0048 0049 PlaylistBrowserNS::PodcastModel* PlaylistBrowserNS::PodcastModel::s_instance = nullptr; 0050 0051 PlaylistBrowserNS::PodcastModel* 0052 PlaylistBrowserNS::PodcastModel::instance() 0053 { 0054 return s_instance ? s_instance : new PodcastModel(); 0055 } 0056 0057 void 0058 PlaylistBrowserNS::PodcastModel::destroy() 0059 { 0060 if ( s_instance ) 0061 { 0062 delete s_instance; 0063 s_instance = nullptr; 0064 } 0065 } 0066 0067 PlaylistBrowserNS::PodcastModel::PodcastModel() 0068 : PlaylistBrowserModel( PlaylistManager::PodcastChannel ) 0069 { 0070 s_instance = this; 0071 } 0072 0073 bool 0074 PlaylistBrowserNS::PodcastModel::isOnDisk( PodcastEpisodePtr episode ) const 0075 { 0076 bool isOnDisk = false; 0077 QUrl episodeFile( episode->localUrl() ); 0078 0079 if( !episodeFile.isEmpty() ) 0080 { 0081 isOnDisk = QFileInfo( episodeFile.toLocalFile() ).exists(); 0082 // reset localUrl because the file is not there. 0083 // FIXME: changing a podcast in innocent-looking getter method is convoluted 0084 if( !isOnDisk ) 0085 episode->setLocalUrl( QUrl() ); 0086 } 0087 0088 return isOnDisk; 0089 } 0090 0091 QVariant 0092 PlaylistBrowserNS::PodcastModel::icon( const PodcastChannelPtr &channel ) const 0093 { 0094 QStringList emblems; 0095 //TODO: only check visible episodes. For now those are all returned by episodes(). 0096 foreach( const Podcasts::PodcastEpisodePtr ep, channel->episodes() ) 0097 { 0098 if( ep->isNew() ) 0099 { 0100 emblems << QStringLiteral("rating"); 0101 break; 0102 } 0103 } 0104 0105 if( channel->hasImage() ) 0106 { 0107 QSize size( channel->image().size() ); 0108 QPixmap pixmap( 32, 32 ); 0109 pixmap.fill( Qt::transparent ); 0110 0111 size.scale( 32, 32, Qt::KeepAspectRatio ); 0112 0113 int x = 32 / 2 - size.width() / 2; 0114 int y = 32 / 2 - size.height() / 2; 0115 0116 QPainter p( &pixmap ); 0117 p.drawPixmap( x, y, QPixmap::fromImage( channel->image().scaled( size, 0118 Qt::KeepAspectRatio, Qt::SmoothTransformation ) ) ); 0119 0120 // if it's a new episode draw the overlay: 0121 if( !emblems.isEmpty() ) 0122 // draw the overlay the same way KIconLoader does: 0123 p.drawPixmap( 2, 32 - 16 - 2, QIcon::fromTheme( QStringLiteral("rating") ).pixmap( 16, 16 ) ); 0124 p.end(); 0125 0126 return pixmap; 0127 } 0128 else 0129 return ( QIcon(new KIconEngine( "podcast-amarok", nullptr, emblems )).pixmap( 32, 32 ) ); 0130 } 0131 0132 QVariant 0133 PlaylistBrowserNS::PodcastModel::icon( const PodcastEpisodePtr &episode ) const 0134 { 0135 QStringList emblems; 0136 if( isOnDisk( episode ) ) 0137 emblems << QStringLiteral("go-down"); 0138 0139 if( episode->isNew() ) 0140 return ( QIcon( new KIconEngine( "rating", nullptr, emblems )).pixmap( 24, 24 ) ); 0141 else 0142 return ( QIcon( new KIconEngine( "podcast-amarok", nullptr, emblems )).pixmap( 24, 24 )); 0143 } 0144 0145 QVariant 0146 PlaylistBrowserNS::PodcastModel::data( const QModelIndex &idx, int role ) const 0147 { 0148 if( !idx.isValid() ) 0149 return PlaylistBrowserModel::data( idx, role ); 0150 0151 if( IS_TRACK(idx) ) 0152 return episodeData( episodeForIndex( idx ), idx, role ); 0153 else 0154 return channelData( channelForIndex( idx ), idx, role ); 0155 } 0156 0157 QVariant 0158 PlaylistBrowserNS::PodcastModel::channelData( const PodcastChannelPtr &channel, 0159 const QModelIndex &idx, int role ) const 0160 { 0161 if( !channel ) 0162 return QVariant(); 0163 0164 switch( role ) 0165 { 0166 case Qt::DisplayRole: 0167 case Qt::ToolTipRole: 0168 switch( idx.column() ) 0169 { 0170 case PlaylistBrowserModel::PlaylistItemColumn: 0171 return channel->title(); 0172 case SubtitleColumn: 0173 return channel->subtitle(); 0174 case AuthorColumn: 0175 return channel->author(); 0176 case KeywordsColumn: 0177 return channel->keywords(); 0178 case ImageColumn: 0179 { 0180 QUrl imageUrl( PodcastImageFetcher::cachedImagePath( channel ) ); 0181 if( !QFile( imageUrl.toLocalFile() ).exists() ) 0182 imageUrl = channel->imageUrl(); 0183 return imageUrl; 0184 } 0185 case DateColumn: 0186 return channel->subscribeDate(); 0187 case IsEpisodeColumn: 0188 return false; 0189 } 0190 break; 0191 case PrettyTreeRoles::ByLineRole: 0192 if( idx.column() == PlaylistBrowserModel::ProviderColumn ) 0193 { 0194 Playlists::PlaylistProvider *provider = providerForIndex( idx ); 0195 if( provider ) 0196 return i18ncp( "number of podcasts from one source", 0197 "One Channel", "%1 channels", 0198 provider->playlists().count() ); 0199 } 0200 if( idx.column() == PlaylistBrowserModel::PlaylistItemColumn ) 0201 return channel->description(); 0202 break; 0203 case PrettyTreeRoles::HasCoverRole: 0204 return idx.column() == PlaylistBrowserModel::PlaylistItemColumn; 0205 case Qt::DecorationRole: 0206 if( idx.column() == PlaylistBrowserModel::PlaylistItemColumn ) 0207 return icon( channel ); 0208 break; 0209 } 0210 0211 return PlaylistBrowserModel::data( idx, role ); 0212 } 0213 0214 QVariant 0215 PlaylistBrowserNS::PodcastModel::episodeData( const PodcastEpisodePtr &episode, 0216 const QModelIndex &idx, int role ) const 0217 { 0218 if( !episode ) 0219 return QVariant(); 0220 0221 switch( role ) 0222 { 0223 case Qt::DisplayRole: 0224 case Qt::ToolTipRole: 0225 switch( idx.column() ) 0226 { 0227 case PlaylistBrowserModel::PlaylistItemColumn: 0228 return episode->title(); 0229 case SubtitleColumn: 0230 return episode->subtitle(); 0231 case AuthorColumn: 0232 return episode->author(); 0233 case KeywordsColumn: 0234 return episode->keywords(); 0235 case FilesizeColumn: 0236 return episode->filesize(); 0237 case DateColumn: 0238 return episode->pubDate(); 0239 case IsEpisodeColumn: 0240 return true; 0241 } 0242 break; 0243 case PrettyTreeRoles::ByLineRole: 0244 if( idx.column() == PlaylistBrowserModel::ProviderColumn ) 0245 { 0246 Playlists::PlaylistProvider *provider = providerForIndex( idx ); 0247 if( provider ) 0248 return i18ncp( "number of podcasts from one source", 0249 "One Channel", "%1 channels", 0250 provider->playlists().count() ); 0251 } 0252 if( idx.column() == PlaylistBrowserModel::PlaylistItemColumn ) 0253 return episode->description(); 0254 break; 0255 case PrettyTreeRoles::HasCoverRole: 0256 return ( idx.column() == PlaylistBrowserModel::PlaylistItemColumn ); 0257 case Qt::DecorationRole: 0258 if( idx.column() == PlaylistBrowserModel::PlaylistItemColumn ) 0259 return icon( episode ); 0260 break; 0261 case EpisodeIsNewRole: 0262 return episode->isNew(); 0263 } 0264 0265 return PlaylistBrowserModel::data( idx, role ); 0266 } 0267 0268 bool 0269 PlaylistBrowserNS::PodcastModel::setData( const QModelIndex &idx, const QVariant &value, int role ) 0270 { 0271 PodcastEpisodePtr episode = episodeForIndex( idx ); 0272 if( !episode || !value.canConvert<bool>() || role != EpisodeIsNewRole ) 0273 { 0274 return PlaylistBrowserModel::setData( idx, value, role ); 0275 } 0276 0277 bool checked = value.toBool(); 0278 episode->setNew( checked ); 0279 if( checked ) 0280 Q_EMIT episodeMarkedAsNew( episode ); 0281 Q_EMIT dataChanged( idx, idx ); 0282 return true; 0283 } 0284 0285 int 0286 PlaylistBrowserNS::PodcastModel::columnCount( const QModelIndex &parent ) const 0287 { 0288 Q_UNUSED( parent ) 0289 return ColumnCount; 0290 } 0291 0292 QVariant 0293 PlaylistBrowserNS::PodcastModel::headerData( int section, Qt::Orientation orientation, 0294 int role) const 0295 { 0296 if( orientation == Qt::Horizontal && role == Qt::DisplayRole ) 0297 { 0298 switch( section ) 0299 { 0300 case 0: return i18n("Type"); 0301 case 1: return i18n("Title"); 0302 case 2: return i18n("Summary"); 0303 default: return QVariant(); 0304 } 0305 } 0306 0307 return QVariant(); 0308 } 0309 0310 void 0311 PlaylistBrowserNS::PodcastModel::addPodcast() 0312 { 0313 debug() << "adding Podcast"; 0314 0315 //TODO: request the user to which PodcastProvider he wants to add it in case 0316 // of multiple (enabled) Podcast Providers. 0317 Podcasts::PodcastProvider *podcastProvider = The::playlistManager()->defaultPodcasts(); 0318 if( podcastProvider ) 0319 { 0320 bool ok; 0321 QString url = QInputDialog::getText( nullptr, 0322 i18n("Add Podcast"), 0323 i18n("Enter RSS 1.0/2.0 or Atom feed URL:"), 0324 QLineEdit::Normal, 0325 QString(), 0326 &ok ); 0327 if( ok && !url.isEmpty() ) 0328 { 0329 // user entered something and pressed OK 0330 podcastProvider->addPodcast( Podcasts::PodcastProvider::toFeedUrl( url.trimmed() ) ); 0331 } 0332 else 0333 { 0334 // user entered nothing or pressed Cancel 0335 debug() << "invalid input or cancel"; 0336 } 0337 } 0338 else 0339 { 0340 debug() << "PodcastChannel provider is null"; 0341 } 0342 0343 } 0344 0345 void 0346 PlaylistBrowserNS::PodcastModel::refreshPodcasts() 0347 { 0348 foreach( Playlists::PlaylistProvider *provider, 0349 The::playlistManager()->providersForCategory( PlaylistManager::PodcastChannel ) ) 0350 { 0351 PodcastProvider *podcastProvider = dynamic_cast<PodcastProvider *>( provider ); 0352 if( podcastProvider ) 0353 podcastProvider->updateAll(); 0354 } 0355 } 0356 0357 Podcasts::PodcastChannelPtr 0358 PlaylistBrowserNS::PodcastModel::channelForIndex( const QModelIndex &idx ) const 0359 { 0360 return Podcasts::PodcastChannelPtr::dynamicCast( playlistFromIndex( idx ) ); 0361 } 0362 0363 Podcasts::PodcastEpisodePtr 0364 PlaylistBrowserNS::PodcastModel::episodeForIndex( const QModelIndex &idx ) const 0365 { 0366 return Podcasts::PodcastEpisodePtr::dynamicCast( trackFromIndex( idx ) ); 0367 } 0368 0369 Meta::TrackList 0370 PlaylistBrowserNS::PodcastModel::podcastEpisodesToTracks( Podcasts::PodcastEpisodeList episodes ) 0371 { 0372 Meta::TrackList tracks; 0373 foreach( Podcasts::PodcastEpisodePtr episode, episodes ) 0374 tracks << Meta::TrackPtr::staticCast( episode ); 0375 return tracks; 0376 }