]> git.sur5r.net Git - minitube/blob - src/ListModel.cpp
Imported Upstream version 1.9
[minitube] / src / ListModel.cpp
1 #include "ListModel.h"
2 #include "videomimedata.h"
3
4 #define MAX_ITEMS 10
5 static const QString recentKeywordsKey = "recentKeywords";
6 static const QString recentChannelsKey = "recentChannels";
7
8 ListModel::ListModel(QWidget *parent) : QAbstractListModel(parent) {
9     youtubeSearch = 0;
10     searching = false;
11     canSearchMore = true;
12     m_activeVideo = 0;
13     m_activeRow = -1;
14     skip = 1;
15
16     hoveredRow = -1;
17     authorHovered = false;
18     authorPressed = false;
19 }
20
21 ListModel::~ListModel() {
22     delete youtubeSearch;
23 }
24
25 int ListModel::rowCount(const QModelIndex &/*parent*/) const {
26     int count = videos.size();
27     
28     // add the message item
29     if (videos.isEmpty() || !searching)
30         count++;
31     
32     return count;
33 }
34
35 QVariant ListModel::data(const QModelIndex &index, int role) const {
36     
37     int row = index.row();
38     
39     if (row == videos.size()) {
40         
41         QPalette palette;
42         QFont boldFont;
43         boldFont.setBold(true);
44         
45         switch (role) {
46         case ItemTypeRole:
47             return ItemTypeShowMore;
48         case Qt::DisplayRole:
49         case Qt::StatusTipRole:
50             if (!errorMessage.isEmpty()) return errorMessage;
51             if (searching) return tr("Searching...");
52             if (canSearchMore) return tr("Show %1 More").arg(MAX_ITEMS);
53             if (videos.isEmpty()) return tr("No videos");
54             else return tr("No more videos");
55         case Qt::TextAlignmentRole:
56             return QVariant(int(Qt::AlignHCenter | Qt::AlignVCenter));
57         case Qt::ForegroundRole:
58             if (!errorMessage.isEmpty())
59                 return palette.color(QPalette::ToolTipText);
60             else
61                 return palette.color(QPalette::Dark);
62         case Qt::BackgroundColorRole:
63             if (!errorMessage.isEmpty())
64                 return palette.color(QPalette::ToolTipBase);
65             else
66                 return QVariant();
67         case Qt::FontRole:
68             return boldFont;
69         default:
70             return QVariant();
71         }
72         
73     } else if (row < 0 || row >= videos.size())
74         return QVariant();
75     
76     Video *video = videos.at(row);
77     
78     switch (role) {
79     case ItemTypeRole:
80         return ItemTypeVideo;
81     case VideoRole:
82         return QVariant::fromValue(QPointer<Video>(video));
83     case ActiveTrackRole:
84         return video == m_activeVideo;
85     case Qt::DisplayRole:
86         return video->title();
87     case HoveredItemRole:
88         return hoveredRow == index.row();
89     case AuthorHoveredRole:
90         return authorHovered;
91     case AuthorPressedRole:
92         return authorPressed;
93     }
94     
95     return QVariant();
96 }
97
98 void ListModel::setActiveRow( int row) {
99     if ( rowExists( row ) ) {
100         
101         m_activeRow = row;
102         m_activeVideo = videoAt(row);
103         
104         int oldactiverow = m_activeRow;
105         
106         if ( rowExists( oldactiverow ) )
107             emit dataChanged( createIndex( oldactiverow, 0 ), createIndex( oldactiverow, columnCount() - 1 ) );
108         
109         emit dataChanged( createIndex( m_activeRow, 0 ), createIndex( m_activeRow, columnCount() - 1 ) );
110         emit activeRowChanged(row);
111         
112     } else {
113         m_activeRow = -1;
114         m_activeVideo = 0;
115     }
116
117 }
118
119 int ListModel::nextRow() const {
120     int nextRow = m_activeRow + 1;
121     if (rowExists(nextRow))
122         return nextRow;
123     return -1;
124 }
125
126 int ListModel::previousRow() const {
127     int prevRow = m_activeRow - 1;
128     if (rowExists(prevRow))
129         return prevRow;
130     return -1;
131 }
132
133 Video* ListModel::videoAt( int row ) const {
134     if ( rowExists( row ) )
135         return videos.at( row );
136     return 0;
137 }
138
139 Video* ListModel::activeVideo() const {
140     return m_activeVideo;
141 }
142
143 void ListModel::search(SearchParams *searchParams) {
144
145     // delete current videos
146     while (!videos.isEmpty())
147         delete videos.takeFirst();
148     m_activeVideo = 0;
149     m_activeRow = -1;
150     skip = 1;
151     errorMessage.clear();
152     reset();
153
154     // (re)initialize the YouTubeSearch
155     if (youtubeSearch) delete youtubeSearch;
156     youtubeSearch = new YouTubeSearch();
157     connect(youtubeSearch, SIGNAL(gotVideo(Video*)), this, SLOT(addVideo(Video*)));
158     connect(youtubeSearch, SIGNAL(finished(int)), this, SLOT(searchFinished(int)));
159     connect(youtubeSearch, SIGNAL(error(QString)), this, SLOT(searchError(QString)));
160
161     this->searchParams = searchParams;
162     searching = true;
163     youtubeSearch->search(searchParams, MAX_ITEMS, skip);
164     skip += MAX_ITEMS;
165 }
166
167 void ListModel::searchMore(int max) {
168     if (searching) return;
169     searching = true;
170     errorMessage.clear();
171     youtubeSearch->search(searchParams, max, skip);
172     skip += max;
173 }
174
175 void ListModel::searchMore() {
176     searchMore(MAX_ITEMS);
177 }
178
179 void ListModel::searchNeeded() {
180     int remainingRows = videos.size() - m_activeRow;
181     int rowsNeeded = MAX_ITEMS - remainingRows;
182     if (rowsNeeded > 0)
183         searchMore(rowsNeeded);
184 }
185
186 void ListModel::abortSearch() {
187     while (!videos.isEmpty())
188         delete videos.takeFirst();
189     reset();
190     youtubeSearch->abort();
191     searching = false;
192 }
193
194 void ListModel::searchFinished(int total) {
195     searching = false;
196     canSearchMore = total > 0;
197
198     // update the message item
199     emit dataChanged( createIndex( MAX_ITEMS, 0 ), createIndex( MAX_ITEMS, columnCount() - 1 ) );
200
201     if (!youtubeSearch->getSuggestions().isEmpty()) {
202         emit haveSuggestions(youtubeSearch->getSuggestions());
203     }
204 }
205
206 void ListModel::searchError(QString message) {
207     errorMessage = message;
208     // update the message item
209     emit dataChanged( createIndex( MAX_ITEMS, 0 ), createIndex( MAX_ITEMS, columnCount() - 1 ) );
210 }
211
212 void ListModel::addVideo(Video* video) {
213     
214     connect(video, SIGNAL(gotThumbnail()), this, SLOT(updateThumbnail()));
215
216     beginInsertRows(QModelIndex(), videos.size(), videos.size());
217     videos << video;
218     endInsertRows();
219     
220     // first result!
221     if (videos.size() == 1) {
222
223         // manualplay
224         QSettings settings;
225         if (!settings.value("manualplay", false).toBool())
226             setActiveRow(0);
227
228         // save keyword
229         QString query = searchParams->keywords();
230         if (!query.isEmpty() && !searchParams->isTransient()) {
231             if (query.startsWith("http://")) {
232                 // Save the video title
233                 query += "|" + videos.first()->title();
234             }
235             QStringList keywords = settings.value(recentKeywordsKey).toStringList();
236             keywords.removeAll(query);
237             keywords.prepend(query);
238             while (keywords.size() > 10)
239                 keywords.removeLast();
240             settings.setValue(recentKeywordsKey, keywords);
241         }
242
243         // save channel
244         QString channel = searchParams->author();
245         if (!channel.isEmpty() && !searchParams->isTransient()) {
246             QSettings settings;
247             QStringList channels = settings.value(recentChannelsKey).toStringList();
248             channels.removeAll(channel);
249             channels.prepend(channel);
250             while (channels.size() > 10)
251                 channels.removeLast();
252             settings.setValue(recentChannelsKey, channels);
253         }
254
255     }
256
257 }
258
259 void ListModel::updateThumbnail() {
260
261     Video *video = static_cast<Video *>(sender());
262     if (!video) {
263         qDebug() << "Cannot get sender";
264         return;
265     }
266
267     int row = rowForVideo(video);
268     emit dataChanged( createIndex( row, 0 ), createIndex( row, columnCount() - 1 ) );
269
270 }
271
272 // --- item removal
273
274 /**
275   * This function does not free memory
276   */
277 bool ListModel::removeRows(int position, int rows, const QModelIndex & /*parent*/) {
278     beginRemoveRows(QModelIndex(), position, position+rows-1);
279     for (int row = 0; row < rows; ++row) {
280         videos.removeAt(position);
281     }
282     endRemoveRows();
283     return true;
284 }
285
286 void ListModel::removeIndexes(QModelIndexList &indexes) {
287     QList<Video*> originalList(videos);
288     QList<Video*> delitems;
289     foreach (QModelIndex index, indexes) {
290         if (index.row() >= originalList.size()) continue;
291         Video* video = originalList.at(index.row());
292         int idx = videos.indexOf(video);
293         if (idx != -1) {
294             beginRemoveRows(QModelIndex(), idx, idx);
295             delitems.append(video);
296             videos.removeAll(video);
297             endRemoveRows();
298         }
299     }
300
301     qDeleteAll(delitems);
302
303 }
304
305 // --- Sturm und drang ---
306
307
308
309 Qt::DropActions ListModel::supportedDropActions() const {
310     return Qt::MoveAction;
311 }
312
313 Qt::ItemFlags ListModel::flags(const QModelIndex &index) const {
314     Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
315
316     if (index.isValid()) {
317         if (index.row() == videos.size()) {
318             // don't drag the "show 10 more" item
319             return defaultFlags;
320         } else
321             return ( defaultFlags | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled );
322     } else
323         return Qt::ItemIsDropEnabled | defaultFlags;
324 }
325
326 QStringList ListModel::mimeTypes() const {
327     QStringList types;
328     types << "application/x-minitube-video";
329     return types;
330 }
331
332 QMimeData* ListModel::mimeData( const QModelIndexList &indexes ) const {
333     VideoMimeData* mime = new VideoMimeData();
334
335     foreach( const QModelIndex &it, indexes ) {
336         int row = it.row();
337         if (row >= 0 && row < videos.size())
338             mime->addVideo( videos.at( it.row() ) );
339     }
340
341     return mime;
342 }
343
344 bool ListModel::dropMimeData(const QMimeData *data,
345                              Qt::DropAction action, int row, int column,
346                              const QModelIndex &parent) {
347     if (action == Qt::IgnoreAction)
348         return true;
349
350     if (!data->hasFormat("application/x-minitube-video"))
351         return false;
352
353     if (column > 0)
354         return false;
355
356     int beginRow;
357     if (row != -1)
358         beginRow = row;
359     else if (parent.isValid())
360         beginRow = parent.row();
361     else
362         beginRow = rowCount(QModelIndex());
363
364     const VideoMimeData* videoMimeData = dynamic_cast<const VideoMimeData*>( data );
365     if(!videoMimeData ) return false;
366
367     QList<Video*> droppedVideos = videoMimeData->videos();
368     foreach( Video *video, droppedVideos) {
369         
370         // remove videos
371         int videoRow = videos.indexOf(video);
372         removeRows(videoRow, 1, QModelIndex());
373         
374         // and then add them again at the new position
375         beginInsertRows(QModelIndex(), beginRow, beginRow);
376         videos.insert(beginRow, video);
377         endInsertRows();
378
379     }
380
381     // fix m_activeRow after all this
382     m_activeRow = videos.indexOf(m_activeVideo);
383
384     // let the MediaView restore the selection
385     emit needSelectionFor(droppedVideos);
386
387     return true;
388
389 }
390
391 int ListModel::rowForVideo(Video* video) {
392     return videos.indexOf(video);
393 }
394
395 QModelIndex ListModel::indexForVideo(Video* video) {
396     return createIndex(videos.indexOf(video), 0);
397 }
398
399 void ListModel::move(QModelIndexList &indexes, bool up) {
400     QList<Video*> movedVideos;
401
402     foreach (QModelIndex index, indexes) {
403         int row = index.row();
404         if (row >= videos.size()) continue;
405         // qDebug() << "index row" << row;
406         Video *video = videoAt(row);
407         movedVideos << video;
408     }
409
410     int end=up ? -1 : rowCount()-1, mod=up ? -1 : 1;
411     foreach (Video *video, movedVideos) {
412
413         int row = rowForVideo(video);
414         if (row+mod==end) { end=row; continue; }
415         // qDebug() << "video row" << row;
416         removeRows(row, 1, QModelIndex());
417
418         if (up) row--;
419         else row++;
420
421         beginInsertRows(QModelIndex(), row, row);
422         videos.insert(row, video);
423         endInsertRows();
424
425     }
426
427     emit needSelectionFor(movedVideos);
428
429 }
430
431 /* row hovering */
432
433 void ListModel::setHoveredRow(int row) {
434     int oldRow = hoveredRow;
435     hoveredRow = row;
436     emit dataChanged( createIndex( oldRow, 0 ), createIndex( oldRow, columnCount() - 1 ) );
437     emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
438 }
439
440 void ListModel::clearHover() {
441     emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
442     hoveredRow = -1;
443 }
444
445 /* clickable author */
446
447 void ListModel::enterAuthorHover() {
448     if (authorHovered) return;
449     authorHovered = true;
450     updateAuthor();
451 }
452
453 void ListModel::exitAuthorHover() {
454     if (!authorHovered) return;
455     authorHovered = false;
456     updateAuthor();
457     setHoveredRow(hoveredRow);
458 }
459
460 void ListModel::enterAuthorPressed() {
461     if (authorPressed) return;
462     authorPressed = true;
463     updateAuthor();
464 }
465
466 void ListModel::exitAuthorPressed() {
467     if (!authorPressed) return;
468     authorPressed = false;
469     updateAuthor();
470 }
471
472 void ListModel::updateAuthor() {
473     emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
474 }