]> git.sur5r.net Git - minitube/blob - src/listmodel.cpp
Renamed uppercase filenames to lowercase
[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     if (index.isValid())
315         if (index.row() == videos.size()) {
316             // don't drag the "show 10 more" item
317             return Qt::ItemIsEnabled;
318         } else return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
319     return Qt::ItemIsDropEnabled;
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 }