File indexing completed on 2024-05-05 04:48:27

0001 /****************************************************************************************
0002  * Copyright (c) 2008 Téo Mrnjavac <teo@kde.org>                                        *
0003  * Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org>                                *
0004  * Copyright (c) 2009 Daniel Dewald <Daniel.Dewald@time.shift.de>                       *
0005  * Copyright (c) 2012 Ralf Engels <ralf-engels@gmx.de>                                  *
0006  *                                                                                      *
0007  * This program is free software; you can redistribute it and/or modify it under        *
0008  * the terms of the GNU General Public License as published by the Free Software        *
0009  * Foundation; either version 2 of the License, or (at your option) any later           *
0010  * version.                                                                             *
0011  *                                                                                      *
0012  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0013  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0014  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0015  *                                                                                      *
0016  * You should have received a copy of the GNU General Public License along with         *
0017  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0018  ****************************************************************************************/
0019 
0020 #include "TagGuesserDialog.h"
0021 #include "TagGuesser.h"
0022 
0023 #include "../widgets/TokenPool.h"
0024 
0025 #include "core/support/Amarok.h"
0026 #include "core/support/Debug.h"
0027 
0028 #include "MetaValues.h"
0029 #include "TagsFromFileNameGuesser.h"
0030 
0031 #include <QBoxLayout>
0032 #include <KConfigGroup>
0033 #include <QDialogButtonBox>
0034 #include <QPushButton>
0035 #include <QVBoxLayout>
0036 
0037 #define album_color       Qt::red
0038 #define albumartist_color Qt::blue
0039 #define artist_color      Qt::blue
0040 #define comment_color     Qt::gray
0041 #define composer_color    Qt::magenta
0042 #define genre_color       Qt::cyan
0043 #define title_color       Qt::green
0044 #define track_color       Qt::yellow
0045 #define discnr_color      Qt::yellow
0046 #define year_color        Qt::darkRed
0047 
0048 
0049 // -------------- TagGuessOptionWidget ------------
0050 TagGuessOptionWidget::TagGuessOptionWidget( QWidget *parent )
0051     : QWidget( parent )
0052 {
0053     setupUi( this );
0054 
0055     m_caseEditRadioButtons << rbAllUpper
0056         << rbAllLower
0057         << rbFirstLetter
0058         << rbTitleCase;
0059 
0060     int caseOptions = Amarok::config( "TagGuesser" ).readEntry( "Case options", 4 );
0061     if( !caseOptions )
0062         cbCase->setChecked( false );
0063     else
0064     {
0065         cbCase->setChecked( true );
0066         switch( caseOptions )
0067         {
0068         case 4:
0069             rbAllLower->setChecked( true );
0070             break;
0071         case 3:
0072             rbAllUpper->setChecked( true );
0073             break;
0074         case 2:
0075             rbFirstLetter->setChecked( true );
0076             break;
0077         case 1:
0078             rbTitleCase->setChecked( true );
0079             break;
0080         default:
0081             debug() << "OUCH";
0082         }
0083     }
0084 
0085     cbEliminateSpaces->setChecked(    Amarok::config( "TagGuesser" ).readEntry( "Eliminate trailing spaces", false ) );
0086     cbReplaceUnderscores->setChecked( Amarok::config( "TagGuesser" ).readEntry( "Replace underscores", false ) );
0087 
0088     connect( cbCase, &QCheckBox::toggled,
0089              this, &TagGuessOptionWidget::editStateEnable );
0090     connect( cbCase, &QCheckBox::toggled,
0091              this, &TagGuessOptionWidget::changed );
0092     connect( rbTitleCase, &QCheckBox::toggled,
0093              this, &TagGuessOptionWidget::changed );
0094     connect( rbFirstLetter, &QCheckBox::toggled,
0095              this, &TagGuessOptionWidget::changed );
0096     connect( rbAllLower, &QCheckBox::toggled,
0097              this, &TagGuessOptionWidget::changed );
0098     connect( rbAllUpper, &QCheckBox::toggled,
0099              this, &TagGuessOptionWidget::changed );
0100     connect( cbEliminateSpaces, &QCheckBox::toggled,
0101              this, &TagGuessOptionWidget::changed );
0102     connect( cbReplaceUnderscores, &QCheckBox::toggled,
0103              this, &TagGuessOptionWidget::changed );
0104 }
0105 
0106 void
0107 TagGuessOptionWidget::editStateEnable( bool checked )      //SLOT
0108 {
0109     foreach( QRadioButton *rb, m_caseEditRadioButtons )
0110         rb->setEnabled( checked );
0111 }
0112 
0113 //Returns a code for the configuration.
0114 int
0115 TagGuessOptionWidget::getCaseOptions()
0116 {
0117     //Amarok::config( "TagGuesser" ).readEntry( "Filename schemes", QStringList() );
0118     if( !cbCase->isChecked() )
0119         return 0;
0120     else
0121     {
0122         if( rbAllLower->isChecked() )
0123             return 4;
0124         else if( rbAllUpper->isChecked() )
0125             return 3;
0126         else if( rbFirstLetter->isChecked() )
0127             return 2;
0128         else if( rbTitleCase->isChecked() )
0129             return 1;
0130         else
0131         {
0132             debug() << "OUCH!";
0133             return 0;
0134         }
0135     }
0136 }
0137 
0138 //As above
0139 bool
0140 TagGuessOptionWidget::getWhitespaceOptions()
0141 {
0142     return cbEliminateSpaces->isChecked();
0143 }
0144 
0145 //As above
0146 bool
0147 TagGuessOptionWidget::getUnderscoreOptions()
0148 {
0149     return cbReplaceUnderscores->isChecked();
0150 }
0151 
0152 
0153 // ------------------------- TagGuesserWidget -------------------
0154 
0155 TagGuesserWidget::TagGuesserWidget( QWidget *parent )
0156     : FilenameLayoutWidget( parent )
0157 {
0158     m_configCategory = "FilenameLayoutWidget";
0159 
0160     m_tokenPool->addToken( createToken( Title ) );
0161     m_tokenPool->addToken( createToken( Artist ) );
0162     m_tokenPool->addToken( createToken( AlbumArtist ) );
0163     m_tokenPool->addToken( createToken( Album ) );
0164     m_tokenPool->addToken( createToken( Genre ) );
0165     m_tokenPool->addToken( createToken( Composer ) );
0166     m_tokenPool->addToken( createToken( Comment ) );
0167     m_tokenPool->addToken( createToken( Year ) );
0168     m_tokenPool->addToken( createToken( TrackNumber ) );
0169     m_tokenPool->addToken( createToken( DiscNumber ) );
0170     m_tokenPool->addToken( createToken( Ignore ) );
0171 
0172     m_tokenPool->addToken( createToken( Slash ) );
0173     m_tokenPool->addToken( createToken( Underscore ) );
0174     m_tokenPool->addToken( createToken( Dash ) );
0175     m_tokenPool->addToken( createToken( Dot ) );
0176     m_tokenPool->addToken( createToken( Space ) );
0177 
0178     m_syntaxLabel->setText( i18nc("Please do not translate the %foo% words as they define a syntax used internally by a parser to describe a filename.",
0179                           // xgettext: no-c-format
0180                           "The following tokens can be used to define a filename scheme:<br> \
0181                           <font color=\"%1\">%track%</font>, <font color=\"%2\">%title%</font>, \
0182                           <font color=\"%3\">%artist%</font>, <font color=\"%4\">%composer%</font>, \
0183                           <font color=\"%5\">%year%</font>, <font color=\"%6\">%album%</font>, \
0184                           <font color=\"%7\">%albumartist%</font>, <font color=\"%8\">%comment%</font>, \
0185                           <font color=\"%9\">%genre%</font>, %ignore%."
0186                           , QColor( track_color ).name(), QColor( title_color ).name(), QColor( artist_color ).name(), \
0187                           QColor( composer_color ).name(), QColor( year_color ).name(), QColor( album_color ).name(), QColor( albumartist_color ).name(), \
0188                           QColor( comment_color ).name(), QColor( genre_color ).name() ) );
0189 
0190     populateConfiguration();
0191 }
0192 
0193 Token*
0194 TagGuesserWidget::createToken(qint64 value) const
0195 {
0196     Token* token = FilenameLayoutWidget::createToken( value );
0197 
0198     // return colored tokens.
0199     QColor color = Qt::transparent;
0200     switch( value )
0201     {
0202     case TrackNumber: color = QColor( track_color ); break;
0203     case Title: color = QColor( title_color ); break;
0204     case Artist: color = QColor( artist_color ); break;
0205     case Composer: color = QColor( composer_color ); break;
0206     case Year: color = QColor( year_color ); break;
0207     case Album: color = QColor( album_color ); break;
0208     case AlbumArtist: color = QColor( albumartist_color ); break;
0209     case Comment: color = QColor( comment_color ); break;
0210     case Genre: color = QColor( genre_color );
0211     }
0212     if (color != Qt::transparent)
0213         token->setTextColor( color );
0214 
0215     return token;
0216 }
0217 
0218 // -------------- TagGuesserDialog ------------
0219 TagGuesserDialog::TagGuesserDialog( const QString &fileName, QWidget *parent )
0220     : QDialog( parent )
0221     , m_fileName( fileName )
0222 {
0223     setWindowTitle( i18n( "Guess Tags from Filename" ) );
0224 
0225     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, this);
0226     QWidget* mainWidget = new QWidget( this );
0227     QBoxLayout* mainLayout = new QVBoxLayout(this);
0228     QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
0229     okButton->setDefault(true);
0230     okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
0231     connect(buttonBox, &QDialogButtonBox::accepted, this, &TagGuesserDialog::accept);
0232     connect(buttonBox, &QDialogButtonBox::rejected, this, &TagGuesserDialog::reject);
0233 
0234     m_layoutWidget = new TagGuesserWidget( this );
0235     mainLayout->addWidget( m_layoutWidget );
0236 
0237     m_filenamePreview = new QLabel();
0238     m_filenamePreview->setAlignment( Qt::AlignHCenter );
0239     mainLayout->addWidget( m_filenamePreview );
0240 
0241     m_optionsWidget =  new TagGuessOptionWidget();
0242     mainLayout->addWidget( m_optionsWidget );
0243 
0244     connect( m_layoutWidget, &TagGuesserWidget::schemeChanged,
0245              this, &TagGuesserDialog::updatePreview );
0246     connect( m_optionsWidget, &TagGuessOptionWidget::changed,
0247              this, &TagGuesserDialog::updatePreview );
0248 
0249     updatePreview();
0250 
0251     mainLayout->addWidget(mainWidget);
0252     mainLayout->addWidget(buttonBox);
0253 }
0254 
0255 //Sets Filename for Preview
0256 void
0257 TagGuesserDialog::setFileName( const QString& fileName )
0258 {
0259     m_fileName = fileName;
0260     updatePreview();
0261 }
0262 
0263 //Stores the configuration when the dialog is accepted.
0264 void
0265 TagGuesserDialog::onAccept()    //SLOT
0266 {
0267     m_layoutWidget->onAccept();
0268 
0269     Amarok::config( "TagGuesser" ).writeEntry( "Case options", m_optionsWidget->getCaseOptions() );
0270     Amarok::config( "TagGuesser" ).writeEntry( "Eliminate trailing spaces", m_optionsWidget->getWhitespaceOptions() );
0271     Amarok::config( "TagGuesser" ).writeEntry( "Replace underscores", m_optionsWidget->getUnderscoreOptions() );
0272 }
0273 
0274 QMap<qint64,QString>
0275 TagGuesserDialog::guessedTags()
0276 {
0277     DEBUG_BLOCK;
0278 
0279     QString scheme = m_layoutWidget->getParsableScheme();
0280     QString fileName = getParsableFileName();
0281 
0282     if( scheme.isEmpty() )
0283         return QMap<qint64,QString>();
0284 
0285     TagGuesser guesser;
0286     guesser.setFilename( fileName );
0287     guesser.setCaseType( m_optionsWidget->getCaseOptions() );
0288     guesser.setConvertUnderscores( m_optionsWidget->getUnderscoreOptions() );
0289     guesser.setCutTrailingSpaces( m_optionsWidget->getWhitespaceOptions() );
0290     guesser.setSchema( scheme );
0291 
0292     if( !guesser.guess() )
0293     {
0294         m_filenamePreview->setText( getParsableFileName() );
0295         return QMap<qint64,QString>();
0296     }
0297 
0298     return guesser.tags();
0299 }
0300 
0301 
0302 //Updates the Filename Preview
0303 void
0304 TagGuesserDialog::updatePreview()                 //SLOT
0305 {
0306     DEBUG_BLOCK;
0307 
0308     QMap<qint64,QString> tags = guessedTags();
0309 
0310     m_filenamePreview->setText( coloredFileName( tags ) );
0311 
0312     QString emptyTagText = i18nc( "Text to represent an empty tag. Braces (<>) are only to clarify emptiness.", "&lt;empty&gt;" );
0313 
0314     quint64 fields[] = {
0315         Meta::valAlbum,
0316         Meta::valAlbumArtist,
0317         Meta::valTitle,
0318         Meta::valAlbum,
0319         Meta::valArtist,
0320         Meta::valComposer,
0321         Meta::valGenre,
0322         Meta::valComment,
0323         Meta::valTrackNr,
0324         Meta::valYear,
0325         0};
0326 
0327     QLabel *labels[] = {
0328         m_optionsWidget->Album_result,
0329         m_optionsWidget->AlbumArtist_result,
0330         m_optionsWidget->Title_result,
0331         m_optionsWidget->Album_result,
0332         m_optionsWidget->Artist_result,
0333         m_optionsWidget->Composer_result,
0334         m_optionsWidget->Genre_result,
0335         m_optionsWidget->Comment_result,
0336         m_optionsWidget->Track_result,
0337         m_optionsWidget->Year_result,
0338         nullptr};
0339 
0340     for( int i = 0; fields[i]; i++ )
0341     {
0342         if( tags.contains( fields[i] ) )
0343             labels[i]->setText( "<font color='" + TagGuesserDialog::fieldColor( fields[i] ) + "'>" + tags[ fields[i] ] + "</font>" );
0344         else
0345             labels[i]->setText( emptyTagText );
0346     }
0347 }
0348 
0349 QString
0350 TagGuesserDialog::parsableFileName( const QFileInfo &fileInfo ) const
0351 {
0352     DEBUG_BLOCK;
0353     QString path = fileInfo.absoluteFilePath();
0354 
0355     debug() << m_layoutWidget->getParsableScheme() << "; " << path;
0356 
0357     int schemaLevels = m_layoutWidget->getParsableScheme().count( QLatin1Char('/') );
0358     int pathLevels   = path.count( QLatin1Char('/') );
0359 
0360     // -- cut paths
0361     int pos;
0362     for( pos = 0; pathLevels > schemaLevels && pos < path.length(); pos++ )
0363         if( path[pos] == '/' )
0364             pathLevels--;
0365 
0366     // -- cut extension
0367     int dotPos = path.lastIndexOf( QLatin1Char('.') );
0368     if( dotPos >= 0 )
0369         dotPos -= pos;
0370 
0371 debug() << "parsableFileName schemaLevels:" << schemaLevels << "pathLevels:" << pathLevels << "path:" << path << "pos:" << pos << dotPos << path.mid( pos, dotPos );
0372     return path.mid( pos, dotPos );
0373 }
0374 
0375 QString
0376 TagGuesserDialog::getParsableFileName()
0377 {
0378     return parsableFileName( QFileInfo( m_fileName ) );
0379 }
0380 
0381 // creates a colored version of the filename
0382 QString
0383 TagGuesserDialog::coloredFileName( const QMap<qint64,QString> &tags )
0384 {
0385     QString coloredFileName = m_fileName;
0386 
0387     foreach( qint64 key, tags.keys() )
0388     {
0389         QString value = tags[key];
0390         // TODO: replace is not the right way to do this.
0391         coloredFileName.replace( value, "<font color=\"" + fieldColor( key ) +
0392                                  "\">" + value + "</font>", Qt::CaseInsensitive );
0393     }
0394     return coloredFileName;
0395 }
0396 
0397 QString
0398 TagGuesserDialog::fieldColor( qint64 field )
0399 {
0400     Qt::GlobalColor color;
0401     switch ( field )
0402     {
0403         case Meta::valAlbum:
0404             color = album_color;
0405             break;
0406 
0407         case Meta::valAlbumArtist:
0408             color = albumartist_color;
0409             break;
0410 
0411         case Meta::valArtist:
0412             color = artist_color;
0413             break;
0414 
0415         case Meta::valComment:
0416             color = comment_color;
0417             break;
0418 
0419         case Meta::valComposer:
0420             color = composer_color;
0421             break;
0422 
0423         case Meta::valDiscNr:
0424             color = discnr_color;
0425             break;
0426 
0427         case Meta::valGenre:
0428             color = genre_color;
0429             break;
0430 
0431         case Meta::valTitle:
0432             color = title_color;
0433             break;
0434 
0435         case Meta::valTrackNr:
0436             color = track_color;
0437             break;
0438 
0439         case Meta::valYear:
0440             color = year_color;
0441             break;
0442 
0443         default:
0444             color = Qt::black;
0445     }
0446 
0447     return QColor( color ).name();
0448 }