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