1 /****************************************************************************************
2 * Copyright (c) 2007 Ian Monroe <ian@monroe.nu> *
3 * Copyright (c) 2009 Leo Franchi <lfranchi@kde.org> *
4 * *
5 * This program is free software; you can redistribute it and/or modify it under *
6 * the terms of the GNU General Public License as published by the Free Software *
7 * Foundation; either version 2 of the License, or (at your option) version 3 or *
8 * any later version accepted by the membership of KDE e.V. (or its successor approved *
9 * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of *
10 * version 3 of the license. *
11 * *
12 * This program is distributed in the hope that it will be useful, but WITHOUT ANY *
13 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
14 * PARTICULAR PURPOSE. See the GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License along with *
17 * this program. If not, see <http://www.gnu.org/licenses/>. *
18 ****************************************************************************************/
19
20 #define DEBUG_PREFIX "PlaylistDock"
21
22 #include "PlaylistDock.h"
23
24 #include "ActionClasses.h"
25 #include "App.h"
26 #include "MainWindow.h"
27 #include "PaletteHandler.h"
28 #include "amarokconfig.h"
29 #include "amarokurls/AmarokUrl.h"
30 #include "core/support/Debug.h"
31 #include "playlist/PlaylistActions.h"
32 #include "playlist/PlaylistController.h"
33 #include "playlist/PlaylistDefines.h"
34 #include "playlist/PlaylistInfoWidget.h"
35 #include "playlist/PlaylistModelStack.h"
36 #include "playlist/PlaylistQueueEditor.h"
37 #include "playlist/PlaylistToolBar.h"
38 #include "playlist/ProgressiveSearchWidget.h"
39 #include "playlist/layouts/LayoutManager.h"
40 #include "playlist/navigators/NavigatorConfigAction.h"
41 #include "playlistmanager/PlaylistManager.h"
42 #include "core-impl/playlists/providers/user/UserPlaylistProvider.h"
43 #include "widgets/HorizontalDivider.h"
44 #include "widgets/BoxWidget.h"
45
46 #include <QLabel>
47 #include <QStandardPaths>
48 #include <QToolBar>
49
50 #include <KActionMenu>
51 #include <KToolBarSpacerAction>
52
53 static const QString s_dynMode( QStringLiteral("dynamic_mode") );
54 static const QString s_repopulate( QStringLiteral("repopulate") );
55 static const QString s_turnOff( QStringLiteral("turn_off") );
56
Dock(QWidget * parent)57 Playlist::Dock::Dock( QWidget* parent )
58 : AmarokDockWidget( i18n( "&Playlist" ), parent )
59 , m_barBox( 0 )
60 {
61 setObjectName( QStringLiteral("Playlist dock") );
62 setAllowedAreas( Qt::AllDockWidgetAreas );
63 }
64
65 Playlist::PrettyListView *
currentView()66 Playlist::Dock::currentView()
67 {
68 ensurePolish();
69 return m_playlistView;
70 }
71
72 Playlist::SortWidget *
sortWidget()73 Playlist::Dock::sortWidget()
74 {
75 ensurePolish();
76 return m_sortWidget;
77 }
78
79 Playlist::ProgressiveSearchWidget *
searchWidget()80 Playlist::Dock::searchWidget()
81 {
82 ensurePolish();
83 return m_searchWidget;
84 }
85
86 void
polish()87 Playlist::Dock::polish()
88 {
89 m_mainWidget = new BoxWidget( true, this );
90 setWidget( m_mainWidget );
91 m_mainWidget->setContentsMargins( 0, 0, 0, 0 );
92 m_mainWidget->setFrameShape( QFrame::NoFrame );
93 m_mainWidget->setMinimumWidth( 200 );
94 m_mainWidget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Ignored );
95 m_mainWidget->setFocus( Qt::ActiveWindowFocusReason );
96
97 m_sortWidget = new Playlist::SortWidget( m_mainWidget );
98 new HorizontalDivider( m_mainWidget );
99
100 m_searchWidget = new Playlist::ProgressiveSearchWidget( m_mainWidget );
101
102 // show visual indication of dynamic playlists being enabled
103 connect( The::playlistActions(), &Playlist::Actions::navigatorChanged,
104 this, &Playlist::Dock::showDynamicHint );
105 m_dynamicHintWidget = new QLabel( i18n( "<a href='%1'>Dynamic Mode</a> Enabled. "
106 "<a href='%2'>Repopulate</a> | <a href='%3'>Turn off</a>", s_dynMode,
107 s_repopulate, s_turnOff ), m_mainWidget );
108 m_dynamicHintWidget->setAlignment( Qt::AlignCenter );
109 m_dynamicHintWidget->setTextInteractionFlags( Qt::LinksAccessibleByKeyboard | Qt::LinksAccessibleByMouse );
110 m_dynamicHintWidget->setMinimumSize( 1, 1 ); // so that it doesn't prevent playlist from shrinking
111 connect( m_dynamicHintWidget, &QLabel::linkActivated, this, &Dock::slotDynamicHintLinkActivated );
112
113 QFont dynamicHintWidgetFont = m_dynamicHintWidget->font();
114 dynamicHintWidgetFont.setPointSize( dynamicHintWidgetFont.pointSize() + 1 );
115 m_dynamicHintWidget->setFont( dynamicHintWidgetFont );
116
117 showDynamicHint();
118
119 paletteChanged( pApp->palette() );
120 connect( The::paletteHandler(), &PaletteHandler::newPalette,
121 this, &Playlist::Dock::paletteChanged );
122
123 QWidget * layoutHolder = new QWidget( m_mainWidget );
124
125 QVBoxLayout* mainPlaylistlayout = new QVBoxLayout( layoutHolder );
126 mainPlaylistlayout->setContentsMargins( 0, 0, 0, 0 );
127
128 m_playlistView = new PrettyListView();
129 m_playlistView->show();
130
131 connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::filterChanged,
132 m_playlistView, &Playlist::PrettyListView::find );
133 connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::next,
134 m_playlistView, &Playlist::PrettyListView::findNext );
135 connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::previous,
136 m_playlistView, &Playlist::PrettyListView::findPrevious );
137 connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::filterCleared,
138 m_playlistView, &Playlist::PrettyListView::clearSearchTerm );
139 connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::showOnlyMatches,
140 m_playlistView, &Playlist::PrettyListView::showOnlyMatches );
141 connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::activateFilterResult,
142 m_playlistView, &Playlist::PrettyListView::playFirstSelected );
143 connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::downPressed, m_playlistView, &Playlist::PrettyListView::downOneTrack );
144 connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::upPressed, m_playlistView, &Playlist::PrettyListView::upOneTrack );
145
146 connect( The::mainWindow(), &MainWindow::switchQueueStateShortcut,
147 m_playlistView, &Playlist::PrettyListView::switchQueueState );
148
149 KConfigGroup searchConfig = Amarok::config(QStringLiteral("Playlist Search"));
150 m_playlistView->showOnlyMatches( searchConfig.readEntry( "ShowOnlyMatches", false ) );
151
152 connect( m_playlistView, &Playlist::PrettyListView::found, m_searchWidget, &Playlist::ProgressiveSearchWidget::match );
153 connect( m_playlistView, &Playlist::PrettyListView::notFound, m_searchWidget, &Playlist::ProgressiveSearchWidget::noMatch );
154
155 connect( LayoutManager::instance(), &LayoutManager::activeLayoutChanged,
156 m_playlistView, &Playlist::PrettyListView::reset );
157
158 mainPlaylistlayout->setSpacing( 0 );
159 mainPlaylistlayout->addWidget( m_playlistView );
160
161 ModelStack::instance(); //This also creates the Controller.
162
163 { // START: Playlist toolbar
164 // action toolbar
165 m_barBox = new BoxWidget( false, m_mainWidget );
166 m_barBox->setObjectName( QStringLiteral("PlaylistBarBox") );
167 m_barBox->setContentsMargins( 0, 0, 4, 0 );
168 m_barBox->setFixedHeight( 36 );
169
170 // Use QToolBar instead of KToolBar, see bug 228390
171 Playlist::ToolBar *plBar = new Playlist::ToolBar( m_barBox );
172 plBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
173 plBar->setMovable( false );
174
175 QActionGroup *playlistActions = new QActionGroup( m_mainWidget );
176 playlistActions->addAction( Amarok::actionCollection()->action( QStringLiteral("playlist_clear") ) );
177
178 m_savePlaylistMenu = new KActionMenu( QIcon::fromTheme( QStringLiteral("document-save-amarok") ),
179 i18n("&Save Current Playlist"), m_mainWidget );
180 m_savePlaylistMenu->addAction( Amarok::actionCollection()->action( QStringLiteral("playlist_export") ) );
181
182 m_saveActions = new KActionCollection( m_mainWidget );
183
184 connect( m_savePlaylistMenu, &KActionMenu::triggered,
185 this, &Dock::slotSaveCurrentPlaylist );
186 foreach( Playlists::PlaylistProvider *provider, The::playlistManager()->providersForCategory(
187 PlaylistManager::UserPlaylist ) )
188 {
189 playlistProviderAdded( provider, PlaylistManager::UserPlaylist );
190 }
191
192 connect( The::playlistManager(), &PlaylistManager::providerAdded,
193 this, &Dock::playlistProviderAdded );
194 connect( The::playlistManager(), &PlaylistManager::providerRemoved,
195 this, &Dock::playlistProviderRemoved );
196
197 playlistActions->addAction( m_savePlaylistMenu );
198
199 playlistActions->addAction( Amarok::actionCollection()->action( QStringLiteral("playlist_undo") ) );
200 //redo action can be accessed from menu > Playlist
201
202 playlistActions->addAction( Amarok::actionCollection()->action( QStringLiteral("show_active_track") ) );
203
204 plBar->addCollapsibleActions( playlistActions );
205
206 NavigatorConfigAction *navigatorConfig = new NavigatorConfigAction( m_mainWidget );
207 plBar->addAction( navigatorConfig );
208
209 QToolButton *toolButton =
210 qobject_cast<QToolButton *>(plBar->widgetForAction( navigatorConfig ) );
211 if( toolButton )
212 toolButton->setPopupMode( QToolButton::InstantPopup );
213
214 plBar->addAction( new KToolBarSpacerAction( m_mainWidget ) );
215
216 // label widget
217 new PlaylistInfoWidget( m_barBox );
218 } // END Playlist Toolbar
219
220 //set correct colors
221 paletteChanged( QApplication::palette() );
222
223 // If it is active, clear the search filter before replacing the playlist. Fixes Bug #200709.
224 connect( The::playlistController(), &Playlist::Controller::replacingPlaylist,
225 this, &Playlist::Dock::clearFilterIfActive );
226
227 }
228
229 QSize
sizeHint() const230 Playlist::Dock::sizeHint() const
231 {
232 return QSize( static_cast<QWidget*>( parent() )->size().width() / 4 , 300 );
233 }
234
235 void
paletteChanged(const QPalette & palette)236 Playlist::Dock::paletteChanged( const QPalette &palette )
237 {
238 const QString backgroundColor = palette.color( QPalette::Active, QPalette::Mid ).name();
239 const QString textColor = palette.color( QPalette::Active, QPalette::HighlightedText ).name();
240 const QString linkColor = palette.color( QPalette::Active, QPalette::Link ).name();
241 const QString ridgeColor = palette.color( QPalette::Active, QPalette::Window ).name();
242
243 QString hintStyle( "QLabel { background-color: %1; color: %2; border-radius: 3px; } "
244 "a { color: %3; }" );
245 hintStyle = hintStyle.arg( backgroundColor, textColor, linkColor );
246
247 QString barStyle( "QFrame#PlaylistBarBox { border: 1px ridge %1; background-color: %2; "
248 " color: %3; border-radius: 3px; } "
249 "QLabel { color: %4; }" );
250 barStyle = barStyle.arg( ridgeColor, backgroundColor, textColor, textColor );
251
252 m_dynamicHintWidget->setStyleSheet( hintStyle );
253 if( m_barBox )
254 m_barBox->setStyleSheet( barStyle );
255
256 }
257
258 void
playlistProviderAdded(Playlists::PlaylistProvider * provider,int category)259 Playlist::Dock::playlistProviderAdded( Playlists::PlaylistProvider *provider, int category )
260 {
261 if( category != PlaylistManager::UserPlaylist )
262 return;
263
264 debug() << "Adding provider: " << provider->prettyName();
265 Playlists::UserPlaylistProvider *userProvider =
266 dynamic_cast<Playlists::UserPlaylistProvider *>(provider);
267 if( userProvider == 0 )
268 return;
269 QAction *action = new QAction( userProvider->icon(),
270 i18n("&Save playlist to \"%1\"", provider->prettyName() ),
271 this );
272 action->setData( QVariant::fromValue( QPointer<Playlists::UserPlaylistProvider>( userProvider ) ) );
273 m_saveActions->addAction( QString::number( (qlonglong) userProvider ), action );
274
275 // insert the playlist provider actions before "export"
276 QAction* exportAction = Amarok::actionCollection()->action( QStringLiteral("playlist_export") );
277 m_savePlaylistMenu->insertAction( exportAction, action );
278 connect( action, &QAction::triggered, this, &Playlist::Dock::slotSaveCurrentPlaylist );
279 }
280
281 void
playlistProviderRemoved(Playlists::PlaylistProvider * provider,int category)282 Playlist::Dock::playlistProviderRemoved( Playlists::PlaylistProvider *provider, int category )
283 {
284 if( category != PlaylistManager::UserPlaylist )
285 return;
286
287 QAction *action = m_saveActions->action( QString::number( (qlonglong) provider ) );
288 if( action )
289 m_savePlaylistMenu->removeAction( action );
290 else
291 warning() << __PRETTY_FUNCTION__ << ": no save action for provider" << provider->prettyName();
292 }
293
294 void
slotSaveCurrentPlaylist()295 Playlist::Dock::slotSaveCurrentPlaylist()
296 {
297 DEBUG_BLOCK
298
299 QAction *action = qobject_cast<QAction *>( QObject::sender() );
300 if( action == 0 )
301 return;
302
303 QWeakPointer<Playlists::UserPlaylistProvider> pointer =
304 action->data().value< QWeakPointer<Playlists::UserPlaylistProvider> >();
305 Playlists::UserPlaylistProvider* provider = pointer.data();
306
307 const Meta::TrackList tracks = The::playlist()->tracks();
308 The::playlistManager()->save( tracks, Amarok::generatePlaylistName( tracks ), provider );
309 }
310
311 void
slotEditQueue()312 Playlist::Dock::slotEditQueue()
313 {
314 if( m_playlistQueueEditor ) {
315 m_playlistQueueEditor->raise();
316 return;
317 }
318 m_playlistQueueEditor = new PlaylistQueueEditor;
319 m_playlistQueueEditor->setAttribute( Qt::WA_DeleteOnClose );
320 m_playlistQueueEditor->show();
321 }
322
323 void
showActiveTrack()324 Playlist::Dock::showActiveTrack()
325 {
326 ensurePolish();
327 m_playlistView->scrollToActiveTrack();
328 }
329
330 void
editTrackInfo()331 Playlist::Dock::editTrackInfo()
332 {
333 m_playlistView->editTrackInformation();
334 }
335
336 void
showDynamicHint()337 Playlist::Dock::showDynamicHint() // slot
338 {
339 DEBUG_BLOCK
340
341 if( AmarokConfig::dynamicMode() )
342 m_dynamicHintWidget->show();
343 else
344 m_dynamicHintWidget->hide();
345 }
346
347 void
clearFilterIfActive()348 Playlist::Dock::clearFilterIfActive() // slot
349 {
350 DEBUG_BLOCK
351 KConfigGroup config = Amarok::config( QStringLiteral("Playlist Search") );
352 bool filterActive = config.readEntry( "ShowOnlyMatches", true );
353
354 if( filterActive )
355 m_searchWidget->slotFilterClear();
356 }
357
358 void
slotDynamicHintLinkActivated(const QString & href)359 Playlist::Dock::slotDynamicHintLinkActivated( const QString &href )
360 {
361 if( href == s_dynMode )
362 AmarokUrl( QStringLiteral("amarok://navigate/playlists/dynamic category") ).run();
363 else if( href == s_repopulate )
364 The::playlistActions()->repopulateDynamicPlaylist();
365 else if( href == s_turnOff )
366 The::playlistActions()->enableDynamicMode( false );
367 }
368