$END_LICENSE */
#include "playlistmodel.h"
+#include "mediaview.h"
+#include "playlistitemdelegate.h"
+#include "searchparams.h"
+#include "video.h"
#include "videomimedata.h"
#include "videosource.h"
-#include "ytsearch.h"
-#include "video.h"
-#include "searchparams.h"
-#include "mediaview.h"
-static const int maxItems = 10;
-static const QString recentKeywordsKey = "recentKeywords";
-static const QString recentChannelsKey = "recentChannels";
+#include "searchvideosource.h"
+
+namespace {
+const QString recentKeywordsKey = "recentKeywords";
+const QString recentChannelsKey = "recentChannels";
+} // namespace
PlaylistModel::PlaylistModel(QWidget *parent) : QAbstractListModel(parent) {
- videoSource = 0;
+ videoSource = nullptr;
searching = false;
canSearchMore = true;
firstSearch = false;
- m_activeVideo = 0;
+ m_activeVideo = nullptr;
m_activeRow = -1;
- skip = 1;
+ startIndex = 1;
max = 0;
hoveredRow = -1;
authorHovered = false;
authorPressed = false;
}
-int PlaylistModel::rowCount(const QModelIndex &/*parent*/) const {
+int PlaylistModel::rowCount(const QModelIndex & /*parent*/) const {
int count = videos.size();
-
+
// add the message item
- if (videos.isEmpty() || !searching)
- count++;
-
+ if (videos.isEmpty() || !searching) count++;
+
return count;
}
QVariant PlaylistModel::data(const QModelIndex &index, int role) const {
-
int row = index.row();
-
+
if (row == videos.size()) {
-
QPalette palette;
- QFont boldFont;
- boldFont.setBold(true);
-
+
switch (role) {
case ItemTypeRole:
return ItemTypeShowMore;
case Qt::DisplayRole:
if (!errorMessage.isEmpty()) return errorMessage;
- if (searching) return tr("Searching...");
- if (canSearchMore) return tr("Show %1 More").arg(maxItems);
- if (videos.isEmpty()) return tr("No videos");
- else return tr("No more videos");
+ if (searching) return QString(); // tr("Searching...");
+ if (canSearchMore) return tr("Show %1 More").arg("").simplified();
+ if (videos.isEmpty())
+ return tr("No videos");
+ else
+ return tr("No more videos");
case Qt::TextAlignmentRole:
return QVariant(int(Qt::AlignHCenter | Qt::AlignVCenter));
case Qt::ForegroundRole:
- if (!errorMessage.isEmpty())
- return palette.color(QPalette::ToolTipText);
- else
- return palette.color(QPalette::Dark);
- case Qt::BackgroundColorRole:
+ return palette.color(QPalette::Dark);
+ case Qt::BackgroundRole:
if (!errorMessage.isEmpty())
return palette.color(QPalette::ToolTipBase);
else
return QVariant();
- case Qt::FontRole:
- return boldFont;
default:
return QVariant();
}
-
+
} else if (row < 0 || row >= videos.size())
return QVariant();
-
+
Video *video = videos.at(row);
-
+
switch (role) {
case ItemTypeRole:
return ItemTypeVideo;
case ActiveTrackRole:
return video == m_activeVideo;
case Qt::DisplayRole:
- return video->title();
+ return video->getTitle();
case HoveredItemRole:
return hoveredRow == index.row();
case AuthorHoveredRole:
return authorHovered;
case AuthorPressedRole:
return authorPressed;
+ /*
case Qt::StatusTipRole:
return video->description();
+ */
}
-
+
return QVariant();
}
void PlaylistModel::setActiveRow(int row, bool notify) {
- if ( rowExists( row ) ) {
-
+ if (rowExists(row)) {
m_activeRow = row;
+ Video *previousVideo = m_activeVideo;
m_activeVideo = videoAt(row);
-
+
int oldactiverow = m_activeRow;
-
- if ( rowExists( oldactiverow ) )
- emit dataChanged( createIndex( oldactiverow, 0 ), createIndex( oldactiverow, columnCount() - 1 ) );
-
- emit dataChanged( createIndex( m_activeRow, 0 ), createIndex( m_activeRow, columnCount() - 1 ) );
- if (notify) emit activeRowChanged(row);
-
+
+ if (rowExists(oldactiverow))
+ emit dataChanged(createIndex(oldactiverow, 0),
+ createIndex(oldactiverow, columnCount() - 1));
+
+ emit dataChanged(createIndex(m_activeRow, 0), createIndex(m_activeRow, columnCount() - 1));
+ if (notify) emit activeVideoChanged(m_activeVideo, previousVideo);
+
} else {
m_activeRow = -1;
- m_activeVideo = 0;
+ m_activeVideo = nullptr;
}
-
}
int PlaylistModel::nextRow() const {
int nextRow = m_activeRow + 1;
- if (rowExists(nextRow))
- return nextRow;
+ if (rowExists(nextRow)) return nextRow;
return -1;
}
int PlaylistModel::previousRow() const {
int prevRow = m_activeRow - 1;
- if (rowExists(prevRow))
- return prevRow;
+ if (rowExists(prevRow)) return prevRow;
return -1;
}
-Video* PlaylistModel::videoAt( int row ) const {
- if ( rowExists( row ) )
- return videos.at( row );
- return 0;
+Video *PlaylistModel::videoAt(int row) const {
+ if (rowExists(row)) return videos.at(row);
+ return nullptr;
}
-Video* PlaylistModel::activeVideo() const {
+Video *PlaylistModel::activeVideo() const {
return m_activeVideo;
}
void PlaylistModel::setVideoSource(VideoSource *videoSource) {
- while (!videos.isEmpty())
- delete videos.takeFirst();
+ beginResetModel();
+
+ qDeleteAll(videos);
+ videos.clear();
- m_activeVideo = 0;
+ qDeleteAll(deletedVideos);
+ deletedVideos.clear();
+
+ m_activeVideo = nullptr;
m_activeRow = -1;
- skip = 1;
- reset();
+ startIndex = 1;
+ endResetModel();
this->videoSource = videoSource;
- connect(videoSource, SIGNAL(gotVideos(QList<Video*>)),
- SLOT(addVideos(QList<Video*>)), Qt::UniqueConnection);
- connect(videoSource, SIGNAL(finished(int)),
- SLOT(searchFinished(int)), Qt::UniqueConnection);
- connect(videoSource, SIGNAL(error(QString)),
- SLOT(searchError(QString)), Qt::UniqueConnection);
+ connect(videoSource, SIGNAL(gotVideos(QVector<Video *>)), SLOT(addVideos(QVector<Video *>)),
+ Qt::UniqueConnection);
+ connect(videoSource, SIGNAL(finished(int)), SLOT(searchFinished(int)), Qt::UniqueConnection);
+ connect(videoSource, SIGNAL(error(QString)), SLOT(searchError(QString)), Qt::UniqueConnection);
+ connect(videoSource, &QObject::destroyed, this,
+ [this, videoSource] {
+ if (this->videoSource == videoSource) {
+ this->videoSource = nullptr;
+ }
+ },
+ Qt::UniqueConnection);
+ canSearchMore = true;
searchMore();
}
-void PlaylistModel::searchMore(int max) {
- if (searching) return;
+void PlaylistModel::searchMore() {
+ if (!canSearchMore || videoSource == nullptr || searching) return;
searching = true;
- firstSearch = skip == 1;
- this->max = max;
+ firstSearch = startIndex == 1;
+ max = videoSource->maxResults();
+ if (max == 0) max = 20;
errorMessage.clear();
- videoSource->loadVideos(max, skip);
- skip += max;
-}
-
-void PlaylistModel::searchMore() {
- searchMore(maxItems);
+ videoSource->loadVideos(max, startIndex);
+ startIndex += max;
}
void PlaylistModel::searchNeeded() {
+ const int desiredRowsAhead = 10;
int remainingRows = videos.size() - m_activeRow;
- int rowsNeeded = maxItems - remainingRows;
- if (rowsNeeded > 0)
- searchMore(rowsNeeded);
+ if (remainingRows < desiredRowsAhead) searchMore();
}
void PlaylistModel::abortSearch() {
- while (!videos.isEmpty())
- delete videos.takeFirst();
- reset();
- // if (videoSource) videoSource->abort();
+ QMutexLocker locker(&mutex);
+ beginResetModel();
+ if (videoSource) videoSource->abort();
+ qDeleteAll(videos);
+ videos.clear();
+ videos.squeeze();
searching = false;
m_activeRow = -1;
- m_activeVideo = 0;
- skip = 1;
+ m_activeVideo = nullptr;
+ startIndex = 1;
+ endResetModel();
}
void PlaylistModel::searchFinished(int total) {
+ Q_UNUSED(total);
searching = false;
- canSearchMore = total >= max;
+ canSearchMore = videoSource->hasMoreVideos();
// update the message item
- emit dataChanged( createIndex( maxItems, 0 ), createIndex( maxItems, columnCount() - 1 ) );
+ emit dataChanged(createIndex(videos.size(), 0), createIndex(videos.size(), columnCount() - 1));
- if (!videoSource->getSuggestions().isEmpty())
- emit haveSuggestions(videoSource->getSuggestions());
-
- if (firstSearch && !videos.isEmpty())
- handleFirstVideo(videos.first());
+ if (firstSearch && !videos.isEmpty()) handleFirstVideo(videos.at(0));
}
-void PlaylistModel::searchError(QString message) {
+void PlaylistModel::searchError(const QString &message) {
errorMessage = message;
// update the message item
- emit dataChanged( createIndex( maxItems, 0 ), createIndex( maxItems, columnCount() - 1 ) );
+ emit dataChanged(createIndex(videos.size(), 0), createIndex(videos.size(), columnCount() - 1));
}
-void PlaylistModel::addVideos(QList<Video*> newVideos) {
+void PlaylistModel::addVideos(const QVector<Video *> &newVideos) {
if (newVideos.isEmpty()) return;
+ videos.reserve(videos.size() + newVideos.size());
beginInsertRows(QModelIndex(), videos.size(), videos.size() + newVideos.size() - 2);
videos.append(newVideos);
endInsertRows();
- foreach (Video* video, newVideos) {
- connect(video, SIGNAL(gotThumbnail()),
- SLOT(updateThumbnail()), Qt::UniqueConnection);
- video->loadThumbnail();
+ for (Video *video : newVideos) {
+ connect(video, &Video::changed, this, [video, this] {
+ int row = rowForVideo(video);
+ emit dataChanged(createIndex(row, 0), createIndex(row, columnCount() - 1));
+ });
}
}
void PlaylistModel::handleFirstVideo(Video *video) {
-
+ QSettings settings;
int currentVideoRow = rowForCloneVideo(MediaView::instance()->getCurrentVideoId());
- if (currentVideoRow != -1) setActiveRow(currentVideoRow, false);
+ if (currentVideoRow != -1)
+ setActiveRow(currentVideoRow, false);
else {
- QSettings settings;
- if (!settings.value("manualplay", false).toBool())
- setActiveRow(0);
- }
-
- QSettings settings;
- if (!settings.value("manualplay", false).toBool()) {
- int newActiveRow = rowForCloneVideo(MediaView::instance()->getCurrentVideoId());
- if (newActiveRow != -1) setActiveRow(newActiveRow, false);
- else setActiveRow(0);
+ if (!settings.value("manualplay", false).toBool()) setActiveRow(0);
}
- if (videoSource->metaObject()->className() == QLatin1String("YTSearch")) {
-
- static const int maxRecentElements = 10;
-
- YTSearch *search = dynamic_cast<YTSearch *>(videoSource);
+ auto clazz = videoSource->metaObject()->className();
+ if (clazz == QLatin1String("SearchVideoSource")) {
+ auto search = qobject_cast<SearchVideoSource *>(videoSource);
SearchParams *searchParams = search->getSearchParams();
// save keyword
+ static const int maxRecentElements = 10;
QString query = searchParams->keywords();
if (!query.isEmpty() && !searchParams->isTransient()) {
if (query.startsWith("http://")) {
// Save the video title
- query += "|" + videos.first()->title();
+ query += "|" + videos.at(0)->getTitle();
}
QStringList keywords = settings.value(recentKeywordsKey).toStringList();
keywords.removeAll(query);
}
// save channel
- QString channel = searchParams->author();
- if (!channel.isEmpty() && !searchParams->isTransient()) {
+ QString channelId = searchParams->channelId();
+ if (!channelId.isEmpty() && !searchParams->isTransient()) {
QString value;
- if (!video->userId().isEmpty() && video->userId() != video->author())
- value = video->userId() + "|" + video->author();
- else value = video->author();
+ if (!video->getChannelId().isEmpty() &&
+ video->getChannelId() != video->getChannelTitle())
+ value = video->getChannelId() + "|" + video->getChannelTitle();
+ else
+ value = video->getChannelTitle();
QStringList channels = settings.value(recentChannelsKey).toStringList();
channels.removeAll(value);
- channels.removeAll(channel);
+ channels.removeAll(channelId);
channels.prepend(value);
while (channels.size() > maxRecentElements)
channels.removeLast();
}
}
-void PlaylistModel::updateThumbnail() {
-
- Video *video = static_cast<Video *>(sender());
- if (!video) {
- qDebug() << "Cannot get sender";
- return;
- }
-
- int row = rowForVideo(video);
- emit dataChanged( createIndex( row, 0 ), createIndex( row, columnCount() - 1 ) );
-
+void PlaylistModel::emitDataChanged() {
+ QModelIndex index = createIndex(rowCount() - 1, 0);
+ emit dataChanged(index, index);
}
// --- item removal
-/**
- * This function does not free memory
- */
bool PlaylistModel::removeRows(int position, int rows, const QModelIndex & /*parent*/) {
- beginRemoveRows(QModelIndex(), position, position+rows-1);
+ beginRemoveRows(QModelIndex(), position, position + rows - 1);
for (int row = 0; row < rows; ++row) {
- videos.removeAt(position);
+ Video *video = videos.takeAt(position);
}
endRemoveRows();
return true;
}
void PlaylistModel::removeIndexes(QModelIndexList &indexes) {
- QList<Video*> originalList(videos);
- QList<Video*> delitems;
- foreach (QModelIndex index, indexes) {
+ QVector<Video *> originalList(videos);
+ for (const QModelIndex &index : indexes) {
if (index.row() >= originalList.size()) continue;
- Video* video = originalList.at(index.row());
+ Video *video = originalList.at(index.row());
int idx = videos.indexOf(video);
if (idx != -1) {
beginRemoveRows(QModelIndex(), idx, idx);
- delitems.append(video);
+ deletedVideos.append(video);
+ if (m_activeVideo == video) {
+ m_activeVideo = nullptr;
+ m_activeRow = -1;
+ }
videos.removeAll(video);
endRemoveRows();
}
}
-
- qDeleteAll(delitems);
-
+ videos.squeeze();
}
// --- Sturm und drang ---
-
-
Qt::DropActions PlaylistModel::supportedDropActions() const {
return Qt::CopyAction;
}
Qt::ItemFlags PlaylistModel::flags(const QModelIndex &index) const {
if (index.isValid()) {
if (index.row() == videos.size()) {
- // don't drag the "show 10 more" item
+ // don't drag the "show more" item
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
- } else return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
+ } else
+ return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
}
return Qt::ItemIsDropEnabled;
}
return types;
}
-QMimeData* PlaylistModel::mimeData( const QModelIndexList &indexes ) const {
- VideoMimeData* mime = new VideoMimeData();
+QMimeData *PlaylistModel::mimeData(const QModelIndexList &indexes) const {
+ VideoMimeData *mime = new VideoMimeData();
- foreach( const QModelIndex &it, indexes ) {
+ for (const QModelIndex &it : indexes) {
int row = it.row();
- if (row >= 0 && row < videos.size())
- mime->addVideo( videos.at( it.row() ) );
+ if (row >= 0 && row < videos.size()) mime->addVideo(videos.at(it.row()));
}
return mime;
}
bool PlaylistModel::dropMimeData(const QMimeData *data,
- Qt::DropAction action, int row, int column,
+ Qt::DropAction action,
+ int row,
+ int column,
const QModelIndex &parent) {
- if (action == Qt::IgnoreAction)
- return true;
+ if (action == Qt::IgnoreAction) return true;
- if (!data->hasFormat("application/x-minitube-video"))
- return false;
+ if (!data->hasFormat("application/x-minitube-video")) return false;
- if (column > 0)
- return false;
+ if (column > 0) return false;
int beginRow;
if (row != -1)
else
beginRow = rowCount(QModelIndex());
- const VideoMimeData* videoMimeData = dynamic_cast<const VideoMimeData*>( data );
- if(!videoMimeData ) return false;
+ const VideoMimeData *videoMimeData = qobject_cast<const VideoMimeData *>(data);
+ if (!videoMimeData) return false;
- QList<Video*> droppedVideos = videoMimeData->videos();
- foreach( Video *video, droppedVideos) {
-
+ const QVector<Video *> &droppedVideos = videoMimeData->getVideos();
+ for (Video *video : droppedVideos) {
// remove videos
int videoRow = videos.indexOf(video);
removeRows(videoRow, 1, QModelIndex());
-
+
// and then add them again at the new position
beginInsertRows(QModelIndex(), beginRow, beginRow);
- videos.insert(beginRow, video);
+ if (beginRow >= videos.size()) {
+ videos.push_back(video);
+ } else {
+ videos.insert(beginRow, video);
+ }
endInsertRows();
-
}
// fix m_activeRow after all this
emit needSelectionFor(droppedVideos);
return true;
-
}
int PlaylistModel::rowForCloneVideo(const QString &videoId) const {
+ if (videoId.isEmpty()) return -1;
for (int i = 0; i < videos.size(); ++i) {
Video *v = videos.at(i);
- if (v->id() == videoId) return i;
+ // qDebug() << "Comparing" << v->id() << videoId;
+ if (v->getId() == videoId) return i;
}
return -1;
}
-int PlaylistModel::rowForVideo(Video* video) {
+int PlaylistModel::rowForVideo(Video *video) {
return videos.indexOf(video);
}
-QModelIndex PlaylistModel::indexForVideo(Video* video) {
+QModelIndex PlaylistModel::indexForVideo(Video *video) {
return createIndex(videos.indexOf(video), 0);
}
void PlaylistModel::move(QModelIndexList &indexes, bool up) {
- QList<Video*> movedVideos;
+ QVector<Video *> movedVideos;
- foreach (QModelIndex index, indexes) {
+ for (const QModelIndex &index : indexes) {
int row = index.row();
if (row >= videos.size()) continue;
// qDebug() << "index row" << row;
movedVideos << video;
}
- int end=up ? -1 : rowCount()-1, mod=up ? -1 : 1;
- foreach (Video *video, movedVideos) {
-
+ int end = up ? -1 : rowCount() - 1, mod = up ? -1 : 1;
+ for (Video *video : movedVideos) {
int row = rowForVideo(video);
- if (row+mod==end) { end=row; continue; }
+ if (row + mod == end) {
+ end = row;
+ continue;
+ }
// qDebug() << "video row" << row;
removeRows(row, 1, QModelIndex());
- if (up) row--;
- else row++;
+ if (up)
+ row--;
+ else
+ row++;
beginInsertRows(QModelIndex(), row, row);
videos.insert(row, video);
endInsertRows();
-
}
emit needSelectionFor(movedVideos);
-
}
/* row hovering */
void PlaylistModel::setHoveredRow(int row) {
int oldRow = hoveredRow;
hoveredRow = row;
- emit dataChanged( createIndex( oldRow, 0 ), createIndex( oldRow, columnCount() - 1 ) );
- emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
+ emit dataChanged(createIndex(oldRow, 0), createIndex(oldRow, columnCount() - 1));
+ emit dataChanged(createIndex(hoveredRow, 0), createIndex(hoveredRow, columnCount() - 1));
}
void PlaylistModel::clearHover() {
- emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
+ int oldRow = hoveredRow;
hoveredRow = -1;
+ emit dataChanged(createIndex(oldRow, 0), createIndex(oldRow, columnCount() - 1));
+}
+
+void PlaylistModel::updateHoveredRow() {
+ emit dataChanged(createIndex(hoveredRow, 0), createIndex(hoveredRow, columnCount() - 1));
}
/* clickable author */
void PlaylistModel::enterAuthorHover() {
if (authorHovered) return;
authorHovered = true;
- updateAuthor();
+ updateHoveredRow();
}
void PlaylistModel::exitAuthorHover() {
if (!authorHovered) return;
authorHovered = false;
- updateAuthor();
+ updateHoveredRow();
setHoveredRow(hoveredRow);
}
void PlaylistModel::enterAuthorPressed() {
if (authorPressed) return;
authorPressed = true;
- updateAuthor();
+ updateHoveredRow();
}
void PlaylistModel::exitAuthorPressed() {
if (!authorPressed) return;
authorPressed = false;
- updateAuthor();
-}
-
-void PlaylistModel::updateAuthor() {
- emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
+ updateHoveredRow();
}