1 #include "playlistmodel.h"
2 #include "videomimedata.h"
3 #include "videosource.h"
6 #include "searchparams.h"
9 static const int maxItems = 10;
10 static const QString recentKeywordsKey = "recentKeywords";
11 static const QString recentChannelsKey = "recentChannels";
13 PlaylistModel::PlaylistModel(QWidget *parent) : QAbstractListModel(parent) {
23 authorHovered = false;
24 authorPressed = false;
27 int PlaylistModel::rowCount(const QModelIndex &/*parent*/) const {
28 int count = videos.size();
30 // add the message item
31 if (videos.isEmpty() || !searching)
37 QVariant PlaylistModel::data(const QModelIndex &index, int role) const {
39 int row = index.row();
41 if (row == videos.size()) {
45 boldFont.setBold(true);
49 return ItemTypeShowMore;
51 case Qt::StatusTipRole:
52 if (!errorMessage.isEmpty()) return errorMessage;
53 if (searching) return tr("Searching...");
54 if (canSearchMore) return tr("Show %1 More").arg(maxItems);
55 if (videos.isEmpty()) return tr("No videos");
56 else return tr("No more videos");
57 case Qt::TextAlignmentRole:
58 return QVariant(int(Qt::AlignHCenter | Qt::AlignVCenter));
59 case Qt::ForegroundRole:
60 if (!errorMessage.isEmpty())
61 return palette.color(QPalette::ToolTipText);
63 return palette.color(QPalette::Dark);
64 case Qt::BackgroundColorRole:
65 if (!errorMessage.isEmpty())
66 return palette.color(QPalette::ToolTipBase);
75 } else if (row < 0 || row >= videos.size())
78 Video *video = videos.at(row);
84 return QVariant::fromValue(QPointer<Video>(video));
86 return video == m_activeVideo;
88 return video->title();
90 return hoveredRow == index.row();
91 case AuthorHoveredRole:
93 case AuthorPressedRole:
100 void PlaylistModel::setActiveRow(int row, bool notify) {
101 if ( rowExists( row ) ) {
104 m_activeVideo = videoAt(row);
106 int oldactiverow = m_activeRow;
108 if ( rowExists( oldactiverow ) )
109 emit dataChanged( createIndex( oldactiverow, 0 ), createIndex( oldactiverow, columnCount() - 1 ) );
111 emit dataChanged( createIndex( m_activeRow, 0 ), createIndex( m_activeRow, columnCount() - 1 ) );
112 if (notify) emit activeRowChanged(row);
121 int PlaylistModel::nextRow() const {
122 int nextRow = m_activeRow + 1;
123 if (rowExists(nextRow))
128 int PlaylistModel::previousRow() const {
129 int prevRow = m_activeRow - 1;
130 if (rowExists(prevRow))
135 Video* PlaylistModel::videoAt( int row ) const {
136 if ( rowExists( row ) )
137 return videos.at( row );
141 Video* PlaylistModel::activeVideo() const {
142 return m_activeVideo;
145 void PlaylistModel::setVideoSource(VideoSource *videoSource) {
146 while (!videos.isEmpty())
147 delete videos.takeFirst();
154 this->videoSource = videoSource;
155 connect(videoSource, SIGNAL(gotVideos(QList<Video*>)),
156 SLOT(addVideos(QList<Video*>)), Qt::UniqueConnection);
157 connect(videoSource, SIGNAL(finished(int)),
158 SLOT(searchFinished(int)), Qt::UniqueConnection);
159 connect(videoSource, SIGNAL(error(QString)),
160 SLOT(searchError(QString)), Qt::UniqueConnection);
165 void PlaylistModel::searchMore(int max) {
166 if (searching) return;
168 firstSearch = skip == 1;
170 errorMessage.clear();
171 videoSource->loadVideos(max, skip);
175 void PlaylistModel::searchMore() {
176 searchMore(maxItems);
179 void PlaylistModel::searchNeeded() {
180 int remainingRows = videos.size() - m_activeRow;
181 int rowsNeeded = maxItems - remainingRows;
183 searchMore(rowsNeeded);
186 void PlaylistModel::abortSearch() {
187 while (!videos.isEmpty())
188 delete videos.takeFirst();
190 videoSource->abort();
194 void PlaylistModel::searchFinished(int total) {
196 canSearchMore = total >= max;
198 // update the message item
199 emit dataChanged( createIndex( maxItems, 0 ), createIndex( maxItems, columnCount() - 1 ) );
201 if (!videoSource->getSuggestions().isEmpty())
202 emit haveSuggestions(videoSource->getSuggestions());
204 if (firstSearch && !videos.isEmpty())
205 handleFirstVideo(videos.first());
208 void PlaylistModel::searchError(QString message) {
209 errorMessage = message;
210 // update the message item
211 emit dataChanged( createIndex( maxItems, 0 ), createIndex( maxItems, columnCount() - 1 ) );
214 void PlaylistModel::addVideos(QList<Video*> newVideos) {
215 if (newVideos.isEmpty()) return;
217 bool isFirstVideo = videos.isEmpty();
219 beginInsertRows(QModelIndex(), videos.size(), videos.size() + newVideos.size() - 1);
220 videos.append(newVideos);
223 foreach (Video* video, newVideos) {
224 connect(video, SIGNAL(gotThumbnail()),
225 SLOT(updateThumbnail()), Qt::UniqueConnection);
226 video->loadThumbnail();
229 // if (isFirstVideo) handleFirstVideo(newVideos.first());
232 void PlaylistModel::handleFirstVideo(Video *video) {
234 int currentVideoRow = rowForCloneVideo(MediaView::instance()->getCurrentVideo());
235 if (currentVideoRow != -1) setActiveRow(currentVideoRow, false);
238 if (!settings.value("manualplay", false).toBool())
243 if (!settings.value("manualplay", false).toBool()) {
244 int newActiveRow = rowForCloneVideo(MediaView::instance()->getCurrentVideo());
245 if (newActiveRow != -1) setActiveRow(newActiveRow, false);
246 else setActiveRow(0);
249 if (videoSource->metaObject()->className() == QLatin1String("YTSearch")) {
251 static const int maxRecentElements = 10;
253 YTSearch *search = dynamic_cast<YTSearch *>(videoSource);
254 SearchParams *searchParams = search->getSearchParams();
257 QString query = searchParams->keywords();
258 if (!query.isEmpty() && !searchParams->isTransient()) {
259 if (query.startsWith("http://")) {
260 // Save the video title
261 query += "|" + videos.first()->title();
263 QStringList keywords = settings.value(recentKeywordsKey).toStringList();
264 keywords.removeAll(query);
265 keywords.prepend(query);
266 while (keywords.size() > maxRecentElements)
267 keywords.removeLast();
268 settings.setValue(recentKeywordsKey, keywords);
272 QString channel = searchParams->author();
273 if (!channel.isEmpty() && !searchParams->isTransient()) {
275 if (!video->authorUri().isEmpty() && video->authorUri() != video->author())
276 value = video->authorUri() + "|" + video->author();
277 else value = video->author();
278 QStringList channels = settings.value(recentChannelsKey).toStringList();
279 channels.removeAll(value);
280 channels.removeAll(channel);
281 channels.prepend(value);
282 while (channels.size() > maxRecentElements)
283 channels.removeLast();
284 settings.setValue(recentChannelsKey, channels);
289 void PlaylistModel::updateThumbnail() {
291 Video *video = static_cast<Video *>(sender());
293 qDebug() << "Cannot get sender";
297 int row = rowForVideo(video);
298 emit dataChanged( createIndex( row, 0 ), createIndex( row, columnCount() - 1 ) );
305 * This function does not free memory
307 bool PlaylistModel::removeRows(int position, int rows, const QModelIndex & /*parent*/) {
308 beginRemoveRows(QModelIndex(), position, position+rows-1);
309 for (int row = 0; row < rows; ++row) {
310 videos.removeAt(position);
316 void PlaylistModel::removeIndexes(QModelIndexList &indexes) {
317 QList<Video*> originalList(videos);
318 QList<Video*> delitems;
319 foreach (QModelIndex index, indexes) {
320 if (index.row() >= originalList.size()) continue;
321 Video* video = originalList.at(index.row());
322 int idx = videos.indexOf(video);
324 beginRemoveRows(QModelIndex(), idx, idx);
325 delitems.append(video);
326 videos.removeAll(video);
331 qDeleteAll(delitems);
335 // --- Sturm und drang ---
339 Qt::DropActions PlaylistModel::supportedDropActions() const {
340 return Qt::CopyAction;
343 Qt::DropActions PlaylistModel::supportedDragActions() const {
344 return Qt::CopyAction;
347 Qt::ItemFlags PlaylistModel::flags(const QModelIndex &index) const {
348 if (index.isValid()) {
349 if (index.row() == videos.size()) {
350 // don't drag the "show 10 more" item
351 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
352 } else return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
354 return Qt::ItemIsDropEnabled;
357 QStringList PlaylistModel::mimeTypes() const {
359 types << "application/x-minitube-video";
363 QMimeData* PlaylistModel::mimeData( const QModelIndexList &indexes ) const {
364 VideoMimeData* mime = new VideoMimeData();
366 foreach( const QModelIndex &it, indexes ) {
368 if (row >= 0 && row < videos.size())
369 mime->addVideo( videos.at( it.row() ) );
375 bool PlaylistModel::dropMimeData(const QMimeData *data,
376 Qt::DropAction action, int row, int column,
377 const QModelIndex &parent) {
378 if (action == Qt::IgnoreAction)
381 if (!data->hasFormat("application/x-minitube-video"))
390 else if (parent.isValid())
391 beginRow = parent.row();
393 beginRow = rowCount(QModelIndex());
395 const VideoMimeData* videoMimeData = dynamic_cast<const VideoMimeData*>( data );
396 if(!videoMimeData ) return false;
398 QList<Video*> droppedVideos = videoMimeData->videos();
399 foreach( Video *video, droppedVideos) {
402 int videoRow = videos.indexOf(video);
403 removeRows(videoRow, 1, QModelIndex());
405 // and then add them again at the new position
406 beginInsertRows(QModelIndex(), beginRow, beginRow);
407 videos.insert(beginRow, video);
412 // fix m_activeRow after all this
413 m_activeRow = videos.indexOf(m_activeVideo);
415 // let the MediaView restore the selection
416 emit needSelectionFor(droppedVideos);
422 int PlaylistModel::rowForCloneVideo(Video *video) const {
423 if (!video) return -1;
424 for (int i = 0; i < videos.size(); ++i) {
425 Video *v = videos.at(i);
426 if (v->id() == video->id()) return i;
431 int PlaylistModel::rowForVideo(Video* video) {
432 return videos.indexOf(video);
435 QModelIndex PlaylistModel::indexForVideo(Video* video) {
436 return createIndex(videos.indexOf(video), 0);
439 void PlaylistModel::move(QModelIndexList &indexes, bool up) {
440 QList<Video*> movedVideos;
442 foreach (QModelIndex index, indexes) {
443 int row = index.row();
444 if (row >= videos.size()) continue;
445 // qDebug() << "index row" << row;
446 Video *video = videoAt(row);
447 movedVideos << video;
450 int end=up ? -1 : rowCount()-1, mod=up ? -1 : 1;
451 foreach (Video *video, movedVideos) {
453 int row = rowForVideo(video);
454 if (row+mod==end) { end=row; continue; }
455 // qDebug() << "video row" << row;
456 removeRows(row, 1, QModelIndex());
461 beginInsertRows(QModelIndex(), row, row);
462 videos.insert(row, video);
467 emit needSelectionFor(movedVideos);
473 void PlaylistModel::setHoveredRow(int row) {
474 int oldRow = hoveredRow;
476 emit dataChanged( createIndex( oldRow, 0 ), createIndex( oldRow, columnCount() - 1 ) );
477 emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
480 void PlaylistModel::clearHover() {
481 emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
485 /* clickable author */
487 void PlaylistModel::enterAuthorHover() {
488 if (authorHovered) return;
489 authorHovered = true;
493 void PlaylistModel::exitAuthorHover() {
494 if (!authorHovered) return;
495 authorHovered = false;
497 setHoveredRow(hoveredRow);
500 void PlaylistModel::enterAuthorPressed() {
501 if (authorPressed) return;
502 authorPressed = true;
506 void PlaylistModel::exitAuthorPressed() {
507 if (!authorPressed) return;
508 authorPressed = false;
512 void PlaylistModel::updateAuthor() {
513 emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );