]> git.sur5r.net Git - minitube/commitdiff
2013
authorFlavio <flavio@odisseo.local>
Mon, 7 Jan 2013 16:22:42 +0000 (17:22 +0100)
committerFlavio <flavio@odisseo.local>
Mon, 7 Jan 2013 16:22:42 +0000 (17:22 +0100)
17 files changed:
src/aboutview.cpp
src/listmodel.cpp [deleted file]
src/listmodel.h [deleted file]
src/playlistmodel.cpp [new file with mode: 0644]
src/playlistmodel.h [new file with mode: 0644]
src/playlistwidget.cpp [deleted file]
src/playlistwidget.h [deleted file]
src/youtubesearch.cpp [deleted file]
src/youtubesearch.h [deleted file]
src/youtubestreamreader.cpp [deleted file]
src/youtubestreamreader.h [deleted file]
src/youtubesuggest.cpp [deleted file]
src/youtubesuggest.h [deleted file]
src/ytfeedreader.cpp [new file with mode: 0644]
src/ytfeedreader.h [new file with mode: 0644]
src/ytsuggester.cpp [new file with mode: 0644]
src/ytsuggester.h [new file with mode: 0644]

index 9a5e259f4bcbf2f74e078b3538caafcda431129b..78d4a34178948696293b277028c204e8a7e75ff3 100644 (file)
@@ -69,7 +69,7 @@ AboutView::AboutView(QWidget *parent) : QWidget(parent) {
             "<p>" + tr("Released under the <a href='%1'>GNU General Public License</a>")
             .arg("http://www.gnu.org/licenses/gpl.html") + "</p>"
         #endif
-            "<p>&copy; 2009-2012 " + Constants::ORG_NAME + "</p>"
+            "<p>&copy; 2009-2013 " + Constants::ORG_NAME + "</p>"
             "</body></html>";
     QLabel *infoLabel = new QLabel(info, this);
     infoLabel->setOpenExternalLinks(true);
diff --git a/src/listmodel.cpp b/src/listmodel.cpp
deleted file mode 100644 (file)
index 4e8662b..0000000
+++ /dev/null
@@ -1,470 +0,0 @@
-#include "listmodel.h"
-#include "videomimedata.h"
-
-#define MAX_ITEMS 10
-static const QString recentKeywordsKey = "recentKeywords";
-static const QString recentChannelsKey = "recentChannels";
-
-ListModel::ListModel(QWidget *parent) : QAbstractListModel(parent) {
-    youtubeSearch = 0;
-    searching = false;
-    canSearchMore = true;
-    m_activeVideo = 0;
-    m_activeRow = -1;
-    skip = 1;
-
-    hoveredRow = -1;
-    authorHovered = false;
-    authorPressed = false;
-}
-
-ListModel::~ListModel() {
-    delete youtubeSearch;
-}
-
-int ListModel::rowCount(const QModelIndex &/*parent*/) const {
-    int count = videos.size();
-    
-    // add the message item
-    if (videos.isEmpty() || !searching)
-        count++;
-    
-    return count;
-}
-
-QVariant ListModel::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:
-        case Qt::StatusTipRole:
-            if (!errorMessage.isEmpty()) return errorMessage;
-            if (searching) return tr("Searching...");
-            if (canSearchMore) return tr("Show %1 More").arg(MAX_ITEMS);
-            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:
-            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 VideoRole:
-        return QVariant::fromValue(QPointer<Video>(video));
-    case ActiveTrackRole:
-        return video == m_activeVideo;
-    case Qt::DisplayRole:
-        return video->title();
-    case HoveredItemRole:
-        return hoveredRow == index.row();
-    case AuthorHoveredRole:
-        return authorHovered;
-    case AuthorPressedRole:
-        return authorPressed;
-    }
-    
-    return QVariant();
-}
-
-void ListModel::setActiveRow( int row) {
-    if ( rowExists( row ) ) {
-        
-        m_activeRow = row;
-        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 ) );
-        emit activeRowChanged(row);
-        
-    } else {
-        m_activeRow = -1;
-        m_activeVideo = 0;
-    }
-
-}
-
-int ListModel::nextRow() const {
-    int nextRow = m_activeRow + 1;
-    if (rowExists(nextRow))
-        return nextRow;
-    return -1;
-}
-
-int ListModel::previousRow() const {
-    int prevRow = m_activeRow - 1;
-    if (rowExists(prevRow))
-        return prevRow;
-    return -1;
-}
-
-Video* ListModel::videoAt( int row ) const {
-    if ( rowExists( row ) )
-        return videos.at( row );
-    return 0;
-}
-
-Video* ListModel::activeVideo() const {
-    return m_activeVideo;
-}
-
-void ListModel::search(SearchParams *searchParams) {
-
-    // delete current videos
-    while (!videos.isEmpty())
-        delete videos.takeFirst();
-    m_activeVideo = 0;
-    m_activeRow = -1;
-    skip = 1;
-    errorMessage.clear();
-    reset();
-
-    // (re)initialize the YouTubeSearch
-    if (youtubeSearch) delete youtubeSearch;
-    youtubeSearch = new YouTubeSearch();
-    connect(youtubeSearch, SIGNAL(gotVideo(Video*)), this, SLOT(addVideo(Video*)));
-    connect(youtubeSearch, SIGNAL(finished(int)), this, SLOT(searchFinished(int)));
-    connect(youtubeSearch, SIGNAL(error(QString)), this, SLOT(searchError(QString)));
-
-    this->searchParams = searchParams;
-    searching = true;
-    youtubeSearch->search(searchParams, MAX_ITEMS, skip);
-    skip += MAX_ITEMS;
-}
-
-void ListModel::searchMore(int max) {
-    if (searching) return;
-    searching = true;
-    errorMessage.clear();
-    youtubeSearch->search(searchParams, max, skip);
-    skip += max;
-}
-
-void ListModel::searchMore() {
-    searchMore(MAX_ITEMS);
-}
-
-void ListModel::searchNeeded() {
-    int remainingRows = videos.size() - m_activeRow;
-    int rowsNeeded = MAX_ITEMS - remainingRows;
-    if (rowsNeeded > 0)
-        searchMore(rowsNeeded);
-}
-
-void ListModel::abortSearch() {
-    while (!videos.isEmpty())
-        delete videos.takeFirst();
-    reset();
-    youtubeSearch->abort();
-    searching = false;
-}
-
-void ListModel::searchFinished(int total) {
-    searching = false;
-    canSearchMore = total > 0;
-
-    // update the message item
-    emit dataChanged( createIndex( MAX_ITEMS, 0 ), createIndex( MAX_ITEMS, columnCount() - 1 ) );
-
-    if (!youtubeSearch->getSuggestions().isEmpty()) {
-        emit haveSuggestions(youtubeSearch->getSuggestions());
-    }
-}
-
-void ListModel::searchError(QString message) {
-    errorMessage = message;
-    // update the message item
-    emit dataChanged( createIndex( MAX_ITEMS, 0 ), createIndex( MAX_ITEMS, columnCount() - 1 ) );
-}
-
-void ListModel::addVideo(Video* video) {
-    
-    connect(video, SIGNAL(gotThumbnail()), this, SLOT(updateThumbnail()));
-
-    beginInsertRows(QModelIndex(), videos.size(), videos.size());
-    videos << video;
-    endInsertRows();
-    
-    // first result!
-    if (videos.size() == 1) {
-
-        // manualplay
-        QSettings settings;
-        if (!settings.value("manualplay", false).toBool())
-            setActiveRow(0);
-
-        // save keyword
-        QString query = searchParams->keywords();
-        if (!query.isEmpty() && !searchParams->isTransient()) {
-            if (query.startsWith("http://")) {
-                // Save the video title
-                query += "|" + videos.first()->title();
-            }
-            QStringList keywords = settings.value(recentKeywordsKey).toStringList();
-            keywords.removeAll(query);
-            keywords.prepend(query);
-            while (keywords.size() > 10)
-                keywords.removeLast();
-            settings.setValue(recentKeywordsKey, keywords);
-        }
-
-        // save channel
-        QString channel = searchParams->author();
-        if (!channel.isEmpty() && !searchParams->isTransient()) {
-            QSettings settings;
-            QStringList channels = settings.value(recentChannelsKey).toStringList();
-            channels.removeAll(channel);
-            channels.prepend(channel);
-            while (channels.size() > 10)
-                channels.removeLast();
-            settings.setValue(recentChannelsKey, channels);
-        }
-
-    }
-
-}
-
-void ListModel::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 ) );
-
-}
-
-// --- item removal
-
-/**
-  * This function does not free memory
-  */
-bool ListModel::removeRows(int position, int rows, const QModelIndex & /*parent*/) {
-    beginRemoveRows(QModelIndex(), position, position+rows-1);
-    for (int row = 0; row < rows; ++row) {
-        videos.removeAt(position);
-    }
-    endRemoveRows();
-    return true;
-}
-
-void ListModel::removeIndexes(QModelIndexList &indexes) {
-    QList<Video*> originalList(videos);
-    QList<Video*> delitems;
-    foreach (QModelIndex index, indexes) {
-        if (index.row() >= originalList.size()) continue;
-        Video* video = originalList.at(index.row());
-        int idx = videos.indexOf(video);
-        if (idx != -1) {
-            beginRemoveRows(QModelIndex(), idx, idx);
-            delitems.append(video);
-            videos.removeAll(video);
-            endRemoveRows();
-        }
-    }
-
-    qDeleteAll(delitems);
-
-}
-
-// --- Sturm und drang ---
-
-
-
-Qt::DropActions ListModel::supportedDropActions() const {
-    return Qt::MoveAction;
-}
-
-Qt::ItemFlags ListModel::flags(const QModelIndex &index) const {
-    if (index.isValid())
-        if (index.row() == videos.size()) {
-            // don't drag the "show 10 more" item
-            return Qt::ItemIsEnabled;
-        } else return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
-    return Qt::ItemIsDropEnabled;
-}
-
-QStringList ListModel::mimeTypes() const {
-    QStringList types;
-    types << "application/x-minitube-video";
-    return types;
-}
-
-QMimeData* ListModel::mimeData( const QModelIndexList &indexes ) const {
-    VideoMimeData* mime = new VideoMimeData();
-
-    foreach( const QModelIndex &it, indexes ) {
-        int row = it.row();
-        if (row >= 0 && row < videos.size())
-            mime->addVideo( videos.at( it.row() ) );
-    }
-
-    return mime;
-}
-
-bool ListModel::dropMimeData(const QMimeData *data,
-                             Qt::DropAction action, int row, int column,
-                             const QModelIndex &parent) {
-    if (action == Qt::IgnoreAction)
-        return true;
-
-    if (!data->hasFormat("application/x-minitube-video"))
-        return false;
-
-    if (column > 0)
-        return false;
-
-    int beginRow;
-    if (row != -1)
-        beginRow = row;
-    else if (parent.isValid())
-        beginRow = parent.row();
-    else
-        beginRow = rowCount(QModelIndex());
-
-    const VideoMimeData* videoMimeData = dynamic_cast<const VideoMimeData*>( data );
-    if(!videoMimeData ) return false;
-
-    QList<Video*> droppedVideos = videoMimeData->videos();
-    foreach( 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);
-        endInsertRows();
-
-    }
-
-    // fix m_activeRow after all this
-    m_activeRow = videos.indexOf(m_activeVideo);
-
-    // let the MediaView restore the selection
-    emit needSelectionFor(droppedVideos);
-
-    return true;
-
-}
-
-int ListModel::rowForVideo(Video* video) {
-    return videos.indexOf(video);
-}
-
-QModelIndex ListModel::indexForVideo(Video* video) {
-    return createIndex(videos.indexOf(video), 0);
-}
-
-void ListModel::move(QModelIndexList &indexes, bool up) {
-    QList<Video*> movedVideos;
-
-    foreach (QModelIndex index, indexes) {
-        int row = index.row();
-        if (row >= videos.size()) continue;
-        // qDebug() << "index row" << row;
-        Video *video = videoAt(row);
-        movedVideos << video;
-    }
-
-    int end=up ? -1 : rowCount()-1, mod=up ? -1 : 1;
-    foreach (Video *video, movedVideos) {
-
-        int row = rowForVideo(video);
-        if (row+mod==end) { end=row; continue; }
-        // qDebug() << "video row" << row;
-        removeRows(row, 1, QModelIndex());
-
-        if (up) row--;
-        else row++;
-
-        beginInsertRows(QModelIndex(), row, row);
-        videos.insert(row, video);
-        endInsertRows();
-
-    }
-
-    emit needSelectionFor(movedVideos);
-
-}
-
-/* row hovering */
-
-void ListModel::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 ) );
-}
-
-void ListModel::clearHover() {
-    emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
-    hoveredRow = -1;
-}
-
-/* clickable author */
-
-void ListModel::enterAuthorHover() {
-    if (authorHovered) return;
-    authorHovered = true;
-    updateAuthor();
-}
-
-void ListModel::exitAuthorHover() {
-    if (!authorHovered) return;
-    authorHovered = false;
-    updateAuthor();
-    setHoveredRow(hoveredRow);
-}
-
-void ListModel::enterAuthorPressed() {
-    if (authorPressed) return;
-    authorPressed = true;
-    updateAuthor();
-}
-
-void ListModel::exitAuthorPressed() {
-    if (!authorPressed) return;
-    authorPressed = false;
-    updateAuthor();
-}
-
-void ListModel::updateAuthor() {
-    emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
-}
diff --git a/src/listmodel.h b/src/listmodel.h
deleted file mode 100644 (file)
index f9d424b..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-#ifndef LISTMODEL_H
-#define LISTMODEL_H
-
-#include "video.h"
-#include "youtubesearch.h"
-#include "searchparams.h"
-
-enum DataRoles {
-    ItemTypeRole = Qt::UserRole,
-    VideoRole,
-    ActiveTrackRole,
-    DownloadItemRole,
-    HoveredItemRole,
-    DownloadButtonHoveredRole,
-    DownloadButtonPressedRole,
-    AuthorHoveredRole,
-    AuthorPressedRole
-};
-
-enum ItemTypes {
-    ItemTypeVideo = 1,
-    ItemTypeShowMore
-};
-
-class ListModel : public QAbstractListModel {
-
-    Q_OBJECT
-
-public:
-
-    ListModel(QWidget *parent);
-    ~ListModel();
-
-    // inherited from QAbstractListModel
-    int rowCount(const QModelIndex &parent = QModelIndex()) const;
-    // int rowCount( const QModelIndex& parent = QModelIndex() ) const { Q_UNUSED( parent ); return videos.size(); }
-    int columnCount( const QModelIndex& parent = QModelIndex() ) const { Q_UNUSED( parent ); return 4; }
-    QVariant data(const QModelIndex &index, int role) const;
-    bool removeRows(int position, int rows, const QModelIndex &parent);
-
-    Qt::ItemFlags flags(const QModelIndex &index) const;
-    QStringList mimeTypes() const;
-    Qt::DropActions supportedDropActions() const;
-    QMimeData* mimeData( const QModelIndexList &indexes ) const;
-    bool dropMimeData(const QMimeData *data,
-                      Qt::DropAction action, int row, int column,
-                      const QModelIndex &parent);
-
-    // custom methods
-    void setActiveRow( int row );
-    bool rowExists( int row ) const { return (( row >= 0 ) && ( row < videos.size() ) ); }
-    int activeRow() const { return m_activeRow; } // returns -1 if there is no active row
-    int nextRow() const;
-    int previousRow() const;
-    void removeIndexes(QModelIndexList &indexes);
-    int rowForVideo(Video* video);
-    QModelIndex indexForVideo(Video* video);
-    void move(QModelIndexList &indexes, bool up);
-
-    Video* videoAt( int row ) const;
-    Video* activeVideo() const;
-
-    // video search methods
-    void search(SearchParams *searchParams);
-    void abortSearch();
-
-
-public slots:
-    void searchMore();
-    void searchNeeded();
-    void addVideo(Video* video);
-    void searchFinished(int total);
-    void searchError(QString message);
-    void updateThumbnail();
-
-    void setHoveredRow(int row);
-    void clearHover();
-    void enterAuthorHover();
-    void exitAuthorHover();
-    void enterAuthorPressed();
-    void exitAuthorPressed();
-    void updateAuthor();
-
-signals:
-    void activeRowChanged(int);
-    void needSelectionFor(QList<Video*>);
-    void haveSuggestions(const QStringList &suggestions);
-
-private:
-    void searchMore(int max);
-
-    YouTubeSearch *youtubeSearch;
-    SearchParams *searchParams;
-    bool searching;
-    bool canSearchMore;
-
-    QList<Video*> videos;
-    int skip;
-
-    // the row being played
-    int m_activeRow;
-    Video *m_activeVideo;
-
-    QString errorMessage;
-
-    int hoveredRow;
-    bool authorHovered;
-    bool authorPressed;
-};
-
-#endif
diff --git a/src/playlistmodel.cpp b/src/playlistmodel.cpp
new file mode 100644 (file)
index 0000000..4e8662b
--- /dev/null
@@ -0,0 +1,470 @@
+#include "listmodel.h"
+#include "videomimedata.h"
+
+#define MAX_ITEMS 10
+static const QString recentKeywordsKey = "recentKeywords";
+static const QString recentChannelsKey = "recentChannels";
+
+ListModel::ListModel(QWidget *parent) : QAbstractListModel(parent) {
+    youtubeSearch = 0;
+    searching = false;
+    canSearchMore = true;
+    m_activeVideo = 0;
+    m_activeRow = -1;
+    skip = 1;
+
+    hoveredRow = -1;
+    authorHovered = false;
+    authorPressed = false;
+}
+
+ListModel::~ListModel() {
+    delete youtubeSearch;
+}
+
+int ListModel::rowCount(const QModelIndex &/*parent*/) const {
+    int count = videos.size();
+    
+    // add the message item
+    if (videos.isEmpty() || !searching)
+        count++;
+    
+    return count;
+}
+
+QVariant ListModel::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:
+        case Qt::StatusTipRole:
+            if (!errorMessage.isEmpty()) return errorMessage;
+            if (searching) return tr("Searching...");
+            if (canSearchMore) return tr("Show %1 More").arg(MAX_ITEMS);
+            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:
+            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 VideoRole:
+        return QVariant::fromValue(QPointer<Video>(video));
+    case ActiveTrackRole:
+        return video == m_activeVideo;
+    case Qt::DisplayRole:
+        return video->title();
+    case HoveredItemRole:
+        return hoveredRow == index.row();
+    case AuthorHoveredRole:
+        return authorHovered;
+    case AuthorPressedRole:
+        return authorPressed;
+    }
+    
+    return QVariant();
+}
+
+void ListModel::setActiveRow( int row) {
+    if ( rowExists( row ) ) {
+        
+        m_activeRow = row;
+        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 ) );
+        emit activeRowChanged(row);
+        
+    } else {
+        m_activeRow = -1;
+        m_activeVideo = 0;
+    }
+
+}
+
+int ListModel::nextRow() const {
+    int nextRow = m_activeRow + 1;
+    if (rowExists(nextRow))
+        return nextRow;
+    return -1;
+}
+
+int ListModel::previousRow() const {
+    int prevRow = m_activeRow - 1;
+    if (rowExists(prevRow))
+        return prevRow;
+    return -1;
+}
+
+Video* ListModel::videoAt( int row ) const {
+    if ( rowExists( row ) )
+        return videos.at( row );
+    return 0;
+}
+
+Video* ListModel::activeVideo() const {
+    return m_activeVideo;
+}
+
+void ListModel::search(SearchParams *searchParams) {
+
+    // delete current videos
+    while (!videos.isEmpty())
+        delete videos.takeFirst();
+    m_activeVideo = 0;
+    m_activeRow = -1;
+    skip = 1;
+    errorMessage.clear();
+    reset();
+
+    // (re)initialize the YouTubeSearch
+    if (youtubeSearch) delete youtubeSearch;
+    youtubeSearch = new YouTubeSearch();
+    connect(youtubeSearch, SIGNAL(gotVideo(Video*)), this, SLOT(addVideo(Video*)));
+    connect(youtubeSearch, SIGNAL(finished(int)), this, SLOT(searchFinished(int)));
+    connect(youtubeSearch, SIGNAL(error(QString)), this, SLOT(searchError(QString)));
+
+    this->searchParams = searchParams;
+    searching = true;
+    youtubeSearch->search(searchParams, MAX_ITEMS, skip);
+    skip += MAX_ITEMS;
+}
+
+void ListModel::searchMore(int max) {
+    if (searching) return;
+    searching = true;
+    errorMessage.clear();
+    youtubeSearch->search(searchParams, max, skip);
+    skip += max;
+}
+
+void ListModel::searchMore() {
+    searchMore(MAX_ITEMS);
+}
+
+void ListModel::searchNeeded() {
+    int remainingRows = videos.size() - m_activeRow;
+    int rowsNeeded = MAX_ITEMS - remainingRows;
+    if (rowsNeeded > 0)
+        searchMore(rowsNeeded);
+}
+
+void ListModel::abortSearch() {
+    while (!videos.isEmpty())
+        delete videos.takeFirst();
+    reset();
+    youtubeSearch->abort();
+    searching = false;
+}
+
+void ListModel::searchFinished(int total) {
+    searching = false;
+    canSearchMore = total > 0;
+
+    // update the message item
+    emit dataChanged( createIndex( MAX_ITEMS, 0 ), createIndex( MAX_ITEMS, columnCount() - 1 ) );
+
+    if (!youtubeSearch->getSuggestions().isEmpty()) {
+        emit haveSuggestions(youtubeSearch->getSuggestions());
+    }
+}
+
+void ListModel::searchError(QString message) {
+    errorMessage = message;
+    // update the message item
+    emit dataChanged( createIndex( MAX_ITEMS, 0 ), createIndex( MAX_ITEMS, columnCount() - 1 ) );
+}
+
+void ListModel::addVideo(Video* video) {
+    
+    connect(video, SIGNAL(gotThumbnail()), this, SLOT(updateThumbnail()));
+
+    beginInsertRows(QModelIndex(), videos.size(), videos.size());
+    videos << video;
+    endInsertRows();
+    
+    // first result!
+    if (videos.size() == 1) {
+
+        // manualplay
+        QSettings settings;
+        if (!settings.value("manualplay", false).toBool())
+            setActiveRow(0);
+
+        // save keyword
+        QString query = searchParams->keywords();
+        if (!query.isEmpty() && !searchParams->isTransient()) {
+            if (query.startsWith("http://")) {
+                // Save the video title
+                query += "|" + videos.first()->title();
+            }
+            QStringList keywords = settings.value(recentKeywordsKey).toStringList();
+            keywords.removeAll(query);
+            keywords.prepend(query);
+            while (keywords.size() > 10)
+                keywords.removeLast();
+            settings.setValue(recentKeywordsKey, keywords);
+        }
+
+        // save channel
+        QString channel = searchParams->author();
+        if (!channel.isEmpty() && !searchParams->isTransient()) {
+            QSettings settings;
+            QStringList channels = settings.value(recentChannelsKey).toStringList();
+            channels.removeAll(channel);
+            channels.prepend(channel);
+            while (channels.size() > 10)
+                channels.removeLast();
+            settings.setValue(recentChannelsKey, channels);
+        }
+
+    }
+
+}
+
+void ListModel::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 ) );
+
+}
+
+// --- item removal
+
+/**
+  * This function does not free memory
+  */
+bool ListModel::removeRows(int position, int rows, const QModelIndex & /*parent*/) {
+    beginRemoveRows(QModelIndex(), position, position+rows-1);
+    for (int row = 0; row < rows; ++row) {
+        videos.removeAt(position);
+    }
+    endRemoveRows();
+    return true;
+}
+
+void ListModel::removeIndexes(QModelIndexList &indexes) {
+    QList<Video*> originalList(videos);
+    QList<Video*> delitems;
+    foreach (QModelIndex index, indexes) {
+        if (index.row() >= originalList.size()) continue;
+        Video* video = originalList.at(index.row());
+        int idx = videos.indexOf(video);
+        if (idx != -1) {
+            beginRemoveRows(QModelIndex(), idx, idx);
+            delitems.append(video);
+            videos.removeAll(video);
+            endRemoveRows();
+        }
+    }
+
+    qDeleteAll(delitems);
+
+}
+
+// --- Sturm und drang ---
+
+
+
+Qt::DropActions ListModel::supportedDropActions() const {
+    return Qt::MoveAction;
+}
+
+Qt::ItemFlags ListModel::flags(const QModelIndex &index) const {
+    if (index.isValid())
+        if (index.row() == videos.size()) {
+            // don't drag the "show 10 more" item
+            return Qt::ItemIsEnabled;
+        } else return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
+    return Qt::ItemIsDropEnabled;
+}
+
+QStringList ListModel::mimeTypes() const {
+    QStringList types;
+    types << "application/x-minitube-video";
+    return types;
+}
+
+QMimeData* ListModel::mimeData( const QModelIndexList &indexes ) const {
+    VideoMimeData* mime = new VideoMimeData();
+
+    foreach( const QModelIndex &it, indexes ) {
+        int row = it.row();
+        if (row >= 0 && row < videos.size())
+            mime->addVideo( videos.at( it.row() ) );
+    }
+
+    return mime;
+}
+
+bool ListModel::dropMimeData(const QMimeData *data,
+                             Qt::DropAction action, int row, int column,
+                             const QModelIndex &parent) {
+    if (action == Qt::IgnoreAction)
+        return true;
+
+    if (!data->hasFormat("application/x-minitube-video"))
+        return false;
+
+    if (column > 0)
+        return false;
+
+    int beginRow;
+    if (row != -1)
+        beginRow = row;
+    else if (parent.isValid())
+        beginRow = parent.row();
+    else
+        beginRow = rowCount(QModelIndex());
+
+    const VideoMimeData* videoMimeData = dynamic_cast<const VideoMimeData*>( data );
+    if(!videoMimeData ) return false;
+
+    QList<Video*> droppedVideos = videoMimeData->videos();
+    foreach( 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);
+        endInsertRows();
+
+    }
+
+    // fix m_activeRow after all this
+    m_activeRow = videos.indexOf(m_activeVideo);
+
+    // let the MediaView restore the selection
+    emit needSelectionFor(droppedVideos);
+
+    return true;
+
+}
+
+int ListModel::rowForVideo(Video* video) {
+    return videos.indexOf(video);
+}
+
+QModelIndex ListModel::indexForVideo(Video* video) {
+    return createIndex(videos.indexOf(video), 0);
+}
+
+void ListModel::move(QModelIndexList &indexes, bool up) {
+    QList<Video*> movedVideos;
+
+    foreach (QModelIndex index, indexes) {
+        int row = index.row();
+        if (row >= videos.size()) continue;
+        // qDebug() << "index row" << row;
+        Video *video = videoAt(row);
+        movedVideos << video;
+    }
+
+    int end=up ? -1 : rowCount()-1, mod=up ? -1 : 1;
+    foreach (Video *video, movedVideos) {
+
+        int row = rowForVideo(video);
+        if (row+mod==end) { end=row; continue; }
+        // qDebug() << "video row" << row;
+        removeRows(row, 1, QModelIndex());
+
+        if (up) row--;
+        else row++;
+
+        beginInsertRows(QModelIndex(), row, row);
+        videos.insert(row, video);
+        endInsertRows();
+
+    }
+
+    emit needSelectionFor(movedVideos);
+
+}
+
+/* row hovering */
+
+void ListModel::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 ) );
+}
+
+void ListModel::clearHover() {
+    emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
+    hoveredRow = -1;
+}
+
+/* clickable author */
+
+void ListModel::enterAuthorHover() {
+    if (authorHovered) return;
+    authorHovered = true;
+    updateAuthor();
+}
+
+void ListModel::exitAuthorHover() {
+    if (!authorHovered) return;
+    authorHovered = false;
+    updateAuthor();
+    setHoveredRow(hoveredRow);
+}
+
+void ListModel::enterAuthorPressed() {
+    if (authorPressed) return;
+    authorPressed = true;
+    updateAuthor();
+}
+
+void ListModel::exitAuthorPressed() {
+    if (!authorPressed) return;
+    authorPressed = false;
+    updateAuthor();
+}
+
+void ListModel::updateAuthor() {
+    emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
+}
diff --git a/src/playlistmodel.h b/src/playlistmodel.h
new file mode 100644 (file)
index 0000000..f9d424b
--- /dev/null
@@ -0,0 +1,111 @@
+#ifndef LISTMODEL_H
+#define LISTMODEL_H
+
+#include "video.h"
+#include "youtubesearch.h"
+#include "searchparams.h"
+
+enum DataRoles {
+    ItemTypeRole = Qt::UserRole,
+    VideoRole,
+    ActiveTrackRole,
+    DownloadItemRole,
+    HoveredItemRole,
+    DownloadButtonHoveredRole,
+    DownloadButtonPressedRole,
+    AuthorHoveredRole,
+    AuthorPressedRole
+};
+
+enum ItemTypes {
+    ItemTypeVideo = 1,
+    ItemTypeShowMore
+};
+
+class ListModel : public QAbstractListModel {
+
+    Q_OBJECT
+
+public:
+
+    ListModel(QWidget *parent);
+    ~ListModel();
+
+    // inherited from QAbstractListModel
+    int rowCount(const QModelIndex &parent = QModelIndex()) const;
+    // int rowCount( const QModelIndex& parent = QModelIndex() ) const { Q_UNUSED( parent ); return videos.size(); }
+    int columnCount( const QModelIndex& parent = QModelIndex() ) const { Q_UNUSED( parent ); return 4; }
+    QVariant data(const QModelIndex &index, int role) const;
+    bool removeRows(int position, int rows, const QModelIndex &parent);
+
+    Qt::ItemFlags flags(const QModelIndex &index) const;
+    QStringList mimeTypes() const;
+    Qt::DropActions supportedDropActions() const;
+    QMimeData* mimeData( const QModelIndexList &indexes ) const;
+    bool dropMimeData(const QMimeData *data,
+                      Qt::DropAction action, int row, int column,
+                      const QModelIndex &parent);
+
+    // custom methods
+    void setActiveRow( int row );
+    bool rowExists( int row ) const { return (( row >= 0 ) && ( row < videos.size() ) ); }
+    int activeRow() const { return m_activeRow; } // returns -1 if there is no active row
+    int nextRow() const;
+    int previousRow() const;
+    void removeIndexes(QModelIndexList &indexes);
+    int rowForVideo(Video* video);
+    QModelIndex indexForVideo(Video* video);
+    void move(QModelIndexList &indexes, bool up);
+
+    Video* videoAt( int row ) const;
+    Video* activeVideo() const;
+
+    // video search methods
+    void search(SearchParams *searchParams);
+    void abortSearch();
+
+
+public slots:
+    void searchMore();
+    void searchNeeded();
+    void addVideo(Video* video);
+    void searchFinished(int total);
+    void searchError(QString message);
+    void updateThumbnail();
+
+    void setHoveredRow(int row);
+    void clearHover();
+    void enterAuthorHover();
+    void exitAuthorHover();
+    void enterAuthorPressed();
+    void exitAuthorPressed();
+    void updateAuthor();
+
+signals:
+    void activeRowChanged(int);
+    void needSelectionFor(QList<Video*>);
+    void haveSuggestions(const QStringList &suggestions);
+
+private:
+    void searchMore(int max);
+
+    YouTubeSearch *youtubeSearch;
+    SearchParams *searchParams;
+    bool searching;
+    bool canSearchMore;
+
+    QList<Video*> videos;
+    int skip;
+
+    // the row being played
+    int m_activeRow;
+    Video *m_activeVideo;
+
+    QString errorMessage;
+
+    int hoveredRow;
+    bool authorHovered;
+    bool authorPressed;
+};
+
+#endif
diff --git a/src/playlistwidget.cpp b/src/playlistwidget.cpp
deleted file mode 100644 (file)
index 1101e4d..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-#include "playlistwidget.h"
-#include "segmentedcontrol.h"
-
-PlaylistWidget::PlaylistWidget (QWidget *parent, SegmentedControl *tabBar, QListView *listView)
-    : QWidget(parent) {
-    QBoxLayout *layout = new QVBoxLayout(this);
-    layout->setMargin(0);
-    layout->setSpacing(0);
-    layout->addWidget(tabBar);
-    layout->addWidget(listView);
-}
diff --git a/src/playlistwidget.h b/src/playlistwidget.h
deleted file mode 100644 (file)
index 5e3342c..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-#ifndef PLAYLISTWIDGET_H
-#define PLAYLISTWIDGET_H
-
-#include <QtGui>
-
-class SegmentedControl;
-
-class PlaylistWidget : public QWidget {
-
-    Q_OBJECT
-
-public:
-    PlaylistWidget(QWidget *parent, SegmentedControl *tabBar, QListView *listView);
-
-};
-
-#endif // PLAYLISTWIDGET_H
diff --git a/src/youtubesearch.cpp b/src/youtubesearch.cpp
deleted file mode 100644 (file)
index 3263525..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-#include "youtubesearch.h"
-#include "youtubestreamreader.h"
-#include "constants.h"
-#include "networkaccess.h"
-
-namespace The {
-    NetworkAccess* http();
-}
-
-YouTubeSearch::YouTubeSearch() : QObject() {}
-
-void YouTubeSearch::search(SearchParams *searchParams, int max, int skip) {
-    this->abortFlag = false;
-
-    QUrl url("http://gdata.youtube.com/feeds/api/videos/");
-    url.addQueryItem("v", "2");
-
-    url.addQueryItem("max-results", QString::number(max));
-    url.addQueryItem("start-index", QString::number(skip));
-
-    if (!searchParams->keywords().isEmpty()) {
-        if (searchParams->keywords().startsWith("http://") ||
-                searchParams->keywords().startsWith("https://")) {
-            url.addQueryItem("q", YouTubeSearch::videoIdFromUrl(searchParams->keywords()));
-        } else url.addQueryItem("q", searchParams->keywords());
-    }
-
-    if (!searchParams->author().isEmpty())
-        url.addQueryItem("author", searchParams->author());
-
-    switch (searchParams->sortBy()) {
-    case SearchParams::SortByNewest:
-        url.addQueryItem("orderby", "published");
-        break;
-    case SearchParams::SortByViewCount:
-        url.addQueryItem("orderby", "viewCount");
-        break;
-    case SearchParams::SortByRating:
-        url.addQueryItem("orderby", "rating");
-        break;
-    }
-
-    switch (searchParams->duration()) {
-    case SearchParams::DurationShort:
-        url.addQueryItem("duration", "short");
-        break;
-    case SearchParams::DurationMedium:
-        url.addQueryItem("duration", "medium");
-        break;
-    case SearchParams::DurationLong:
-        url.addQueryItem("duration", "long");
-        break;
-    }
-
-    switch (searchParams->time()) {
-    case SearchParams::TimeToday:
-        url.addQueryItem("time", "today");
-        break;
-    case SearchParams::TimeWeek:
-        url.addQueryItem("time", "this_week");
-        break;
-    case SearchParams::TimeMonth:
-        url.addQueryItem("time", "this_month");
-        break;
-    }
-
-    switch (searchParams->quality()) {
-    case SearchParams::QualityHD:
-        url.addQueryItem("hd", "true");
-        break;
-    }
-
-    QObject *reply = The::http()->get(url);
-    connect(reply, SIGNAL(data(QByteArray)), SLOT(parseResults(QByteArray)));
-    connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(error(QNetworkReply*)));
-
-}
-
-void YouTubeSearch::error(QNetworkReply *reply) {
-    emit error(reply->errorString());
-}
-
-void YouTubeSearch::parseResults(QByteArray data) {
-
-    YouTubeStreamReader reader;
-    if (!reader.read(data)) {
-        qDebug() << "Error parsing XML";
-    }
-    videos = reader.getVideos();
-    suggestions = reader.getSuggestions();
-
-    foreach (Video *video, videos) {
-        // send it to the model
-        emit gotVideo(video);
-    }
-
-    foreach (Video *video, videos) {
-        // preload the thumb
-        if (abortFlag) return;
-        video->preloadThumbnail();
-    }
-
-    emit finished(videos.size());
-}
-
-QList<Video*> YouTubeSearch::getResults() {
-    return videos;
-}
-
-const QStringList & YouTubeSearch::getSuggestions() const {
-    return suggestions;
-}
-
-void YouTubeSearch::abort() {
-    this->abortFlag = true;
-}
-
-QString YouTubeSearch::videoIdFromUrl(QString url) {
-    QRegExp re = QRegExp("^.*\\?v=([^&]+).*$");
-    if (re.exactMatch(url)) {
-        QString videoId = re.cap(1);
-        videoId.remove('-');
-        return videoId;
-    }
-    return QString();
-}
diff --git a/src/youtubesearch.h b/src/youtubesearch.h
deleted file mode 100644 (file)
index 254ea02..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-#ifndef YOUTUBESEARCH_H
-#define YOUTUBESEARCH_H
-
-#include "video.h"
-#include "searchparams.h"
-
-class YouTubeSearch : public QObject {
-
-    Q_OBJECT
-
-public:
-    YouTubeSearch();
-    void search(SearchParams *searchParams, int max, int skip);
-    void abort();
-    QList<Video*> getResults();
-    const QStringList & getSuggestions() const;
-    static QString videoIdFromUrl(QString url);
-
-signals:
-    void gotVideo(Video*);
-    void finished(int total);
-    void error(QString message);
-
-private slots:
-    void parseResults(QByteArray data);
-    void error(QNetworkReply *reply);
-
-private:
-
-    QList<Video*> videos;
-    QStringList suggestions;
-
-    bool abortFlag;
-
-};
-
-#endif // YOUTUBESEARCH_H
diff --git a/src/youtubestreamreader.cpp b/src/youtubestreamreader.cpp
deleted file mode 100644 (file)
index 78a9675..0000000
+++ /dev/null
@@ -1,122 +0,0 @@
-#include "youtubestreamreader.h"
-#include <QtGui>
-
-
-YouTubeStreamReader::YouTubeStreamReader() {
-
-}
-
-bool YouTubeStreamReader::read(QByteArray data) {
-    addData(data);
-
-    while (!atEnd()) {
-        readNext();
-        if (isStartElement()) {
-            if (name() == "feed") {
-                while (!atEnd()) {
-                    readNext();
-                    if (isStartElement() && name() == "entry") {
-                        readEntry();
-                    } else if (name() == "link"
-                        && attributes().value("rel").toString()
-                           == "http://schemas.google.com/g/2006#spellcorrection") {
-                        suggestions << attributes().value("title").toString();
-                    }
-                }
-            }
-        }
-    }
-
-    return !error();
-}
-
-void YouTubeStreamReader::readMediaGroup() {
-
-}
-
-void YouTubeStreamReader::readEntry() {
-    Video* video = new Video();
-    // qDebug(" *** ENTRY ***");
-
-    while (!atEnd()) {
-        readNext();
-
-        /*
-        qDebug() << name();
-        QXmlStreamAttribute attribute;
-        foreach (attribute, attributes())
-            qDebug() << attribute.name() << ":" << attribute.value();
-        */
-
-        if (isEndElement() && name() == "entry") break;
-        if (isStartElement()) {
-
-            if (name() == "link"
-                && attributes().value("rel").toString() == "alternate"
-                && attributes().value("type").toString() == "text/html"
-                ) {
-                QString webpage = attributes().value("href").toString();
-                webpage.remove("&feature=youtube_gdata");
-                video->setWebpage(QUrl(webpage));
-            } else if (name() == "author") {
-                while(readNextStartElement())
-                    if (name() == "name") {
-                        QString author = readElementText();
-                        video->setAuthor(author);
-                    } else if (name() == "uri") {
-                        QString uri = readElementText();
-                        int i = uri.lastIndexOf('/');
-                        if (i != -1) uri = uri.mid(i+1);
-                        video->setAuthorUri(uri);
-                    } else skipCurrentElement();
-            } else if (name() == "published") {
-                video->setPublished(QDateTime::fromString(readElementText(), Qt::ISODate));
-            } else if (namespaceUri() == "http://gdata.youtube.com/schemas/2007"
-                       && name() == "statistics") {
-                QString viewCount = attributes().value("viewCount").toString();
-                video->setViewCount(viewCount.toInt());
-            }
-            else if (namespaceUri() == "http://search.yahoo.com/mrss/" && name() == "group") {
-
-                // read media group
-                while (!atEnd()) {
-                    readNext();
-                    if (isEndElement() && name() == "group") break;
-                    if (isStartElement()) {
-                        if (name() == "thumbnail") {
-                            // qDebug() << "Thumb: " << attributes().value("url").toString();
-                            // video->thumbnailUrls() << QUrl(attributes().value("url").toString());
-                            video->addThumbnailUrl(QUrl(attributes().value("url").toString()));
-                        }
-                        else if (name() == "title") {
-                            QString title = readElementText();
-                            // qDebug() << "Title: " << title;
-                            video->setTitle(title);
-                        }
-                        else if (name() == "description") {
-                            QString desc = readElementText();
-                            // qDebug() << "Description: " << desc;
-                            video->setDescription(desc);
-                        }
-                        else if (name() == "duration") {
-                            QString duration = attributes().value("seconds").toString();
-                            // qDebug() << "Duration: " << duration;
-                            video->setDuration(duration.toInt());
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    videos.append(video);
-
-}
-
-QList<Video*> YouTubeStreamReader::getVideos() {
-    return videos;
-}
-
-const QStringList & YouTubeStreamReader::getSuggestions() const {
-    return suggestions;
-}
diff --git a/src/youtubestreamreader.h b/src/youtubestreamreader.h
deleted file mode 100644 (file)
index 8c8c617..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-#ifndef YOUTUBESTREAMREADER_H
-#define YOUTUBESTREAMREADER_H
-
-#include <QXmlStreamReader>
-#include <QBuffer>
-#include "video.h"
-
-class YouTubeStreamReader : public QXmlStreamReader
-{
-public:
-    YouTubeStreamReader();
-    bool read(QByteArray data);
-    QList<Video*> getVideos();
-    const QStringList & getSuggestions() const;
-
-private:
-    void readMediaGroup();
-    void readEntry();
-    QList<Video*> videos;
-    QStringList suggestions;
-};
-
-#endif // YOUTUBESTREAMREADER_H
diff --git a/src/youtubesuggest.cpp b/src/youtubesuggest.cpp
deleted file mode 100644 (file)
index c1965ca..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-#include "youtubesuggest.h"
-#include <QtXml>
-#include "networkaccess.h"
-
-#define GSUGGEST_URL "http://suggestqueries.google.com/complete/search?ds=yt&output=toolbar&hl=%1&q=%2"
-
-namespace The {
-    NetworkAccess* http();
-}
-
-YouTubeSuggest::YouTubeSuggest(QObject *parent) : Suggester() {
-
-}
-
-void YouTubeSuggest::suggest(QString query) {
-    QString locale = QLocale::system().name().replace("_", "-");
-    // case for system locales such as "C"
-    if (locale.length() < 2) {
-        locale = "en-US";
-    }
-
-    QString url = QString(GSUGGEST_URL).arg(locale, query);
-
-    QObject *reply = The::http()->get(url);
-    connect(reply, SIGNAL(data(QByteArray)), SLOT(handleNetworkData(QByteArray)));
-}
-
-void YouTubeSuggest::handleNetworkData(QByteArray response) {
-    QStringList choices;
-
-    QXmlStreamReader xml(response);
-    while (!xml.atEnd()) {
-        xml.readNext();
-        if (xml.tokenType() == QXmlStreamReader::StartElement) {
-            if (xml.name() == "suggestion") {
-                QStringRef str = xml.attributes().value("data");
-                choices << str.toString();
-            }
-        }
-    }
-    emit ready(choices);
-}
diff --git a/src/youtubesuggest.h b/src/youtubesuggest.h
deleted file mode 100644 (file)
index 2d2221e..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-#ifndef YOUTUBESUGGEST_H
-#define YOUTUBESUGGEST_H
-
-#include <QtCore>
-
-#include "suggester.h"
-
-class YouTubeSuggest : public Suggester {
-
-    Q_OBJECT
-
-public:
-    YouTubeSuggest(QObject *parent = 0);
-    void suggest(QString query);
-
-signals:
-    void ready(QStringList);
-
-private slots:
-    void handleNetworkData(QByteArray response);
-
-};
-
-#endif // YOUTUBESUGGEST_H
diff --git a/src/ytfeedreader.cpp b/src/ytfeedreader.cpp
new file mode 100644 (file)
index 0000000..78a9675
--- /dev/null
@@ -0,0 +1,122 @@
+#include "youtubestreamreader.h"
+#include <QtGui>
+
+
+YouTubeStreamReader::YouTubeStreamReader() {
+
+}
+
+bool YouTubeStreamReader::read(QByteArray data) {
+    addData(data);
+
+    while (!atEnd()) {
+        readNext();
+        if (isStartElement()) {
+            if (name() == "feed") {
+                while (!atEnd()) {
+                    readNext();
+                    if (isStartElement() && name() == "entry") {
+                        readEntry();
+                    } else if (name() == "link"
+                        && attributes().value("rel").toString()
+                           == "http://schemas.google.com/g/2006#spellcorrection") {
+                        suggestions << attributes().value("title").toString();
+                    }
+                }
+            }
+        }
+    }
+
+    return !error();
+}
+
+void YouTubeStreamReader::readMediaGroup() {
+
+}
+
+void YouTubeStreamReader::readEntry() {
+    Video* video = new Video();
+    // qDebug(" *** ENTRY ***");
+
+    while (!atEnd()) {
+        readNext();
+
+        /*
+        qDebug() << name();
+        QXmlStreamAttribute attribute;
+        foreach (attribute, attributes())
+            qDebug() << attribute.name() << ":" << attribute.value();
+        */
+
+        if (isEndElement() && name() == "entry") break;
+        if (isStartElement()) {
+
+            if (name() == "link"
+                && attributes().value("rel").toString() == "alternate"
+                && attributes().value("type").toString() == "text/html"
+                ) {
+                QString webpage = attributes().value("href").toString();
+                webpage.remove("&feature=youtube_gdata");
+                video->setWebpage(QUrl(webpage));
+            } else if (name() == "author") {
+                while(readNextStartElement())
+                    if (name() == "name") {
+                        QString author = readElementText();
+                        video->setAuthor(author);
+                    } else if (name() == "uri") {
+                        QString uri = readElementText();
+                        int i = uri.lastIndexOf('/');
+                        if (i != -1) uri = uri.mid(i+1);
+                        video->setAuthorUri(uri);
+                    } else skipCurrentElement();
+            } else if (name() == "published") {
+                video->setPublished(QDateTime::fromString(readElementText(), Qt::ISODate));
+            } else if (namespaceUri() == "http://gdata.youtube.com/schemas/2007"
+                       && name() == "statistics") {
+                QString viewCount = attributes().value("viewCount").toString();
+                video->setViewCount(viewCount.toInt());
+            }
+            else if (namespaceUri() == "http://search.yahoo.com/mrss/" && name() == "group") {
+
+                // read media group
+                while (!atEnd()) {
+                    readNext();
+                    if (isEndElement() && name() == "group") break;
+                    if (isStartElement()) {
+                        if (name() == "thumbnail") {
+                            // qDebug() << "Thumb: " << attributes().value("url").toString();
+                            // video->thumbnailUrls() << QUrl(attributes().value("url").toString());
+                            video->addThumbnailUrl(QUrl(attributes().value("url").toString()));
+                        }
+                        else if (name() == "title") {
+                            QString title = readElementText();
+                            // qDebug() << "Title: " << title;
+                            video->setTitle(title);
+                        }
+                        else if (name() == "description") {
+                            QString desc = readElementText();
+                            // qDebug() << "Description: " << desc;
+                            video->setDescription(desc);
+                        }
+                        else if (name() == "duration") {
+                            QString duration = attributes().value("seconds").toString();
+                            // qDebug() << "Duration: " << duration;
+                            video->setDuration(duration.toInt());
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    videos.append(video);
+
+}
+
+QList<Video*> YouTubeStreamReader::getVideos() {
+    return videos;
+}
+
+const QStringList & YouTubeStreamReader::getSuggestions() const {
+    return suggestions;
+}
diff --git a/src/ytfeedreader.h b/src/ytfeedreader.h
new file mode 100644 (file)
index 0000000..8c8c617
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef YOUTUBESTREAMREADER_H
+#define YOUTUBESTREAMREADER_H
+
+#include <QXmlStreamReader>
+#include <QBuffer>
+#include "video.h"
+
+class YouTubeStreamReader : public QXmlStreamReader
+{
+public:
+    YouTubeStreamReader();
+    bool read(QByteArray data);
+    QList<Video*> getVideos();
+    const QStringList & getSuggestions() const;
+
+private:
+    void readMediaGroup();
+    void readEntry();
+    QList<Video*> videos;
+    QStringList suggestions;
+};
+
+#endif // YOUTUBESTREAMREADER_H
diff --git a/src/ytsuggester.cpp b/src/ytsuggester.cpp
new file mode 100644 (file)
index 0000000..c1965ca
--- /dev/null
@@ -0,0 +1,42 @@
+#include "youtubesuggest.h"
+#include <QtXml>
+#include "networkaccess.h"
+
+#define GSUGGEST_URL "http://suggestqueries.google.com/complete/search?ds=yt&output=toolbar&hl=%1&q=%2"
+
+namespace The {
+    NetworkAccess* http();
+}
+
+YouTubeSuggest::YouTubeSuggest(QObject *parent) : Suggester() {
+
+}
+
+void YouTubeSuggest::suggest(QString query) {
+    QString locale = QLocale::system().name().replace("_", "-");
+    // case for system locales such as "C"
+    if (locale.length() < 2) {
+        locale = "en-US";
+    }
+
+    QString url = QString(GSUGGEST_URL).arg(locale, query);
+
+    QObject *reply = The::http()->get(url);
+    connect(reply, SIGNAL(data(QByteArray)), SLOT(handleNetworkData(QByteArray)));
+}
+
+void YouTubeSuggest::handleNetworkData(QByteArray response) {
+    QStringList choices;
+
+    QXmlStreamReader xml(response);
+    while (!xml.atEnd()) {
+        xml.readNext();
+        if (xml.tokenType() == QXmlStreamReader::StartElement) {
+            if (xml.name() == "suggestion") {
+                QStringRef str = xml.attributes().value("data");
+                choices << str.toString();
+            }
+        }
+    }
+    emit ready(choices);
+}
diff --git a/src/ytsuggester.h b/src/ytsuggester.h
new file mode 100644 (file)
index 0000000..2d2221e
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef YOUTUBESUGGEST_H
+#define YOUTUBESUGGEST_H
+
+#include <QtCore>
+
+#include "suggester.h"
+
+class YouTubeSuggest : public Suggester {
+
+    Q_OBJECT
+
+public:
+    YouTubeSuggest(QObject *parent = 0);
+    void suggest(QString query);
+
+signals:
+    void ready(QStringList);
+
+private slots:
+    void handleNetworkData(QByteArray response);
+
+};
+
+#endif // YOUTUBESUGGEST_H