File indexing completed on 2024-05-05 04:49:26

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 "FilenameLayoutWidget.h"
0021 #include "TokenDropTarget.h"
0022 #include "TokenPool.h"
0023 
0024 #include "amarokconfig.h"
0025 
0026 #include "MetaValues.h"
0027 #include "core/support/Amarok.h"
0028 #include "core/support/Debug.h"
0029 #include "core/meta/support/MetaConstants.h"
0030 
0031 #include <QInputDialog>
0032 #include <QLineEdit>
0033 #include <KLocalizedString>
0034 
0035 #include <QComboBox>
0036 #include <QGroupBox>
0037 #include <QLabel>
0038 #include <QPushButton>
0039 #include <QBoxLayout>
0040 #include <QStackedWidget>
0041 
0042 // the order of these strings depends on the order of the
0043 // Type enum.
0044 static const QStringList typeElements = ( QStringList()
0045 << QString()
0046 << QLatin1String("%ignore%")
0047 << QLatin1String("%track%")
0048 << QLatin1String("%title%")
0049 << QLatin1String("%artist%")
0050 << QLatin1String("%composer%")
0051 << QLatin1String("%year%")
0052 << QLatin1String("%album%")
0053 << QLatin1String("%albumartist%")
0054 << QLatin1String("%comment%")
0055 << QLatin1String("%genre%")
0056 << QLatin1String("%filetype%")
0057 << QLatin1String("%folder%")
0058 << QLatin1String("%initial%")
0059 << QLatin1String("%discnumber%")
0060 << QLatin1String(" ")
0061 << QLatin1String("/")
0062 << QLatin1String(".")
0063 << QLatin1String("-")
0064 << QLatin1String("_")
0065 << QLatin1String("%collectionroot%") );
0066 
0067 using namespace Meta;
0068 
0069 // ------------------------- FilenameLayoutWidget -------------------
0070 
0071 FilenameLayoutWidget::FilenameLayoutWidget( QWidget *parent )
0072     : QWidget( parent )
0073     , m_advancedMode( false )
0074 {
0075     m_mainLayout = new QVBoxLayout( this );
0076     m_mainLayout->setContentsMargins( 0, 0, 0, 0 );
0077 
0078     QGroupBox* schemeGroup = new QGroupBox( i18n("Scheme"), this );
0079     QVBoxLayout* schemeGroupLayout = new QVBoxLayout( schemeGroup );
0080 
0081     // --- presets
0082     QHBoxLayout* presetLayout1 = new QHBoxLayout();
0083 
0084     QLabel* presetLabel = new QLabel( i18n("Preset:"), this );
0085     presetLayout1->addWidget( presetLabel, 0 );
0086 
0087     m_presetCombo = new QComboBox( this );
0088     m_presetCombo->setWhatsThis( i18n("A list of selectable filename scheme/format presets." ) );
0089     presetLayout1->addWidget( m_presetCombo, 1 );
0090 
0091     // - the preset buttons
0092     m_addPresetButton = new QPushButton( i18n("Add preset"), this );
0093     m_addPresetButton->setToolTip( i18n("Saves the current scheme/format as new preset.") );
0094     presetLayout1->addWidget( m_addPresetButton, 0 );
0095 
0096     m_updatePresetButton = new QPushButton( i18n("Update preset"), this );
0097     m_updatePresetButton->setToolTip( i18n("Updates the preset with the current scheme/format.") );
0098     presetLayout1->addWidget( m_updatePresetButton, 0 );
0099 
0100     m_removePresetButton = new QPushButton( i18n("Remove preset"), this );
0101     m_removePresetButton->setToolTip( i18n("Removes the currently selected preset.") );
0102     presetLayout1->addWidget( m_removePresetButton, 0 );
0103 
0104     schemeGroupLayout->addLayout( presetLayout1 );
0105 
0106     // --- stacked widget
0107     m_schemeStack = new QStackedWidget( this );
0108 
0109     // -- simple schema
0110     QWidget* simpleLayoutWidget = new QWidget( this );
0111     QVBoxLayout *simpleLayout = new QVBoxLayout( simpleLayoutWidget );
0112 
0113     // a token pool
0114     m_tokenPool = new TokenPool( this );
0115     simpleLayout->addWidget( m_tokenPool, 1 );
0116 
0117     // token drop target inside a frame
0118     QFrame* dropTargetFrame = new QFrame( this );
0119     dropTargetFrame->setFrameShape(QFrame::StyledPanel);
0120     dropTargetFrame->setFrameShadow(QFrame::Sunken);
0121     m_dropTarget = new TokenDropTarget( this );
0122     m_dropTarget->setRowLimit( 1 );
0123 
0124     m_schemaLineLayout = new QHBoxLayout();
0125     m_schemaLineLayout->setSpacing( 0 );
0126     m_schemaLineLayout->setContentsMargins( 0, 0, 0, 0 );
0127     m_schemaLineLayout->addWidget( m_dropTarget );
0128     dropTargetFrame->setLayout( m_schemaLineLayout );
0129     simpleLayout->addWidget( dropTargetFrame, 0 );
0130 
0131     m_schemeStack->addWidget( simpleLayoutWidget );
0132 
0133     // -- advanced schema
0134     QWidget* advancedLayoutWidget = new QWidget( this );
0135     QVBoxLayout *advancedLayout = new QVBoxLayout( advancedLayoutWidget );
0136 
0137     m_syntaxLabel = new QLabel( this ); // placeholder for format description
0138     advancedLayout->addWidget( m_syntaxLabel );
0139 
0140     m_filenameLayoutEdit = new QLineEdit( this );
0141     advancedLayout->addWidget( m_filenameLayoutEdit );
0142 
0143     m_schemeStack->addWidget( advancedLayoutWidget );
0144 
0145     schemeGroupLayout->addWidget( m_schemeStack );
0146 
0147     m_advancedButton = new QPushButton( i18n("Advanced"), this );
0148     schemeGroupLayout->addWidget( m_advancedButton );
0149 
0150     // --
0151 
0152     m_mainLayout->addWidget( schemeGroup );
0153 
0154     connect( m_tokenPool, &TokenPool::onDoubleClick,
0155              m_dropTarget, &TokenDropTarget::appendToken );
0156     connect( m_advancedButton, &QAbstractButton::clicked,
0157              this, &FilenameLayoutWidget::toggleAdvancedMode );
0158     connect( m_dropTarget, &TokenDropTarget::changed,
0159              this, &FilenameLayoutWidget::schemeChanged );
0160     connect( m_dropTarget, &TokenDropTarget::changed,
0161              this, &FilenameLayoutWidget::slotUpdatePresetButton );
0162     connect( m_addPresetButton, &QPushButton::clicked,
0163              this, &FilenameLayoutWidget::slotAddFormat );
0164     connect( m_removePresetButton, &QPushButton::clicked,
0165              this, &FilenameLayoutWidget::slotRemoveFormat );
0166     connect( m_updatePresetButton, &QPushButton::clicked,
0167              this, &FilenameLayoutWidget::slotUpdateFormat );
0168 
0169     connect( m_filenameLayoutEdit, &QLineEdit::textChanged,
0170              this, &FilenameLayoutWidget::schemeChanged );
0171     connect( m_filenameLayoutEdit, &QLineEdit::textChanged,
0172              this, &FilenameLayoutWidget::slotUpdatePresetButton );
0173 }
0174 
0175 Token*
0176 FilenameLayoutWidget::createToken(qint64 value) const
0177 {
0178     struct TokenDefinition
0179     {
0180         QString name;
0181         QString iconName;
0182         Type    value;
0183     };
0184 
0185     static const TokenDefinition tokenDefinitions[] = {
0186         { i18nForField( valTrackNr ),     iconForField( valTrackNr ),     TrackNumber },
0187         { i18nForField( valDiscNr ),      iconForField( valDiscNr ),      DiscNumber },
0188         { i18nForField( valTitle ),       iconForField( valTitle ),       Title },
0189         { i18nForField( valArtist ),      iconForField( valArtist ),      Artist },
0190         { i18nForField( valComposer ),    iconForField( valComposer ),    Composer },
0191         { i18nForField( valYear ),        iconForField( valYear ),        Year },
0192         { i18nForField( valAlbum ),       iconForField( valAlbum ),       Album },
0193         { i18nForField( valAlbumArtist ), iconForField( valAlbumArtist ), AlbumArtist },
0194         { i18nForField( valComment ),     iconForField( valComment ),     Comment },
0195         { i18nForField( valGenre ),       iconForField( valGenre ),       Genre },
0196         { i18nForField( valFormat ),      iconForField( valFormat ),      FileType },
0197 
0198         { i18n( "Ignore" ), "filename-ignore-amarok", Ignore },
0199         { i18n( "Folder" ), "filename-folder-amarok", Folder },
0200         { i18nc( "Artist's Initial", "Initial" ), "filename-initial-amarok", Initial },
0201 
0202         { "/", "filename-slash-amarok", Slash },
0203         { "_", "filename-underscore-amarok", Underscore },
0204         { "-", "filename-dash-amarok", Dash },
0205         { ".", "filename-dot-amarok", Dot },
0206         { " ", "filename-space-amarok", Space },
0207         { i18n( "Collection root" ), "drive-harddisk", CollectionRoot },
0208         { QString(), nullptr, Space }
0209     };
0210 
0211     for( int i = 0; !tokenDefinitions[i].name.isNull(); ++i )
0212     {
0213         if( value == tokenDefinitions[i].value )
0214         {
0215             return new Token( tokenDefinitions[i].name,
0216                               tokenDefinitions[i].iconName,
0217                               static_cast<qint64>(tokenDefinitions[i].value) );
0218         }
0219     }
0220 
0221     return nullptr;
0222 }
0223 
0224 Token*
0225 FilenameLayoutWidget::createStaticToken(qint64 value) const
0226 {
0227     Token* token = createToken( value );
0228     token->setEnabled( false );
0229     token->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
0230 
0231     return token;
0232 }
0233 
0234 //Stores the configuration when the dialog is accepted.
0235 void
0236 FilenameLayoutWidget::onAccept()    //SLOT
0237 {
0238     QString custom = getParsableScheme();
0239 
0240     // Custom scheme is stored per dialog
0241     debug() << "--- saving custom scheme for" << m_configCategory << custom;
0242     Amarok::config( m_configCategory ).writeEntry( "Custom Scheme", custom );
0243 }
0244 
0245 QString
0246 FilenameLayoutWidget::getParsableScheme() const
0247 {
0248     return m_advancedMode ? m_filenameLayoutEdit->text() : dropTargetScheme();
0249 }
0250 
0251 // attempts to set the scheme
0252 void FilenameLayoutWidget::setScheme(const QString& scheme)
0253 {
0254     m_filenameLayoutEdit->setText( scheme );
0255     inferScheme( scheme );
0256 
0257     slotUpdatePresetButton();
0258     Q_EMIT schemeChanged();
0259 }
0260 
0261 //Handles the modifications to the dialog to toggle between advanced and basic editing mode.
0262 void
0263 FilenameLayoutWidget::toggleAdvancedMode()
0264 {
0265     setAdvancedMode( !m_advancedMode );
0266 }
0267 
0268 //handles switching between basic and advanced mode
0269 void
0270 FilenameLayoutWidget::setAdvancedMode( bool isAdvanced )
0271 {
0272     setScheme( getParsableScheme() ); // setScheme set's both the edit and the drop target
0273     m_advancedMode = isAdvanced;
0274 
0275     if( isAdvanced )
0276     {
0277         m_advancedButton->setText( i18n( "&Basic..." ) );
0278         m_schemeStack->setCurrentIndex( 1 );
0279     }
0280     else // set Basic mode
0281     {
0282         m_advancedButton->setText( i18n( "&Advanced..." ) );
0283         m_schemeStack->setCurrentIndex( 0 );
0284     }
0285 
0286     QString entryValue  = m_advancedMode ? "Advanced" : "Basic";
0287 
0288     Amarok::config( m_configCategory ).writeEntry( "Mode", entryValue );
0289 }
0290 
0291 QString
0292 FilenameLayoutWidget::dropTargetScheme() const
0293 {
0294     QString parsableScheme = "";
0295 
0296     QList< Token *> list = m_dropTarget->tokensAtRow();
0297 
0298     foreach( Token *token, list )
0299     {
0300         parsableScheme += typeElements[token->value()];
0301     }
0302 
0303     return parsableScheme;
0304 }
0305 
0306 void
0307 FilenameLayoutWidget::inferScheme( const QString &s ) //SLOT
0308 {
0309     DEBUG_BLOCK
0310 
0311     debug() << "inferring scheme: " << s;
0312 
0313     m_dropTarget->clear();
0314     for( int i = 0; i < s.size(); )
0315     {
0316         // - search if there is a type with the matching string
0317         //   representation.
0318         bool found = false;
0319         for( int j = 1; j < typeElements.size() && !found; j++ )
0320         {
0321             int typeNameLength = typeElements[j].length();
0322             Type type = static_cast<Type>(j);
0323             if( s.midRef( i, typeNameLength ) == typeElements[j] )
0324             {
0325                 m_dropTarget->appendToken( createToken( type ) );
0326                 i += typeNameLength;
0327                 found = true;
0328             }
0329         }
0330 
0331         if( !found )
0332         {
0333             debug() << "'" << s.at(i) << "' can't be represented as TokenLayoutWidget Token";
0334             ++i; // skip junk
0335         }
0336     }
0337 }
0338 
0339 void
0340 FilenameLayoutWidget::populateConfiguration()
0341 {
0342     QString mode = Amarok::config( m_configCategory ).readEntry( "Mode" );
0343     setAdvancedMode( mode == QLatin1String( "Advanced" ) );
0344 
0345     // Custom scheme is stored per dialog
0346     QString custom = Amarok::config( m_configCategory ).readEntryUntranslated( "Custom Scheme" );
0347     debug() << "--- got custom scheme for" << m_configCategory << custom;
0348 
0349     populateFormatList( custom );
0350 
0351     setScheme( custom );
0352 }
0353 
0354 void
0355 FilenameLayoutWidget::populateFormatList( const QString& custom )
0356 {
0357     DEBUG_BLOCK
0358 
0359     // Configuration is not symmetric: dialog-specific settings are saved
0360     // using m_configCategory, that is different per dialog. The presets are saved
0361     // only in one single place, so these can be shared. This place is the "default" one,
0362     // that is the configuration for OrganizeCollectionDialog.
0363 
0364     // items are stored in the config list in the following format:
0365     // Label#DELIM#format string
0366     QStringList presets_raw;
0367     int selected_index = -1;
0368     m_presetCombo->clear();
0369     presets_raw = AmarokConfig::formatPresets(); // Always use the one in OrganizeCollectionDialog
0370     // presets_raw = Amarok::config( m_configCategory ).readEntry( QString::fromLatin1( "Format Presets" ), QStringList() );
0371 
0372     debug() << "--- got presets" << presets_raw;
0373 
0374     foreach( const QString &str, presets_raw )
0375     {
0376         QStringList items;
0377         items = str.split( "#DELIM#", Qt::SkipEmptyParts );
0378         if( items.size() < 2 )
0379             continue;
0380         m_presetCombo->addItem( items.at( 0 ), items.at( 1 ) ); // Label, format string
0381         if( items.at( 1 ) == custom )
0382             selected_index = m_presetCombo->findData( items.at( 1 ) );
0383     }
0384 
0385     if( selected_index >= 0 )
0386         m_presetCombo->setCurrentIndex( selected_index );
0387 
0388     connect( m_presetCombo, QOverload<int>::of(&QComboBox::activated),
0389              this, &FilenameLayoutWidget::slotFormatPresetSelected );
0390     connect( m_presetCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
0391              this, &FilenameLayoutWidget::slotFormatPresetSelected );
0392 }
0393 
0394 void
0395 FilenameLayoutWidget::saveFormatList() const
0396 {
0397     DEBUG_BLOCK
0398 
0399     QStringList presets_raw;
0400     int n = m_presetCombo->count();
0401 
0402     for( int i = 0; i < n; ++i )
0403     {
0404         QString item = "%1#DELIM#%2";
0405         QString scheme = m_presetCombo->itemData( i ).toString();
0406         QString label = m_presetCombo->itemText( i );
0407         item = item.arg( label, scheme );
0408         presets_raw.append( item );
0409     }
0410 
0411    debug() << "--- saving presets" << presets_raw;
0412    AmarokConfig::setFormatPresets( presets_raw ); // Always use the one in OrganizeCollectionDialog
0413    // Amarok::config( m_configCategory ).writeEntry( QString::fromLatin1( "Format Presets" ), presets_raw );
0414 }
0415 
0416 void
0417 FilenameLayoutWidget::slotUpdatePresetButton()
0418 {
0419     QString comboScheme = m_presetCombo->itemData( m_presetCombo->currentIndex() ).toString();
0420     m_updatePresetButton->setEnabled( comboScheme != getParsableScheme() );
0421 }
0422 
0423 void
0424 FilenameLayoutWidget::slotFormatPresetSelected( int index )
0425 {
0426     QString scheme = m_presetCombo->itemData( index ).toString();
0427     setScheme( scheme );
0428 }
0429 
0430 void
0431 FilenameLayoutWidget::slotAddFormat()
0432 {
0433     bool ok = false;
0434     QString name = QInputDialog::getText( this, i18n( "New Preset" ), i18n( "Preset Name" ), QLineEdit::Normal, i18n( "New Preset" ), &ok );
0435     if( !ok )
0436         return; // user canceled.
0437 
0438     QString format = getParsableScheme();
0439     m_presetCombo->addItem( name, format );
0440     m_presetCombo->setCurrentIndex( m_presetCombo->count() - 1 );
0441 
0442     saveFormatList();
0443 }
0444 
0445 void
0446 FilenameLayoutWidget::slotRemoveFormat()
0447 {
0448     int idx = m_presetCombo->currentIndex();
0449     m_presetCombo->removeItem( idx );
0450 
0451     saveFormatList();
0452 }
0453 
0454 void
0455 FilenameLayoutWidget::slotUpdateFormat()
0456 {
0457     int idx = m_presetCombo->currentIndex();
0458     QString formatString = getParsableScheme();
0459     m_presetCombo->setItemData( idx, formatString );
0460     m_updatePresetButton->setEnabled( false );
0461 
0462     saveFormatList();
0463 }