1 /* $BEGIN_LICENSE
2
3 This file is part of Musique.
4 Copyright 2013, Flavio Tordini <flavio.tordini@gmail.com>
5
6 Musique is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Musique is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Musique. If not, see <http://www.gnu.org/licenses/>.
18
19 $END_LICENSE */
20
21 #include "finderwidget.h"
22 #include "finderitemdelegate.h"
23
24 #include "segmentedcontrol.h"
25
26 #include "breadcrumb.h"
27
28 #include "playlistmodel.h"
29 #include "playlistview.h"
30
31 #include "model/album.h"
32 #include "model/artist.h"
33 #include "model/genre.h"
34
35 #include "artistlistview.h"
36 #include "artistsqlmodel.h"
37
38 #include "albumlistview.h"
39 #include "albumsqlmodel.h"
40
41 #include "genresmodel.h"
42
43 #include "tracklistview.h"
44 #include "tracksqlmodel.h"
45
46 #include "filesystemfinderview.h"
47 #include "filesystemmodel.h"
48 #include "filteringfilesystemmodel.h"
49
50 #include "searchmodel.h"
51 #include "searchview.h"
52
53 #include "database.h"
54 #include <QtSql>
55
56 namespace {
57 const QString finderViewKey = QStringLiteral("finderView");
58 }
59
FinderWidget(QWidget * parent)60 FinderWidget::FinderWidget(QWidget *parent) : QWidget(parent) {
61 fileSystemView = nullptr;
62 artistListView = nullptr;
63 albumListView = nullptr;
64 trackListView = nullptr;
65 genresListView = nullptr;
66
67 fileSystemModel = nullptr;
68 artistListModel = nullptr;
69 albumListModel = nullptr;
70 trackListModel = nullptr;
71 genresModel = nullptr;
72
73 searchView = nullptr;
74
75 // colors
76 QPalette p = palette();
77 QColor backgroundColor(0x20, 0x20, 0x20);
78 p.setBrush(QPalette::Window, backgroundColor);
79 p.setBrush(QPalette::Base, backgroundColor);
80 p.setBrush(QPalette::Text, Qt::white);
81 p.setBrush(QPalette::WindowText, Qt::white);
82
83 QBoxLayout *layout = new QVBoxLayout();
84 layout->setMargin(0);
85 layout->setSpacing(0);
86
87 setupBar();
88 layout->addWidget(finderBar);
89
90 breadcrumb = new Breadcrumb(this);
91 breadcrumb->setPalette(p);
92 breadcrumb->hide();
93 connect(breadcrumb, SIGNAL(goneBack()), SLOT(goBack()));
94 layout->addWidget(breadcrumb);
95
96 folderBreadcrumb = new Breadcrumb(this);
97 folderBreadcrumb->setPalette(p);
98 folderBreadcrumb->hide();
99 connect(folderBreadcrumb, SIGNAL(goneBack()), SLOT(folderGoBack()));
100 layout->addWidget(folderBreadcrumb);
101
102 stackedWidget = new QStackedWidget(this);
103 stackedWidget->setPalette(p);
104
105 layout->addWidget(stackedWidget);
106 setLayout(layout);
107
108 setMinimumWidth(FinderItemDelegate::ITEM_WIDTH * 3 + 4 +
109 style()->pixelMetric(QStyle::PM_ScrollBarExtent));
110 setMinimumHeight(FinderItemDelegate::ITEM_HEIGHT + finderBar->minimumHeight());
111
112 restoreSavedView();
113 }
114
restoreSavedView()115 void FinderWidget::restoreSavedView() {
116 QSettings settings;
117 QString currentViewName = settings.value(finderViewKey).toString();
118
119 if (currentViewName == "folders")
120 QTimer::singleShot(0, this, SLOT(showFolders()));
121 else if (currentViewName == "albums")
122 QTimer::singleShot(0, this, SLOT(showAlbums()));
123 else if (currentViewName == "genres")
124 QTimer::singleShot(0, this, SLOT(showGenres()));
125 else
126 QTimer::singleShot(0, this, SLOT(showArtists()));
127
128 /*
129 if (currentViewName == "folders") QTimer::singleShot(0, foldersAction, SLOT(trigger()));
130 else if (currentViewName == "albums") QTimer::singleShot(0, albumsAction, SLOT(trigger()));
131 else QTimer::singleShot(0, artistsAction, SLOT(trigger()));
132 */
133 }
134
setupBar()135 void FinderWidget::setupBar() {
136 finderBar = new SegmentedControl(this);
137
138 artistsAction = new QAction(tr("Artists"), this);
139 artistsAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_1));
140 connect(artistsAction, SIGNAL(triggered()), SLOT(showArtists()));
141 finderBar->addAction(artistsAction);
142
143 albumsAction = new QAction(tr("Albums"), this);
144 albumsAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_2));
145 connect(albumsAction, SIGNAL(triggered()), SLOT(showAlbums()));
146 finderBar->addAction(albumsAction);
147
148 genresAction = new QAction(tr("Genres"), this);
149 genresAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_3));
150 connect(genresAction, SIGNAL(triggered()), SLOT(showGenres()));
151 finderBar->addAction(genresAction);
152
153 foldersAction = new QAction(tr("Folders"), this);
154 foldersAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_4));
155 connect(foldersAction, SIGNAL(triggered()), SLOT(showFolders()));
156 finderBar->addAction(foldersAction);
157
158 for (QAction *action : finderBar->actions()) {
159 addAction(action);
160 action->setAutoRepeat(false);
161 if (!action->shortcut().isEmpty())
162 action->setStatusTip(action->text() + " (" +
163 action->shortcut().toString(QKeySequence::NativeText) + ")");
164 }
165
166 finderBar->setCheckedAction(0);
167 }
168
setupArtists()169 void FinderWidget::setupArtists() {
170 artistListModel = new ArtistSqlModel(this);
171 artistListView = new ArtistListView(stackedWidget);
172 artistListView->setEnabled(false);
173 connect(artistListView, SIGNAL(activated(const QModelIndex &)),
174 SLOT(artistActivated(const QModelIndex &)));
175 connect(artistListView, SIGNAL(play(const QModelIndex &)),
176 SLOT(artistPlayed(const QModelIndex &)));
177 artistListView->setModel(artistListModel);
178 stackedWidget->addWidget(artistListView);
179 }
180
setupAlbums()181 void FinderWidget::setupAlbums() {
182 albumListModel = new AlbumSqlModel(this);
183 albumListView = new AlbumListView(stackedWidget);
184 albumListView->setEnabled(false);
185 connect(albumListView, SIGNAL(activated(const QModelIndex &)),
186 SLOT(albumActivated(const QModelIndex &)));
187 connect(albumListView, SIGNAL(play(const QModelIndex &)),
188 SLOT(albumPlayed(const QModelIndex &)));
189 albumListView->setModel(albumListModel);
190 stackedWidget->addWidget(albumListView);
191 }
192
setupGenres()193 void FinderWidget::setupGenres() {
194 genresModel = new GenresModel(this);
195 genresListView = new FinderListView(stackedWidget);
196 genresListView->setEnabled(false);
197 connect(genresListView, SIGNAL(activated(const QModelIndex &)),
198 SLOT(genreActivated(const QModelIndex &)));
199 connect(genresListView, SIGNAL(play(const QModelIndex &)),
200 SLOT(genrePlayed(const QModelIndex &)));
201 genresListView->setModel(genresModel);
202 stackedWidget->addWidget(genresListView);
203 }
204
setupTracks()205 void FinderWidget::setupTracks() {
206 trackListModel = new TrackSqlModel(this);
207 trackListView = new TrackListView(stackedWidget);
208 connect(trackListView, SIGNAL(activated(const QModelIndex &)),
209 SLOT(trackActivated(const QModelIndex &)));
210 trackListView->setModel(trackListModel);
211 stackedWidget->addWidget(trackListView);
212 trackListView->setPalette(stackedWidget->palette());
213 }
214
setupFolders()215 void FinderWidget::setupFolders() {
216 fileSystemModel = new FileSystemModel(this);
217 fileSystemModel->setResolveSymlinks(true);
218 fileSystemModel->setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
219 filteringFileSystemModel = new FilteringFileSystemModel(this);
220 filteringFileSystemModel->setSourceModel(fileSystemModel);
221
222 fileSystemView = new FileSystemFinderView(stackedWidget);
223 connect(fileSystemView, SIGNAL(activated(const QModelIndex &)),
224 SLOT(folderActivated(const QModelIndex &)));
225 connect(fileSystemView, SIGNAL(play(const QModelIndex &)),
226 SLOT(folderPlayed(const QModelIndex &)));
227 fileSystemView->setModel(filteringFileSystemModel);
228 fileSystemView->setFileSystemModel(fileSystemModel);
229 stackedWidget->addWidget(fileSystemView);
230 }
231
setupSearch()232 void FinderWidget::setupSearch() {
233 searchModel = new SearchModel(this);
234 searchView = new SearchView(stackedWidget);
235
236 connect(searchView, SIGNAL(activated(const QModelIndex &)), searchModel,
237 SLOT(itemActivated(const QModelIndex &)));
238 connect(searchView, SIGNAL(play(const QModelIndex &)), searchModel,
239 SLOT(itemPlayed(const QModelIndex &)));
240
241 searchView->setEnabled(false);
242 searchView->setModel(searchModel);
243 stackedWidget->addWidget(searchView);
244 }
245
showArtists()246 void FinderWidget::showArtists() {
247 if (!artistListView) setupArtists();
248 artistListView->updateQuery();
249 showWidget(artistListView, true);
250 finderBar->setCheckedAction(artistsAction);
251 QSettings settings;
252 settings.setValue(finderViewKey, "artists");
253 }
254
showAlbums()255 void FinderWidget::showAlbums() {
256 if (!albumListView) setupAlbums();
257 albumListView->updateQuery();
258 albumListView->setShowToolBar(true);
259 showWidget(albumListView, true);
260 finderBar->setCheckedAction(albumsAction);
261 QSettings settings;
262 settings.setValue(finderViewKey, "albums");
263 }
264
showGenres()265 void FinderWidget::showGenres() {
266 if (!genresListView) setupGenres();
267 if (genresListView->rootIndex().isValid()) genresListView->setRootIndex(QModelIndex());
268 showWidget(genresListView, true);
269 finderBar->setCheckedAction(genresAction);
270 QSettings settings;
271 settings.setValue(finderViewKey, "genres");
272 }
273
showFolders()274 void FinderWidget::showFolders() {
275 if (!fileSystemView) setupFolders();
276
277 const QString path = Database::instance().collectionRoot();
278 fileSystemModel->setRootPath(path);
279 QSortFilterProxyModel *proxyModel =
280 static_cast<QSortFilterProxyModel *>(fileSystemView->model());
281 fileSystemView->setRootIndex(proxyModel->mapFromSource(fileSystemModel->index(path)));
282
283 showWidget(fileSystemView, true);
284 finderBar->setCheckedAction(foldersAction);
285 QSettings settings;
286 settings.setValue(finderViewKey, "folders");
287 }
288
showSearch(const QString & query)289 void FinderWidget::showSearch(const QString &query) {
290 if (query.isEmpty()) {
291 if (stackedWidget->currentWidget() == searchView) restoreSavedView();
292 return;
293 }
294
295 if (!searchView) setupSearch();
296
297 showWidget(searchView, true);
298 finderBar->setCheckedAction(-1);
299 searchModel->search(query);
300 }
301
showWidget(QWidget * widget,bool isRoot)302 void FinderWidget::showWidget(QWidget *widget, bool isRoot) {
303 // breadcrumb behaviour
304 if (isRoot) {
305 history.clear();
306 breadcrumb->clear();
307 folderBreadcrumb->clear();
308 folderBreadcrumb->hide();
309 } else {
310 breadcrumb->addItem(widget->windowTitle());
311 }
312 breadcrumb->setVisible(!isRoot);
313
314 // call disappear() on previous widget
315 QWidget *currentWidget = stackedWidget->currentWidget();
316 if (currentWidget && currentWidget != widget) {
317 bool ret = QMetaObject::invokeMethod(currentWidget, "disappear", Qt::DirectConnection);
318 if (!ret) qDebug() << "FinderWidget::showWidget invokeMethod failed for" << currentWidget;
319 }
320
321 // call appear() on new widget
322 bool ret = QMetaObject::invokeMethod(widget, "appear", Qt::DirectConnection);
323 if (!ret) qDebug() << "FinderWidget::showWidget invokeMethod failed for" << widget;
324
325 stackedWidget->setCurrentWidget(widget);
326 history.push(widget);
327
328 widget->setFocus();
329 }
330
goBack()331 void FinderWidget::goBack() {
332 if (history.size() > 1) {
333 breadcrumb->goBack();
334 breadcrumb->goBack();
335 history.pop();
336 QWidget *widget = history.pop();
337 bool isRoot = history.isEmpty();
338 showWidget(widget, isRoot);
339 if (genresListView && widget == genresListView) {
340 genresListView->setRootIndex(genresListView->rootIndex().parent());
341 }
342 }
343 }
344
folderGoBack()345 void FinderWidget::folderGoBack() {
346 folderBreadcrumb->goBack();
347 QModelIndex index = fileSystemView->rootIndex();
348 QSortFilterProxyModel *proxyModel =
349 static_cast<QSortFilterProxyModel *>(fileSystemView->model());
350 if (proxyModel) {
351 index = proxyModel->mapToSource(index);
352 QString path = fileSystemModel->filePath(index);
353 QDir dir(path);
354 dir.cdUp();
355 // qDebug() << d << folderListModel->rootPath();
356 if (dir.absolutePath() == fileSystemModel->rootDirectory().absolutePath()) {
357 folderBreadcrumb->clear();
358 folderBreadcrumb->hide();
359 }
360 index = fileSystemModel->index(dir.absolutePath(), 0);
361 qDebug() << dir.absolutePath() << index.isValid();
362 index = proxyModel->mapFromSource(index);
363 fileSystemView->setRootIndex(index);
364 }
365 }
366
appear()367 void FinderWidget::appear() {
368 QWidget *currentWidget = stackedWidget->currentWidget();
369 if (currentWidget) {
370 bool success = QMetaObject::invokeMethod(stackedWidget->currentWidget(), "appear",
371 Qt::DirectConnection);
372 if (!success) qDebug() << "Error invoking appear() on" << stackedWidget->currentWidget();
373 }
374 }
375
disappear()376 void FinderWidget::disappear() {
377 QWidget *currentWidget = stackedWidget->currentWidget();
378 if (currentWidget) {
379 bool success = QMetaObject::invokeMethod(stackedWidget->currentWidget(), "disappear",
380 Qt::DirectConnection);
381 if (!success) qDebug() << "Error invoking disappear() on" << stackedWidget->currentWidget();
382 }
383 }
384
artistActivated(const QModelIndex & index)385 void FinderWidget::artistActivated(const QModelIndex &index) {
386 // get the data object
387 const ArtistPointer artistPointer = index.data(Finder::DataObjectRole).value<ArtistPointer>();
388 Artist *artist = artistPointer.data();
389 if (artist) artistActivated(artist);
390 }
391
artistActivated(Artist * artist)392 void FinderWidget::artistActivated(Artist *artist) {
393 if (!albumListView) setupAlbums();
394
395 QString qry("select id from albums where artist=%1 and trackCount>0 order by year desc, "
396 "trackCount desc");
397 qry = qry.arg(artist->getId());
398 qDebug() << qry;
399 albumListModel->setQuery(qry, Database::instance().getConnection());
400 if (albumListModel->lastError().isValid()) qDebug() << albumListModel->lastError();
401
402 albumListView->setWindowTitle(artist->getName());
403 albumListView->scrollToTop();
404 albumListView->setShowToolBar(false);
405 showWidget(albumListView, false);
406 }
407
artistPlayed(const QModelIndex & index)408 void FinderWidget::artistPlayed(const QModelIndex &index) {
409 const ArtistPointer artistPointer = index.data(Finder::DataObjectRole).value<ArtistPointer>();
410 Artist *artist = artistPointer.data();
411 if (artist) {
412 QVector<Track *> tracks = artist->getTracks();
413 addTracksAndPlay(tracks);
414 }
415 }
416
albumActivated(Album * album)417 void FinderWidget::albumActivated(Album *album) {
418 if (!trackListView) setupTracks();
419
420 QString qry("select id from tracks where album=%1 order by disk, track, path");
421 qry = qry.arg(album->getId());
422 qDebug() << qry;
423 trackListModel->setQuery(qry, Database::instance().getConnection());
424 if (trackListModel->lastError().isValid()) qDebug() << trackListModel->lastError();
425
426 trackListView->setWindowTitle(album->getName());
427 trackListView->scrollToTop();
428 showWidget(trackListView, false);
429 }
430
albumActivated(const QModelIndex & index)431 void FinderWidget::albumActivated(const QModelIndex &index) {
432 const AlbumPointer albumPointer = index.data(Finder::DataObjectRole).value<AlbumPointer>();
433 Album *album = albumPointer.data();
434 if (album) albumActivated(album);
435 }
436
albumPlayed(const QModelIndex & index)437 void FinderWidget::albumPlayed(const QModelIndex &index) {
438 const AlbumPointer albumPointer = index.data(Finder::DataObjectRole).value<AlbumPointer>();
439 Album *album = albumPointer.data();
440 if (album) {
441 QVector<Track *> tracks = album->getTracks();
442 addTracksAndPlay(tracks);
443 }
444 }
445
trackActivated(Track * track)446 void FinderWidget::trackActivated(Track *track) {
447 playlistModel->addTrack(track);
448 playlistModel->setActiveRow(playlistModel->rowForTrack(track));
449 }
450
trackActivated(const QModelIndex & index)451 void FinderWidget::trackActivated(const QModelIndex &index) {
452 const TrackPointer trackPointer = index.data(Finder::DataObjectRole).value<TrackPointer>();
453 Track *track = trackPointer.data();
454 if (track) trackActivated(track);
455 }
456
folderActivated(const QModelIndex & index)457 void FinderWidget::folderActivated(const QModelIndex &index) {
458 const TrackPointer trackPointer = index.data(Finder::DataObjectRole).value<TrackPointer>();
459 Track *track = trackPointer.data();
460 if (track) {
461 addTracksAndPlay(track->getTracks());
462 } else {
463 fileSystemView->setRootIndex(index);
464 const FolderPointer folderPointer =
465 index.data(Finder::DataObjectRole).value<FolderPointer>();
466 Folder *folder = folderPointer.data();
467 if (folder) {
468 folderBreadcrumb->addItem(folder->getName());
469 folderBreadcrumb->setVisible(true);
470 }
471 }
472 }
473
folderPlayed(const QModelIndex & index)474 void FinderWidget::folderPlayed(const QModelIndex &index) {
475 const FolderPointer folderPointer = index.data(Finder::DataObjectRole).value<FolderPointer>();
476 Folder *folder = folderPointer.data();
477 if (folder) {
478 QVector<Track *> tracks = folder->getTracks();
479 addTracksAndPlay(tracks);
480 } else {
481 const TrackPointer trackPointer = index.data(Finder::DataObjectRole).value<TrackPointer>();
482 Track *track = trackPointer.data();
483 if (track) {
484 addTracksAndPlay(track->getTracks());
485 }
486 }
487 }
488
genreActivated(const QModelIndex & index)489 void FinderWidget::genreActivated(const QModelIndex &index) {
490 qDebug() << "Activating index" << index;
491 Genre *genre = index.data(Finder::DataObjectRole).value<GenrePointer>().data();
492 if (genre && !genre->getChildren().isEmpty()) {
493 genresListView->setWindowTitle(genre->getName());
494 genresListView->setRootIndex(index);
495 showWidget(genresListView, false);
496 }
497 }
498
genrePlayed(const QModelIndex & index)499 void FinderWidget::genrePlayed(const QModelIndex &index) {
500 Item *item = index.data(Finder::ItemObjectRole).value<ItemPointer>().data();
501 if (item) addTracksAndPlay(item->getTracks());
502 }
503
addTracksAndPlay(const QVector<Track * > & tracks)504 void FinderWidget::addTracksAndPlay(const QVector<Track *> &tracks) {
505 if (tracks.isEmpty()) return;
506 playlistModel->addTracks(tracks);
507
508 Track *trackToPlay = nullptr;
509
510 QSettings settings;
511 const bool shuffle = settings.value("shuffle").toBool();
512 if (shuffle) {
513 int nextRow = (int)((float)qrand() / (float)RAND_MAX * tracks.size());
514 trackToPlay = tracks.at(nextRow);
515 } else {
516 trackToPlay = tracks.first();
517 }
518 playlistModel->setActiveRow(playlistModel->rowForTrack(trackToPlay));
519 playlistView->scrollTo(playlistModel->indexForTrack(trackToPlay),
520 QAbstractItemView::PositionAtCenter);
521 }
522