1 /* GUI_LocalLibrary.cpp */ 2 3 /* Copyright (C) 2011-2020 Michael Lugmair (Lucio Carreras) 4 * 5 * This file is part of sayonara player 6 * 7 * This program is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation, either version 3 of the License, or 10 * (at your option) any later version. 11 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 17 * You should have received a copy of the GNU General Public License 18 * along with this program. If not, see <http://www.gnu.org/licenses/>. 19 */ 20 21 /* 22 * GUI_LocalLibrary.cpp 23 * 24 * Created on: Apr 24, 2011 25 * Author: Michael Lugmair (Lucio Carreras) 26 */ 27 28 #include "GUI_LocalLibrary.h" 29 #include "GUI_ImportDialog.h" 30 31 #include "Gui/Library/Utils/GUI_DeleteDialog.h" 32 #include "Gui/Library/ui_GUI_LocalLibrary.h" 33 34 #include "Gui/Library/CoverView/GUI_CoverView.h" 35 #include "Gui/Library/Utils/DirChooserDialog.h" 36 #include "Gui/Library/Utils/GUI_ReloadLibraryDialog.h" 37 #include "Gui/Library/Utils/GUI_LibraryInfoBox.h" 38 #include "Gui/Library/Utils/LocalLibraryMenu.h" 39 40 #include "Gui/Utils/Icons.h" 41 #include "Gui/Utils/Style.h" 42 #include "Gui/Utils/PreferenceAction.h" 43 #include "Gui/Utils/Shortcuts/ShortcutHandler.h" 44 #include "Gui/Utils/Shortcuts/Shortcut.h" 45 46 #include "Components/Library/LocalLibrary.h" 47 #include "Components/LibraryManagement/LibraryManager.h" 48 49 #include "Utils/Utils.h" 50 #include "Utils/FileUtils.h" 51 #include "Utils/Set.h" 52 #include "Utils/Language/Language.h" 53 #include "Utils/Settings/Settings.h" 54 #include "Utils/Library/LibraryInfo.h" 55 56 #include <QDir> 57 #include <QFileDialog> 58 #include <QStringList> 59 #include <QFileSystemWatcher> 60 61 namespace 62 { 63 // ui->swViewType 64 enum AlbumViewIndex 65 { 66 ArtistAlbumTableView = 0, 67 AlbumCoverView = 1, 68 DirectoryView = 2 69 }; 70 71 // ui->swReload 72 enum ReloadWidgetIndex 73 { 74 TableView = 0, 75 ReloadView = 1, 76 NoDirView = 2 77 }; 78 79 ReloadWidgetIndex getReloadWidgetIndex(const Library::Info& libraryInfo, bool isLibraryEmpty) 80 { 81 const auto pathExists = Util::File::exists(libraryInfo.path()); 82 if(!pathExists) 83 { 84 return ReloadWidgetIndex::NoDirView; 85 } 86 87 else if(isLibraryEmpty) 88 { 89 return ReloadWidgetIndex::ReloadView; 90 } 91 92 return ReloadWidgetIndex::TableView; 93 } 94 } 95 96 using namespace Library; 97 98 struct GUI_LocalLibrary::Private 99 { 100 LocalLibrary* library; 101 LocalLibraryMenu* libraryMenu; 102 103 Private(LocalLibrary* localLibrary, GUI_LocalLibrary* parent) : 104 library {localLibrary}, 105 libraryMenu {new LocalLibraryMenu(library->info().name(), library->info().path(), parent)} {} 106 }; 107 108 GUI_LocalLibrary::GUI_LocalLibrary(LibraryId id, Library::Manager* libraryManager, QWidget* parent) : 109 GUI_AbstractLibrary(libraryManager->libraryInstance(id), parent) 110 { 111 m = Pimpl::make<Private>(libraryManager->libraryInstance(id), this); 112 113 setupParent(this, &ui); 114 this->setFocusProxy(ui->leSearch); 115 116 ui->directoryView->init(libraryManager, id); 117 118 connect(m->library, &LocalLibrary::sigReloadingLibrary, this, &GUI_LocalLibrary::progressChanged); 119 connect(m->library, &LocalLibrary::sigReloadingLibraryFinished, this, &GUI_LocalLibrary::reloadFinished); 120 connect(m->library, &LocalLibrary::sigReloadingLibraryFinished, ui->lvGenres, &GenreView::reloadGenres); 121 connect(m->library, &LocalLibrary::sigAllTracksLoaded, this, &GUI_LocalLibrary::tracksLoaded); 122 connect(m->library, &LocalLibrary::sigImportDialogRequested, this, &GUI_LocalLibrary::importDialogRequested); 123 connect(m->library, &LocalLibrary::sigRenamed, this, &GUI_LocalLibrary::nameChanged); 124 connect(m->library, &LocalLibrary::sigPathChanged, this, &GUI_LocalLibrary::pathChanged); 125 126 connect(ui->tvAlbums, &AlbumView::sigDiscPressed, m->library, &LocalLibrary::changeCurrentDisc); 127 connect(ui->lvGenres, &GenreView::sigSelectedChanged, this, &GUI_LocalLibrary::genreSelectionChanged); 128 connect(ui->lvGenres, &GenreView::sigInvalidGenreSelected, this, &GUI_LocalLibrary::invalidGenreSelected); 129 connect(ui->lvGenres, &GenreView::sigProgress, this, &GUI_LocalLibrary::progressChanged); 130 131 connect(m->libraryMenu, &LocalLibraryMenu::sigPathChanged, m->library, &LocalLibrary::setLibraryPath); 132 connect(m->libraryMenu, &LocalLibraryMenu::sigNameChanged, m->library, &LocalLibrary::setLibraryName); 133 connect(m->libraryMenu, &LocalLibraryMenu::sigImportFile, this, &GUI_LocalLibrary::importFilesRequested); 134 connect(m->libraryMenu, &LocalLibraryMenu::sigImportFolder, this, &GUI_LocalLibrary::importDirsRequested); 135 connect(m->libraryMenu, &LocalLibraryMenu::sigInfo, this, &GUI_LocalLibrary::showInfoBox); 136 connect(m->libraryMenu, &LocalLibraryMenu::sigReloadLibrary, this, &GUI_LocalLibrary::reloadLibraryRequested); 137 138 connect(ui->btnScanForFiles, &QPushButton::clicked, this, &GUI_LocalLibrary::reloadLibraryDeepRequested); 139 connect(ui->btnImportDirectories, &QPushButton::clicked, this, &GUI_LocalLibrary::importDirsRequested); 140 141 connect(ui->splitterArtistAlbum, &QSplitter::splitterMoved, this, &GUI_LocalLibrary::splitterArtistMoved); 142 connect(ui->splitterTracks, &QSplitter::splitterMoved, this, &GUI_LocalLibrary::splitterTracksMoved); 143 connect(ui->splitterGenre, &QSplitter::splitterMoved, this, &GUI_LocalLibrary::splitterGenreMoved); 144 145 connect(ui->tvAlbums, &ItemView::sigReloadClicked, this, &GUI_LocalLibrary::reloadLibraryRequested); 146 connect(ui->tvArtists, &ItemView::sigReloadClicked, this, &GUI_LocalLibrary::reloadLibraryRequested); 147 connect(ui->tvTracks, &ItemView::sigReloadClicked, this, &GUI_LocalLibrary::reloadLibraryRequested); 148 149 ui->extensionBar->init(m->library); 150 ui->lvGenres->init(m->library); 151 ui->btnView->setOverrideText(true); 152 153 ListenSetting(Set::Lib_ViewType, GUI_LocalLibrary::switchViewType); 154 ListenSetting(Set::Lib_ShowFilterExtBar, GUI_LocalLibrary::tracksLoaded); 155 156 auto* shortcutHandler = ShortcutHandler::instance(); 157 auto shortcut = shortcutHandler->shortcut(ShortcutIdentifier::CoverView); 158 shortcut.connect(this, [this]() { 159 this->selectNextViewType(); 160 }); 161 162 const auto libraryPath = m->library->info().path(); 163 164 const auto paths = QStringList() 165 << libraryPath 166 << Util::File::getParentDirectory(libraryPath); 167 168 auto* action = new Gui::LibraryPreferenceAction(this); 169 connect(ui->btnLibraryPreferences, &QPushButton::clicked, action, &QAction::trigger); 170 171 auto* fileSystemWatcher = new QFileSystemWatcher(this); 172 fileSystemWatcher->addPaths(paths); // old qt versions don't have the new constructor 173 connect(fileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, [this]([[maybe_unused]] const auto& path) { 174 this->checkMainSplitterStatus(); 175 }); 176 } 177 178 GUI_LocalLibrary::~GUI_LocalLibrary() 179 { 180 delete ui; 181 ui = nullptr; 182 } 183 184 void GUI_LocalLibrary::checkViewState() 185 { 186 if(this->isVisible()) 187 { 188 checkMainSplitterStatus(); 189 190 if(!m->library->isReloading()) 191 { 192 checkFileExtensionBar(); 193 } 194 } 195 } 196 197 void GUI_LocalLibrary::checkMainSplitterStatus() 198 { 199 const auto isLibraryEmpty = m->library->isEmpty(); 200 const auto libraryInfo = m->library->info(); 201 202 const auto index = getReloadWidgetIndex(libraryInfo, isLibraryEmpty); 203 ui->swReload->setCurrentIndex(static_cast<int>(index)); 204 205 const auto inLibraryState = (index == ReloadWidgetIndex::TableView); 206 ui->leSearch->setVisible(inLibraryState); 207 ui->btnScanForFiles->setVisible(!inLibraryState); 208 ui->btnImportDirectories->setVisible(!inLibraryState); 209 210 if(index == ReloadWidgetIndex::NoDirView) 211 { 212 ui->labDir->setText(libraryInfo.path()); 213 } 214 215 else 216 { 217 const auto isReloading = m->library->isReloading(); 218 219 ui->pbProgress->setVisible(isReloading); 220 ui->labProgress->setVisible(isReloading); 221 ui->widgetReload->setVisible(isReloading || isLibraryEmpty); 222 m->libraryMenu->setLibraryEmpty(isLibraryEmpty); 223 } 224 } 225 226 void GUI_LocalLibrary::checkFileExtensionBar() 227 { 228 ui->extensionBar->refresh(); 229 ui->extensionBar->setVisible 230 ( 231 GetSetting(Set::Lib_ShowFilterExtBar) && 232 ui->extensionBar->hasExtensions() 233 ); 234 } 235 236 void GUI_LocalLibrary::tracksLoaded() 237 { 238 checkViewState(); 239 240 const auto info = m->library->info(); 241 242 ui->labLibraryName->setText(info.name()); 243 ui->labPath->setText(Util::createLink(info.path(), Style::isDark())); 244 245 ui->btnScanForFiles->setIcon(Gui::Icons::icon(Gui::Icons::Refresh)); 246 ui->btnImportDirectories->setIcon(Gui::Icons::icon(Gui::Icons::Folder)); 247 } 248 249 void GUI_LocalLibrary::clearSelections() 250 { 251 GUI_AbstractLibrary::clearSelections(); 252 253 if(ui->coverView) 254 { 255 ui->coverView->clearSelections(); 256 } 257 258 ui->lvGenres->clearSelection(); 259 } 260 261 void GUI_LocalLibrary::invalidGenreSelected() 262 { 263 ui->leSearch->setInvalidGenreMode(true); 264 ui->leSearch->setCurrentMode(Filter::Genre); 265 ui->leSearch->setText(GenreView::invalidGenreName()); 266 267 searchTriggered(); 268 269 ui->leSearch->setInvalidGenreMode(false); 270 } 271 272 void GUI_LocalLibrary::genreSelectionChanged(const QStringList& genres) 273 { 274 if(!genres.isEmpty()) 275 { 276 ui->leSearch->setInvalidGenreMode(false); 277 ui->leSearch->setCurrentMode(Filter::Genre); 278 ui->leSearch->setText(genres.join(",")); 279 280 searchTriggered(); 281 } 282 } 283 284 TrackDeletionMode GUI_LocalLibrary::showDeleteDialog(int track_count) 285 { 286 GUI_DeleteDialog dialog(track_count, this); 287 dialog.exec(); 288 289 return dialog.answer(); 290 } 291 292 void GUI_LocalLibrary::progressChanged(const QString& type, int progress) 293 { 294 checkViewState(); 295 296 ui->pbProgress->setMaximum((progress > 0) ? 100 : 0); 297 ui->pbProgress->setValue(progress); 298 ui->labProgress->setText 299 ( 300 this->fontMetrics().elidedText(type, Qt::ElideRight, ui->widgetReload->width() / 2) 301 ); 302 } 303 304 void GUI_LocalLibrary::reloadLibraryRequested() 305 { 306 reloadLibraryRequestedWithQuality(ReloadQuality::Unknown); 307 } 308 309 void GUI_LocalLibrary::reloadLibraryDeepRequested() 310 { 311 reloadLibraryRequestedWithQuality(ReloadQuality::Accurate); 312 } 313 314 void GUI_LocalLibrary::reloadLibraryRequestedWithQuality(ReloadQuality quality) 315 { 316 if(quality == ReloadQuality::Unknown) 317 { 318 auto* dialog = new GUI_LibraryReloadDialog(m->library->info().name(), this); 319 connect(dialog, &GUI_LibraryReloadDialog::sigAccepted, this, &GUI_LocalLibrary::reloadLibraryAccepted); 320 321 dialog->setQuality(quality); 322 dialog->show(); 323 } 324 325 else 326 { 327 reloadLibrary(quality); 328 } 329 } 330 331 void GUI_LocalLibrary::reloadLibraryAccepted(ReloadQuality quality) 332 { 333 if(sender()) 334 { 335 sender()->deleteLater(); 336 } 337 338 reloadLibrary(quality); 339 } 340 341 void GUI_LocalLibrary::reloadLibrary(ReloadQuality quality) 342 { 343 m->libraryMenu->setLibraryBusy(true); 344 m->library->reloadLibrary(false, quality); 345 } 346 347 void GUI_LocalLibrary::reloadFinished() 348 { 349 m->libraryMenu->setLibraryBusy(false); 350 checkViewState(); 351 } 352 353 void GUI_LocalLibrary::showInfoBox() 354 { 355 const auto info = m->library->info(); 356 GUI_LibraryInfoBox infoBox(info, this); 357 infoBox.exec(); 358 } 359 360 void GUI_LocalLibrary::importDirsRequested() 361 { 362 DirChooserDialog dialog(this); 363 364 if(dialog.exec() == QFileDialog::Accepted) 365 { 366 const auto dirs = dialog.selectedFiles(); 367 m->library->importFiles(dirs); 368 } 369 } 370 371 void GUI_LocalLibrary::importFilesRequested() 372 { 373 const auto files = QFileDialog::getOpenFileNames 374 ( 375 nullptr, 376 Lang::get(Lang::ImportFiles), 377 QDir::homePath(), 378 Util::getFileFilter(Util::Extensions(Util::Extension::Soundfile), tr("Audio files")) 379 ); 380 381 m->library->importFiles(files); 382 } 383 384 void GUI_LocalLibrary::nameChanged(const QString& newName) 385 { 386 m->libraryMenu->refreshName(newName); 387 ui->labLibraryName->setText(newName); 388 } 389 390 void GUI_LocalLibrary::pathChanged(const QString& newPath) 391 { 392 m->libraryMenu->refreshPath(newPath); 393 394 if(this->isVisible()) 395 { 396 reloadLibraryRequestedWithQuality(ReloadQuality::Accurate); 397 ui->labPath->setText(newPath); 398 } 399 } 400 401 void GUI_LocalLibrary::importDialogRequested(const QString& targetDirectory) 402 { 403 if(this->isVisible()) 404 { 405 auto* uiImporter = new GUI_ImportDialog(m->library, true, this); 406 uiImporter->setTargetDirectory(targetDirectory); 407 408 connect(uiImporter, &Gui::Dialog::sigClosed, uiImporter, &QObject::deleteLater); 409 uiImporter->show(); 410 } 411 } 412 413 void GUI_LocalLibrary::splitterArtistMoved([[maybe_unused]] int pos, [[maybe_unused]] int idx) 414 { 415 const auto data = ui->splitterArtistAlbum->saveState(); 416 SetSetting(Set::Lib_SplitterStateArtist, data); 417 } 418 419 void GUI_LocalLibrary::splitterTracksMoved([[maybe_unused]] int pos, [[maybe_unused]] int idx) 420 { 421 const auto data = ui->splitterTracks->saveState(); 422 SetSetting(Set::Lib_SplitterStateTrack, data); 423 } 424 425 void GUI_LocalLibrary::splitterGenreMoved([[maybe_unused]] int pos, [[maybe_unused]] int idx) 426 { 427 const auto data = ui->splitterGenre->saveState(); 428 SetSetting(Set::Lib_SplitterStateGenre, data); 429 } 430 431 void GUI_LocalLibrary::switchViewType() 432 { 433 const auto viewType = GetSetting(Set::Lib_ViewType); 434 switch(viewType) 435 { 436 case Library::ViewType::CoverView: 437 if(!ui->coverView->isInitialized()) 438 { 439 ui->coverView->init(m->library); 440 connect(ui->coverView, &GUI_CoverView::sigDeleteClicked, this, &GUI_LocalLibrary::itemDeleteClicked); 441 connect(ui->coverView, 442 &GUI_CoverView::sigReloadClicked, 443 this, 444 &GUI_LocalLibrary::reloadLibraryRequested); 445 } 446 447 if(m->library->isLoaded() && (!m->library->selectedArtists().isEmpty())) 448 { 449 m->library->selectedArtistsChanged(IndexSet()); 450 } 451 452 ui->swViewType->setCurrentIndex(AlbumViewIndex::AlbumCoverView); 453 break; 454 455 case Library::ViewType::FileView: 456 ui->swViewType->setCurrentIndex(AlbumViewIndex::DirectoryView); 457 break; 458 459 case Library::ViewType::Standard: 460 default: 461 ui->swViewType->setCurrentIndex(AlbumViewIndex::ArtistAlbumTableView); 462 break; 463 } 464 465 ui->swViewType->setFocus(); 466 } 467 468 void GUI_LocalLibrary::selectNextViewType() 469 { 470 auto viewType = static_cast<int>(GetSetting(Set::Lib_ViewType)); 471 viewType = (viewType + 1) % 3; 472 SetSetting(Set::Lib_ViewType, ViewType(viewType)); 473 } 474 475 bool GUI_LocalLibrary::hasSelections() const 476 { 477 return GUI_AbstractLibrary::hasSelections() || 478 (!ui->lvGenres->selectedItems().isEmpty()) || 479 (!ui->coverView->selectedItems().isEmpty()); 480 } 481 482 QList<Filter::Mode> GUI_LocalLibrary::searchOptions() const 483 { 484 return {Filter::Fulltext, Filter::Filename, Filter::Genre}; 485 } 486 487 void GUI_LocalLibrary::queryLibrary() 488 { 489 GUI_AbstractLibrary::queryLibrary(); 490 ui->directoryView->setFilterTerm(m->library->filter().filtertext(false).join("")); 491 } 492 493 void GUI_LocalLibrary::showEvent(QShowEvent* e) 494 { 495 GUI_AbstractLibrary::showEvent(e); 496 497 const QMap<QSplitter*, QByteArray> splitters 498 { 499 {ui->splitterArtistAlbum, GetSetting(Set::Lib_SplitterStateArtist)}, 500 {ui->splitterTracks, GetSetting(Set::Lib_SplitterStateTrack)}, 501 {ui->splitterGenre, GetSetting(Set::Lib_SplitterStateGenre)} 502 }; 503 504 for(auto it = splitters.begin(); it != splitters.end(); it++) 505 { 506 if(!it.value().isEmpty()) 507 { 508 it.key()->restoreState(it.value()); 509 } 510 } 511 512 checkViewState(); 513 m->library->load(); 514 } 515 516 void GUI_LocalLibrary::languageChanged() 517 { 518 ui->retranslateUi(this); 519 520 ui->btnLibraryPreferences->setText(Lang::get(Lang::Preferences)); 521 ui->labGenres->setText(Lang::get(Lang::Genres)); 522 ui->btnScanForFiles->setText(Lang::get(Lang::ScanForFiles)); 523 ui->btnImportDirectories->setText(Lang::get(Lang::ImportDir)); 524 525 GUI_AbstractLibrary::languageChanged(); 526 } 527 528 void GUI_LocalLibrary::skinChanged() 529 { 530 GUI_AbstractLibrary::skinChanged(); 531 532 checkViewState(); 533 } 534 535 // GUI_AbstractLibrary 536 Library::TableView* GUI_LocalLibrary::lvArtist() const { return ui->tvArtists; } 537 538 Library::TableView* GUI_LocalLibrary::lvAlbum() const { return ui->tvAlbums; } 539 540 Library::TableView* GUI_LocalLibrary::lvTracks() const { return ui->tvTracks; } 541 542 QList<QAbstractItemView*> GUI_LocalLibrary::allViews() const 543 { 544 return {ui->lvGenres, ui->tvAlbums, ui->tvArtists, ui->tvTracks}; 545 } 546 547 Library::SearchBar* GUI_LocalLibrary::leSearch() const { return ui->leSearch; } 548 549 // LocalLibraryContainer 550 QMenu* GUI_LocalLibrary::menu() const { return m->libraryMenu; } 551 552 QFrame* GUI_LocalLibrary::headerFrame() const { return ui->headerFrame; } 553