File indexing completed on 2024-05-19 04:48:39

0001 /****************************************************************************************
0002  * Copyright (c) 2009-2010 Bart Cerneels <bart.cerneels@kde.org>                        *
0003  *                                                                                      *
0004  * This program is free software; you can redistribute it and/or modify it under        *
0005  * the terms of the GNU General Public License as published by the Free Software        *
0006  * Foundation; either version 2 of the License, or (at your option) any later           *
0007  * version.                                                                             *
0008  *                                                                                      *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0012  *                                                                                      *
0013  * You should have received a copy of the GNU General Public License along with         *
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0015  ****************************************************************************************/
0016 
0017 #define DEBUG_PREFIX "PlaylistBrowserCategory"
0018 
0019 #include "PlaylistBrowserCategory.h"
0020 
0021 #include "amarokconfig.h"
0022 #include "core/support/Debug.h"
0023 #include "PaletteHandler.h"
0024 #include "PlaylistBrowserModel.h"
0025 #include "playlist/PlaylistModel.h"
0026 #include "playlistmanager/PlaylistManager.h"
0027 #include "PlaylistsInFoldersProxy.h"
0028 #include "PlaylistsByProviderProxy.h"
0029 #include "PlaylistBrowserFilterProxy.h"
0030 #include "SvgHandler.h"
0031 #include "PlaylistBrowserView.h"
0032 #include "widgets/PrettyTreeDelegate.h"
0033 
0034 #include <KActionMenu>
0035 #include <KConfigGroup>
0036 #include <QIcon>
0037 #include <QStandardPaths>
0038 #include <KToolBar>
0039 
0040 #include <QHeaderView>
0041 #include <QToolBar>
0042 
0043 #include <typeinfo>
0044 
0045 using namespace PlaylistBrowserNS;
0046 
0047 QString PlaylistBrowserCategory::s_mergeViewKey( QStringLiteral("Merged View") );
0048 
0049 PlaylistBrowserCategory::PlaylistBrowserCategory( int playlistCategory,
0050                                                   const QString &categoryName,
0051                                                   const QString &configGroup,
0052                                                   PlaylistBrowserModel *model,
0053                                                   QWidget *parent ) :
0054     BrowserCategory( categoryName, parent ),
0055     m_configGroup( configGroup ),
0056     m_playlistCategory( playlistCategory )
0057 {
0058     setContentsMargins( 0, 0, 0, 0 );
0059     setImagePath( QStandardPaths::locate( QStandardPaths::GenericDataLocation, QStringLiteral("amarok/images/hover_info_podcasts.png") ) );
0060 
0061     // set background
0062     if( AmarokConfig::showBrowserBackgroundImage() )
0063         setBackgroundImage( imagePath() );
0064 
0065     m_toolBar = new KToolBar( this, false, false );
0066     m_toolBar->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
0067 
0068     //a QWidget with minimumExpanding makes the next button right aligned.
0069     QWidget *spacerWidget = new QWidget( this );
0070     spacerWidget->setSizePolicy( QSizePolicy::MinimumExpanding,
0071                                  QSizePolicy::MinimumExpanding );
0072     // add a separator so subclasses can add their actions before it
0073     m_separator = m_toolBar->addWidget( spacerWidget );
0074 
0075     m_toolBar->addSeparator();
0076 
0077     m_addFolderAction = new QAction( QIcon::fromTheme( QStringLiteral("folder-new") ), i18n( "Add Folder" ), this  );
0078     m_addFolderAction->setPriority( QAction::LowPriority );
0079     m_toolBar->addAction( m_addFolderAction );
0080     connect( m_addFolderAction, &QAction::triggered, this, &PlaylistBrowserCategory::createNewFolder );
0081 
0082     m_providerMenu = new KActionMenu( QIcon::fromTheme( QStringLiteral("checkbox") ), i18n( "Visible Sources"), this );
0083     m_providerMenu->setDelayed( false );
0084     m_providerMenu->setPriority( QAction::HighPriority );
0085     m_toolBar->addAction( m_providerMenu );
0086 
0087     QAction *toggleAction = new QAction( QIcon::fromTheme( QStringLiteral("view-list-tree") ), i18n( "Merged View" ),
0088                                          m_toolBar );
0089     toggleAction->setCheckable( true );
0090     toggleAction->setChecked( Amarok::config( m_configGroup ).readEntry( s_mergeViewKey, false ) );
0091     toggleAction->setPriority( QAction::LowPriority );
0092     m_toolBar->addAction( toggleAction );
0093     connect( toggleAction, &QAction::triggered, this, &PlaylistBrowserCategory::toggleView );
0094 
0095     m_toolBar->addSeparator();
0096 
0097     m_byProviderProxy = new PlaylistsByProviderProxy( m_playlistCategory, this );
0098     m_byProviderProxy->setSourceModel( model );
0099     m_byProviderProxy->setGroupedColumn( PlaylistBrowserModel::ProviderColumn );
0100     m_byFolderProxy = new PlaylistsInFoldersProxy( model );
0101 
0102     m_filterProxy = new PlaylistBrowserFilterProxy( this );
0103     //no need to setModel on filterProxy since it will be done in toggleView anyway.
0104     m_filterProxy->setDynamicSortFilter( true );
0105     m_filterProxy->setFilterKeyColumn( PlaylistBrowserModel::ProviderColumn );
0106 
0107     m_playlistView = new PlaylistBrowserView( m_filterProxy, this );
0108     m_defaultItemDelegate = m_playlistView->itemDelegate();
0109     m_byProviderDelegate = new PrettyTreeDelegate( m_playlistView );
0110 
0111     toggleView( toggleAction->isChecked() );
0112 
0113     m_playlistView->setFrameShape( QFrame::NoFrame );
0114     m_playlistView->setContentsMargins( 0, 0, 0, 0 );
0115     m_playlistView->setAlternatingRowColors( true );
0116     m_playlistView->header()->hide();
0117     //hide all columns except the first.
0118     for( int i = 1; i < m_playlistView->model()->columnCount(); i++ )
0119       m_playlistView->hideColumn( i );
0120 
0121     m_playlistView->setDragEnabled( true );
0122     m_playlistView->setAcceptDrops( true );
0123     m_playlistView->setDropIndicatorShown( true );
0124 
0125     foreach( const Playlists::PlaylistProvider *provider,
0126              The::playlistManager()->providersForCategory( m_playlistCategory ) )
0127     {
0128         createProviderButton( provider );
0129     }
0130 
0131     connect( The::playlistManager(), &PlaylistManager::providerAdded,
0132              this, &PlaylistBrowserCategory::slotProviderAdded );
0133     connect( The::playlistManager(), &PlaylistManager::providerRemoved,
0134              this, &PlaylistBrowserCategory::slotProviderRemoved );
0135 
0136     connect( The::paletteHandler(), &PaletteHandler::newPalette,
0137              this, &PlaylistBrowserCategory::newPalette );
0138 }
0139 
0140 PlaylistBrowserCategory::~PlaylistBrowserCategory()
0141 {
0142 }
0143 
0144 QString
0145 PlaylistBrowserCategory::filter() const
0146 {
0147     return QUrl::toPercentEncoding( m_filterProxy->filterRegExp().pattern() );
0148 }
0149 
0150 void
0151 PlaylistBrowserCategory::setFilter( const QString &filter )
0152 {
0153     debug() << "Setting filter " << filter;
0154     m_filterProxy->setFilterRegExp( QRegExp( QUrl::fromPercentEncoding( filter.toUtf8() ) ) );
0155     //disable all other provider-buttons
0156     foreach( QAction * const providerAction, m_providerActions )
0157     {
0158         const Playlists::PlaylistProvider *provider =
0159                 providerAction->data().value<const Playlists::PlaylistProvider *>();
0160         if( provider )
0161             providerAction->setChecked(
0162                     m_filterProxy->filterRegExp().exactMatch( provider->prettyName() ) );
0163     }
0164 }
0165 
0166 QTreeView *
0167 PlaylistBrowserCategory::playlistView()
0168 {
0169     return m_playlistView;
0170 }
0171 
0172 void
0173 PlaylistBrowserCategory::toggleView( bool merged )
0174 {
0175     if( merged )
0176     {
0177         m_filterProxy->setSourceModel( m_byFolderProxy );
0178         m_playlistView->setItemDelegate( m_defaultItemDelegate );
0179         m_playlistView->setRootIsDecorated( true );
0180         setHelpText( m_addFolderAction->text(), m_addFolderAction );
0181     }
0182     else
0183     {
0184         m_filterProxy->setSourceModel( m_byProviderProxy );
0185         m_playlistView->setItemDelegate( m_byProviderDelegate );
0186         m_playlistView->setRootIsDecorated( false );
0187         setHelpText( i18n( "Folders are only shown in <b>merged view</b>." ), m_addFolderAction );
0188     }
0189 
0190     //folders don't make sense in per-provider view
0191     m_addFolderAction->setEnabled( merged );
0192 
0193     Amarok::config( m_configGroup ).writeEntry( s_mergeViewKey, merged );
0194 }
0195 
0196 void
0197 PlaylistBrowserCategory::setHelpText(const QString &text, QAction *qa)
0198 {
0199     qa->setStatusTip(text);
0200     qa->setToolTip(text);
0201     if ((qa->whatsThis()).isEmpty()) {
0202         qa->setWhatsThis(text);
0203     }
0204 }
0205 
0206 void
0207 PlaylistBrowserCategory::slotProviderAdded( Playlists::PlaylistProvider *provider, int category )
0208 {
0209     if( category != m_playlistCategory )
0210         return; //ignore
0211 
0212     if( !m_providerActions.keys().contains( provider ) )
0213         createProviderButton( provider );
0214 
0215     if( provider->playlistCount() != 0 )
0216         toggleView( false ); // set view to non-merged if new provider has some tracks
0217 
0218     slotToggleProviderButton();
0219 }
0220 
0221 void
0222 PlaylistBrowserCategory::slotProviderRemoved( Playlists::PlaylistProvider *provider, int category )
0223 {
0224     Q_UNUSED( category )
0225 
0226     if( m_providerActions.keys().contains( provider ) )
0227     {
0228         QAction *providerToggle = m_providerActions.take( provider );
0229         m_providerMenu->removeAction( providerToggle );
0230     }
0231 }
0232 
0233 void
0234 PlaylistBrowserCategory::createProviderButton( const Playlists::PlaylistProvider *provider )
0235 {
0236     QAction *providerToggle = new QAction( provider->icon(), provider->prettyName(), this );
0237     providerToggle->setCheckable( true );
0238     providerToggle->setChecked( true );
0239     providerToggle->setData( QVariant::fromValue( provider ) );
0240     connect( providerToggle, &QAction::toggled, this, &PlaylistBrowserCategory::slotToggleProviderButton );
0241     m_providerMenu->addAction( providerToggle );
0242 
0243     //if there is only one provider the button needs to be disabled.
0244     //When a second is added we can enable the first.
0245     if( m_providerActions.isEmpty() )
0246         providerToggle->setEnabled( false );
0247     else if( m_providerActions.count() == 1 )
0248         m_providerActions.values().first()->setEnabled( true );
0249 
0250     m_providerActions.insert( provider, providerToggle );
0251 }
0252 
0253 void
0254 PlaylistBrowserCategory::slotToggleProviderButton()
0255 {
0256     QString filter;
0257     QActionList checkedActions;
0258     foreach( const Playlists::PlaylistProvider *p, m_providerActions.keys() )
0259     {
0260         QAction *action = m_providerActions.value( p );
0261         if( action->isChecked() )
0262         {
0263             QString escapedName = QRegExp::escape( p->prettyName() ).replace( ' ', QLatin1String("\\ ") );
0264             filter += QString( filter.isEmpty() ? "%1" : "|%1" ).arg( escapedName );
0265             checkedActions << action;
0266             action->setEnabled( true );
0267         }
0268     }
0269     //if all are enabled the filter can be completely disabled.
0270     if( checkedActions.count() == m_providerActions.count() )
0271         filter.clear();
0272 
0273     m_filterProxy->setFilterRegExp( filter );
0274 
0275     //don't allow the last visible provider to be hidden
0276     if( checkedActions.count() == 1 )
0277         checkedActions.first()->setEnabled( false );
0278 }
0279 
0280 void
0281 PlaylistBrowserCategory::createNewFolder()
0282 {
0283     QString name = i18nc( "default name for new folder", "New Folder" );
0284     const QModelIndex &rootIndex = m_byFolderProxy->index(0,0);
0285     QModelIndexList folderIndices = m_byFolderProxy->match( rootIndex, Qt::DisplayRole, name, -1 );
0286     QString groupName = name;
0287     if( !folderIndices.isEmpty() )
0288     {
0289         int folderCount( 0 );
0290         foreach( const QModelIndex &folder, folderIndices )
0291         {
0292             QRegExp regex( name + " \\((\\d+)\\)" );
0293             int matchIndex = regex.indexIn( folder.data( Qt::DisplayRole ).toString() );
0294             if (matchIndex != -1)
0295             {
0296                 int newNumber = regex.cap( 1 ).toInt();
0297                 if (newNumber > folderCount)
0298                     folderCount = newNumber;
0299             }
0300         }
0301         groupName += QStringLiteral( " (%1)" ).arg( folderCount + 1 );
0302     }
0303     QModelIndex idx = m_filterProxy->mapFromSource( m_byFolderProxy->createNewFolder( groupName ) );
0304     m_playlistView->setCurrentIndex( idx );
0305     m_playlistView->edit( idx );
0306 }
0307 
0308 void
0309 PlaylistBrowserCategory::newPalette( const QPalette &palette )
0310 {
0311     Q_UNUSED( palette )
0312 
0313     The::paletteHandler()->updateItemView( m_playlistView );
0314 }
0315