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