Commit 0fdc1c20 authored by Michael Pyne's avatar Michael Pyne

Change the tree view mode in JuK to create and delete playlists as needed to

keep in sync with your music collection.  What this means is that if you
should remove a song, or edit a tag such that one of the tree view categories
is empty, the category will automatically delete itself.  Also, adding tracks
or editing a new tag will automatically create the appropriate entry, even if
you've already expanded the tree view out.

This fixes a couple of bugs, and although the patch is rather large, it has been
reviewed by Scott <wheeler@kde.org>, with some of his suggestions implemented.
Of course, I would appreciate testing, I've been running it here for quite a few days.

No strings were harmed during the making of this patch. :-)

CCMAIL:62303-done@bugs.kde.org
CCMAIL:74598-done@bugs.kde.org

svn path=/trunk/kdemultimedia/juk/; revision=332224
parent cb72ebea
......@@ -28,6 +28,8 @@
#include "stringshare.h"
#include "cache.h"
#include "actioncollection.h"
#include "tag.h"
#include "viewmode.h"
using namespace ActionCollection;
......@@ -59,6 +61,13 @@ void CollectionList::initialize(PlaylistCollection *collection)
new CollectionListItem(*it);
SplashScreen::update();
// The CollectionList is created with sorting disabled for speed. Re-enable
// it here, and perform the sort.
KConfigGroup config(KGlobal::config(), "Playlists");
m_list->setSortColumn(config.readNumEntry("CollectionListSortColumn", 1));
m_list->sort();
collection->setupPlaylist(m_list, "folder_sound");
}
......@@ -95,6 +104,24 @@ void CollectionList::clearItems(const PlaylistItemList &items)
dataChanged();
}
void CollectionList::setupTreeViewEntries(ViewMode *viewMode) const
{
TreeViewMode *treeViewMode = dynamic_cast<TreeViewMode*>(viewMode);
if(!treeViewMode) {
kdWarning(65432) << "Can't setup entries on a non-tree-view mode!\n";
return;
}
QValueList<int> columnList;
columnList << PlaylistItem::ArtistColumn;
columnList << PlaylistItem::GenreColumn;
columnList << PlaylistItem::AlbumColumn;
for(QValueList<int>::Iterator colIt = columnList.begin(); colIt != columnList.end(); ++colIt)
for(TagCountDictIterator it(*m_columnTags[*colIt]); it.current(); ++it)
treeViewMode->slotAddItem(it.currentKey(), *colIt);
}
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////
......@@ -142,8 +169,7 @@ void CollectionList::slotRefreshItem(const QString &file)
CollectionList::CollectionList(PlaylistCollection *collection) :
Playlist(collection, true),
m_itemsDict(5003),
m_uniqueSets(m_uniqueSetCount, SortedStringList()),
m_uniqueSetLast(m_uniqueSetCount, QString::null)
m_columnTags(15, 0)
{
new KAction(i18n("Show Playing"), KShortcut(), actions(), "showPlaying");
......@@ -157,9 +183,16 @@ CollectionList::CollectionList(PlaylistCollection *collection) :
this, SLOT(slotPopulateBackMenu()));
connect(action<KToolBarPopupAction>("back")->popupMenu(), SIGNAL(activated(int)),
this, SLOT(slotPlayFromBackMenu(int)));
setSorting(-1); // Temporarily disable sorting to add items faster.
KConfigGroup config(KGlobal::config(), "Playlists");
setSortColumn(config.readNumEntry("CollectionListSortColumn", 1));
m_columnTags[PlaylistItem::ArtistColumn] = new TagCountDict(5001, false);
m_columnTags[PlaylistItem::ArtistColumn]->setAutoDelete(true);
m_columnTags[PlaylistItem::AlbumColumn] = new TagCountDict(5001, false);
m_columnTags[PlaylistItem::AlbumColumn]->setAutoDelete(true);
m_columnTags[PlaylistItem::GenreColumn] = new TagCountDict(5001, false);
m_columnTags[PlaylistItem::GenreColumn]->setAutoDelete(true);
polish();
}
......@@ -169,6 +202,14 @@ CollectionList::~CollectionList()
KConfigGroup config(KGlobal::config(), "Playlists");
config.writeEntry("CollectionListSortColumn", sortColumn());
// 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());
for(TagCountDicts::Iterator it = m_columnTags.begin(); it != m_columnTags.end(); ++it)
delete *it;
delete m_dirWatch;
}
......@@ -188,14 +229,68 @@ void CollectionList::contentsDragMoveEvent(QDragMoveEvent *e)
e->accept(false);
}
void CollectionList::addUnique(UniqueSetType t, const QString &value)
QString CollectionList::addStringToDict(const QString &value, unsigned column)
{
if(column > m_columnTags.count() || value.stripWhiteSpace().isEmpty())
return QString::null;
int *refCountPtr = m_columnTags[column]->find(value);
if(refCountPtr)
++(*refCountPtr);
else {
m_columnTags[column]->insert(value, new int(1));
emit signalNewTag(value, column);
}
return value;
}
QStringList CollectionList::uniqueSet(UniqueSetType t) const
{
if(value.isEmpty())
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();
}
if((unsigned) column >= m_columnTags.count())
return QStringList();
TagCountDictIterator it(*m_columnTags[column]);
QStringList list;
for(; it.current(); ++it)
list += it.currentKey();
return list;
}
void CollectionList::removeStringFromDict(const QString &value, unsigned column)
{
if(column > m_columnTags.count() || value.isEmpty())
return;
if(value != m_uniqueSetLast[t] && !m_uniqueSets[t].insert(value)) {
m_uniqueSetLast[t] = value;
emit signalCollectionChanged();
int *refCountPtr = m_columnTags[column]->find(value);
if(refCountPtr) {
--(*refCountPtr);
if(*refCountPtr == 0) {
emit signalRemovedTag(value, column);
m_columnTags[column]->remove(value);
}
}
}
......@@ -205,10 +300,6 @@ void CollectionList::addUnique(UniqueSetType t, const QString &value)
void CollectionListItem::refresh()
{
CollectionList::instance()->addUnique(CollectionList::Artists, text(ArtistColumn));
CollectionList::instance()->addUnique(CollectionList::Albums, text(AlbumColumn));
CollectionList::instance()->addUnique(CollectionList::Genres, text(GenreColumn));
int offset = static_cast<Playlist *>(listView())->columnOffset();
int columns = lastColumn() + offset + 1;
data()->local8Bit.resize(columns);
......@@ -230,7 +321,13 @@ void CollectionListItem::refresh()
(id == CommentColumn))
{
lower = StringShare::tryShare(lower);
if(id != YearColumn && id != CommentColumn && data()->local8Bit[id] != lower) {
CollectionList::instance()->removeStringFromDict(data()->local8Bit[id], id);
CollectionList::instance()->addStringToDict(text(i), id);
}
}
data()->local8Bit[id] = lower;
}
......@@ -298,8 +395,12 @@ CollectionListItem::~CollectionListItem()
}
CollectionList *l = CollectionList::instance();
if(l)
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)
......
......@@ -21,12 +21,31 @@
#include <qdict.h>
#include <qclipboard.h>
#include <qvaluevector.h>
#include "playlist.h"
#include "playlistitem.h"
#include "sortedstringlist.h"
class CollectionListItem;
class ViewMode;
/**
* 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 QDict<int> TagCountDict;
typedef QDictIterator<int> TagCountDictIterator;
/**
* We then have an array of dicts, one for each column in the list view. We
* use pointers to TagCountDicts because QDict has a broken copy ctor, which
* doesn't copy the case sensitivity setting.
*/
typedef QValueVector<TagCountDict*> TagCountDicts;
/**
* This is the "collection", or all of the music files that have been opened
......@@ -58,7 +77,7 @@ public:
/**
* Returns a unique set of values associated with the type specified.
*/
QStringList uniqueSet(UniqueSetType t) const { return m_uniqueSets[t].values(); }
QStringList uniqueSet(UniqueSetType t) const;
CollectionListItem *lookup(const QString &file) { return m_itemsDict.find(file); }
......@@ -70,6 +89,8 @@ public:
virtual void clearItems(const PlaylistItemList &items);
void setupTreeViewEntries(ViewMode *viewMode) const;
public slots:
virtual void paste() { decode(kapp->clipboard()->data()); }
virtual void clear();
......@@ -90,10 +111,11 @@ protected:
void addToDict(const QString &file, CollectionListItem *item) { m_itemsDict.replace(file, item); }
void removeFromDict(const QString &file) { m_itemsDict.remove(file); }
/**
* Add a value to one of the unique value lists; use the UniqueSetType as a key.
*/
void addUnique(UniqueSetType t, const QString &value);
// 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, unsigned column);
void removeStringFromDict(const QString &value, unsigned column);
void addWatched(const QString &file) { m_dirWatch->addFile(file); }
void removeWatched(const QString &file) { m_dirWatch->removeFile(file); }
......@@ -110,6 +132,10 @@ signals:
*/
void signalVisibleColumnsChanged();
void signalNewTag(const QString &, unsigned);
void signalRemovedTag(const QString &, unsigned);
private:
/**
* Just the size of the above enum to keep from hard coding it in several
......@@ -120,8 +146,7 @@ private:
static CollectionList *m_list;
QDict<CollectionListItem> m_itemsDict;
KDirWatch *m_dirWatch;
QValueVector<SortedStringList> m_uniqueSets;
QValueVector<QString> m_uniqueSetLast;
TagCountDicts m_columnTags;
};
class CollectionListItem : public PlaylistItem
......
......@@ -1784,12 +1784,12 @@ void Playlist::slotInlineEditDone(QListViewItem *, const QString &, int column)
}
for(PlaylistItemList::ConstIterator it = l.begin(); it != l.end(); ++it) {
processEvents();
QFileInfo fi((*it)->file().absFilePath());
if(!fi.isWritable() || !editTag(*it, text, column))
failedFiles += fi.absFilePath();
processEvents();
}
if(!failedFiles.isEmpty())
......
......@@ -35,7 +35,6 @@ class KActionMenu;
class QEvent;
class PlaylistCollection;
class PlaylistSearch;
class PlaylistItem;
class PlaylistToolTip;
......
......@@ -125,6 +125,20 @@ PlaylistBox::PlaylistBox(QWidget *parent, QWidgetStack *playlistStack,
Cache::loadPlaylists(this);
raise(CollectionList::instance());
setSorting(-1); // Disable sorting for speed
CollectionList::instance()->setupTreeViewEntries(m_viewModes[2]);
setSorting(0);
sort();
connect(CollectionList::instance(), SIGNAL(signalNewTag(const QString &, unsigned)),
m_viewModes[2], SLOT(slotAddItem(const QString &, unsigned)));
connect(m_viewModes[2], SIGNAL(signalPlaylistDestroyed(Playlist*)),
this, SLOT(slotPlaylistDestroyed(Playlist*)));
connect(CollectionList::instance(), SIGNAL(signalRemovedTag(const QString &, unsigned)),
m_viewModes[2], SLOT(slotRemoveItem(const QString &, unsigned)));
QTimer::singleShot(0, object(), SLOT(slotScanFolders()));
}
......@@ -299,6 +313,15 @@ void PlaylistBox::remove()
delete *it;
}
void PlaylistBox::slotPlaylistDestroyed(Playlist *p)
{
emit signalPlaylistDestroyed(p);
removeName(m_playlistDict[p]->text(0));
delete m_playlistDict[p];
m_playlistDict.remove(p);
}
void PlaylistBox::decode(QMimeSource *s, Item *item)
{
if(!s || (item && item->playlist() && item->playlist()->readOnly()))
......
......@@ -70,6 +70,9 @@ protected:
virtual void setupPlaylist(Playlist *playlist, const QString &iconName);
virtual void setupPlaylist(Playlist *playlist, const QString &iconName, Item *parentItem);
signals:
void signalPlaylistDestroyed(Playlist *);
private:
void readConfig();
void saveConfig();
......@@ -99,6 +102,7 @@ private slots:
void slotDoubleClicked();
void slotShowContextMenu(QListViewItem *, const QPoint &point, int);
void slotSetViewMode(int index);
void slotPlaylistDestroyed(Playlist*);
private:
KPopupMenu *m_contextMenu;
......
......@@ -43,6 +43,7 @@ PlaylistSplitter::PlaylistSplitter(QWidget *parent, const char *name) :
readConfig();
m_editor->slotUpdateCollection();
m_editor->setupObservers();
}
PlaylistSplitter::~PlaylistSplitter()
......@@ -96,6 +97,8 @@ void PlaylistSplitter::setupLayout()
connect(m_playlistBox->object(), SIGNAL(signalSelectedItemsChanged()),
this, SLOT(slotPlaylistSelectionChanged()));
connect(m_playlistBox, SIGNAL(signalPlaylistDestroyed(Playlist*)),
m_editor, SLOT(slotPlaylistDestroyed(Playlist*)));
moveToFirst(m_playlistBox);
......
......@@ -283,37 +283,68 @@ void TreeViewMode::setShown(bool show)
}
}
void TreeViewMode::setupCategory(const QString &searchCategory, const QStringList &members, int column, bool exact)
void TreeViewMode::slotRemoveItem(const QString &item, unsigned column)
{
QString itemKey;
if(column == PlaylistItem::ArtistColumn)
itemKey = "artists" + item.lower();
else if(column == PlaylistItem::GenreColumn)
itemKey = "genres" + item.lower();
else if(column == PlaylistItem::AlbumColumn)
itemKey = "albums" + item.lower();
else {
kdWarning() << k_funcinfo << "Unhandled column type " << column << endl;
return;
}
if(!m_treeViewItems.contains(itemKey))
return;
TreeViewItemPlaylist *itemPlaylist = m_treeViewItems[itemKey];
m_treeViewItems.remove(itemKey);
itemPlaylist->deleteLater();
emit signalPlaylistDestroyed(itemPlaylist);
}
void TreeViewMode::slotAddItem(const QString &item, unsigned column)
{
CollectionList *collection = CollectionList::instance();
QValueList<int> columns;
columns.append(column);
KApplication::setOverrideCursor(Qt::waitCursor);
QString itemKey, searchCategory;
if(column == PlaylistItem::ArtistColumn)
searchCategory = "artists";
else if(column == PlaylistItem::GenreColumn)
searchCategory = "genres";
else if(column == PlaylistItem::AlbumColumn)
searchCategory = "albums";
else {
kdWarning() << k_funcinfo << "Unhandled column type " << column << endl;
return;
}
for(QStringList::ConstIterator it = members.begin(); it != members.end(); ++it) {
itemKey = searchCategory + item.lower();
if(m_treeViewItems.contains(itemKey))
return;
PlaylistSearch::ComponentList components;
PlaylistSearch::ComponentList components;
PlaylistSearch::Component::MatchMode mode;
if(exact)
mode = PlaylistSearch::Component::Exact;
else
mode = PlaylistSearch::Component::ContainsWord;
PlaylistSearch::Component::MatchMode mode = PlaylistSearch::Component::ContainsWord;
if(column != PlaylistItem::ArtistColumn)
mode = PlaylistSearch::Component::Exact;
components.append(PlaylistSearch::Component(*it, true, columns, mode));
components.append(PlaylistSearch::Component(item, true, columns, mode));
PlaylistList playlists;
playlists.append(collection);
PlaylistList playlists;
playlists.append(CollectionList::instance());
PlaylistSearch s(playlists, components, PlaylistSearch::MatchAny, false);
TreeViewItemPlaylist *p = new TreeViewItemPlaylist(playlistBox(), s, *it, false);
playlistBox()->setupPlaylist(p, "midi", m_searchCategories[searchCategory]);
PlaylistSearch s(playlists, components, PlaylistSearch::MatchAny, false);
processEvents();
}
TreeViewItemPlaylist *p = new TreeViewItemPlaylist(playlistBox(), s, item, false);
playlistBox()->setupPlaylist(p, "midi", m_searchCategories[searchCategory]);
KApplication::restoreOverrideCursor();
m_treeViewItems[itemKey] = p;
}
void TreeViewMode::setupCategories()
......@@ -323,18 +354,12 @@ void TreeViewMode::setupCategories()
i = new PlaylistBox::Item(collectionItem, "cdimage", i18n("Artists"));
m_searchCategories.insert("artists", i);
setupCategory("artists", CollectionList::instance()->uniqueSet(CollectionList::Artists),
PlaylistItem::ArtistColumn, false);
i = new PlaylistBox::Item(collectionItem, "cdimage", i18n("Albums"));
m_searchCategories.insert("albums", i);
setupCategory("albums", CollectionList::instance()->uniqueSet(CollectionList::Albums),
PlaylistItem::AlbumColumn);
i = new PlaylistBox::Item(collectionItem, "cdimage", i18n("Genres"));
m_searchCategories.insert("genres", i);
setupCategory("genres", CollectionList::instance()->uniqueSet(CollectionList::Genres),
PlaylistItem::GenreColumn);
for(QDictIterator<PlaylistBox::Item> it(m_searchCategories); it.current(); ++it)
it.current()->setSortedFirst(true);
......
......@@ -90,6 +90,8 @@ protected:
////////////////////////////////////////////////////////////////////////////////
class TreeViewItemPlaylist;
class TreeViewMode : public CompactViewMode
{
Q_OBJECT
......@@ -102,11 +104,16 @@ public:
virtual void setShown(bool shown);
void setupCategories();
private:
void setupCategory(const QString &searchCategory, const QStringList &members,
int column, bool exact = true);
public slots:
void slotRemoveItem(const QString &item, unsigned column);
void slotAddItem(const QString &item, unsigned column);
signals:
void signalPlaylistDestroyed(Playlist*);
private:
QDict<PlaylistBox::Item> m_searchCategories;
QMap<QString, TreeViewItemPlaylist*> m_treeViewItems;
};
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment