]> git.sur5r.net Git - minitube/blob - src/ListModel.cpp
468435f8908060b7cabf170de1825183d0792f23
[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
202 void ListModel::searchError(QString message) {
203     errorMessage = message;
204     // update the message item
205     emit dataChanged( createIndex( MAX_ITEMS, 0 ), createIndex( MAX_ITEMS, columnCount() - 1 ) );
206 }
207
208 void ListModel::addVideo(Video* video) {
209     
210     connect(video, SIGNAL(gotThumbnail()), this, SLOT(updateThumbnail()));
211
212     beginInsertRows(QModelIndex(), videos.size(), videos.size());
213     videos << video;
214     endInsertRows();
215     
216     // first result!
217     if (videos.size() == 1) {
218
219         // manualplay
220         QSettings settings;
221         if (!settings.value("manualplay", false).toBool())
222             setActiveRow(0);
223
224         // save keyword
225         QString query = searchParams->keywords();
226         if (!query.isEmpty() && !searchParams->isTransient()) {
227             if (query.startsWith("http://")) {
228                 // Save the video title
229                 query += "|" + videos.first()->title();
230             }
231             QStringList keywords = settings.value(recentKeywordsKey).toStringList();
232             keywords.removeAll(query);
233             keywords.prepend(query);
234             while (keywords.size() > 10)
235                 keywords.removeLast();
236             settings.setValue(recentKeywordsKey, keywords);
237         }
238
239         // save channel
240         QString channel = searchParams->author();
241         if (!channel.isEmpty() && !searchParams->isTransient()) {
242             QSettings settings;
243             QStringList channels = settings.value(recentChannelsKey).toStringList();
244             channels.removeAll(channel);
245             channels.prepend(channel);
246             while (channels.size() > 10)
247                 channels.removeLast();
248             settings.setValue(recentChannelsKey, channels);
249         }
250
251     }
252
253 }
254
255 void ListModel::updateThumbnail() {
256
257     Video *video = static_cast<Video *>(sender());
258     if (!video) {
259         qDebug() << "Cannot get sender";
260         return;
261     }
262
263     int row = rowForVideo(video);
264     emit dataChanged( createIndex( row, 0 ), createIndex( row, columnCount() - 1 ) );
265
266 }
267
268 // --- item removal
269
270 /**
271   * This function does not free memory
272   */
273 bool ListModel::removeRows(int position, int rows, const QModelIndex & /*parent*/) {
274     beginRemoveRows(QModelIndex(), position, position+rows-1);
275     for (int row = 0; row < rows; ++row) {
276         videos.removeAt(position);
277     }
278     endRemoveRows();
279     return true;
280 }
281
282 void ListModel::removeIndexes(QModelIndexList &indexes) {
283     QList<Video*> originalList(videos);
284     QList<Video*> delitems;
285     foreach (QModelIndex index, indexes) {
286         if (index.row() >= originalList.size()) continue;
287         Video* video = originalList.at(index.row());
288         int idx = videos.indexOf(video);
289         if (idx != -1) {
290             beginRemoveRows(QModelIndex(), idx, idx);
291             delitems.append(video);
292             videos.removeAll(video);
293             endRemoveRows();
294         }
295     }
296
297     qDeleteAll(delitems);
298
299 }
300
301 // --- Sturm und drang ---
302
303
304
305 Qt::DropActions ListModel::supportedDropActions() const {
306     return Qt::MoveAction;
307 }
308
309 Qt::ItemFlags ListModel::flags(const QModelIndex &index) const {
310     Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
311
312     if (index.isValid()) {
313         if (index.row() == videos.size()) {
314             // don't drag the "show 10 more" item
315             return defaultFlags;
316         } else
317             return ( defaultFlags | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled );
318     } else
319         return Qt::ItemIsDropEnabled | defaultFlags;
320 }
321
322 QStringList ListModel::mimeTypes() const {
323     QStringList types;
324     types << "application/x-minitube-video";
325     return types;
326 }
327
328 QMimeData* ListModel::mimeData( const QModelIndexList &indexes ) const {
329     VideoMimeData* mime = new VideoMimeData();
330
331     foreach( const QModelIndex &it, indexes ) {
332         int row = it.row();
333         if (row >= 0 && row < videos.size())
334             mime->addVideo( videos.at( it.row() ) );
335     }
336
337     return mime;
338 }
339
340 bool ListModel::dropMimeData(const QMimeData *data,
341                              Qt::DropAction action, int row, int column,
342                              const QModelIndex &parent) {
343     if (action == Qt::IgnoreAction)
344         return true;
345
346     if (!data->hasFormat("application/x-minitube-video"))
347         return false;
348
349     if (column > 0)
350         return false;
351
352     int beginRow;
353     if (row != -1)
354         beginRow = row;
355     else if (parent.isValid())
356         beginRow = parent.row();
357     else
358         beginRow = rowCount(QModelIndex());
359
360     const VideoMimeData* videoMimeData = dynamic_cast<const VideoMimeData*>( data );
361     if(!videoMimeData ) return false;
362
363     QList<Video*> droppedVideos = videoMimeData->videos();
364     foreach( Video *video, droppedVideos) {
365         
366         // remove videos
367         int videoRow = videos.indexOf(video);
368         removeRows(videoRow, 1, QModelIndex());
369         
370         // and then add them again at the new position
371         beginInsertRows(QModelIndex(), beginRow, beginRow);
372         videos.insert(beginRow, video);
373         endInsertRows();
374
375     }
376
377     // fix m_activeRow after all this
378     m_activeRow = videos.indexOf(m_activeVideo);
379
380     // let the MediaView restore the selection
381     emit needSelectionFor(droppedVideos);
382
383     return true;
384
385 }
386
387 int ListModel::rowForVideo(Video* video) {
388     return videos.indexOf(video);
389 }
390
391 QModelIndex ListModel::indexForVideo(Video* video) {
392     return createIndex(videos.indexOf(video), 0);
393 }
394
395 void ListModel::move(QModelIndexList &indexes, bool up) {
396     QList<Video*> movedVideos;
397
398     foreach (QModelIndex index, indexes) {
399         int row = index.row();
400         if (row >= videos.size()) continue;
401         // qDebug() << "index row" << row;
402         Video *video = videoAt(row);
403         movedVideos << video;
404     }
405
406     int end=up ? -1 : rowCount()-1, mod=up ? -1 : 1;
407     foreach (Video *video, movedVideos) {
408
409         int row = rowForVideo(video);
410         if (row+mod==end) { end=row; continue; }
411         // qDebug() << "video row" << row;
412         removeRows(row, 1, QModelIndex());
413
414         if (up) row--;
415         else row++;
416
417         beginInsertRows(QModelIndex(), row, row);
418         videos.insert(row, video);
419         endInsertRows();
420
421     }
422
423     emit needSelectionFor(movedVideos);
424
425 }
426
427 /* row hovering */
428
429 void ListModel::setHoveredRow(int row) {
430     int oldRow = hoveredRow;
431     hoveredRow = row;
432     emit dataChanged( createIndex( oldRow, 0 ), createIndex( oldRow, columnCount() - 1 ) );
433     emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
434 }
435
436 void ListModel::clearHover() {
437     emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
438     hoveredRow = -1;
439 }
440
441 /* clickable author */
442
443 void ListModel::enterAuthorHover() {
444     if (authorHovered) return;
445     authorHovered = true;
446     updateAuthor();
447 }
448
449 void ListModel::exitAuthorHover() {
450     if (!authorHovered) return;
451     authorHovered = false;
452     updateAuthor();
453     setHoveredRow(hoveredRow);
454 }
455
456 void ListModel::enterAuthorPressed() {
457     if (authorPressed) return;
458     authorPressed = true;
459     updateAuthor();
460 }
461
462 void ListModel::exitAuthorPressed() {
463     if (!authorPressed) return;
464     authorPressed = false;
465     updateAuthor();
466 }
467
468 void ListModel::updateAuthor() {
469     emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
470 }