#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "playerwidget.h" #include "beetview.h" #include "indexerdialog.h" #include "globals.h" #include "helper.h" #include "webdownloader.h" #include "webradiodialog.h" #include "collectionalbumsview.h" #include "collectionartistsview.h" #include "collectiondatesview.h" #include "collectionfavoritesview.h" #include "collectionfoldersview.h" #include "collectionwebradioview.h" #include "collectionwidgetproxy.h" PlayerWidget::PlayerWidget(QSplashScreen *splash, QWidget *parent) : QWidget(parent), mDurSecs(0), mPlayListLength(0) { mStarting = true; setupGui(splash); createActions(); mWebDownloader = new WebDownloader(this); connect(mWebDownloader, &WebDownloader::done, this, &PlayerWidget::webDlDone); mStarting = false; mStopA->trigger(); } PlayerWidget::~PlayerWidget(){ writeSettings(); } void PlayerWidget::setupGui(QSplashScreen *splash){ //the Player mPlayer = new QMediaPlayer(this); connect(mPlayer, &QMediaPlayer::positionChanged, this, &PlayerWidget::setPosition); connect(mPlayer, &QMediaPlayer::mediaStatusChanged, this, &PlayerWidget::continuePlaying); //tray icon mTrayIcon = new QSystemTrayIcon(this); mTrayIcon->setIcon(QIcon(":/stop.png")); mTrayIcon->show(); mVolumeTimer = new QTimer(this); mVolumeTimer->setSingleShot(true); connect(mVolumeTimer, &QTimer::timeout, this, &PlayerWidget::showVolume); //THE views QToolBar *bottomTB = new QToolBar; mBottomTBG = new QActionGroup(this); mBottomTBG->setExclusive(true); mCollectionStack = new QStackedWidget; //artists view CollectionArtistsView *artistsView = new CollectionArtistsView; artistsView->setHeaders(QStringList() << tr("Artist")); artistsView->setObjectName("artists"); splash->showMessage(tr("Populating Artists..."), Qt::AlignHCenter, Qt::yellow); qApp->processEvents(); artistsView->populate(); int cIdx = mCollectionStack->addWidget(artistsView); QAction *viewByArtistsA = new QAction(QIcon(":/artist.png"), tr("View by artist"), this); viewByArtistsA->setCheckable(true); connect(viewByArtistsA, &QAction::triggered, [this, cIdx] { setCollectionIndex(cIdx, false); }); mBottomTBG->addAction(viewByArtistsA); //albums view CollectionAlbumsView *albumsView = new CollectionAlbumsView; albumsView->setHeaders(QStringList() << tr("Album")); albumsView->setObjectName("albums"); splash->showMessage(tr("Populating Albums..."), Qt::AlignHCenter, Qt::yellow); qApp->processEvents(); albumsView->populate(); cIdx = mCollectionStack->addWidget(albumsView); mViewByAlbumsA = new QAction(QIcon(":/album.png"), tr("View by album"), this); mViewByAlbumsA->setCheckable(true); connect(mViewByAlbumsA, &QAction::triggered, [this, cIdx] { setCollectionIndex(cIdx, false); }); mBottomTBG->addAction(mViewByAlbumsA); //dates view CollectionDatesView *datesView = new CollectionDatesView;; datesView->setHeaders(QStringList() << tr("Date added")); datesView->setObjectName("dates"); splash->showMessage(tr("Populating Dates..."), Qt::AlignHCenter, Qt::yellow); qApp->processEvents(); datesView->populate(); cIdx = mCollectionStack->addWidget(datesView); QAction *viewByDatesA = new QAction(QIcon(":/sissyd.png"), tr("View by date"), this); viewByDatesA->setCheckable(true); connect(viewByDatesA, &QAction::triggered, [this, cIdx] { setCollectionIndex(cIdx, false); }); mBottomTBG->addAction(viewByDatesA); //favorites view CollectionFavoritesView *favoritesView = new CollectionFavoritesView; favoritesView->setHeaders(QStringList() << tr("Song")); favoritesView->setObjectName("favorites"); splash->showMessage(tr("Populating Favorites..."), Qt::AlignHCenter, Qt::yellow); qApp->processEvents(); favoritesView->populate(); cIdx = mCollectionStack->addWidget(favoritesView); QAction *viewByFavoritesA = new QAction(QIcon(":/male_chastity_belt.png"), tr("View by favorites"), this); viewByFavoritesA->setCheckable(true); connect(viewByFavoritesA, &QAction::triggered, [this, cIdx] { setCollectionIndex(cIdx, false); }); mBottomTBG->addAction(viewByFavoritesA); //webradio view CollectionWebradioView *webradioView = new CollectionWebradioView; webradioView->setHeaders(QStringList() << tr("Webradio")); webradioView->setObjectName("webradio"); splash->showMessage(tr("Populating Webradio..."), Qt::AlignHCenter, Qt::yellow); qApp->processEvents(); webradioView->populate(); cIdx = mCollectionStack->addWidget(webradioView); QAction *viewByWebradioA = new QAction(QIcon(":/dog_hood_light.png"), tr("Webradio"), this); viewByWebradioA->setCheckable(true); connect(viewByWebradioA, &QAction::triggered, [this, cIdx] { setCollectionIndex(cIdx, true); }); mBottomTBG->addAction(viewByWebradioA); //folders view CollectionFoldersView *foldersView = new CollectionFoldersView; foldersView->setHeaders(QStringList() << tr("Folder contents")); foldersView->setObjectName("foldersview"); splash->showMessage(tr("Populating Folders..."), Qt::AlignHCenter, Qt::yellow); qApp->processEvents(); foldersView->populate(); cIdx = mCollectionStack->addWidget(foldersView); QAction *viewByFoldersA = new QAction(QIcon(":/folder.png"), tr("View by folder"), this); viewByFoldersA->setCheckable(true); connect(viewByFoldersA, &QAction::triggered, [this, cIdx] { setCollectionIndex(cIdx, false); }); mBottomTBG->addAction(viewByFoldersA); for(QAction *a : mBottomTBG->actions()){ bottomTB->addAction(a); } for(int i = 0; i < mCollectionStack->count(); ++i){ CollectionWidget *curW = qobject_cast(mCollectionStack->widget(i)); const QString name = curW->objectName(); // special case webradio if(name == "webradio"){ QAction *playA = new QAction(QIcon(":/play.png"), tr("Play"), this); connect(playA, &QAction::triggered, [this, curW] { playUrl(curW->view()->selectionModel()->currentIndex()); }); curW->view()->addAction(playA); QAction *refreshA = new QAction(QIcon(":/refresh.png"), tr("Refresh view"), this); connect(refreshA, &QAction::triggered, [curW] { qApp->setOverrideCursor(Qt::BusyCursor); curW->populate(); qApp->restoreOverrideCursor(); } ); curW->view()->addAction(Helper::createSeparator(this)); curW->view()->addAction(refreshA); curW->view()->addAction(Helper::createSeparator(this)); QAction *editA = new QAction(QIcon(":/quadd.png"), tr("Edit..."), this); connect(editA, &QAction::triggered, [this, refreshA] { WebRadioDialog dlg(this); dlg.exec(); refreshA->trigger(); }); curW->view()->addAction(editA); connect(curW->view(), &BeetView::doubleClicked, playA, &QAction::trigger); }else{ QAction *addToPlayListA = new QAction(QIcon(":/belly_right.png"), tr("Add to playlist"), this); connect(addToPlayListA, &QAction::triggered, this, &PlayerWidget::addToPlayList); curW->view()->addAction(addToPlayListA); QAction *addToPlayListAndClearA = new QAction(QIcon(":/belly_right_and_clear.png"), tr("Clear and add"), this); connect(addToPlayListAndClearA, &QAction::triggered, this, &PlayerWidget::addToPlayListAndClear); curW->view()->addAction(addToPlayListAndClearA); curW->view()->addAction(Helper::createSeparator(this)); QStandardItemModel *model = curW->model(); QModelIndex rootIdx = model->invisibleRootItem()->index(); if(model->hasChildren(model->index(0, 0, rootIdx))){ QAction *expandA = new QAction(Helper::iconFromQChar(QChar(0x2640), 28), tr("Expand"), this); connect(expandA, &QAction::triggered, [curW] { curW->view()->expandOrCollapse(BeetView::Expand);} ); curW->view()->addAction(expandA); QAction *collapseA = new QAction(Helper::iconFromQChar(QChar(0x2642), 28), tr("Collapse"), this); connect(collapseA, &QAction::triggered, [curW] { curW->view()->expandOrCollapse(BeetView::Collapse);} ); curW->view()->addAction(collapseA); curW->view()->addAction(Helper::createSeparator(this)); } // special case file view -> add delete if(name == "foldersview"){ QAction *deleteItemA = new QAction(QIcon(":/delete.png"), tr("Delete..."), this); connect(deleteItemA, &QAction::triggered, foldersView, &CollectionFoldersView::deleteCurrent); curW->view()->addAction(deleteItemA); curW->view()->addAction(Helper::createSeparator(this)); } // special case dates -> add copy to... if(name == "dates"){ QAction *copyToA = new QAction(QIcon::fromTheme("edit-copy"), tr("Copy to..."), this); connect(copyToA, &QAction::triggered, datesView, &CollectionDatesView::copyTo); curW->view()->addAction(copyToA); curW->view()->addAction(Helper::createSeparator(this)); } QAction *refreshA = new QAction(QIcon(":/refresh.png"), tr("Refresh view"), this); connect(refreshA, &QAction::triggered, [curW] { qApp->setOverrideCursor(Qt::BusyCursor); curW->populate(); qApp->restoreOverrideCursor(); } ); curW->view()->addAction(refreshA); curW->view()->addAction(Helper::createSeparator(this)); QAction *searchMBA = new QAction(Helper::iconFromQChar(QChar(0x2047), 28), tr("Search Musicbrainz"), this); connect(searchMBA, &QAction::triggered, [this, curW] { searchMusicbrainz(curW->view()->selectionModel()->currentIndex()); }); curW->view()->addAction(searchMBA); if(name == "favorites"){ QAction *addAllFavsA = new QAction((QIcon(":/shuffle_light.png")), tr("Clear and Add all"), this); connect(addAllFavsA, &QAction::triggered, this, &PlayerWidget::clearAndAddAllFavorites); curW->customActions()->addAction(addAllFavsA); } curW->view()->addActions(curW->customActions()->actions()); } } QPixmap buttPlug(":/butt_plug_light.png"); QMatrix rotateMatrix; rotateMatrix.rotate(90); QIcon gotoIcon(buttPlug.transformed(rotateMatrix)); QAction *gotoAlbumA = new QAction(gotoIcon, tr("Goto album"), this); connect(gotoAlbumA, &QAction::triggered, this, &PlayerWidget::gotoAlbum); artistsView->view()->addAction(gotoAlbumA); //left widget QWidget *leftWidget = new QWidget; QVBoxLayout *leftWidgetL = new QVBoxLayout; leftWidgetL->addWidget(mCollectionStack); QHBoxLayout *selViewL = new QHBoxLayout; selViewL->addWidget(new QLabel(tr("View by:"))); selViewL->addStretch(); selViewL->addWidget(bottomTB); selViewL->addStretch(); leftWidgetL->addLayout(selViewL); leftWidget->setLayout(leftWidgetL); //now playing label mNowPlayingL = new QLabel; mNowPlayingL->setAlignment(Qt::AlignCenter); mNowPlayingL->setFont(QFont("courier new", 20, QFont::Bold)); mNowPlayingL->setText(tr("(none)")); //song slider QLabel *l1 = new QLabel(tr("Song")); mSongSlider = new QSlider; mSongSlider->setOrientation(Qt::Horizontal); connect(mSongSlider, &QSlider::sliderMoved, this, &PlayerWidget::slide); mPos = new QLabel(tr("00:00")); mPos->setFont(QFont("courier")); QHBoxLayout *songSliderL = new QHBoxLayout; songSliderL->addWidget(l1); songSliderL->addWidget(mSongSlider); songSliderL->addWidget(mPos); //now playing info QString currentInfoGBS = QString(tr("Now Playing %1")).arg(QChar(0x26A5)); QGroupBox *currentInfoGB = new QGroupBox(currentInfoGBS); mCurrentTE = new QTextEdit; mCurrentTE->setFont(QFont("courier")); mCurrentTE->setReadOnly(true); QVBoxLayout *currentInfoL = new QVBoxLayout; currentInfoL->addWidget(mCurrentTE); currentInfoGB->setLayout(currentInfoL); //misc info QString leftInfoGBS = QString(tr("%1 Misc. %1")).arg(QChar(0x26A5)); QGroupBox *leftInfoGB = new QGroupBox(leftInfoGBS); mLeftTE = new QTextBrowser; mLeftTE->setOpenExternalLinks(true); mLeftTE->setFont(QFont("courier")); mLeftTE->setReadOnly(true); QVBoxLayout *leftInfoL = new QVBoxLayout; leftInfoL->addWidget(mLeftTE); leftInfoGB->setLayout(leftInfoL); //current right info QString rightInfoGBS = QString(tr("Selected %1")).arg(QChar(0x26A5)); QGroupBox *rightInfoGB = new QGroupBox(rightInfoGBS); mRightTE = new QTextEdit; mRightTE->setFont(QFont("courier")); mRightTE->setReadOnly(true); QVBoxLayout *rightInfoL = new QVBoxLayout; rightInfoL->addWidget(mRightTE); rightInfoGB->setLayout(rightInfoL); //volume slider QLabel *l2 = new QLabel(tr("Volume")); mVolumeSlider = new QSlider; mVolumeSlider->setOrientation(Qt::Horizontal); mVolumeSlider->setMinimum(0); mVolumeSlider->setMaximum(100); mVolumePos = new QLabel(tr("000 %")); mVolumePos->setFont(QFont("courier")); connect(mVolumeSlider, &QSlider::valueChanged, mPlayer, &QMediaPlayer::setVolume); connect(mVolumeSlider, &QSlider::valueChanged, this, &PlayerWidget::volumeChanged); mVolumeSlider->setValue(33); QHBoxLayout *volumeL = new QHBoxLayout; volumeL->addWidget(l2); volumeL->addWidget(mVolumeSlider); volumeL->addWidget(mVolumePos); //center widget QWidget *centerWidget = new QWidget; QVBoxLayout *centerWidgetL = new QVBoxLayout; QSplitter *centerSplitter = new QSplitter; centerSplitter->setOrientation(Qt::Vertical); centerSplitter->addWidget(currentInfoGB); centerSplitter->addWidget(leftInfoGB); centerSplitter->addWidget(rightInfoGB); mToolBar = new QToolBar; centerWidgetL->addWidget(mToolBar); centerWidgetL->addWidget(mNowPlayingL); centerWidgetL->addLayout(songSliderL); centerWidgetL->addWidget(centerSplitter); centerWidgetL->addLayout(volumeL); centerWidget->setLayout(centerWidgetL); //playlist mSongModel = new QStandardItemModel; mSongModel->setHorizontalHeaderLabels(QStringList() << "Title"); mSongView = new BeetView; mSongView->setAlternatingRowColors(true); mSongView->setModel(mSongModel); mSongView->setRootIsDecorated(false); mSongView->setSelectionMode(QAbstractItemView::ExtendedSelection); connect(mSongView, &BeetView::doubleClicked, this, &PlayerWidget::playCurrent); connect(mSongView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PlayerWidget::rightCurrentChanged); mWebRadioModel = new QStandardItemModel; mWebRadioModel->setHorizontalHeaderLabels(QStringList() << tr("Title")); mWebRadioView = new BeetView; mWebRadioView->setAlternatingRowColors(true); mWebRadioView->setModel(mWebRadioModel); mWebRadioView->setRootIsDecorated(false); mWebRadioView->setSelectionMode(QAbstractItemView::NoSelection); mPlaylistTab = new QTabWidget; mPlaylistTab->addTab(mSongView, tr("Files")); mPlaylistTab->addTab(mWebRadioView, tr("Web")); //right widget QWidget *rightWidget = new QWidget; QVBoxLayout *rightWidgetL = new QVBoxLayout; rightWidgetL->addWidget(mPlaylistTab); rightWidget->setLayout(rightWidgetL); //stream data connect(this, &PlayerWidget::streamDataNeedsUpdate, this, &PlayerWidget::updateStreamData); //put it all together QSplitter *splitter = new QSplitter; splitter->addWidget(leftWidget); splitter->addWidget(centerWidget); splitter->addWidget(rightWidget); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(splitter); setLayout(mainLayout); viewByArtistsA->trigger(); } void PlayerWidget::createActions(){ QActionGroup *playAG = new QActionGroup(this); playAG->setExclusive(true); mPlayA = new QAction(QIcon(":/play.png"), tr("Play"), this); mPlayA->setCheckable(true); playAG->addAction(mPlayA); connect(mPlayA, &QAction::triggered, this, &PlayerWidget::doPlay); mPauseA = new QAction(QIcon(":/pause.png"), tr("Pause"), this); mPauseA->setCheckable(true); playAG->addAction(mPauseA); connect(mPauseA, &QAction::triggered, this, &PlayerWidget::doPause); mStopA = new QAction(QIcon(":/stop.png"), tr("Stop"), this); mStopA->setCheckable(true); playAG->addAction(mStopA); mStopA->setChecked(true); connect(mStopA, &QAction::triggered, this, &PlayerWidget::doStop); QAction *playOrPauseA = new QAction(tr("Play or Pause"), this); playOrPauseA->setObjectName("beetPlayerDoPlayOrPause"); connect(playOrPauseA, &QAction::triggered, this, &PlayerWidget::doPlayOrPause); KGlobalAccel::self()->setShortcut(playOrPauseA, QList() << QKeySequence(Qt::Key_MediaPlay), KGlobalAccel::Autoloading); QPixmap buttPlug(":/butt_plug_light.png"); QMatrix rotateMatrix; rotateMatrix.rotate(-90); QIcon prevIcon(buttPlug.transformed(rotateMatrix)); rotateMatrix.rotate(180); QIcon nextIcon(buttPlug.transformed(rotateMatrix)); QAction *previousA = new QAction(prevIcon, tr("Previous"), this); previousA->setObjectName("beetPlayerPrevious"); connect(previousA, &QAction::triggered, this, &PlayerWidget::previous); KGlobalAccel::self()->setShortcut(previousA, QList() << QKeySequence(Qt::Key_Launch2), KGlobalAccel::Autoloading); QAction *nextA = new QAction(nextIcon, tr("Next"), this); nextA->setObjectName("beetPlayerNext"); connect(nextA, &QAction::triggered, this, &PlayerWidget::next); KGlobalAccel::self()->setShortcut(nextA, QList() << QKeySequence(Qt::Key_Launch3), KGlobalAccel::Autoloading); QAction *addToPlayListAndClearA = new QAction(QIcon(":/belly_right_and_clear.png"), tr("Clear and add"), this); connect(addToPlayListAndClearA, &QAction::triggered, this, &PlayerWidget::addToPlayListAndClear); QAction *removeFromPlayListA = new QAction(QIcon(":/belly_left.png"), tr("Remove from playlist"), this); connect(removeFromPlayListA, &QAction::triggered, this, &PlayerWidget::removeFromPlayList); QAction *clearPlayListA = new QAction(QIcon(":/delete.png"), tr("Clear Playlist"), this); connect(clearPlayListA, &QAction::triggered, this, &PlayerWidget::clearPlayList); QAction *refreshA = new QAction(QIcon(":/refresh.png"), tr("Refresh..."), this); connect(refreshA, &QAction::triggered, this, &PlayerWidget::reindex); QAction *shufflePlayistA = new QAction(QIcon(":/shuffle_light.png"), tr("Shuffle playlist"), this); connect(shufflePlayistA, &QAction::triggered, this, &PlayerWidget::shufflePlayList); QAction *randomPlayA = new QAction(QIcon(":/dice_light.png"), tr("Play random"), this); connect(randomPlayA, &QAction::triggered, this, &PlayerWidget::randomPlay); QAction *muteA = new QAction(QIcon(":/mute.png"), tr("Mute"), this); muteA->setObjectName("beetPlayerMute"); muteA->setCheckable(true); connect(muteA, &QAction::triggered, this, &PlayerWidget::mute); KGlobalAccel::self()->setShortcut(muteA, QList() << QKeySequence(Qt::Key_VolumeMute), KGlobalAccel::Autoloading); QAction *volumeUpA = new QAction(tr("Increase volume"), this); volumeUpA->setObjectName("beetPlayerIncVolume"); connect(volumeUpA, &QAction::triggered, this, &PlayerWidget::volumeUp); KGlobalAccel::self()->setShortcut(volumeUpA, QList() << QKeySequence(Qt::Key_VolumeUp), KGlobalAccel::Autoloading); QAction *volumeDownA = new QAction(tr("Decrease volume"), this); volumeDownA->setObjectName("beetPlayerDecVolume"); connect(volumeDownA, &QAction::triggered, this, &PlayerWidget::volumeDown); KGlobalAccel::self()->setShortcut(volumeDownA, QList() << QKeySequence(Qt::Key_VolumeDown), KGlobalAccel::Autoloading); QAction *configA = Globals::instance()->action(Globals::ConfigAction); QAction *helpA = new QAction(Helper::iconFromQChar(QChar(0x00BF), 28), tr("Help"), this); QMenu *helpM = new QMenu; QAction *aboutThisA = new QAction(tr("About..."), this); connect(aboutThisA, &QAction::triggered, this, &PlayerWidget::aboutDlg);; helpM->addAction(aboutThisA); QAction *aboutQtA = new QAction(tr("About Qt..."), this); connect(aboutQtA, &QAction::triggered, qApp, &QApplication::aboutQt); helpM->addAction(aboutQtA); helpA->setMenu(helpM); QAction *plSearchMB = new QAction(Helper::iconFromQChar(QChar(0x2047), 28), tr("Search Musicbrainz"), this); connect(plSearchMB, &QAction::triggered, [this] { searchMusicbrainz(mSongView->selectionModel()->currentIndex()); }); QAction *addToFavoritesA = new QAction(QIcon(":/male_chastity_belt.png"), tr("Add to Favorites"), this); connect(addToFavoritesA, &QAction::triggered, [this] { addToFavorites(mSongView->selectionModel()->selectedRows()); }); mSongView->addAction(removeFromPlayListA); mSongView->addAction(Helper::createSeparator(this)); mSongView->addAction(plSearchMB); mSongView->addAction(Helper::createSeparator(this)); mSongView->addAction(shufflePlayistA); mSongView->addAction(clearPlayListA); mSongView->addAction(Helper::createSeparator(this)); mSongView->addAction(addToFavoritesA); QWidget* spacer1 = new QWidget(); spacer1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); mToolBar->addWidget(spacer1); mToolBar->addAction(mPlayA); mToolBar->addAction(mPauseA); mToolBar->addAction(mStopA); mToolBar->addSeparator(); mToolBar->addAction(previousA); mToolBar->addAction(nextA); mToolBar->addSeparator(); mToolBar->addAction(clearPlayListA); mToolBar->addAction(shufflePlayistA); mToolBar->addAction(randomPlayA); mToolBar->addSeparator(); mToolBar->addAction(refreshA); mToolBar->addSeparator(); mToolBar->addAction(muteA); mToolBar->addSeparator(); mToolBar->addAction(configA); mToolBar->addSeparator(); mToolBar->addAction(helpA); QWidget* spacer2 = new QWidget(); spacer2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); mToolBar->addWidget(spacer2); QMenu *trayMenu = new QMenu; trayMenu->addAction(mPlayA); trayMenu->addAction(mStopA); trayMenu->addAction(mPauseA); trayMenu->addSeparator(); trayMenu->addAction(nextA); trayMenu->addAction(previousA); trayMenu->addSeparator(); QAction *quitA = new QAction(tr("Quit"), this); connect(quitA, &QAction::triggered, qApp, &QApplication::quit); trayMenu->addAction(quitA); mTrayIcon->setContextMenu(trayMenu); } void PlayerWidget::rightCurrentChanged(const QModelIndex &cur, const QModelIndex &prev){ Q_UNUSED(prev) QString fullPath(cur.data(CollectionWidget::FullPathRole).toString()); TagLib::FileRef file(QString(fullPath).toUtf8()); fillWithText(mRightTE, file); } void PlayerWidget::doPlay(){ int playListCount = mSongModel->rowCount(); if(playListCount == 0){ emit message(tr("Playlist is empty! Dazed and confused, but trying to continue...")); doStop(); return; } QModelIndexList sel = mSongView->selectionModel()->selectedRows(); if(mPlayer->state() == QMediaPlayer::StoppedState){ if(sel.isEmpty()){ mSongView->selectionModel()->setCurrentIndex(mSongModel->index(0, 0), QItemSelectionModel::SelectCurrent); sel = mSongView->selectionModel()->selectedRows(); } } playCurrent(sel.first()); } void PlayerWidget::doStop(){ mPlayer->stop(); mStopA->setChecked(true); emit playModeChanged(tr("Stopped")); QString winTitle = QString(tr("%1 [Stopped]")).arg(qApp->applicationName()); emit setWinTitle(winTitle); mTrayIcon->setIcon(QIcon(":/stop.png")); mTrayIcon->setToolTip(tr("[Stopped]")); } void PlayerWidget::doPause(){ mPlayer->pause(); mPauseA->setChecked(true); emit playModeChanged(tr("Paused")); QString winTitle = QString(tr("%1 [Paused]")).arg(qApp->applicationName()); emit setWinTitle(winTitle); mTrayIcon->setIcon(QIcon(":/pause.png")); mTrayIcon->setToolTip(tr("[Paused]")); } void PlayerWidget::doPlayOrPause(){ if(mPlayA->isChecked()){ doPause(); }else if(mPauseA->isChecked()){ doContinue(); }else if (mStopA->isChecked()){ doPlay(); } } void PlayerWidget::doContinue(){ mPlayer->play(); mPlayA->setChecked(true); emit playModeChanged(tr("Playing")); mTrayIcon->setIcon(QIcon(":/play.png")); mTrayIcon->setToolTip(tr("[Playing]")); } void PlayerWidget::volumeUp(){ adjustVolume(2); } void PlayerWidget::volumeDown(){ adjustVolume(-2); } void PlayerWidget::showVolume(){ int volume = mVolumeSlider->value(); QString msg = QString(tr("Volume: %1%")).arg(volume, 3, 10, QChar('0')); mTrayIcon->showMessage(msg, QString(), QSystemTrayIcon::Information, 2000); } void PlayerWidget::adjustVolume(int by){ int curVol = mVolumeSlider->value(); int newVol = curVol + by; if(newVol > 100){ newVol = 100; } if(newVol < 0){ newVol = 0; } mVolumeSlider->setValue(newVol); } void PlayerWidget::fillWithText(QTextEdit *te, const TagLib::FileRef &fr){ if(fr.isNull()){ return; } QString artist = QString::fromStdWString(fr.tag()->artist().toWString()); QString album = QString::fromStdWString(fr.tag()->album().toWString()); QString title = QString::fromStdWString(fr.tag()->title().toWString()); QString genre = QString::fromStdWString(fr.tag()->genre().toWString()); quint32 track = fr.tag()->track(); quint32 year = fr.tag()->year(); te->clear(); te->append(QString("%1 %2").arg(tr("Artist:"), -20).arg(artist)); te->append(QString("%1 %2").arg(tr("Album:"), -20).arg(album)); te->append(QString("%1 %2").arg(tr("Title:"), -20).arg(title)); te->append(QString("%1 %2").arg(tr("Genre:"), -20).arg(genre)); te->append(QString("%1 %2").arg(tr("Track:"), -20).arg(track, 3, 10, QChar('0'))); te->append(QString("%1 %2").arg(tr("Year:"), -20).arg(year, 4, 10)); te->append(QString("%1 %2 kb/s").arg(tr("Bitrate:"), -20).arg(fr.audioProperties()->bitrate(), 4, 10, QChar('0'))); } void PlayerWidget::setNowPlaying(const QString &what){ QFont f = mNowPlayingL->font(); int width = mNowPlayingL->width(); QFontMetrics fm(f); QString newText = fm.elidedText(what, Qt::ElideRight, width); mNowPlayingL->setText(newText); } void PlayerWidget::recurse(const QModelIndex &parent){ const QAbstractItemModel *m = parent.model(); for(int i = 0; i < m->rowCount(parent); ++i){ QModelIndex cur = m->index(i, 0, parent); int type = cur.data(CollectionWidget::TypeRole).toInt(); if(type == CollectionWidget::Song){ addSong(cur); }else{ recurse(cur); } } } void PlayerWidget::addSong(const QModelIndex &idx){ QStandardItem *root = mSongModel->invisibleRootItem(); QString title = idx.data(CollectionWidget::TitleRole).toString(); QString artist = idx.data(CollectionWidget::ArtistRole).toString(); QString album = idx.data(CollectionWidget::AlbumRole).toString(); QStringList args = QStringList() << title << artist << album; for(QString &s : args){ if(s.isEmpty()){ s = QString(tr("n/a")); } } QString display = QString(tr("%1 - %2 - %3")).arg(artist).arg(title).arg(album); QStandardItem *item = new QStandardItem; item->setEditable(false); item->setFont(QFont("courier")); item->setText(display); item->setIcon(QIcon(":/song.png")); item->setData(idx.data(CollectionWidget::FullPathRole), CollectionWidget::FullPathRole); int len = idx.data(CollectionWidget::LengthRole).toInt(); if(len == 0){ len = Helper::lengthInSeconds(idx.data(CollectionWidget::FullPathRole).toString()); } item->setData(len, CollectionWidget::LengthRole); item->setData(artist, CollectionWidget::ArtistRole); item->setData(album, CollectionWidget::AlbumRole); item->setData(title, CollectionWidget::TitleRole); root->appendRow(item); mPlayListLength += len; } void PlayerWidget::reindex(){ IndexerDialog dlg(this); dlg.exec(); } void PlayerWidget::addToPlayList(){ CollectionWidget *curWidget = qobject_cast(mCollectionStack->currentWidget()); QModelIndexList sel = curWidget->view()->selectionModel()->selectedRows(); if(sel.isEmpty()){ return; } for(const QModelIndex &i : sel){ int type = i.data(CollectionWidget::TypeRole).toInt(); if(type == CollectionWidget::Song){ addSong(i); }else if (type == CollectionWidget::WebRadio){ mSongModel->clear(); QStandardItem *root = mSongModel->invisibleRootItem(); mSongModel->setHorizontalHeaderLabels(QStringList() << tr("Title")); QStandardItem *wrItem = new QStandardItem; wrItem->setEditable(false); wrItem->setIcon(QIcon(":/dog_hood_light.png")); wrItem->setText(i.data(CollectionWidget::TitleRole).toString()); wrItem->setData(CollectionWidget::WebRadio, CollectionWidget::TypeRole); wrItem->setData(i.data(CollectionWidget::UrlRole), CollectionWidget::UrlRole); wrItem->setData(true, CollectionWidget::RemoteRole); root->appendRow(wrItem); }else{ recurse(i); } } QStandardItem *playListRoot = mSongModel->invisibleRootItem(); emit numFilesChanged(playListRoot->rowCount()); emit playListLengthChanged(mPlayListLength); } void PlayerWidget::addToPlayListAndClear(){ clearPlayList(); addToPlayList(); } void PlayerWidget::removeFromPlayList(){ QModelIndexList sel = mSongView->selectionModel()->selectedRows(); QList persistent; quint64 subSecs = 0; for(const QModelIndex &i : sel){ subSecs = i.data(CollectionWidget::LengthRole).toUInt(); persistent << QPersistentModelIndex(i); } for(const QPersistentModelIndex &i : persistent){ mSongModel->removeRow(i.row()); } QStandardItem *root = mSongModel->invisibleRootItem(); emit numFilesChanged(root->rowCount()); mPlayListLength -= subSecs; emit playListLengthChanged(mPlayListLength); } void PlayerWidget::clearPlayList(){ mSongModel->clear(); mSongModel->setHorizontalHeaderLabels(QStringList() << "Title"); mPlayListLength = 0; emit numFilesChanged(0); emit playListLengthChanged(0); } void PlayerWidget::shufflePlayList(){ QVector items; for(int i = 0; i < mSongModel->rowCount(); ++i){ QStandardItem *cur = mSongModel->item(i, 0)->clone(); items << cur; } std::random_shuffle(items.begin(), items.end()); mSongModel->clear(); mSongModel->setHorizontalHeaderLabels(QStringList() << "Title"); QStandardItem *root = mSongModel->invisibleRootItem(); for(QStandardItem *i : items){ root->appendRow(i); } } void PlayerWidget::randomPlay(){ clearPlayList(); QStandardItem *root = mSongModel->invisibleRootItem(); QIcon songIcon(":/song.png"); QSqlDatabase db = QSqlDatabase::database("beetplayerdb"); QSqlQuery randomQ(db); randomQ.prepare("SELECT sipos, ttitle, tfullpath, igenres_id, artists.tartists_name, albums.talbum_name, ilength FROM songs, artists, albums WHERE songs.iartists_id = artists.iartists_id AND songs.ialbums_id = albums.ialbums_id ORDER BY random() LIMIT 1000"); randomQ.exec(); while(randomQ.next()){ QString display = QString(tr("%1 - %2 - %3")).arg(randomQ.value(4).toString()).arg(randomQ.value(1).toString()).arg(randomQ.value(5).toString()); QStandardItem *item = new QStandardItem; item->setEditable(false); item->setFont(QFont("courier")); item->setText(display); item->setIcon(songIcon); item->setData(randomQ.value(2), CollectionWidget::FullPathRole); item->setData(randomQ.value(6), CollectionWidget::LengthRole); item->setData(randomQ.value(4), CollectionWidget::ArtistRole); item->setData(randomQ.value(5), CollectionWidget::AlbumRole); item->setData(randomQ.value(1), CollectionWidget::TitleRole); mPlayListLength += randomQ.value(6).toUInt(); root->appendRow(item); } emit numFilesChanged(root->rowCount()); emit playListLengthChanged(mPlayListLength); } void PlayerWidget::playCurrent(const QModelIndex &index){ mPlayer->stop(); QString fullPath = index.data(CollectionWidget::FullPathRole).toString(); play(fullPath); mPlayer->play(); mPlayA->setChecked(true); emit playModeChanged(tr("Playing")); emit setWinTitle(mCurWinTitle); mTrayIcon->setIcon(QIcon(":/play.png")); mTrayIcon->setToolTip(mCurToolTip); } void PlayerWidget::printList(){ CollectionWidget *curWidget = qobject_cast(mCollectionStack->currentWidget()); QModelIndexList sel = curWidget->view()->selectionModel()->selectedRows(); if(sel.isEmpty()){ return; } QString output; for(const QModelIndex &i : sel){ QString fp = i.data(CollectionWidget::FullPathRole).toString(); TagLib::FileRef fr(fp.toUtf8()); if(!fr.isNull()){ quint32 track = fr.tag()->track(); QString title = QString::fromStdWString(fr.tag()->title().toWString()); int length = fr.audioProperties()->lengthInSeconds(); int minutes = length / 60; int seconds = length % 60; QString t = QString("%1 - %2 (%3:%4)\n").arg(track, 2, 10, QChar('0')).arg(title).arg(minutes, 2, 10, QChar('0')).arg(seconds, 2, 10, QChar('0')); output.append(t); } } mLeftTE->clear(); mLeftTE->setPlainText(output); } void PlayerWidget::searchMusicbrainz(const QModelIndex &idx){ if(!idx.isValid()){ return; } QString artist = idx.data(CollectionWidget::ArtistRole).toString(); QString album = idx.data(CollectionWidget::AlbumRole).toString(); if(album.isEmpty()){ QModelIndex firstChild = idx.model()->index(0, 0, idx); if(firstChild.isValid()){ album = firstChild.data(CollectionWidget::AlbumRole).toString(); } } mWebDownloader->fetchData(artist, album); } void PlayerWidget::webDlDone(){ QString aId = mWebDownloader->artistId(); QString artist = mWebDownloader->artist(); QHash dbAlbums; QSqlDatabase db = QSqlDatabase::database("beetplayerdb"); QSqlQuery aQ(db); aQ.prepare("SELECT DISTINCT(albums.talbum_name), siyear FROM artists, albums, songs WHERE artists.tartists_name = :aname AND artists.iartists_id = songs.iartists_id AND songs.ialbums_id = albums.ialbums_id"); aQ.bindValue(":aname", artist); aQ.exec(); while(aQ.next()){ dbAlbums.insert(aQ.value(0).toString(), aQ.value(1).toString()); } QString text; if(!aId.isEmpty()){ text.append(QString(tr("Musicbrainz:"))); text.append(QString(tr("")).arg(aId).arg(mWebDownloader->artist())); text.append("
Artist%2
"); QList > other = mWebDownloader->otherData(); std::sort(other.begin(), other.end(), [](auto a, auto b){ return a.at(2).toInt() < b.at(2).toInt(); }); text.append(QString(tr("All Albums (%1):
")).arg(QString::number(other.count()))); for(const QVariant vl : other){ QVariantList cur = vl.toList(); QString id = cur.at(0).toString(); QString album = cur.at(1).toString(); QString year = cur.at(2).toString(); bool present = false; if(year == "-1"){ year = "nota"; } if(dbAlbums.contains(album)){ year = dbAlbums.value(album); present = true; } if(present){ text.append(QString("
♂ %1: %3
").arg(year).arg(id).arg(album)); }else{ text.append(QString("
♀ %1: %3
").arg(year).arg(id).arg(album)); } } text.append("
"); }else{ text.append(QString(tr("Musicbrainz: No match!
"))); const QMap alt = mWebDownloader->alternateArtists(); if(!alt.isEmpty()){ text.append(QString(tr("Guesses:
    "))); for(QMap::const_iterator it = alt.constBegin(); it != alt.constEnd(); ++it){ text.append(QString("
  • %2
  • ").arg(it.key()).arg(it.value())); } text.append("
"); } } mLeftTE->setText(text); } void PlayerWidget::doMetadataChange(const QString &key, const QVariant &value){ if(key == "Title"){ QString np = value.toString(); QRegularExpression re("\\s?-\\s?"); QStringList titleParts = np.split(re); QString artist(tr("n/a")), title(tr("n/a")); if(titleParts.count() >= 2){ artist = titleParts.at(0); title = titleParts.at(1); } QStandardItem *plRoot = mWebRadioModel->invisibleRootItem(); QStandardItem *nowP = new QStandardItem; nowP->setFont(QFont("courier")); nowP->setEditable(false); nowP->setIcon(QIcon(":/dog_hood_light.png")); nowP->setData(1, CollectionWidget::RemoteRole); nowP->setData(artist, CollectionWidget::ArtistRole); nowP->setData(title, CollectionWidget::TitleRole); nowP->setText(np); plRoot->appendRow(nowP); mWebRadioView->scrollTo(mWebRadioModel->indexFromItem(nowP), QAbstractItemView::EnsureVisible); mCurrentTE->clear(); mCurrentTE->append(QString("%1 %2").arg(tr("Artist:"), -20).arg(artist)); mCurrentTE->append(QString("%1 %2").arg(tr("Title:"), -20).arg(title)); setNowPlaying(np); mCurWinTitle = QString(tr("%1 - [%2] - [%3]")).arg(qApp->applicationName()).arg(artist).arg(title); mCurToolTip = QString(tr("%1: [%2]")).arg(artist).arg(title); mTrayIcon->setToolTip(mCurToolTip); mTrayIcon->showMessage(QString(tr("Now Playing:")), mCurToolTip, QSystemTrayIcon::Information, 5000); emit setWinTitle(mCurWinTitle); }else{ if(mOtherMeta[key] != value){ mOtherMeta[key] = value; emit streamDataNeedsUpdate(); } } } void PlayerWidget::updateStreamData(){ QString retval; retval.append(""); retval.append(QString(tr("")).arg(mOtherMeta.value("Genre").toString())); retval.append(QString(tr("")).arg(mOtherMeta.value("Publisher").toString())); retval.append(QString(tr("")).arg(mOtherMeta.value("location").toString())); retval.append(QString(tr("")).arg(mOtherMeta.value("AudioCodec").toString())); retval.append("
Genre%1
Publisher%1
Location%1
Audio Codec%1
"); mLeftTE->setText(retval); } void PlayerWidget::play(const QString &fullPath){ disconnect(mPlayer, static_cast(&QMediaObject::metaDataChanged), this, &PlayerWidget::doMetadataChange); mSongSlider->setEnabled(true); mLeftTE->clear(); mPlayer->setMedia(QUrl::fromLocalFile(fullPath)); TagLib::FileRef file(QString(fullPath).toUtf8()); fillWithText(mCurrentTE, file); if(file.isNull()){ return; } QString artist = QString::fromStdWString(file.tag()->artist().toWString()); QString album = QString::fromStdWString(file.tag()->album().toWString()); QString title = QString::fromStdWString(file.tag()->title().toWString()); setNowPlaying(title); int length = file.audioProperties()->lengthInSeconds(); mDurSecs = length; mSongSlider->setMinimum(0); mSongSlider->setMaximum(mDurSecs); int minutes = mDurSecs / 60; int seconds = mDurSecs % 60; mCurrentTE->append(QString("%1 %2:%3").arg(tr("Length:"), -20).arg(minutes, 2, 10, QChar('0')).arg(seconds, 2, 10, QChar('0'))); QString msg = QString("File: %1").arg(fullPath); emit message(msg); mCurWinTitle = QString(tr("%1 - [%2] - [%3] - [%4]")).arg(qApp->applicationName()).arg(artist).arg(album).arg(title); mCurToolTip = QString(tr("%1: [%2] - [%3]")).arg(artist).arg(album).arg(title); mTrayIcon->setToolTip(mCurToolTip); mTrayIcon->showMessage(QString(tr("Now Playing:")), mCurToolTip, QSystemTrayIcon::Information, 5000); emit setWinTitle(mCurWinTitle); mPlayer->play(); mPlayA->setChecked(true); searchMusicbrainz(mSongView->selectionModel()->currentIndex()); emit playModeChanged(tr("Playing")); } void PlayerWidget::playUrl(const QModelIndex &idx){ //Honestly, I have no idea why we have to disconnect first, //but if we don't, doMetadataChange will be called times playUrl :( disconnect(mPlayer, static_cast(&QMediaObject::metaDataChanged), this, &PlayerWidget::doMetadataChange); connect(mPlayer, static_cast(&QMediaObject::metaDataChanged), this, &PlayerWidget::doMetadataChange); mSongSlider->setValue(0); mSongSlider->setEnabled(false); QUrl u(idx.data(CollectionWidget::UrlRole).toString()); mPlayer->setMedia(u); mPlayer->play(); mPlayA->setChecked(true); mTrayIcon->setIcon(QIcon(":/play.png")); emit playModeChanged(tr("Playing")); QString msg = QString(tr("Streaming: %1").arg(idx.data(CollectionWidget::UrlRole).toString())); emit message(msg); emit numFilesChanged(0); emit playListLengthChanged(0); mRightTE->clear(); } void PlayerWidget::volumeChanged(int volume){ QString s = QString("%1 %").arg(volume, 3, 10, QChar('0')); mVolumePos->setText(s); if(!mStarting){ if(!mVolumeTimer->isActive()){ mVolumeTimer->start(500); } } } void PlayerWidget::mute(bool triggered){ mPlayer->setMuted(triggered); } void PlayerWidget::next(){ advance(1); } void PlayerWidget::previous(){ advance(-1); } void PlayerWidget::advance(int numSongs){ mPlayer->stop(); QModelIndexList sel = mSongView->selectionModel()->selectedRows(); if(sel.isEmpty()){ QStandardItem *root = mSongModel->invisibleRootItem(); if(root->rowCount() > 0){ QModelIndex first = mSongModel->index(0, 0); mSongView->selectionModel()->setCurrentIndex(first, QItemSelectionModel::ClearAndSelect); playCurrent(first); return; } return; } QModelIndex cur = sel.first(); QModelIndex nextIdx = mSongModel->index(cur.row() + numSongs, 0); if(nextIdx.isValid()){ mSongView->selectionModel()->setCurrentIndex(nextIdx, QItemSelectionModel::ClearAndSelect); playCurrent(nextIdx); }else{ doStop(); } } void PlayerWidget::slide(int value){ qint64 newValue = value * 1000; mPlayer->setPosition(newValue); } void PlayerWidget::setPosition(int pos){ int curPos = pos / 1000; if(mSongSlider->isEnabled()){ mSongSlider->setValue(curPos); } int minutes = curPos / 60; int seconds = curPos % 60; QString posString = QString("%1:%2").arg(minutes, 2, 10, QChar('0')).arg(seconds, 2, 10, QChar('0')); mPos->setText(posString); } void PlayerWidget::continuePlaying(QMediaPlayer::MediaStatus state){ if(state == QMediaPlayer::EndOfMedia){ next(); } } void PlayerWidget::readSettings(){ mStarting = true; QSettings s; int vol = s.value("volume").toInt(); mVolumeSlider->setValue(vol); QString dir = s.value("folderdir", QDir::homePath()).toString(); mStarting = false; } void PlayerWidget::writeSettings(){ QSettings s; s.setValue("volume", mVolumeSlider->value()); } void PlayerWidget::aboutDlg(){ QString title = QString(tr("About %1")).arg(qApp->applicationName()); QString aboutText = QString(tr("

%1: A basic music player without all the fancy stuff implemented in eg. Amarok

")).arg(qApp->applicationName()); aboutText.append(""); aboutText.append(QString(tr("")).arg(QChar(0x26A4))); aboutText.append(QString(tr("")).arg(qApp->organizationName())); aboutText.append(QString(tr("")).arg(qApp->applicationVersion())); aboutText.append(QString(tr(""))); aboutText.append(tr("")); aboutText.append(tr("
AuthorSissy herself %1
Organization%1
Version%1
Build:/* __debug build__ */
DependsQt PostgreSQL TagLib
LicenseGPL 2 or later
")); QMessageBox::about(this, title, aboutText); } void PlayerWidget::gotoAlbum(){ CollectionArtistsView *artistView = mCollectionStack->findChild("artists"); CollectionAlbumsView *albumView = mCollectionStack->findChild("albums"); QString album = artistView->view()->selectionModel()->currentIndex().data(CollectionWidget::AlbumRole).toString(); if(!album.isEmpty()){ const CollectionWidgetProxy *proxy = qobject_cast(albumView->view()->model()); QModelIndexList albumIdxs = proxy->match(proxy->index(0, 0, QModelIndex()), CollectionWidget::AlbumRole, album); if(!albumIdxs.isEmpty()){ mViewByAlbumsA->trigger(); QModelIndex albumIdx = albumIdxs.first(); albumView->view()->selectionModel()->select(albumIdx, QItemSelectionModel::ClearAndSelect); albumView->view()->scrollTo(albumIdx, QAbstractItemView::PositionAtCenter); albumView->view()->expand(albumIdx); } } } void PlayerWidget::setCollectionIndex(int collectionIndex, bool isWebradio){ mCollectionStack->setCurrentIndex(collectionIndex); int plIdx = 0; if(isWebradio){ plIdx = mPlaylistTab->indexOf(mWebRadioView); }else{ plIdx = mPlaylistTab->indexOf(mSongView); } mPlaylistTab->setCurrentIndex(plIdx); } void PlayerWidget::addToFavorites(const QModelIndexList &idxs){ QSqlDatabase db = QSqlDatabase::database("beetplayerdb"); QSqlQuery favInsertQ(db); favInsertQ.prepare("INSERT INTO persistent_favorites (tartist_name, talbum_name, ttitle) VALUES(:artist, :album, :title)"); int insertCount = 0; for(const QModelIndex &idx : idxs){ favInsertQ.bindValue(":artist", idx.data(CollectionWidget::ArtistRole)); favInsertQ.bindValue(":album", idx.data(CollectionWidget::AlbumRole)); favInsertQ.bindValue(":title", idx.data(CollectionWidget::TitleRole)); if(favInsertQ.exec()){ ++insertCount; } } QString msg = QString(tr("Added %1 file(s) to favorites!")).arg(QString::number(insertCount)); emit message(msg); if(insertCount){ CollectionWidget *fav = mCollectionStack->findChild("favorites"); fav->populate(); } } void PlayerWidget::clearAndAddAllFavorites(){ mSongModel->clear(); mSongModel->setHorizontalHeaderLabels(QStringList() << "Song"); CollectionWidget *favWidget = qobject_cast(mCollectionStack->currentWidget()); QStandardItemModel *m = favWidget->model(); QStandardItem *root = m->invisibleRootItem(); for(int i = 0; i < root->rowCount(); ++i){ QModelIndex curIdx = m->indexFromItem(root->child(i, 0)); addSong(curIdx); } shufflePlayList(); }