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

0001 /****************************************************************************************
0002  * Copyright (c) 2008-2009 Nikolaj Hald Nielsen <nhn@kde.org>                           *
0003  * Copyright (c) 2009 Seb Ruiz <ruiz@kde.org>                                           *
0004  * Copyright (c) 2010 Oleksandr Khayrullin <saniokh@gmail.com>                          *
0005  *                                                                                      *
0006  * This program is free software; you can redistribute it and/or modify it under        *
0007  * the terms of the GNU General Public License as published by the Free Software        *
0008  * Foundation; either version 2 of the License, or (at your option) any later           *
0009  * version.                                                                             *
0010  *                                                                                      *
0011  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0012  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0013  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0014  *                                                                                      *
0015  * You should have received a copy of the GNU General Public License along with         *
0016  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0017  ****************************************************************************************/
0018 
0019 #include "LayoutManager.h"
0020 
0021 #include "core/support/Amarok.h"
0022 #include "core/support/Components.h"
0023 #include "core/support/Debug.h"
0024 #include "core/logger/Logger.h"
0025 #include "playlist/PlaylistDefines.h"
0026 #include "playlist/PlaylistModelStack.h"
0027 
0028 #include <KConfigGroup>
0029 #include <KMessageBox>
0030 
0031 #include <QDir>
0032 #include <QDomDocument>
0033 #include <QFile>
0034 #include <QStandardPaths>
0035 #include <QStringList>
0036 
0037 
0038 namespace Playlist {
0039 
0040 static const QString PREVIEW_LAYOUT = QStringLiteral("%%PREVIEW%%");
0041 LayoutManager* LayoutManager::s_instance = nullptr;
0042 
0043 LayoutManager* LayoutManager::instance()
0044 {
0045     if( !s_instance )
0046         s_instance = new LayoutManager();
0047     return s_instance;
0048 }
0049 
0050 LayoutManager::LayoutManager()
0051     : QObject()
0052 {
0053     DEBUG_BLOCK
0054 
0055     loadDefaultLayouts();
0056     loadUserLayouts();
0057     orderLayouts();
0058 
0059     KConfigGroup config = Amarok::config(QStringLiteral("Playlist Layout"));
0060     m_activeLayout = config.readEntry( "CurrentLayout", "Default" );
0061     if( !layouts().contains( m_activeLayout ) )
0062         m_activeLayout = QStringLiteral("Default");
0063     Playlist::ModelStack::instance()->groupingProxy()->setGroupingCategory( activeLayout().groupBy() );
0064 }
0065 
0066 QStringList LayoutManager::layouts() const
0067 {
0068     return m_layoutNames;
0069 }
0070 
0071 void LayoutManager::setActiveLayout( const QString &layout )
0072 {
0073     m_activeLayout = layout;
0074     Amarok::config( QStringLiteral("Playlist Layout") ).writeEntry( "CurrentLayout", m_activeLayout );
0075     Q_EMIT( activeLayoutChanged() );
0076 
0077     //Change the grouping style to that of this layout.
0078     Playlist::ModelStack::instance()->groupingProxy()->setGroupingCategory( activeLayout().groupBy() );
0079 
0080 }
0081 
0082 void LayoutManager::setPreviewLayout( const PlaylistLayout &layout )
0083 {
0084     DEBUG_BLOCK
0085     m_activeLayout = PREVIEW_LAYOUT;
0086     m_previewLayout = layout;
0087     Q_EMIT( activeLayoutChanged() );
0088 
0089     //Change the grouping style to that of this layout.
0090     Playlist::ModelStack::instance()->groupingProxy()->setGroupingCategory( activeLayout().groupBy() );
0091 }
0092 
0093 void LayoutManager::updateCurrentLayout( const PlaylistLayout &layout )
0094 {
0095     //Do not store preview layouts.
0096     if ( m_activeLayout == PREVIEW_LAYOUT )
0097         return;
0098 
0099     if ( m_layouts.value( m_activeLayout ).isEditable() )
0100     {
0101         addUserLayout( m_activeLayout, layout );
0102         setActiveLayout( m_activeLayout );
0103     }
0104     else
0105     {
0106         //create a writable copy of this layout. (Copy on Write)
0107         QString newLayoutName = i18n( "copy of %1", m_activeLayout );
0108         QString orgCopyName = newLayoutName;
0109 
0110         int copyNumber = 1;
0111         QStringList existingLayouts = LayoutManager::instance()->layouts();
0112         while( existingLayouts.contains( newLayoutName ) )
0113         {
0114             copyNumber++;
0115             newLayoutName = i18nc( "adds a copy number to a generated name if the name already exists, for instance 'copy of Foo 2' if 'copy of Foo' is taken", "%1 %2", orgCopyName, copyNumber );
0116         }
0117 
0118 
0119         Amarok::Logger::longMessage( i18n( "Current layout '%1' is read only. " \
0120                     "Creating a new layout '%2' with your changes and setting this as active",
0121                                                          m_activeLayout, newLayoutName )
0122                                                  );
0123 
0124         addUserLayout( newLayoutName, layout );
0125         setActiveLayout( newLayoutName );
0126     }
0127 }
0128 
0129 PlaylistLayout LayoutManager::activeLayout() const
0130 {
0131     if ( m_activeLayout == PREVIEW_LAYOUT )
0132         return m_previewLayout;
0133     return m_layouts.value( m_activeLayout );
0134 }
0135 
0136 void LayoutManager::loadUserLayouts()
0137 {
0138     QDir layoutsDir = QDir( Amarok::saveLocation( QStringLiteral("playlist_layouts/") ) );
0139 
0140     layoutsDir.setSorting( QDir::Name );
0141 
0142     QStringList filters;
0143     filters << QStringLiteral("*.xml") << QStringLiteral("*.XML");
0144     layoutsDir.setNameFilters( filters );
0145     layoutsDir.setSorting( QDir::Name );
0146 
0147     QFileInfoList list = layoutsDir.entryInfoList();
0148 
0149     for ( int i = 0; i < list.size(); ++i )
0150     {
0151         QFileInfo fileInfo = list.at(i);
0152         loadLayouts( layoutsDir.filePath( fileInfo.fileName() ), true );
0153     }
0154 }
0155 
0156 void LayoutManager::loadDefaultLayouts()
0157 {
0158     const QString dataLocation = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
0159                                                        QStringLiteral("amarok/data"),
0160                                                        QStandardPaths::LocateDirectory);
0161 
0162 
0163     QString configFile = dataLocation + QStringLiteral("/DefaultPlaylistLayouts.xml");
0164     loadLayouts( configFile, false );
0165 }
0166 
0167 
0168 void LayoutManager::loadLayouts( const QString &fileName, bool user )
0169 {
0170     DEBUG_BLOCK
0171     QDomDocument doc( QStringLiteral("layouts") );
0172 
0173     if ( !QFile::exists( fileName ) )
0174     {
0175         debug() << "file " << fileName << "does not exist";
0176         return;
0177     }
0178 
0179     QFile *file = new QFile( fileName );
0180     if( !file || !file->open( QIODevice::ReadOnly ) )
0181     {
0182         debug() << "error reading file " << fileName;
0183         return;
0184     }
0185     if ( !doc.setContent( file ) )
0186     {
0187         debug() << "error parsing file " << fileName;
0188         file->close();
0189         return ;
0190     }
0191     file->close();
0192     delete file;
0193 
0194     QDomElement layouts_element = doc.firstChildElement( QStringLiteral("playlist_layouts") );
0195     QDomNodeList layouts = layouts_element.elementsByTagName(QStringLiteral("layout"));
0196 
0197     int index = 0;
0198     while ( index < layouts.size() )
0199     {
0200         QDomNode layout = layouts.item( index );
0201         index++;
0202 
0203         QString layoutName = layout.toElement().attribute( QStringLiteral("name"), QLatin1String("") );
0204         debug() << "loading layout " << layoutName;
0205 
0206         PlaylistLayout currentLayout;
0207         currentLayout.setEditable( user );
0208         currentLayout.setInlineControls( layout.toElement().attribute( QStringLiteral("inline_controls"), QStringLiteral("false") ).compare( QLatin1String("true"), Qt::CaseInsensitive ) == 0 );
0209         currentLayout.setTooltips( layout.toElement().attribute( QStringLiteral("tooltips"), QStringLiteral("false") ).compare( QLatin1String("true"), Qt::CaseInsensitive ) == 0 );
0210 
0211         //For backwards compatibility, if a grouping is not set in the XML file assume "group by album" (which was previously the default)
0212         currentLayout.setGroupBy( layout.toElement().attribute( QStringLiteral("group_by"), QStringLiteral("Album") ) );
0213         debug() << "grouping mode is: " << layout.toElement().attribute( QStringLiteral("group_by"), QStringLiteral("Album") );
0214 
0215 
0216         currentLayout.setLayoutForPart( PlaylistLayout::Head, parseItemConfig( layout.toElement().firstChildElement( QStringLiteral("group_head") ) ) );
0217         currentLayout.setLayoutForPart( PlaylistLayout::StandardBody, parseItemConfig( layout.toElement().firstChildElement( QStringLiteral("group_body") ) ) );
0218         QDomElement variousArtistsXML = layout.toElement().firstChildElement( QStringLiteral("group_variousArtistsBody") );
0219         if ( !variousArtistsXML.isNull() )
0220             currentLayout.setLayoutForPart( PlaylistLayout::VariousArtistsBody, parseItemConfig( variousArtistsXML ) );
0221         else    // Handle old custom layout XMLs
0222             currentLayout.setLayoutForPart( PlaylistLayout::VariousArtistsBody, parseItemConfig( layout.toElement().firstChildElement( QStringLiteral("group_body") ) ) );
0223         currentLayout.setLayoutForPart( PlaylistLayout::Single, parseItemConfig( layout.toElement().firstChildElement( QStringLiteral("single_track") ) ) );
0224 
0225         if ( !layoutName.isEmpty() )
0226             m_layouts.insert( layoutName, currentLayout );
0227     }
0228 }
0229 
0230 LayoutItemConfig LayoutManager::parseItemConfig( const QDomElement &elem ) const
0231 {
0232     const bool showCover = ( elem.attribute( QStringLiteral("show_cover"), QStringLiteral("false") ).compare( QLatin1String("true"), Qt::CaseInsensitive ) == 0 );
0233     const int activeIndicatorRow = elem.attribute( QStringLiteral("active_indicator_row"), QStringLiteral("0") ).toInt();
0234 
0235     LayoutItemConfig config;
0236     config.setShowCover( showCover );
0237     config.setActiveIndicatorRow( activeIndicatorRow );
0238 
0239     QDomNodeList rows = elem.elementsByTagName(QStringLiteral("row"));
0240 
0241     int index = 0;
0242     while ( index < rows.size() )
0243     {
0244         QDomNode rowNode = rows.item( index );
0245         index++;
0246 
0247         LayoutItemConfigRow row;
0248 
0249         QDomNodeList elements = rowNode.toElement().elementsByTagName(QStringLiteral("element"));
0250 
0251         int index2 = 0;
0252         while ( index2 < elements.size() )
0253         {
0254             QDomNode elementNode = elements.item( index2 );
0255             index2++;
0256 
0257             int value = columnForName( elementNode.toElement().attribute( QStringLiteral("value"), QStringLiteral("Title") ) );
0258             QString prefix = elementNode.toElement().attribute( QStringLiteral("prefix"), QString() );
0259             QString sufix = elementNode.toElement().attribute( QStringLiteral("suffix"), QString() );
0260             qreal size = elementNode.toElement().attribute( QStringLiteral("size"), QStringLiteral("0.0") ).toDouble();
0261             bool bold = ( elementNode.toElement().attribute( QStringLiteral("bold"), QStringLiteral("false") ).compare( QLatin1String("true"), Qt::CaseInsensitive ) == 0 );
0262             bool italic = ( elementNode.toElement().attribute( QStringLiteral("italic"), QStringLiteral("false") ).compare( QLatin1String("true"), Qt::CaseInsensitive ) == 0 );
0263             bool underline = ( elementNode.toElement().attribute( QStringLiteral("underline"), QStringLiteral("false") ).compare( QLatin1String("true"), Qt::CaseInsensitive ) == 0 );
0264             QString alignmentString = elementNode.toElement().attribute( QStringLiteral("alignment"), QStringLiteral("left") );
0265             Qt::Alignment alignment;
0266 
0267 
0268             if ( alignmentString.compare( QLatin1String("left"), Qt::CaseInsensitive ) == 0 )
0269                 alignment = Qt::AlignLeft | Qt::AlignVCenter;
0270             else if ( alignmentString.compare( QLatin1String("right"), Qt::CaseInsensitive ) == 0 )
0271                  alignment = Qt::AlignRight| Qt::AlignVCenter;
0272             else
0273                 alignment = Qt::AlignCenter| Qt::AlignVCenter;
0274 
0275             row.addElement( LayoutItemConfigRowElement( value, size, bold, italic, underline,
0276                                                         alignment, prefix, sufix ) );
0277         }
0278 
0279         config.addRow( row );
0280     }
0281 
0282     return config;
0283 }
0284 
0285 PlaylistLayout LayoutManager::layout( const QString &layout ) const
0286 {
0287     return m_layouts.value( layout );
0288 }
0289 
0290 void LayoutManager::addUserLayout( const QString &name, PlaylistLayout layout )
0291 {
0292     layout.setEditable( true );
0293     if( m_layouts.find( name ) != m_layouts.end() )
0294         m_layouts.remove( name );
0295     else
0296         m_layoutNames.append( name );
0297 
0298     m_layouts.insert( name, layout );
0299 
0300 
0301     QDomDocument doc( QStringLiteral("layouts") );
0302     QDomElement layouts_element = doc.createElement( QStringLiteral("playlist_layouts") );
0303     QDomElement newLayout = doc.createElement( ("layout" ) );
0304     newLayout.setAttribute( QStringLiteral("name"), name );
0305 
0306     doc.appendChild( layouts_element );
0307     layouts_element.appendChild( newLayout );
0308 
0309     Q_EMIT( layoutListChanged() );
0310 
0311     QDomElement body = doc.createElement( QStringLiteral("body") );
0312     QDomElement single = doc.createElement( QStringLiteral("single") );
0313 
0314     newLayout.appendChild( createItemElement( doc, QStringLiteral("single_track"), layout.layoutForPart( PlaylistLayout::Single ) ) );
0315     newLayout.appendChild( createItemElement( doc, QStringLiteral("group_head"), layout.layoutForPart( PlaylistLayout::Head ) ) );
0316     newLayout.appendChild( createItemElement( doc, QStringLiteral("group_body"), layout.layoutForPart( PlaylistLayout::StandardBody ) ) );
0317     newLayout.appendChild( createItemElement( doc, QStringLiteral("group_variousArtistsBody"), layout.layoutForPart( PlaylistLayout::VariousArtistsBody ) ) );
0318 
0319     if( layout.inlineControls() )
0320         newLayout.setAttribute( QStringLiteral("inline_controls"), QStringLiteral("true") );
0321 
0322     if( layout.tooltips() )
0323         newLayout.setAttribute( QStringLiteral("tooltips"), QStringLiteral("true") );
0324 
0325     newLayout.setAttribute( QStringLiteral("group_by"), layout.groupBy() );
0326 
0327     QDir layoutsDir = QDir( Amarok::saveLocation( QStringLiteral("playlist_layouts/") ) );
0328 
0329     //make sure that this directory exists
0330     if ( !layoutsDir.exists() )
0331         layoutsDir.mkpath( Amarok::saveLocation( QStringLiteral("playlist_layouts/") ) );
0332 
0333     QFile file( layoutsDir.filePath( name + ".xml" ) );
0334     if ( !file.open(QIODevice::WriteOnly | QIODevice::Text) )
0335         return;
0336 
0337     QTextStream out( &file );
0338     out << doc.toString();
0339 }
0340 
0341 QDomElement LayoutManager::createItemElement( QDomDocument doc, const QString &name, const LayoutItemConfig & item ) const
0342 {
0343     QDomElement element = doc.createElement( name );
0344 
0345     QString showCover = item.showCover() ? "true" : "false";
0346     element.setAttribute ( QStringLiteral("show_cover"), showCover );
0347     element.setAttribute ( QStringLiteral("active_indicator_row"), QString::number( item.activeIndicatorRow() ) );
0348 
0349     for( int i = 0; i < item.rows(); i++ )
0350     {
0351         LayoutItemConfigRow row = item.row( i );
0352 
0353         QDomElement rowElement = doc.createElement( QStringLiteral("row") );
0354         element.appendChild( rowElement );
0355 
0356         for( int j = 0; j < row.count(); j++ ) {
0357             LayoutItemConfigRowElement element = row.element( j );
0358             QDomElement elementElement = doc.createElement( QStringLiteral("element") );
0359 
0360             elementElement.setAttribute ( QStringLiteral("prefix"), element.prefix() );
0361             elementElement.setAttribute ( QStringLiteral("suffix"), element.suffix() );
0362             elementElement.setAttribute ( QStringLiteral("value"), internalColumnName( static_cast<Playlist::Column>( element.value() ) ) );
0363             elementElement.setAttribute ( QStringLiteral("size"), QString::number( element.size() ) );
0364             elementElement.setAttribute ( QStringLiteral("bold"), element.bold() ? "true" : "false" );
0365             elementElement.setAttribute ( QStringLiteral("italic"), element.italic() ? "true" : "false" );
0366             elementElement.setAttribute ( QStringLiteral("underline"), element.underline() ? "true" : "false" );
0367 
0368             QString alignmentString;
0369             if ( element.alignment() & Qt::AlignLeft )
0370                 alignmentString = QStringLiteral("left");
0371             else  if ( element.alignment() & Qt::AlignRight )
0372                 alignmentString = QStringLiteral("right");
0373             else
0374                 alignmentString = QStringLiteral("center");
0375 
0376             elementElement.setAttribute ( QStringLiteral("alignment"), alignmentString );
0377 
0378             rowElement.appendChild( elementElement );
0379         }
0380     }
0381 
0382     return element;
0383 }
0384 
0385 bool LayoutManager::isDefaultLayout( const QString & layout ) const
0386 {
0387     if ( m_layouts.keys().contains( layout ) )
0388         return !m_layouts.value( layout ).isEditable();
0389 
0390     return false;
0391 }
0392 
0393 QString LayoutManager::activeLayoutName() const
0394 {
0395     return m_activeLayout;
0396 }
0397 
0398 void LayoutManager::deleteLayout( const QString &layout )
0399 {
0400     //check if layout is editable
0401     if ( m_layouts.value( layout ).isEditable() )
0402     {
0403         QDir layoutsDir = QDir( Amarok::saveLocation( QStringLiteral("playlist_layouts/") ) );
0404         QString xmlFile = layoutsDir.path() + QLatin1Char('/') + layout + ".xml";
0405 
0406         if ( !QFile::remove( xmlFile ) )
0407             debug() << "error deleting file" << xmlFile;
0408 
0409         m_layouts.remove( layout );
0410         m_layoutNames.removeAll( layout );
0411         Q_EMIT( layoutListChanged() );
0412 
0413         if ( layout == m_activeLayout )
0414             setActiveLayout( QStringLiteral("Default") );
0415     }
0416     else
0417         KMessageBox::error( nullptr, i18n( "The layout '%1' is one of the default layouts and cannot be deleted.", layout ), i18n( "Cannot Delete Default Layouts" ) );
0418 }
0419 
0420 bool LayoutManager::isDeleteable( const QString &layout ) const
0421 {
0422     return m_layouts.value( layout ).isEditable();
0423 }
0424 
0425 int LayoutManager::moveUp( const QString &layout )
0426 {
0427     int index = m_layoutNames.indexOf( layout );
0428     if ( index > 0 ) {
0429         m_layoutNames.swapItemsAt ( index, index - 1 );
0430         Q_EMIT( layoutListChanged() );
0431         storeLayoutOrdering();
0432         return index - 1;
0433     }
0434 
0435     return index;
0436 }
0437 
0438 int LayoutManager::moveDown( const QString &layout )
0439 {
0440     int index = m_layoutNames.indexOf( layout );
0441     if ( index < m_layoutNames.size() -1 ) {
0442         m_layoutNames.swapItemsAt ( index, index + 1 );
0443         Q_EMIT( layoutListChanged() );
0444         storeLayoutOrdering();
0445         return index + 1;
0446     }
0447 
0448     return index;
0449 }
0450 
0451 void LayoutManager::orderLayouts()
0452 {
0453     KConfigGroup config = Amarok::config( QStringLiteral("Playlist Layout") );
0454     QString orderString = config.readEntry( "Order", "Default" );
0455 
0456     QStringList knownLayouts = m_layouts.keys();
0457 
0458     QStringList orderingList = orderString.split( QLatin1Char(';'), Qt::SkipEmptyParts );
0459 
0460     foreach( const QString &layout, orderingList )
0461     {
0462         if ( knownLayouts.contains( layout ) )
0463         {
0464             //skip any layout names that are in config but that we don't know. Perhaps someone manually deleted a layout file
0465             m_layoutNames.append( layout );
0466             knownLayouts.removeAll( layout );
0467         }
0468     }
0469 
0470     //now add any layouts that were not in the order config to end of list:
0471     foreach( const QString &layout, knownLayouts )
0472         m_layoutNames.append( layout );
0473 }
0474 
0475 } //namespace Playlist
0476 
0477 void Playlist::LayoutManager::storeLayoutOrdering()
0478 {
0479 
0480     QString ordering;
0481 
0482     foreach( const QString &name, m_layoutNames )
0483     {
0484         ordering += name;
0485         ordering += ';';
0486     }
0487 
0488     if ( !ordering.isEmpty() )
0489         ordering.chop( 1 ); //remove trailing;
0490 
0491     KConfigGroup config = Amarok::config(QStringLiteral("Playlist Layout"));
0492     config.writeEntry( "Order", ordering );
0493 }
0494 
0495 
0496 
0497