File indexing completed on 2024-04-21 04:47:53
0001 /**************************************************************************************** 0002 * Copyright (c) 2003 Stanislav Karchebny <berkus@users.sf.net> * 0003 * Copyright (c) 2003 Max Howell <max.howell@methylblue.com> * 0004 * Copyright (c) 2004 Enrico Ros <eros.kde@email.it> * 0005 * Copyright (c) 2006 Ian Monroe <ian@monroe.nu> * 0006 * Copyright (c) 2009-2011 Kevin Funk <krf@electrostorm.net> * 0007 * Copyright (c) 2009 Mark Kretschmann <kretschmann@kde.org> * 0008 * * 0009 * This program is free software; you can redistribute it and/or modify it under * 0010 * the terms of the GNU General Public License as published by the Free Software * 0011 * Foundation; either version 2 of the License, or (at your option) any later * 0012 * version. * 0013 * * 0014 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0015 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0016 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0017 * * 0018 * You should have received a copy of the GNU General Public License along with * 0019 * this program. If not, see <http://www.gnu.org/licenses/>. * 0020 ****************************************************************************************/ 0021 0022 #include "TrayIcon.h" 0023 0024 #include "App.h" 0025 #include "EngineController.h" 0026 #include "GlobalCurrentTrackActions.h" 0027 #include "SvgHandler.h" 0028 #include "amarokconfig.h" 0029 #include "core/capabilities/ActionsCapability.h" 0030 #include "core/capabilities/BookmarkThisCapability.h" 0031 #include "core/meta/Meta.h" 0032 #include "core/meta/Statistics.h" 0033 #include "core/support/Amarok.h" 0034 #include "playlist/PlaylistActions.h" 0035 0036 #include <KLocalizedString> 0037 #include <KIconLoader> 0038 0039 #include <QAction> 0040 #include <QFontMetrics> 0041 #include <QMenu> 0042 #include <QPixmap> 0043 #include <QStandardPaths> 0044 #include <QToolTip> 0045 0046 #ifdef Q_WS_MAC 0047 extern void qt_mac_set_dock_menu(QMenu *); 0048 #endif 0049 0050 Amarok::TrayIcon::TrayIcon( QObject *parent ) 0051 : KStatusNotifierItem( parent ) 0052 , m_track( The::engineController()->currentTrack() ) 0053 { 0054 PERF_LOG( "Beginning TrayIcon Constructor" ); 0055 KActionCollection* const ac = Amarok::actionCollection(); 0056 0057 setStatus( KStatusNotifierItem::Active ); 0058 0059 // Remove the "Configure Amarok..." action, as it makes no sense in the tray menu 0060 const QString preferences = KStandardAction::name( KStandardAction::Preferences ); 0061 contextMenu()->removeAction( ac->action( preferences ) ); 0062 0063 PERF_LOG( "Before adding actions" ); 0064 0065 #ifdef Q_WS_MAC 0066 // Add these functions to the dock icon menu in OS X 0067 qt_mac_set_dock_menu( contextMenu() ); 0068 contextMenu()->addAction( ac->action( "playlist_playmedia" ) ); 0069 contextMenu()->addSeparator(); 0070 #endif 0071 0072 contextMenu()->addAction( ac->action( "prev" ) ); 0073 contextMenu()->addAction( ac->action( "play_pause" ) ); 0074 contextMenu()->addAction( ac->action( "stop" ) ); 0075 contextMenu()->addAction( ac->action( "next" ) ); 0076 0077 contextMenu()->addSeparator(); 0078 0079 contextMenu()->setObjectName( "TrayIconContextMenu" ); 0080 0081 PERF_LOG( "Initializing system tray icon" ); 0082 0083 setIconByName( "amarok" ); 0084 updateOverlayIcon(); 0085 updateToolTipIcon(); 0086 updateMenu(); 0087 0088 const EngineController* engine = The::engineController(); 0089 connect( engine, &EngineController::trackPlaying, 0090 this, &TrayIcon::trackPlaying ); 0091 connect( engine, &EngineController::stopped, 0092 this, &TrayIcon::stopped ); 0093 connect( engine, &EngineController::paused, 0094 this, &TrayIcon::paused ); 0095 0096 connect( engine, &EngineController::trackMetadataChanged, 0097 this, &TrayIcon::trackMetadataChanged ); 0098 0099 connect( engine, &EngineController::albumMetadataChanged, 0100 this, &TrayIcon::albumMetadataChanged ); 0101 0102 connect( engine, &EngineController::volumeChanged, 0103 this, &TrayIcon::updateToolTip ); 0104 0105 connect( engine, &EngineController::muteStateChanged, 0106 this, &TrayIcon::updateToolTip ); 0107 0108 connect( engine, &EngineController::playbackStateChanged, 0109 this, &TrayIcon::updateOverlayIcon ); 0110 0111 connect( this, &TrayIcon::scrollRequested, this, &TrayIcon::slotScrollRequested ); 0112 connect( this, &TrayIcon::secondaryActivateRequested, 0113 The::engineController(), &EngineController::playPause ); 0114 } 0115 0116 void 0117 Amarok::TrayIcon::updateToolTipIcon() 0118 { 0119 updateToolTip(); // the normal update 0120 0121 if( m_track ) 0122 { 0123 if( m_track->album() && m_track->album()->hasImage() ) 0124 { 0125 QPixmap image = The::svgHandler()->imageWithBorder( m_track->album(), KIconLoader::SizeLarge, 5 ); 0126 setToolTipIconByPixmap( image ); 0127 } 0128 else 0129 { 0130 setToolTipIconByName( "amarok" ); 0131 } 0132 } 0133 else 0134 { 0135 setToolTipIconByName( "amarok" ); 0136 } 0137 } 0138 0139 0140 void 0141 Amarok::TrayIcon::updateToolTip() 0142 { 0143 if( m_track ) 0144 { 0145 setToolTipTitle( i18n( "Now playing" ) ); 0146 0147 QStringList tooltip; 0148 tooltip << The::engineController()->prettyNowPlaying( false ); 0149 0150 QString volume; 0151 if ( The::engineController()->isMuted() ) 0152 { 0153 volume = i18n( "Muted" ); 0154 } 0155 else 0156 { 0157 volume = i18n( "%1%", The::engineController()->volume() ); 0158 } 0159 tooltip << i18n( "<i>Volume: %1</i>", volume ); 0160 0161 Meta::StatisticsPtr statistics = m_track->statistics(); 0162 const float score = statistics->score(); 0163 if( score > 0.f ) 0164 { 0165 tooltip << i18n( "Score: %1", QString::number( score, 'f', 2 ) ); 0166 } 0167 0168 const int rating = statistics->rating(); 0169 if( rating > 0 ) 0170 { 0171 QString stars; 0172 for( int i = 0; i < rating / 2; ++i ) 0173 stars += QStringLiteral( "<img src=\"%1\" height=\"%2\" width=\"%3\">" ) 0174 .arg( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/star.png" ) ) 0175 .arg( QFontMetrics( QToolTip::font() ).height() ) 0176 .arg( QFontMetrics( QToolTip::font() ).height() ); 0177 if( rating % 2 ) 0178 stars += QStringLiteral( "<img src=\"%1\" height=\"%2\" width=\"%3\">" ) 0179 .arg( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/smallstar.png" ) ) 0180 .arg( QFontMetrics( QToolTip::font() ).height() ) 0181 .arg( QFontMetrics( QToolTip::font() ).height() ); 0182 0183 tooltip << i18n( "Rating: %1", stars ); 0184 } 0185 0186 const int count = statistics->playCount(); 0187 if( count > 0 ) 0188 { 0189 tooltip << i18n( "Play count: %1", count ); 0190 } 0191 0192 const QDateTime lastPlayed = statistics->lastPlayed(); 0193 tooltip << i18n( "Last played: %1", Amarok::verboseTimeSince( lastPlayed ) ); 0194 0195 setToolTipSubTitle( tooltip.join("<br>") ); 0196 } 0197 else 0198 { 0199 setToolTipTitle( pApp->applicationDisplayName() ); 0200 setToolTipSubTitle( The::engineController()->prettyNowPlaying( false ) ); 0201 } 0202 } 0203 0204 void 0205 Amarok::TrayIcon::trackPlaying( const Meta::TrackPtr &track ) 0206 { 0207 m_track = track; 0208 0209 updateMenu(); 0210 updateToolTipIcon(); 0211 } 0212 0213 void 0214 Amarok::TrayIcon::paused() 0215 { 0216 updateToolTipIcon(); 0217 0218 } 0219 0220 void 0221 Amarok::TrayIcon::stopped() 0222 { 0223 m_track = nullptr; 0224 updateMenu(); // remove custom track actions on stop 0225 updateToolTipIcon(); 0226 } 0227 0228 void 0229 Amarok::TrayIcon::trackMetadataChanged( const Meta::TrackPtr &track ) 0230 { 0231 Q_UNUSED( track ) 0232 0233 updateToolTip(); 0234 updateMenu(); 0235 } 0236 0237 void 0238 Amarok::TrayIcon::albumMetadataChanged( const Meta::AlbumPtr &album ) 0239 { 0240 Q_UNUSED( album ) 0241 0242 updateToolTipIcon(); 0243 updateMenu(); 0244 } 0245 0246 void 0247 Amarok::TrayIcon::slotScrollRequested( int delta, Qt::Orientation orientation ) 0248 { 0249 Q_UNUSED( orientation ) 0250 0251 The::engineController()->increaseVolume( delta / Amarok::VOLUME_SENSITIVITY ); 0252 } 0253 0254 QAction* 0255 Amarok::TrayIcon::action( const QString& name, const QMap<QString, QAction*> &actionByName ) 0256 { 0257 QAction* action = nullptr; 0258 0259 if ( !name.isEmpty() ) 0260 action = actionByName.value(name); 0261 0262 return action; 0263 } 0264 0265 void 0266 Amarok::TrayIcon::updateMenu() 0267 { 0268 foreach( QAction* action, m_extraActions ) 0269 { 0270 contextMenu()->removeAction( action ); 0271 // -- delete actions without parent (e.g. the ones from the capabilities) 0272 if( action && !action->parent() ) 0273 { 0274 delete action; 0275 } 0276 } 0277 0278 QMap<QString, QAction*> actionByName; 0279 foreach (QAction* action, actionCollection()) 0280 { 0281 actionByName.insert(action->text(), action); 0282 } 0283 0284 m_extraActions.clear(); 0285 0286 contextMenu()->removeAction( m_separator.data() ); 0287 0288 delete m_separator.data(); 0289 0290 if( m_track ) 0291 { 0292 foreach( QAction *action, The::globalCurrentTrackActions()->actions() ) 0293 { 0294 m_extraActions.append( action ); 0295 connect( action, &QObject::destroyed, this, [this, action]() { m_extraActions.removeAll( action ); } ); 0296 } 0297 0298 QScopedPointer<Capabilities::ActionsCapability> ac( m_track->create<Capabilities::ActionsCapability>() ); 0299 if( ac ) 0300 { 0301 QList<QAction*> actions = ac->actions(); 0302 foreach( QAction *action, actions ) 0303 { 0304 m_extraActions.append( action ); 0305 connect( action, &QObject::destroyed, this, [this, action]() { m_extraActions.removeAll( action ); } ); 0306 } 0307 } 0308 0309 QScopedPointer<Capabilities::BookmarkThisCapability> btc( m_track->create<Capabilities::BookmarkThisCapability>() ); 0310 if( btc ) 0311 { 0312 QAction *action = btc->bookmarkAction(); 0313 m_extraActions.append( action ); 0314 connect( action, &QObject::destroyed, this, [this, action]() { m_extraActions.removeAll( action ); } ); 0315 } 0316 } 0317 0318 // second statement checks if the menu has already been populated (first startup), if not: do it 0319 if( m_extraActions.count() > 0 || 0320 contextMenu()->actions().last() != actionByName.value( "file_quit" ) ) 0321 { 0322 // remove the 2 bottom items, so we can push them to the bottom again 0323 contextMenu()->removeAction( action( "file_quit", actionByName ) ); 0324 contextMenu()->removeAction( action( "minimizeRestore", actionByName ) ); 0325 0326 foreach( QAction* action, m_extraActions ) 0327 contextMenu()->addAction( action ); 0328 0329 m_separator = contextMenu()->addSeparator(); 0330 // readd 0331 contextMenu()->addAction( action( "minimizeRestore", actionByName ) ); 0332 contextMenu()->addAction( action( "file_quit", actionByName ) ); 0333 } 0334 } 0335 0336 void 0337 Amarok::TrayIcon::updateOverlayIcon() 0338 { 0339 if( The::engineController()->isPlaying() ) 0340 setOverlayIconByName( "media-playback-start" ); 0341 else if( The::engineController()->isPaused() ) 0342 setOverlayIconByName( "media-playback-pause" ); 0343 else 0344 setOverlayIconByName( QString() ); 0345 } 0346