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