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.
2364 lines
62 KiB
2364 lines
62 KiB
/***************************************************************************
|
|
begin : Sat Feb 16 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 <tdeconfig.h>
|
|
#include <tdemessagebox.h>
|
|
#include <kurldrag.h>
|
|
#include <kiconloader.h>
|
|
#include <klineedit.h>
|
|
#include <tdeaction.h>
|
|
#include <tdepopupmenu.h>
|
|
#include <tdelocale.h>
|
|
#include <kdebug.h>
|
|
#include <kinputdialog.h>
|
|
#include <tdefiledialog.h>
|
|
#include <tdeglobalsettings.h>
|
|
#include <kurl.h>
|
|
#include <tdeio/netaccess.h>
|
|
#include <tdeio/job.h>
|
|
#include <dcopclient.h>
|
|
|
|
#include <tqheader.h>
|
|
#include <tqcursor.h>
|
|
#include <tqdir.h>
|
|
#include <tqeventloop.h>
|
|
#include <tqtooltip.h>
|
|
#include <tqwidgetstack.h>
|
|
#include <tqfile.h>
|
|
#include <tqhbox.h>
|
|
|
|
#include <id3v1genres.h>
|
|
|
|
#include <time.h>
|
|
#include <math.h>
|
|
#include <dirent.h>
|
|
|
|
#include "playlist.h"
|
|
#include "playlistitem.h"
|
|
#include "playlistcollection.h"
|
|
#include "playlistsearch.h"
|
|
#include "mediafiles.h"
|
|
#include "collectionlist.h"
|
|
#include "filerenamer.h"
|
|
#include "actioncollection.h"
|
|
#include "tracksequencemanager.h"
|
|
#include "juk.h"
|
|
#include "tag.h"
|
|
#include "k3bexporter.h"
|
|
#include "upcomingplaylist.h"
|
|
#include "deletedialog.h"
|
|
#include "webimagefetcher.h"
|
|
#include "coverinfo.h"
|
|
#include "coverdialog.h"
|
|
#include "tagtransactionmanager.h"
|
|
#include "cache.h"
|
|
|
|
#define TStringToTQString(s) TQString::fromUtf8((s).toCString(true))
|
|
|
|
using namespace ActionCollection;
|
|
|
|
/**
|
|
* Just a shortcut of sorts.
|
|
*/
|
|
|
|
static bool manualResize()
|
|
{
|
|
return action<TDEToggleAction>("resizeColumnsManually")->isChecked();
|
|
}
|
|
|
|
/**
|
|
* A tooltip specialized to show full filenames over the file name column.
|
|
*/
|
|
|
|
class PlaylistToolTip : public TQToolTip
|
|
{
|
|
public:
|
|
PlaylistToolTip(TQWidget *parent, Playlist *playlist) :
|
|
TQToolTip(parent), m_playlist(playlist) {}
|
|
|
|
virtual void maybeTip(const TQPoint &p)
|
|
{
|
|
PlaylistItem *item = static_cast<PlaylistItem *>(m_playlist->itemAt(p));
|
|
|
|
if(!item)
|
|
return;
|
|
|
|
TQPoint contentsPosition = m_playlist->viewportToContents(p);
|
|
|
|
int column = m_playlist->header()->sectionAt(contentsPosition.x());
|
|
|
|
if(column == m_playlist->columnOffset() + PlaylistItem::FileNameColumn ||
|
|
item->cachedWidths()[column] > m_playlist->columnWidth(column) ||
|
|
(column == m_playlist->columnOffset() + PlaylistItem::CoverColumn &&
|
|
item->file().coverInfo()->hasCover()))
|
|
{
|
|
TQRect r = m_playlist->itemRect(item);
|
|
int headerPosition = m_playlist->header()->sectionPos(column);
|
|
r.setLeft(headerPosition);
|
|
r.setRight(headerPosition + m_playlist->header()->sectionSize(column));
|
|
|
|
if(column == m_playlist->columnOffset() + PlaylistItem::FileNameColumn)
|
|
tip(r, item->file().absFilePath());
|
|
else if(column == m_playlist->columnOffset() + PlaylistItem::CoverColumn) {
|
|
TQMimeSourceFactory *f = TQMimeSourceFactory::defaultFactory();
|
|
f->setImage("coverThumb",
|
|
TQImage(item->file().coverInfo()->pixmap(CoverInfo::Thumbnail).convertToImage()));
|
|
tip(r, "<center><img source=\"coverThumb\"/></center>");
|
|
}
|
|
else
|
|
tip(r, item->text(column));
|
|
}
|
|
}
|
|
|
|
private:
|
|
Playlist *m_playlist;
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Playlist::SharedSettings definition
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool Playlist::m_visibleChanged = false;
|
|
bool Playlist::m_shuttingDown = false;
|
|
|
|
/**
|
|
* Shared settings between the playlists.
|
|
*/
|
|
|
|
class Playlist::SharedSettings
|
|
{
|
|
public:
|
|
static SharedSettings *instance();
|
|
/**
|
|
* Sets the default column order to that of Playlist @param p.
|
|
*/
|
|
void setColumnOrder(const Playlist *l);
|
|
void toggleColumnVisible(int column);
|
|
void setInlineCompletionMode(TDEGlobalSettings::Completion mode);
|
|
|
|
/**
|
|
* Apply the settings.
|
|
*/
|
|
void apply(Playlist *l) const;
|
|
void sync() { writeConfig(); }
|
|
|
|
protected:
|
|
SharedSettings();
|
|
~SharedSettings() {}
|
|
|
|
private:
|
|
void writeConfig();
|
|
|
|
static SharedSettings *m_instance;
|
|
TQValueList<int> m_columnOrder;
|
|
TQValueVector<bool> m_columnsVisible;
|
|
TDEGlobalSettings::Completion m_inlineCompletion;
|
|
};
|
|
|
|
Playlist::SharedSettings *Playlist::SharedSettings::m_instance = 0;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Playlist::SharedSettings public members
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Playlist::SharedSettings *Playlist::SharedSettings::instance()
|
|
{
|
|
static SharedSettings settings;
|
|
return &settings;
|
|
}
|
|
|
|
void Playlist::SharedSettings::setColumnOrder(const Playlist *l)
|
|
{
|
|
if(!l)
|
|
return;
|
|
|
|
m_columnOrder.clear();
|
|
|
|
for(int i = l->columnOffset(); i < l->columns(); ++i)
|
|
m_columnOrder.append(l->header()->mapToIndex(i));
|
|
|
|
writeConfig();
|
|
}
|
|
|
|
void Playlist::SharedSettings::toggleColumnVisible(int column)
|
|
{
|
|
if(column >= int(m_columnsVisible.size()))
|
|
m_columnsVisible.resize(column + 1, true);
|
|
|
|
m_columnsVisible[column] = !m_columnsVisible[column];
|
|
|
|
writeConfig();
|
|
}
|
|
|
|
void Playlist::SharedSettings::setInlineCompletionMode(TDEGlobalSettings::Completion mode)
|
|
{
|
|
m_inlineCompletion = mode;
|
|
writeConfig();
|
|
}
|
|
|
|
|
|
void Playlist::SharedSettings::apply(Playlist *l) const
|
|
{
|
|
if(!l)
|
|
return;
|
|
|
|
int offset = l->columnOffset();
|
|
int i = 0;
|
|
for(TQValueListConstIterator<int> it = m_columnOrder.begin(); it != m_columnOrder.end(); ++it)
|
|
l->header()->moveSection(i++ + offset, *it + offset);
|
|
|
|
for(uint i = 0; i < m_columnsVisible.size(); i++) {
|
|
if(m_columnsVisible[i] && !l->isColumnVisible(i + offset))
|
|
l->showColumn(i + offset, false);
|
|
else if(!m_columnsVisible[i] && l->isColumnVisible(i + offset))
|
|
l->hideColumn(i + offset, false);
|
|
}
|
|
|
|
l->updateLeftColumn();
|
|
l->renameLineEdit()->setCompletionMode(m_inlineCompletion);
|
|
l->slotColumnResizeModeChanged();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Playlist::ShareSettings protected members
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Playlist::SharedSettings::SharedSettings()
|
|
{
|
|
TDEConfigGroup config(TDEGlobal::config(), "PlaylistShared");
|
|
|
|
bool resizeColumnsManually = config.readBoolEntry("ResizeColumnsManually", false);
|
|
action<TDEToggleAction>("resizeColumnsManually")->setChecked(resizeColumnsManually);
|
|
|
|
// save column order
|
|
m_columnOrder = config.readIntListEntry("ColumnOrder");
|
|
|
|
TQValueList<int> l = config.readIntListEntry("VisibleColumns");
|
|
|
|
if(l.isEmpty()) {
|
|
|
|
// Provide some default values for column visibility if none were
|
|
// read from the configuration file.
|
|
|
|
for(int i = 0; i <= PlaylistItem::lastColumn(); i++) {
|
|
switch(i) {
|
|
case PlaylistItem::BitrateColumn:
|
|
case PlaylistItem::CommentColumn:
|
|
case PlaylistItem::FileNameColumn:
|
|
case PlaylistItem::FullPathColumn:
|
|
m_columnsVisible.append(false);
|
|
break;
|
|
default:
|
|
m_columnsVisible.append(true);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Convert the int list into a bool list.
|
|
|
|
m_columnsVisible.resize(l.size(), true);
|
|
uint i = 0;
|
|
for(TQValueList<int>::Iterator it = l.begin(); it != l.end(); ++it) {
|
|
if(! bool(*it))
|
|
m_columnsVisible[i] = bool(*it);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
m_inlineCompletion = TDEGlobalSettings::Completion(
|
|
config.readNumEntry("InlineCompletionMode", TDEGlobalSettings::CompletionAuto));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Playlist::SharedSettings private members
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Playlist::SharedSettings::writeConfig()
|
|
{
|
|
TDEConfigGroup config(TDEGlobal::config(), "PlaylistShared");
|
|
config.writeEntry("ColumnOrder", m_columnOrder);
|
|
|
|
TQValueList<int> l;
|
|
for(uint i = 0; i < m_columnsVisible.size(); i++)
|
|
l.append(int(m_columnsVisible[i]));
|
|
|
|
config.writeEntry("VisibleColumns", l);
|
|
config.writeEntry("InlineCompletionMode", m_inlineCompletion);
|
|
|
|
config.writeEntry("ResizeColumnsManually", manualResize());
|
|
|
|
TDEGlobal::config()->sync();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// public members
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
PlaylistItemList Playlist::m_history;
|
|
TQMap<int, PlaylistItem *> Playlist::m_backMenuItems;
|
|
int Playlist::m_leftColumn = 0;
|
|
|
|
Playlist::Playlist(PlaylistCollection *collection, const TQString &name,
|
|
const TQString &iconName) :
|
|
TDEListView(collection->playlistStack(), name.latin1()),
|
|
m_collection(collection),
|
|
m_fetcher(new WebImageFetcher(TQT_TQOBJECT(this))),
|
|
m_selectedCount(0),
|
|
m_allowDuplicates(false),
|
|
m_polished(false),
|
|
m_applySharedSettings(true),
|
|
m_columnWidthModeChanged(false),
|
|
m_disableColumnWidthUpdates(true),
|
|
m_time(0),
|
|
m_widthsDirty(true),
|
|
m_searchEnabled(true),
|
|
m_lastSelected(0),
|
|
m_playlistName(name),
|
|
m_rmbMenu(0),
|
|
m_toolTip(0),
|
|
m_blockDataChanged(false)
|
|
{
|
|
setup();
|
|
collection->setupPlaylist(this, iconName);
|
|
}
|
|
|
|
Playlist::Playlist(PlaylistCollection *collection, const PlaylistItemList &items,
|
|
const TQString &name, const TQString &iconName) :
|
|
TDEListView(collection->playlistStack(), name.latin1()),
|
|
m_collection(collection),
|
|
m_fetcher(new WebImageFetcher(TQT_TQOBJECT(this))),
|
|
m_selectedCount(0),
|
|
m_allowDuplicates(false),
|
|
m_polished(false),
|
|
m_applySharedSettings(true),
|
|
m_columnWidthModeChanged(false),
|
|
m_disableColumnWidthUpdates(true),
|
|
m_time(0),
|
|
m_widthsDirty(true),
|
|
m_searchEnabled(true),
|
|
m_lastSelected(0),
|
|
m_playlistName(name),
|
|
m_rmbMenu(0),
|
|
m_toolTip(0),
|
|
m_blockDataChanged(false)
|
|
{
|
|
setup();
|
|
collection->setupPlaylist(this, iconName);
|
|
createItems(items);
|
|
}
|
|
|
|
Playlist::Playlist(PlaylistCollection *collection, const TQFileInfo &playlistFile,
|
|
const TQString &iconName) :
|
|
TDEListView(collection->playlistStack()),
|
|
m_collection(collection),
|
|
m_fetcher(new WebImageFetcher(TQT_TQOBJECT(this))),
|
|
m_selectedCount(0),
|
|
m_allowDuplicates(false),
|
|
m_polished(false),
|
|
m_applySharedSettings(true),
|
|
m_columnWidthModeChanged(false),
|
|
m_disableColumnWidthUpdates(true),
|
|
m_time(0),
|
|
m_widthsDirty(true),
|
|
m_searchEnabled(true),
|
|
m_lastSelected(0),
|
|
m_fileName(playlistFile.absFilePath()),
|
|
m_rmbMenu(0),
|
|
m_toolTip(0),
|
|
m_blockDataChanged(false)
|
|
{
|
|
setup();
|
|
loadFile(m_fileName, playlistFile);
|
|
collection->setupPlaylist(this, iconName);
|
|
}
|
|
|
|
Playlist::Playlist(PlaylistCollection *collection, bool delaySetup) :
|
|
TDEListView(collection->playlistStack()),
|
|
m_collection(collection),
|
|
m_fetcher(new WebImageFetcher(TQT_TQOBJECT(this))),
|
|
m_selectedCount(0),
|
|
m_allowDuplicates(false),
|
|
m_polished(false),
|
|
m_applySharedSettings(true),
|
|
m_columnWidthModeChanged(false),
|
|
m_disableColumnWidthUpdates(true),
|
|
m_time(0),
|
|
m_widthsDirty(true),
|
|
m_searchEnabled(true),
|
|
m_lastSelected(0),
|
|
m_rmbMenu(0),
|
|
m_toolTip(0),
|
|
m_blockDataChanged(false)
|
|
{
|
|
setup();
|
|
|
|
if(!delaySetup)
|
|
collection->setupPlaylist(this, "audio-midi");
|
|
}
|
|
|
|
Playlist::~Playlist()
|
|
{
|
|
// clearItem() will take care of removing the items from the history,
|
|
// so call clearItems() to make sure it happens.
|
|
|
|
clearItems(items());
|
|
|
|
delete m_toolTip;
|
|
|
|
// Select a different playlist if we're the selected one
|
|
|
|
if(isVisible() && this != CollectionList::instance())
|
|
m_collection->raise(CollectionList::instance());
|
|
|
|
if(!m_shuttingDown)
|
|
m_collection->removePlaylist(this);
|
|
}
|
|
|
|
TQString Playlist::name() const
|
|
{
|
|
if(m_playlistName.isNull())
|
|
return m_fileName.section(TQDir::separator(), -1).section('.', 0, -2);
|
|
else
|
|
return m_playlistName;
|
|
}
|
|
|
|
FileHandle Playlist::currentFile() const
|
|
{
|
|
return playingItem() ? playingItem()->file() : FileHandle::null();
|
|
}
|
|
|
|
int Playlist::time() const
|
|
{
|
|
// Since this method gets a lot of traffic, let's optimize for such.
|
|
|
|
if(!m_addTime.isEmpty()) {
|
|
for(PlaylistItemList::ConstIterator it = m_addTime.begin();
|
|
it != m_addTime.end(); ++it)
|
|
{
|
|
if(*it)
|
|
m_time += (*it)->file().tag()->seconds();
|
|
}
|
|
|
|
m_addTime.clear();
|
|
}
|
|
|
|
if(!m_subtractTime.isEmpty()) {
|
|
for(PlaylistItemList::ConstIterator it = m_subtractTime.begin();
|
|
it != m_subtractTime.end(); ++it)
|
|
{
|
|
if(*it)
|
|
m_time -= (*it)->file().tag()->seconds();
|
|
}
|
|
|
|
m_subtractTime.clear();
|
|
}
|
|
|
|
return m_time;
|
|
}
|
|
|
|
void Playlist::playFirst()
|
|
{
|
|
TrackSequenceManager::instance()->setNextItem(static_cast<PlaylistItem *>(
|
|
TQListViewItemIterator(const_cast<Playlist *>(this), TQListViewItemIterator::Visible).current()));
|
|
action("forward")->activate();
|
|
}
|
|
|
|
void Playlist::playNextAlbum()
|
|
{
|
|
PlaylistItem *current = TrackSequenceManager::instance()->currentItem();
|
|
if(!current)
|
|
return; // No next album if we're not already playing.
|
|
|
|
TQString currentAlbum = current->file().tag()->album();
|
|
current = TrackSequenceManager::instance()->nextItem();
|
|
|
|
while(current && current->file().tag()->album() == currentAlbum)
|
|
current = TrackSequenceManager::instance()->nextItem();
|
|
|
|
TrackSequenceManager::instance()->setNextItem(current);
|
|
action("forward")->activate();
|
|
}
|
|
|
|
void Playlist::playNext()
|
|
{
|
|
TrackSequenceManager::instance()->setCurrentPlaylist(this);
|
|
setPlaying(TrackSequenceManager::instance()->nextItem());
|
|
}
|
|
|
|
void Playlist::stop()
|
|
{
|
|
m_history.clear();
|
|
setPlaying(0);
|
|
}
|
|
|
|
void Playlist::playPrevious()
|
|
{
|
|
if(!playingItem())
|
|
return;
|
|
|
|
bool random = action("randomPlay") && action<TDEToggleAction>("randomPlay")->isChecked();
|
|
|
|
PlaylistItem *previous = 0;
|
|
|
|
if(random && !m_history.isEmpty()) {
|
|
PlaylistItemList::Iterator last = m_history.fromLast();
|
|
previous = *last;
|
|
m_history.remove(last);
|
|
}
|
|
else {
|
|
m_history.clear();
|
|
previous = TrackSequenceManager::instance()->previousItem();
|
|
}
|
|
|
|
if(!previous)
|
|
previous = static_cast<PlaylistItem *>(playingItem()->itemAbove());
|
|
|
|
setPlaying(previous, false);
|
|
}
|
|
|
|
void Playlist::setName(const TQString &n)
|
|
{
|
|
m_collection->addNameToDict(n);
|
|
m_collection->removeNameFromDict(m_playlistName);
|
|
|
|
m_playlistName = n;
|
|
emit signalNameChanged(m_playlistName);
|
|
}
|
|
|
|
void Playlist::save()
|
|
{
|
|
if(m_fileName.isEmpty())
|
|
return saveAs();
|
|
|
|
TQFile file(m_fileName);
|
|
|
|
if(!file.open(IO_WriteOnly))
|
|
return KMessageBox::error(this, i18n("Could not save to file %1.").arg(m_fileName));
|
|
|
|
TQTextStream stream(&file);
|
|
|
|
TQStringList fileList = files();
|
|
|
|
for(TQStringList::Iterator it = fileList.begin(); it != fileList.end(); ++it)
|
|
stream << *it << endl;
|
|
|
|
file.close();
|
|
}
|
|
|
|
void Playlist::saveAs()
|
|
{
|
|
m_collection->removeFileFromDict(m_fileName);
|
|
|
|
m_fileName = MediaFiles::savePlaylistDialog(name(), this);
|
|
|
|
if(!m_fileName.isEmpty()) {
|
|
m_collection->addFileToDict(m_fileName);
|
|
|
|
// If there's no playlist name set, use the file name.
|
|
if(m_playlistName.isEmpty())
|
|
emit signalNameChanged(name());
|
|
save();
|
|
}
|
|
}
|
|
|
|
void Playlist::clearItem(PlaylistItem *item, bool emitChanged)
|
|
{
|
|
emit signalAboutToRemove(item);
|
|
m_members.remove(item->file().absFilePath());
|
|
m_search.clearItem(item);
|
|
|
|
m_history.remove(item);
|
|
m_addTime.remove(item);
|
|
m_subtractTime.remove(item);
|
|
|
|
delete item;
|
|
if(emitChanged)
|
|
dataChanged();
|
|
}
|
|
|
|
void Playlist::clearItems(const PlaylistItemList &items)
|
|
{
|
|
m_blockDataChanged = true;
|
|
|
|
for(PlaylistItemList::ConstIterator it = items.begin(); it != items.end(); ++it)
|
|
clearItem(*it, false);
|
|
|
|
m_blockDataChanged = false;
|
|
|
|
dataChanged();
|
|
}
|
|
|
|
PlaylistItem *Playlist::playingItem() // static
|
|
{
|
|
return PlaylistItem::playingItems().isEmpty() ? 0 : PlaylistItem::playingItems().front();
|
|
}
|
|
|
|
TQStringList Playlist::files() const
|
|
{
|
|
TQStringList list;
|
|
|
|
for(TQListViewItemIterator it(const_cast<Playlist *>(this)); it.current(); ++it)
|
|
list.append(static_cast<PlaylistItem *>(*it)->file().absFilePath());
|
|
|
|
return list;
|
|
}
|
|
|
|
PlaylistItemList Playlist::items()
|
|
{
|
|
return items(TQListViewItemIterator::IteratorFlag(0));
|
|
}
|
|
|
|
PlaylistItemList Playlist::visibleItems()
|
|
{
|
|
return items(TQListViewItemIterator::Visible);
|
|
}
|
|
|
|
PlaylistItemList Playlist::selectedItems()
|
|
{
|
|
PlaylistItemList list;
|
|
|
|
switch(m_selectedCount) {
|
|
case 0:
|
|
break;
|
|
// case 1:
|
|
// list.append(m_lastSelected);
|
|
// break;
|
|
default:
|
|
list = items(TQListViewItemIterator::IteratorFlag(TQListViewItemIterator::Selected |
|
|
TQListViewItemIterator::Visible));
|
|
break;
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
PlaylistItem *Playlist::firstChild() const
|
|
{
|
|
return static_cast<PlaylistItem *>(TDEListView::firstChild());
|
|
}
|
|
|
|
void Playlist::updateLeftColumn()
|
|
{
|
|
int newLeftColumn = leftMostVisibleColumn();
|
|
|
|
if(m_leftColumn != newLeftColumn) {
|
|
updatePlaying();
|
|
m_leftColumn = newLeftColumn;
|
|
}
|
|
}
|
|
|
|
void Playlist::setItemsVisible(const PlaylistItemList &items, bool visible) // static
|
|
{
|
|
m_visibleChanged = true;
|
|
for(PlaylistItemList::ConstIterator it = items.begin(); it != items.end(); ++it)
|
|
(*it)->setVisible(visible);
|
|
}
|
|
|
|
void Playlist::setSearch(const PlaylistSearch &s)
|
|
{
|
|
m_search = s;
|
|
|
|
if(!m_searchEnabled)
|
|
return;
|
|
|
|
setItemsVisible(s.matchedItems(), true);
|
|
setItemsVisible(s.unmatchedItems(), false);
|
|
|
|
TrackSequenceManager::instance()->iterator()->playlistChanged();
|
|
}
|
|
|
|
void Playlist::setSearchEnabled(bool enabled)
|
|
{
|
|
if(m_searchEnabled == enabled)
|
|
return;
|
|
|
|
m_searchEnabled = enabled;
|
|
|
|
if(enabled) {
|
|
setItemsVisible(m_search.matchedItems(), true);
|
|
setItemsVisible(m_search.unmatchedItems(), false);
|
|
}
|
|
else
|
|
setItemsVisible(items(), true);
|
|
}
|
|
|
|
void Playlist::markItemSelected(PlaylistItem *item, bool selected)
|
|
{
|
|
if(selected && !item->isSelected()) {
|
|
m_selectedCount++;
|
|
m_lastSelected = item;
|
|
}
|
|
else if(!selected && item->isSelected())
|
|
m_selectedCount--;
|
|
}
|
|
|
|
void Playlist::synchronizePlayingItems(const PlaylistList &sources, bool setMaster)
|
|
{
|
|
for(PlaylistList::ConstIterator it = sources.begin(); it != sources.end(); ++it) {
|
|
if((*it)->playing()) {
|
|
CollectionListItem *base = playingItem()->collectionItem();
|
|
for(TQListViewItemIterator itemIt(this); itemIt.current(); ++itemIt) {
|
|
PlaylistItem *item = static_cast<PlaylistItem *>(itemIt.current());
|
|
if(base == item->collectionItem()) {
|
|
item->setPlaying(true, setMaster);
|
|
PlaylistItemList playing = PlaylistItem::playingItems();
|
|
TrackSequenceManager::instance()->setCurrent(item);
|
|
return;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// public slots
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Playlist::copy()
|
|
{
|
|
kapp->clipboard()->setData(dragObject(0), TQClipboard::Clipboard);
|
|
}
|
|
|
|
void Playlist::paste()
|
|
{
|
|
decode(kapp->clipboard()->data(), static_cast<PlaylistItem *>(currentItem()));
|
|
}
|
|
|
|
void Playlist::clear()
|
|
{
|
|
PlaylistItemList l = selectedItems();
|
|
if(l.isEmpty())
|
|
l = items();
|
|
|
|
clearItems(l);
|
|
}
|
|
|
|
void Playlist::slotRefresh()
|
|
{
|
|
PlaylistItemList l = selectedItems();
|
|
if(l.isEmpty())
|
|
l = visibleItems();
|
|
|
|
TDEApplication::setOverrideCursor(TQt::waitCursor);
|
|
for(PlaylistItemList::Iterator it = l.begin(); it != l.end(); ++it) {
|
|
(*it)->refreshFromDisk();
|
|
|
|
if(!(*it)->file().tag() || !(*it)->file().fileInfo().exists()) {
|
|
kdDebug(65432) << "Error while trying to refresh the tag. "
|
|
<< "This file has probably been removed."
|
|
<< endl;
|
|
clearItem((*it)->collectionItem());
|
|
}
|
|
|
|
processEvents();
|
|
}
|
|
TDEApplication::restoreOverrideCursor();
|
|
}
|
|
|
|
void Playlist::slotRenameFile()
|
|
{
|
|
FileRenamer renamer;
|
|
PlaylistItemList items = selectedItems();
|
|
|
|
if(items.isEmpty())
|
|
return;
|
|
|
|
emit signalEnableDirWatch(false);
|
|
|
|
m_blockDataChanged = true;
|
|
renamer.rename(items);
|
|
m_blockDataChanged = false;
|
|
dataChanged();
|
|
|
|
emit signalEnableDirWatch(true);
|
|
}
|
|
|
|
void Playlist::slotViewCover()
|
|
{
|
|
PlaylistItemList items = selectedItems();
|
|
if (items.isEmpty())
|
|
return;
|
|
for(PlaylistItemList::Iterator it = items.begin(); it != items.end(); ++it)
|
|
(*it)->file().coverInfo()->popup();
|
|
}
|
|
|
|
void Playlist::slotRemoveCover()
|
|
{
|
|
PlaylistItemList items = selectedItems();
|
|
if(items.isEmpty())
|
|
return;
|
|
int button = KMessageBox::warningContinueCancel(this,
|
|
i18n("Are you sure you want to delete these covers?"),
|
|
TQString(),
|
|
i18n("&Delete Covers"));
|
|
if(button == KMessageBox::Continue)
|
|
refreshAlbums(items);
|
|
}
|
|
|
|
void Playlist::slotShowCoverManager()
|
|
{
|
|
static CoverDialog *managerDialog = 0;
|
|
|
|
if(!managerDialog)
|
|
managerDialog = new CoverDialog(this);
|
|
|
|
managerDialog->show();
|
|
}
|
|
|
|
unsigned int Playlist::eligibleCoverItems(const PlaylistItemList &items)
|
|
{
|
|
// This used to count the number of tracks with an artist and album, that
|
|
// is not strictly required anymore. This may prove useful in the future
|
|
// so I'm leaving it in for now, right now we just mark every item as
|
|
// eligible.
|
|
|
|
return items.count();
|
|
}
|
|
|
|
void Playlist::slotAddCover(bool retrieveLocal)
|
|
{
|
|
PlaylistItemList items = selectedItems();
|
|
|
|
if(items.isEmpty())
|
|
return;
|
|
|
|
if(eligibleCoverItems(items) == 0) {
|
|
// No items in the list can be assigned a cover, inform the user and
|
|
// bail.
|
|
|
|
// KDE 4.0 Fix this string.
|
|
KMessageBox::sorry(this, i18n("None of the items you have selected can "
|
|
"be assigned a cover. A track must have both the Artist "
|
|
"and Album tags set to be assigned a cover."));
|
|
|
|
return;
|
|
}
|
|
|
|
TQPixmap newCover;
|
|
|
|
if(retrieveLocal) {
|
|
KURL file = KFileDialog::getImageOpenURL(
|
|
":homedir", this, i18n("Select Cover Image File"));
|
|
newCover = TQPixmap(file.directory() + "/" + file.fileName());
|
|
}
|
|
else {
|
|
m_fetcher->setFile((*items.begin())->file());
|
|
m_fetcher->chooseCover();
|
|
return;
|
|
}
|
|
|
|
if(newCover.isNull())
|
|
return;
|
|
|
|
TQString artist = items.front()->file().tag()->artist();
|
|
TQString album = items.front()->file().tag()->album();
|
|
|
|
coverKey newId = CoverManager::addCover(newCover, artist, album);
|
|
refreshAlbums(items, newId);
|
|
}
|
|
|
|
// Called when image fetcher has added a new cover.
|
|
void Playlist::slotCoverChanged(int coverId)
|
|
{
|
|
kdDebug(65432) << "Refreshing information for newly changed covers.\n";
|
|
refreshAlbums(selectedItems(), coverId);
|
|
}
|
|
|
|
void Playlist::slotGuessTagInfo(TagGuesser::Type type)
|
|
{
|
|
TDEApplication::setOverrideCursor(TQt::waitCursor);
|
|
PlaylistItemList items = selectedItems();
|
|
setDynamicListsFrozen(true);
|
|
|
|
m_blockDataChanged = true;
|
|
|
|
for(PlaylistItemList::Iterator it = items.begin(); it != items.end(); ++it) {
|
|
(*it)->guessTagInfo(type);
|
|
processEvents();
|
|
}
|
|
|
|
// MusicBrainz queries automatically commit at this point. What would
|
|
// be nice is having a signal emitted when the last query is completed.
|
|
|
|
if(type == TagGuesser::FileName)
|
|
TagTransactionManager::instance()->commit();
|
|
|
|
m_blockDataChanged = false;
|
|
|
|
dataChanged();
|
|
setDynamicListsFrozen(false);
|
|
TDEApplication::restoreOverrideCursor();
|
|
}
|
|
|
|
void Playlist::slotReload()
|
|
{
|
|
TQFileInfo fileInfo(m_fileName);
|
|
if(!fileInfo.exists() || !fileInfo.isFile() || !fileInfo.isReadable())
|
|
return;
|
|
|
|
clearItems(items());
|
|
loadFile(m_fileName, fileInfo);
|
|
}
|
|
|
|
void Playlist::slotWeightDirty(int column)
|
|
{
|
|
if(column < 0) {
|
|
m_weightDirty.clear();
|
|
for(int i = 0; i < columns(); i++) {
|
|
if(isColumnVisible(i))
|
|
m_weightDirty.append(i);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(m_weightDirty.find(column) == m_weightDirty.end())
|
|
m_weightDirty.append(column);
|
|
}
|
|
|
|
void Playlist::slotShowPlaying()
|
|
{
|
|
if(!playingItem())
|
|
return;
|
|
|
|
Playlist *l = playingItem()->playlist();
|
|
|
|
l->clearSelection();
|
|
|
|
// Raise the playlist before selecting the items otherwise the tag editor
|
|
// will not update when it gets the selectionChanged() notification
|
|
// because it will think the user is choosing a different playlist but not
|
|
// selecting a different item.
|
|
|
|
m_collection->raise(l);
|
|
|
|
l->setSelected(playingItem(), true);
|
|
l->setCurrentItem(playingItem());
|
|
l->ensureItemVisible(playingItem());
|
|
}
|
|
|
|
void Playlist::slotColumnResizeModeChanged()
|
|
{
|
|
if(manualResize())
|
|
setHScrollBarMode(Auto);
|
|
else
|
|
setHScrollBarMode(AlwaysOff);
|
|
|
|
if(!manualResize())
|
|
slotUpdateColumnWidths();
|
|
|
|
SharedSettings::instance()->sync();
|
|
}
|
|
|
|
void Playlist::dataChanged()
|
|
{
|
|
if(m_blockDataChanged)
|
|
return;
|
|
PlaylistInterface::dataChanged();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// protected members
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Playlist::removeFromDisk(const PlaylistItemList &items)
|
|
{
|
|
if(isVisible() && !items.isEmpty()) {
|
|
|
|
TQStringList files;
|
|
for(PlaylistItemList::ConstIterator it = items.begin(); it != items.end(); ++it)
|
|
files.append((*it)->file().absFilePath());
|
|
|
|
DeleteDialog dialog(this);
|
|
|
|
m_blockDataChanged = true;
|
|
|
|
if(dialog.confirmDeleteList(files)) {
|
|
bool shouldDelete = dialog.shouldDelete();
|
|
TQStringList errorFiles;
|
|
|
|
for(PlaylistItemList::ConstIterator it = items.begin(); it != items.end(); ++it) {
|
|
if(playingItem() == *it)
|
|
action("forward")->activate();
|
|
|
|
TQString removePath = (*it)->file().absFilePath();
|
|
if((!shouldDelete && TDEIO::NetAccess::synchronousRun(TDEIO::trash(removePath), this)) ||
|
|
(shouldDelete && TQFile::remove(removePath)))
|
|
{
|
|
CollectionList::instance()->clearItem((*it)->collectionItem());
|
|
}
|
|
else
|
|
errorFiles.append((*it)->file().absFilePath());
|
|
}
|
|
|
|
if(!errorFiles.isEmpty()) {
|
|
TQString errorMsg = shouldDelete ?
|
|
i18n("Could not delete these files") :
|
|
i18n("Could not move these files to the Trash");
|
|
KMessageBox::errorList(this, errorMsg, errorFiles);
|
|
}
|
|
}
|
|
|
|
m_blockDataChanged = false;
|
|
|
|
dataChanged();
|
|
}
|
|
}
|
|
|
|
TQDragObject *Playlist::dragObject(TQWidget *parent)
|
|
{
|
|
PlaylistItemList items = selectedItems();
|
|
KURL::List urls;
|
|
for(PlaylistItemList::Iterator it = items.begin(); it != items.end(); ++it) {
|
|
KURL url;
|
|
url.setPath((*it)->file().absFilePath());
|
|
urls.append(url);
|
|
}
|
|
|
|
KURLDrag *drag = new KURLDrag(urls, parent, "Playlist Items");
|
|
drag->setPixmap(BarIcon("audio-x-generic"));
|
|
|
|
return drag;
|
|
}
|
|
|
|
void Playlist::contentsDragEnterEvent(TQDragEnterEvent *e)
|
|
{
|
|
TDEListView::contentsDragEnterEvent(e);
|
|
|
|
if(CoverDrag::canDecode(e)) {
|
|
setDropHighlighter(true);
|
|
setDropVisualizer(false);
|
|
|
|
e->accept();
|
|
return;
|
|
}
|
|
|
|
setDropHighlighter(false);
|
|
setDropVisualizer(true);
|
|
|
|
KURL::List urls;
|
|
if(!KURLDrag::decode(e, urls) || urls.isEmpty()) {
|
|
e->ignore();
|
|
return;
|
|
}
|
|
|
|
e->accept();
|
|
return;
|
|
}
|
|
|
|
bool Playlist::acceptDrag(TQDropEvent *e) const
|
|
{
|
|
return CoverDrag::canDecode(e) || KURLDrag::canDecode(e);
|
|
}
|
|
|
|
bool Playlist::canDecode(TQMimeSource *s)
|
|
{
|
|
KURL::List urls;
|
|
|
|
if(CoverDrag::canDecode(s))
|
|
return true;
|
|
|
|
return KURLDrag::decode(s, urls) && !urls.isEmpty();
|
|
}
|
|
|
|
void Playlist::decode(TQMimeSource *s, PlaylistItem *item)
|
|
{
|
|
KURL::List urls;
|
|
|
|
if(!KURLDrag::decode(s, urls) || urls.isEmpty())
|
|
return;
|
|
|
|
// handle dropped images
|
|
|
|
if(!MediaFiles::isMediaFile(urls.front().path())) {
|
|
|
|
TQString file;
|
|
|
|
if(urls.front().isLocalFile())
|
|
file = urls.front().path();
|
|
else
|
|
TDEIO::NetAccess::download(urls.front(), file, 0);
|
|
|
|
KMimeType::Ptr mimeType = KMimeType::findByPath(file);
|
|
|
|
if(item && mimeType->name().startsWith("image/")) {
|
|
item->file().coverInfo()->setCover(TQImage(file));
|
|
refreshAlbum(item->file().tag()->artist(),
|
|
item->file().tag()->album());
|
|
}
|
|
|
|
TDEIO::NetAccess::removeTempFile(file);
|
|
}
|
|
|
|
TQStringList fileList;
|
|
|
|
for(KURL::List::Iterator it = urls.begin(); it != urls.end(); ++it)
|
|
fileList += MediaFiles::convertURLsToLocal((*it).path(), this);
|
|
|
|
addFiles(fileList, item);
|
|
}
|
|
|
|
bool Playlist::eventFilter(TQObject *watched, TQEvent *e)
|
|
{
|
|
if(watched == header()) {
|
|
switch(e->type()) {
|
|
case TQEvent::MouseMove:
|
|
{
|
|
if((TQT_TQMOUSEEVENT(e)->state() & TQt::LeftButton) == TQt::LeftButton &&
|
|
!action<TDEToggleAction>("resizeColumnsManually")->isChecked())
|
|
{
|
|
m_columnWidthModeChanged = true;
|
|
|
|
action<TDEToggleAction>("resizeColumnsManually")->setChecked(true);
|
|
slotColumnResizeModeChanged();
|
|
}
|
|
|
|
break;
|
|
}
|
|
case TQEvent::MouseButtonPress:
|
|
{
|
|
if(TQT_TQMOUSEEVENT(e)->button() == TQt::RightButton)
|
|
m_headerMenu->popup(TQCursor::pos());
|
|
|
|
break;
|
|
}
|
|
case TQEvent::MouseButtonRelease:
|
|
{
|
|
if(m_columnWidthModeChanged) {
|
|
m_columnWidthModeChanged = false;
|
|
notifyUserColumnWidthModeChanged();
|
|
}
|
|
|
|
if(!manualResize() && m_widthsDirty)
|
|
TQTimer::singleShot(0, this, TQT_SLOT(slotUpdateColumnWidths()));
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return TDEListView::eventFilter(watched, e);
|
|
}
|
|
|
|
void Playlist::keyPressEvent(TQKeyEvent *event)
|
|
{
|
|
if(event->key() == Key_Up) {
|
|
TQListViewItemIterator selected(this, TQListViewItemIterator::IteratorFlag(
|
|
TQListViewItemIterator::Selected |
|
|
TQListViewItemIterator::Visible));
|
|
if(selected.current()) {
|
|
TQListViewItemIterator visible(this, TQListViewItemIterator::IteratorFlag(
|
|
TQListViewItemIterator::Visible));
|
|
if(selected.current() == visible.current())
|
|
TDEApplication::postEvent(parent(), new FocusUpEvent);
|
|
}
|
|
|
|
}
|
|
|
|
TDEListView::keyPressEvent(event);
|
|
}
|
|
|
|
void Playlist::contentsDropEvent(TQDropEvent *e)
|
|
{
|
|
TQPoint vp = contentsToViewport(e->pos());
|
|
PlaylistItem *item = static_cast<PlaylistItem *>(itemAt(vp));
|
|
|
|
// First see if we're dropping a cover, if so we can get it out of the
|
|
// way early.
|
|
if(item && CoverDrag::canDecode(e)) {
|
|
coverKey id;
|
|
CoverDrag::decode(e, id);
|
|
|
|
// If the item we dropped on is selected, apply cover to all selected
|
|
// items, otherwise just apply to the dropped item.
|
|
|
|
if(item->isSelected()) {
|
|
PlaylistItemList selItems = selectedItems();
|
|
for(PlaylistItemList::Iterator it = selItems.begin(); it != selItems.end(); ++it) {
|
|
(*it)->file().coverInfo()->setCoverId(id);
|
|
(*it)->refresh();
|
|
}
|
|
}
|
|
else {
|
|
item->file().coverInfo()->setCoverId(id);
|
|
item->refresh();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// When dropping on the upper half of an item, insert before this item.
|
|
// This is what the user expects, and also allows the insertion at
|
|
// top of the list
|
|
|
|
if(!item)
|
|
item = static_cast<PlaylistItem *>(lastItem());
|
|
else if(vp.y() < item->itemPos() + item->height() / 2)
|
|
item = static_cast<PlaylistItem *>(item->itemAbove());
|
|
|
|
m_blockDataChanged = true;
|
|
|
|
if(e->source() == this) {
|
|
|
|
// Since we're trying to arrange things manually, turn off sorting.
|
|
|
|
setSorting(columns() + 1);
|
|
|
|
TQPtrList<TQListViewItem> items = TDEListView::selectedItems();
|
|
|
|
for(TQPtrListIterator<TQListViewItem> it(items); it.current(); ++it) {
|
|
if(!item) {
|
|
|
|
// Insert the item at the top of the list. This is a bit ugly,
|
|
// but I don't see another way.
|
|
|
|
takeItem(it.current());
|
|
insertItem(it.current());
|
|
}
|
|
else
|
|
it.current()->moveItem(item);
|
|
|
|
item = static_cast<PlaylistItem *>(it.current());
|
|
}
|
|
}
|
|
else
|
|
decode(e, item);
|
|
|
|
m_blockDataChanged = false;
|
|
|
|
dataChanged();
|
|
emit signalPlaylistItemsDropped(this);
|
|
TDEListView::contentsDropEvent(e);
|
|
}
|
|
|
|
void Playlist::contentsMouseDoubleClickEvent(TQMouseEvent *e)
|
|
{
|
|
// Filter out non left button double clicks, that way users don't have the
|
|
// weird experience of switching songs from a double right-click.
|
|
|
|
if(e->button() == TQt::LeftButton)
|
|
TDEListView::contentsMouseDoubleClickEvent(e);
|
|
}
|
|
|
|
void Playlist::showEvent(TQShowEvent *e)
|
|
{
|
|
if(m_applySharedSettings) {
|
|
SharedSettings::instance()->apply(this);
|
|
m_applySharedSettings = false;
|
|
}
|
|
TDEListView::showEvent(e);
|
|
}
|
|
|
|
void Playlist::applySharedSettings()
|
|
{
|
|
m_applySharedSettings = true;
|
|
}
|
|
|
|
void Playlist::read(TQDataStream &s)
|
|
{
|
|
TQString buffer;
|
|
|
|
s >> m_playlistName
|
|
>> m_fileName;
|
|
|
|
TQStringList files;
|
|
s >> files;
|
|
|
|
TQListViewItem *after = 0;
|
|
|
|
m_blockDataChanged = true;
|
|
|
|
for(TQStringList::ConstIterator it = files.begin(); it != files.end(); ++it)
|
|
after = createItem(FileHandle(*it), after, false);
|
|
|
|
m_blockDataChanged = false;
|
|
|
|
dataChanged();
|
|
m_collection->setupPlaylist(this, "audio-midi");
|
|
}
|
|
|
|
void Playlist::viewportPaintEvent(TQPaintEvent *pe)
|
|
{
|
|
// If there are columns that need to be updated, well, update them.
|
|
|
|
if(!m_weightDirty.isEmpty() && !manualResize())
|
|
{
|
|
calculateColumnWeights();
|
|
slotUpdateColumnWidths();
|
|
}
|
|
|
|
TDEListView::viewportPaintEvent(pe);
|
|
}
|
|
|
|
void Playlist::viewportResizeEvent(TQResizeEvent *re)
|
|
{
|
|
// If the width of the view has changed, manually update the column
|
|
// widths.
|
|
|
|
if(re->size().width() != re->oldSize().width() && !manualResize())
|
|
slotUpdateColumnWidths();
|
|
|
|
TDEListView::viewportResizeEvent(re);
|
|
}
|
|
|
|
void Playlist::insertItem(TQListViewItem *item)
|
|
{
|
|
// Because we're called from the PlaylistItem ctor, item may not be a
|
|
// PlaylistItem yet (it would be TQListViewItem when being inserted. But,
|
|
// it will be a PlaylistItem by the time it matters, but be careful if
|
|
// you need to use the PlaylistItem from here.
|
|
|
|
m_addTime.append(static_cast<PlaylistItem *>(item));
|
|
TDEListView::insertItem(item);
|
|
}
|
|
|
|
void Playlist::takeItem(TQListViewItem *item)
|
|
{
|
|
// See the warning in Playlist::insertItem.
|
|
|
|
m_subtractTime.append(static_cast<PlaylistItem *>(item));
|
|
TDEListView::takeItem(item);
|
|
}
|
|
|
|
void Playlist::addColumn(const TQString &label)
|
|
{
|
|
slotWeightDirty(columns());
|
|
TDEListView::addColumn(label, 30);
|
|
}
|
|
|
|
PlaylistItem *Playlist::createItem(const FileHandle &file,
|
|
TQListViewItem *after, bool emitChanged)
|
|
{
|
|
return createItem<PlaylistItem, CollectionListItem, CollectionList>(file, after, emitChanged);
|
|
}
|
|
|
|
void Playlist::createItems(const PlaylistItemList &siblings, PlaylistItem *after)
|
|
{
|
|
createItems<CollectionListItem, PlaylistItem, PlaylistItem>(siblings, after);
|
|
}
|
|
|
|
void Playlist::addFiles(const TQStringList &files, PlaylistItem *after)
|
|
{
|
|
if(!after)
|
|
after = static_cast<PlaylistItem *>(lastItem());
|
|
|
|
TDEApplication::setOverrideCursor(TQt::waitCursor);
|
|
|
|
m_blockDataChanged = true;
|
|
|
|
FileHandleList queue;
|
|
|
|
const TQStringList::ConstIterator filesEnd = files.end();
|
|
for(TQStringList::ConstIterator it = files.begin(); it != filesEnd; ++it)
|
|
addFile(*it, queue, true, &after);
|
|
|
|
addFileHelper(queue, &after, true);
|
|
|
|
m_blockDataChanged = false;
|
|
|
|
slotWeightDirty();
|
|
dataChanged();
|
|
|
|
TDEApplication::restoreOverrideCursor();
|
|
}
|
|
|
|
void Playlist::refreshAlbums(const PlaylistItemList &items, coverKey id)
|
|
{
|
|
TQValueList< TQPair<TQString, TQString> > albums;
|
|
bool setAlbumCovers = items.count() == 1;
|
|
|
|
for(PlaylistItemList::ConstIterator it = items.begin(); it != items.end(); ++it) {
|
|
TQString artist = (*it)->file().tag()->artist();
|
|
TQString album = (*it)->file().tag()->album();
|
|
|
|
if(albums.find(qMakePair(artist, album)) == albums.end())
|
|
albums.append(qMakePair(artist, album));
|
|
|
|
(*it)->file().coverInfo()->setCoverId(id);
|
|
if(setAlbumCovers)
|
|
(*it)->file().coverInfo()->applyCoverToWholeAlbum(true);
|
|
}
|
|
|
|
for(TQValueList< TQPair<TQString, TQString> >::ConstIterator it = albums.begin();
|
|
it != albums.end(); ++it)
|
|
{
|
|
refreshAlbum((*it).first, (*it).second);
|
|
}
|
|
}
|
|
|
|
void Playlist::updatePlaying() const
|
|
{
|
|
for(PlaylistItemList::ConstIterator it = PlaylistItem::playingItems().begin();
|
|
it != PlaylistItem::playingItems().end(); ++it)
|
|
{
|
|
(*it)->listView()->triggerUpdate();
|
|
}
|
|
}
|
|
|
|
void Playlist::refreshAlbum(const TQString &artist, const TQString &album)
|
|
{
|
|
ColumnList columns;
|
|
columns.append(PlaylistItem::ArtistColumn);
|
|
PlaylistSearch::Component artistComponent(artist, false, columns,
|
|
PlaylistSearch::Component::Exact);
|
|
|
|
columns.clear();
|
|
columns.append(PlaylistItem::AlbumColumn);
|
|
PlaylistSearch::Component albumComponent(album, false, columns,
|
|
PlaylistSearch::Component::Exact);
|
|
|
|
PlaylistSearch::ComponentList components;
|
|
components.append(artist);
|
|
components.append(album);
|
|
|
|
PlaylistList playlists;
|
|
playlists.append(CollectionList::instance());
|
|
|
|
PlaylistSearch search(playlists, components);
|
|
PlaylistItemList matches = search.matchedItems();
|
|
|
|
for(PlaylistItemList::Iterator it = matches.begin(); it != matches.end(); ++it)
|
|
(*it)->refresh();
|
|
}
|
|
|
|
void Playlist::hideColumn(int c, bool updateSearch)
|
|
{
|
|
m_headerMenu->setItemChecked(c, false);
|
|
|
|
if(!isColumnVisible(c))
|
|
return;
|
|
|
|
setColumnWidthMode(c, Manual);
|
|
setColumnWidth(c, 0);
|
|
|
|
// Moving the column to the end seems to prevent it from randomly
|
|
// popping up.
|
|
|
|
header()->moveSection(c, header()->count());
|
|
header()->setResizeEnabled(false, c);
|
|
|
|
if(c == m_leftColumn) {
|
|
updatePlaying();
|
|
m_leftColumn = leftMostVisibleColumn();
|
|
}
|
|
|
|
if(!manualResize()) {
|
|
slotUpdateColumnWidths();
|
|
triggerUpdate();
|
|
}
|
|
|
|
if(this != CollectionList::instance())
|
|
CollectionList::instance()->hideColumn(c, false);
|
|
|
|
if(updateSearch)
|
|
redisplaySearch();
|
|
}
|
|
|
|
void Playlist::showColumn(int c, bool updateSearch)
|
|
{
|
|
m_headerMenu->setItemChecked(c, true);
|
|
|
|
if(isColumnVisible(c))
|
|
return;
|
|
|
|
// Just set the width to one to mark the column as visible -- we'll update
|
|
// the real size in the next call.
|
|
|
|
if(manualResize())
|
|
setColumnWidth(c, 35); // Make column at least slightly visible.
|
|
else
|
|
setColumnWidth(c, 1);
|
|
|
|
header()->setResizeEnabled(true, c);
|
|
header()->moveSection(c, c); // Approximate old position
|
|
|
|
if(c == leftMostVisibleColumn()) {
|
|
updatePlaying();
|
|
m_leftColumn = leftMostVisibleColumn();
|
|
}
|
|
|
|
if(!manualResize()) {
|
|
slotUpdateColumnWidths();
|
|
triggerUpdate();
|
|
}
|
|
|
|
if(this != CollectionList::instance())
|
|
CollectionList::instance()->showColumn(c, false);
|
|
|
|
if(updateSearch)
|
|
redisplaySearch();
|
|
}
|
|
|
|
bool Playlist::isColumnVisible(int c) const
|
|
{
|
|
return columnWidth(c) != 0;
|
|
}
|
|
|
|
void Playlist::polish()
|
|
{
|
|
TDEListView::polish();
|
|
|
|
if(m_polished)
|
|
return;
|
|
|
|
m_polished = true;
|
|
|
|
addColumn(i18n("Track Name"));
|
|
addColumn(i18n("Artist"));
|
|
addColumn(i18n("Album"));
|
|
addColumn(i18n("Cover"));
|
|
addColumn(i18n("Track"));
|
|
addColumn(i18n("Genre"));
|
|
addColumn(i18n("Year"));
|
|
addColumn(i18n("Length"));
|
|
addColumn(i18n("Bitrate"));
|
|
addColumn(i18n("Comment"));
|
|
addColumn(i18n("File Name"));
|
|
addColumn(i18n("File Name (full path)"));
|
|
|
|
setRenameable(PlaylistItem::TrackColumn, true);
|
|
setRenameable(PlaylistItem::ArtistColumn, true);
|
|
setRenameable(PlaylistItem::AlbumColumn, true);
|
|
setRenameable(PlaylistItem::TrackNumberColumn, true);
|
|
setRenameable(PlaylistItem::GenreColumn, true);
|
|
setRenameable(PlaylistItem::YearColumn, true);
|
|
|
|
setAllColumnsShowFocus(true);
|
|
setSelectionMode(TQListView::Extended);
|
|
setShowSortIndicator(true);
|
|
setDropVisualizer(true);
|
|
|
|
m_columnFixedWidths.resize(columns(), 0);
|
|
|
|
//////////////////////////////////////////////////
|
|
// setup header RMB menu
|
|
//////////////////////////////////////////////////
|
|
|
|
m_columnVisibleAction = new TDEActionMenu(i18n("&Show Columns"), TQT_TQOBJECT(this), "showColumns");
|
|
|
|
m_headerMenu = m_columnVisibleAction->popupMenu();
|
|
m_headerMenu->insertTitle(i18n("Show"));
|
|
m_headerMenu->setCheckable(true);
|
|
|
|
for(int i = 0; i < header()->count(); ++i) {
|
|
if(i == PlaylistItem::FileNameColumn)
|
|
m_headerMenu->insertSeparator();
|
|
m_headerMenu->insertItem(header()->label(i), i);
|
|
m_headerMenu->setItemChecked(i, true);
|
|
adjustColumn(i);
|
|
}
|
|
|
|
connect(m_headerMenu, TQT_SIGNAL(activated(int)), this, TQT_SLOT(slotToggleColumnVisible(int)));
|
|
|
|
connect(this, TQT_SIGNAL(contextMenuRequested(TQListViewItem *, const TQPoint &, int)),
|
|
this, TQT_SLOT(slotShowRMBMenu(TQListViewItem *, const TQPoint &, int)));
|
|
connect(this, TQT_SIGNAL(itemRenamed(TQListViewItem *, const TQString &, int)),
|
|
this, TQT_SLOT(slotInlineEditDone(TQListViewItem *, const TQString &, int)));
|
|
connect(this, TQT_SIGNAL(doubleClicked(TQListViewItem *)),
|
|
this, TQT_SLOT(slotPlayCurrent()));
|
|
connect(this, TQT_SIGNAL(returnPressed(TQListViewItem *)),
|
|
this, TQT_SLOT(slotPlayCurrent()));
|
|
|
|
connect(header(), TQT_SIGNAL(sizeChange(int, int, int)),
|
|
this, TQT_SLOT(slotColumnSizeChanged(int, int, int)));
|
|
|
|
connect(renameLineEdit(), TQT_SIGNAL(completionModeChanged(TDEGlobalSettings::Completion)),
|
|
this, TQT_SLOT(slotInlineCompletionModeChanged(TDEGlobalSettings::Completion)));
|
|
|
|
connect(action("resizeColumnsManually"), TQT_SIGNAL(activated()),
|
|
this, TQT_SLOT(slotColumnResizeModeChanged()));
|
|
|
|
if(action<TDEToggleAction>("resizeColumnsManually")->isChecked())
|
|
setHScrollBarMode(Auto);
|
|
else
|
|
setHScrollBarMode(AlwaysOff);
|
|
|
|
setAcceptDrops(true);
|
|
setDropVisualizer(true);
|
|
|
|
m_disableColumnWidthUpdates = false;
|
|
|
|
setShowToolTips(false);
|
|
m_toolTip = new PlaylistToolTip(viewport(), this);
|
|
}
|
|
|
|
void Playlist::setupItem(PlaylistItem *item)
|
|
{
|
|
if(!m_search.isEmpty())
|
|
item->setVisible(m_search.checkItem(item));
|
|
|
|
if(childCount() <= 2 && !manualResize()) {
|
|
slotWeightDirty();
|
|
slotUpdateColumnWidths();
|
|
triggerUpdate();
|
|
}
|
|
}
|
|
|
|
void Playlist::setDynamicListsFrozen(bool frozen)
|
|
{
|
|
m_collection->setDynamicListsFrozen(frozen);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// protected slots
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Playlist::slotPopulateBackMenu() const
|
|
{
|
|
if(!playingItem())
|
|
return;
|
|
|
|
TDEPopupMenu *menu = action<TDEToolBarPopupAction>("back")->popupMenu();
|
|
menu->clear();
|
|
m_backMenuItems.clear();
|
|
|
|
int count = 0;
|
|
PlaylistItemList::ConstIterator it = m_history.end();
|
|
|
|
while(it != m_history.begin() && count < 10) {
|
|
++count;
|
|
--it;
|
|
int index = menu->insertItem((*it)->file().tag()->title());
|
|
m_backMenuItems[index] = *it;
|
|
}
|
|
}
|
|
|
|
void Playlist::slotPlayFromBackMenu(int number) const
|
|
{
|
|
if(!m_backMenuItems.contains(number))
|
|
return;
|
|
|
|
TrackSequenceManager::instance()->setNextItem(m_backMenuItems[number]);
|
|
action("forward")->activate();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// private members
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Playlist::setup()
|
|
{
|
|
setItemMargin(3);
|
|
|
|
connect(header(), TQT_SIGNAL(indexChange(int, int, int)), this, TQT_SLOT(slotColumnOrderChanged(int, int, int)));
|
|
|
|
connect(m_fetcher, TQT_SIGNAL(signalCoverChanged(int)), this, TQT_SLOT(slotCoverChanged(int)));
|
|
|
|
// Prevent list of selected items from changing while internet search is in
|
|
// progress.
|
|
connect(this, TQT_SIGNAL(selectionChanged()), m_fetcher, TQT_SLOT(abortSearch()));
|
|
|
|
setSorting(1);
|
|
}
|
|
|
|
void Playlist::loadFile(const TQString &fileName, const TQFileInfo &fileInfo)
|
|
{
|
|
TQFile file(fileName);
|
|
if(!file.open(IO_ReadOnly))
|
|
return;
|
|
|
|
TQTextStream stream(&file);
|
|
|
|
// Turn off non-explicit sorting.
|
|
|
|
setSorting(PlaylistItem::lastColumn() + columnOffset() + 1);
|
|
|
|
PlaylistItem *after = 0;
|
|
|
|
m_disableColumnWidthUpdates = true;
|
|
|
|
m_blockDataChanged = true;
|
|
|
|
while(!stream.atEnd()) {
|
|
TQString itemName = stream.readLine().stripWhiteSpace();
|
|
|
|
TQFileInfo item(itemName);
|
|
|
|
if(item.isRelative())
|
|
item.setFile(TQDir::cleanDirPath(fileInfo.dirPath(true) + "/" + itemName));
|
|
|
|
if(item.exists() && item.isFile() && item.isReadable() &&
|
|
MediaFiles::isMediaFile(item.fileName()))
|
|
{
|
|
if(after)
|
|
after = createItem(FileHandle(item, item.absFilePath()), after, false);
|
|
else
|
|
after = createItem(FileHandle(item, item.absFilePath()), 0, false);
|
|
}
|
|
}
|
|
|
|
m_blockDataChanged = false;
|
|
|
|
file.close();
|
|
|
|
dataChanged();
|
|
|
|
m_disableColumnWidthUpdates = false;
|
|
}
|
|
|
|
void Playlist::setPlaying(PlaylistItem *item, bool addToHistory)
|
|
{
|
|
if(playingItem() == item)
|
|
return;
|
|
|
|
if(playingItem()) {
|
|
if(addToHistory) {
|
|
if(playingItem()->playlist() ==
|
|
playingItem()->playlist()->m_collection->upcomingPlaylist())
|
|
m_history.append(playingItem()->collectionItem());
|
|
else
|
|
m_history.append(playingItem());
|
|
}
|
|
playingItem()->setPlaying(false);
|
|
}
|
|
|
|
TrackSequenceManager::instance()->setCurrent(item);
|
|
TQByteArray data;
|
|
kapp->dcopClient()->emitDCOPSignal("Player", "trackChanged()", data);
|
|
|
|
if(!item)
|
|
return;
|
|
|
|
item->setPlaying(true);
|
|
|
|
bool enableBack = !m_history.isEmpty();
|
|
action<TDEToolBarPopupAction>("back")->popupMenu()->setEnabled(enableBack);
|
|
}
|
|
|
|
bool Playlist::playing() const
|
|
{
|
|
return playingItem() && this == playingItem()->playlist();
|
|
}
|
|
|
|
int Playlist::leftMostVisibleColumn() const
|
|
{
|
|
int i = 0;
|
|
while(!isColumnVisible(header()->mapToSection(i)) && i < PlaylistItem::lastColumn())
|
|
i++;
|
|
|
|
return header()->mapToSection(i);
|
|
}
|
|
|
|
PlaylistItemList Playlist::items(TQListViewItemIterator::IteratorFlag flags)
|
|
{
|
|
PlaylistItemList list;
|
|
|
|
for(TQListViewItemIterator it(this, flags); it.current(); ++it)
|
|
list.append(static_cast<PlaylistItem *>(it.current()));
|
|
|
|
return list;
|
|
}
|
|
|
|
void Playlist::calculateColumnWeights()
|
|
{
|
|
if(m_disableColumnWidthUpdates)
|
|
return;
|
|
|
|
PlaylistItemList l = items();
|
|
TQValueListConstIterator<int> columnIt;
|
|
|
|
TQValueVector<double> averageWidth(columns(), 0);
|
|
double itemCount = l.size();
|
|
|
|
TQValueVector<int> cachedWidth;
|
|
|
|
// Here we're not using a real average, but averaging the squares of the
|
|
// column widths and then using the square root of that value. This gives
|
|
// a nice weighting to the longer columns without doing something arbitrary
|
|
// like adding a fixed amount of padding.
|
|
|
|
for(PlaylistItemList::ConstIterator it = l.begin(); it != l.end(); ++it) {
|
|
cachedWidth = (*it)->cachedWidths();
|
|
for(columnIt = m_weightDirty.begin(); columnIt != m_weightDirty.end(); ++columnIt)
|
|
averageWidth[*columnIt] += pow(double(cachedWidth[*columnIt]), 2.0) / itemCount;
|
|
}
|
|
|
|
m_columnWeights.resize(columns(), -1);
|
|
|
|
for(columnIt = m_weightDirty.begin(); columnIt != m_weightDirty.end(); ++columnIt) {
|
|
m_columnWeights[*columnIt] = int(sqrt(averageWidth[*columnIt]) + 0.5);
|
|
|
|
// kdDebug(65432) << k_funcinfo << "m_columnWeights[" << *columnIt << "] == "
|
|
// << m_columnWeights[*columnIt] << endl;
|
|
}
|
|
|
|
m_weightDirty.clear();
|
|
}
|
|
|
|
void Playlist::addFile(const TQString &file, FileHandleList &files, bool importPlaylists,
|
|
PlaylistItem **after)
|
|
{
|
|
if(hasItem(file) && !m_allowDuplicates)
|
|
return;
|
|
|
|
processEvents();
|
|
addFileHelper(files, after);
|
|
|
|
// Our biggest thing that we're fighting during startup is too many stats
|
|
// of files. Make sure that we don't do one here if it's not needed.
|
|
|
|
FileHandle cached = Cache::instance()->value(file);
|
|
|
|
if(!cached.isNull()) {
|
|
cached.tag();
|
|
files.append(cached);
|
|
return;
|
|
}
|
|
|
|
|
|
const TQFileInfo fileInfo = TQDir::cleanDirPath(file);
|
|
if(!fileInfo.exists())
|
|
return;
|
|
|
|
if(fileInfo.isFile() && fileInfo.isReadable()) {
|
|
if(MediaFiles::isMediaFile(file)) {
|
|
FileHandle f(fileInfo, fileInfo.absFilePath());
|
|
f.tag();
|
|
files.append(f);
|
|
}
|
|
}
|
|
|
|
if(importPlaylists && MediaFiles::isPlaylistFile(file) &&
|
|
!m_collection->containsPlaylistFile(fileInfo.absFilePath()))
|
|
{
|
|
new Playlist(m_collection, fileInfo);
|
|
return;
|
|
}
|
|
|
|
if(fileInfo.isDir()) {
|
|
|
|
// Resorting to the POSIX API because TQDir::listEntries() stats every
|
|
// file and blocks while it's doing so.
|
|
|
|
DIR *dir = ::opendir(TQFile::encodeName(fileInfo.filePath()));
|
|
|
|
if(dir) {
|
|
struct dirent *dirEntry;
|
|
|
|
for(dirEntry = ::readdir(dir); dirEntry; dirEntry = ::readdir(dir)) {
|
|
if(strcmp(dirEntry->d_name, ".") != 0 && strcmp(dirEntry->d_name, "..") != 0) {
|
|
|
|
// We set importPlaylists to the value from the add directories
|
|
// dialog as we want to load all of the ones that the user has
|
|
// explicitly asked for, but not those that we find in lower
|
|
// directories.
|
|
|
|
addFile(fileInfo.filePath() + TQDir::separator() + TQFile::decodeName(dirEntry->d_name),
|
|
files, m_collection->importPlaylists(), after);
|
|
}
|
|
}
|
|
::closedir(dir);
|
|
}
|
|
else {
|
|
kdWarning(65432) << "Unable to open directory "
|
|
<< fileInfo.filePath()
|
|
<< ", make sure it is readable.\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
void Playlist::addFileHelper(FileHandleList &files, PlaylistItem **after, bool ignoreTimer)
|
|
{
|
|
static TQTime time = TQTime::currentTime();
|
|
|
|
// Process new items every 10 seconds, when we've loaded 1000 items, or when
|
|
// it's been requested in the API.
|
|
|
|
if(ignoreTimer || time.elapsed() > 10000 ||
|
|
(files.count() >= 1000 && time.elapsed() > 1000))
|
|
{
|
|
time.restart();
|
|
|
|
const bool focus = hasFocus();
|
|
const bool visible = isVisible() && files.count() > 20;
|
|
|
|
if(visible)
|
|
m_collection->raiseDistraction();
|
|
const FileHandleList::ConstIterator filesEnd = files.end();
|
|
for(FileHandleList::ConstIterator it = files.begin(); it != filesEnd; ++it)
|
|
*after = createItem(*it, *after, false);
|
|
files.clear();
|
|
|
|
if(visible)
|
|
m_collection->lowerDistraction();
|
|
|
|
if(focus)
|
|
setFocus();
|
|
|
|
processEvents();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// private slots
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Playlist::slotUpdateColumnWidths()
|
|
{
|
|
if(m_disableColumnWidthUpdates || manualResize())
|
|
return;
|
|
|
|
// Make sure that the column weights have been initialized before trying to
|
|
// update the columns.
|
|
|
|
TQValueList<int> visibleColumns;
|
|
for(int i = 0; i < columns(); i++) {
|
|
if(isColumnVisible(i))
|
|
visibleColumns.append(i);
|
|
}
|
|
|
|
TQValueListConstIterator<int> it;
|
|
|
|
if(count() == 0) {
|
|
for(it = visibleColumns.begin(); it != visibleColumns.end(); ++it)
|
|
setColumnWidth(*it, header()->fontMetrics().width(header()->label(*it)) + 10);
|
|
|
|
return;
|
|
}
|
|
|
|
if(m_columnWeights.isEmpty())
|
|
return;
|
|
|
|
// First build a list of minimum widths based on the strings in the listview
|
|
// header. We won't let the width of the column go below this width.
|
|
|
|
TQValueVector<int> minimumWidth(columns(), 0);
|
|
int minimumWidthTotal = 0;
|
|
|
|
// Also build a list of either the minimum *or* the fixed width -- whichever is
|
|
// greater.
|
|
|
|
TQValueVector<int> minimumFixedWidth(columns(), 0);
|
|
int minimumFixedWidthTotal = 0;
|
|
|
|
for(it = visibleColumns.begin(); it != visibleColumns.end(); ++it) {
|
|
int column = *it;
|
|
minimumWidth[column] = header()->fontMetrics().width(header()->label(column)) + 10;
|
|
minimumWidthTotal += minimumWidth[column];
|
|
|
|
minimumFixedWidth[column] = TQMAX(minimumWidth[column], m_columnFixedWidths[column]);
|
|
minimumFixedWidthTotal += minimumFixedWidth[column];
|
|
}
|
|
|
|
// Make sure that the width won't get any smaller than this. We have to
|
|
// account for the scrollbar as well. Since this method is called from the
|
|
// resize event this will set a pretty hard lower bound on the size.
|
|
|
|
setMinimumWidth(minimumWidthTotal + verticalScrollBar()->width());
|
|
|
|
// If we've got enough room for the fixed widths (larger than the minimum
|
|
// widths) then instead use those for our "minimum widths".
|
|
|
|
if(minimumFixedWidthTotal < visibleWidth()) {
|
|
minimumWidth = minimumFixedWidth;
|
|
minimumWidthTotal = minimumFixedWidthTotal;
|
|
}
|
|
|
|
// We've got a list of columns "weights" based on some statistics gathered
|
|
// about the widths of the items in that column. We need to find the total
|
|
// useful weight to use as a divisor for each column's weight.
|
|
|
|
double totalWeight = 0;
|
|
for(it = visibleColumns.begin(); it != visibleColumns.end(); ++it)
|
|
totalWeight += m_columnWeights[*it];
|
|
|
|
// Computed a "weighted width" for each visible column. This would be the
|
|
// width if we didn't have to handle the cases of minimum and maximum widths.
|
|
|
|
TQValueVector<int> weightedWidth(columns(), 0);
|
|
for(it = visibleColumns.begin(); it != visibleColumns.end(); ++it)
|
|
weightedWidth[*it] = int(double(m_columnWeights[*it]) / totalWeight * visibleWidth() + 0.5);
|
|
|
|
// The "extra" width for each column. This is the weighted width less the
|
|
// minimum width or zero if the minimum width is greater than the weighted
|
|
// width.
|
|
|
|
TQValueVector<int> extraWidth(columns(), 0);
|
|
|
|
// This is used as an indicator if we have any columns where the weighted
|
|
// width is less than the minimum width. If this is false then we can
|
|
// just use the weighted width with no problems, otherwise we have to
|
|
// "readjust" the widths.
|
|
|
|
bool readjust = false;
|
|
|
|
// If we have columns where the weighted width is less than the minimum width
|
|
// we need to steal that space from somewhere. The amount that we need to
|
|
// steal is the "neededWidth".
|
|
|
|
int neededWidth = 0;
|
|
|
|
// While we're on the topic of stealing -- we have to have somewhere to steal
|
|
// from. availableWidth is the sum of the amount of space beyond the minimum
|
|
// width that each column has been allocated -- the sum of the values of
|
|
// extraWidth[].
|
|
|
|
int availableWidth = 0;
|
|
|
|
// Fill in the values discussed above.
|
|
|
|
for(it = visibleColumns.begin(); it != visibleColumns.end(); ++it) {
|
|
if(weightedWidth[*it] < minimumWidth[*it]) {
|
|
readjust = true;
|
|
extraWidth[*it] = 0;
|
|
neededWidth += minimumWidth[*it] - weightedWidth[*it];
|
|
}
|
|
else {
|
|
extraWidth[*it] = weightedWidth[*it] - minimumWidth[*it];
|
|
availableWidth += extraWidth[*it];
|
|
}
|
|
}
|
|
|
|
// The adjustmentRatio is the amount of the "extraWidth[]" that columns will
|
|
// actually be given.
|
|
|
|
double adjustmentRatio = (double(availableWidth) - double(neededWidth)) / double(availableWidth);
|
|
|
|
// This will be the sum of the total space that we actually use. Because of
|
|
// rounding error this won't be the exact available width.
|
|
|
|
int usedWidth = 0;
|
|
|
|
// Now set the actual column widths. If the weighted widths are all greater
|
|
// than the minimum widths, just use those, otherwise use the "reajusted
|
|
// weighted width".
|
|
|
|
for(it = visibleColumns.begin(); it != visibleColumns.end(); ++it) {
|
|
int width;
|
|
if(readjust) {
|
|
int adjustedExtraWidth = int(double(extraWidth[*it]) * adjustmentRatio + 0.5);
|
|
width = minimumWidth[*it] + adjustedExtraWidth;
|
|
}
|
|
else
|
|
width = weightedWidth[*it];
|
|
|
|
setColumnWidth(*it, width);
|
|
usedWidth += width;
|
|
}
|
|
|
|
// Fill the remaining gap for a clean fit into the available space.
|
|
|
|
int remainingWidth = visibleWidth() - usedWidth;
|
|
setColumnWidth(visibleColumns.back(), columnWidth(visibleColumns.back()) + remainingWidth);
|
|
|
|
m_widthsDirty = false;
|
|
}
|
|
|
|
void Playlist::slotAddToUpcoming()
|
|
{
|
|
m_collection->setUpcomingPlaylistEnabled(true);
|
|
m_collection->upcomingPlaylist()->appendItems(selectedItems());
|
|
}
|
|
|
|
void Playlist::slotShowRMBMenu(TQListViewItem *item, const TQPoint &point, int column)
|
|
{
|
|
if(!item)
|
|
return;
|
|
|
|
// Create the RMB menu on demand.
|
|
|
|
if(!m_rmbMenu) {
|
|
|
|
// A bit of a hack to get a pointer to the action collection.
|
|
// Probably more of these actions should be ported over to using TDEActions.
|
|
|
|
m_rmbMenu = new TDEPopupMenu(this);
|
|
|
|
m_rmbUpcomingID = m_rmbMenu->insertItem(SmallIcon("today"),
|
|
i18n("Add to Play Queue"), this, TQT_SLOT(slotAddToUpcoming()));
|
|
m_rmbMenu->insertSeparator();
|
|
|
|
if(!readOnly()) {
|
|
action("edit_cut")->plug(m_rmbMenu);
|
|
action("edit_copy")->plug(m_rmbMenu);
|
|
action("edit_paste")->plug(m_rmbMenu);
|
|
m_rmbMenu->insertSeparator();
|
|
action("removeFromPlaylist")->plug(m_rmbMenu);
|
|
}
|
|
else
|
|
action("edit_copy")->plug(m_rmbMenu);
|
|
|
|
m_rmbEditID = m_rmbMenu->insertItem(
|
|
i18n("Edit"), this, TQT_SLOT(slotRenameTag()));
|
|
|
|
action("refresh")->plug(m_rmbMenu);
|
|
action("removeItem")->plug(m_rmbMenu);
|
|
|
|
m_rmbMenu->insertSeparator();
|
|
|
|
action("guessTag")->plug(m_rmbMenu);
|
|
action("renameFile")->plug(m_rmbMenu);
|
|
|
|
action("coverManager")->plug(m_rmbMenu);
|
|
|
|
m_rmbMenu->insertSeparator();
|
|
|
|
m_rmbMenu->insertItem(
|
|
SmallIcon("folder-new"), i18n("Create Playlist From Selected Items..."), this, TQT_SLOT(slotCreateGroup()));
|
|
|
|
K3bExporter *exporter = new K3bExporter(this);
|
|
TDEAction *k3bAction = exporter->action();
|
|
if(k3bAction)
|
|
k3bAction->plug(m_rmbMenu);
|
|
}
|
|
|
|
// Ignore any columns added by subclasses.
|
|
|
|
column -= columnOffset();
|
|
|
|
bool showEdit =
|
|
(column == PlaylistItem::TrackColumn) ||
|
|
(column == PlaylistItem::ArtistColumn) ||
|
|
(column == PlaylistItem::AlbumColumn) ||
|
|
(column == PlaylistItem::TrackNumberColumn) ||
|
|
(column == PlaylistItem::GenreColumn) ||
|
|
(column == PlaylistItem::YearColumn);
|
|
|
|
if(showEdit)
|
|
m_rmbMenu->changeItem(m_rmbEditID,
|
|
i18n("Edit '%1'").arg(columnText(column + columnOffset())));
|
|
|
|
m_rmbMenu->setItemVisible(m_rmbEditID, showEdit);
|
|
|
|
// Disable edit menu if only one file is selected, and it's read-only
|
|
|
|
FileHandle file = static_cast<PlaylistItem*>(item)->file();
|
|
|
|
m_rmbMenu->setItemEnabled(m_rmbEditID, file.fileInfo().isWritable() ||
|
|
selectedItems().count() > 1);
|
|
|
|
action("viewCover")->setEnabled(file.coverInfo()->hasCover());
|
|
action("removeCover")->setEnabled(file.coverInfo()->hasCover());
|
|
|
|
m_rmbMenu->popup(point);
|
|
m_currentColumn = column + columnOffset();
|
|
}
|
|
|
|
void Playlist::slotRenameTag()
|
|
{
|
|
// kdDebug(65432) << "Playlist::slotRenameTag()" << endl;
|
|
|
|
// setup completions and validators
|
|
|
|
CollectionList *list = CollectionList::instance();
|
|
|
|
KLineEdit *edit = renameLineEdit();
|
|
|
|
switch(m_currentColumn - columnOffset())
|
|
{
|
|
case PlaylistItem::ArtistColumn:
|
|
edit->completionObject()->setItems(list->uniqueSet(CollectionList::Artists));
|
|
break;
|
|
case PlaylistItem::AlbumColumn:
|
|
edit->completionObject()->setItems(list->uniqueSet(CollectionList::Albums));
|
|
break;
|
|
case PlaylistItem::GenreColumn:
|
|
{
|
|
TQStringList genreList;
|
|
TagLib::StringList genres = TagLib::ID3v1::genreList();
|
|
for(TagLib::StringList::ConstIterator it = genres.begin(); it != genres.end(); ++it)
|
|
genreList.append(TStringToTQString((*it)));
|
|
edit->completionObject()->setItems(genreList);
|
|
break;
|
|
}
|
|
default:
|
|
edit->completionObject()->clear();
|
|
break;
|
|
}
|
|
|
|
m_editText = currentItem()->text(m_currentColumn);
|
|
|
|
rename(currentItem(), m_currentColumn);
|
|
}
|
|
|
|
bool Playlist::editTag(PlaylistItem *item, const TQString &text, int column)
|
|
{
|
|
Tag *newTag = TagTransactionManager::duplicateTag(item->file().tag());
|
|
|
|
switch(column - columnOffset())
|
|
{
|
|
case PlaylistItem::TrackColumn:
|
|
newTag->setTitle(text);
|
|
break;
|
|
case PlaylistItem::ArtistColumn:
|
|
newTag->setArtist(text);
|
|
break;
|
|
case PlaylistItem::AlbumColumn:
|
|
newTag->setAlbum(text);
|
|
break;
|
|
case PlaylistItem::TrackNumberColumn:
|
|
{
|
|
bool ok;
|
|
int value = text.toInt(&ok);
|
|
if(ok)
|
|
newTag->setTrack(value);
|
|
break;
|
|
}
|
|
case PlaylistItem::GenreColumn:
|
|
newTag->setGenre(text);
|
|
break;
|
|
case PlaylistItem::YearColumn:
|
|
{
|
|
bool ok;
|
|
int value = text.toInt(&ok);
|
|
if(ok)
|
|
newTag->setYear(value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
TagTransactionManager::instance()->changeTagOnItem(item, newTag);
|
|
return true;
|
|
}
|
|
|
|
void Playlist::slotInlineEditDone(TQListViewItem *, const TQString &, int column)
|
|
{
|
|
TQString text = renameLineEdit()->text();
|
|
bool changed = false;
|
|
|
|
PlaylistItemList l = selectedItems();
|
|
|
|
// See if any of the files have a tag different from the input.
|
|
|
|
for(PlaylistItemList::ConstIterator it = l.begin(); it != l.end() && !changed; ++it)
|
|
if((*it)->text(column - columnOffset()) != text)
|
|
changed = true;
|
|
|
|
if(!changed ||
|
|
(l.count() > 1 && KMessageBox::warningContinueCancel(
|
|
0,
|
|
i18n("This will edit multiple files. Are you sure?"),
|
|
TQString(),
|
|
i18n("Edit"),
|
|
"DontWarnMultipleTags") == KMessageBox::Cancel))
|
|
{
|
|
return;
|
|
}
|
|
|
|
for(PlaylistItemList::ConstIterator it = l.begin(); it != l.end(); ++it)
|
|
editTag(*it, text, column);
|
|
|
|
TagTransactionManager::instance()->commit();
|
|
|
|
CollectionList::instance()->dataChanged();
|
|
dataChanged();
|
|
update();
|
|
}
|
|
|
|
void Playlist::slotColumnOrderChanged(int, int from, int to)
|
|
{
|
|
if(from == 0 || to == 0) {
|
|
updatePlaying();
|
|
m_leftColumn = header()->mapToSection(0);
|
|
}
|
|
|
|
SharedSettings::instance()->setColumnOrder(this);
|
|
}
|
|
|
|
void Playlist::slotToggleColumnVisible(int column)
|
|
{
|
|
if(!isColumnVisible(column)) {
|
|
int fileNameColumn = PlaylistItem::FileNameColumn + columnOffset();
|
|
int fullPathColumn = PlaylistItem::FullPathColumn + columnOffset();
|
|
|
|
if(column == fileNameColumn && isColumnVisible(fullPathColumn)) {
|
|
hideColumn(fullPathColumn, false);
|
|
SharedSettings::instance()->toggleColumnVisible(fullPathColumn);
|
|
}
|
|
if(column == fullPathColumn && isColumnVisible(fileNameColumn)) {
|
|
hideColumn(fileNameColumn, false);
|
|
SharedSettings::instance()->toggleColumnVisible(fileNameColumn);
|
|
}
|
|
}
|
|
|
|
if(isColumnVisible(column))
|
|
hideColumn(column);
|
|
else
|
|
showColumn(column);
|
|
|
|
SharedSettings::instance()->toggleColumnVisible(column - columnOffset());
|
|
}
|
|
|
|
void Playlist::slotCreateGroup()
|
|
{
|
|
TQString name = m_collection->playlistNameDialog(i18n("Create New Playlist"));
|
|
|
|
if(!name.isEmpty())
|
|
new Playlist(m_collection, selectedItems(), name);
|
|
}
|
|
|
|
void Playlist::notifyUserColumnWidthModeChanged()
|
|
{
|
|
KMessageBox::information(this,
|
|
i18n("Manual column widths have been enabled. You can "
|
|
"switch back to automatic column sizes in the view "
|
|
"menu."),
|
|
i18n("Manual Column Widths Enabled"),
|
|
"ShowManualColumnWidthInformation");
|
|
}
|
|
|
|
void Playlist::slotColumnSizeChanged(int column, int, int newSize)
|
|
{
|
|
m_widthsDirty = true;
|
|
m_columnFixedWidths[column] = newSize;
|
|
}
|
|
|
|
void Playlist::slotInlineCompletionModeChanged(TDEGlobalSettings::Completion mode)
|
|
{
|
|
SharedSettings::instance()->setInlineCompletionMode(mode);
|
|
}
|
|
|
|
void Playlist::slotPlayCurrent()
|
|
{
|
|
TQListViewItemIterator it(this, TQListViewItemIterator::Selected);
|
|
PlaylistItem *next = static_cast<PlaylistItem *>(it.current());
|
|
TrackSequenceManager::instance()->setNextItem(next);
|
|
action("forward")->activate();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// helper functions
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TQDataStream &operator<<(TQDataStream &s, const Playlist &p)
|
|
{
|
|
s << p.name();
|
|
s << p.fileName();
|
|
s << p.files();
|
|
|
|
return s;
|
|
}
|
|
|
|
TQDataStream &operator>>(TQDataStream &s, Playlist &p)
|
|
{
|
|
p.read(s);
|
|
return s;
|
|
}
|
|
|
|
bool processEvents()
|
|
{
|
|
static TQTime time = TQTime::currentTime();
|
|
|
|
if(time.elapsed() > 100) {
|
|
time.restart();
|
|
kapp->processEvents();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#include "playlist.moc"
|