You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdemultimedia/juk/collectionlist.cpp

512 lines
14 KiB

/***************************************************************************
begin : Fri Sep 13 2002
copyright : (C) 2002 - 2004 by Scott Wheeler
email : wheeler@kde.org
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#include <kurldrag.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kdebug.h>
#include <kpopupmenu.h>
#include <kiconloader.h>
#include <kconfig.h>
#include <kaction.h>
#include <kurl.h>
#include "collectionlist.h"
#include "playlistcollection.h"
#include "splashscreen.h"
#include "stringshare.h"
#include "cache.h"
#include "actioncollection.h"
#include "tag.h"
#include "viewmode.h"
#include "coverinfo.h"
#include "covermanager.h"
using namespace ActionCollection;
////////////////////////////////////////////////////////////////////////////////
// static methods
////////////////////////////////////////////////////////////////////////////////
CollectionList *CollectionList::m_list = 0;
CollectionList *CollectionList::instance()
{
return m_list;
}
void CollectionList::initialize(PlaylistCollection *collection)
{
if(m_list)
return;
// We have to delay initilaization 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"));
FileHandleHash::Iterator end = Cache::instance()->end();
for(FileHandleHash::Iterator it = Cache::instance()->begin(); it != end; ++it)
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");
SortOrder order = Descending;
if(config.readBoolEntry("CollectionListSortAscending", true))
order = Ascending;
m_list->setSortOrder(order);
m_list->setSortColumn(config.readNumEntry("CollectionListSortColumn", 1));
m_list->sort();
collection->setupPlaylist(m_list, "folder_sound");
}
////////////////////////////////////////////////////////////////////////////////
// public methods
////////////////////////////////////////////////////////////////////////////////
PlaylistItem *CollectionList::createItem(const FileHandle &file, TQListViewItem *, 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.find(file.absFilePath()))
return 0;
PlaylistItem *item = new CollectionListItem(file);
if(!item->isValid()) {
kdError() << "CollectionList::createItem() -- A valid tag was not created for \""
<< file.absFilePath() << "\"" << endl;
delete item;
return 0;
}
setupItem(item);
return item;
}
void CollectionList::clearItems(const PlaylistItemList &items)
{
for(PlaylistItemList::ConstIterator it = items.begin(); it != items.end(); ++it) {
Cache::instance()->remove((*it)->file());
clearItem(*it, false);
}
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;
}
TQValueList<int> columnList;
columnList << PlaylistItem::ArtistColumn;
columnList << PlaylistItem::GenreColumn;
columnList << PlaylistItem::AlbumColumn;
TQStringList items;
for(TQValueList<int>::Iterator colIt = columnList.begin(); colIt != columnList.end(); ++colIt) {
items.clear();
for(TagCountDictIterator it(*m_columnTags[*colIt]); it.current(); ++it)
items << it.currentKey();
treeViewMode->addItems(items, *colIt);
}
}
void CollectionList::slotNewItems(const KFileItemList &items)
{
TQStringList files;
for(KFileItemListIterator it(items); it.current(); ++it)
files.append((*it)->url().path());
addFiles(files);
update();
}
void CollectionList::slotRefreshItems(const KFileItemList &items)
{
for(KFileItemListIterator it(items); it.current(); ++it) {
CollectionListItem *item = lookup((*it)->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
clearItem(item);
}
}
update();
}
void CollectionList::slotDeleteItem(KFileItem *item)
{
CollectionListItem *listItem = lookup(item->url().path());
if(listItem)
clearItem(listItem);
}
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////
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;
for(TQDictIterator<CollectionListItem>it(m_itemsDict); it.current(); ++it) {
if(!it.current()->checkCurrent())
invalidItems.append(*it);
processEvents();
}
clearItems(invalidItems);
}
void CollectionList::slotRemoveItem(const TQString &file)
{
clearItem(m_itemsDict[file]);
}
void CollectionList::slotRefreshItem(const TQString &file)
{
m_itemsDict[file]->refresh();
}
////////////////////////////////////////////////////////////////////////////////
// protected methods
////////////////////////////////////////////////////////////////////////////////
CollectionList::CollectionList(PlaylistCollection *collection) :
Playlist(collection, true),
m_itemsDict(5003),
m_columnTags(15, 0)
{
new KAction(i18n("Show Playing"), KShortcut(), ActionCollection::actions(), "showPlaying");
connect(action("showPlaying"), TQT_SIGNAL(activated()), this, TQT_SLOT(slotShowPlaying()));
connect(action<KToolBarPopupAction>("back")->popupMenu(), TQT_SIGNAL(aboutToShow()),
this, TQT_SLOT(slotPopulateBackMenu()));
connect(action<KToolBarPopupAction>("back")->popupMenu(), TQT_SIGNAL(activated(int)),
this, TQT_SLOT(slotPlayFromBackMenu(int)));
setSorting(-1); // Temporarily disable sorting to add items faster.
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();
}
CollectionList::~CollectionList()
{
KConfigGroup config(KGlobal::config(), "Playlists");
config.writeEntry("CollectionListSortColumn", sortColumn());
config.writeEntry("CollectionListSortAscending", sortOrder() == Ascending);
// 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;
}
void CollectionList::contentsDropEvent(TQDropEvent *e)
{
if(e->source() == this)
return; // Don't rearrange in the CollectionList.
else
Playlist::contentsDropEvent(e);
}
void CollectionList::contentsDragMoveEvent(TQDragMoveEvent *e)
{
if(canDecode(e) && e->source() != this)
e->accept(true);
else
e->accept(false);
}
TQString CollectionList::addStringToDict(const TQString &value, unsigned column)
{
if(column > m_columnTags.count() || value.stripWhiteSpace().isEmpty())
return TQString();
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;
}
TQStringList 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 TQStringList();
}
if((unsigned) column >= m_columnTags.count())
return TQStringList();
TagCountDictIterator it(*m_columnTags[column]);
TQStringList list;
for(; it.current(); ++it)
list += it.currentKey();
return list;
}
void CollectionList::removeStringFromDict(const TQString &value, unsigned column)
{
if(column > m_columnTags.count() || value.isEmpty())
return;
int *refCountPtr = m_columnTags[column]->find(value);
if(refCountPtr) {
--(*refCountPtr);
if(*refCountPtr == 0) {
emit signalRemovedTag(value, column);
m_columnTags[column]->remove(value);
}
}
}
////////////////////////////////////////////////////////////////////////////////
// CollectionListItem public methods
////////////////////////////////////////////////////////////////////////////////
void CollectionListItem::refresh()
{
int offset = static_cast<Playlist *>(listView())->columnOffset();
int columns = lastColumn() + offset + 1;
data()->local8Bit.resize(columns);
data()->cachedWidths.resize(columns);
for(int i = offset; i < columns; i++) {
int id = i - offset;
if(id != TrackNumberColumn && id != LengthColumn) {
// All columns other than track num and length need local-encoded data for sorting
TQCString lower = text(i).lower().local8Bit();
// For some columns, we may be able to share some strings
if((id == ArtistColumn) || (id == AlbumColumn) ||
(id == GenreColumn) || (id == YearColumn) ||
(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;
}
int newWidth = width(listView()->fontMetrics(), listView(), i);
data()->cachedWidths[i] = newWidth;
if(newWidth != data()->cachedWidths[i])
playlist()->slotWeightDirty(i);
}
file().coverInfo()->setCover();
if(listView()->isVisible())
repaint();
for(PlaylistItemList::Iterator it = m_children.begin(); it != m_children.end(); ++it) {
(*it)->playlist()->update();
(*it)->playlist()->dataChanged();
if((*it)->listView()->isVisible())
(*it)->repaint();
}
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.begin(); it != m_children.end(); ++it)
if((*it)->playlist() == playlist)
return *it;
return 0;
}
void CollectionListItem::updateCollectionDict(const TQString &oldPath, const TQString &newPath)
{
CollectionList *collection = CollectionList::instance();
if(!collection)
return;
collection->removeFromDict(oldPath);
collection->addToDict(newPath, this);
}
void CollectionListItem::repaint() const
{
TQListViewItem::repaint();
for(PlaylistItemList::ConstIterator it = m_children.begin(); it != m_children.end(); ++it)
(*it)->repaint();
}
////////////////////////////////////////////////////////////////////////////////
// CollectionListItem protected methods
////////////////////////////////////////////////////////////////////////////////
CollectionListItem::CollectionListItem(const FileHandle &file) :
PlaylistItem(CollectionList::instance()),
m_shuttingDown(false)
{
CollectionList *l = CollectionList::instance();
if(l) {
l->addToDict(file.absFilePath(), this);
data()->fileHandle = file;
if(file.tag()) {
refresh();
l->dataChanged();
// l->addWatched(m_path);
}
else
kdError() << "CollectionListItem::CollectionListItem() -- Tag() could not be created." << endl;
}
else
kdError(65432) << "CollectionListItems should not be created before "
<< "CollectionList::initialize() has been called." << endl;
SplashScreen::increment();
}
CollectionListItem::~CollectionListItem()
{
m_shuttingDown = true;
for(PlaylistItemList::ConstIterator it = m_children.begin();
it != m_children.end();
++it)
{
(*it)->playlist()->clearItem(*it);
}
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.remove(child);
}
bool CollectionListItem::checkCurrent()
{
if(!file().fileInfo().exists() || !file().fileInfo().isFile())
return false;
if(!file().current()) {
file().refresh();
refresh();
}
return true;
}
#include "collectionlist.moc"