diff --git a/CMakeLists.txt b/CMakeLists.txt index d89b6d86..8f71d85b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,197 +1,196 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(juk) set(QT_MIN_VERSION "5.6.0") set(KF5_MIN_VERSION "5.35.0") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) include(CMakePushCheckState) include(CheckIncludeFileCXX) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings NO_POLICY_SCOPE) include(FeatureSummary) include(ECMInstallIcons) include(ECMAddAppIcon) include(ECMQtDeclareLoggingCategory) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Gui Svg Network Test Widgets) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS CoreAddons Completion Config GlobalAccel I18n IconThemes DocTools TextWidgets XmlGui Wallet WidgetsAddons WindowSystem KDELibs4Support) find_package(Phonon4Qt5 4.6.60 REQUIRED NO_MODULE) find_package(Taglib 1.6 REQUIRED) include_directories(${CMAKE_BINARY_DIR} ${PHONON_INCLUDES}) add_subdirectory( doc ) add_subdirectory( tests ) add_subdirectory( pics ) find_package(TunePimp) set_package_properties(FEATURE PROPERTIES DESCRIPTION "A library for developing MusicBrainz enabled tagging applications" URL "http://www.musicbrainz.org/products/tunepimp" TYPE OPTIONAL PURPOSE "Provides MusicBrainz tagging in Juk.") if(TUNEPIMP_FOUND) set(HAVE_TUNEPIMP 1) if(TUNEPIMP_FOUND_VERSION_4) set(HAVE_TUNEPIMP 4) endif(TUNEPIMP_FOUND_VERSION_4) if(TUNEPIMP_FOUND_VERSION_5) set(HAVE_TUNEPIMP 5) endif(TUNEPIMP_FOUND_VERSION_5) else(TUNEPIMP_FOUND) set(HAVE_TUNEPIMP 0) endif(TUNEPIMP_FOUND) ########### next target ############### include_directories( ${TAGLIB_INCLUDES} ) add_definitions(-DQT_STL) # Look for Ogg Opus support in taglib (not released yet) cmake_push_check_state() set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES} ${TAGLIB_INCLUDES}) check_include_file_cxx(opusfile.h TAGLIB_HAS_OPUSFILE) cmake_pop_check_state() configure_file (config-juk.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-juk.h ) set(tunepimp_SRCS) if(TUNEPIMP_FOUND) set(tunepimp_SRCS trackpickerdialog.cpp) include_directories( ${TUNEPIMP_INCLUDE_DIR} ) endif(TUNEPIMP_FOUND) set(juk_SRCS ${tunepimp_SRCS} advancedsearchdialog.cpp slider.cpp svghandler.cpp volumepopupbutton.cpp actioncollection.cpp cache.cpp categoryreaderinterface.cpp collectionlist.cpp coverdialog.cpp covericonview.cpp coverinfo.cpp covermanager.cpp coverproxy.cpp dbuscollectionproxy.cpp deletedialog.cpp directorylist.cpp dynamicplaylist.cpp exampleoptions.cpp folderplaylist.cpp filehandle.cpp filerenamer.cpp filerenameroptions.cpp filerenamerconfigdlg.cpp webimagefetcher.cpp historyplaylist.cpp juk.cpp keydialog.cpp lyricswidget.cpp main.cpp mediafiles.cpp mpris2/mediaplayer2.cpp mpris2/mediaplayer2player.cpp mpris2/mpris2.cpp musicbrainzquery.cpp nowplaying.cpp playermanager.cpp playlist.cpp playlistbox.cpp playlistcollection.cpp playlistinterface.cpp playlistitem.cpp playlistsearch.cpp playlistsplitter.cpp scrobbler.cpp scrobbleconfigdlg.cpp searchplaylist.cpp searchwidget.cpp slideraction.cpp sortedstringlist.cpp - splashscreen.cpp statuslabel.cpp stringshare.cpp systemtray.cpp tag.cpp tageditor.cpp tagguesser.cpp tagguesserconfigdlg.cpp tagrenameroptions.cpp tagtransactionmanager.cpp tracksequenceiterator.cpp tracksequencemanager.cpp treeviewitemplaylist.cpp upcomingplaylist.cpp ktrm.cpp viewmode.cpp ) ecm_qt_declare_logging_category(juk_SRCS HEADER juk_debug.h IDENTIFIER JUK_LOG CATEGORY_NAME org.kde.juk) qt5_add_dbus_adaptor( juk_SRCS org.kde.juk.collection.xml dbuscollectionproxy.h DBusCollectionProxy ) qt5_add_dbus_adaptor( juk_SRCS org.kde.juk.player.xml playermanager.h PlayerManager) qt5_add_dbus_adaptor( juk_SRCS org.kde.juk.search.xml searchwidget.h SearchWidget) ki18n_wrap_ui(juk_SRCS filerenamerbase.ui filerenameroptionsbase.ui directorylistbase.ui trackpickerdialogbase.ui tagguesserconfigdlgwidget.ui exampleoptionsbase.ui coverdialogbase.ui deletedialogbase.ui tageditor.ui ) file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/*-apps-juk.png") ecm_add_app_icon(juk_SRCS ICONS ${ICONS_SRCS}) add_executable(juk ${juk_SRCS}) kde_target_enable_exceptions(juk PRIVATE) target_compile_definitions(juk PRIVATE QT_USE_QSTRINGBUILDER) if(NOT MSVC AND NOT ( WIN32 AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel" ) ) set( LIBMATH m ) endif() target_link_libraries(juk ${LIBMATH} Qt5::Gui Qt5::Svg Qt5::Widgets Qt5::Network KF5::ConfigCore KF5::CoreAddons KF5::Completion KF5::GlobalAccel KF5::I18n KF5::IconThemes KF5::TextWidgets KF5::XmlGui KF5::WindowSystem KF5::WidgetsAddons KF5::Wallet KF5::KDELibs4Support Phonon::phonon4qt5 ${TAGLIB_LIBRARIES}) if(TUNEPIMP_FOUND) target_link_libraries(juk ${TUNEPIMP_LIBRARIES}) endif(TUNEPIMP_FOUND) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) install(TARGETS juk ${INSTALL_TARGETS_DEFAULT_ARGS} ) ########### install files ############### install( PROGRAMS org.kde.juk.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) install( FILES juk.notifyrc jukui.rc jukui-rtl.rc DESTINATION ${KXMLGUI_INSTALL_DIR}/juk ) install( FILES org.kde.juk.appdata.xml DESTINATION ${SHARE_INSTALL_PREFIX}/metainfo ) install( FILES jukservicemenu.desktop DESTINATION ${SERVICES_INSTALL_DIR}/ServiceMenus ) install( FILES org.kde.juk.collection.xml org.kde.juk.player.xml org.kde.juk.search.xml DESTINATION ${DBUS_INTERFACES_INSTALL_DIR} ) ecm_install_icons(ICONS 128-apps-juk.png 16-apps-juk.png 32-apps-juk.png 48-apps-juk.png 64-apps-juk.png DESTINATION ${ICON_INSTALL_DIR} THEME hicolor ) diff --git a/collectionlist.cpp b/collectionlist.cpp index c146a327..6a56dd8e 100644 --- a/collectionlist.cpp +++ b/collectionlist.cpp @@ -1,604 +1,597 @@ /** * 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 "collectionlist.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "playlistcollection.h" -#include "splashscreen.h" #include "stringshare.h" #include "cache.h" #include "actioncollection.h" #include "tag.h" #include "viewmode.h" #include "juk_debug.h" using ActionCollection::action; //////////////////////////////////////////////////////////////////////////////// // static methods //////////////////////////////////////////////////////////////////////////////// CollectionList *CollectionList::m_list = 0; CollectionList *CollectionList::instance() { return m_list; } static QTime stopwatch; void CollectionList::startLoadingCachedItems() { if(!m_list) return; qCDebug(JUK_LOG) << "Starting to load cached items"; stopwatch.start(); if(!Cache::instance()->prepareToLoadCachedItems()) { qCCritical(JUK_LOG) << "Unable to setup to load cache... perhaps it doesn't exist?"; completedLoadingCachedItems(); return; } qCDebug(JUK_LOG) << "Kicked off first batch"; QTimer::singleShot(0, this, SLOT(loadNextBatchCachedItems())); } void CollectionList::loadNextBatchCachedItems() { Cache *cache = Cache::instance(); bool done = false; for(int i = 0; i < 20; ++i) { FileHandle cachedItem(cache->loadNextCachedItem()); if(cachedItem.isNull()) { done = true; break; } // This may have already been created via a loaded playlist. if(!m_itemsDict.contains(cachedItem.absFilePath())) { CollectionListItem *newItem = new CollectionListItem(this, cachedItem); setupItem(newItem); } } - SplashScreen::update(); - if(!done) { QTimer::singleShot(0, this, SLOT(loadNextBatchCachedItems())); } else { completedLoadingCachedItems(); } } void CollectionList::completedLoadingCachedItems() { // The CollectionList is created with sorting disabled for speed. Re-enable // it here, and perform the sort. KConfigGroup config(KSharedConfig::openConfig(), "Playlists"); Qt::SortOrder order = Qt::DescendingOrder; if(config.readEntry("CollectionListSortAscending", true)) order = Qt::AscendingOrder; m_list->sortByColumn(config.readEntry("CollectionListSortColumn", 1), order); - SplashScreen::finishedLoading(); - qCDebug(JUK_LOG) << "Finished loading cached items, took" << stopwatch.elapsed() << "ms"; qCDebug(JUK_LOG) << m_itemsDict.size() << "items are in the CollectionList"; emit cachedItemsLoaded(); } void CollectionList::initialize(PlaylistCollection *collection) { if(m_list) return; // We have to delay initialization here because dynamic_cast or comparing to // the collection instance won't work in the PlaylistBox::Item initialization // won't work until the CollectionList is fully constructed. m_list = new CollectionList(collection); m_list->setName(i18n("Collection List")); collection->setupPlaylist(m_list, "folder-sound"); } //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// CollectionListItem *CollectionList::createItem(const FileHandle &file, QTreeWidgetItem *, bool) { // It's probably possible to optimize the line below away, but, well, right // now it's more important to not load duplicate items. if(m_itemsDict.contains(file.absFilePath())) return 0; CollectionListItem *item = new CollectionListItem(this, file); if(!item->isValid()) { qCCritical(JUK_LOG) << "CollectionList::createItem() -- A valid tag was not created for \"" << file.absFilePath() << "\""; delete item; return 0; } setupItem(item); return item; } void CollectionList::clearItems(const PlaylistItemList &items) { foreach(PlaylistItem *item, items) { delete item; } dataChanged(); } void CollectionList::setupTreeViewEntries(ViewMode *viewMode) const { TreeViewMode *treeViewMode = dynamic_cast(viewMode); if(!treeViewMode) { qCWarning(JUK_LOG) << "Can't setup entries on a non-tree-view mode!\n"; return; } QList columnList; columnList << PlaylistItem::ArtistColumn; columnList << PlaylistItem::GenreColumn; columnList << PlaylistItem::AlbumColumn; foreach(int column, columnList) treeViewMode->addItems(m_columnTags[column]->keys(), column); } void CollectionList::slotNewItems(const KFileItemList &items) { QStringList files; for(KFileItemList::ConstIterator it = items.constBegin(); it != items.constEnd(); ++it) files.append((*it).url().path()); addFiles(files); update(); } void CollectionList::slotRefreshItems(const QList > &items) { for(int i = 0; i < items.count(); ++i) { const KFileItem fileItem = items[i].second; CollectionListItem *item = lookup(fileItem.url().path()); if(item) { item->refreshFromDisk(); // If the item is no longer on disk, remove it from the collection. if(item->file().fileInfo().exists()) item->repaint(); else delete item; } } update(); } void CollectionList::slotDeleteItem(const KFileItem &item) { delete lookup(item.url().path()); } void CollectionList::saveItemsToCache() const { qCDebug(JUK_LOG) << "Saving collection list to cache"; QSaveFile f(Cache::fileHandleCacheFileName()); if(!f.open(QIODevice::WriteOnly)) { qCCritical(JUK_LOG) << "Error saving cache:" << f.errorString(); return; } QByteArray data; QDataStream s(&data, QIODevice::WriteOnly); s.setVersion(QDataStream::Qt_4_3); QHash::const_iterator it; for(it = m_itemsDict.begin(); it != m_itemsDict.end(); ++it) { s << it.key(); s << (*it)->file(); } QDataStream fs(&f); qint32 checksum = qChecksum(data.data(), data.size()); fs << qint32(Cache::playlistItemsCacheVersion) << checksum << data; if(!f.commit()) qCCritical(JUK_LOG) << "Error saving cache:" << f.errorString(); } //////////////////////////////////////////////////////////////////////////////// // public slots //////////////////////////////////////////////////////////////////////////////// void CollectionList::paste() { decode(QApplication::clipboard()->mimeData()); } void CollectionList::clear() { int result = KMessageBox::warningContinueCancel(this, i18n("Removing an item from the collection will also remove it from " "all of your playlists. Are you sure you want to continue?\n\n" "Note, however, that if the directory that these files are in is in " "your \"scan on startup\" list, they will be readded on startup.")); if(result == KMessageBox::Continue) { Playlist::clear(); emit signalCollectionChanged(); } } void CollectionList::slotCheckCache() { PlaylistItemList invalidItems; qCDebug(JUK_LOG) << "Starting to check cached items for consistency"; stopwatch.start(); int i = 0; foreach(CollectionListItem *item, m_itemsDict) { if(!item->checkCurrent()) invalidItems.append(item); if(++i == (m_itemsDict.size() / 2)) qCDebug(JUK_LOG) << "Checkpoint"; } clearItems(invalidItems); qCDebug(JUK_LOG) << "Finished consistency check, took" << stopwatch.elapsed() << "ms"; } void CollectionList::slotRemoveItem(const QString &file) { delete m_itemsDict[file]; } void CollectionList::slotRefreshItem(const QString &file) { if(m_itemsDict[file]) m_itemsDict[file]->refresh(); } //////////////////////////////////////////////////////////////////////////////// // protected methods //////////////////////////////////////////////////////////////////////////////// CollectionList::CollectionList(PlaylistCollection *collection) : Playlist(collection, true), m_columnTags(15, 0) { QAction *spaction = ActionCollection::actions()->addAction("showPlaying"); spaction->setText(i18n("Show Playing")); connect(spaction, SIGNAL(triggered(bool)), SLOT(slotShowPlaying())); connect(action("back")->menu(), SIGNAL(aboutToShow()), this, SLOT(slotPopulateBackMenu())); connect(action("back")->menu(), SIGNAL(triggered(QAction*)), this, SLOT(slotPlayFromBackMenu(QAction*))); setSortingEnabled(false); // Temporarily disable sorting to add items faster. m_columnTags[PlaylistItem::ArtistColumn] = new TagCountDict; m_columnTags[PlaylistItem::AlbumColumn] = new TagCountDict; m_columnTags[PlaylistItem::GenreColumn] = new TagCountDict; // Even set to true it wouldn't work with this class due to other checks setAllowDuplicates(false); } CollectionList::~CollectionList() { KConfigGroup config(KSharedConfig::openConfig(), "Playlists"); config.writeEntry("CollectionListSortColumn", header()->sortIndicatorSection()); config.writeEntry("CollectionListSortAscending", header()->sortIndicatorOrder() == Qt::AscendingOrder); // In some situations the dataChanged signal from clearItems will cause observers to // subsequently try to access a deleted item. Since we're going away just remove all // observers. clearObservers(); // The CollectionListItems will try to remove themselves from the // m_columnTags member, so we must make sure they're gone before we // are. clearItems(items()); qDeleteAll(m_columnTags); m_columnTags.clear(); } void CollectionList::dropEvent(QDropEvent *e) { if(e->source() == this) return; // Don't rearrange in the CollectionList. else Playlist::dropEvent(e); } void CollectionList::dragMoveEvent(QDragMoveEvent *e) { if(e->source() != this) Playlist::dragMoveEvent(e); else e->setAccepted(false); } QString CollectionList::addStringToDict(const QString &value, int column) { if(column > m_columnTags.count() || value.trimmed().isEmpty()) return QString(); if(m_columnTags[column]->contains(value)) ++((*m_columnTags[column])[value]); else { m_columnTags[column]->insert(value, 1); emit signalNewTag(value, column); } return value; } QStringList CollectionList::uniqueSet(UniqueSetType t) const { int column; switch(t) { case Artists: column = PlaylistItem::ArtistColumn; break; case Albums: column = PlaylistItem::AlbumColumn; break; case Genres: column = PlaylistItem::GenreColumn; break; default: return QStringList(); } return m_columnTags[column]->keys(); } CollectionListItem *CollectionList::lookup(const QString &file) const { return m_itemsDict.value(file, 0); } void CollectionList::removeStringFromDict(const QString &value, int column) { if(column > m_columnTags.count() || value.trimmed().isEmpty()) return; if(m_columnTags[column]->contains(value) && --((*m_columnTags[column])[value])) // If the decrement goes to 0... { emit signalRemovedTag(value, column); m_columnTags[column]->remove(value); } } void CollectionList::addWatched(const QString &file) { m_dirWatch->addFile(file); } void CollectionList::removeWatched(const QString &file) { m_dirWatch->removeFile(file); } //////////////////////////////////////////////////////////////////////////////// // CollectionListItem public methods //////////////////////////////////////////////////////////////////////////////// void CollectionListItem::refresh() { int offset = CollectionList::instance()->columnOffset(); int columns = lastColumn() + offset + 1; sharedData()->metadata.resize(columns); sharedData()->cachedWidths.resize(columns); for(int i = offset; i < columns; i++) { setText(i, text(i)); int id = i - offset; if(id != TrackNumberColumn && id != LengthColumn) { // All columns other than track num and length need local-encoded data for sorting QString toLower = text(i).toLower(); // For some columns, we may be able to share some strings if((id == ArtistColumn) || (id == AlbumColumn) || (id == GenreColumn) || (id == YearColumn) || (id == CommentColumn)) { toLower = StringShare::tryShare(toLower); if(id != YearColumn && id != CommentColumn && sharedData()->metadata[id] != toLower) { CollectionList::instance()->removeStringFromDict(sharedData()->metadata[id], id); CollectionList::instance()->addStringToDict(text(i), id); } } sharedData()->metadata[id] = toLower; } int newWidth = treeWidget()->fontMetrics().width(text(i)); if(newWidth != sharedData()->cachedWidths[i]) playlist()->slotWeightDirty(i); sharedData()->cachedWidths[i] = newWidth; } for(PlaylistItemList::Iterator it = m_children.begin(); it != m_children.end(); ++it) { (*it)->playlist()->update(); (*it)->playlist()->dataChanged(); } if(treeWidget()->isVisible()) treeWidget()->viewport()->update(); CollectionList::instance()->dataChanged(); emit CollectionList::instance()->signalCollectionChanged(); } PlaylistItem *CollectionListItem::itemForPlaylist(const Playlist *playlist) { if(playlist == CollectionList::instance()) return this; PlaylistItemList::ConstIterator it; for(it = m_children.constBegin(); it != m_children.constEnd(); ++it) if((*it)->playlist() == playlist) return *it; return 0; } void CollectionListItem::updateCollectionDict(const QString &oldPath, const QString &newPath) { CollectionList *collection = CollectionList::instance(); if(!collection) return; collection->removeFromDict(oldPath); collection->addToDict(newPath, this); } void CollectionListItem::repaint() const { // FIXME repaint /*QItemDelegate::repaint(); for(PlaylistItemList::ConstIterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) (*it)->repaint();*/ } //////////////////////////////////////////////////////////////////////////////// // CollectionListItem protected methods //////////////////////////////////////////////////////////////////////////////// CollectionListItem::CollectionListItem(CollectionList *parent, const FileHandle &file) : PlaylistItem(parent), m_shuttingDown(false) { parent->addToDict(file.absFilePath(), this); sharedData()->fileHandle = file; if(file.tag()) { refresh(); parent->dataChanged(); } else { qCCritical(JUK_LOG) << "CollectionListItem::CollectionListItem() -- Tag() could not be created."; } - - SplashScreen::increment(); } CollectionListItem::~CollectionListItem() { m_shuttingDown = true; foreach(PlaylistItem *item, m_children) delete item; CollectionList *l = CollectionList::instance(); if(l) { l->removeFromDict(file().absFilePath()); l->removeStringFromDict(file().tag()->album(), AlbumColumn); l->removeStringFromDict(file().tag()->artist(), ArtistColumn); l->removeStringFromDict(file().tag()->genre(), GenreColumn); } } void CollectionListItem::addChildItem(PlaylistItem *child) { m_children.append(child); } void CollectionListItem::removeChildItem(PlaylistItem *child) { if(!m_shuttingDown) m_children.removeAll(child); } bool CollectionListItem::checkCurrent() { if(!file().fileInfo().exists() || !file().fileInfo().isFile()) return false; if(!file().current()) { file().refresh(); refresh(); } return true; } // vim: set et sw=4 tw=0 sta: diff --git a/collectionlist.h b/collectionlist.h index 69f91e3b..a1622e5a 100644 --- a/collectionlist.h +++ b/collectionlist.h @@ -1,216 +1,216 @@ /** * 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 COLLECTIONLIST_H -#define COLLECTIONLIST_H +#ifndef JUK_COLLECTIONLIST_H +#define JUK_COLLECTIONLIST_H #include #include #include "playlist.h" #include "playlistitem.h" class ViewMode; class KFileItem; class KFileItemList; class KDirWatch; /** * This type is for mapping QString track attributes like the album, artist * and track to an integer count representing the number of outstanding items * that hold the string. */ typedef QHash TagCountDict; typedef QHashIterator TagCountDictIterator; /** * We then have an array of dicts, one for each column in the list view. * The array is sparse (not every vector will have a TagCountDict so we use * pointers. */ typedef QVector TagCountDicts; /** * This is the "collection", or all of the music files that have been opened * in any playlist and not explicitly removed from the collection. * * It is being implemented as a "semi-singleton" because I need universal access * to just one instance. However, because the collection needs initialization * parameters (that will not always be available when an instance is needed). * Hence there will be the familiar singleton "instance()" method allong with an * "initialize()" method. */ class CollectionListItem : public PlaylistItem { friend class Playlist; friend class CollectionList; friend class PlaylistItem; public: virtual void refresh(); PlaylistItem *itemForPlaylist(const Playlist *playlist); void updateCollectionDict(const QString &oldPath, const QString &newPath); void repaint() const; PlaylistItemList children() const { return m_children; } protected: CollectionListItem(CollectionList *parent, const FileHandle &file); virtual ~CollectionListItem(); void addChildItem(PlaylistItem *child); void removeChildItem(PlaylistItem *child); /** * Returns true if the item is now up to date (even if this required a refresh) or * false if the item is invalid. */ bool checkCurrent(); virtual CollectionListItem *collectionItem() { return this; } private: bool m_shuttingDown; PlaylistItemList m_children; }; class CollectionList : public Playlist { friend class CollectionListItem; Q_OBJECT public: /** * A variety of unique value lists will be kept in the collection. This * enum can be used as an index into those structures. */ enum UniqueSetType { Artists = 0, Albums = 1, Genres = 2 }; static CollectionList *instance(); static void initialize(PlaylistCollection *collection); /** * Returns a unique set of values associated with the type specified. */ QStringList uniqueSet(UniqueSetType t) const; CollectionListItem *lookup(const QString &file) const; virtual CollectionListItem *createItem(const FileHandle &file, QTreeWidgetItem * = 0, bool = false); void emitVisibleColumnsChanged() { emit signalVisibleColumnsChanged(); } virtual void clearItems(const PlaylistItemList &items); void setupTreeViewEntries(ViewMode *viewMode) const; virtual bool canReload() const { return true; } void saveItemsToCache() const; public slots: virtual void paste(); virtual void clear(); void slotCheckCache(); void slotRemoveItem(const QString &file); void slotRefreshItem(const QString &file); void slotNewItems(const KFileItemList &items); void slotRefreshItems(const QList > &items); void slotDeleteItem(const KFileItem &item); protected: CollectionList(PlaylistCollection *collection); virtual ~CollectionList(); virtual void dropEvent(QDropEvent *e); virtual void dragMoveEvent(QDragMoveEvent *e); // These methods are used by CollectionListItem, which is a friend class. void addToDict(const QString &file, CollectionListItem *item) { m_itemsDict.insert(file, item); } void removeFromDict(const QString &file) { m_itemsDict.remove(file); } // These methods are also used by CollectionListItem, to manage the // strings used in generating the unique sets and tree view mode playlists. QString addStringToDict(const QString &value, int column); void removeStringFromDict(const QString &value, int column); void addWatched(const QString &file); void removeWatched(const QString &file); virtual bool hasItem(const QString &file) const { return m_itemsDict.contains(file); } signals: void signalCollectionChanged(); /** * This is emitted when the set of columns that is visible is changed. * * \see Playlist::hideColumn() * \see Playlist::showColumn() * \see Playlsit::isColumnVisible() */ void signalVisibleColumnsChanged(); void signalNewTag(const QString &, unsigned); void signalRemovedTag(const QString &, unsigned); // Emitted once cached items are loaded, which allows for folder scanning // and invalid track detection to proceed. void cachedItemsLoaded(); public slots: /** * Loads the CollectionListItems from the Cache. Should be called after program * initialization. */ void startLoadingCachedItems(); /** * Loads a few items at a time. Intended to be single-shotted into the event * loop so that loading the music doesn't freeze the GUI. */ void loadNextBatchCachedItems(); /** - * Teardown from cache loading (e.g. splash screen, sorting, etc.). Should + * Teardown from cache loading (e.g. a sort operation). Should * always be called if startLoadingCachedItems is called. */ void completedLoadingCachedItems(); private: /** * Just the size of the above enum to keep from hard coding it in several * locations. */ static const int m_uniqueSetCount = 3; static CollectionList *m_list; QHash m_itemsDict; KDirWatch *m_dirWatch; TagCountDicts m_columnTags; }; #endif // vim: set et sw=4 tw=0 sta: diff --git a/doc/index.docbook b/doc/index.docbook index 3ff07518..4b8c1370 100644 --- a/doc/index.docbook +++ b/doc/index.docbook @@ -1,1758 +1,1747 @@ JuK"> ]> The &juk; Handbook &Lauri.Watts; &Lauri.Watts.mail; Michael Pyne
michael.pyne@kdemail.net
Scott Wheeler
&Scott.Wheeler.mail;
2001 2002 2004 &Scott.Wheeler; &FDLNotice; 2013-11-08 3.11 (&kde; 4.12) &juk; is a jukebox, tagger and music collection manager. KDE kdemultimedia audio tagger player jukebox JuK
Introduction &juk; is, well, a jukebox. As is typical with many jukebox applications, &juk; allows you to edit the tags of your audio files, and manage your collection and playlists. Using &juk; Here's a screenshot of &juk; Screenshot of &juk; in action. &juk; maintains a list of all files that it knows about. This is called the Collection List. The collection list is specific to &juk; and is not shared with other applications. Independent of the Collection List, are playlists. You can have as many playlists as you want. You can use &juk; created playlists with other media players (such as &amarok; or xmms) and you can manage playlists created in those applications from within &juk;. You can add files to the Collection List individually, using FileOpen ... and selecting them from a standard &kde; file dialog. You can add entire folders using FileManage Folders.... Folders added this way will be rescanned every time you start &juk;. You can even define folders inside these folders that will be excluded from scan. You can force the folders to be rescanned by right-clicking on the Collection List icon, and selecting Reload. Adding a song to a playlist will automatically add its file to the Collection List, but adding a file to the Collection List won't automatically add the song to any playlists. You can quickly create a playlist from your entire Collection List, by &RMB; clicking on the Collection List icon, and choosing Duplicate. The resulting playlist is a normal playlist, and editing it will not affect the Collection List. You can add playlist files created outside &juk; individually by selecting them with FileOpen .... Any playlist files found in folders you add with FileManage Folders... will also be added automatically. You can create a new empty playlist, a search playlist or a playlist from a folder by choosing FileNew or the New icon on the toolbar. You will be prompted for a name, and then an icon for that playlist will appear in the playlist pane. You can now drag and drop files from the Collection List, or from other playlists, to your playlist. Use the Save icon or FileSave to save the playlist at any time. The Song List When you are viewing the Collection List, the main pane contains all the files that &juk; knows about. When you are viewing a playlist, only the songs that are in that playlist are shown. In either case, the appearance and behavior of the list is the same. Each song takes one row in the display. There is a column for each metadata field that &juk; tracks. These columns correspond to the fields available to edit in the tag editor. You can reorder the list at any time by &LMB; clicking on the column header. This will first sort the files in ascending order based on the contents of that column. If you &LMB; click again on the same header, the files will be re-sorted in descending order. The columns are initially sized wide enough to show the longest entry available. You can resize the columns by placing your mouse cursor on the divider between two columns. When the cursor changes from a pointer, &LMB; click and drag in the direction you want to resize the columns. You can reorder the columns by &LMB; clicking on a header and dragging the header to the left or right. You cannot drag past the edge of the window when doing this however, so you may need to scroll a little to the left or right, and repeat dragging the header, until you have placed it in your preferred position. You can hide or unhide columns by &RMB; clicking on a column header, and clicking on the name of the column to change. &LMB; double clicking on a file will play it with the built-in player. If another song was already playing, it will stop, and the new song will play. &RMB; clicking on a file offers you several options: Add to Play Queue This will start playing the file as soon as the current song is over. If no song is playing, the file will be played when you next hit the Play button. If you have already chosen the Add to Play Queue option on a different file, then this file will override that selection. Cut Copy Paste ... Remove from Playlist If you are viewing the Collection List, choosing this action will remove the file from the list, and will also remove all corresponding entries for this song from all playlists. You should note that if this file is in a folder that &juk; scans on startup, it will be readded to the Collection List the next time you start up &juk; but it won't be automatically added to any playlists. If you are viewing a playlist, Remove from Playlist will simply remove the song from the playlist. Edit Column Title Will allow you to edit the currently highlighted song, in the column you clicked in. For example, if you do not have the tag editor visible, and you are busy creating a playlist, but you notice a mis-spelling in an artist name, you can edit it directly with this menu item. Changes made in this manner are always saved immediately as soon as you click elsewhere and are finished editing. This menu item will be disabled if &juk; detects that the track you have selected is read-only. Refresh This will reload the tag information of the selected files, in case the files have been changed while &juk; was running. Delete This will remove the file from the Collection List, remove all entries for the song in all playlists, and delete the file from your disk. You cannot undo this, although you will be asked to confirm your choice. Use this with caution. Guess Tag Information This will make &juk; try to guess information such as the Artist and Title of a song. &juk; employs different methods of guessing: From File Name &juk; will try to guess the tags of the song based on its filename. For example, a song name such as Roxette - You've Got the Look.mp3 would guess Roxette for the artist and You've Got the Look as the title. You can adjust the way &juk; guesses for tags by selecting Settings Tag Guesser..., which will open the Tag Guesser dialog. &juk; will not replace tags that it did not guess from the file name. From Internet &juk; will try to guess the tags of the song by using the MusicBrainz program. You must have MusicBrainz installed for this command to work. Rename File This will rename the selected files to conform to a given format. You must choose the way you want the files renamed first by selecting SettingsFile Renamer.... The resulting name of each file is based on its metadata tags. For example, the Ogg Vorbis song The Theme (Metroid) by Stemage could result in /usr/share/music/Stemage/The Theme (Metroid).ogg. Create Playlist From Selected Items This allows you to quickly create a playlist from songs in your Collection List. This function will prompt you for a name for the new playlist, and will then insert all of the songs that are selected into the new playlist. Add Selected Items to Audio or Data CD This allows you to quickly create a K3b &CD;-burning project from your selected songs. &juk; will ask you if you would like an Audio &CD; project or a Data &CD; project, unless K3b already has a project open. If K3b is not already running, &juk; will start it up for you. After that, &juk; will add your selected files to the current K3b project. You can then save the project in K3b for burning later, or burn the &CD; right away. &juk; Playlists A playlist is simply a collection of songs grouped by some category. For example, you may have a playlist of songs that you listen to while coding, while trying to sleep, or even when you need a laugh. &juk; supports several different types of playlists. Normal playlists This is the most common kind of playlist. It is a playlist composed of files, just like the Collection List. The history playlist If you enable this playlist (by enabling ViewShow History, this playlist will record every song that &juk; plays. The playlist will have an extra column, Time, which records the exact time and date the song played. The playlist doesn't start tracking the history until it is enabled, however. Search playlists This is a playlist which is based off of a search. You can create a playlist like this by clicking FileNewSearch Playlist, or by clicking on the Search Playlist button on the toolbar. After creating this playlist, it will keep track of which songs in the Collection List match your criteria, and automatically update itself accordingly whenever the Collection List changes. Playlists are organized in the Playlist pane, which is the vertical bar at the left. In this pane is an icon for every playlist you have. There are different view mode for this pane, which can be selected from the ViewView Modes menu. Default View mode This is the default view mode. In this mode, all the playlists are shown as large icons, one above the other in the view mode. Compact View mode This mode is similar to the Normal Viewmode, with the exception that the playlists are represented with horizontal bars with small icons instead of with square boxes. Tree View mode This mode is the most powerful. This mode is just like the Compact viewmode, except that the Collection List is now the root of a tree of virtual playlists. The Collection List has three children nodes, Artist, Album, and Genre. Each of these node has children representing all of the entries from that specific category. For example, if your Collection List contains music from 4 different artists, you would have 4 entries under the artist node. One nifty feature of the tree view mode is something called drag-and-drop retagging. Simply select some files in the track list, and drag them onto one of the artist, album, or genre nodes under Collection List. The songs will automatically be retagged to match the item you dropped the tracks on. For example, if you drag a group of tracks onto a Genre called "Rock", all of the tracks will be retagged will a Genre tag of Rock. The &juk; Tag Editor For many file formats, it is practical to use the filename to describe the contents of the file: Report for the board - June 2003.doc for example, may be all the information you need in order to find that file again. Trying to capture all the useful information about a particular song however, could lead to filenames like this: Type O Negative - The Glorious Liberation Of The Peoples Technocratic Republic Of Vinnland By The Combined Forces Of The United Territories Of Europa.mp3 or Various Artists_15_The Smithsonian Collection of Classic Jazz Volume II_Jimmie Lunceford & His Orchestra - Organ Grinder's Swing.mp3. These are neither very practical to use, nor do they contain all of the useful information that you might have collected about the song. Adding the album, and track number, for example, to the first would make it even longer and more unmanageable, while still not telling you at a glance the year it was released, or what style of music it is, if you're not familiar with the artist. The solution then, is to store this kind of metadata inside the files themselves. Mp3 and ogg files can also contain small snippets of text which you can use to describe the content of the file. There are several formats, but &juk; hides the details of the differences between them, and provides a standard way to edit a standard subset of well known tags for all your audio files. &juk;'s full featured tag editor allows you to edit the tags in both mp3 and ogg files. You can edit single files or multiple files, and you can select a mix of mp3 and ogg files to edit. The only requirement is that you have write access to the files themselves; you cannot edit the tags of a file that is mounted from a &CD-ROM; for example. Editing the Tags in a Single File To edit the tag in a single file, select it in either the collection list or any entries it has in any playlist. If the tag editor is not visible, enable it by choosing ViewShow Tag Editor. The tag editor displays in the bottom of the list view. Simply type into any of the editable fields to change the information. When you are done, &LMB; click back in the list, and you will be prompted to save your changes. You may find that the tag editor remains disabled when you've clicked on a file. This happens when &juk; has detected that the track is read-only. Tag Editor Fields Artist Name: The name of the Artist(s) who released the song. Track name: The name of the song. Album name: The name of the album the song was released on. Genre: The Style of the music. &juk; provides a list corresponding roughly to the informal id3 standard, but you are free to type your own entries in this list. File name: The file name of the actual file on disk. You can edit this directly, and when you save, the file will be renamed. Track: The position of the track on the original recording. Year: The year the song was released. Length: This is not editable, it is simply shown for information. Bitrate: This is not editable, it is simply shown for information. Comment: You can add your own free text comment here, with additional notes &etc; You can explicitly and immediately save your changes at any time using the TaggerSave menu entry or by pressing &Ctrl;T. Editing the Tags in Multiple Files You can select multiple files in the list view, and edit one or more fields in the tags for all files at once. Use &Shift; and the &LMB; to select a contiguous list of files, and &Ctrl; and &LMB; to select individual non-contiguous files. If the tag editor is not visible, you can enable it by choosing ViewShow Tag Editor. The tag editor displays in the bottom of the list view. The tag editor behaves slightly differently when you have selected multiple files. Each field in the tag editor will now show an Enable check box next to it. Any field that has exactly the same contents for all the files you selected, displays that content, and is enabled for editing, with the Enable check box checked. Any field that does not have matching contents in all selected files is not initially editable, and does not display any contents at all. To change the content of any field, check the Enable check box if it is not already checked, and edit the field as you normally would. When you are done, &LMB; click back in the list view and you will be prompted to save your changes. The prompt dialog will show you a list of the affected files, so you have a chance to check that you are indeed altering the files you intended to. You can explicitly and immediately save your changes at any time using the TaggerSave menu entry or by pressing &Ctrl;T. The Rename File dialog The Rename File dialog Screenshot of the Rename File dialog. The File Renamer Configuration dialog box is used to configure the Rename File action, which renames a song's based on the information contained within its metadata tags. The whole Rename File feature is used to get consistent file names for your music collection (including moving the files if necessary). For example you may have some songs which appear on disk as 01 - Title.mp3 if you got them from Amazon, while songs from OCRemix might be Ailsean- Mega_Man_3_Mega_Fire_(OC_Remix).mp3 or something along those lines. Using the file renamer allows &juk; to rename those songs to something sane on-disk, such as Ailsean - Mega Fire.mp3 and The Strokes - Reptilia.mp3. You can even include paths throughout to automatically sort the music (⪚ by year or album), like $HOME/Music/2003/The Strokes - Reptilia.mp3. Doing this is as simple as activating the Rename File action, either from the Tagger menu, or right-clicking on the files. The default is the &Ctrl;R shortcut. But before this is done you should setup how the files are to be renamed. That is done from the above mentioned dialog that can be opened with SettingsFile Renamer... menu item. You can define a destination path, by using a base music folder and then adding pieces to there. These path pieces would be based on the music metadata such as song title, artist, &etc;, and are added using the Add Category selector. Each "path piece" has a check box between it allowing a folder separator to be added. The options for each piece, which simply control what happens if the appropriate metadata is empty, and allow a prefix/suffix when the entry is substituted in, can help you to make the renaming procedure more effective. The Tag Guesser Configuration dialog The Tag Guesser Configuration dialog Screenshot of the Tag Guesser Configuration dialog. The Tag Guesser Configuration dialog is used to configure the Guess from File Name action. In the dialog you will see a list on the left of different filename scheme patterns. &juk; includes an extensive set of defaults patterns to match most common filenaming styles. If you'd like to add a new scheme, click on the Add button, and type in your scheme and click on OK. Here you can see the currently configured file name schemes which the tag editor uses to extract tag information from a file name. Each string may contain one of the following placeholders: %t: Title %A: Album %a: Artist %T: Track %c: Comment For example, the file name scheme [%T] %a - %t would match [01] Deep Purple - Smoke on the water but not (Deep Purple) Smoke on the water. For that second name, you would use the scheme (%a) %t. &juk; will try the schemes you have listed one at a time, starting at the top of the list. The first scheme which results in a match will be the scheme used to guess the song's tags. Some songs may match more than one scheme. You can make sure that the correct scheme matches first by selecting the scheme in the list box and then using the arrow buttons to move it to the top of the list. You can also edit or remove a scheme from the list. Just select the scheme in the list, and use the Modify button to change the scheme, or the Remove button to remove the scheme from the list. The Advanced Search dialog The Advanced Search dialog Screenshot of the Advanced Search Dialog. The Advanced Search dialog is used to create Search Playlists. It allows you to make a fine-grained search among the different tags of your song collection. At the top of the dialog, you can type in the name of your search playlist. Then, you can define your search criteria in the Search Criteria group. The top of the Search Criteria group has two radio buttons, Match any of the following and Match all of the following. If you select Match any of the following, then a match by any of the conditions you define will include the song in the playlist. Otherwise, every condition you define must match in order to include the song in the playlist. Below the radio buttons are the condition definitions. You can add more conditions by using the More button, and remove conditions by using the Fewer button. Any conditions you leave blank are ignored, so you do not have to use Fewer to eliminate empty conditions. Every condition definition has three parts: The tag chooser list on the left, the matching style list on the right, and the search text in the middle. The tag chooser lets &juk; know what tag you want to search for the text in. If you choose the special tag "<All Visible>", then any tag that you can see in the Collection List listing is fair game to match the search text. The match style list lets &juk; know which search method to use. The search methods you can use are as follows: Normal Matching This is the default matching style. It searches for the given text anywhere in the chosen tag, ignoring case differences. For example a search for mode in the Artist tag would match Depeche Mode. Case Sensitive This search is just like Normal Matching, except that the search must match the exact case of the text. Pattern Matching This is the most powerful search method. The search text you type in will define a regular expression used to search within the tag. Regular expressions are beyond the scope of this documentation, but the application &kregexpeditor; can help you form a regular expression. &juk; uses the &Qt; regular expression style. Simply choose the conditions you want to include in your search, and click OK to create your search playlist! The &juk; Toolbar The Main Toolbar The &juk; toolbar. The &juk; toolbar. The &juk; toolbar. From left to right in the screenshot above, the icons on the default toolbar are: New Create a new playlist. If you hold down the button, a menu will pop up allowing you to select the different kinds of playlists to create. Empty Playlist... This prompts you for a playlist name, and then inserts it into the Playlist view. The playlist starts out completely empty. Playlist From Folder... This prompts you for a folder to open, and then creates a playlist containing the music within the folder and any sub-folders. The name of the created playlist is the same as the name of the selected folder. Search Playlist... This brings up the Advanced Search Dialog, allowing you to create a virtual playlist. Any songs in your Collection List that match the search criteria that you specify in the Advanced Search Dialog will be added to the new playlist. As your Collection List changes, the new playlist will as well. For example, if you create a playlist of all of your Depeche Mode songs, and then add another Depeche Mode song to your Collection List, it will show up in the Depeche Mode playlist with no special action required on your part. Open Add a file to the collection list (if it's active) or to the currently selected playlist. Adding a file to a playlist will add it to the collection list automatically, but not vice versa. Save Save the currently selected playlist. To save a tag you have edited, either select another item, or press &Ctrl;T instead. Cut If a playlist or song is selected, cut (remove) it from the list. If the tag editor is active, this works like cut in any editor, removing the selected text, but keeping a copy on the clipboard. Copy If the tag editor is active, this works like copy in any editor, placing a copy of the selected text on the clipboard. If you use copy on a song in the collection list, the url is placed on the clipboard, so you can paste it. For example, you could paste the url into a text editor, &konqueror;, or another playlist. Paste If you previously either cut or copied a url from the collection list, you can paste the url back into a new playlist. You could also paste a url you have copied from &konqueror; or any other application. If you are operating in the tag editor, paste will paste any text currently on the clipboard into the selected field. Show Search Bar Show or hide the search bar. Show Tag Editor Show or hide the tag editor. Play controls These work like any standard media player you may have come across. The controls are Play, Pause, Stop, Previous and Next. There is also a tracking bar, showing how far along (relatively) in the current song you are. You can drag this slider with the mouse in order to skip forwards or backwards within a track. Finally there is a volume slider. As you may expect, this raises and lowers the volume. Loud is on the right, and Quiet is on the left. The Search bar The search bar allows you to quickly search for a song in the collection list or a playlist. Simply typing text into the search bar will reduce the visible list of songs to those which contain that text in any visible column. Pressing &Enter; will start playing the top match in the playlist view. Searching begins instantly when text is entered into the search field. Searching is incremental, that is, as you type each character into the text field, the search is narrowed further. This is useful to find a song where you only remember part of a name, for instance. Menu and Command Reference Menus File Menu &Ctrl;N FileNewEmpty Playlist... Create a new playlist &Ctrl;F FileNewSearch Playlist... Creates a new search playlist. &Ctrl;D FileNewPlaylist From Folder... Creates a new playlist, containing all music files in a folder and any sub-folders. Any music within playlists files that &juk; recognizes will also be added. &Ctrl;O File Open... Select a file (or files) to add to the collection list. If you select a playlist file, every file in the playlist will be added. FileManage Folders... This menu item opens the same window that is shown on a first &juk; start Select a folder (or folders) to add to the collection list. These folders will also be rescanned whenever &juk; is started or FileReload is chosen. You can also select folders that should not be automatically searched (which would override the list of folders to scan on startup). This way you can avoid having to completely change up your music layout on-disk just to keep &juk; from automatically grabbing songs you do not want or need managed from within &juk;. The Manage Folders dialog Screenshot of the Manage Folders Dialog. If you want to remove files that are already in the collection list, you can enable the File Name (full path) column in a playlist and then use the search bar to find the offending path name, select and remove the files that are shown. File Rename... Rename a playlist. FileDuplicate... Create a duplicate of the selected playlist, and prompt for a new name. FileReload Reloads the tag information on every file in the selected playlist. FileRemove Remove the selected playlist. &Ctrl;S FileSave Save the selected playlist. FileSave As... Save the selected playlist, with a different name. Edit Menu EditClear Removes the selected songs from the playlist. View Menu ViewShow Search Bar This is a toggle action that sets whether or not the Search Bar is shown. ViewShow Tag Editor This is a toggle action that sets whether or not the Tag Editor is shown. ViewShow History This is a toggle action that sets whether or not the History Playlist is shown. ViewView ModesDefault Switches to Default View mode. ViewView ModesCompact Switches to Compact View mode. ViewView ModesTree Switches to Tree View mode. Player Menu PlayerRandom Play This is a toggle option which controls the Random Play setting. If Random Play is enabled, then &juk; will randomly select a random song from the current playlist when the currently playing song is over. PlayerLoop Playlist This is a toggle option which controls the Loop Playlist setting. If Loop Playlist is enabled, then &juk; will start playing from the beginning when it has finished playing every song in the current playlist. PlayerPlay This command starts playing the currently selected song, or resumes playback of the song if it was paused. PlayerPause This command pauses the currently playing song. Use the Play command to restart playback. PlayerStop This command stops the playback of the currently playing song. You cannot resume playback from its current position after that. PlayerNext This command skips to the next song to play in the playlist. PlayerPrevious This command plays the song that was playing before the currently playing song. Tagger Menu &Ctrl;T TaggerSave This command saves any changes to the tags that you are editing. Normally, changes are not saved until you deselect the file you are editing. TaggerDelete This command deletes the currently selected files from the Collection List and any playlists containing it, and then deletes the selected file from the disk. TaggerRefresh This command allows you to refresh playlist tags after changing them. &Ctrl;G TaggerGuess Tag InformationFrom Filename This command tries to guess the tags of the selected files by scanning the filename. You can configure the patterns used for guessing by selecting Settings Tag Guesser..., which opens the Tag Guesser Configuration dialog. &Ctrl;I TaggerGuess Tag InformationFrom Internet This command tries to guess the tags of the selected files by using the TunePimp library provided with MusicBrainz. &Ctrl;R TaggerRename File This command allows you to rename a file or a group of files for the selected songs in the playlist. Before the real rename happens &juk; will show a preview dialog where you can confirm or cancel the action. The files will be renamed according to the parameters defined using Rename File dialog. Settings Menu SettingsToolbars Shown Main / Play Toolbar These commands show or hide the Main Toolbar and the Play Toolbar. - - -Settings -Show Splash Screen on Startup - - -This is a toggle option. If enabled, &juk; will display -an informational screen upon startup as it loads your music collection. - - - Settings Dock in System Tray This is a toggle option. If enabled, &juk; will display an icon in your system tray. You can use the system tray icon to tell if &juk; is playing, and control playback. Settings Stay in System Tray on Close This is a toggle option. If enabled, &juk; will remain running if you close the main window. The Dock in System Tray option must also be enabled. To quit &juk;, use the File Quit command from the main window, or the Quit command from the system tray's context menu. If &juk; is started up when it is set to dock in the system tray on startup a simple notification will be shown (as otherwise the user may wonder why &juk; window did not open up). It is possible to remove this additional notification by going to Common Appearance and Behavior Application and System Notifications in &systemsettings; to manage JuK music player application notifications. Just uncheck Show a message in a popup item for JuK running in dock mode. More information on &kde; notification settings can be found here. Settings Popup Track Announcement This is a toggle option. If enabled, &juk; will display an indicator whenever a song starts playing, with information on the artist and title, and with buttons allowing you to quickly switch to a different song. The Dock in System Tray option must also be enabled. Settings Tag Guesser... This command brings up the Tag Guesser Configuration dialog box, where you can alter the patterns used to guess tag information from filenames. Settings File Renamer... This command brings up the File Renamer Configuration dialog box, where you can alter the way &juk; renames files for you. Additionally &juk; has the common &kde; Settings and Help menu items, for more information read the sections about the Settings Menu and Help Menu of the &kde; Fundamentals. Keybinding Reference Key Combination Action &Ctrl;A Select all &Ctrl;C Copy &Ctrl;R Rename file &Ctrl;I Guess tags from the Internet. &Ctrl;G Guess tags from the filename. &Ctrl;F New search playlist. &Ctrl;N New empty Playlist &Ctrl;D New playlist from folder. &Ctrl;T Save changes to edited tags. Credits and License &juk; Copyright © 2002-2013 &Scott.Wheeler;, Michael Pyne, and others. &juk; is developed and maintained by &Scott.Wheeler; &Scott.Wheeler.mail; and Michael Pyne michael.pyne@kdemail.net. Many thanks to the following people who have contributed to &juk;: &Daniel.Molkentin; &Daniel.Molkentin.mail; for system tray docking, inline tag editing, bug fixes, evangelism, moral support. Tim Jansen tim@tjansen.de for the GStreamer port Stefan Asserhäll stefan.asserhall@telia.com for global shortcut support. Stephen Douglas stephen_douglas@yahoo.com for track announcement popups. &Frerich.Raabe; &Frerich.Raabe.mail; for automagical track data guessing, and bugfixes. Zack Rusin zack@kde.org for more automagical things, including MusicBrainz support. Adam Treat manyoso@yahoo.com for co-conspiring in MusicBrainz wizardry. Matthias Kretz kretz@kde.org for being the friendly neighborhood &arts; guru. Maks Orlovich maksim@kde.org for making &juk; friendlier to people with terabytes of music. Antonio Larrosa Jimenez larrosa@kde.org for the &DCOP; interface. Allan Sandfield Jensen kde@carewolf.com for the FLAC and MPC support. Nathan Toone nathan@toonetown.com for the Album cover manager. Pascal Klein 4pascal@tpg.com.au for the splash screen. Laurent Montel montel@kde.org for the porting to &kde; 4 when no one else around. Georg Grabler georg@grabler.net for the &kde; Platform porting efforts. Martin Sandsmark martin.sandsmark@kde.org for the Last.fm scrobbling support, lyrics, prepping for &kde; Frameworks. Eike Hein hein@kde.org for the MPRIS2 interface implementation. Documentation Copyright © 2003, &Lauri.Watts;, and copyright © 2004-2013 Michael Pyne. &underFDL; &underGPL; &documentation.index;
diff --git a/juk.cpp b/juk.cpp index 1dd6db65..d34bf968 100644 --- a/juk.cpp +++ b/juk.cpp @@ -1,629 +1,614 @@ /** * Copyright (C) 2002-2004 Scott Wheeler * 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 "juk.h" #include #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 "splashscreen.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(0), m_statusLabel(0), m_systemTray(0), m_player(new PlayerManager), m_scrobbler(0), - m_shuttingDown(false), - m_filesToOpen(filesToOpen) + m_filesToOpen(filesToOpen), + m_shuttingDown(false) { // Expect segfaults if you change this order. m_instance = this; readSettings(); Cache::ensureAppDataStorageExists(); - if(m_showSplash && !m_startDocked && Cache::cacheFileExists()) { - if(SplashScreen* splash = SplashScreen::instance()) { - splash->show(); - qApp->processEvents(); - } - } - 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(), SIGNAL(signalCollectionChanged()), m_statusLabel, SLOT(updateData())); statusBar()->addWidget(m_statusLabel, 1); m_player->setStatusLabel(m_statusLabel); 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->add("crossfadeTracks"); act->setText(i18n("Crossfade Between Tracks")); connect(act, SIGNAL(triggered(bool)), m_player, SLOT(setCrossfadeEnabled(bool))); 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_toggleSplashAction = collection->add("showSplashScreen"); - m_toggleSplashAction->setText(i18n("Show Splash Screen on Startup")); - 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 QStringList oldFiles = KGlobal::dirs()->findAllResources("tmp", "juk-cover-*.png"); foreach(const QString &file, oldFiles) { qCWarning(JUK_LOG) << "Removing old cover" << file; if(!QFile::remove(file)) { qCCritical(JUK_LOG) << "Failed to remove old cover" << file; } } } 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_showSplash = config.readEntry("ShowSplashScreen", true); 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); - - m_toggleSplashAction->setChecked(m_showSplash); } 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()); a = ActionCollection::action("crossfadeTracks"); playerConfig.writeEntry("CrossfadeTracks", a->isChecked()); 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("ShowSplashScreen", m_toggleSplashAction->isChecked()); 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); // Playlists depend on CoverManager, so CoverManager should shutdown as // late as possible CoverManager::shutdown(); } void JuK::slotQuit() { m_shuttingDown = true; 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::configure(ActionCollection::actions(), this); } 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/juk.h b/juk.h index 210749b2..a13e36ac 100644 --- a/juk.h +++ b/juk.h @@ -1,109 +1,107 @@ /** * 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_H #define JUK_H #include #include "playermanager.h" #include class KToggleAction; class KGlobalAccel; class SliderAction; class StatusLabel; class SystemTray; class PlayerManager; class PlaylistSplitter; class Scrobbler; class JuK : public KXmlGuiWindow { Q_OBJECT public: JuK(const QStringList &filesToOpen, QWidget* parent = 0); virtual ~JuK(); static JuK* JuKInstance(); PlayerManager *playerManager() const; // Use a null cover for failure void coverDownloaded(const QPixmap &cover); private: void setupLayout(); void setupActions(); void setupGlobalAccels(); void keyPressEvent(QKeyEvent *); void activateScrobblerIfEnabled(); /** * readSettings() is separate from readConfig() in that it contains settings * that need to be read before the GUI is setup. */ void readSettings(); void readConfig(); void saveConfig(); virtual bool queryClose(); private slots: void slotSetupSystemTray(); void slotShowHide(); void slotAboutToQuit(); void slotQuit(); void slotToggleSystemTray(bool enabled); void slotEditKeys(); void slotConfigureTagGuesser(); void slotConfigureFileRenamer(); void slotConfigureScrobbling(); void slotUndo(); void slotCheckAlbumNextAction(bool albumRandomEnabled); void slotProcessArgs(); void slotClearOldCovers(); private: PlaylistSplitter *m_splitter; StatusLabel *m_statusLabel; SystemTray *m_systemTray; KToggleAction *m_randomPlayAction; KToggleAction *m_toggleSystemTrayAction; KToggleAction *m_toggleDockOnCloseAction; KToggleAction *m_togglePopupsAction; - KToggleAction *m_toggleSplashAction; PlayerManager *m_player; Scrobbler *m_scrobbler; QStringList m_filesToOpen; bool m_startDocked; - bool m_showSplash; bool m_shuttingDown; static JuK* m_instance; }; #endif // vim: set et sw=4 tw=0 sta: diff --git a/jukui-rtl.rc b/jukui-rtl.rc index 77c3b896..38d74477 100644 --- a/jukui-rtl.rc +++ b/jukui-rtl.rc @@ -1,113 +1,112 @@ - + &File &View &Player &Tagger &Settings - Play Toolbar diff --git a/jukui.rc b/jukui.rc index 4a14d454..4277d28e 100644 --- a/jukui.rc +++ b/jukui.rc @@ -1,113 +1,112 @@ - + &File &View &Player &Tagger &Settings - Play Toolbar diff --git a/pics/CMakeLists.txt b/pics/CMakeLists.txt index 66a35879..11ff67c2 100644 --- a/pics/CMakeLists.txt +++ b/pics/CMakeLists.txt @@ -1,3 +1,3 @@ -install( FILES theme.svg playing.png splash.png DESTINATION ${DATA_INSTALL_DIR}/juk/pics ) +install( FILES theme.svg playing.png DESTINATION ${DATA_INSTALL_DIR}/juk/pics ) diff --git a/pics/splash.png b/pics/splash.png deleted file mode 100644 index 3cbaca7e..00000000 Binary files a/pics/splash.png and /dev/null differ diff --git a/splashscreen.cpp b/splashscreen.cpp deleted file mode 100644 index ca7b1bba..00000000 --- a/splashscreen.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/** - * 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 "splashscreen.h" -#include "juk_debug.h" - -#include -#include -#include - -#include -#include -#include - -SplashScreen *SplashScreen::splash = 0; -bool SplashScreen::done = false; -int SplashScreen::count = 0; - -static QString loadedText(int i) -{ - return i18nc("%1 is a count of loaded music tracks", "Loading: %1", i); -} - -//////////////////////////////////////////////////////////////////////////////// -// pubic members -//////////////////////////////////////////////////////////////////////////////// - -SplashScreen *SplashScreen::instance() -{ - if(!splash && !done) - splash = new SplashScreen(); - return splash; -} - -void SplashScreen::finishedLoading() -{ - done = true; - delete splash; - splash = 0; -} - -void SplashScreen::increment() -{ - if(splash) { - count++; - if(( count & 63 ) == 0) - splash->processEvents(); - } -} - -void SplashScreen::update() -{ - if(splash) - splash->processEvents(); -} - -//////////////////////////////////////////////////////////////////////////////// -// protected members -//////////////////////////////////////////////////////////////////////////////// - -SplashScreen::SplashScreen() : QLabel(0, Qt::SplashScreen) -{ - setObjectName( QLatin1String("splashScreen" )); - - QPixmap background = UserIcon("splash"); - resize(background.size()); - QPalette palette; - palette.setBrush(backgroundRole(), QBrush(background)); - - setMargin(7); - setAlignment(Qt::AlignLeft | Qt::AlignBottom); - - palette.setColor(foregroundRole(), QColor(107, 158, 194)); - setPalette(palette); - - QFont f = font(); - f.setPixelSize(10); - setFont(f); - - setText(loadedText(0)); -} - -SplashScreen::~SplashScreen() -{ - -} - -//////////////////////////////////////////////////////////////////////////////// -// private methods -//////////////////////////////////////////////////////////////////////////////// - -void SplashScreen::processEvents() -{ - setText(loadedText(count)); -} - -// vim: set et sw=4 tw=0 sta: diff --git a/splashscreen.h b/splashscreen.h deleted file mode 100644 index 5ef87e74..00000000 --- a/splashscreen.h +++ /dev/null @@ -1,55 +0,0 @@ -/** - * 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 SPLASHSCREEN_H -#define SPLASHSCREEN_H - -#include - -/** - * Well, all of this session restoration sure is fun, but it's starting to take - * a while, especially say, if you're building KDE and indexing your file system - * in the background. ;-) So, despite my general hate of splashscreens I - * thought on appropriate here. - * - * As in other places, this is a singleton. That makes it relatively easy to - * handle the updates from whichever class seems appropriate through static - * methods. - */ - -class SplashScreen : public QLabel -{ -public: - static SplashScreen *instance(); - static void finishedLoading(); - static void increment(); - static void update(); - -protected: - SplashScreen(); - virtual ~SplashScreen(); - -private: - void processEvents(); - - static SplashScreen *splash; - static bool done; - static int count; -}; - -#endif - -// vim: set et sw=4 tw=0 sta: