File indexing completed on 2024-05-05 04:48:30
0001 /**************************************************************************************** 0002 * Copyright (c) 2008 Daniel Jones <danielcjones@gmail.com> * 0003 * Copyright (c) 2009-2010 Leo Franchi <lfranchi@kde.org> * 0004 * Copyright (c) 2011 Ralf Engels <ralf-engels@gmx.de> * 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 #define DEBUG_PREFIX "DynamicModel" 0020 0021 #include "DynamicModel.h" 0022 0023 #include "App.h" 0024 0025 #include "Bias.h" 0026 #include "BiasFactory.h" 0027 #include "BiasedPlaylist.h" 0028 #include "biases/AlbumPlayBias.h" 0029 #include "biases/IfElseBias.h" 0030 #include "biases/PartBias.h" 0031 #include "biases/SearchQueryBias.h" 0032 #include "biases/TagMatchBias.h" 0033 #include "core/support/Amarok.h" 0034 #include "core/support/Debug.h" 0035 0036 #include "playlist/PlaylistActions.h" 0037 0038 #include <QIcon> 0039 0040 #include <QFile> 0041 #include <QBuffer> 0042 #include <QByteArray> 0043 #include <QDataStream> 0044 #include <QMimeData> 0045 #include <QXmlStreamReader> 0046 #include <QXmlStreamWriter> 0047 0048 /* general note: 0049 For the sake of this file we are handling a modified active playlist as 0050 a different one. 0051 */ 0052 0053 Dynamic::DynamicModel* Dynamic::DynamicModel::s_instance = nullptr; 0054 0055 Dynamic::DynamicModel* 0056 Dynamic::DynamicModel::instance() 0057 { 0058 if( !s_instance ) 0059 { 0060 s_instance = new DynamicModel( pApp ); 0061 s_instance->loadPlaylists(); 0062 } 0063 return s_instance; 0064 } 0065 0066 0067 Dynamic::DynamicModel::DynamicModel(QObject* parent) 0068 : QAbstractItemModel( parent ) 0069 , m_activePlaylistIndex( 0 ) 0070 { } 0071 0072 Dynamic::DynamicModel::~DynamicModel() 0073 { 0074 savePlaylists(); 0075 } 0076 0077 Dynamic::DynamicPlaylist* 0078 Dynamic::DynamicModel::setActivePlaylist( int index ) 0079 { 0080 if( index < 0 || index >= m_playlists.count() ) 0081 return m_playlists[m_activePlaylistIndex]; 0082 0083 if( m_activePlaylistIndex == index ) 0084 return m_playlists[m_activePlaylistIndex]; 0085 0086 Q_EMIT dataChanged( this->index( m_activePlaylistIndex, 0 ), 0087 this->index( m_activePlaylistIndex, 0 ) ); 0088 m_activePlaylistIndex = index; 0089 Q_EMIT dataChanged( this->index( m_activePlaylistIndex, 0 ), 0090 this->index( m_activePlaylistIndex, 0 ) ); 0091 0092 Q_EMIT activeChanged( index ); 0093 savePlaylists(); // save in between to prevent loosing too much in case of a crash 0094 0095 return m_playlists[m_activePlaylistIndex]; 0096 } 0097 0098 Dynamic::DynamicPlaylist* 0099 Dynamic::DynamicModel::activePlaylist() const 0100 { 0101 if( m_activePlaylistIndex < 0 || m_activePlaylistIndex >= m_playlists.count() ) 0102 return nullptr; 0103 0104 return m_playlists[m_activePlaylistIndex]; 0105 } 0106 0107 int 0108 Dynamic::DynamicModel::activePlaylistIndex() const 0109 { 0110 return m_activePlaylistIndex; 0111 } 0112 0113 int 0114 Dynamic::DynamicModel::playlistIndex( Dynamic::DynamicPlaylist* playlist ) const 0115 { 0116 return m_playlists.indexOf( playlist ); 0117 } 0118 0119 QModelIndex 0120 Dynamic::DynamicModel::insertPlaylist( int index, Dynamic::DynamicPlaylist* playlist ) 0121 { 0122 if( !playlist ) 0123 return QModelIndex(); 0124 0125 int oldIndex = playlistIndex( playlist ); 0126 bool wasActive = (oldIndex == m_activePlaylistIndex); 0127 0128 // -- remove the playlist if it was already in our model 0129 if( oldIndex >= 0 ) 0130 { 0131 beginRemoveRows( QModelIndex(), oldIndex, oldIndex ); 0132 m_playlists.removeAt( oldIndex ); 0133 endRemoveRows(); 0134 0135 if( oldIndex < index ) 0136 index--; 0137 0138 if( m_activePlaylistIndex > oldIndex ) 0139 m_activePlaylistIndex--; 0140 } 0141 0142 if( index < 0 ) 0143 index = 0; 0144 if( index > m_playlists.count() ) 0145 index = m_playlists.count(); 0146 0147 // -- insert it at the new position 0148 beginInsertRows( QModelIndex(), index, index ); 0149 0150 if( m_activePlaylistIndex > index ) 0151 m_activePlaylistIndex++; 0152 0153 if( wasActive ) 0154 m_activePlaylistIndex = index; 0155 0156 m_playlists.insert( index, playlist ); 0157 0158 endInsertRows(); 0159 0160 return this->index( index, 0 ); 0161 } 0162 0163 QModelIndex 0164 Dynamic::DynamicModel::insertBias( int row, const QModelIndex &parentIndex, const Dynamic::BiasPtr &bias ) 0165 { 0166 QObject* o = static_cast<QObject*>(parentIndex.internalPointer()); 0167 BiasedPlaylist* parentPlaylist = qobject_cast<BiasedPlaylist*>(o); 0168 AndBias* parentBias = qobject_cast<Dynamic::AndBias*>(o); 0169 AbstractBias* aBias = qobject_cast<Dynamic::AbstractBias*>(o); 0170 0171 // Add something directly to the top 0172 if( !parentIndex.isValid() ) 0173 { 0174 if( row >= 0 && row < m_playlists.count() ) 0175 { 0176 o = m_playlists[row]; 0177 parentPlaylist = qobject_cast<BiasedPlaylist*>(o); 0178 } 0179 else 0180 { 0181 return QModelIndex(); 0182 } 0183 } 0184 0185 if( parentPlaylist ) 0186 { 0187 // already have an AND bias 0188 if( parentPlaylist && qobject_cast<Dynamic::AndBias*>(parentPlaylist->bias().data()) ) 0189 { 0190 return insertBias( 0, index( parentPlaylist->bias() ), bias ); 0191 } 0192 else 0193 { 0194 // need a new AND bias 0195 parentBias = new Dynamic::AndBias(); 0196 Dynamic::BiasPtr b( parentPlaylist->bias() ); // ensure that the bias does not get freed 0197 parentPlaylist->bias()->replace( Dynamic::BiasPtr( parentBias ) ); 0198 parentBias->appendBias( b ); 0199 parentBias->appendBias( bias ); 0200 } 0201 } 0202 else if( parentBias ) 0203 { 0204 parentBias->appendBias( bias ); 0205 parentBias->moveBias( parentBias->biases().count()-1, row ); 0206 } 0207 else if( aBias ) 0208 { 0209 // insert the bias after 0210 return insertBias( parentIndex.row(), parentIndex.parent(), bias ); 0211 } 0212 return this->index( bias ); 0213 } 0214 0215 0216 Qt::DropActions 0217 Dynamic::DynamicModel::supportedDropActions() const 0218 { 0219 return Qt::MoveAction; 0220 // return Qt::CopyAction | Qt::MoveAction; 0221 } 0222 0223 // ok. the item model stuff is a little bit complicate 0224 // let's just pull it though and use Standard items the next time 0225 // see http://doc.qt.nokia.com/4.7/itemviews-simpletreemodel.html 0226 0227 // note to our indices: the internal pointer points to the object behind the index (not to it's parent) 0228 // row is the row number inside the parent. 0229 0230 QVariant 0231 Dynamic::DynamicModel::data( const QModelIndex& i, int role ) const 0232 { 0233 if( !i.isValid() ) 0234 return QVariant(); 0235 0236 int row = i.row(); 0237 int column = i.column(); 0238 if( row < 0 || column != 0 ) 0239 return QVariant(); 0240 0241 QObject* o = static_cast<QObject*>(i.internalPointer()); 0242 BiasedPlaylist* indexPlaylist = qobject_cast<BiasedPlaylist*>(o); 0243 AbstractBias* indexBias = qobject_cast<Dynamic::AbstractBias*>(o); 0244 0245 // level 1 0246 if( indexPlaylist ) 0247 { 0248 QString title = indexPlaylist->title(); 0249 0250 switch( role ) 0251 { 0252 case Qt::DisplayRole: 0253 return title; 0254 0255 case Qt::EditRole: 0256 return title; 0257 0258 case Qt::DecorationRole: 0259 if( activePlaylist() == indexPlaylist ) 0260 return QIcon::fromTheme( QStringLiteral("amarok_playlist") ); 0261 else 0262 return QIcon::fromTheme( QStringLiteral("amarok_playlist_clear") ); 0263 0264 case Qt::FontRole: 0265 { 0266 QFont font = QFont(); 0267 if( activePlaylist() == indexPlaylist ) 0268 font.setBold( true ); 0269 else 0270 font.setBold( false ); 0271 return font; 0272 } 0273 0274 case PlaylistRole: 0275 return QVariant::fromValue<QObject*>( indexPlaylist ); 0276 0277 default: 0278 return QVariant(); 0279 } 0280 } 0281 // level > 1 0282 else if( indexBias ) 0283 { 0284 switch( role ) 0285 { 0286 case Qt::DisplayRole: 0287 return QVariant(indexBias->toString()); 0288 // return QVariant(QStringLiteral("and: ")+indexBias->toString()); 0289 0290 case Qt::ToolTipRole: 0291 { 0292 // find the factory for the bias 0293 QList<Dynamic::AbstractBiasFactory*> factories = Dynamic::BiasFactory::factories(); 0294 foreach( Dynamic::AbstractBiasFactory* factory, factories ) 0295 { 0296 if( factory->name() == indexBias->name() ) 0297 return factory->i18nDescription(); 0298 } 0299 return QVariant(); 0300 } 0301 0302 case BiasRole: 0303 return QVariant::fromValue<QObject*>( indexBias ); 0304 0305 default: 0306 return QVariant(); 0307 } 0308 } 0309 // level 0 0310 else 0311 { 0312 return QVariant(); 0313 } 0314 } 0315 0316 bool 0317 Dynamic::DynamicModel::setData( const QModelIndex& index, const QVariant& value, int role ) 0318 { 0319 if( !index.isValid() ) 0320 return false; 0321 0322 int row = index.row(); 0323 int column = index.column(); 0324 if( row < 0 || column != 0 ) 0325 return false; 0326 0327 QObject* o = static_cast<QObject*>(index.internalPointer()); 0328 BiasedPlaylist* indexPlaylist = qobject_cast<BiasedPlaylist*>(o); 0329 // AbstractBias* indexBias = qobject_cast<Dynamic::AbstractBias*>(o); 0330 0331 // level 1 0332 if( indexPlaylist ) 0333 { 0334 switch( role ) 0335 { 0336 case Qt::EditRole: 0337 indexPlaylist->setTitle( value.toString() ); 0338 return true; 0339 0340 default: 0341 return false; 0342 } 0343 } 0344 0345 return false; 0346 } 0347 0348 0349 Qt::ItemFlags 0350 Dynamic::DynamicModel::flags( const QModelIndex& index ) const 0351 { 0352 Qt::ItemFlags defaultFlags = Qt::ItemIsDropEnabled; 0353 0354 if( !index.isValid() ) 0355 return defaultFlags; 0356 0357 int row = index.row(); 0358 int column = index.column(); 0359 if( row < 0 || column != 0 ) 0360 return defaultFlags; 0361 0362 QObject* o = static_cast<QObject*>(index.internalPointer()); 0363 BiasedPlaylist* indexPlaylist = qobject_cast<BiasedPlaylist*>(o); 0364 AbstractBias* indexBias = qobject_cast<Dynamic::AbstractBias*>(o); 0365 0366 // level 1 0367 if( indexPlaylist ) 0368 { 0369 return Qt::ItemIsSelectable | Qt::ItemIsEditable | 0370 Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | 0371 Qt::ItemIsUserCheckable | Qt::ItemIsEnabled; 0372 } 0373 // level > 1 0374 else if( indexBias ) 0375 { 0376 QModelIndex parentIndex = parent( index ); 0377 QObject* o2 = static_cast<QObject*>(parentIndex.internalPointer()); 0378 BiasedPlaylist* parentPlaylist = qobject_cast<BiasedPlaylist*>(o2); 0379 0380 // level 2 0381 if( parentPlaylist ) // you can't drag all the biases away from a playlist 0382 return Qt::ItemIsSelectable | /* Qt::ItemIsEditable | */ 0383 /* Qt::ItemIsDragEnabled | */ Qt::ItemIsDropEnabled | 0384 Qt::ItemIsUserCheckable | Qt::ItemIsEnabled; 0385 // level > 2 0386 else 0387 return Qt::ItemIsSelectable | /* Qt::ItemIsEditable | */ 0388 Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | 0389 Qt::ItemIsUserCheckable | Qt::ItemIsEnabled; 0390 } 0391 0392 return defaultFlags; 0393 } 0394 0395 QModelIndex 0396 Dynamic::DynamicModel::index( int row, int column, const QModelIndex& parent ) const 0397 { 0398 //ensure sanity of parameters 0399 //we are a tree model, there are no columns 0400 if( row < 0 || column != 0 ) 0401 return QModelIndex(); 0402 0403 QObject* o = static_cast<QObject*>(parent.internalPointer()); 0404 BiasedPlaylist* parentPlaylist = qobject_cast<BiasedPlaylist*>(o); 0405 AndBias* parentBias = qobject_cast<Dynamic::AndBias*>(o); 0406 0407 // level 1 0408 if( parentPlaylist ) 0409 { 0410 if( row >= 1 ) 0411 return QModelIndex(); 0412 else 0413 return createIndex( row, column, parentPlaylist->bias().data() ); 0414 } 0415 // level > 1 0416 else if( parentBias ) 0417 { 0418 if( row >= parentBias->biases().count() ) 0419 return QModelIndex(); 0420 else 0421 return createIndex( row, column, parentBias->biases().at( row ).data() ); 0422 } 0423 // level 0 0424 else 0425 { 0426 if( row >= m_playlists.count() ) 0427 return QModelIndex(); 0428 else 0429 return createIndex( row, column, m_playlists.at( row ) ); 0430 } 0431 } 0432 0433 QModelIndex 0434 Dynamic::DynamicModel::parent( int row, BiasedPlaylist* list, const BiasPtr &bias ) const 0435 { 0436 if( list->bias() == bias ) 0437 return createIndex( row, 0, list ); 0438 return parent( 0, list->bias(), bias ); 0439 } 0440 0441 QModelIndex 0442 Dynamic::DynamicModel::parent( int row, const BiasPtr &parent, const BiasPtr &bias ) const 0443 { 0444 Dynamic::AndBias* andBias = qobject_cast<Dynamic::AndBias*>(parent.data()); 0445 if( !andBias ) 0446 return QModelIndex(); 0447 0448 for( int i = 0; i < andBias->biases().count(); i++ ) 0449 { 0450 Dynamic::BiasPtr child = andBias->biases().at( i ); 0451 if( child == bias ) 0452 return createIndex( row, 0, andBias ); 0453 QModelIndex res = this->parent( i, child, bias ); 0454 if( res.isValid() ) 0455 return res; 0456 } 0457 return QModelIndex(); 0458 } 0459 0460 QModelIndex 0461 Dynamic::DynamicModel::parent(const QModelIndex& index) const 0462 { 0463 if( !index.isValid() ) 0464 return QModelIndex(); 0465 0466 QObject* o = static_cast<QObject*>( index.internalPointer() ); 0467 BiasedPlaylist* indexPlaylist = qobject_cast<BiasedPlaylist*>(o); 0468 BiasPtr indexBias( qobject_cast<AbstractBias*>(o) ); 0469 0470 if( indexPlaylist ) 0471 return QModelIndex(); // abstract root 0472 else if( indexBias ) 0473 { 0474 // search for the parent 0475 for( int i = 0; i < m_playlists.count(); i++ ) 0476 { 0477 QModelIndex res = parent( i, qobject_cast<BiasedPlaylist*>(m_playlists[i]), indexBias ); 0478 if( res.isValid() ) 0479 return res; 0480 } 0481 } 0482 return QModelIndex(); 0483 } 0484 0485 int 0486 Dynamic::DynamicModel::rowCount(const QModelIndex& parent) const 0487 { 0488 QObject* o = static_cast<QObject*>(parent.internalPointer()); 0489 BiasedPlaylist* parentPlaylist = qobject_cast<BiasedPlaylist*>(o); 0490 AndBias* parentBias = qobject_cast<Dynamic::AndBias*>(o); 0491 AbstractBias* bias = qobject_cast<Dynamic::AbstractBias*>(o); 0492 0493 // level 1 0494 if( parentPlaylist ) 0495 { 0496 return 1; 0497 } 0498 // level > 1 0499 else if( parentBias ) 0500 { 0501 return parentBias->biases().count(); 0502 } 0503 // for all other biases that are no And-Bias 0504 else if( bias ) 0505 { 0506 return 0; 0507 } 0508 // level 0 0509 else 0510 { 0511 return m_playlists.count(); 0512 } 0513 } 0514 0515 int 0516 Dynamic::DynamicModel::columnCount(const QModelIndex & parent) const 0517 { 0518 Q_UNUSED( parent ) 0519 return 1; 0520 } 0521 0522 QStringList 0523 Dynamic::DynamicModel::mimeTypes() const 0524 { 0525 QStringList types; 0526 types << QStringLiteral("application/amarok.biasModel.index"); 0527 return types; 0528 } 0529 0530 QMimeData* 0531 Dynamic::DynamicModel::mimeData(const QModelIndexList &indexes) const 0532 { 0533 // note: we only use the first index 0534 0535 if( indexes.isEmpty() ) 0536 return new QMimeData(); 0537 0538 QModelIndex index = indexes.first(); 0539 if( !index.isValid() ) 0540 return new QMimeData(); 0541 0542 // store the index in the mime data 0543 QByteArray bytes; 0544 QDataStream stream( &bytes, QIODevice::WriteOnly ); 0545 serializeIndex( &stream, index ); 0546 QMimeData *mimeData = new QMimeData(); 0547 mimeData->setData(QStringLiteral("application/amarok.biasModel.index"), bytes); 0548 return mimeData; 0549 } 0550 0551 bool 0552 Dynamic::DynamicModel::dropMimeData(const QMimeData *data, 0553 Qt::DropAction action, 0554 int row, int column, const QModelIndex &_parent) 0555 { 0556 Q_UNUSED( column ); 0557 0558 QModelIndex parent = _parent; 0559 0560 if( action == Qt::IgnoreAction ) 0561 return true; 0562 0563 if( data->hasFormat(QStringLiteral("application/amarok.biasModel.index")) ) 0564 { 0565 // get the source index from the mime data 0566 QByteArray bytes = data->data(QStringLiteral("application/amarok.biasModel.index")); 0567 QDataStream stream( &bytes, QIODevice::ReadOnly ); 0568 QModelIndex index = unserializeIndex( &stream ); 0569 0570 if( !index.isValid() ) 0571 return false; 0572 0573 QObject* o = static_cast<QObject*>(index.internalPointer()); 0574 BiasedPlaylist* indexPlaylist = qobject_cast<BiasedPlaylist*>(o); 0575 BiasPtr indexBias( qobject_cast<Dynamic::AbstractBias*>(o) ); 0576 0577 // in case of moving or inserting a playlist, we 0578 // move to the top level 0579 if( indexPlaylist ) 0580 { 0581 while( parent.isValid() ) 0582 { 0583 row = parent.row() + 1; 0584 column = parent.column(); 0585 parent = parent.parent(); 0586 } 0587 } 0588 0589 debug() << "dropMimeData action" << action; 0590 0591 // -- insert 0592 if( action == Qt::CopyAction ) 0593 { 0594 // -- playlist 0595 if( indexPlaylist ) 0596 { 0597 insertPlaylist( row, cloneList( indexPlaylist ) ); 0598 return true; 0599 } 0600 // -- bias 0601 else if( indexBias ) 0602 { 0603 insertBias( row, parent, cloneBias( indexBias ) ); 0604 return true; 0605 } 0606 } 0607 else if( action == Qt::MoveAction ) 0608 { 0609 // -- playlist 0610 if( indexPlaylist ) 0611 { 0612 insertPlaylist( row, indexPlaylist ); 0613 return true; 0614 } 0615 // -- bias 0616 else if( indexBias ) 0617 { 0618 indexBias->replace( Dynamic::BiasPtr() ); 0619 insertBias( row, parent, Dynamic::BiasPtr(indexBias) ); 0620 return true; 0621 } 0622 } 0623 } 0624 0625 return false; 0626 } 0627 0628 0629 QModelIndex 0630 Dynamic::DynamicModel::index( const Dynamic::BiasPtr &bias ) const 0631 { 0632 QModelIndex res; 0633 0634 // search for the parent 0635 for( int i = 0; i < m_playlists.count(); i++ ) 0636 { 0637 res = parent( i, qobject_cast<BiasedPlaylist*>(m_playlists[i]), bias ); 0638 if( res.isValid() ) 0639 break; 0640 } 0641 0642 if( !res.isValid() ) 0643 return res; 0644 0645 QObject* o = static_cast<QObject*>(res.internalPointer()); 0646 BiasedPlaylist* parentPlaylist = qobject_cast<BiasedPlaylist*>(o); 0647 AndBias* parentBias = qobject_cast<Dynamic::AndBias*>(o); 0648 0649 // level 1 0650 if( parentPlaylist ) 0651 { 0652 return createIndex( 0, 0, bias.data() ); 0653 } 0654 // level > 1 0655 else if( parentBias ) 0656 { 0657 return createIndex( parentBias->biases().indexOf( bias ), 0, bias.data() ); 0658 } 0659 else 0660 { 0661 return QModelIndex(); 0662 } 0663 } 0664 0665 QModelIndex 0666 Dynamic::DynamicModel::index( Dynamic::DynamicPlaylist* playlist ) const 0667 { 0668 return createIndex( playlistIndex( playlist ), 0, playlist ); 0669 } 0670 0671 0672 void 0673 Dynamic::DynamicModel::savePlaylists() 0674 { 0675 savePlaylists( QStringLiteral("dynamic.xml") ); 0676 } 0677 0678 void 0679 Dynamic::DynamicModel::loadPlaylists() 0680 { 0681 loadPlaylists( QStringLiteral("dynamic.xml") ); 0682 } 0683 0684 void 0685 Dynamic::DynamicModel::removeAt( const QModelIndex& index ) 0686 { 0687 if( !index.isValid() ) 0688 return; 0689 0690 QObject* o = static_cast<QObject*>(index.internalPointer()); 0691 BiasedPlaylist* indexPlaylist = qobject_cast<BiasedPlaylist*>(o); 0692 AbstractBias* indexBias = qobject_cast<Dynamic::AbstractBias*>(o); 0693 0694 // remove a playlist 0695 if( indexPlaylist ) 0696 { 0697 if( !indexPlaylist || !m_playlists.contains( indexPlaylist ) ) 0698 return; 0699 0700 int i = playlistIndex( indexPlaylist ); 0701 0702 beginRemoveRows( QModelIndex(), i, i ); 0703 m_playlists.removeAt(i); 0704 endRemoveRows(); 0705 0706 delete indexPlaylist; 0707 0708 if( m_playlists.isEmpty() ) 0709 { 0710 The::playlistActions()->enableDynamicMode( false ); 0711 m_activePlaylistIndex = 0; 0712 } 0713 else 0714 { 0715 setActivePlaylist( qBound(0, m_activePlaylistIndex, m_playlists.count() - 1 ) ); 0716 } 0717 } 0718 // remove a bias 0719 else if( indexBias ) 0720 { 0721 QModelIndex parentIndex = parent( index ); 0722 0723 QObject* o2 = static_cast<QObject*>(parentIndex.internalPointer()); 0724 BiasedPlaylist* parentPlaylist = qobject_cast<BiasedPlaylist*>(o2); 0725 AndBias* parentBias = qobject_cast<Dynamic::AndBias*>(o2); 0726 0727 // parent of the bias is a playlist 0728 if( parentPlaylist ) 0729 { 0730 // a playlist always needs a bias, so we can only remove this one 0731 // if we can come up with a replacement 0732 AndBias* andBias = qobject_cast<Dynamic::AndBias*>(indexBias); 0733 if( andBias && !andBias->biases().isEmpty() ) 0734 andBias->replace( andBias->biases().first() ); // replace by the first sub-bias 0735 else 0736 { 0737 ; // can't remove the last bias directly under a playlist 0738 } 0739 } 0740 // parent of the bias is another bias 0741 else if( parentBias ) 0742 { 0743 indexBias->replace( Dynamic::BiasPtr() ); // replace by nothing 0744 } 0745 } 0746 0747 savePlaylists(); 0748 } 0749 0750 0751 QModelIndex 0752 Dynamic::DynamicModel::cloneAt( const QModelIndex& index ) 0753 { 0754 DEBUG_BLOCK; 0755 0756 QObject* o = static_cast<QObject*>(index.internalPointer()); 0757 BiasedPlaylist* indexPlaylist = qobject_cast<BiasedPlaylist*>(o); 0758 BiasPtr indexBias( qobject_cast<Dynamic::AbstractBias*>(o) ); 0759 0760 if( indexPlaylist ) 0761 { 0762 return insertPlaylist( m_playlists.count(), cloneList( indexPlaylist ) ); 0763 } 0764 else if( indexBias ) 0765 { 0766 return insertBias( -1, index.parent(), cloneBias( indexBias ) ); 0767 } 0768 0769 return QModelIndex(); 0770 } 0771 0772 0773 QModelIndex 0774 Dynamic::DynamicModel::newPlaylist() 0775 { 0776 Dynamic::BiasedPlaylist *playlist = new Dynamic::BiasedPlaylist( this ); 0777 Dynamic::BiasPtr bias( new Dynamic::SearchQueryBias() ); 0778 playlist->setTitle( i18nc( "Default name for new playlists", "New playlist") ); 0779 playlist->bias()->replace( bias ); 0780 0781 return insertPlaylist( m_playlists.count(), playlist ); 0782 } 0783 0784 0785 bool 0786 Dynamic::DynamicModel::savePlaylists( const QString &filename ) 0787 { 0788 DEBUG_BLOCK; 0789 0790 QFile xmlFile( Amarok::saveLocation() + filename ); 0791 if( !xmlFile.open( QIODevice::WriteOnly ) ) 0792 { 0793 error() << "Can not write" << xmlFile.fileName(); 0794 return false; 0795 } 0796 0797 QXmlStreamWriter xmlWriter( &xmlFile ); 0798 xmlWriter.setAutoFormatting( true ); 0799 xmlWriter.writeStartDocument(); 0800 xmlWriter.writeStartElement(QStringLiteral("biasedPlaylists")); 0801 xmlWriter.writeAttribute(QStringLiteral("version"), QStringLiteral("2") ); 0802 xmlWriter.writeAttribute(QStringLiteral("current"), QString::number( m_activePlaylistIndex ) ); 0803 0804 foreach( Dynamic::DynamicPlaylist *playlist, m_playlists ) 0805 { 0806 xmlWriter.writeStartElement(QStringLiteral("playlist")); 0807 playlist->toXml( &xmlWriter ); 0808 xmlWriter.writeEndElement(); 0809 } 0810 0811 xmlWriter.writeEndElement(); 0812 xmlWriter.writeEndDocument(); 0813 0814 return true; 0815 } 0816 0817 bool 0818 Dynamic::DynamicModel::loadPlaylists( const QString &filename ) 0819 { 0820 // -- clear all the old playlists 0821 beginResetModel(); 0822 foreach( Dynamic::DynamicPlaylist* playlist, m_playlists ) 0823 delete playlist; 0824 m_playlists.clear(); 0825 0826 // -- open the file 0827 QFile xmlFile( Amarok::saveLocation() + filename ); 0828 if( !xmlFile.open( QIODevice::ReadOnly ) ) 0829 { 0830 error() << "Can not read" << xmlFile.fileName(); 0831 initPlaylists(); 0832 return false; 0833 } 0834 0835 QXmlStreamReader xmlReader( &xmlFile ); 0836 0837 // -- check the version 0838 xmlReader.readNextStartElement(); 0839 if( xmlReader.atEnd() || 0840 !xmlReader.isStartElement() || 0841 xmlReader.name() != QLatin1String("biasedPlaylists") || 0842 xmlReader.attributes().value( QLatin1String("version") ) != QLatin1String("2") ) 0843 { 0844 error() << "Playlist file" << xmlFile.fileName() << "is invalid or has wrong version"; 0845 initPlaylists(); 0846 return false; 0847 } 0848 0849 int newPlaylistIndex = xmlReader.attributes().value( QLatin1String("current") ).toString().toInt(); 0850 0851 while (!xmlReader.atEnd()) { 0852 xmlReader.readNext(); 0853 0854 if( xmlReader.isStartElement() ) 0855 { 0856 QStringRef name = xmlReader.name(); 0857 if( name == QLatin1String("playlist") ) 0858 { 0859 Dynamic::BiasedPlaylist *playlist = new Dynamic::BiasedPlaylist( &xmlReader, this ); 0860 if( playlist->bias() ) 0861 { 0862 insertPlaylist( m_playlists.count(), playlist ); 0863 } 0864 else 0865 { 0866 delete playlist; 0867 warning() << "Just read a playlist without bias from"<<xmlFile.fileName(); 0868 } 0869 } 0870 else 0871 { 0872 debug() << "Unexpected xml start element"<<name<<"in input"; 0873 xmlReader.skipCurrentElement(); 0874 } 0875 } 0876 0877 else if( xmlReader.isEndElement() ) 0878 { 0879 break; 0880 } 0881 } 0882 0883 // -- validate the index 0884 if( m_playlists.isEmpty() ) { 0885 error() << "Could not read the default playlist from" << xmlFile.fileName(); 0886 initPlaylists(); 0887 return false; 0888 } 0889 0890 m_activePlaylistIndex = qBound( 0, newPlaylistIndex, m_playlists.count()-1 ); 0891 0892 Q_EMIT activeChanged( m_activePlaylistIndex ); 0893 endResetModel(); 0894 0895 return true; 0896 } 0897 0898 void 0899 Dynamic::DynamicModel::initPlaylists() 0900 { 0901 // -- clear all the old playlists 0902 beginResetModel(); 0903 foreach( Dynamic::DynamicPlaylist* playlist, m_playlists ) 0904 delete playlist; 0905 m_playlists.clear(); 0906 0907 Dynamic::BiasedPlaylist *playlist; 0908 0909 // -- create the empty default random playlists 0910 0911 // - first one random playlist 0912 playlist = new Dynamic::BiasedPlaylist( this ); 0913 insertPlaylist( 0, playlist ); 0914 0915 // - a playlist demonstrating the SearchQueryBias 0916 playlist = new Dynamic::BiasedPlaylist( this ); 0917 playlist->setTitle( i18n("Rock and Pop") ); 0918 QString query = Meta::shortI18nForField( Meta::valGenre ) + QLatin1Char(':') + i18n( "Rock" ); 0919 /* following cannot be currently translated, see ExpressionParser::isAdvancedExpression() 0920 * and ExpressionParser::finishedToken() */ 0921 query += QLatin1String(" AND "); 0922 query += Meta::shortI18nForField( Meta::valGenre ) + QLatin1Char(':') + i18n( "Pop" ); 0923 playlist->bias()->replace( Dynamic::BiasPtr( new Dynamic::SearchQueryBias( query ) ) ); 0924 insertPlaylist( 1, playlist ); 0925 0926 // - a complex playlist demonstrating AlbumPlay and IfElse 0927 playlist = new Dynamic::BiasedPlaylist( this ); 0928 playlist->setTitle( i18n("Album play") ); 0929 Dynamic::IfElseBias *ifElse = new Dynamic::IfElseBias(); 0930 playlist->bias()->replace( Dynamic::BiasPtr( ifElse ) ); 0931 ifElse->appendBias( Dynamic::BiasPtr( new Dynamic::AlbumPlayBias() ) ); 0932 query = Meta::shortI18nForField( Meta::valTrackNr ) + ":1"; 0933 ifElse->appendBias( Dynamic::BiasPtr( new Dynamic::SearchQueryBias( query ) ) ); 0934 insertPlaylist( 2, playlist ); 0935 0936 // - a complex playlist demonstrating PartBias and TagMatchBias 0937 playlist = new Dynamic::BiasedPlaylist( this ); 0938 playlist->setTitle( i18nc( "Name of a dynamic playlist", "Rating" ) ); 0939 Dynamic::PartBias *part = new Dynamic::PartBias(); 0940 playlist->bias()->replace( Dynamic::BiasPtr( part ) ); 0941 0942 part->appendBias( Dynamic::BiasPtr( new Dynamic::RandomBias() ) ); 0943 0944 MetaQueryWidget::Filter ratingFilter; 0945 ratingFilter.setField( Meta::valRating ); 0946 ratingFilter.numValue = 5; 0947 ratingFilter.condition = MetaQueryWidget::GreaterThan; 0948 0949 Dynamic::TagMatchBias* ratingBias1 = new Dynamic::TagMatchBias(); 0950 Dynamic::BiasPtr ratingBias1Ptr( ratingBias1 ); 0951 ratingBias1->setFilter( ratingFilter ); 0952 part->appendBias( ratingBias1Ptr ); 0953 0954 ratingFilter.numValue = 8; 0955 Dynamic::TagMatchBias* ratingBias2 = new Dynamic::TagMatchBias(); 0956 Dynamic::BiasPtr ratingBias2Ptr( ratingBias2 ); 0957 ratingBias2->setFilter( ratingFilter ); 0958 part->appendBias( ratingBias2Ptr ); 0959 0960 part->changeBiasWeight( 2, 0.2 ); 0961 part->changeBiasWeight( 1, 0.5 ); 0962 0963 insertPlaylist( 3, playlist ); 0964 0965 0966 m_activePlaylistIndex = 0; 0967 0968 Q_EMIT activeChanged( m_activePlaylistIndex ); 0969 endResetModel(); 0970 } 0971 0972 void 0973 Dynamic::DynamicModel::serializeIndex( QDataStream *stream, const QModelIndex& index ) const 0974 { 0975 QList<int> rows; 0976 QModelIndex current = index; 0977 while( current.isValid() ) 0978 { 0979 rows.prepend( current.row() ); 0980 current = current.parent(); 0981 } 0982 0983 foreach( int row, rows ) 0984 *stream << row; 0985 *stream << -1; 0986 } 0987 0988 QModelIndex 0989 Dynamic::DynamicModel::unserializeIndex( QDataStream *stream ) const 0990 { 0991 QModelIndex result; 0992 do 0993 { 0994 int row; 0995 *stream >> row; 0996 if( row < 0 ) 0997 break; 0998 result = index( row, 0, result ); 0999 } while( result.isValid() ); 1000 return result; 1001 } 1002 1003 Dynamic::BiasedPlaylist* 1004 Dynamic::DynamicModel::cloneList( Dynamic::BiasedPlaylist* list ) 1005 { 1006 QByteArray bytes; 1007 QBuffer buffer( &bytes, nullptr ); 1008 buffer.open( QIODevice::ReadWrite ); 1009 1010 // write the list 1011 QXmlStreamWriter xmlWriter( &buffer ); 1012 xmlWriter.writeStartElement( QStringLiteral("playlist") ); 1013 list->toXml( &xmlWriter ); 1014 xmlWriter.writeEndElement(); 1015 1016 // and read a new list 1017 buffer.seek( 0 ); 1018 QXmlStreamReader xmlReader( &buffer ); 1019 while( !xmlReader.isStartElement() ) 1020 xmlReader.readNext(); 1021 return new Dynamic::BiasedPlaylist( &xmlReader, this ); 1022 } 1023 1024 Dynamic::BiasPtr 1025 Dynamic::DynamicModel::cloneBias( Dynamic::BiasPtr bias ) 1026 { 1027 return bias->clone(); 1028 } 1029 1030 void 1031 Dynamic::DynamicModel::playlistChanged( Dynamic::DynamicPlaylist* p ) 1032 { 1033 DEBUG_BLOCK; 1034 QModelIndex index = this->index( p ); 1035 Q_EMIT dataChanged( index, index ); 1036 } 1037 1038 void 1039 Dynamic::DynamicModel::biasChanged( const Dynamic::BiasPtr &b ) 1040 { 1041 QModelIndex index = this->index( b ); 1042 Q_EMIT dataChanged( index, index ); 1043 } 1044 1045 void 1046 Dynamic::DynamicModel::beginRemoveBias( Dynamic::BiasedPlaylist* parent ) 1047 { 1048 QModelIndex index = this->index( parent ); 1049 beginRemoveRows( index, 0, 0 ); 1050 } 1051 1052 void 1053 Dynamic::DynamicModel::beginRemoveBias( const Dynamic::BiasPtr &parent, int index ) 1054 { 1055 QModelIndex parentIndex = this->index( parent ); 1056 beginRemoveRows( parentIndex, index, index ); 1057 } 1058 1059 void 1060 Dynamic::DynamicModel::endRemoveBias() 1061 { 1062 endRemoveRows(); 1063 } 1064 1065 void 1066 Dynamic::DynamicModel::beginInsertBias( Dynamic::BiasedPlaylist* parent ) 1067 { 1068 QModelIndex index = this->index( parent ); 1069 beginInsertRows( index, 0, 0 ); 1070 } 1071 1072 1073 void 1074 Dynamic::DynamicModel::beginInsertBias( const Dynamic::BiasPtr &parent, int index ) 1075 { 1076 QModelIndex parentIndex = this->index( parent ); 1077 beginInsertRows( parentIndex, index, index ); 1078 } 1079 1080 void 1081 Dynamic::DynamicModel::endInsertBias() 1082 { 1083 endInsertRows(); 1084 } 1085 1086 void 1087 Dynamic::DynamicModel::beginMoveBias( const Dynamic::BiasPtr &parent, int from, int to ) 1088 { 1089 QModelIndex parentIndex = this->index( parent ); 1090 beginMoveRows( parentIndex, from, from, parentIndex, to ); 1091 } 1092 1093 void 1094 Dynamic::DynamicModel::endMoveBias() 1095 { 1096 endMoveRows(); 1097 } 1098 1099 1100 // --- debug methods 1101 1102 static QString 1103 biasToString( Dynamic::BiasPtr bias, int level ) 1104 { 1105 QString result; 1106 result += QStringLiteral(" ").repeated(level) + bias->toString() + ' ' + QString::number(quintptr(bias.data()), 16) + '\n'; 1107 if( Dynamic::AndBias* aBias = qobject_cast<Dynamic::AndBias*>(bias.data()) ) 1108 { 1109 foreach( Dynamic::BiasPtr bias2, aBias->biases() ) 1110 result += biasToString( bias2, level + 1 ); 1111 } 1112 return result; 1113 } 1114 1115 QString 1116 Dynamic::DynamicModel::toString() 1117 { 1118 QString result; 1119 1120 foreach( Dynamic::DynamicPlaylist* playlist, m_playlists ) 1121 { 1122 result += playlist->title() + ' ' + QString::number(quintptr(playlist), 16) + '\n'; 1123 if( Dynamic::BiasedPlaylist* bPlaylist = qobject_cast<Dynamic::BiasedPlaylist*>(playlist ) ) 1124 result += biasToString( bPlaylist->bias(), 1 ); 1125 } 1126 return result; 1127 } 1128