/*************************************************************************** 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 #include #include #include #include #include #include #include #include #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. TDEConfigGroup config(TDEGlobal::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(viewMode); if(!treeViewMode) { kdWarning(65432) << "Can't setup entries on a non-tree-view mode!\n"; return; } TQValueList columnList; columnList << PlaylistItem::ArtistColumn; columnList << PlaylistItem::GenreColumn; columnList << PlaylistItem::AlbumColumn; TQStringList items; for(TQValueList::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(TQDictIteratorit(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 TDEAction(i18n("Show Playing"), TDEShortcut(), ActionCollection::actions(), "showPlaying"); connect(action("showPlaying"), TQT_SIGNAL(activated()), this, TQT_SLOT(slotShowPlaying())); connect(action("back")->popupMenu(), TQT_SIGNAL(aboutToShow()), this, TQT_SLOT(slotPopulateBackMenu())); connect(action("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() { TDEConfigGroup config(TDEGlobal::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(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"