/*************************************************************************** begin : Thu Sep 12 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 "playlistbox.h" #include "playlist.h" #include "collectionlist.h" #include "covermanager.h" #include "dynamicplaylist.h" #include "historyplaylist.h" #include "upcomingplaylist.h" #include "viewmode.h" #include "searchplaylist.h" #include "treeviewitemplaylist.h" #include "actioncollection.h" #include "cache.h" #include "k3bexporter.h" #include "tracksequencemanager.h" #include "tagtransactionmanager.h" using namespace ActionCollection; //////////////////////////////////////////////////////////////////////////////// // PlaylistBox public methods //////////////////////////////////////////////////////////////////////////////// PlaylistBox::PlaylistBox(TQWidget *parent, TQWidgetStack *playlistStack, const char *name) : TDEListView(parent, name), PlaylistCollection(playlistStack), m_viewModeIndex(0), m_hasSelection(false), m_doingMultiSelect(false), m_dropItem(0), m_showTimer(0) { readConfig(); addColumn("Playlists", width()); header()->blockSignals(true); header()->hide(); header()->blockSignals(false); setSorting(0); setFullWidth(true); setItemMargin(3); setAcceptDrops(true); setSelectionModeExt(Extended); m_contextMenu = new TDEPopupMenu(this); K3bPlaylistExporter *exporter = new K3bPlaylistExporter(this); m_k3bAction = exporter->action(); action("file_new")->plug(m_contextMenu); action("renamePlaylist")->plug(m_contextMenu); action("editSearch")->plug(m_contextMenu); action("duplicatePlaylist")->plug(m_contextMenu); action("reloadPlaylist")->plug(m_contextMenu); action("deleteItemPlaylist")->plug(m_contextMenu); action("file_save")->plug(m_contextMenu); action("file_save_as")->plug(m_contextMenu); if(m_k3bAction) m_k3bAction->plug(m_contextMenu); m_contextMenu->insertSeparator(); // add the view modes stuff TDESelectAction *viewModeAction = new TDESelectAction(i18n("View Modes"), "view_choose", TDEShortcut(), ActionCollection::actions(), "viewModeMenu"); m_viewModes.append(new ViewMode(this)); m_viewModes.append(new CompactViewMode(this)); m_viewModes.append(new TreeViewMode(this)); // m_viewModes.append(new CoverManagerMode(this)); TQStringList modeNames; for(TQValueListIterator it = m_viewModes.begin(); it != m_viewModes.end(); ++it) modeNames.append((*it)->name()); viewModeAction->setItems(modeNames); TQPopupMenu *p = viewModeAction->popupMenu(); p->changeItem(0, SmallIconSet("view_detailed"), modeNames[0]); p->changeItem(1, SmallIconSet("view_text"), modeNames[1]); p->changeItem(2, SmallIconSet("view_tree"), modeNames[2]); CollectionList::initialize(this); Cache::loadPlaylists(this); viewModeAction->setCurrentItem(m_viewModeIndex); m_viewModes[m_viewModeIndex]->setShown(true); TrackSequenceManager::instance()->setCurrentPlaylist(CollectionList::instance()); raise(CollectionList::instance()); viewModeAction->plug(m_contextMenu); connect(viewModeAction, TQT_SIGNAL(activated(int)), this, TQT_SLOT(slotSetViewMode(int))); connect(this, TQT_SIGNAL(selectionChanged()), this, TQT_SLOT(slotPlaylistChanged())); connect(this, TQT_SIGNAL(doubleClicked(TQListViewItem *)), this, TQT_SLOT(slotDoubleClicked())); connect(this, TQT_SIGNAL(contextMenuRequested(TQListViewItem *, const TQPoint &, int)), this, TQT_SLOT(slotShowContextMenu(TQListViewItem *, const TQPoint &, int))); TagTransactionManager *tagManager = TagTransactionManager::instance(); connect(tagManager, TQT_SIGNAL(signalAboutToModifyTags()), TQT_SLOT(slotFreezePlaylists())); connect(tagManager, TQT_SIGNAL(signalDoneModifyingTags()), TQT_SLOT(slotUnfreezePlaylists())); setupUpcomingPlaylist(); connect(CollectionList::instance(), TQT_SIGNAL(signalNewTag(const TQString &, unsigned)), this, TQT_SLOT(slotAddItem(const TQString &, unsigned))); connect(CollectionList::instance(), TQT_SIGNAL(signalRemovedTag(const TQString &, unsigned)), this, TQT_SLOT(slotRemoveItem(const TQString &, unsigned))); TQTimer::singleShot(0, object(), TQT_SLOT(slotScanFolders())); enableDirWatch(true); // Auto-save playlists after 10 minutes TQTimer::singleShot(600000, this, TQT_SLOT(slotSavePlaylists())); m_showTimer = new TQTimer(this); connect(m_showTimer, TQT_SIGNAL(timeout()), TQT_SLOT(slotShowDropTarget())); } PlaylistBox::~PlaylistBox() { PlaylistList l; CollectionList *collection = CollectionList::instance(); for(TQListViewItem *i = firstChild(); i; i = i->nextSibling()) { Item *item = static_cast(i); if(item->playlist() && item->playlist() != collection) l.append(item->playlist()); } Cache::savePlaylists(l); saveConfig(); } void PlaylistBox::raise(Playlist *playlist) { if(!playlist) return; Item *i = m_playlistDict.find(playlist); if(i) { clearSelection(); setSelected(i, true); setSingleItem(i); ensureItemVisible(currentItem()); } else PlaylistCollection::raise(playlist); slotPlaylistChanged(); } void PlaylistBox::duplicate() { Item *item = static_cast(currentItem()); if(!item || !item->playlist()) return; TQString name = playlistNameDialog(i18n("Duplicate"), item->text(0)); if(name.isNull()) return; Playlist *p = new Playlist(this, name); p->createItems(item->playlist()->items()); } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox public slots //////////////////////////////////////////////////////////////////////////////// void PlaylistBox::paste() { Item *i = static_cast(currentItem()); decode(kapp->clipboard()->data(), i); } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox protected methods //////////////////////////////////////////////////////////////////////////////// void PlaylistBox::slotFreezePlaylists() { setDynamicListsFrozen(true); } void PlaylistBox::slotUnfreezePlaylists() { setDynamicListsFrozen(false); } void PlaylistBox::setupPlaylist(Playlist *playlist, const TQString &iconName) { setupPlaylist(playlist, iconName, 0); } void PlaylistBox::setupPlaylist(Playlist *playlist, const TQString &iconName, Item *parentItem) { connect(playlist, TQT_SIGNAL(signalPlaylistItemsDropped(Playlist *)), TQT_SLOT(slotPlaylistItemsDropped(Playlist *))); PlaylistCollection::setupPlaylist(playlist, iconName); if(parentItem) new Item(parentItem, iconName, playlist->name(), playlist); else new Item(this, iconName, playlist->name(), playlist); } void PlaylistBox::removePlaylist(Playlist *playlist) { removeNameFromDict(m_playlistDict[playlist]->text(0)); removeFileFromDict(playlist->fileName()); m_playlistDict.remove(playlist); } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox private methods //////////////////////////////////////////////////////////////////////////////// void PlaylistBox::readConfig() { TDEConfigGroup config(TDEGlobal::config(), "PlaylistBox"); m_viewModeIndex = config.readNumEntry("ViewMode", 0); } void PlaylistBox::saveConfig() { TDEConfigGroup config(TDEGlobal::config(), "PlaylistBox"); config.writeEntry("ViewMode", action("viewModeMenu")->currentItem()); TDEGlobal::config()->sync(); } void PlaylistBox::remove() { ItemList items = selectedItems(); if(items.isEmpty()) return; TQStringList files; TQStringList names; for(ItemList::ConstIterator it = items.begin(); it != items.end(); ++it) { if(*it && (*it)->playlist() && !(*it)->playlist()->fileName().isEmpty() && TQFileInfo((*it)->playlist()->fileName()).exists()) { files.append((*it)->playlist()->fileName()); } names.append((*it)->playlist()->name()); } if(!files.isEmpty()) { int remove = KMessageBox::warningYesNoCancelList( this, i18n("Do you want to delete these files from the disk as well?"), files, TQString(), KStdGuiItem::del(), i18n("Keep")); if(remove == KMessageBox::Yes) { TQStringList couldNotDelete; for(TQStringList::ConstIterator it = files.begin(); it != files.end(); ++it) { if(!TQFile::remove(*it)) couldNotDelete.append(*it); } if(!couldNotDelete.isEmpty()) KMessageBox::errorList(this, i18n("Could not delete these files."), couldNotDelete); } else if(remove == KMessageBox::Cancel) return; } else if(items.count() > 1 || items.front()->playlist() != upcomingPlaylist()) { if(KMessageBox::warningContinueCancelList(this, i18n("Are you sure you want to remove these " "playlists from your collection?"), names, i18n("Remove Items?"), KGuiItem(i18n("&Remove"), "edittrash")) == KMessageBox::Cancel) { return; } } PlaylistList removeQueue; for(ItemList::ConstIterator it = items.begin(); it != items.end(); ++it) { if(*it != Item::collectionItem() && (*it)->playlist() && (!(*it)->playlist()->readOnly())) { removeQueue.append((*it)->playlist()); } } if(items.back()->nextSibling() && static_cast(items.back()->nextSibling())->playlist()) setSingleItem(items.back()->nextSibling()); else { Item *i = static_cast(items.front()->itemAbove()); while(i && !i->playlist()) i = static_cast(i->itemAbove()); if(!i) i = Item::collectionItem(); setSingleItem(i); } for(PlaylistList::ConstIterator it = removeQueue.begin(); it != removeQueue.end(); ++it) { if(*it != upcomingPlaylist()) delete *it; else { action("showUpcoming")->setChecked(false); setUpcomingPlaylistEnabled(false); } } } void PlaylistBox::setDynamicListsFrozen(bool frozen) { for(TQValueList::Iterator it = m_viewModes.begin(); it != m_viewModes.end(); ++it) { (*it)->setDynamicListsFrozen(frozen); } } void PlaylistBox::slotSavePlaylists() { kdDebug(65432) << "Auto-saving playlists and covers.\n"; PlaylistList l; CollectionList *collection = CollectionList::instance(); for(TQListViewItem *i = firstChild(); i; i = i->nextSibling()) { Item *item = static_cast(i); if(item->playlist() && item->playlist() != collection) l.append(item->playlist()); } Cache::savePlaylists(l); CoverManager::saveCovers(); TQTimer::singleShot(600000, this, TQT_SLOT(slotSavePlaylists())); } void PlaylistBox::slotShowDropTarget() { if(!m_dropItem) { kdError(65432) << "Trying to show the playlist of a null item!\n"; return; } raise(m_dropItem->playlist()); } void PlaylistBox::slotAddItem(const TQString &tag, unsigned column) { for(TQValueListIterator it = m_viewModes.begin(); it != m_viewModes.end(); ++it) (*it)->addItems(tag, column); } void PlaylistBox::slotRemoveItem(const TQString &tag, unsigned column) { for(TQValueListIterator it = m_viewModes.begin(); it != m_viewModes.end(); ++it) (*it)->removeItem(tag, column); } void PlaylistBox::decode(TQMimeSource *s, Item *item) { if(!s || (item && item->playlist() && item->playlist()->readOnly())) return; KURL::List urls; if(KURLDrag::decode(s, urls) && !urls.isEmpty()) { TQStringList files; for(KURL::List::Iterator it = urls.begin(); it != urls.end(); ++it) files.append((*it).path()); if(item) { TreeViewItemPlaylist *playlistItem; playlistItem = dynamic_cast(item->playlist()); if(playlistItem) { playlistItem->retag(files, currentPlaylist()); TagTransactionManager::instance()->commit(); currentPlaylist()->update(); return; } } if(item && item->playlist()) item->playlist()->addFiles(files); else { TQString name = playlistNameDialog(); if(!name.isNull()) { Playlist *p = new Playlist(this, name); p->addFiles(files); } } } } void PlaylistBox::contentsDropEvent(TQDropEvent *e) { m_showTimer->stop(); Item *i = static_cast(itemAt(contentsToViewport(e->pos()))); decode(e, i); if(m_dropItem) { Item *old = m_dropItem; m_dropItem = 0; old->repaint(); } } void PlaylistBox::contentsDragMoveEvent(TQDragMoveEvent *e) { // If we can decode the input source, there is a non-null item at the "move" // position, the playlist for that Item is non-null, is not the // selected playlist and is not the CollectionList, then accept the event. // // Otherwise, do not accept the event. if(!KURLDrag::canDecode(e)) { e->accept(false); return; } Item *target = static_cast(itemAt(contentsToViewport(e->pos()))); if(target) { if(target->playlist() && target->playlist()->readOnly()) return; // This is a semi-dirty hack to check if the items are coming from within // JuK. If they are not coming from a Playlist (or subclass) then the // dynamic_cast will fail and we can safely assume that the item is // coming from outside of JuK. if(dynamic_cast(e->source())) { if(target->playlist() && target->playlist() != CollectionList::instance() && !target->isSelected()) { e->accept(true); } else e->accept(false); } else // the dropped items are coming from outside of JuK e->accept(true); if(m_dropItem != target) { Item *old = m_dropItem; m_showTimer->stop(); if(e->isAccepted()) { m_dropItem = target; target->repaint(); m_showTimer->start(1500, true); } else m_dropItem = 0; if(old) old->repaint(); } } else { // We're dragging over the whitespace. We'll use this case to make it // possible to create new lists. e->accept(true); } } void PlaylistBox::contentsDragLeaveEvent(TQDragLeaveEvent *e) { if(m_dropItem) { Item *old = m_dropItem; m_dropItem = 0; old->repaint(); } TDEListView::contentsDragLeaveEvent(e); } void PlaylistBox::contentsMousePressEvent(TQMouseEvent *e) { if(e->button() == Qt::LeftButton) m_doingMultiSelect = true; TDEListView::contentsMousePressEvent(e); } void PlaylistBox::contentsMouseReleaseEvent(TQMouseEvent *e) { if(e->button() == Qt::LeftButton) { m_doingMultiSelect = false; slotPlaylistChanged(); } TDEListView::contentsMouseReleaseEvent(e); } void PlaylistBox::keyPressEvent(TQKeyEvent *e) { if((e->key() == Key_Up || e->key() == Key_Down) && e->state() == ShiftButton) m_doingMultiSelect = true; TDEListView::keyPressEvent(e); } void PlaylistBox::keyReleaseEvent(TQKeyEvent *e) { if(m_doingMultiSelect && e->key() == Key_Shift) { m_doingMultiSelect = false; slotPlaylistChanged(); } TDEListView::keyReleaseEvent(e); } PlaylistBox::ItemList PlaylistBox::selectedItems() const { ItemList l; for(TQListViewItemIterator it(const_cast(this), TQListViewItemIterator::Selected); it.current(); ++it) l.append(static_cast(*it)); return l; } void PlaylistBox::setSingleItem(TQListViewItem *item) { setSelectionModeExt(Single); TDEListView::setCurrentItem(item); setSelectionModeExt(Extended); } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox private slots //////////////////////////////////////////////////////////////////////////////// void PlaylistBox::slotPlaylistChanged() { // Don't update while the mouse is pressed down. if(m_doingMultiSelect) return; ItemList items = selectedItems(); m_hasSelection = !items.isEmpty(); bool allowReload = false; PlaylistList playlists; for(ItemList::ConstIterator it = items.begin(); it != items.end(); ++it) { Playlist *p = (*it)->playlist(); if(p) { if(p->canReload()) allowReload = true; playlists.append(p); } } bool singlePlaylist = playlists.count() == 1; if(playlists.isEmpty() || (singlePlaylist && (playlists.front() == CollectionList::instance() || playlists.front()->readOnly()))) { action("file_save")->setEnabled(false); action("file_save_as")->setEnabled(false); action("renamePlaylist")->setEnabled(false); action("deleteItemPlaylist")->setEnabled(false); } else { action("file_save")->setEnabled(true); action("file_save_as")->setEnabled(true); action("renamePlaylist")->setEnabled(playlists.count() == 1); action("deleteItemPlaylist")->setEnabled(true); } action("reloadPlaylist")->setEnabled(allowReload); action("duplicatePlaylist")->setEnabled(!playlists.isEmpty()); if(m_k3bAction) m_k3bAction->setEnabled(!playlists.isEmpty()); action("editSearch")->setEnabled(singlePlaylist && playlists.front()->searchIsEditable()); if(singlePlaylist) { PlaylistCollection::raise(playlists.front()); if(playlists.front() == upcomingPlaylist()) action("deleteItemPlaylist")->setText(i18n("Hid&e")); else action("deleteItemPlaylist")->setText(i18n("R&emove")); } else if(!playlists.isEmpty()) createDynamicPlaylist(playlists); } void PlaylistBox::slotDoubleClicked() { action("stop")->activate(); action("play")->activate(); } void PlaylistBox::slotShowContextMenu(TQListViewItem *, const TQPoint &point, int) { m_contextMenu->popup(point); } void PlaylistBox::slotPlaylistItemsDropped(Playlist *p) { raise(p); } void PlaylistBox::slotSetViewMode(int index) { if(index == m_viewModeIndex) return; viewMode()->setShown(false); m_viewModeIndex = index; viewMode()->setShown(true); } void PlaylistBox::setupItem(Item *item) { m_playlistDict.insert(item->playlist(), item); viewMode()->queueRefresh(); } void PlaylistBox::setupUpcomingPlaylist() { TDEConfigGroup config(TDEGlobal::config(), "Playlists"); bool enable = config.readBoolEntry("showUpcoming", false); setUpcomingPlaylistEnabled(enable); action("showUpcoming")->setChecked(enable); } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox::Item protected methods //////////////////////////////////////////////////////////////////////////////// PlaylistBox::Item *PlaylistBox::Item::m_collectionItem = 0; PlaylistBox::Item::Item(PlaylistBox *listBox, const TQString &icon, const TQString &text, Playlist *l) : TQObject(listBox), TDEListViewItem(listBox, 0, text), m_playlist(l), m_text(text), m_iconName(icon), m_sortedFirst(false) { init(); } PlaylistBox::Item::Item(Item *parent, const TQString &icon, const TQString &text, Playlist *l) : TQObject(parent->listView()), TDEListViewItem(parent, text), m_playlist(l), m_text(text), m_iconName(icon), m_sortedFirst(false) { init(); } PlaylistBox::Item::~Item() { } int PlaylistBox::Item::compare(TQListViewItem *i, int col, bool) const { Item *otherItem = static_cast(i); PlaylistBox *playlistBox = static_cast(listView()); if(m_playlist == playlistBox->upcomingPlaylist() && otherItem->m_playlist != CollectionList::instance()) return -1; if(otherItem->m_playlist == playlistBox->upcomingPlaylist() && m_playlist != CollectionList::instance()) return 1; if(m_sortedFirst && !otherItem->m_sortedFirst) return -1; else if(otherItem->m_sortedFirst && !m_sortedFirst) return 1; return text(col).lower().localeAwareCompare(i->text(col).lower()); } void PlaylistBox::Item::paintCell(TQPainter *painter, const TQColorGroup &colorGroup, int column, int width, int align) { PlaylistBox *playlistBox = static_cast(listView()); playlistBox->viewMode()->paintCell(this, painter, colorGroup, column, width, align); } void PlaylistBox::Item::setText(int column, const TQString &text) { m_text = text; TDEListViewItem::setText(column, text); } void PlaylistBox::Item::setup() { listView()->viewMode()->setupItem(this); } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox::Item protected slots //////////////////////////////////////////////////////////////////////////////// void PlaylistBox::Item::slotSetName(const TQString &name) { if(listView()) { setText(0, name); setSelected(true); listView()->sort(); listView()->ensureItemVisible(listView()->currentItem()); listView()->viewMode()->queueRefresh(); } } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox::Item private methods //////////////////////////////////////////////////////////////////////////////// void PlaylistBox::Item::init() { PlaylistBox *list = listView(); list->setupItem(this); int iconSize = list->viewModeIndex() == 0 ? 32 : 16; setPixmap(0, SmallIcon(m_iconName, iconSize)); list->addNameToDict(m_text); if(m_playlist) { connect(m_playlist, TQT_SIGNAL(signalNameChanged(const TQString &)), this, TQT_SLOT(slotSetName(const TQString &))); connect(m_playlist, TQT_SIGNAL(destroyed()), this, TQT_SLOT(deleteLater())); connect(m_playlist, TQT_SIGNAL(signalEnableDirWatch(bool)), list->object(), TQT_SLOT(slotEnableDirWatch(bool))); } if(m_playlist == CollectionList::instance()) { m_sortedFirst = true; m_collectionItem = this; list->viewMode()->setupDynamicPlaylists(); } if(m_playlist == list->historyPlaylist() || m_playlist == list->upcomingPlaylist()) m_sortedFirst = true; } #include "playlistbox.moc"