1 /****************************************************************************************
2  * Copyright (c) 2004 Max Howell <max.howell@methylblue.com>                            *
3  * Copyright (c) 2008 Mark Kretschmann <kretschmann@kde.org>                            *
4  * Copyright (c) 2009 Artur Szymiec <artur.szymiec@gmail.com>                           *
5  *                                                                                      *
6  * This program is free software; you can redistribute it and/or modify it under        *
7  * the terms of the GNU General Public License as published by the Free Software        *
8  * Foundation; either version 2 of the License, or (at your option) any later           *
9  * version.                                                                             *
10  *                                                                                      *
11  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
12  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
13  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
14  *                                                                                      *
15  * You should have received a copy of the GNU General Public License along with         *
16  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
17  ****************************************************************************************/
18 
19 #define DEBUG_PREFIX "ActionClasses"
20 
21 #include "ActionClasses.h"
22 
23 #include "App.h"
24 #include "EngineController.h"
25 #include "KNotificationBackend.h"
26 #include "MainWindow.h"
27 #include "aboutdialog/OcsData.h"
28 #include "amarokconfig.h"
29 #include <config.h>
30 #include "core/support/Amarok.h"
31 #include "core/support/Debug.h"
32 #include "playlist/PlaylistActions.h"
33 #include "playlist/PlaylistModelStack.h"
34 #include "widgets/Osd.h"
35 
36 #include <QKeySequence>
37 
38 #include <KAuthorized>
39 #include <KHelpMenu>
40 #include <KLocalizedString>
41 #include <KToolBar>
42 #include <KGlobalAccel>
43 
44 extern OcsData ocsData;
45 
46 namespace Amarok
47 {
favorNone()48     bool favorNone()      { return AmarokConfig::favorTracks() == AmarokConfig::EnumFavorTracks::Off; }
favorScores()49     bool favorScores()    { return AmarokConfig::favorTracks() == AmarokConfig::EnumFavorTracks::HigherScores; }
favorRatings()50     bool favorRatings()   { return AmarokConfig::favorTracks() == AmarokConfig::EnumFavorTracks::HigherRatings; }
favorLastPlay()51     bool favorLastPlay()  { return AmarokConfig::favorTracks() == AmarokConfig::EnumFavorTracks::LessRecentlyPlayed; }
52 
53 
entireAlbums()54     bool entireAlbums()   { return AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatAlbum ||
55                                    AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RandomAlbum; }
56 
repeatEnabled()57     bool repeatEnabled()  { return AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatTrack ||
58                                    AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatAlbum  ||
59                                    AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatPlaylist; }
60 
randomEnabled()61     bool randomEnabled()  { return AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RandomTrack ||
62                                    AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RandomAlbum; }
63 }
64 
65 using namespace Amarok;
66 
67 KHelpMenu *Menu::s_helpMenu = 0;
68 
69 static void
safePlug(KActionCollection * ac,const char * name,QWidget * w)70 safePlug( KActionCollection *ac, const char *name, QWidget *w )
71 {
72     if( ac )
73     {
74         QAction *a = (QAction*) ac->action( name );
75         if( a && w ) w->addAction( a );
76     }
77 }
78 
79 
80 //////////////////////////////////////////////////////////////////////////////////////////
81 // MenuAction && Menu
82 // KActionMenu doesn't work very well, so we derived our own
83 //////////////////////////////////////////////////////////////////////////////////////////
84 
MenuAction(KActionCollection * ac,QObject * parent)85 MenuAction::MenuAction( KActionCollection *ac, QObject *parent )
86   : QAction( parent )
87 {
88     setText(i18n( "Amarok Menu" ));
89     ac->addAction(QStringLiteral("amarok_menu"), this);
90     setShortcutConfigurable ( false ); //FIXME disabled as it doesn't work, should use QCursor::pos()
91 }
92 
setShortcutConfigurable(bool b)93 void MenuAction::setShortcutConfigurable(bool b)
94 {
95     setProperty("isShortcutConfigurable", b);
96 }
97 
98 Menu* Menu::s_instance = nullptr;
99 
Menu(QWidget * parent)100 Menu::Menu( QWidget* parent )
101     : QMenu( parent )
102 {
103     s_instance = this;
104 
105     KActionCollection *ac = Amarok::actionCollection();
106 
107     safePlug( ac, "repeat", this );
108     safePlug( ac, "random_mode", this );
109 
110     addSeparator();
111 
112     safePlug( ac, "playlist_playmedia", this );
113 
114     addSeparator();
115 
116     safePlug( ac, "cover_manager", this );
117     safePlug( ac, "queue_manager", this );
118     safePlug( ac, "script_manager", this );
119 
120     addSeparator();
121 
122     safePlug( ac, "update_collection", this );
123     safePlug( ac, "rescan_collection", this );
124 
125 #ifndef Q_WS_MAC
126     addSeparator();
127 
128     safePlug( ac, KStandardAction::name(KStandardAction::ShowMenubar), this );
129 #endif
130 
131     addSeparator();
132 
133     safePlug( ac, KStandardAction::name(KStandardAction::ConfigureToolbars), this );
134     safePlug( ac, KStandardAction::name(KStandardAction::KeyBindings), this );
135 //    safePlug( ac, "options_configure_globals", this ); //we created this one
136     safePlug( ac, KStandardAction::name(KStandardAction::Preferences), this );
137 
138     addSeparator();
139 
140     addMenu( helpMenu( this ) );
141 
142     addSeparator();
143 
144     safePlug( ac, KStandardAction::name(KStandardAction::Quit), this );
145 }
146 
147 Menu*
instance()148 Menu::instance()
149 {
150     return s_instance ? s_instance : new Menu( The::mainWindow() );
151 }
152 
153 QMenu*
helpMenu(QWidget * parent)154 Menu::helpMenu( QWidget *parent ) //STATIC
155 {
156     if ( s_helpMenu == 0 )
157         s_helpMenu = new KHelpMenu( parent, KAboutData::applicationData(), Amarok::actionCollection() );
158 
159     QMenu* menu = s_helpMenu->menu();
160 
161     // "What's This" isn't currently defined for anything in Amarok, so let's remove it
162     s_helpMenu->action( KHelpMenu::menuWhatsThis )->setVisible( false );
163 
164     // Hide the default "About App" dialog, as we replace it with a custom one
165     s_helpMenu->action( KHelpMenu::menuAboutApp )->setVisible( false );
166 
167     return menu;
168 }
169 
170 //////////////////////////////////////////////////////////////////////////////////////////
171 // PlayPauseAction
172 //////////////////////////////////////////////////////////////////////////////////////////
173 
PlayPauseAction(KActionCollection * ac,QObject * parent)174 PlayPauseAction::PlayPauseAction( KActionCollection *ac, QObject *parent )
175         : KToggleAction( parent )
176 {
177 
178     ac->addAction( QStringLiteral("play_pause"), this );
179     setText( i18n( "Play/Pause" ) );
180     setShortcut( Qt::Key_Space );
181     KGlobalAccel::setGlobalShortcut( this, QKeySequence() );
182 
183     EngineController *engine = The::engineController();
184 
185     if( engine->isPaused() )
186         paused();
187     else if( engine->isPlaying() )
188         playing();
189     else
190         stopped();
191 
192     connect( this, &PlayPauseAction::triggered,
193              engine, &EngineController::playPause );
194 
195     connect( engine, &EngineController::stopped,
196              this, &PlayPauseAction::stopped );
197     connect( engine, &EngineController::paused,
198              this, &PlayPauseAction::paused );
199     connect( engine, &EngineController::trackPlaying,
200              this, &PlayPauseAction::playing );
201 }
202 
203 void
stopped()204 PlayPauseAction::stopped()
205 {
206     setChecked( false );
207     setIcon( QIcon::fromTheme(QStringLiteral("media-playback-start-amarok")) );
208 }
209 
210 void
paused()211 PlayPauseAction::paused()
212 {
213     setChecked( true );
214     setIcon( QIcon::fromTheme(QStringLiteral("media-playback-start-amarok")) );
215 }
216 
217 void
playing()218 PlayPauseAction::playing()
219 {
220     setChecked( false );
221     setIcon( QIcon::fromTheme(QStringLiteral("media-playback-pause-amarok")) );
222 }
223 
224 
225 //////////////////////////////////////////////////////////////////////////////////////////
226 // ToggleAction
227 //////////////////////////////////////////////////////////////////////////////////////////
228 
ToggleAction(const QString & text,void (* f)(bool),KActionCollection * const ac,const char * name,QObject * parent)229 ToggleAction::ToggleAction( const QString &text, void ( *f ) ( bool ), KActionCollection* const ac, const char *name, QObject *parent )
230         : KToggleAction( parent )
231         , m_function( f )
232 {
233     setText(text);
234     ac->addAction(name, this);
235 }
236 
setChecked(bool b)237 void ToggleAction::setChecked( bool b )
238 {
239     const bool announce = b != isChecked();
240 
241     m_function( b );
242     KToggleAction::setChecked( b );
243     AmarokConfig::self()->save(); //So we don't lose the setting when crashing
244     if( announce ) Q_EMIT toggled( b ); //KToggleAction doesn't do this for us. How gay!
245 }
246 
setEnabled(bool b)247 void ToggleAction::setEnabled( bool b )
248 {
249     const bool announce = b != isEnabled();
250 
251     KToggleAction::setEnabled( b );
252     AmarokConfig::self()->save(); //So we don't lose the setting when crashing
253     if( announce ) Q_EMIT QAction::triggered( b );
254 }
255 
256 //////////////////////////////////////////////////////////////////////////////////////////
257 // SelectAction
258 //////////////////////////////////////////////////////////////////////////////////////////
259 
SelectAction(const QString & text,void (* f)(int),KActionCollection * const ac,const char * name,QObject * parent)260 SelectAction::SelectAction( const QString &text, void ( *f ) ( int ), KActionCollection* const ac, const char *name, QObject *parent )
261         : KSelectAction( parent )
262         , m_function( f )
263 {
264     PERF_LOG( "In SelectAction" );
265     setText(text);
266     ac->addAction(name, this);
267 }
268 
setCurrentItem(int n)269 void SelectAction::setCurrentItem( int n )
270 {
271     const bool announce = n != currentItem();
272 
273     debug() << "setCurrentItem: " << n;
274 
275     m_function( n );
276     KSelectAction::setCurrentItem( n );
277     AmarokConfig::self()->save(); //So we don't lose the setting when crashing
278     if( announce ) Q_EMIT triggered( n );
279 }
280 
actionTriggered(QAction * a)281 void SelectAction::actionTriggered( QAction *a )
282 {
283     m_function( currentItem() );
284     AmarokConfig::self()->save();
285     KSelectAction::actionTriggered( a );
286 }
287 
setEnabled(bool b)288 void SelectAction::setEnabled( bool b )
289 {
290     const bool announce = b != isEnabled();
291 
292     KSelectAction::setEnabled( b );
293     AmarokConfig::self()->save(); //So we don't lose the setting when crashing
294     if( announce ) Q_EMIT QAction::triggered( b );
295 }
296 
setIcons(QStringList icons)297 void SelectAction::setIcons( QStringList icons )
298 {
299     m_icons = icons;
300     foreach( QAction *a, selectableActionGroup()->actions() )
301     {
302         a->setIcon( QIcon::fromTheme(icons.takeFirst()) );
303     }
304 }
305 
icons() const306 QStringList SelectAction::icons() const { return m_icons; }
307 
currentIcon() const308 QString SelectAction::currentIcon() const
309 {
310     if( m_icons.count() )
311         return m_icons.at( currentItem() );
312     return QString();
313 }
314 
currentText() const315 QString SelectAction::currentText() const {
316     return KSelectAction::currentText() + "<br /><br />" + i18n("Click to change");
317 }
318 
319 
320 void
setCurrentItem(int n)321 RandomAction::setCurrentItem( int n )
322 {
323     // Porting
324     //if( QAction *a = parentCollection()->action( "favor_tracks" ) )
325     //    a->setEnabled( n );
326     SelectAction::setCurrentItem( n );
327 }
328 
329 //////////////////////////////////////////////////////////////////////////////////////////
330 // ReplayGainModeAction
331 //////////////////////////////////////////////////////////////////////////////////////////
ReplayGainModeAction(KActionCollection * ac,QObject * parent)332 ReplayGainModeAction::ReplayGainModeAction( KActionCollection *ac, QObject *parent ) :
333     SelectAction( i18n( "&Replay Gain Mode" ), &AmarokConfig::setReplayGainMode, ac, "replay_gain_mode", parent )
334 {
335     setItems( QStringList() << i18nc( "Replay Gain state, as in, disabled", "&Off" )
336                             << i18nc( "Item, as in, music", "&Track" )
337                             << i18n( "&Album" ) );
338     EngineController *engine = EngineController::instance();
339     Q_ASSERT( engine );
340     if( engine->supportsGainAdjustments() )
341         setCurrentItem( AmarokConfig::replayGainMode() );
342     else
343     {
344         // Note: it would be nice to set a tooltip that would explain why this is disabled
345         // to users, but tooltips aren't shown in menu anyway :-(
346         actions().at( 1 )->setEnabled( false );
347         actions().at( 2 )->setEnabled( false );
348     }
349 }
350 
351 //////////////////////////////////////////////////////////////////////////////////////////
352 // BurnMenuAction
353 //////////////////////////////////////////////////////////////////////////////////////////
BurnMenuAction(KActionCollection * ac,QObject * parent)354 BurnMenuAction::BurnMenuAction( KActionCollection *ac, QObject *parent )
355   : QAction( parent )
356 {
357     setText(i18n( "Burn" ));
358     ac->addAction(QStringLiteral("burn_menu"), this);
359 }
360 
361 QWidget*
createWidget(QWidget * w)362 BurnMenuAction::createWidget( QWidget *w )
363 {
364     KToolBar *bar = dynamic_cast<KToolBar*>(w);
365 
366     if( bar && KAuthorized::authorizeAction( objectName() ) )
367     {
368         //const int id = QAction::getToolButtonID();
369 
370         //addContainer( bar, id );
371         w->addAction( this );
372         //connect( bar, &KToolBar::destroyed, this, &BurnMenuAction::slotDestroyed );
373 
374         //bar->insertButton( QString::null, id, true, i18n( "Burn" ), index );
375 
376         //KToolBarButton* button = bar->getButton( id );
377         //button->setPopup( Amarok::BurnMenu::instance() );
378         //button->setObjectName( "toolbutton_burn_menu" );
379         //button->setIcon( "k3b" );
380 
381         //return associatedWidgets().count() - 1;
382         return 0;
383     }
384     //else return -1;
385     else return 0;
386 }
387 
388 
389 BurnMenu* BurnMenu::s_instance = nullptr;
390 
BurnMenu(QWidget * parent)391 BurnMenu::BurnMenu( QWidget* parent )
392     : QMenu( parent )
393 {
394     s_instance = this;
395 
396     addAction( i18n("Current Playlist"), this, &BurnMenu::slotBurnCurrentPlaylist );
397     addAction( i18n("Selected Tracks"), this, &BurnMenu::slotBurnSelectedTracks );
398     //TODO add "album" and "all tracks by artist"
399 }
400 
401 QMenu*
instance()402 BurnMenu::instance()
403 {
404     return s_instance ? s_instance : new BurnMenu( The::mainWindow() );
405 }
406 
407 void
slotBurnCurrentPlaylist()408 BurnMenu::slotBurnCurrentPlaylist() //SLOT
409 {
410     //K3bExporter::instance()->exportCurrentPlaylist();
411 }
412 
413 void
slotBurnSelectedTracks()414 BurnMenu::slotBurnSelectedTracks() //SLOT
415 {
416     //K3bExporter::instance()->exportSelectedTracks();
417 }
418 
419 
420 //////////////////////////////////////////////////////////////////////////////////////////
421 // StopAction
422 //////////////////////////////////////////////////////////////////////////////////////////
423 
StopAction(KActionCollection * ac,QObject * parent)424 StopAction::StopAction( KActionCollection *ac, QObject *parent )
425   : QAction( parent )
426 {
427     ac->addAction( QStringLiteral("stop"), this );
428     setText( i18n( "Stop" ) );
429     setIcon( QIcon::fromTheme(QStringLiteral("media-playback-stop-amarok")) );
430     KGlobalAccel::setGlobalShortcut(this, QKeySequence() );
431     connect( this, &StopAction::triggered, this, &StopAction::stop );
432 
433     EngineController *engine = The::engineController();
434 
435     if( engine->isStopped() )
436         stopped();
437     else
438         playing();
439 
440     connect( engine, &EngineController::stopped,
441              this, &StopAction::stopped );
442     connect( engine, &EngineController::trackPlaying,
443              this, &StopAction::playing );
444 }
445 
446 void
stopped()447 StopAction::stopped()
448 {
449     setEnabled( false );
450 }
451 
452 void
playing()453 StopAction::playing()
454 {
455     setEnabled( true );
456 }
457 
458 void
stop()459 StopAction::stop()
460 {
461     The::engineController()->stop();
462 }
463 
464 //////////////////////////////////////////////////////////////////////////////////////////
465 // StopPlayingAfterCurrentTrackAction
466 //////////////////////////////////////////////////////////////////////////////////////////
467 
StopPlayingAfterCurrentTrackAction(KActionCollection * ac,QObject * parent)468 StopPlayingAfterCurrentTrackAction::StopPlayingAfterCurrentTrackAction( KActionCollection *ac, QObject *parent )
469 : QAction( parent )
470 {
471     ac->addAction( QStringLiteral("stop_after_current"), this );
472     setText( i18n( "Stop after current Track" ) );
473     setIcon( QIcon::fromTheme(QStringLiteral("media-playback-stop-amarok")) );
474     KGlobalAccel::setGlobalShortcut(this, QKeySequence( Qt::META + Qt::SHIFT + Qt::Key_V ) );
475     connect( this, &StopPlayingAfterCurrentTrackAction::triggered, this, &StopPlayingAfterCurrentTrackAction::stopPlayingAfterCurrentTrack );
476 }
477 
478 void
stopPlayingAfterCurrentTrack()479 StopPlayingAfterCurrentTrackAction::stopPlayingAfterCurrentTrack()
480 {
481     QString text;
482 
483     quint64 activeTrack = Playlist::ModelStack::instance()->bottom()->activeId();
484     if( activeTrack )
485     {
486         if( The::playlistActions()->willStopAfterTrack( activeTrack ) )
487         {
488             The::playlistActions()->stopAfterPlayingTrack( 0 );
489             text = i18n( "Stop after current track: Off" );
490         }
491         else
492         {
493             The::playlistActions()->stopAfterPlayingTrack( activeTrack );
494             text = i18n( "Stop after current track: On" );
495         }
496     }
497     else
498         text = i18n( "No track playing" );
499 
500     Amarok::OSD::instance()->OSDWidget::show( text );
501     if( Amarok::KNotificationBackend::instance()->isEnabled() )
502         Amarok::KNotificationBackend::instance()->show( i18n( "Amarok" ), text );
503 }
504