1 /****************************************************************************************
2  * Copyright (c) 2009-2010 Bart Cerneels <bart.cerneels@kde.org>                        *
3  *                                                                                      *
4  * This program is free software; you can redistribute it and/or modify it under        *
5  * the terms of the GNU General Public License as published by the Free Software        *
6  * Foundation; either version 2 of the License, or (at your option) any later           *
7  * version.                                                                             *
8  *                                                                                      *
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
11  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
12  *                                                                                      *
13  * You should have received a copy of the GNU General Public License along with         *
14  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
15  ****************************************************************************************/
16 
17 #define DEBUG_PREFIX "PlaylistBrowserCategory"
18 
19 #include "PlaylistBrowserCategory.h"
20 
21 #include "amarokconfig.h"
22 #include "core/support/Debug.h"
23 #include "PaletteHandler.h"
24 #include "PlaylistBrowserModel.h"
25 #include "playlist/PlaylistModel.h"
26 #include "playlistmanager/PlaylistManager.h"
27 #include "PlaylistsInFoldersProxy.h"
28 #include "PlaylistsByProviderProxy.h"
29 #include "PlaylistBrowserFilterProxy.h"
30 #include "SvgHandler.h"
31 #include "PlaylistBrowserView.h"
32 #include "widgets/PrettyTreeDelegate.h"
33 
34 #include <KActionMenu>
35 #include <KConfigGroup>
36 #include <QIcon>
37 #include <QStandardPaths>
38 #include <KToolBar>
39 
40 #include <QHeaderView>
41 #include <QToolBar>
42 
43 #include <typeinfo>
44 
45 using namespace PlaylistBrowserNS;
46 
47 QString PlaylistBrowserCategory::s_mergeViewKey( QStringLiteral("Merged View") );
48 
PlaylistBrowserCategory(int playlistCategory,const QString & categoryName,const QString & configGroup,PlaylistBrowserModel * model,QWidget * parent)49 PlaylistBrowserCategory::PlaylistBrowserCategory( int playlistCategory,
50                                                   const QString &categoryName,
51                                                   const QString &configGroup,
52                                                   PlaylistBrowserModel *model,
53                                                   QWidget *parent ) :
54     BrowserCategory( categoryName, parent ),
55     m_configGroup( configGroup ),
56     m_playlistCategory( playlistCategory )
57 {
58     setContentsMargins( 0, 0, 0, 0 );
59     setImagePath( QStandardPaths::locate( QStandardPaths::GenericDataLocation, QStringLiteral("amarok/images/hover_info_podcasts.png") ) );
60 
61     // set background
62     if( AmarokConfig::showBrowserBackgroundImage() )
63         setBackgroundImage( imagePath() );
64 
65     m_toolBar = new KToolBar( this, false, false );
66     m_toolBar->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
67 
68     //a QWidget with minimumExpanding makes the next button right aligned.
69     QWidget *spacerWidget = new QWidget( this );
70     spacerWidget->setSizePolicy( QSizePolicy::MinimumExpanding,
71                                  QSizePolicy::MinimumExpanding );
72     // add a separator so subclasses can add their actions before it
73     m_separator = m_toolBar->addWidget( spacerWidget );
74 
75     m_toolBar->addSeparator();
76 
77     m_addFolderAction = new QAction( QIcon::fromTheme( QStringLiteral("folder-new") ), i18n( "Add Folder" ), this  );
78     m_addFolderAction->setPriority( QAction::LowPriority );
79     m_toolBar->addAction( m_addFolderAction );
80     connect( m_addFolderAction, &QAction::triggered, this, &PlaylistBrowserCategory::createNewFolder );
81 
82     m_providerMenu = new KActionMenu( QIcon::fromTheme( QStringLiteral("checkbox") ), i18n( "Visible Sources"), this );
83     m_providerMenu->setDelayed( false );
84     m_providerMenu->setPriority( QAction::HighPriority );
85     m_toolBar->addAction( m_providerMenu );
86 
87     QAction *toggleAction = new QAction( QIcon::fromTheme( QStringLiteral("view-list-tree") ), i18n( "Merged View" ),
88                                          m_toolBar );
89     toggleAction->setCheckable( true );
90     toggleAction->setChecked( Amarok::config( m_configGroup ).readEntry( s_mergeViewKey, false ) );
91     toggleAction->setPriority( QAction::LowPriority );
92     m_toolBar->addAction( toggleAction );
93     connect( toggleAction, &QAction::triggered, this, &PlaylistBrowserCategory::toggleView );
94 
95     m_toolBar->addSeparator();
96 
97     m_byProviderProxy = new PlaylistsByProviderProxy( m_playlistCategory, this );
98     m_byProviderProxy->setSourceModel( model );
99     m_byProviderProxy->setGroupedColumn( PlaylistBrowserModel::ProviderColumn );
100     m_byFolderProxy = new PlaylistsInFoldersProxy( model );
101 
102     m_filterProxy = new PlaylistBrowserFilterProxy( this );
103     //no need to setModel on filterProxy since it will be done in toggleView anyway.
104     m_filterProxy->setDynamicSortFilter( true );
105     m_filterProxy->setFilterKeyColumn( PlaylistBrowserModel::ProviderColumn );
106 
107     m_playlistView = new PlaylistBrowserView( m_filterProxy, this );
108     m_defaultItemDelegate = m_playlistView->itemDelegate();
109     m_byProviderDelegate = new PrettyTreeDelegate( m_playlistView );
110 
111     toggleView( toggleAction->isChecked() );
112 
113     m_playlistView->setFrameShape( QFrame::NoFrame );
114     m_playlistView->setContentsMargins( 0, 0, 0, 0 );
115     m_playlistView->setAlternatingRowColors( true );
116     m_playlistView->header()->hide();
117     //hide all columns except the first.
118     for( int i = 1; i < m_playlistView->model()->columnCount(); i++ )
119       m_playlistView->hideColumn( i );
120 
121     m_playlistView->setDragEnabled( true );
122     m_playlistView->setAcceptDrops( true );
123     m_playlistView->setDropIndicatorShown( true );
124 
125     foreach( const Playlists::PlaylistProvider *provider,
126              The::playlistManager()->providersForCategory( m_playlistCategory ) )
127     {
128         createProviderButton( provider );
129     }
130 
131     connect( The::playlistManager(), &PlaylistManager::providerAdded,
132              this, &PlaylistBrowserCategory::slotProviderAdded );
133     connect( The::playlistManager(), &PlaylistManager::providerRemoved,
134              this, &PlaylistBrowserCategory::slotProviderRemoved );
135 
136     connect( The::paletteHandler(), &PaletteHandler::newPalette,
137              this, &PlaylistBrowserCategory::newPalette );
138 }
139 
~PlaylistBrowserCategory()140 PlaylistBrowserCategory::~PlaylistBrowserCategory()
141 {
142 }
143 
144 QString
filter() const145 PlaylistBrowserCategory::filter() const
146 {
147     return QUrl::toPercentEncoding( m_filterProxy->filterRegExp().pattern() );
148 }
149 
150 void
setFilter(const QString & filter)151 PlaylistBrowserCategory::setFilter( const QString &filter )
152 {
153     debug() << "Setting filter " << filter;
154     m_filterProxy->setFilterRegExp( QRegExp( QUrl::fromPercentEncoding( filter.toUtf8() ) ) );
155     //disable all other provider-buttons
156     foreach( QAction * const providerAction, m_providerActions )
157     {
158         const Playlists::PlaylistProvider *provider =
159                 providerAction->data().value<const Playlists::PlaylistProvider *>();
160         if( provider )
161             providerAction->setChecked(
162                     m_filterProxy->filterRegExp().exactMatch( provider->prettyName() ) );
163     }
164 }
165 
166 QTreeView *
playlistView()167 PlaylistBrowserCategory::playlistView()
168 {
169     return m_playlistView;
170 }
171 
172 void
toggleView(bool merged)173 PlaylistBrowserCategory::toggleView( bool merged )
174 {
175     if( merged )
176     {
177         m_filterProxy->setSourceModel( m_byFolderProxy );
178         m_playlistView->setItemDelegate( m_defaultItemDelegate );
179         m_playlistView->setRootIsDecorated( true );
180         setHelpText( m_addFolderAction->text(), m_addFolderAction );
181     }
182     else
183     {
184         m_filterProxy->setSourceModel( m_byProviderProxy );
185         m_playlistView->setItemDelegate( m_byProviderDelegate );
186         m_playlistView->setRootIsDecorated( false );
187         setHelpText( i18n( "Folders are only shown in <b>merged view</b>." ), m_addFolderAction );
188     }
189 
190     //folders don't make sense in per-provider view
191     m_addFolderAction->setEnabled( merged );
192 
193     Amarok::config( m_configGroup ).writeEntry( s_mergeViewKey, merged );
194 }
195 
196 void
setHelpText(const QString & text,QAction * qa)197 PlaylistBrowserCategory::setHelpText(const QString &text, QAction *qa)
198 {
199     qa->setStatusTip(text);
200     qa->setToolTip(text);
201     if ((qa->whatsThis()).isEmpty()) {
202         qa->setWhatsThis(text);
203     }
204 }
205 
206 void
slotProviderAdded(Playlists::PlaylistProvider * provider,int category)207 PlaylistBrowserCategory::slotProviderAdded( Playlists::PlaylistProvider *provider, int category )
208 {
209     if( category != m_playlistCategory )
210         return; //ignore
211 
212     if( !m_providerActions.keys().contains( provider ) )
213         createProviderButton( provider );
214 
215     if( provider->playlistCount() != 0 )
216         toggleView( false ); // set view to non-merged if new provider has some tracks
217 
218     slotToggleProviderButton();
219 }
220 
221 void
slotProviderRemoved(Playlists::PlaylistProvider * provider,int category)222 PlaylistBrowserCategory::slotProviderRemoved( Playlists::PlaylistProvider *provider, int category )
223 {
224     Q_UNUSED( category )
225 
226     if( m_providerActions.keys().contains( provider ) )
227     {
228         QAction *providerToggle = m_providerActions.take( provider );
229         m_providerMenu->removeAction( providerToggle );
230     }
231 }
232 
233 void
createProviderButton(const Playlists::PlaylistProvider * provider)234 PlaylistBrowserCategory::createProviderButton( const Playlists::PlaylistProvider *provider )
235 {
236     QAction *providerToggle = new QAction( provider->icon(), provider->prettyName(), this );
237     providerToggle->setCheckable( true );
238     providerToggle->setChecked( true );
239     providerToggle->setData( QVariant::fromValue( provider ) );
240     connect( providerToggle, &QAction::toggled, this, &PlaylistBrowserCategory::slotToggleProviderButton );
241     m_providerMenu->addAction( providerToggle );
242 
243     //if there is only one provider the button needs to be disabled.
244     //When a second is added we can enable the first.
245     if( m_providerActions.isEmpty() )
246         providerToggle->setEnabled( false );
247     else if( m_providerActions.count() == 1 )
248         m_providerActions.values().first()->setEnabled( true );
249 
250     m_providerActions.insert( provider, providerToggle );
251 }
252 
253 void
slotToggleProviderButton()254 PlaylistBrowserCategory::slotToggleProviderButton()
255 {
256     QString filter;
257     QActionList checkedActions;
258     foreach( const Playlists::PlaylistProvider *p, m_providerActions.keys() )
259     {
260         QAction *action = m_providerActions.value( p );
261         if( action->isChecked() )
262         {
263             QString escapedName = QRegExp::escape( p->prettyName() ).replace( ' ', QLatin1String("\\ ") );
264             filter += QString( filter.isEmpty() ? "%1" : "|%1" ).arg( escapedName );
265             checkedActions << action;
266             action->setEnabled( true );
267         }
268     }
269     //if all are enabled the filter can be completely disabled.
270     if( checkedActions.count() == m_providerActions.count() )
271         filter.clear();
272 
273     m_filterProxy->setFilterRegExp( filter );
274 
275     //don't allow the last visible provider to be hidden
276     if( checkedActions.count() == 1 )
277         checkedActions.first()->setEnabled( false );
278 }
279 
280 void
createNewFolder()281 PlaylistBrowserCategory::createNewFolder()
282 {
283     QString name = i18nc( "default name for new folder", "New Folder" );
284     const QModelIndex &rootIndex = m_byFolderProxy->index(0,0);
285     QModelIndexList folderIndices = m_byFolderProxy->match( rootIndex, Qt::DisplayRole, name, -1 );
286     QString groupName = name;
287     if( !folderIndices.isEmpty() )
288     {
289         int folderCount( 0 );
290         foreach( const QModelIndex &folder, folderIndices )
291         {
292             QRegExp regex( name + " \\((\\d+)\\)" );
293             int matchIndex = regex.indexIn( folder.data( Qt::DisplayRole ).toString() );
294             if (matchIndex != -1)
295             {
296                 int newNumber = regex.cap( 1 ).toInt();
297                 if (newNumber > folderCount)
298                     folderCount = newNumber;
299             }
300         }
301         groupName += QStringLiteral( " (%1)" ).arg( folderCount + 1 );
302     }
303     QModelIndex idx = m_filterProxy->mapFromSource( m_byFolderProxy->createNewFolder( groupName ) );
304     m_playlistView->setCurrentIndex( idx );
305     m_playlistView->edit( idx );
306 }
307 
308 void
newPalette(const QPalette & palette)309 PlaylistBrowserCategory::newPalette( const QPalette &palette )
310 {
311     Q_UNUSED( palette )
312 
313     The::paletteHandler()->updateItemView( m_playlistView );
314 }
315 
316