File indexing completed on 2024-05-05 04:48:43

0001 /****************************************************************************************
0002  * Copyright (c) 2007 Ian Monroe <ian@monroe.nu>                                        *
0003  * Copyright (c) 2009 Leo Franchi <lfranchi@kde.org>                                    *
0004  *                                                                                      *
0005  * This program is free software; you can redistribute it and/or modify it under        *
0006  * the terms of the GNU General Public License as published by the Free Software        *
0007  * Foundation; either version 2 of the License, or (at your option) version 3 or        *
0008  * any later version accepted by the membership of KDE e.V. (or its successor approved  *
0009  * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of  *
0010  * version 3 of the license.                                                            *
0011  *                                                                                      *
0012  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0013  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0014  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0015  *                                                                                      *
0016  * You should have received a copy of the GNU General Public License along with         *
0017  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0018  ****************************************************************************************/
0019 
0020 #define DEBUG_PREFIX "PlaylistDock"
0021 
0022 #include "PlaylistDock.h"
0023 
0024 #include "ActionClasses.h"
0025 #include "App.h"
0026 #include "MainWindow.h"
0027 #include "PaletteHandler.h"
0028 #include "amarokconfig.h"
0029 #include "amarokurls/AmarokUrl.h"
0030 #include "core/support/Debug.h"
0031 #include "playlist/PlaylistActions.h"
0032 #include "playlist/PlaylistController.h"
0033 #include "playlist/PlaylistDefines.h"
0034 #include "playlist/PlaylistInfoWidget.h"
0035 #include "playlist/PlaylistModelStack.h"
0036 #include "playlist/PlaylistQueueEditor.h"
0037 #include "playlist/PlaylistToolBar.h"
0038 #include "playlist/ProgressiveSearchWidget.h"
0039 #include "playlist/layouts/LayoutManager.h"
0040 #include "playlist/navigators/NavigatorConfigAction.h"
0041 #include "playlistmanager/PlaylistManager.h"
0042 #include "core-impl/playlists/providers/user/UserPlaylistProvider.h"
0043 #include "widgets/HorizontalDivider.h"
0044 #include "widgets/BoxWidget.h"
0045 
0046 #include <QLabel>
0047 #include <QSharedPointer>
0048 #include <QStandardPaths>
0049 #include <QToolBar>
0050 
0051 #include <KActionMenu>
0052 #include <KToolBarSpacerAction>
0053 
0054 static const QString s_dynMode( QStringLiteral("dynamic_mode") );
0055 static const QString s_repopulate( QStringLiteral("repopulate") );
0056 static const QString s_turnOff( QStringLiteral("turn_off") );
0057 
0058 Playlist::Dock::Dock( QWidget* parent )
0059     : AmarokDockWidget( i18n( "&Playlist" ), parent )
0060     , m_barBox( nullptr )
0061 {
0062     setObjectName( QStringLiteral("Playlist dock") );
0063     setAllowedAreas( Qt::AllDockWidgetAreas );
0064 }
0065 
0066 Playlist::PrettyListView *
0067 Playlist::Dock::currentView()
0068 {
0069     ensurePolish();
0070     return m_playlistView;
0071 }
0072 
0073 Playlist::SortWidget *
0074 Playlist::Dock::sortWidget()
0075 {
0076     ensurePolish();
0077     return m_sortWidget;
0078 }
0079 
0080 Playlist::ProgressiveSearchWidget *
0081 Playlist::Dock::searchWidget()
0082 {
0083     ensurePolish();
0084     return m_searchWidget;
0085 }
0086 
0087 void
0088 Playlist::Dock::polish()
0089 {
0090     m_mainWidget = new BoxWidget( true, this );
0091     setWidget( m_mainWidget );
0092     m_mainWidget->setContentsMargins( 0, 0, 0, 0 );
0093     m_mainWidget->setFrameShape( QFrame::NoFrame );
0094     m_mainWidget->setMinimumWidth( 200 );
0095     m_mainWidget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Ignored );
0096     m_mainWidget->setFocus( Qt::ActiveWindowFocusReason );
0097 
0098     m_sortWidget = new Playlist::SortWidget( m_mainWidget );
0099     new HorizontalDivider( m_mainWidget );
0100 
0101     m_searchWidget = new Playlist::ProgressiveSearchWidget( m_mainWidget );
0102 
0103     // show visual indication of dynamic playlists  being enabled
0104     connect( The::playlistActions(), &Playlist::Actions::navigatorChanged,
0105              this, &Playlist::Dock::showDynamicHint );
0106     m_dynamicHintWidget = new QLabel( i18n( "<a href='%1'>Dynamic Mode</a> Enabled. "
0107         "<a href='%2'>Repopulate</a> | <a href='%3'>Turn off</a>", s_dynMode,
0108         s_repopulate, s_turnOff ), m_mainWidget );
0109     m_dynamicHintWidget->setAlignment( Qt::AlignCenter );
0110     m_dynamicHintWidget->setTextInteractionFlags( Qt::LinksAccessibleByKeyboard | Qt::LinksAccessibleByMouse );
0111     m_dynamicHintWidget->setMinimumSize( 1, 1 ); // so that it doesn't prevent playlist from shrinking
0112     connect( m_dynamicHintWidget, &QLabel::linkActivated, this, &Dock::slotDynamicHintLinkActivated );
0113 
0114     QFont dynamicHintWidgetFont = m_dynamicHintWidget->font();
0115     dynamicHintWidgetFont.setPointSize( dynamicHintWidgetFont.pointSize() + 1 );
0116     m_dynamicHintWidget->setFont( dynamicHintWidgetFont );
0117 
0118     showDynamicHint();
0119 
0120     paletteChanged( pApp->palette() );
0121     connect( The::paletteHandler(), &PaletteHandler::newPalette,
0122              this, &Playlist::Dock::paletteChanged );
0123 
0124     QWidget * layoutHolder = new QWidget( m_mainWidget );
0125 
0126     QVBoxLayout* mainPlaylistlayout = new QVBoxLayout( layoutHolder );
0127     mainPlaylistlayout->setContentsMargins( 0, 0, 0, 0 );
0128 
0129     m_playlistView = new PrettyListView();
0130     m_playlistView->show();
0131 
0132     connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::filterChanged,
0133              m_playlistView, &Playlist::PrettyListView::find );
0134     connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::next,
0135              m_playlistView, &Playlist::PrettyListView::findNext );
0136     connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::previous,
0137              m_playlistView, &Playlist::PrettyListView::findPrevious );
0138     connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::filterCleared,
0139              m_playlistView, &Playlist::PrettyListView::clearSearchTerm );
0140     connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::showOnlyMatches,
0141              m_playlistView, &Playlist::PrettyListView::showOnlyMatches );
0142     connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::activateFilterResult,
0143              m_playlistView, &Playlist::PrettyListView::playFirstSelected );
0144     connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::downPressed, m_playlistView, &Playlist::PrettyListView::downOneTrack );
0145     connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::upPressed, m_playlistView, &Playlist::PrettyListView::upOneTrack );
0146 
0147     connect( The::mainWindow(), &MainWindow::switchQueueStateShortcut,
0148              m_playlistView, &Playlist::PrettyListView::switchQueueState );
0149 
0150     KConfigGroup searchConfig = Amarok::config(QStringLiteral("Playlist Search"));
0151     m_playlistView->showOnlyMatches( searchConfig.readEntry( "ShowOnlyMatches", false ) );
0152 
0153     connect( m_playlistView, &Playlist::PrettyListView::found, m_searchWidget, &Playlist::ProgressiveSearchWidget::match );
0154     connect( m_playlistView, &Playlist::PrettyListView::notFound, m_searchWidget, &Playlist::ProgressiveSearchWidget::noMatch );
0155 
0156     connect( LayoutManager::instance(), &LayoutManager::activeLayoutChanged,
0157              m_playlistView, &Playlist::PrettyListView::reset );
0158 
0159     mainPlaylistlayout->setSpacing( 0 );
0160     mainPlaylistlayout->addWidget( m_playlistView );
0161 
0162     ModelStack::instance(); //This also creates the Controller.
0163 
0164     { // START: Playlist toolbar
0165         // action toolbar
0166         m_barBox = new BoxWidget( false, m_mainWidget );
0167         m_barBox->setObjectName( QStringLiteral("PlaylistBarBox") );
0168         m_barBox->setContentsMargins( 0, 0, 4, 0 );
0169         m_barBox->setFixedHeight( 36 );
0170 
0171         // Use QToolBar instead of KToolBar, see bug 228390
0172         Playlist::ToolBar *plBar = new Playlist::ToolBar( m_barBox );
0173         plBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
0174         plBar->setMovable( false );
0175 
0176         QActionGroup *playlistActions = new QActionGroup( m_mainWidget );
0177         playlistActions->addAction( Amarok::actionCollection()->action( QStringLiteral("playlist_clear") ) );
0178 
0179         m_savePlaylistMenu = new KActionMenu( QIcon::fromTheme( QStringLiteral("document-save-amarok") ),
0180                                               i18n("&Save Current Playlist"), m_mainWidget );
0181         m_savePlaylistMenu->addAction( Amarok::actionCollection()->action( QStringLiteral("playlist_export") ) );
0182 
0183         m_saveActions = new KActionCollection( m_mainWidget );
0184 
0185         connect( m_savePlaylistMenu, &KActionMenu::triggered,
0186                  this, &Dock::slotSaveCurrentPlaylist );
0187         foreach( Playlists::PlaylistProvider *provider, The::playlistManager()->providersForCategory(
0188                             PlaylistManager::UserPlaylist ) )
0189         {
0190             playlistProviderAdded( provider, PlaylistManager::UserPlaylist );
0191         }
0192 
0193         connect( The::playlistManager(), &PlaylistManager::providerAdded,
0194                  this, &Dock::playlistProviderAdded );
0195         connect( The::playlistManager(), &PlaylistManager::providerRemoved,
0196                  this, &Dock::playlistProviderRemoved );
0197 
0198         playlistActions->addAction( m_savePlaylistMenu );
0199 
0200         playlistActions->addAction( Amarok::actionCollection()->action( QStringLiteral("playlist_undo") ) );
0201         //redo action can be accessed from menu > Playlist
0202 
0203         playlistActions->addAction( Amarok::actionCollection()->action( QStringLiteral("show_active_track") ) );
0204 
0205         plBar->addCollapsibleActions( playlistActions );
0206 
0207         NavigatorConfigAction *navigatorConfig = new NavigatorConfigAction( m_mainWidget );
0208         plBar->addAction( navigatorConfig );
0209 
0210         QToolButton *toolButton =
0211                 qobject_cast<QToolButton *>(plBar->widgetForAction( navigatorConfig ) );
0212         if( toolButton )
0213             toolButton->setPopupMode( QToolButton::InstantPopup );
0214 
0215         plBar->addAction( new KToolBarSpacerAction( m_mainWidget ) );
0216 
0217         // label widget
0218         new PlaylistInfoWidget( m_barBox );
0219     } // END Playlist Toolbar
0220 
0221     //set correct colors
0222     paletteChanged( QApplication::palette() );
0223 
0224     // If it is active, clear the search filter before replacing the playlist. Fixes Bug #200709.
0225     connect( The::playlistController(), &Playlist::Controller::replacingPlaylist,
0226              this, &Playlist::Dock::clearFilterIfActive );
0227 
0228 }
0229 
0230 QSize
0231 Playlist::Dock::sizeHint() const
0232 {
0233     return QSize( static_cast<QWidget*>( parent() )->size().width() / 4 , 300 );
0234 }
0235 
0236 void
0237 Playlist::Dock::paletteChanged( const QPalette &palette )
0238 {
0239     const QString backgroundColor = palette.color( QPalette::Active, QPalette::Mid ).name();
0240     const QString textColor = palette.color( QPalette::Active, QPalette::HighlightedText ).name();
0241     const QString linkColor = palette.color( QPalette::Active, QPalette::Link ).name();
0242     const QString ridgeColor = palette.color( QPalette::Active, QPalette::Window ).name();
0243 
0244     QString hintStyle( "QLabel { background-color: %1; color: %2; border-radius: 3px; } "
0245                        "a { color: %3; }" );
0246     hintStyle = hintStyle.arg( backgroundColor, textColor, linkColor );
0247 
0248     QString barStyle( "QFrame#PlaylistBarBox { border: 1px ridge %1; background-color: %2; "
0249                                              " color: %3; border-radius: 3px; } "
0250                       "QLabel { color: %4; }" );
0251     barStyle = barStyle.arg( ridgeColor, backgroundColor, textColor, textColor );
0252 
0253     m_dynamicHintWidget->setStyleSheet( hintStyle );
0254     if( m_barBox )
0255         m_barBox->setStyleSheet( barStyle );
0256 
0257 }
0258 
0259 void
0260 Playlist::Dock::playlistProviderAdded( Playlists::PlaylistProvider *provider, int category )
0261 {
0262     if( category != PlaylistManager::UserPlaylist )
0263         return;
0264 
0265     debug() << "Adding provider: " << provider->prettyName();
0266     Playlists::UserPlaylistProvider *userProvider =
0267             dynamic_cast<Playlists::UserPlaylistProvider *>(provider);
0268     if( userProvider == nullptr )
0269         return;
0270     QAction *action = new QAction( userProvider->icon(),
0271                                    i18n("&Save playlist to \"%1\"", provider->prettyName() ),
0272                                    this );
0273     action->setData( QVariant::fromValue( QPointer<Playlists::UserPlaylistProvider>( userProvider ) ) );
0274     m_saveActions->addAction( QString::number( (qlonglong) userProvider ), action );
0275 
0276     // insert the playlist provider actions before "export"
0277     QAction* exportAction = Amarok::actionCollection()->action( QStringLiteral("playlist_export") );
0278     m_savePlaylistMenu->insertAction( exportAction, action );
0279     connect( action, &QAction::triggered, this, &Playlist::Dock::slotSaveCurrentPlaylist );
0280 }
0281 
0282 void
0283 Playlist::Dock::playlistProviderRemoved( Playlists::PlaylistProvider *provider, int category )
0284 {
0285     if( category != PlaylistManager::UserPlaylist )
0286         return;
0287 
0288     QAction *action = m_saveActions->action( QString::number( (qlonglong) provider ) );
0289     if( action )
0290         m_savePlaylistMenu->removeAction( action );
0291     else
0292         warning() << __PRETTY_FUNCTION__ << ": no save action for provider" << provider->prettyName();
0293 }
0294 
0295 void
0296 Playlist::Dock::slotSaveCurrentPlaylist()
0297 {
0298     DEBUG_BLOCK
0299 
0300     QAction *action = qobject_cast<QAction *>( QObject::sender() );
0301     if( action == nullptr )
0302         return;
0303 
0304     QWeakPointer<Playlists::UserPlaylistProvider> weakPointer =
0305             action->data().value< QWeakPointer<Playlists::UserPlaylistProvider> >();
0306     QSharedPointer<Playlists::UserPlaylistProvider> strongPointer = weakPointer.toStrongRef();
0307     if ( strongPointer ) {
0308         Playlists::UserPlaylistProvider* provider = strongPointer.data();
0309 
0310         const Meta::TrackList tracks = The::playlist()->tracks();
0311         The::playlistManager()->save( tracks, Amarok::generatePlaylistName( tracks ), provider );
0312     }
0313 }
0314 
0315 void
0316 Playlist::Dock::slotEditQueue()
0317 {
0318     if( m_playlistQueueEditor ) {
0319         m_playlistQueueEditor->raise();
0320         return;
0321     }
0322     m_playlistQueueEditor = new PlaylistQueueEditor;
0323     m_playlistQueueEditor->setAttribute( Qt::WA_DeleteOnClose );
0324     m_playlistQueueEditor->show();
0325 }
0326 
0327 void
0328 Playlist::Dock::showActiveTrack()
0329 {
0330     ensurePolish();
0331     m_playlistView->scrollToActiveTrack();
0332 }
0333 
0334 void
0335 Playlist::Dock::editTrackInfo()
0336 {
0337     m_playlistView->editTrackInformation();
0338 }
0339 
0340 void
0341 Playlist::Dock::showDynamicHint() // slot
0342 {
0343     DEBUG_BLOCK
0344 
0345     if( AmarokConfig::dynamicMode() )
0346         m_dynamicHintWidget->show();
0347     else
0348         m_dynamicHintWidget->hide();
0349 }
0350 
0351 void
0352 Playlist::Dock::clearFilterIfActive() // slot
0353 {
0354     DEBUG_BLOCK
0355     KConfigGroup config = Amarok::config( QStringLiteral("Playlist Search") );
0356     bool filterActive = config.readEntry( "ShowOnlyMatches", true );
0357 
0358     if( filterActive )
0359         m_searchWidget->slotFilterClear();
0360 }
0361 
0362 void
0363 Playlist::Dock::slotDynamicHintLinkActivated( const QString &href )
0364 {
0365     if( href == s_dynMode )
0366         AmarokUrl( QStringLiteral("amarok://navigate/playlists/dynamic category") ).run();
0367     else if( href == s_repopulate )
0368         The::playlistActions()->repopulateDynamicPlaylist();
0369     else if( href == s_turnOff )
0370         The::playlistActions()->enableDynamicMode( false );
0371 }