437 lines
16 KiB
C++
437 lines
16 KiB
C++
/*
|
|
* Cantata
|
|
*
|
|
* Copyright (c) 2011-2022 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 "devicepropertieswidget.h"
|
|
#include "filenameschemedialog.h"
|
|
#include "gui/covers.h"
|
|
#include "widgets/icons.h"
|
|
#include "support/utils.h"
|
|
#include <QValidator>
|
|
#include <QTabWidget>
|
|
#include <QTimer>
|
|
|
|
class CoverNameValidator : public QValidator
|
|
{
|
|
public:
|
|
|
|
CoverNameValidator(QObject *parent) : QValidator(parent) { }
|
|
|
|
State validate(QString &input, int &) const override
|
|
{
|
|
int dotCount(0);
|
|
|
|
for (int i=0; i<input.length(); ++i) {
|
|
if (QChar('.')==input[i]) {
|
|
if (++dotCount>1) {
|
|
return Invalid;
|
|
}
|
|
}
|
|
else if (!input[i].isLetterOrNumber() || input[i].isSpace()) {
|
|
return Invalid;
|
|
}
|
|
}
|
|
if (input.endsWith('.')) {
|
|
return Intermediate;
|
|
}
|
|
|
|
return Acceptable;
|
|
}
|
|
|
|
// void fixup(QString &input) const
|
|
// {
|
|
// QString out;
|
|
// int dotCount(0);
|
|
// for (int i=0; i<input.length(); ++i) {
|
|
// if (input[i].isLetterOrNumber() && !input[i].isSpace()) {
|
|
// out+=input[i];
|
|
// } else if (QChar('.')==input[i] && ++dotCount<1) {
|
|
// out+=input[i];
|
|
// }
|
|
// }
|
|
//
|
|
// if (!out.endsWith(".jpg") && !out.endsWith(".png")) {
|
|
// out.replace(".", "");
|
|
// out+=".jpg";
|
|
// }
|
|
// input=out;
|
|
// }
|
|
};
|
|
|
|
DevicePropertiesWidget::DevicePropertiesWidget(QWidget *parent)
|
|
: QWidget(parent)
|
|
, schemeDlg(nullptr)
|
|
, noCoverText(tr("Don't copy covers"))
|
|
, embedCoverText(tr("Embed cover within each file"))
|
|
, modified(false)
|
|
, saveable(false)
|
|
{
|
|
setupUi(this);
|
|
configFilename->setIcon(Icons::self()->configureIcon);
|
|
coverMaxSize->insertItems(0, QStringList() << tr("No maximum size") << tr("400 pixels") << tr("300 pixels") << tr("200 pixels") << tr("100 pixels"));
|
|
fixVariousArtists->setToolTip(tr("<p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', "
|
|
"then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the "
|
|
"track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata "
|
|
"will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it "
|
|
"will attempt to extract the real artist from the 'Title' tag, and remove the artist name "
|
|
"from the 'Title' tag.</p>"));
|
|
|
|
useCache->setToolTip(tr("<p>If you enable this, then Cantata will create a cache of the device's music library. "
|
|
"This will help to speed up subsequent library scans (as the cache file will be used instead of "
|
|
"having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update "
|
|
"the device's library, then this cache will become out-of-date. To rectify this, simply "
|
|
"click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and "
|
|
"the contents of the device re-scanned.</p>"));
|
|
|
|
if (qobject_cast<QTabWidget *>(parent)) {
|
|
verticalLayout->setMargin(4);
|
|
}
|
|
}
|
|
|
|
#define REMOVE(w) \
|
|
w->setVisible(false); \
|
|
w->deleteLater(); \
|
|
w=0;
|
|
|
|
void DevicePropertiesWidget::update(const QString &path, const DeviceOptions &opts, const QList<DeviceStorage> &storage, int props, int disabledProps)
|
|
{
|
|
bool allowCovers=(props&Prop_CoversAll)||(props&Prop_CoversBasic);
|
|
albumCovers->clear();
|
|
if (allowCovers) {
|
|
if (props&Prop_CoversAll) {
|
|
albumCovers->insertItems(0, QStringList() << noCoverText << embedCoverText << Covers::standardNames());
|
|
} else {
|
|
albumCovers->insertItems(0, QStringList() << noCoverText << embedCoverText);
|
|
}
|
|
}
|
|
if (props&Prop_Name) {
|
|
name->setText(opts.name);
|
|
connect(name, SIGNAL(textChanged(const QString &)), SLOT(checkSaveable()));
|
|
} else {
|
|
REMOVE(name)
|
|
REMOVE(nameLabel)
|
|
}
|
|
if (props&Prop_FileName) {
|
|
filenameScheme->setText(opts.scheme);
|
|
vfatSafe->setChecked(opts.vfatSafe);
|
|
asciiOnly->setChecked(opts.asciiOnly);
|
|
ignoreThe->setChecked(opts.ignoreThe);
|
|
replaceSpaces->setChecked(opts.replaceSpaces);
|
|
} else {
|
|
REMOVE(filenamesGroupBox)
|
|
filenameScheme=nullptr;
|
|
vfatSafe=nullptr;
|
|
asciiOnly=nullptr;
|
|
ignoreThe=nullptr;
|
|
replaceSpaces=nullptr;
|
|
configFilename=nullptr;
|
|
}
|
|
origOpts=opts;
|
|
|
|
if (props&Prop_Folder) {
|
|
musicFolder->setText(Utils::convertPathForDisplay(path));
|
|
connect(musicFolder, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable()));
|
|
if (disabledProps&Prop_Folder) {
|
|
musicFolder->setDisabled(true);
|
|
}
|
|
} else {
|
|
REMOVE(musicFolder);
|
|
REMOVE(musicFolderLabel);
|
|
}
|
|
if (allowCovers) {
|
|
albumCovers->setEditable(props&Prop_CoversAll);
|
|
if (origOpts.coverName==Device::constNoCover) {
|
|
origOpts.coverName=noCoverText;
|
|
albumCovers->setCurrentIndex(0);
|
|
}
|
|
if (origOpts.coverName==Device::constEmbedCover) {
|
|
origOpts.coverName=embedCoverText;
|
|
albumCovers->setCurrentIndex(1);
|
|
} else {
|
|
albumCovers->setCurrentIndex(0);
|
|
for (int i=1; i<albumCovers->count(); ++i) {
|
|
if (albumCovers->itemText(i)==origOpts.coverName) {
|
|
albumCovers->setCurrentIndex(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (0!=origOpts.coverMaxSize) {
|
|
int coverMax=origOpts.coverMaxSize/100;
|
|
if (coverMax<0 || coverMax>=coverMaxSize->count()) {
|
|
coverMax=0;
|
|
}
|
|
coverMaxSize->setCurrentIndex(0==coverMax ? 0 : (coverMaxSize->count()-coverMax));
|
|
} else {
|
|
coverMaxSize->setCurrentIndex(0);
|
|
}
|
|
albumCovers->setValidator(new CoverNameValidator(this));
|
|
connect(albumCovers, SIGNAL(editTextChanged(const QString &)), this, SLOT(albumCoversChanged()));
|
|
connect(coverMaxSize, SIGNAL(currentIndexChanged(int)), this, SLOT(checkSaveable()));
|
|
} else {
|
|
REMOVE(albumCovers);
|
|
REMOVE(albumCoversLabel);
|
|
REMOVE(coverMaxSize);
|
|
REMOVE(coverMaxSizeLabel);
|
|
}
|
|
if (props&Prop_Va) {
|
|
fixVariousArtists->setChecked(opts.fixVariousArtists);
|
|
connect(fixVariousArtists, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable()));
|
|
} else {
|
|
REMOVE(fixVariousArtists);
|
|
}
|
|
if (props&Prop_Cache) {
|
|
useCache->setChecked(opts.useCache);
|
|
connect(useCache, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable()));
|
|
} else {
|
|
REMOVE(useCache);
|
|
}
|
|
if (props&Prop_AutoScan) {
|
|
autoScan->setChecked(opts.autoScan);
|
|
connect(autoScan, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable()));
|
|
} else {
|
|
REMOVE(autoScan);
|
|
}
|
|
|
|
if (props&Prop_Transcoder || props&Prop_Encoder) {
|
|
bool transcode=props&Prop_Transcoder;
|
|
transcoderName->clear();
|
|
if (transcode) {
|
|
transcoderName->addItem(tr("Do not transcode"), QString());
|
|
transcoderName->setCurrentIndex(0);
|
|
transcoderValue->setVisible(false);
|
|
transcoderWhen->addItem(tr("Always transcode"), DeviceOptions::TW_Always);
|
|
transcoderWhen->addItem(tr("Only transcode if source file is of a different format"), DeviceOptions::TW_IfDifferent);
|
|
transcoderWhen->addItem(tr("Only transcode if source is FLAC/WAV"), DeviceOptions::TW_IfLossess);
|
|
transcoderWhen->setVisible(false);
|
|
transcoderWhen->setCurrentIndex(0);
|
|
for (int i=0; i<transcoderWhen->count(); ++i) {
|
|
if (transcoderWhen->itemData(i).toInt()==opts.transcoderWhen) {
|
|
transcoderWhen->setCurrentIndex(i);
|
|
break;
|
|
}
|
|
}
|
|
connect(transcoderWhen, SIGNAL(currentIndexChanged(int)), this, SLOT(checkSaveable()));
|
|
} else {
|
|
transcoderFrame->setTitle(tr("Encoder"));
|
|
REMOVE(transcoderWhen);
|
|
}
|
|
|
|
QList<Encoders::Encoder> encs=Encoders::getAvailable();
|
|
|
|
if (encs.isEmpty()) {
|
|
transcoderFrame->setVisible(false);
|
|
} else {
|
|
for (const Encoders::Encoder &e: encs) {
|
|
if (!transcode || e.transcoder) {
|
|
QString name=e.name;
|
|
if (transcode && name.endsWith(QLatin1String(" (ffmpeg)"))) {
|
|
name=name.left(name.length()-9);
|
|
}
|
|
transcoderName->addItem(transcode ? tr("Transcode to %1").arg(name) : name, e.codec);
|
|
}
|
|
}
|
|
|
|
if (opts.transcoderCodec.isEmpty()) {
|
|
transcoderChanged();
|
|
} else {
|
|
Encoders::Encoder enc=Encoders::getEncoder(opts.transcoderCodec);
|
|
if (!enc.isNull()) {
|
|
for (int i=1; i<transcoderName->count(); ++i) {
|
|
if (transcoderName->itemData(i).toString()==opts.transcoderCodec) {
|
|
transcoderName->setCurrentIndex(i);
|
|
transcoderChanged();
|
|
transcoderValue->setValue(opts.transcoderValue);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
connect(transcoderName, SIGNAL(currentIndexChanged(int)), this, SLOT(transcoderChanged()));
|
|
connect(transcoderValue, SIGNAL(valueChanged(int)), this, SLOT(checkSaveable()));
|
|
} else {
|
|
REMOVE(transcoderFrame);
|
|
}
|
|
|
|
if (storage.count()<2) {
|
|
REMOVE(defaultVolume);
|
|
REMOVE(defaultVolumeLabel);
|
|
} else {
|
|
for (const DeviceStorage &ds: storage) {
|
|
defaultVolume->addItem(tr("%1 (%2 free)", "name (size free)")
|
|
.arg(ds.description).arg(Utils::formatByteSize(ds.size-ds.used)), ds.volumeIdentifier);
|
|
}
|
|
|
|
for (int i=0; i<defaultVolume->count(); ++i) {
|
|
if (defaultVolume->itemData(i).toString()==opts.volumeId) {
|
|
defaultVolume->setCurrentIndex(i);
|
|
break;
|
|
}
|
|
}
|
|
connect(defaultVolume,SIGNAL(currentIndexChanged(int)), this, SLOT(checkSaveable()));
|
|
}
|
|
|
|
origMusicFolder=Utils::fixPath(path);
|
|
if (props&Prop_FileName) {
|
|
connect(configFilename, SIGNAL(clicked()), SLOT(configureFilenameScheme()));
|
|
connect(filenameScheme, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable()));
|
|
connect(vfatSafe, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable()));
|
|
connect(asciiOnly, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable()));
|
|
connect(ignoreThe, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable()));
|
|
connect(replaceSpaces, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable()));
|
|
}
|
|
|
|
if (albumCovers) {
|
|
albumCoversChanged();
|
|
}
|
|
QTimer::singleShot(0, this, SLOT(setSize()));
|
|
}
|
|
|
|
void DevicePropertiesWidget::transcoderChanged()
|
|
{
|
|
QString codec=transcoderName->itemData(transcoderName->currentIndex()).toString();
|
|
if (codec.isEmpty()) {
|
|
transcoderName->setToolTip(QString());
|
|
transcoderValue->setVisible(false);
|
|
if (transcoderWhen) {
|
|
transcoderWhen->setVisible(false);
|
|
}
|
|
} else {
|
|
Encoders::Encoder enc=Encoders::getEncoder(codec);
|
|
transcoderName->setToolTip(enc.description);
|
|
if (transcoderWhen) {
|
|
transcoderWhen->setVisible(true);
|
|
}
|
|
if (enc.values.count()) {
|
|
transcoderValue->setValues(enc);
|
|
transcoderValue->setVisible(true);
|
|
} else {
|
|
transcoderValue->setVisible(false);
|
|
}
|
|
}
|
|
|
|
Utils::resizeWindow(this, true, false);
|
|
checkSaveable();
|
|
}
|
|
|
|
void DevicePropertiesWidget::albumCoversChanged()
|
|
{
|
|
if (coverMaxSize) {
|
|
bool enableSize=albumCovers->currentText()!=noCoverText;
|
|
coverMaxSize->setEnabled(enableSize);
|
|
coverMaxSizeLabel->setEnabled(enableSize);
|
|
}
|
|
checkSaveable();
|
|
}
|
|
|
|
void DevicePropertiesWidget::checkSaveable()
|
|
{
|
|
DeviceOptions opts=settings();
|
|
bool checkFolder=musicFolder ? musicFolder->isEnabled() : false;
|
|
|
|
modified=opts!=origOpts;
|
|
if (!modified && checkFolder) {
|
|
modified=music()!=origMusicFolder;
|
|
}
|
|
saveable=!opts.scheme.isEmpty() && (!checkFolder || !music().isEmpty()) && !opts.coverName.isEmpty();
|
|
if (saveable &&
|
|
( (-1!=opts.coverName.indexOf(noCoverText) && opts.coverName!=noCoverText) ||
|
|
(-1!=opts.coverName.indexOf(embedCoverText) && opts.coverName!=embedCoverText) ) ) {
|
|
saveable=false;
|
|
}
|
|
emit updated();
|
|
}
|
|
|
|
void DevicePropertiesWidget::configureFilenameScheme()
|
|
{
|
|
if (!schemeDlg) {
|
|
schemeDlg=new FilenameSchemeDialog(this);
|
|
connect(schemeDlg, SIGNAL(scheme(const QString &)), filenameScheme, SLOT(setText(const QString &)));
|
|
}
|
|
schemeDlg->show(settings());
|
|
}
|
|
|
|
DeviceOptions DevicePropertiesWidget::settings()
|
|
{
|
|
DeviceOptions opts;
|
|
if (name && name->isEnabled()) {
|
|
opts.name=name->text().trimmed();
|
|
}
|
|
if (filenameScheme) {
|
|
opts.scheme=filenameScheme->text().trimmed();
|
|
}
|
|
if (vfatSafe) {
|
|
opts.vfatSafe=vfatSafe->isChecked();
|
|
}
|
|
if (asciiOnly) {
|
|
opts.asciiOnly=asciiOnly->isChecked();
|
|
}
|
|
if (ignoreThe) {
|
|
opts.ignoreThe=ignoreThe->isChecked();
|
|
}
|
|
if (replaceSpaces) {
|
|
opts.replaceSpaces=replaceSpaces->isChecked();
|
|
}
|
|
opts.fixVariousArtists=fixVariousArtists ? fixVariousArtists->isChecked() : false;
|
|
opts.useCache=useCache ? useCache->isChecked() : false;
|
|
opts.autoScan=autoScan ? autoScan->isChecked() : false;
|
|
opts.transcoderCodec=QString();
|
|
opts.transcoderValue=0;
|
|
opts.transcoderWhen=DeviceOptions::TW_Always;
|
|
opts.coverName=cover();
|
|
opts.coverMaxSize=(!coverMaxSize || 0==coverMaxSize->currentIndex()) ? 0 : ((coverMaxSize->count()-coverMaxSize->currentIndex())*100);
|
|
opts.volumeId=defaultVolume && defaultVolume->isVisible() ? defaultVolume->itemData(defaultVolume->currentIndex()).toString() : QString();
|
|
if (transcoderFrame && transcoderFrame->isVisible()) {
|
|
opts.transcoderCodec=transcoderName->itemData(transcoderName->currentIndex()).toString();
|
|
|
|
if (!opts.transcoderCodec.isEmpty()) {
|
|
Encoders::Encoder enc=Encoders::getEncoder(opts.transcoderCodec);
|
|
|
|
if (transcoderWhen) {
|
|
opts.transcoderWhen=(DeviceOptions::TranscodeWhen)transcoderWhen->itemData(transcoderWhen->currentIndex()).toUInt();
|
|
}
|
|
if (!enc.isNull() && transcoderValue->value()<enc.values.count()) {
|
|
opts.transcoderValue=enc.values.at(transcoderValue->value()).value;
|
|
}
|
|
}
|
|
}
|
|
return opts;
|
|
}
|
|
|
|
QString DevicePropertiesWidget::cover() const
|
|
{
|
|
QString coverName=albumCovers ? albumCovers->currentText().trimmed() : noCoverText;
|
|
return coverName==noCoverText
|
|
? Device::constNoCover
|
|
: coverName==embedCoverText
|
|
? Device::constEmbedCover
|
|
: coverName;
|
|
}
|
|
|
|
void DevicePropertiesWidget::setSize()
|
|
{
|
|
Utils::resizeWindow(this, true, false);
|
|
}
|
|
|
|
#include "moc_devicepropertieswidget.cpp"
|