diff --git a/filehandle.h b/filehandle.h index 40121a72..d1003522 100644 --- a/filehandle.h +++ b/filehandle.h @@ -1,89 +1,89 @@ /** * Copyright (C) 2004 Scott Wheeler * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -#ifndef FILEHANDLE_H -#define FILEHANDLE_H +#ifndef JUK_FILEHANDLE_H +#define JUK_FILEHANDLE_H #include class QFileInfo; class QDateTime; class QDataStream; class QStringList; class CoverInfo; class Tag; class CacheDataStream; template class QList; /** * An value based, explicitly shared wrapper around file related information * used in JuK's playlists. */ class FileHandle { public: FileHandle(); FileHandle(const FileHandle &f); explicit FileHandle(const QFileInfo &info, const QString &path = QString()); explicit FileHandle(const QString &path); FileHandle(const QString &path, CacheDataStream &s); ~FileHandle(); /** * Forces the FileHandle to reread its information from the disk. */ void refresh(); void setFile(const QString &path); Tag *tag() const; CoverInfo *coverInfo() const; QString absFilePath() const; const QFileInfo &fileInfo() const; bool isNull() const; bool current() const; const QDateTime &lastModified() const; void read(CacheDataStream &s); FileHandle &operator=(const FileHandle &f); bool operator==(const FileHandle &f) const; bool operator!=(const FileHandle &f) const; static QStringList properties(); QString property(const QString &name) const; static const FileHandle &null(); private: class FileHandlePrivate; FileHandlePrivate *d; void setup(const QFileInfo &info, const QString &path); }; typedef QList FileHandleList; QDataStream &operator<<(QDataStream &s, const FileHandle &f); CacheDataStream &operator>>(CacheDataStream &s, FileHandle &f); #endif // vim: set et sw=4 tw=0 sta: diff --git a/juk.cpp b/juk.cpp index f52605a3..d7d58c3d 100644 --- a/juk.cpp +++ b/juk.cpp @@ -1,605 +1,602 @@ /** * Copyright (C) 2002-2004 Scott Wheeler * Copyright (C) 2008, 2009, 2017 Michael Pyne * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "juk.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "slideraction.h" #include "statuslabel.h" #include "systemtray.h" #include "keydialog.h" #include "tagguesserconfigdlg.h" #include "filerenamerconfigdlg.h" #include "scrobbler.h" #include "scrobbleconfigdlg.h" #include "actioncollection.h" #include "cache.h" #include "playlistsplitter.h" #include "collectionlist.h" #include "covermanager.h" #include "tagtransactionmanager.h" #include "juk_debug.h" using namespace ActionCollection; JuK* JuK::m_instance; template void deleteAndClear(T *&ptr) { delete ptr; ptr = 0; } //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// JuK::JuK(const QStringList &filesToOpen, QWidget *parent) : KXmlGuiWindow(parent, Qt::WindowFlags(Qt::WA_DeleteOnClose)), m_splitter(nullptr), m_statusLabel(nullptr), m_systemTray(nullptr), m_player(new PlayerManager), m_scrobbler(nullptr), m_filesToOpen(filesToOpen), m_shuttingDown(false) { // Expect segfaults if you change this order. m_instance = this; readSettings(); Cache::ensureAppDataStorageExists(); setupActions(); setupLayout(); bool firstRun = !KSharedConfig::openConfig()->hasGroup("MainWindow"); if(firstRun) { KConfigGroup mainWindowConfig(KSharedConfig::openConfig(), "MainWindow"); KConfigGroup playToolBarConfig(&mainWindowConfig, "Toolbar playToolBar"); playToolBarConfig.writeEntry("ToolButtonStyle", "IconOnly"); } QSize defaultSize(800, 480); if(QApplication::isRightToLeft()) setupGUI(defaultSize, ToolBar | Save | Create, "jukui-rtl.rc"); else setupGUI(defaultSize, ToolBar | Save | Create); // Center the GUI if this is our first run ever. if(firstRun) { QRect r = rect(); r.moveCenter(QApplication::desktop()->screenGeometry().center()); move(r.topLeft()); } connect(m_splitter, SIGNAL(guiReady()), SLOT(slotSetupSystemTray())); readConfig(); setupGlobalAccels(); activateScrobblerIfEnabled(); connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), SLOT(slotAboutToQuit())); // slotCheckCache loads the cached entries first to populate the collection list QTimer::singleShot(0, this, SLOT(slotClearOldCovers())); QTimer::singleShot(0, CollectionList::instance(), SLOT(startLoadingCachedItems())); QTimer::singleShot(0, this, SLOT(slotProcessArgs())); } JuK::~JuK() { } JuK* JuK::JuKInstance() { return m_instance; } PlayerManager *JuK::playerManager() const { return m_player; } void JuK::coverDownloaded(const QPixmap &cover) { QString event(cover.isNull() ? "coverFailed" : "coverDownloaded"); KNotification *notification = new KNotification(event, this); notification->setPixmap(cover); notification->setFlags(KNotification::CloseOnTimeout); if(cover.isNull()) notification->setText(i18n("Your album art failed to download.")); else notification->setText(i18n("Your album art has finished downloading.")); notification->sendEvent(); } //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// void JuK::setupLayout() { new TagTransactionManager(this); - qCDebug(JUK_LOG) << "Creating GUI"; - QTime stopwatch; - stopwatch.start(); - m_splitter = new PlaylistSplitter(m_player, this); setCentralWidget(m_splitter); - m_statusLabel = new StatusLabel(m_splitter->playlist(), statusBar()); - connect(CollectionList::instance(), &CollectionList::signalCollectionChanged, - m_statusLabel, &StatusLabel::playlistItemDataHasChanged); + m_statusLabel = new StatusLabel(*m_splitter->playlist(), statusBar()); statusBar()->addWidget(m_statusLabel, 1); - m_player->setStatusLabel(m_statusLabel); + connect(m_player, &PlayerManager::tick, m_statusLabel, + &StatusLabel::setItemCurrentTime); + connect(m_player, &PlayerManager::totalTimeChanged, + m_statusLabel, &StatusLabel::setItemTotalTime); + connect(m_splitter, &PlaylistSplitter::currentPlaylistChanged, + m_statusLabel, &StatusLabel::slotCurrentPlaylistHasChanged); m_splitter->setFocus(); - - qCDebug(JUK_LOG) << "GUI created in" << stopwatch.elapsed() << "ms"; } void JuK::setupActions() { KActionCollection *collection = ActionCollection::actions(); // Setup KDE standard actions that JuK uses. KStandardAction::quit(this, SLOT(slotQuit()), collection); KStandardAction::undo(this, SLOT(slotUndo()), collection); KStandardAction::cut(collection); KStandardAction::copy(collection); KStandardAction::paste(collection); QAction *clear = KStandardAction::clear(collection); KStandardAction::selectAll(collection); KStandardAction::keyBindings(this, SLOT(slotEditKeys()), collection); // Setup the menu which handles the random play options. KActionMenu *actionMenu = collection->add("actionMenu"); actionMenu->setText(i18n("&Random Play")); actionMenu->setIcon(QIcon::fromTheme( QLatin1String( "media-playlist-shuffle" ))); actionMenu->setDelayed(false); QActionGroup* randomPlayGroup = new QActionGroup(this); QAction *act = collection->add("disableRandomPlay"); act->setText(i18n("&Disable Random Play")); act->setIcon(QIcon::fromTheme( QLatin1String( "go-down" ))); act->setActionGroup(randomPlayGroup); actionMenu->addAction(act); m_randomPlayAction = collection->add("randomPlay"); m_randomPlayAction->setText(i18n("Use &Random Play")); m_randomPlayAction->setIcon(QIcon::fromTheme( QLatin1String( "media-playlist-shuffle" ))); m_randomPlayAction->setActionGroup(randomPlayGroup); actionMenu->addAction(m_randomPlayAction); act = collection->add("albumRandomPlay"); act->setText(i18n("Use &Album Random Play")); act->setIcon(QIcon::fromTheme( QLatin1String( "media-playlist-shuffle" ))); act->setActionGroup(randomPlayGroup); connect(act, SIGNAL(triggered(bool)), SLOT(slotCheckAlbumNextAction(bool))); actionMenu->addAction(act); act = collection->addAction("removeFromPlaylist", clear, SLOT(clear())); act->setText(i18n("Remove From Playlist")); act->setIcon(QIcon::fromTheme( QLatin1String( "list-remove" ))); act = collection->addAction("play", m_player, SLOT(play())); act->setText(i18n("&Play")); act->setIcon(QIcon::fromTheme( QLatin1String( "media-playback-start" ))); act = collection->addAction("pause", m_player, SLOT(pause())); act->setText(i18n("P&ause")); act->setIcon(QIcon::fromTheme( QLatin1String( "media-playback-pause" ))); act = collection->addAction("stop", m_player, SLOT(stop())); act->setText(i18n("&Stop")); act->setIcon(QIcon::fromTheme( QLatin1String( "media-playback-stop" ))); act = new KToolBarPopupAction(QIcon::fromTheme( QLatin1String( "media-skip-backward") ), i18nc("previous track", "Previous" ), collection); collection->addAction("back", act); connect(act, SIGNAL(triggered(bool)), m_player, SLOT(back())); act = collection->addAction("forward", m_player, SLOT(forward())); act->setText(i18nc("next track", "&Next")); act->setIcon(QIcon::fromTheme( QLatin1String( "media-skip-forward" ))); act = collection->addAction("loopPlaylist"); act->setText(i18n("&Loop Playlist")); act->setCheckable(true); act = collection->add("resizeColumnsManually"); act->setText(i18n("&Resize Playlist Columns Manually")); // the following are not visible by default act = collection->addAction("mute", m_player, SLOT(mute())); act->setText(i18nc("silence playback", "Mute")); act->setIcon(QIcon::fromTheme( QLatin1String( "audio-volume-muted" ))); act = collection->addAction("volumeUp", m_player, SLOT(volumeUp())); act->setText(i18n("Volume Up")); act->setIcon(QIcon::fromTheme( QLatin1String( "audio-volume-high" ))); act = collection->addAction("volumeDown", m_player, SLOT(volumeDown())); act->setText(i18n("Volume Down")); act->setIcon(QIcon::fromTheme( QLatin1String( "audio-volume-low" ))); act = collection->addAction("playPause", m_player, SLOT(playPause())); act->setText(i18n("Play / Pause")); act->setIcon(QIcon::fromTheme( QLatin1String( "media-playback-start" ))); act = collection->addAction("seekForward", m_player, SLOT(seekForward())); act->setText(i18n("Seek Forward")); act->setIcon(QIcon::fromTheme( QLatin1String( "media-seek-forward" ))); act = collection->addAction("seekBack", m_player, SLOT(seekBack())); act->setText(i18n("Seek Back")); act->setIcon(QIcon::fromTheme( QLatin1String( "media-seek-backward" ))); act = collection->addAction("showHide", this, SLOT(slotShowHide())); act->setText(i18n("Show / Hide")); ////////////////////////////////////////////////// // settings menu ////////////////////////////////////////////////// m_toggleSystemTrayAction = collection->add("toggleSystemTray"); m_toggleSystemTrayAction->setText(i18n("&Dock in System Tray")); connect(m_toggleSystemTrayAction, SIGNAL(triggered(bool)), SLOT(slotToggleSystemTray(bool))); m_toggleDockOnCloseAction = collection->add("dockOnClose"); m_toggleDockOnCloseAction->setText(i18n("&Stay in System Tray on Close")); m_togglePopupsAction = collection->add("togglePopups"); m_togglePopupsAction->setText(i18n("Popup &Track Announcement")); act = collection->add("saveUpcomingTracks"); act->setText(i18n("Save &Play Queue on Exit")); act = collection->addAction("tagGuesserConfig", this, SLOT(slotConfigureTagGuesser())); act->setText(i18n("&Tag Guesser...")); act = collection->addAction("fileRenamerConfig", this, SLOT(slotConfigureFileRenamer())); act->setText(i18n("&File Renamer...")); act = collection->addAction("scrobblerConfig", this, SLOT(slotConfigureScrobbling())); act->setText(i18n("&Configure scrobbling...")); ////////////////////////////////////////////////// // just in the toolbar ////////////////////////////////////////////////// collection->addAction("trackPositionAction", new TrackPositionAction(i18n("Track Position"), this)); collection->addAction("volumeAction", new VolumeAction(i18n("Volume"), this)); ActionCollection::actions()->addAssociatedWidget(this); foreach (QAction* action, ActionCollection::actions()->actions()) action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } void JuK::slotSetupSystemTray() { if(m_toggleSystemTrayAction && m_toggleSystemTrayAction->isChecked()) { qCDebug(JUK_LOG) << "Setting up systray"; QTime stopwatch; stopwatch.start(); m_systemTray = new SystemTray(m_player, this); m_systemTray->setObjectName( QLatin1String("systemTray" )); m_toggleDockOnCloseAction->setEnabled(true); m_togglePopupsAction->setEnabled(true); qCDebug(JUK_LOG) << "Finished setting up systray, took" << stopwatch.elapsed() << "ms"; } else { m_systemTray = 0; m_toggleDockOnCloseAction->setEnabled(false); m_togglePopupsAction->setEnabled(false); } } void JuK::setupGlobalAccels() { KeyDialog::setupActionShortcut("play"); KeyDialog::setupActionShortcut("playPause"); KeyDialog::setupActionShortcut("stop"); KeyDialog::setupActionShortcut("back"); KeyDialog::setupActionShortcut("forward"); KeyDialog::setupActionShortcut("seekBack"); KeyDialog::setupActionShortcut("seekForward"); KeyDialog::setupActionShortcut("volumeUp"); KeyDialog::setupActionShortcut("volumeDown"); KeyDialog::setupActionShortcut("mute"); KeyDialog::setupActionShortcut("showHide"); KeyDialog::setupActionShortcut("forwardAlbum"); } void JuK::slotProcessArgs() { CollectionList::instance()->addFiles(m_filesToOpen); } void JuK::slotClearOldCovers() { // Find all saved covers from the previous run of JuK and clear them out, in case // we find our tracks in a different order this run, which would cause old saved // covers to be wrong. // See mpris2/mediaplayer2player.cpp QString tmpDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); QStringList nameFilters; nameFilters << QStringLiteral("juk-cover-*.png"); QDirIterator jukCoverIter(tmpDir, nameFilters); while (jukCoverIter.hasNext()) { QFile::remove(jukCoverIter.next()); } } void JuK::keyPressEvent(QKeyEvent *e) { if (e->key() >= Qt::Key_Back && e->key() <= Qt::Key_MediaLast) e->accept(); KXmlGuiWindow::keyPressEvent(e); } /** * These are settings that need to be know before setting up the GUI. */ void JuK::readSettings() { KConfigGroup config(KSharedConfig::openConfig(), "Settings"); m_startDocked = config.readEntry("StartDocked", false); } void JuK::readConfig() { // player settings KConfigGroup playerConfig(KSharedConfig::openConfig(), "Player"); if(m_player) { const int maxVolume = 100; const int volume = playerConfig.readEntry("Volume", maxVolume); m_player->setVolume(volume * 0.01); //bool enableCrossfade = playerConfig.readEntry("CrossfadeTracks", true); //m_player->setCrossfadeEnabled(enableCrossfade); //ActionCollection::action("crossfadeTracks")->setChecked(enableCrossfade); } // Default to no random play ActionCollection::action("disableRandomPlay")->setChecked(true); QString randomPlayMode = playerConfig.readEntry("RandomPlay", "Disabled"); if(randomPlayMode == "true" || randomPlayMode == "Normal") m_randomPlayAction->setChecked(true); else if(randomPlayMode == "AlbumRandomPlay") ActionCollection::action("albumRandomPlay")->setChecked(true); bool loopPlaylist = playerConfig.readEntry("LoopPlaylist", false); ActionCollection::action("loopPlaylist")->setChecked(loopPlaylist); // general settings KConfigGroup settingsConfig(KSharedConfig::openConfig(), "Settings"); bool dockInSystemTray = settingsConfig.readEntry("DockInSystemTray", true); m_toggleSystemTrayAction->setChecked(dockInSystemTray); bool dockOnClose = settingsConfig.readEntry("DockOnClose", true); m_toggleDockOnCloseAction->setChecked(dockOnClose); bool showPopups = settingsConfig.readEntry("TrackPopup", false); m_togglePopupsAction->setChecked(showPopups); } void JuK::saveConfig() { // player settings KConfigGroup playerConfig(KSharedConfig::openConfig(), "Player"); if (m_player) { playerConfig.writeEntry("Volume", static_cast(100.0 * m_player->volume())); } playerConfig.writeEntry("RandomPlay", m_randomPlayAction->isChecked()); QAction *a = ActionCollection::action("loopPlaylist"); playerConfig.writeEntry("LoopPlaylist", a->isChecked()); playerConfig.writeEntry("CrossfadeTracks", false); // TODO bring back a = ActionCollection::action("albumRandomPlay"); if(a->isChecked()) playerConfig.writeEntry("RandomPlay", "AlbumRandomPlay"); else if(m_randomPlayAction->isChecked()) playerConfig.writeEntry("RandomPlay", "Normal"); else playerConfig.writeEntry("RandomPlay", "Disabled"); // general settings KConfigGroup settingsConfig(KSharedConfig::openConfig(), "Settings"); settingsConfig.writeEntry("StartDocked", m_startDocked); settingsConfig.writeEntry("DockInSystemTray", m_toggleSystemTrayAction->isChecked()); settingsConfig.writeEntry("DockOnClose", m_toggleDockOnCloseAction->isChecked()); settingsConfig.writeEntry("TrackPopup", m_togglePopupsAction->isChecked()); KSharedConfig::openConfig()->sync(); } bool JuK::queryClose() { if(!m_shuttingDown && !qApp->isSavingSession() && m_systemTray && m_toggleDockOnCloseAction->isChecked()) { KMessageBox::information(this, i18n("Closing the main window will keep JuK running in the system tray. " "Use Quit from the File menu to quit the application."), i18n("Docking in System Tray"), "hideOnCloseInfo"); hide(); return false; } else { // Some phonon backends will crash on shutdown unless we've stopped // playback. if(m_player->playing()) m_player->stop(); // Save configuration data. m_startDocked = !isVisible(); saveConfig(); return true; } } //////////////////////////////////////////////////////////////////////////////// // private slot definitions //////////////////////////////////////////////////////////////////////////////// void JuK::slotShowHide() { setHidden(!isHidden()); } void JuK::slotAboutToQuit() { m_shuttingDown = true; deleteAndClear(m_systemTray); deleteAndClear(m_splitter); deleteAndClear(m_player); deleteAndClear(m_statusLabel); } void JuK::slotQuit() { m_shuttingDown = true; saveConfig(); qApp->quit(); } //////////////////////////////////////////////////////////////////////////////// // settings menu //////////////////////////////////////////////////////////////////////////////// void JuK::slotToggleSystemTray(bool enabled) { if(enabled && !m_systemTray) slotSetupSystemTray(); else if(!enabled && m_systemTray) { delete m_systemTray; m_systemTray = 0; m_toggleDockOnCloseAction->setEnabled(false); m_togglePopupsAction->setEnabled(false); } } void JuK::slotEditKeys() { KeyDialog(ActionCollection::actions(), this).configure(); } void JuK::slotConfigureTagGuesser() { TagGuesserConfigDlg(this).exec(); } void JuK::slotConfigureFileRenamer() { FileRenamerConfigDlg(this).exec(); } void JuK::slotConfigureScrobbling() { ScrobbleConfigDlg(this).exec(); activateScrobblerIfEnabled(); } void JuK::activateScrobblerIfEnabled() { bool isScrobbling = Scrobbler::isScrobblingEnabled(); if (!m_scrobbler && isScrobbling) { m_scrobbler = new Scrobbler(this); connect (m_player, SIGNAL(signalItemChanged(FileHandle)), m_scrobbler, SLOT(nowPlaying(FileHandle))); } else if (m_scrobbler && !isScrobbling) { delete m_scrobbler; m_scrobbler = 0; } } void JuK::slotUndo() { TagTransactionManager::instance()->undo(); } void JuK::slotCheckAlbumNextAction(bool albumRandomEnabled) { // If album random play is enabled, then enable the Play Next Album action // unless we're not playing right now. if(albumRandomEnabled && !m_player->playing()) albumRandomEnabled = false; action("forwardAlbum")->setEnabled(albumRandomEnabled); } // vim: set et sw=4 tw=0 sta: diff --git a/mpris2/mediaplayer2player.cpp b/mpris2/mediaplayer2player.cpp index 28d57c3d..fca9b7fa 100644 --- a/mpris2/mediaplayer2player.cpp +++ b/mpris2/mediaplayer2player.cpp @@ -1,324 +1,324 @@ /*********************************************************************** * Copyright 2012 Eike Hein * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . ***********************************************************************/ #include "mpris2/mediaplayer2player.h" #include "juk.h" #include "playermanager.h" #include "coverinfo.h" #include "playlist.h" #include "playlistitem.h" #include "tag.h" #include "filehandle.h" #include #include #include #include #include #include #include static QByteArray idFromPlaylistItem(const PlaylistItem *item) { return QByteArray("/org/kde/juk/tid_") + QByteArray::number(item->trackId(), 16).rightJustified(8, '0'); } MediaPlayer2Player::MediaPlayer2Player(QObject* parent) : QDBusAbstractAdaptor(parent) , m_player(JuK::JuKInstance()->playerManager()) { - connect(m_player, SIGNAL(signalItemChanged(FileHandle)), this, SLOT(currentSourceChanged())); - connect(m_player, SIGNAL(signalPlay()), this, SLOT(stateUpdated())); - connect(m_player, SIGNAL(signalPause()), this, SLOT(stateUpdated())); - connect(m_player, SIGNAL(signalStop()), this, SLOT(stateUpdated())); - connect(m_player, SIGNAL(totalTimeChanged(int)), this, SLOT(totalTimeChanged())); - connect(m_player, SIGNAL(seekableChanged(bool)), this, SLOT(seekableChanged(bool))); - connect(m_player, SIGNAL(volumeChanged(float)), this, SLOT(volumeChanged(float))); - connect(m_player, SIGNAL(seeked(int)), this, SLOT(seeked(int))); + connect(m_player, &PlayerManager::signalItemChanged, this, &MediaPlayer2Player::currentSourceChanged); + connect(m_player, &PlayerManager::signalPlay, this, &MediaPlayer2Player::stateUpdated); + connect(m_player, &PlayerManager::signalPause, this, &MediaPlayer2Player::stateUpdated); + connect(m_player, &PlayerManager::signalStop, this, &MediaPlayer2Player::stateUpdated); + connect(m_player, &PlayerManager::totalTimeChanged, this, &MediaPlayer2Player::totalTimeChanged); + connect(m_player, &PlayerManager::seekableChanged, this, &MediaPlayer2Player::seekableChanged); + connect(m_player, &PlayerManager::volumeChanged, this, &MediaPlayer2Player::volumeChanged); + connect(m_player, &PlayerManager::seeked, this, &MediaPlayer2Player::seeked); } MediaPlayer2Player::~MediaPlayer2Player() { } bool MediaPlayer2Player::CanGoNext() const { return true; } void MediaPlayer2Player::Next() const { m_player->forward(); } bool MediaPlayer2Player::CanGoPrevious() const { return true; } void MediaPlayer2Player::Previous() const { m_player->back(); } bool MediaPlayer2Player::CanPause() const { return true; } void MediaPlayer2Player::Pause() const { m_player->pause(); } void MediaPlayer2Player::PlayPause() const { m_player->playPause(); } void MediaPlayer2Player::Stop() const { m_player->stop(); } bool MediaPlayer2Player::CanPlay() const { return true; } void MediaPlayer2Player::Play() const { m_player->play(); } void MediaPlayer2Player::SetPosition(const QDBusObjectPath& TrackId, qlonglong Position) const { PlaylistItem *playingItem = Playlist::playingItem(); if (!playingItem) { return; } // Verify the SetPosition call is against the currently playing track QByteArray currentTrackId = idFromPlaylistItem(playingItem); if (TrackId.path().toLatin1() == currentTrackId) { m_player->seek(Position / 1000); } } void MediaPlayer2Player::OpenUri(QString Uri) const { QUrl url = QUrl::fromUserInput(Uri); // JuK does not yet support KIO if (url.isLocalFile()) { m_player->play(url.toLocalFile()); } } QString MediaPlayer2Player::PlaybackStatus() const { if (m_player->playing()) { return QLatin1String("Playing"); } else if (m_player->paused()) { return QLatin1String("Paused"); } return QLatin1String("Stopped"); } QString MediaPlayer2Player::LoopStatus() const { // TODO: Implement, although this is orthogonal to the PlayerManager return "None"; } void MediaPlayer2Player::setLoopStatus(const QString& loopStatus) const { Q_UNUSED(loopStatus) } double MediaPlayer2Player::Rate() const { return 1.0; } void MediaPlayer2Player::setRate(double rate) const { Q_UNUSED(rate) } bool MediaPlayer2Player::Shuffle() const { // TODO: Implement return false; } void MediaPlayer2Player::setShuffle(bool shuffle) const { Q_UNUSED(shuffle) // TODO: Implement } QVariantMap MediaPlayer2Player::Metadata() const { QVariantMap metaData; // The track ID is annoying since it must result in a valid DBus object // path, and the regex for that is, and I quote: [a-zA-Z0-9_]*, along with // the normal / delimiters for paths. PlaylistItem *item = Playlist::playingItem(); if (!item) return metaData; FileHandle playingFile = item->file(); QByteArray playingTrackFileId = idFromPlaylistItem(item); metaData["mpris:trackid"] = QVariant::fromValue( QDBusObjectPath(playingTrackFileId.constData())); metaData["xesam:album"] = playingFile.tag()->album(); metaData["xesam:title"] = playingFile.tag()->title(); metaData["xesam:artist"] = QStringList(playingFile.tag()->artist()); metaData["xesam:genre"] = QStringList(playingFile.tag()->genre()); metaData["mpris:length"] = qint64(playingFile.tag()->seconds() * 1000000); metaData["xesam:url"] = QString::fromUtf8( QUrl::fromLocalFile(playingFile.absFilePath()).toEncoded()); if(playingFile.coverInfo()->hasCover()) { QString fallbackFileName = QStandardPaths::locate(QStandardPaths::TempLocation, QString("juk-cover-%1.png").arg(item->trackId())); QString path = fallbackFileName; if(!QFile::exists(path)) { path = playingFile.coverInfo()->localPathToCover(fallbackFileName); } metaData["mpris:artUrl"] = QString::fromUtf8( QUrl::fromLocalFile(path).toEncoded()); } return metaData; } double MediaPlayer2Player::Volume() const { return m_player->volume(); } void MediaPlayer2Player::setVolume(double volume) const { if (volume < 0.0) volume = 0.0; if (volume > 1.0) volume = 1.0; m_player->setVolume(volume); } qlonglong MediaPlayer2Player::Position() const { return m_player->currentTimeMSecs() * 1000; } double MediaPlayer2Player::MinimumRate() const { return 1.0; } double MediaPlayer2Player::MaximumRate() const { return 1.0; } bool MediaPlayer2Player::CanSeek() const { return m_player->seekable(); } void MediaPlayer2Player::Seek(qlonglong Offset) const { m_player->seek(((m_player->currentTimeMSecs() * 1000) + Offset) / 1000); } bool MediaPlayer2Player::CanControl() const { return true; } void MediaPlayer2Player::currentSourceChanged() const { QVariantMap properties; properties["Metadata"] = Metadata(); properties["CanSeek"] = CanSeek(); signalPropertiesChange(properties); } void MediaPlayer2Player::stateUpdated() const { QVariantMap properties; properties["PlaybackStatus"] = PlaybackStatus(); signalPropertiesChange(properties); } void MediaPlayer2Player::totalTimeChanged() const { QVariantMap properties; properties["Metadata"] = Metadata(); signalPropertiesChange(properties); } void MediaPlayer2Player::seekableChanged(bool seekable) const { QVariantMap properties; properties["CanSeek"] = seekable; signalPropertiesChange(properties); } void MediaPlayer2Player::volumeChanged(float newVol) const { Q_UNUSED(newVol) QVariantMap properties; properties["Volume"] = Volume(); signalPropertiesChange(properties); } void MediaPlayer2Player::seeked(int newPos) const { // casts int to uint64 emit Seeked(newPos); } void MediaPlayer2Player::signalPropertiesChange(const QVariantMap& properties) const { QDBusMessage msg = QDBusMessage::createSignal("/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "PropertiesChanged" ); msg << "org.mpris.MediaPlayer2.Player"; msg << properties; msg << QStringList(); QDBusConnection::sessionBus().send(msg); } diff --git a/playermanager.cpp b/playermanager.cpp index 106d5a3a..64880fc3 100644 --- a/playermanager.cpp +++ b/playermanager.cpp @@ -1,574 +1,561 @@ /** * Copyright (C) 2004 Scott Wheeler * Copyright (C) 2007 Matthias Kretz * Copyright (C) 2008, 2009 Michael Pyne * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "playermanager.h" #include #include #include #include #include #include #include #include #include #include #include #include "playlistinterface.h" #include "playeradaptor.h" #include "slideraction.h" #include "statuslabel.h" #include "actioncollection.h" #include "collectionlist.h" #include "coverinfo.h" #include "tag.h" #include "scrobbler.h" #include "juk.h" #include "juk_debug.h" using namespace ActionCollection; enum PlayerManagerStatus { StatusStopped = -1, StatusPaused = 1, StatusPlaying = 2 }; //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// PlayerManager::PlayerManager() : QObject(), m_playlistInterface(nullptr), - m_statusLabel(nullptr), m_setup(false) { // This class is the first thing constructed during program startup, and // therefore has no access to the widgets needed by the setup() method. // Since the setup() method will be called indirectly by the player() method // later, just disable it here. -- mpyne // setup(); new PlayerAdaptor(this); } //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// bool PlayerManager::playing() const { if(!m_setup) return false; Phonon::State state = m_media->state(); return (state == Phonon::PlayingState || state == Phonon::BufferingState); } bool PlayerManager::paused() const { if(!m_setup) return false; return m_media->state() == Phonon::PausedState; } bool PlayerManager::muted() const { if(!m_setup) return false; return m_output->isMuted(); } float PlayerManager::volume() const { if(!m_setup) return 1.0; return m_output->volume(); } int PlayerManager::status() const { if(!m_setup) return StatusStopped; if(paused()) return StatusPaused; if(playing()) return StatusPlaying; return StatusStopped; } int PlayerManager::totalTime() const { return totalTimeMSecs() / 1000; } int PlayerManager::currentTime() const { return currentTimeMSecs() / 1000; } int PlayerManager::totalTimeMSecs() const { if(!m_setup) return 0; return m_media->totalTime(); } int PlayerManager::currentTimeMSecs() const { if(!m_setup) return 0; return m_media->currentTime(); } bool PlayerManager::seekable() const { if(!m_setup) return false; return m_media->isSeekable(); } QStringList PlayerManager::trackProperties() { return FileHandle::properties(); } QString PlayerManager::trackProperty(const QString &property) const { if(!playing() && !paused()) return QString(); return m_file.property(property); } QPixmap PlayerManager::trackCover(const QString &size) const { if(!playing() && !paused()) return QPixmap(); if(size.toLower() == "small") return m_file.coverInfo()->pixmap(CoverInfo::Thumbnail); if(size.toLower() == "large") return m_file.coverInfo()->pixmap(CoverInfo::FullSize); return QPixmap(); } FileHandle PlayerManager::playingFile() const { return m_file; } QString PlayerManager::playingString() const { if(!playing() || m_file.isNull()) return QString(); return m_file.tag()->playingString(); } void PlayerManager::setPlaylistInterface(PlaylistInterface *interface) { m_playlistInterface = interface; } -void PlayerManager::setStatusLabel(StatusLabel *label) -{ - m_statusLabel = label; -} - //////////////////////////////////////////////////////////////////////////////// // public slots //////////////////////////////////////////////////////////////////////////////// void PlayerManager::play(const FileHandle &file) { if(!m_setup) setup(); if(!m_media || !m_playlistInterface) return; if(file.isNull()) { if(paused()) m_media->play(); else if(playing()) { m_media->seek(0); emit seeked(0); } else { m_playlistInterface->playNext(); m_file = m_playlistInterface->currentFile(); if(!m_file.isNull()) { m_media->setCurrentSource(QUrl::fromLocalFile(m_file.absFilePath())); m_media->play(); emit signalItemChanged(m_file); } } } else { m_media->setCurrentSource(QUrl::fromLocalFile(file.absFilePath())); m_media->play(); if(m_file != file) emit signalItemChanged(file); m_file = file; } // Our state changed handler will perform the follow up actions necessary // once we actually start playing. } void PlayerManager::play(const QString &file) { CollectionListItem *item = CollectionList::instance()->lookup(file); if(item) { Playlist::setPlaying(item); play(item->file()); } } void PlayerManager::play() { play(FileHandle::null()); } void PlayerManager::pause() { if(!m_setup) return; if(paused()) { play(); return; } action("pause")->setEnabled(false); m_media->pause(); } void PlayerManager::stop() { if(!m_setup || !m_playlistInterface) return; action("pause")->setEnabled(false); action("stop")->setEnabled(false); action("back")->setEnabled(false); action("forward")->setEnabled(false); action("forwardAlbum")->setEnabled(false); m_media->stop(); if(!m_file.isNull()) { m_file = FileHandle::null(); emit signalItemChanged(m_file); } } void PlayerManager::setVolume(float volume) { if(!m_setup) setup(); m_output->setVolume(volume); } void PlayerManager::seek(int seekTime) { if(!m_setup || m_media->currentTime() == seekTime) return; m_media->seek(seekTime); emit seeked(seekTime); } void PlayerManager::seekForward() { const qint64 total = m_media->totalTime(); const qint64 newtime = m_media->currentTime() + total / 100; const qint64 seekTo = qMin(total, newtime); m_media->seek(seekTo); emit seeked(seekTo); } void PlayerManager::seekBack() { const qint64 total = m_media->totalTime(); const qint64 newtime = m_media->currentTime() - total / 100; const qint64 seekTo = qMax(qint64(0), newtime); m_media->seek(seekTo); emit seeked(seekTo); } void PlayerManager::playPause() { playing() ? action("pause")->trigger() : action("play")->trigger(); } void PlayerManager::forward() { m_playlistInterface->playNext(); FileHandle file = m_playlistInterface->currentFile(); if(!file.isNull()) play(file); else stop(); } void PlayerManager::back() { m_playlistInterface->playPrevious(); FileHandle file = m_playlistInterface->currentFile(); if(!file.isNull()) play(file); else stop(); } void PlayerManager::volumeUp() { if(!m_setup) return; setVolume(volume() + 0.04); // 4% up } void PlayerManager::volumeDown() { if(!m_setup) return; setVolume(volume() - 0.04); // 4% down } void PlayerManager::setMuted(bool m) { if(!m_setup) return; m_output->setMuted(m); } bool PlayerManager::mute() { if(!m_setup) return false; bool newState = !muted(); setMuted(newState); return newState; } //////////////////////////////////////////////////////////////////////////////// // private slots //////////////////////////////////////////////////////////////////////////////// void PlayerManager::slotFinished() { // It is possible to end up in this function if a file simply fails to play or if the // user moves the slider all the way to the end, therefore see if we can keep playing // and if we can, do so. Otherwise, stop. m_playlistInterface->playNext(); play(m_playlistInterface->currentFile()); } void PlayerManager::slotLength(qint64 msec) { - m_statusLabel->setItemTotalTime(msec / 1000); emit totalTimeChanged(msec); } void PlayerManager::slotTick(qint64 msec) { - if(!m_setup || !m_playlistInterface) - return; - - if(m_statusLabel) - m_statusLabel->setItemCurrentTime(msec / 1000); - emit tick(msec); } void PlayerManager::slotStateChanged(Phonon::State newstate, Phonon::State) { if(newstate == Phonon::ErrorState) { QString errorMessage = i18nc( "%1 will be the /path/to/file, %2 will be some string from Phonon describing the error", "JuK is unable to play the audio file%1" "for the following reason:%2", m_file.absFilePath(), m_media->errorString() ); qCWarning(JUK_LOG) << "Phonon is in error state" << m_media->errorString() << "while playing" << m_file.absFilePath(); switch(m_media->errorType()) { case Phonon::NoError: qCDebug(JUK_LOG) << "received a state change to ErrorState but errorType is NoError!?"; break; case Phonon::NormalError: KMessageBox::information(0, errorMessage); break; case Phonon::FatalError: KMessageBox::sorry(0, errorMessage); break; } stop(); return; } // "normal" path if(newstate == Phonon::StoppedState) { JuK::JuKInstance()->setWindowTitle(i18n("JuK")); emit signalStop(); } else if(newstate == Phonon::PausedState) { emit signalPause(); } else { // PlayingState or BufferingState action("pause")->setEnabled(true); action("stop")->setEnabled(true); action("forward")->setEnabled(true); if(action("albumRandomPlay")->isChecked()) action("forwardAlbum")->setEnabled(true); action("back")->setEnabled(true); JuK::JuKInstance()->setWindowTitle(i18nc( "%1 is the artist and %2 is the title of the currently playing track.", "%1 - %2 :: JuK", m_file.tag()->artist(), m_file.tag()->title())); emit signalPlay(); } } void PlayerManager::slotSeekableChanged(bool isSeekable) { emit seekableChanged(isSeekable); } void PlayerManager::slotMutedChanged(bool muted) { emit mutedChanged(muted); } void PlayerManager::slotVolumeChanged(qreal volume) { if(qFuzzyCompare(m_output->volume(), volume)) { return; } emit volumeChanged(volume); } //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// void PlayerManager::setup() { if(m_setup) return; m_setup = true; // All of the actions required by this class should be listed here. if(!action("pause") || !action("stop") || !action("back") || !action("forwardAlbum") || !action("forward") || !action("trackPositionAction")) { qCWarning(JUK_LOG) << "Could not find all of the required actions."; return; } using namespace Phonon; m_output = new AudioOutput(MusicCategory, this); connect(m_output, &AudioOutput::mutedChanged, this, &PlayerManager::slotMutedChanged); connect(m_output, &AudioOutput::volumeChanged, this, &PlayerManager::slotVolumeChanged); m_media = new MediaObject(this); m_audioPath = createPath(m_media, m_output); connect(m_media, &MediaObject::stateChanged, this, &PlayerManager::slotStateChanged); connect(m_media, &MediaObject::totalTimeChanged, this, &PlayerManager::slotLength); connect(m_media, &MediaObject::tick, this, &PlayerManager::slotTick); connect(m_media, &MediaObject::finished, this, &PlayerManager::slotFinished); connect(m_media, &MediaObject::seekableChanged, this, &PlayerManager::slotSeekableChanged); // initialize action states action("pause")->setEnabled(false); action("stop")->setEnabled(false); action("back")->setEnabled(false); action("forward")->setEnabled(false); action("forwardAlbum")->setEnabled(false); QDBusConnection::sessionBus().registerObject("/Player", this); } QString PlayerManager::randomPlayMode() const { if(action("randomPlay")->isChecked()) return "Random"; if(action("albumRandomPlay")->isChecked()) return "AlbumRandom"; return "NoRandom"; } void PlayerManager::setRandomPlayMode(const QString &randomMode) { if(randomMode.toLower() == "random") action("randomPlay")->setChecked(true); if(randomMode.toLower() == "albumrandom") action("albumRandomPlay")->setChecked(true); if(randomMode.toLower() == "norandom") action("disableRandomPlay")->setChecked(true); } // vim: set et sw=4 tw=0 sta: diff --git a/playermanager.h b/playermanager.h index 969d1338..94503732 100644 --- a/playermanager.h +++ b/playermanager.h @@ -1,143 +1,140 @@ /** * Copyright (C) 2004 Scott Wheeler * Copyright (C) 2007 Matthias Kretz * Copyright (C) 2008 Michael Pyne * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #ifndef JUK_PLAYERMANAGER_H #define JUK_PLAYERMANAGER_H #include #include "filehandle.h" #include #include -class StatusLabel; class PlaylistInterface; class QPixmap; namespace Phonon { class AudioOutput; class MediaObject; } /** * This class serves as a proxy to the Player interface and handles managing * the actions from the top-level mainwindow. */ class PlayerManager : public QObject { Q_OBJECT public: PlayerManager(); bool playing() const; bool paused() const; bool muted() const; float volume() const; int status() const; // These two have been part of the prior public DBus interface so they have // been retained. You should use the MSecs versions below. These return in units // of seconds instead. int totalTime() const; int currentTime() const; int totalTimeMSecs() const; int currentTimeMSecs() const; bool seekable() const; //int position() const; QStringList trackProperties(); QString trackProperty(const QString &property) const; QPixmap trackCover(const QString &size) const; FileHandle playingFile() const; QString playingString() const; void setPlaylistInterface(PlaylistInterface *interface); - void setStatusLabel(StatusLabel *label); QString randomPlayMode() const; public slots: void play(const FileHandle &file); void play(const QString &file); void play(); void pause(); void stop(); void setVolume(float volume = 1.0); void seek(int seekTime); //void seekPosition(int position); void seekForward(); void seekBack(); void playPause(); void forward(); void back(); void volumeUp(); void volumeDown(); void setMuted(bool m); bool mute(); void setRandomPlayMode(const QString &randomMode); signals: - void tick(int time); - void totalTimeChanged(int time); + void tick(qint64 /* time_msec */); + void totalTimeChanged(qint64 /* time_msec */); void mutedChanged(bool muted); void volumeChanged(float volume); void seeked(int newPos); void seekableChanged(bool muted); void signalPlay(); void signalPause(); void signalStop(); void signalItemChanged(const FileHandle &file); private: void setup(); private slots: void slotFinished(); void slotLength(qint64); void slotTick(qint64); void slotStateChanged(Phonon::State, Phonon::State); void slotSeekableChanged(bool); void slotMutedChanged(bool); void slotVolumeChanged(qreal); private: FileHandle m_file; PlaylistInterface *m_playlistInterface; - StatusLabel *m_statusLabel; bool m_muted; bool m_setup; static const int m_pollInterval = 800; Phonon::AudioOutput *m_output; Phonon::Path m_audioPath; Phonon::MediaObject *m_media; }; #endif // vim: set et sw=4 tw=0 sta: diff --git a/playlistbox.h b/playlistbox.h index 344c0b40..4c6fda27 100644 --- a/playlistbox.h +++ b/playlistbox.h @@ -1,205 +1,206 @@ /** * Copyright (C) 2002-2004 Scott Wheeler * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #ifndef PLAYLISTBOX_H #define PLAYLISTBOX_H #include "playlistcollection.h" #include #include class Playlist; class PlaylistItem; class ViewMode; class QMenu; template class QList; typedef QList PlaylistList; /** * This is the play list selection box that is by default on the left side of * JuK's main widget (PlaylistSplitter). */ class PlaylistBox : public QTreeWidget, public PlaylistCollection { Q_OBJECT public: class Item; typedef QList ItemList; friend class Item; PlaylistBox(PlayerManager *player, QWidget *parent, QStackedWidget *playlistStack); virtual ~PlaylistBox(); virtual void raise(Playlist *playlist); virtual void duplicate(); virtual void remove(); // Called after files loaded to pickup any new files that might be present // in managed directories. virtual void scanFolders(); /** * For view modes that have dynamic playlists, this freezes them from * removing playlists. */ virtual void setDynamicListsFrozen(bool frozen); Item *dropItem() const { return m_dropItem; } void setupPlaylist(Playlist *playlist, const QString &iconName, Item *parentItem = 0); public slots: void paste(); void clear() {} void slotFreezePlaylists(); void slotUnfreezePlaylists(); void slotPlaylistDataChanged(); void slotSetHistoryPlaylistEnabled(bool enable); protected: virtual void setupPlaylist(Playlist *playlist, const QString &iconName); virtual void removePlaylist(Playlist *playlist); signals: void signalPlaylistDestroyed(Playlist *); void startupComplete(); ///< Emitted after playlists are loaded. void startFilePlayback(const FileHandle &file); private: void readConfig(); void saveConfig(); virtual void mousePressEvent(QMouseEvent *e); virtual void mouseReleaseEvent(QMouseEvent *e); virtual void keyPressEvent(QKeyEvent *e); virtual void keyReleaseEvent(QKeyEvent *e); // selectedItems already used for something different ItemList selectedBoxItems() const; void setSingleItem(QTreeWidgetItem *item); void setupItem(Item *item); void setupUpcomingPlaylist(); int viewModeIndex() const { return m_viewModeIndex; } ViewMode *viewMode() const { return m_viewModes[m_viewModeIndex]; } private slots: /** * Catches QListBox::currentChanged(QListBoxItem *), does a cast and then re-emits * the signal as currentChanged(Item *). */ void slotPlaylistChanged(); void slotDoubleClicked(QTreeWidgetItem *); void slotShowContextMenu(const QPoint &point); void slotSetViewMode(int index); void slotSavePlaylists(); void slotShowDropTarget(); void slotPlaylistItemsDropped(Playlist *p); void slotAddItem(const QString &tag, unsigned column); void slotRemoveItem(const QString &tag, unsigned column); // Used to load the playlists after GUI setup. void slotLoadCachedPlaylists(); private: QMenu *m_contextMenu; QHash m_playlistDict; int m_viewModeIndex; QList m_viewModes; bool m_hasSelection; bool m_doingMultiSelect; Item *m_dropItem; QTimer *m_showTimer; QTimer *m_savePlaylistTimer; }; class PlaylistBox::Item : public QObject, public QTreeWidgetItem, public PlaylistObserver { friend class PlaylistBox; + friend class PlaylistSplitter; friend class ViewMode; friend class CompactViewMode; friend class TreeViewMode; Q_OBJECT // moc won't let me create private QObject subclasses and Qt won't let me // make the destructor protected, so here's the closest hack that will // compile. public: virtual ~Item(); protected: Item(PlaylistBox *listBox, const QString &icon, const QString &text, Playlist *l = 0); Item(Item *parent, const QString &icon, const QString &text, Playlist *l = 0); Playlist *playlist() const { return m_playlist; } PlaylistBox *listView() const { return static_cast(QTreeWidgetItem::treeWidget()); } QString iconName() const { return m_iconName; } QString text() const { return m_text; } void setSortedFirst(bool first = true) { m_sortedFirst = first; } virtual int compare(QTreeWidgetItem *i, int col, bool) const; /*virtual void paintCell(QPainter *p, const QColorGroup &colorGroup, int column, int width, int align); virtual void paintFocus(QPainter *, const QColorGroup &, const QRect &) {}*/ virtual void setText(int column, const QString &text); virtual QString text(int column) const { return QTreeWidgetItem::text(column); } virtual void setup(); static Item *collectionItem() { return m_collectionItem; } static void setCollectionItem(Item *item) { m_collectionItem = item; } // // Reimplemented from PlaylistObserver // virtual void playingItemHasChanged() Q_DECL_FINAL; // Used to post a timer in PlaylistBox to save playlists. virtual void playlistItemDataHasChanged() Q_DECL_FINAL; protected slots: void slotSetName(const QString &name); private: // setup() was already taken. void init(); Playlist *m_playlist; QString m_text; QString m_iconName; bool m_sortedFirst; static Item *m_collectionItem; }; #endif // vim: set et sw=4 tw=0 sta: diff --git a/playlistsplitter.cpp b/playlistsplitter.cpp index 792dd7cb..b5f26c72 100644 --- a/playlistsplitter.cpp +++ b/playlistsplitter.cpp @@ -1,339 +1,344 @@ /** * Copyright (C) 2002-2004 Scott Wheeler * Copyright (C) 2009 Michael Pyne * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "playlistsplitter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "searchwidget.h" #include "playlistsearch.h" #include "actioncollection.h" #include "tageditor.h" #include "collectionlist.h" #include "playermanager.h" #include "nowplaying.h" #include "playlistbox.h" #include "lyricswidget.h" #include "mpris2/mpris2.h" #include "juk_debug.h" //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// PlaylistSplitter::PlaylistSplitter(PlayerManager *player, QWidget *parent) : QSplitter(Qt::Horizontal, parent), m_newVisible(0), m_playlistBox(0), m_searchWidget(0), m_playlistStack(0), m_editor(0), m_nowPlaying(0), m_player(player), m_lyricsWidget(0), m_editorSplitter(0) { setObjectName(QLatin1String("playlistSplitter")); setupActions(); setupLayout(); readConfig(); m_editor->slotUpdateCollection(); m_editor->setupObservers(); } PlaylistSplitter::~PlaylistSplitter() { saveConfig(); // TagEditor needs to write its configuration out while it's still valid, // destroy it now. delete m_editor; delete m_lyricsWidget; // NowPlaying depends on the PlaylistCollection, so kill it now. delete m_nowPlaying; m_nowPlaying = 0; delete m_searchWidget; // Take no chances here either. // Since we want to ensure that the shutdown process for the PlaylistCollection // (a base class for PlaylistBox) has a chance to write the playlists to disk // before they are deleted we're explicitly deleting the PlaylistBox here. delete m_playlistBox; } PlaylistInterface *PlaylistSplitter::playlist() const { return m_playlistBox; } bool PlaylistSplitter::eventFilter(QObject *, QEvent *event) { if(event->type() == FocusUpEvent::id) { m_searchWidget->setFocus(); return true; } return false; } //////////////////////////////////////////////////////////////////////////////// // public slots //////////////////////////////////////////////////////////////////////////////// void PlaylistSplitter::setFocus() { m_searchWidget->setFocus(); } void PlaylistSplitter::slotFocusCurrentPlaylist() { Playlist *playlist = m_playlistBox->visiblePlaylist(); if(playlist) { playlist->setFocus(); playlist->clearSelection(); // Select the top visible (and matching) item. PlaylistItem *item = static_cast(playlist->itemAt(QPoint(0, 0))); if(!item) return; // A little bit of a hack to make QListView repaint things properly. Switch // to single selection mode, set the selection and then switch back. playlist->setSelectionMode(QTreeWidget::SingleSelection); playlist->setCurrentItem(item); playlist->setSelectionMode(QTreeWidget::ExtendedSelection); } } //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// Playlist *PlaylistSplitter::visiblePlaylist() const { return m_newVisible ? m_newVisible : m_playlistBox->visiblePlaylist(); } void PlaylistSplitter::setupActions() { KActionCollection* coll = ActionCollection::actions(); KToggleAction *showSearch = new KToggleAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("Show &Search Bar"), this); coll->addAction("showSearch", showSearch); QAction *act = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18n("Edit Track Search"), this); coll->addAction("editTrackSearch", act); coll->setDefaultShortcut(act, Qt::Key_F6); connect(act, SIGNAL(triggered(bool)), SLOT(setFocus())); } void PlaylistSplitter::setupLayout() { setOpaqueResize(false); // Disable the GUI until startup is complete (as indicated by PlaylistBox) setEnabled(false); // Create a splitter to go between the playlists and the editor. m_editorSplitter = new QSplitter(Qt::Vertical, this); - m_editorSplitter->setObjectName( QLatin1String("editorSplitter" )); + m_editorSplitter->setObjectName(QLatin1String("editorSplitter")); // Make sure none of the optional widgets are collapsible, this causes the // widget to be essentially invisible but logically shown. this->setChildrenCollapsible(false); m_editorSplitter->setChildrenCollapsible(false); // Create the playlist and the editor. QWidget *top = new QWidget(m_editorSplitter); QVBoxLayout *topLayout = new QVBoxLayout(top); topLayout->setMargin(0); topLayout->setSpacing(0); m_playlistStack = new QStackedWidget(top); - m_playlistStack->setObjectName( QLatin1String("playlistStack" )); + m_playlistStack->setObjectName(QLatin1String("playlistStack")); m_playlistStack->installEventFilter(this); m_playlistStack->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_playlistStack->hide(); // Will be shown after CollectionList filled. m_editor = new TagEditor(m_editorSplitter); - m_editor->setObjectName( QLatin1String("TagEditor" )); + m_editor->setObjectName(QLatin1String("TagEditor")); // Create the lyrics widget m_lyricsWidget = new LyricsWidget(this); insertWidget(2, m_lyricsWidget); // Create the PlaylistBox m_playlistBox = new PlaylistBox(m_player, this, m_playlistStack); - m_playlistBox->setObjectName( QLatin1String( "playlistBox" ) ); + m_playlistBox->setObjectName(QLatin1String("playlistBox")); connect(m_playlistBox->object(), SIGNAL(signalSelectedItemsChanged()), this, SLOT(slotPlaylistSelectionChanged())); connect(m_playlistBox, SIGNAL(signalPlaylistDestroyed(Playlist*)), m_editor, SLOT(slotPlaylistDestroyed(Playlist*))); connect(m_playlistBox, SIGNAL(startupComplete()), SLOT(slotEnable())); + connect(m_playlistBox, &QTreeWidget::currentItemChanged, + this, &PlaylistSplitter::slotCurrentPlaylistChanged); connect(m_playlistBox, SIGNAL(startFilePlayback(FileHandle)), m_player, SLOT(play(FileHandle))); m_player->setPlaylistInterface(m_playlistBox); // Let interested parties know we're ready connect(m_playlistBox, SIGNAL(startupComplete()), SIGNAL(guiReady())); insertWidget(0, m_playlistBox); m_nowPlaying = new NowPlaying(top, m_playlistBox); connect(m_player, SIGNAL(signalItemChanged(FileHandle)), m_nowPlaying, SLOT(slotUpdate(FileHandle))); connect(m_player, SIGNAL(signalItemChanged(FileHandle)), m_lyricsWidget, SLOT(playing(FileHandle))); // Create the search widget -- this must be done after the CollectionList is created. m_searchWidget = new SearchWidget(top); // auto-shortcuts don't seem to work and aren't needed anyway. KAcceleratorManager::setNoAccel(m_searchWidget); connect(m_searchWidget, SIGNAL(signalQueryChanged()), this, SLOT(slotShowSearchResults())); connect(m_searchWidget, SIGNAL(signalDownPressed()), this, SLOT(slotFocusCurrentPlaylist())); connect(m_searchWidget, SIGNAL(signalShown(bool)), m_playlistBox->object(), SLOT(slotSetSearchEnabled(bool))); connect(m_searchWidget, SIGNAL(returnPressed()), m_playlistBox->object(), SLOT(slotPlayFirst())); connect(ActionCollection::action("showSearch"), SIGNAL(toggled(bool)), m_searchWidget, SLOT(setEnabled(bool))); topLayout->addWidget(m_nowPlaying); topLayout->addWidget(m_searchWidget); topLayout->insertStretch(-1); // Force search bar to top while playlistStack hides topLayout->addWidget(m_playlistStack, 1); // Now that GUI setup is complete, add some auto-update signals. connect(CollectionList::instance(), SIGNAL(signalCollectionChanged()), m_editor, SLOT(slotUpdateCollection())); connect(m_playlistStack, SIGNAL(currentChanged(int)), this, SLOT(slotPlaylistChanged(int))); // Show the collection on startup. m_playlistBox->setCurrentItem(m_playlistBox->topLevelItem(0)); } void PlaylistSplitter::readConfig() { KConfigGroup config(KSharedConfig::openConfig(), "Splitter"); QList splitterSizes = config.readEntry("PlaylistSplitterSizes",QList()); if(splitterSizes.isEmpty()) { splitterSizes.append(100); splitterSizes.append(640); } setSizes(splitterSizes); bool showSearch = config.readEntry("ShowSearch", true); ActionCollection::action("showSearch")->setChecked(showSearch); m_searchWidget->setHidden(!showSearch); splitterSizes = config.readEntry("EditorSplitterSizes",QList()); if(splitterSizes.isEmpty()) { // If no sizes were saved, use default sizes for the playlist and the // editor, respectively. The values are just hints for the actual size, // m_editorSplitter will distribute the space according to their // relative weight. splitterSizes.append(300); splitterSizes.append(200); } m_editorSplitter->setSizes(splitterSizes); } void PlaylistSplitter::saveConfig() { KConfigGroup config(KSharedConfig::openConfig(), "Splitter"); config.writeEntry("PlaylistSplitterSizes", sizes()); config.writeEntry("ShowSearch", ActionCollection::action("showSearch")->isChecked()); config.writeEntry("EditorSplitterSizes", m_editorSplitter->sizes()); } void PlaylistSplitter::slotShowSearchResults() { PlaylistList playlists; playlists.append(visiblePlaylist()); PlaylistSearch search = m_searchWidget->search(playlists); visiblePlaylist()->setSearch(search); } void PlaylistSplitter::slotPlaylistSelectionChanged() { m_editor->slotSetItems(visiblePlaylist()->selectedItems()); } void PlaylistSplitter::slotPlaylistChanged(int i) { Playlist *p = qobject_cast(m_playlistStack->widget(i)); if(!p) return; m_newVisible = p; m_searchWidget->setSearch(p->search()); m_newVisible = 0; } +void PlaylistSplitter::slotCurrentPlaylistChanged(QTreeWidgetItem *item) +{ + auto pItem = static_cast(item); + emit currentPlaylistChanged(*(pItem->playlist())); +} + void PlaylistSplitter::slotEnable() { - qCDebug(JUK_LOG) << "Enabling GUI"; - QTime stopwatch; stopwatch.start(); setEnabled(true); // Ready to go. m_playlistStack->show(); - qCDebug(JUK_LOG) << "Finished enabling GUI, took" << stopwatch.elapsed() << "ms"; (void) new Mpris2(this); } // vim: set et sw=4 tw=0 sta: diff --git a/playlistsplitter.h b/playlistsplitter.h index 42ea0ccd..8b025d6a 100644 --- a/playlistsplitter.h +++ b/playlistsplitter.h @@ -1,105 +1,108 @@ /** * Copyright (C) 2002-2004 Scott Wheeler * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #ifndef PLAYLISTSPLITTER_H #define PLAYLISTSPLITTER_H #include class QStackedWidget; +class QTreeWidgetItem; class Playlist; class SearchWidget; class PlaylistInterface; class TagEditor; class PlaylistBox; class NowPlaying; class PlayerManager; class FileHandle; class LyricsWidget; /** * This is the main layout class of JuK. It should contain a PlaylistBox and * a QStackedWidget of the Playlists. * * This class serves as a "mediator" (see "Design Patterns") between the JuK * class and the playlist classes. Thus all access to the playlist classes from * non-Playlist related classes should be through the public API of this class. */ class PlaylistSplitter : public QSplitter { Q_OBJECT public: PlaylistSplitter(PlayerManager *player, QWidget *parent); virtual ~PlaylistSplitter(); PlaylistInterface *playlist() const; virtual bool eventFilter(QObject *watched, QEvent *event); signals: /** * Emitted when GUI is created and the cache is loaded. Is kind of a hack * until we move the time-intensive parts to a separate thread but then * again at least this works. */ void guiReady(); + void currentPlaylistChanged(const PlaylistInterface ¤tPlaylist); public slots: virtual void setFocus(); virtual void slotFocusCurrentPlaylist(); void slotEnable(); private: /** * This returns a pointer to the first item in the playlist on the top * of the QStackedWidget of playlists. */ Playlist *visiblePlaylist() const; void setupActions(); void setupLayout(); void readConfig(); void saveConfig(); private slots: /** * Updates the visible search results based on the result of the search * associated with the currently visible playlist. */ void slotShowSearchResults(); void slotPlaylistSelectionChanged(); void slotPlaylistChanged(int i); + void slotCurrentPlaylistChanged(QTreeWidgetItem *item); private: Playlist *m_newVisible; PlaylistBox *m_playlistBox; SearchWidget *m_searchWidget; QStackedWidget *m_playlistStack; TagEditor *m_editor; NowPlaying *m_nowPlaying; PlayerManager *m_player; LyricsWidget *m_lyricsWidget; QSplitter *m_editorSplitter; }; #endif // vim: set et sw=4 tw=0 sta: diff --git a/statuslabel.cpp b/statuslabel.cpp index 25571396..7e56d120 100644 --- a/statuslabel.cpp +++ b/statuslabel.cpp @@ -1,192 +1,182 @@ /** * Copyright (C) 2002-2004 Scott Wheeler * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "statuslabel.h" #include #include #include #include #include #include #include #include #include #include #include #include "filehandle.h" #include "playlistinterface.h" #include "actioncollection.h" +#include "playermanager.h" #include "tag.h" #include "juk_debug.h" using namespace ActionCollection; //////////////////////////////////////////////////////////////////////////////// // static helpers //////////////////////////////////////////////////////////////////////////////// -static QString formatTime(int seconds) +static QString formatTime(qint64 milliseconds) { static const KFormat fmt; - return fmt.formatDuration(seconds * 1000); + return fmt.formatDuration(milliseconds); } //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// -StatusLabel::StatusLabel(PlaylistInterface *playlist, QWidget *parent) : - QWidget(parent), - PlaylistObserver(playlist) +StatusLabel::StatusLabel(const PlaylistInterface ¤tPlaylist, QWidget *parent) : + QWidget(parent) { auto hboxLayout = new QHBoxLayout(this); QFrame *trackAndPlaylist = new QFrame(this); hboxLayout->addWidget(trackAndPlaylist); trackAndPlaylist->setFrameStyle(QFrame::Box | QFrame::Sunken); trackAndPlaylist->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // Make sure that we have enough of a margin to suffice for the borders, // hence the "lineWidth() * 2" QHBoxLayout *trackAndPlaylistLayout = new QHBoxLayout(trackAndPlaylist); trackAndPlaylistLayout->setMargin(trackAndPlaylist->lineWidth() * 2); trackAndPlaylistLayout->setSpacing(5); trackAndPlaylistLayout->setObjectName(QLatin1String("trackAndPlaylistLayout")); trackAndPlaylistLayout->addSpacing(5); m_playlistLabel = new KSqueezedTextLabel(trackAndPlaylist); trackAndPlaylistLayout->addWidget(m_playlistLabel); m_playlistLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_playlistLabel->setTextFormat(Qt::PlainText); m_playlistLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); m_trackLabel = new KSqueezedTextLabel(trackAndPlaylist); trackAndPlaylistLayout->addWidget(m_trackLabel); m_trackLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_trackLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_trackLabel->setTextFormat(Qt::PlainText); trackAndPlaylistLayout->addSpacing(5); m_itemTimeLabel = new QLabel(this); hboxLayout->addWidget(m_itemTimeLabel); QFontMetrics fontMetrics(font()); m_itemTimeLabel->setAlignment(Qt::AlignCenter); m_itemTimeLabel->setMinimumWidth(fontMetrics.boundingRect("000:00 / 000:00").width()); m_itemTimeLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); m_itemTimeLabel->setFrameStyle(QFrame::Box | QFrame::Sunken); m_itemTimeLabel->installEventFilter(this); setItemTotalTime(0); setItemCurrentTime(0); auto jumpBox = new QFrame(this); hboxLayout->addWidget(jumpBox); jumpBox->setFrameStyle(QFrame::Box | QFrame::Sunken); jumpBox->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Minimum); auto jumpBoxHLayout = new QHBoxLayout(jumpBox); QPushButton *jumpButton = new QPushButton(jumpBox); jumpBoxHLayout->addWidget(jumpButton); jumpButton->setIcon(SmallIcon("go-up")); jumpButton->setFlat(true); jumpButton->setToolTip(i18n("Jump to the currently playing item")); connect(jumpButton, &QPushButton::clicked, action("showPlaying"), &QAction::trigger); installEventFilter(this); - playlistItemDataHasChanged(); + slotCurrentPlaylistHasChanged(currentPlaylist); } -void StatusLabel::playingItemHasChanged() +void StatusLabel::slotPlayingItemHasChanged(const FileHandle &file) { - if(!playlist()->playing()) { - return; - } - - const FileHandle file = playlist()->currentFile(); const Tag *tag = file.tag(); const QString mid = (tag->artist().isEmpty() || tag->title().isEmpty()) ? QString() : QStringLiteral(" - "); + setItemTotalTime(tag->seconds()); + setItemCurrentTime(0); + m_trackLabel->setText(tag->artist() + mid + tag->title()); - m_playlistLabel->setText(playlist()->name().simplified()); } -void StatusLabel::playlistItemDataHasChanged() +void StatusLabel::slotCurrentPlaylistHasChanged(const PlaylistInterface ¤tPlaylist) { - playingItemHasChanged(); - - const auto plist = playlist(); - - if(!plist->playing()) { + if(!currentPlaylist.playing()) { return; } - setItemTotalTime(0); - setItemCurrentTime(0); - - m_playlistLabel->setText(plist->name()); + m_playlistLabel->setText(currentPlaylist.name()); m_trackLabel->setText( - i18np("1 item", "%1 items", plist->count()) + + i18np("1 item", "%1 items", currentPlaylist.count()) + QStringLiteral(" - ") + - formatTime(plist->time()) + formatTime(qint64(1000) * currentPlaylist.time()) ); } //////////////////////////////////////////////////////////////////////////////// // private methods //////////////////////////////////////////////////////////////////////////////// void StatusLabel::updateTime() { - const int seconds = m_showTimeRemaining + const qint64 milliseconds = m_showTimeRemaining ? m_itemTotalTime - m_itemCurrentTime : m_itemCurrentTime; - const QString timeString = formatTime(seconds) + QStringLiteral(" / ") + + const QString timeString = formatTime(milliseconds) + QStringLiteral(" / ") + formatTime(m_itemTotalTime); m_itemTimeLabel->setText(timeString); } bool StatusLabel::eventFilter(QObject *o, QEvent *e) { if(!o || !e) return false; QMouseEvent *mouseEvent = static_cast(e); if(e->type() == QEvent::MouseButtonRelease && mouseEvent->button() == Qt::LeftButton) { if(o == m_itemTimeLabel) { m_showTimeRemaining = !m_showTimeRemaining; updateTime(); } else action("showPlaying")->trigger(); return true; } return false; } // vim: set et sw=4 tw=0 sta: diff --git a/statuslabel.h b/statuslabel.h index 0771a894..ef1ff06e 100644 --- a/statuslabel.h +++ b/statuslabel.h @@ -1,60 +1,63 @@ /** * Copyright (C) 2002-2004 Scott Wheeler * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #ifndef JUK_STATUSLABEL_H #define JUK_STATUSLABEL_H -#include "playlistinterface.h" - #include +class KSqueezedTextLabel; + class QEvent; class QLabel; -class KSqueezedTextLabel; -class StatusLabel : public QWidget, public PlaylistObserver +class FileHandle; +class PlaylistInterface; + +class StatusLabel : public QWidget { Q_OBJECT public: - explicit StatusLabel(PlaylistInterface *playlist, QWidget *parent = nullptr); - virtual void playingItemHasChanged() Q_DECL_FINAL; + explicit StatusLabel(const PlaylistInterface ¤tPlaylist, QWidget *parent = nullptr); public slots: + void slotPlayingItemHasChanged(const FileHandle &file); + void slotCurrentPlaylistHasChanged(const PlaylistInterface ¤tPlaylist); + /** - * This just sets internal variables that are used by setItemCurrentTime(). + * This just sets internal variables that are used by updateTime(). * Please call that method to display the time. */ - void setItemTotalTime(int time) { m_itemTotalTime = time; } - void setItemCurrentTime(int time) { m_itemCurrentTime = time; updateTime(); } - virtual void playlistItemDataHasChanged() Q_DECL_FINAL; + void setItemTotalTime(qint64 time_msec) { m_itemTotalTime = time_msec; } + void setItemCurrentTime(qint64 time_msec) { m_itemCurrentTime = time_msec; updateTime(); } private: void updateTime(); virtual bool eventFilter(QObject *o, QEvent *e); KSqueezedTextLabel *m_playlistLabel = nullptr; KSqueezedTextLabel *m_trackLabel = nullptr; QLabel *m_itemTimeLabel = nullptr; int m_itemTotalTime = 0; int m_itemCurrentTime = 0; bool m_showTimeRemaining = false; }; #endif // vim: set et sw=4 tw=0 sta: