Files
cantata/gui/tageditor.cpp
2012-02-02 21:30:46 +00:00

404 lines
13 KiB
C++

/*
* Cantata
*
* Copyright (c) 2011-2012 Craig Drummond <craig.p.drummond@gmail.com>
*
* ----
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "tageditor.h"
#include "tags.h"
#include "musiclibrarymodel.h"
#include "mpdconnection.h"
#include "settings.h"
#ifdef ENABLE_KDE_SUPPORT
#include <KDE/KStandardGuiItem>
#else
#include <QtGui/QDialogButtonBox>
#endif
#ifdef ENABLE_DEVICES_SUPPORT
#include "devicesmodel.h"
#endif
static bool equalTags(const Song &a, const Song &b, bool isAllTracks)
{
return (isAllTracks || a.track==b.track) && a.year==b.year && a.disc==b.disc &&
a.artist==b.artist && a.genre==b.genre && a.album==b.album &&
a.albumartist==b.albumartist && (isAllTracks || a.title==b.title);
}
static void setString(QString &str, const QString &v, bool skipEmpty) {
if (!skipEmpty || !v.isEmpty()) {
str=v;
}
}
TagEditor::TagEditor(QWidget *parent, const QList<Song> &songs,
const QSet<QString> &existingArtists, const QSet<QString> &existingAlbumArtists,
const QSet<QString> &existingAlbums, const QSet<QString> &existingGenres)
#ifdef ENABLE_KDE_SUPPORT
: KDialog(parent)
#else
: QDialog(parent)
#endif
, currentSongIndex(-1)
, updating(false)
{
QWidget *mainWidet = new QWidget(this);
setupUi(mainWidet);
#ifdef ENABLE_KDE_SUPPORT
setMainWidget(mainWidet);
ButtonCodes buttons=Ok|Cancel|User1;
if (songs.count()>1) {
buttons|=User3|User2;
}
setButtons(buttons);
setCaption(i18n("Tags"));
if (songs.count()>1) {
setButtonGuiItem(User3, KStandardGuiItem::back(KStandardGuiItem::UseRTL));
setButtonText(User3, i18n("Previous"));
setButtonGuiItem(User2, KStandardGuiItem::forward(KStandardGuiItem::UseRTL));
setButtonText(User2, i18n("Next"));
enableButton(User2, false);
enableButton(User3, false);
}
setButtonGuiItem(Ok, KStandardGuiItem::save());
setButtonGuiItem(User1, KStandardGuiItem::reset());
enableButton(Ok, false);
enableButton(User1, false);
#else
setWindowTitle(tr("Tags"));
QBoxLayout *layout=new QBoxLayout(QBoxLayout::TopToBottom, this);
layout->addWidget(mainWidet);
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::Apply,
Qt::Horizontal, this);
layout->addWidget(buttonBox);
connect(buttonBox, SIGNAL(clicked(QAbstractButton *)), this, SLOT(buttonPressed(QAbstractButton *)));
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
#endif
setWindowModality(Qt::WindowModal);
setAttribute(Qt::WA_DeleteOnClose);
QStringList strings=existingArtists.toList();
strings.sort();
artist->clear();
artist->insertItems(0, strings);
strings=existingAlbumArtists.toList();
strings.sort();
albumArtist->clear();
albumArtist->insertItems(0, strings);
strings=existingAlbums.toList();
strings.sort();
album->clear();
album->insertItems(0, strings);
strings=existingGenres.toList();
strings.sort();
genre->clear();
genre->insertItems(0, strings);
trackName->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
trackName->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength);
trackName->view()->setTextElideMode(Qt::ElideLeft);
resize(500, 200);
original=songs;
if (original.count()>1) {
QSet<QString> songArtists;
QSet<QString> songAlbumArtists;
QSet<QString> songAlbums;
QSet<QString> songGenres;
QSet<int> songYears;
QSet<int> songDiscs;
foreach (const Song &s, songs) {
songArtists.insert(s.artist);
songAlbumArtists.insert(s.albumartist);
songAlbums.insert(s.album);
songGenres.insert(s.genre);
songYears.insert(s.year);
songDiscs.insert(s.disc);
if (songArtists.count()>1 && songAlbumArtists.count()>1 && songAlbums.count()>1 && songGenres.count()>1 && songYears.count()>1 && songDiscs.count()>1) {
break;
}
}
Song all;
all.file.clear();
all.title.clear();
all.track=0;
all.artist=1==songArtists.count() ? *(songArtists.begin()) : QString();
all.albumartist=1==songAlbumArtists.count() ? *(songAlbumArtists.begin()) : QString();
all.album=1==songAlbums.count() ? *(songAlbums.begin()) : QString();
all.genre=1==songGenres.count() ? *(songGenres.begin()) : QString();
all.year=1==songYears.count() ? *(songYears.begin()) : 0;
all.disc=1==songDiscs.count() ? *(songDiscs.begin()) : 0;
original.prepend(all);
artist->setFocus();
} else {
title->setFocus();
}
edited=original;
setIndex(0);
bool first=original.count()>1;
foreach (const Song &s, original) {
if (first) {
#ifdef ENABLE_KDE_SUPPORT
trackName->insertItem(trackName->count(), i18n("All tracks"));
#else
trackName->insertItem(trackName->count(), tr("All tracks"));
#endif
first=false;
} else {
trackName->insertItem(trackName->count(), s.file);
}
}
connect(title, SIGNAL(textChanged(const QString &)), SLOT(checkChanged()));
connect(artist, SIGNAL(activated(int)), SLOT(checkChanged()));
connect(artist, SIGNAL(editTextChanged(const QString &)), SLOT(checkChanged()));
connect(albumArtist, SIGNAL(activated(int)), SLOT(checkChanged()));
connect(albumArtist, SIGNAL(editTextChanged(const QString &)), SLOT(checkChanged()));
connect(genre, SIGNAL(activated(int)), SLOT(checkChanged()));
connect(track, SIGNAL(valueChanged(int)), SLOT(checkChanged()));
connect(disc, SIGNAL(valueChanged(int)), SLOT(checkChanged()));
connect(genre, SIGNAL(editTextChanged(const QString &)), SLOT(checkChanged()));
connect(year, SIGNAL(valueChanged(int)), SLOT(checkChanged()));
connect(trackName, SIGNAL(activated(int)), SLOT(setIndex(int)));
connect(this, SIGNAL(update()), MPDConnection::self(), SLOT(update()));
}
void TagEditor::fillSong(Song &s, bool skipEmpty) const
{
setString(s.title, title->text().trimmed(), skipEmpty);
setString(s.artist, artist->text().trimmed(), skipEmpty);
setString(s.albumartist, albumArtist->text().trimmed(), skipEmpty);
s.track=track->value();
s.disc=disc->value();
setString(s.genre, genre->text().trimmed(), skipEmpty);
s.year=year->value();
}
void TagEditor::enableOkButton()
{
bool isAll=0==currentSongIndex && original.count()>1;
bool allEdited=isAll && editedIndexes.contains(0);
enableButton(Ok, (isAll && allEdited) ||
(!isAll && ((allEdited && editedIndexes.count()>1) || (!allEdited && editedIndexes.count()>0))));
enableButton(User1, isButtonEnabled(Ok));
}
void TagEditor::checkChanged()
{
if (updating) {
return;
}
updateEdited();
enableOkButton();
}
void TagEditor::updateTrackName(int index, bool edited)
{
bool isAll=0==index && original.count()>1;
if (edited) {
if (isAll) {
#ifdef ENABLE_KDE_SUPPORT
trackName->setItemText(index, i18n("All tracks [modified]"));
#else
trackName->setItemText(index, tr("All tracks [modified]"));
#endif
} else {
#ifdef ENABLE_KDE_SUPPORT
trackName->setItemText(index, i18n("%1 [modified]", original.at(index).file));
#else
trackName->setItemText(index, tr("%1 [modified]").arg(original.at(index).file));
#endif
}
} else {
if (isAll) {
#ifdef ENABLE_KDE_SUPPORT
trackName->setItemText(index, i18n("All tracks"));
#else
trackName->setItemText(index, tr("All tracks"));
#endif
} else {
trackName->setItemText(index, original.at(index).file);
}
}
}
void TagEditor::updateEdited(bool skipEmpty)
{
Song s=edited.at(currentSongIndex);
bool isAll=0==currentSongIndex && original.count()>1;
fillSong(s, skipEmpty);
if (equalTags(s, original.at(currentSongIndex), isAll)) {
if (editedIndexes.contains(currentSongIndex)) {
editedIndexes.remove(currentSongIndex);
updateTrackName(currentSongIndex, false);
edited.replace(currentSongIndex, s);
}
} else {
if (!editedIndexes.contains(currentSongIndex)) {
editedIndexes.insert(currentSongIndex);
updateTrackName(currentSongIndex, true);
}
edited.replace(currentSongIndex, s);
}
}
void TagEditor::setSong(const Song &s)
{
title->setText(s.title);
artist->setText(s.artist);
albumArtist->setText(s.albumartist);
album->setText(s.album);
track->setValue(s.track);
disc->setValue(s.disc);
genre->setText(s.genre);
year->setValue(s.year);
}
void TagEditor::setIndex(int idx)
{
if (currentSongIndex==idx || idx>(original.count()-1)) {
return;
}
updating=true;
bool haveMultiple=original.count()>1;
if (haveMultiple && currentSongIndex>=0) {
updateEdited();
}
Song s=edited.at(!haveMultiple || idx==0 ? 0 : idx);
setSong(s);
currentSongIndex=idx;
bool isMultiple=haveMultiple && 0==idx;
title->setEnabled(!isMultiple);
titleLabel->setEnabled(!isMultiple);
track->setEnabled(!isMultiple);
trackLabel->setEnabled(!isMultiple);
if (isMultiple) {
title->setText(QString());
track->setValue(0);
}
if (original.count()>1) {
enableButton(User2, !isMultiple && idx<(original.count()-1)); // Next
enableButton(User3, !isMultiple && idx>1); // Prev
}
enableOkButton();
trackName->setCurrentIndex(idx);
updating=false;
}
void TagEditor::applyUpdates()
{
bool allEdited=editedIndexes.contains(0);
bool isAll=0==currentSongIndex && original.count()>1;
if (isAll && allEdited) {
for (int i=1; i<edited.count(); ++i) {
currentSongIndex=i;
updateEdited(true);
}
}
bool updated=false;
bool skipFirst=original.count()>1;
foreach (int idx, editedIndexes) {
if (skipFirst && 0==idx) {
continue;
}
Song orig=original.at(idx);
Song edit=edited.at(idx);
if (equalTags(orig, edit, false)) {
continue;
}
#ifdef ENABLE_DEVICES_SUPPORT
if (orig.file.startsWith('/')) { // Device paths are complete, MPD paths are not :-)
if (Tags::update(orig.file, orig, edit)) {
DevicesModel::self()->updateSong(orig, edit);
}
} else
#endif
if (Tags::update(Settings::self()->mpdDir()+orig.file, orig, edit)) {
MusicLibraryModel::self()->removeSongFromList(orig);
MusicLibraryModel::self()->addSongToList(edit);
updated=true;
}
}
if (updated) {
MusicLibraryModel::self()->removeCache();
emit update();
}
}
#ifdef ENABLE_KDE_SUPPORT
void TagEditor::slotButtonClicked(int button)
{
switch (button) {
case Ok: {
applyUpdates();
accept();
break;
}
case User1: // Reset
setSong(original.at(currentSongIndex));
break;
case User2: // Next
setIndex(currentSongIndex+1);
break;
case User3: // Prev
setIndex(currentSongIndex-1);
break;
case Cancel:
reject();
break;
default:
break;
}
KDialog::slotButtonClicked(button);
}
#else
void TagEditor::buttonPressed(QAbstractButton *button)
{
// switch (buttonBox->buttonRole(button)) {
// case QDialogButtonBox::AcceptRole:
// case QDialogButtonBox::ApplyRole:
// writeSettings();
// break;
// case QDialogButtonBox::RejectRole:
// break;
// default:
// break;
// }
}
#endif