Commit d9223918 authored by Michael Pyne's avatar Michael Pyne

GUI: Overhaul the file renamer configuration dialog based on a design of...

GUI: Overhaul the file renamer configuration dialog based on a design of Scott's.  This involved a lot more code than I thought at first, but it should be pretty neat.

This also should fix wish 64849 (Add genre to file renamer) and wish 63912.
In essence this creates a dialog that has a row with each possible category, and arrows allowing you to move that category up and down.
* In the options for the category, you can control leading and trailing text, and what happens if the tag is empty.
* You can set a minimum width for the track (bug 63912).
* The dialog includes a sample result, and you can either edit the sample tag values manually, or load tags from a file to see what it would look like.
* Right now the file renamer code automatically suppresses separators between tag values that use bracketing characters, since I feel that stuff like Artist - Title - [Track] looks dumb, but that may be changed/removed later.

Suggestions are accepted: michael (dot) pyne (at) kdemail (dot) net.

BUG:63912
BUG:64849

svn path=/trunk/kdemultimedia/juk/; revision=359516
parent 606a309a
/***************************************************************************
begin : Sun Oct 31 2004
copyright : (C) 2004 by Michael Pyne
email : michael.pyne@kdemail.net
***************************************************************************/
/***************************************************************************
* *
* 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 <qstring.h>
#include "filerenameroptions.h"
#include "categoryreaderinterface.h"
QString CategoryReaderInterface::value(TagType category) const
{
QString value = categoryValue(category).stripWhiteSpace();
if(value.isEmpty() && emptyAction(category) == TagRenamerOptions::UseReplacementValue)
value = emptyText(category);
return prefix(category) + value + suffix(category);
}
bool CategoryReaderInterface::isRequired(TagType category) const
{
return emptyAction(category) != TagRenamerOptions::IgnoreEmptyTag;
}
bool CategoryReaderInterface::isEmpty(TagType category) const
{
return categoryValue(category).isEmpty();
}
// vim: set et sw=4 ts=4:
/***************************************************************************
begin : Sun Oct 31 2004
copyright : (C) 2004 by Michael Pyne
email : michael.pyne@kdemail.net
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifndef JUK_CATEGORYREADERINTERFACE_H
#define JUK_CATEGORYREADERINTERFACE_H
#include "tagrenameroptions.h"
class QString;
template<class T> class QValueList;
/**
* This class is used to map categories into values. You should implement the
* functionality in a subclass.
*
* @author Michael Pyne <michael.pyne@kdemail.net>
*/
class CategoryReaderInterface
{
public:
virtual ~CategoryReaderInterface() { }
/**
* Returns the textual representation of \p type, without any processing done
* on it. For example, track values shouldn't be expanded out to the minimum
* width from this function.
*
* @param category to retrieve the value of.
* @return textual representation of that category's value.
*/
virtual QString categoryValue(TagType type) const = 0;
/**
* Returns the user-specified prefix string for \p category.
*
* @param category the category to retrieve the value for.
* @return user-specified prefix string for \p category.
*/
virtual QString prefix(TagType category) const = 0;
/**
* Returns the user-specified suffix string for \p category.
*
* @param category the category to retrieve the value for.
* @return user-specified suffix string for \p category.
*/
virtual QString suffix(TagType category) const = 0;
/**
* Returns the user-specified empty action for \p category.
*
* @param category the category to retrieve the value for.
* @return user-specified empty action for \p category.
*/
virtual TagRenamerOptions::EmptyActions emptyAction(TagType category) const = 0;
/**
* Returns the user-specified empty text for \p category. This text might
* be used to replace an empty value.
*
* @param category the category to retrieve the value for.
* @return the user-specified empty text for \p category.
*/
virtual QString emptyText(TagType category) const = 0;
virtual QValueList<TagType> categoryOrder() const = 0;
// You probably shouldn't reimplement this
virtual QString value(TagType category) const;
virtual QString separator() const = 0;
virtual QString musicDirectory() const = 0;
virtual int trackWidth() const = 0;
virtual bool hasDirSeparator(int index) const = 0;
virtual bool isDisabled(TagType category) const = 0;
// You probably shouldn't reimplement this
virtual bool isRequired(TagType category) const;
// You probably shouldn't reimplement this
virtual bool isEmpty(TagType category) const;
};
#endif /* JUK_CATEGORYREADERINTERFACE_H */
// vim: set et sw=4 ts=4:
/***************************************************************************
begin : Thu Oct 28 2004
copyright : (C) 2004 by Michael Pyne
email : michael.pyne@kdemail.net
***************************************************************************/
/***************************************************************************
* *
* 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 <kurlrequester.h>
#include <qradiobutton.h>
#include <qlayout.h>
#include "exampleoptions.h"
ExampleOptions::ExampleOptions(QWidget *parent) :
ExampleOptionsBase(parent, "example options widget")
{
}
void ExampleOptions::exampleSelectionChanged()
{
if(m_fileTagsButton->isChecked())
emit fileChanged();
else
emit dataChanged();
}
void ExampleOptions::exampleDataChanged()
{
emit dataChanged();
}
void ExampleOptions::exampleFileChanged()
{
emit fileChanged();
}
ExampleOptionsDialog::ExampleOptionsDialog(QWidget *parent) :
QDialog(parent, "example options dialog")
{
QVBoxLayout *l = new QVBoxLayout(this);
m_options = new ExampleOptions(this);
l->addWidget(m_options);
// Forward signals
connect(m_options, SIGNAL(fileChanged()), SLOT(fileModeSelected()));
connect(m_options, SIGNAL(dataChanged()), SIGNAL(dataChanged()));
connect(m_options->m_exampleFile, SIGNAL(urlSelected(const QString &)),
this, SIGNAL(fileChanged(const QString &)));
connect(m_options->m_exampleFile, SIGNAL(returnPressed(const QString &)),
this, SIGNAL(fileChanged(const QString &)));
}
void ExampleOptionsDialog::hideEvent(QHideEvent *)
{
emit signalHidden();
}
void ExampleOptionsDialog::showEvent(QShowEvent *)
{
emit signalShown();
}
void ExampleOptionsDialog::fileModeSelected()
{
emit fileChanged(m_options->m_exampleFile->url());
}
#include "exampleoptions.moc"
// vim: set et sw=4 ts=4:
/***************************************************************************
begin : Thu Oct 28 2004
copyright : (C) 2004 by Michael Pyne
email : michael.pyne@kdemail.net
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifndef JUK_EXAMPLEOPTIONS_H
#define JUK_EXAMPLEOPTIONS_H
#include <qdialog.h>
#include "exampleoptionsbase.h"
class ExampleOptions : public ExampleOptionsBase
{
Q_OBJECT
public:
ExampleOptions(QWidget *parent);
protected slots:
virtual void exampleSelectionChanged();
virtual void exampleDataChanged();
virtual void exampleFileChanged();
};
// We're not using KDialog(Base) because this dialog won't have any push
// buttons to close it. It's just a little floating dialog.
class ExampleOptionsDialog : public QDialog
{
Q_OBJECT
public:
ExampleOptionsDialog(QWidget *parent);
const ExampleOptions *widget() const { return m_options; }
protected:
virtual void hideEvent(QHideEvent *);
virtual void showEvent(QShowEvent *);
protected slots:
void fileModeSelected();
signals:
void fileChanged(const QString &);
void dataChanged();
void signalHidden();
void signalShown();
private:
ExampleOptions *m_options;
};
#endif /* JUK_EXAMPLEOPTIONS_H */
// vim: set et sw=4 ts=4:
<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
<class>ExampleOptionsBase</class>
<widget class="QWidget">
<property name="name">
<cstring>ExampleOptionsBase</cstring>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>308</width>
<height>330</height>
</rect>
</property>
<property name="caption">
<string>Example</string>
</property>
<vbox>
<property name="name">
<cstring>unnamed</cstring>
</property>
<widget class="QButtonGroup">
<property name="name">
<cstring>buttonGroup3</cstring>
</property>
<property name="title">
<string>Example Tag Selection</string>
</property>
<vbox>
<property name="name">
<cstring>unnamed</cstring>
</property>
<widget class="QRadioButton">
<property name="name">
<cstring>m_fileTagsButton</cstring>
</property>
<property name="text">
<string>Get example tags from this file:</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
<widget class="KURLRequester">
<property name="name">
<cstring>m_exampleFile</cstring>
</property>
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy>
<hsizetype>5</hsizetype>
<vsizetype>5</vsizetype>
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="mode">
<number>24</number>
</property>
</widget>
<widget class="QRadioButton">
<property name="name">
<cstring>m_manualTagsButton</cstring>
</property>
<property name="text">
<string>Enter example tags manually:</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
<widget class="QGroupBox">
<property name="name">
<cstring>m_manualGroup</cstring>
</property>
<property name="title">
<string>Example Tags</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<grid>
<property name="name">
<cstring>unnamed</cstring>
</property>
<widget class="QLineEdit" row="1" column="1">
<property name="name">
<cstring>m_exampleArtist</cstring>
</property>
<property name="text">
<string>Artist</string>
</property>
</widget>
<widget class="QLineEdit" row="0" column="1">
<property name="name">
<cstring>m_exampleTitle</cstring>
</property>
<property name="text">
<string>Title</string>
</property>
</widget>
<widget class="QLineEdit" row="2" column="1">
<property name="name">
<cstring>m_exampleAlbum</cstring>
</property>
<property name="text">
<string>Album</string>
</property>
</widget>
<widget class="QLabel" row="0" column="0">
<property name="name">
<cstring>textLabel1</cstring>
</property>
<property name="text">
<string>Title</string>
</property>
</widget>
<widget class="QLabel" row="1" column="0">
<property name="name">
<cstring>textLabel2</cstring>
</property>
<property name="text">
<string>Artist</string>
</property>
</widget>
<widget class="QSpinBox" row="5" column="1">
<property name="name">
<cstring>m_exampleYear</cstring>
</property>
<property name="maxValue">
<number>2006</number>
</property>
<property name="minValue">
<number>1901</number>
</property>
<property name="value">
<number>2004</number>
</property>
</widget>
<widget class="QLabel" row="2" column="0">
<property name="name">
<cstring>textLabel3</cstring>
</property>
<property name="text">
<string>Album</string>
</property>
</widget>
<widget class="QLabel" row="3" column="0">
<property name="name">
<cstring>textLabel4</cstring>
</property>
<property name="text">
<string>Genre</string>
</property>
</widget>
<widget class="QLabel" row="4" column="0">
<property name="name">
<cstring>textLabel5</cstring>
</property>
<property name="text">
<string>Track Number</string>
</property>
</widget>
<widget class="QSpinBox" row="4" column="1">
<property name="name">
<cstring>m_exampleTrack</cstring>
</property>
<property name="value">
<number>1</number>
</property>
</widget>
<widget class="QLineEdit" row="3" column="1">
<property name="name">
<cstring>m_exampleGenre</cstring>
</property>
<property name="text">
<string>Genre</string>
</property>
</widget>
<widget class="QLabel" row="5" column="0">
<property name="name">
<cstring>textLabel6</cstring>
</property>
<property name="text">
<string>Year</string>
</property>
</widget>
</grid>
</widget>
</vbox>
</widget>
</vbox>
</widget>
<customwidgets>
</customwidgets>
<connections>
<connection>
<sender>m_manualTagsButton</sender>
<signal>toggled(bool)</signal>
<receiver>m_manualGroup</receiver>
<slot>setEnabled(bool)</slot>
</connection>
<connection>
<sender>m_fileTagsButton</sender>
<signal>toggled(bool)</signal>
<receiver>m_exampleFile</receiver>
<slot>setEnabled(bool)</slot>
</connection>
<connection>
<sender>m_fileTagsButton</sender>
<signal>stateChanged(int)</signal>
<receiver>ExampleOptionsBase</receiver>
<slot>exampleSelectionChanged()</slot>
</connection>
<connection>
<sender>m_manualTagsButton</sender>
<signal>stateChanged(int)</signal>
<receiver>ExampleOptionsBase</receiver>
<slot>exampleSelectionChanged()</slot>
</connection>
<connection>
<sender>m_exampleTitle</sender>
<signal>textChanged(const QString&amp;)</signal>
<receiver>ExampleOptionsBase</receiver>
<slot>exampleDataChanged()</slot>
</connection>
<connection>
<sender>m_exampleArtist</sender>
<signal>textChanged(const QString&amp;)</signal>
<receiver>ExampleOptionsBase</receiver>
<slot>exampleDataChanged()</slot>
</connection>
<connection>
<sender>m_exampleAlbum</sender>
<signal>textChanged(const QString&amp;)</signal>
<receiver>ExampleOptionsBase</receiver>
<slot>exampleDataChanged()</slot>
</connection>
<connection>
<sender>m_exampleGenre</sender>
<signal>textChanged(const QString&amp;)</signal>
<receiver>ExampleOptionsBase</receiver>
<slot>exampleDataChanged()</slot>
</connection>
<connection>
<sender>m_exampleTrack</sender>
<signal>valueChanged(int)</signal>
<receiver>ExampleOptionsBase</receiver>
<slot>exampleDataChanged()</slot>
</connection>
<connection>
<sender>m_exampleYear</sender>
<signal>valueChanged(int)</signal>
<receiver>ExampleOptionsBase</receiver>
<slot>exampleDataChanged()</slot>
</connection>
</connections>
<signals>
<signal>dataChanged()</signal>
<signal>fileChanged()</signal>
</signals>
<slots>
<slot access="protected">exampleSelectionChanged()</slot>
<slot access="protected">exampleDataChanged()</slot>
<slot access="protected">exampleFileChanged()</slot>
</slots>
<layoutdefaults spacing="6" margin="11"/>
<includehints>
<includehint>kurlrequester.h</includehint>
<includehint>klineedit.h</includehint>
<includehint>kpushbutton.h</includehint>
</includehints>
</UI>
/*
* filerenamer.cpp - (c) 2003 Frerich Raabe <raabe@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 "filerenamer.h"
#include "playlistitem.h"
#include "tag.h"
/***************************************************************************
begin : Thu Oct 28 2004
copyright : (C) 2004 by Michael Pyne
: (c) 2003 Frerich Raabe <raabe@kde.org>
email : michael.pyne@kdemail.net
***************************************************************************/
/***************************************************************************
* *
* 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 <taglib/tstring.h>
#include <taglib/fileref.h>
#include <taglib/tag.h>
#include <algorithm>
#include <kapplication.h>
#include <kconfig.h>
#include <kdialogbase.h>
#include <kdebug.h>
#include <kcombobox.h>
#include <kurl.h>
#include <kurlrequester.h>
#include <kiconloader.h>
#include <knuminput.h>
#include <kstandarddirs.h>
#include <kio/netaccess.h>
#include <kconfigbase.h>
#include <kconfig.h>
#include <kglobal.h>
#include <klineedit.h>
#include <klocale.h>
#include <kmacroexpander.h>
#include <kmessagebox.h>
#include <kstandarddirs.h>
#include <kpushbutton.h>
#include <qdir.h>
#include <qfile.h>
#include <qhbox.h>
#include <qheader.h>
#include <qlabel.h>
#include <qvbox.h>
#include <qpainter.h>
#include <qsimplerichtext.h>
#include <qstylesheet.h>
#include <qscrollview.h>
#include <qobjectlist.h>
#include <qtimer.h>
#include <qregexp.h>
#include <qcheckbox.h>
#include <qdir.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qsignalmapper.h>
class FileRenamer::ConfirmationDialog : public KDialogBase
#include "tag.h"
#include "filehandle.h"
#include "filerenamer.h"
#include "exampleoptions.h"
#include "playlistitem.h"
//
// Implementation of ConfigCategoryReader
//
ConfigCategoryReader::ConfigCategoryReader() : CategoryReaderInterface(),
m_currentItem(0)
{
public:
ConfirmationDialog(const QMap<QString, QString> &files,
QWidget *parent = 0, const char *name = 0)
: KDialogBase(parent, name, true, i18n("Warning"), Ok | Cancel)
{
QVBox *vbox = makeVBoxMainWidget();
QHBox *hbox = new QHBox(vbox);
KConfigGroup config(KGlobal::config(), "FileRenamer");
QLabel *l = new QLabel(hbox);
l->setPixmap(SmallIcon("messagebox_warning", 32));
for(unsigned i = StartTag; i < NumTypes; ++i)
m_options[i] = TagRenamerOptions(static_cast<TagType>(i));
l = new QLabel(i18n("You are about to rename the following files. "
"Are you sure you want to continue?"), hbox);
hbox->setStretchFactor(l, 1);
for(unsigned i = 0; i < (NumTypes - 1); ++i)
m_folderSeparators[i] = false;
KListView *lv = new KListView(vbox);
QValueList<int> checkedSeparators = config.readIntListEntry("CheckedDirSeparators");
QValueList<int>::ConstIterator it = checkedSeparators.begin();
for(; it != checkedSeparators.end(); ++it)
if(*it > 0 && *it < (NumTypes - 1))
m_folderSeparators[*it] = true;
lv->addColumn(i18n("Original Name"));
lv->addColumn(i18n("New Name"));
m_musicDirectory = config.readPathEntry("MusicDirectory", "${HOME}/music");
m_separator = config.readEntry("Separator", " - ");
int lvHeight = 0;
checkedSeparators = config.readIntListEntry("CategoryOrder");
for(it = checkedSeparators.begin(); it != checkedSeparators.end(); ++it)
m_categoryOrder.append(static_cast<TagType>(*it));
}
QMap<QString, QString>::ConstIterator it = files.begin();
for(; it != files.end(); ++it) {
KListViewItem *i = it.key() != it.data()
? new KListViewItem(lv, it.key(), it.data())
: new KListViewItem(lv, it.key(), i18n("No Change"));
lvHeight += i->height();
}
QString ConfigCategoryReader::categoryValue(TagType type) const
{
if(!m_currentItem)
return "";
Tag *tag = m_currentItem->file().tag();
switch(type) {
case Track:
return FileRenamer::fixupTrack(QString::number(tag->track()), *this);
case Year:
return QString::number(tag->year());
case Title:
return tag->title();
case Artist:
return tag->artist();
case Album:
return tag->album();
lvHeight += lv->horizontalScrollBar()->height() + lv->header()->height();
lv->setMinimumHeight(QMIN(lvHeight, 400));
resize(QMIN(width(), 500), QMIN(minimumHeight(), 400));
case Genre:
return tag->genre();
default:
return "";
}
};
}
FileRenamer::Config::Config(KConfigBase *cfg)
: m_grp(cfg, "FileRenamer")
QString ConfigCategoryReader::prefix(TagType category) const
{
return m_options[category].prefix();
}
QString FileRenamer::Config::filenameScheme() const
QString ConfigCategoryReader::suffix(TagType category) const
{
return m_grp.readEntry("FilenameScheme", QDir::homeDirPath() + "/Music/%a%A%T%t%c");