Fix several problems with new DynamicBias
[amarok:rengelss-amarok.git] / src / dynamic / DynamicModel.cpp
1 /****************************************************************************************
2  * Copyright (c) 2008 Daniel Jones <danielcjones@gmail.com>                             *
3  * Copyright (c) 2009-2010 Leo Franchi <lfranchi@kde.org>                               *
4  * Copyright (c) 2011 Ralf Engels <ralf-engels@gmx.de>                                  *
5  *                                                                                      *
6  * This program is free software; you can redistribute it and/or modify it under        *
7  * the terms of the GNU General Public License as published by the Free Software        *
8  * Foundation; either version 2 of the License, or (at your option) any later           *
9  * version.                                                                             *
10  *                                                                                      *
11  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
12  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
13  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
14  *                                                                                      *
15  * You should have received a copy of the GNU General Public License along with         *
16  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
17  ****************************************************************************************/
18
19 #define DEBUG_PREFIX "DynamicModel"
20
21 #include "DynamicModel.h"
22
23 #include "App.h"
24
25 #include "Bias.h"
26 #include "BiasFactory.h"
27 #include "BiasedPlaylist.h"
28 #include "biases/AlbumPlayBias.h"
29 #include "biases/IfElseBias.h"
30 #include "biases/PartBias.h"
31 #include "biases/SearchQueryBias.h"
32 #include "biases/TagMatchBias.h"
33 #include "core/support/Amarok.h"
34 #include "core/support/Debug.h"
35
36 #include "playlist/PlaylistActions.h"
37
38 #include <KIcon>
39
40 #include <QFile>
41 #include <QBuffer>
42 #include <QByteArray>
43 #include <QDataStream>
44 #include <QMimeData>
45 #include <QXmlStreamReader>
46 #include <QXmlStreamWriter>
47
48 #include <klocale.h>
49
50 /* general note:
51    For the sake of this file we are handling a modified active playlist as
52    a different one.
53 */
54
55 Dynamic::DynamicModel* Dynamic::DynamicModel::s_instance = 0;
56
57 Dynamic::DynamicModel*
58 Dynamic::DynamicModel::instance()
59 {
60     if( !s_instance )
61     {
62         s_instance = new DynamicModel( App::instance() );
63         s_instance->loadPlaylists();
64     }
65     return s_instance;
66 }
67
68
69 Dynamic::DynamicModel::DynamicModel(QObject* parent)
70     : QAbstractItemModel( parent )
71 { }
72
73 Dynamic::DynamicModel::~DynamicModel()
74 {
75     savePlaylists();
76 }
77
78 Dynamic::DynamicPlaylist*
79 Dynamic::DynamicModel::setActivePlaylist( int index )
80 {
81     if( index < 0 || index >= m_playlists.count() )
82         return m_playlists[m_activePlaylistIndex];
83
84     if( m_activePlaylistIndex == index )
85         return m_playlists[m_activePlaylistIndex];
86
87     emit dataChanged( this->index( m_activePlaylistIndex, 0 ),
88                       this->index( m_activePlaylistIndex, 0 ) );
89     m_activePlaylistIndex = index;
90     emit dataChanged( this->index( m_activePlaylistIndex, 0 ),
91                       this->index( m_activePlaylistIndex, 0 ) );
92
93     emit activeChanged( index );
94     savePlaylists(); // save in between to prevent loosing too much in case of a crash
95
96     return m_playlists[m_activePlaylistIndex];
97 }
98
99 Dynamic::DynamicPlaylist*
100 Dynamic::DynamicModel::activePlaylist() const
101 {
102     return m_playlists[m_activePlaylistIndex];
103 }
104
105 int
106 Dynamic::DynamicModel::activePlaylistIndex() const
107 {
108     return m_activePlaylistIndex;
109 }
110
111 int
112 Dynamic::DynamicModel::playlistIndex( Dynamic::DynamicPlaylist* playlist ) const
113 {
114     return m_playlists.indexOf( playlist );
115 }
116
117 QModelIndex
118 Dynamic::DynamicModel::insertPlaylist( int index, Dynamic::DynamicPlaylist* playlist )
119 {
120     if( !playlist )
121         return QModelIndex();
122
123     int oldIndex = playlistIndex( playlist );
124     bool wasActive = (oldIndex == m_activePlaylistIndex);
125
126     // -- remove the playlist if it was already in our model
127     if( oldIndex >= 0 )
128     {
129         beginRemoveRows( QModelIndex(), oldIndex, oldIndex );
130         m_playlists.removeAt( oldIndex );
131         endRemoveRows();
132
133         if( oldIndex < index )
134             index--;
135
136         if( m_activePlaylistIndex > oldIndex )
137             m_activePlaylistIndex--;
138     }
139
140     if( index < 0 )
141         index = 0;
142     if( index > m_playlists.count() )
143         index = m_playlists.count();
144
145     // -- insert it at the new position
146     beginInsertRows( QModelIndex(), index, index );
147
148     if( m_activePlaylistIndex > index )
149         m_activePlaylistIndex++;
150
151     if( wasActive )
152         m_activePlaylistIndex = index;
153
154     m_playlists.insert( index, playlist );
155
156     endInsertRows();
157
158     return this->index( index, 0 );
159 }
160
161 QModelIndex
162 Dynamic::DynamicModel::insertBias( int row, const QModelIndex &parentIndex, Dynamic::BiasPtr bias )
163 {
164     QObject* o = static_cast<QObject*>(parentIndex.internalPointer());
165     BiasedPlaylist* parentPlaylist = qobject_cast<BiasedPlaylist*>(o);
166     AndBias* parentBias = qobject_cast<Dynamic::AndBias*>(o);
167     AbstractBias* aBias = qobject_cast<Dynamic::AbstractBias*>(o);
168
169     // Add something directly to the top
170     if( !parentIndex.isValid() )
171     {
172         if( row >= 0 && row < m_playlists.count() )
173         {
174             o = m_playlists[row];
175             parentPlaylist = qobject_cast<BiasedPlaylist*>(o);
176         }
177         else
178         {
179             return QModelIndex();
180         }
181     }
182
183     debug() << "insert bias" << bias->toString() << "at" << parentIndex << row;
184
185     if( parentPlaylist )
186     {
187         // already have an AND bias
188         if( parentPlaylist && qobject_cast<Dynamic::AndBias*>(parentPlaylist->bias().data()) )
189         {
190             return insertBias( 0, index( parentPlaylist->bias() ), bias );
191         }
192         else
193         {
194             // need a new AND bias
195             parentBias = new Dynamic::AndBias();
196             Dynamic::BiasPtr b( parentPlaylist->bias() ); // ensure that the bias does not get freed
197             parentPlaylist->bias()->replace( Dynamic::BiasPtr( parentBias ) );
198             parentBias->appendBias( b );
199             parentBias->appendBias( bias );
200         }
201     }
202     else if( parentBias )
203     {
204         parentBias->appendBias( bias );
205         parentBias->moveBias( parentBias->biases().count()-1, row );
206     }
207     else if( aBias )
208     {
209         // insert the bias after
210         return insertBias( parentIndex.row(), parentIndex.parent(), bias );
211     }
212     return this->index( bias );
213 }
214
215
216 Qt::DropActions
217 Dynamic::DynamicModel::supportedDropActions() const
218 {
219     return Qt::MoveAction;
220     // return Qt::CopyAction | Qt::MoveAction;
221 }
222
223 // ok. the item model stuff is a little bit complicate
224 // let's just pull it though and use Standard items the next time
225 // see http://doc.qt.nokia.com/4.7/itemviews-simpletreemodel.html
226
227 // note to our indices: the internal pointer points to the object behind the index (not to it's parent)
228 // row is the row number inside the parent.
229
230 QVariant
231 Dynamic::DynamicModel::data( const QModelIndex& i, int role ) const
232 {
233     if( !i.isValid() )
234         return QVariant();
235
236     int row = i.row();
237     int column = i.column();
238     if( row < 0 || column != 0 )
239         return QVariant();
240
241     QObject* o = static_cast<QObject*>(i.internalPointer());
242     BiasedPlaylist* indexPlaylist = qobject_cast<BiasedPlaylist*>(o);
243     AbstractBias* indexBias = qobject_cast<Dynamic::AbstractBias*>(o);
244
245     // level 1
246     if( indexPlaylist )
247     {
248         QString title = indexPlaylist->title();
249
250         switch( role )
251         {
252         case Qt::DisplayRole:
253             return title;
254
255         case Qt::EditRole:
256             return title;
257
258         case Qt::DecorationRole:
259             if( activePlaylist() == indexPlaylist )
260                 return KIcon( "amarok_playlist" );
261             else
262                 return KIcon( "amarok_playlist_clear" );
263
264         case Qt::FontRole:
265             {
266                 QFont font = QFont();
267                 if( activePlaylist() == indexPlaylist )
268                     font.setBold( true );
269                 else
270                     font.setBold( false );
271                 return font;
272             }
273
274         case PlaylistRole:
275             return QVariant::fromValue<QObject*>( indexPlaylist );
276
277         default:
278             return QVariant();
279         }
280     }
281     // level > 1
282     else if( indexBias )
283     {
284         switch( role )
285         {
286         case Qt::DisplayRole:
287             return QVariant(indexBias->toString());
288             // return QVariant(QString("and: ")+indexBias->toString());
289
290         case Qt::ToolTipRole:
291             {
292                 // find the factory for the bias
293                 QList<Dynamic::AbstractBiasFactory*> factories = Dynamic::BiasFactory::factories();
294                 foreach( Dynamic::AbstractBiasFactory* factory, factories )
295                 {
296                     if( factory->name() == indexBias->name() )
297                         return factory->i18nDescription();
298                 }
299                 return QVariant();
300             }
301
302         case BiasRole:
303             return QVariant::fromValue<QObject*>( indexBias );
304
305         default:
306             return QVariant();
307         }
308     }
309     // level 0
310     else
311     {
312         return QVariant();
313     }
314 }
315
316 bool
317 Dynamic::DynamicModel::setData( const QModelIndex& index, const QVariant& value, int role )
318 {
319     if( !index.isValid() )
320         return false;
321
322     int row = index.row();
323     int column = index.column();
324     if( row < 0 || column != 0 )
325         return false;
326
327     QObject* o = static_cast<QObject*>(index.internalPointer());
328     BiasedPlaylist* indexPlaylist = qobject_cast<BiasedPlaylist*>(o);
329     // AbstractBias* indexBias = qobject_cast<Dynamic::AbstractBias*>(o);
330
331     // level 1
332     if( indexPlaylist )
333     {
334         switch( role )
335         {
336         case Qt::EditRole:
337             indexPlaylist->setTitle( value.toString() );
338             return true;
339
340         default:
341             return false;
342         }
343     }
344
345     return false;
346 }
347
348
349 Qt::ItemFlags
350 Dynamic::DynamicModel::flags( const QModelIndex& index ) const
351 {
352     Qt::ItemFlags defaultFlags = Qt::ItemIsDropEnabled;
353
354     if( !index.isValid() )
355         return defaultFlags;
356
357     int row = index.row();
358     int column = index.column();
359     if( row < 0 || column != 0 )
360         return defaultFlags;
361
362     QObject* o = static_cast<QObject*>(index.internalPointer());
363     BiasedPlaylist* indexPlaylist = qobject_cast<BiasedPlaylist*>(o);
364     AbstractBias* indexBias = qobject_cast<Dynamic::AbstractBias*>(o);
365
366     // level 1
367     if( indexPlaylist )
368     {
369         return Qt::ItemIsSelectable | Qt::ItemIsEditable |
370             Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled |
371             Qt::ItemIsUserCheckable | Qt::ItemIsEnabled;
372     }
373     // level > 1
374     else if( indexBias )
375     {
376         QModelIndex parentIndex = parent( index );
377         QObject* o2 = static_cast<QObject*>(parentIndex.internalPointer());
378         BiasedPlaylist* parentPlaylist = qobject_cast<BiasedPlaylist*>(o2);
379
380         // level 2
381         if( parentPlaylist ) // you can't drag all the biases away from a playlist
382             return Qt::ItemIsSelectable | /* Qt::ItemIsEditable | */
383                 /* Qt::ItemIsDragEnabled | */ Qt::ItemIsDropEnabled |
384                 Qt::ItemIsUserCheckable | Qt::ItemIsEnabled;
385         // level > 2
386         else
387             return Qt::ItemIsSelectable | /* Qt::ItemIsEditable | */
388                 Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled |
389                 Qt::ItemIsUserCheckable | Qt::ItemIsEnabled;
390     }
391
392     return defaultFlags;
393 }
394
395 QModelIndex
396 Dynamic::DynamicModel::index( int row, int column, const QModelIndex& parent ) const
397 {
398     //ensure sanity of parameters
399     //we are a tree model, there are no columns
400     if( row < 0 || column != 0 )
401         return QModelIndex();
402
403     QObject* o = static_cast<QObject*>(parent.internalPointer());
404     BiasedPlaylist* parentPlaylist = qobject_cast<BiasedPlaylist*>(o);
405     AndBias* parentBias = qobject_cast<Dynamic::AndBias*>(o);
406
407     // level 1
408     if( parentPlaylist )
409     {
410         if( row >= 1 )
411             return QModelIndex();
412         else
413             return createIndex( row, column, parentPlaylist->bias().data() );
414     }
415     // level > 1
416     else if( parentBias )
417     {
418         if( row >= parentBias->biases().count() )
419             return QModelIndex();
420         else
421             return createIndex( row, column, parentBias->biases().at( row ).data() );
422     }
423     // level 0
424     else
425     {
426         if( row >= m_playlists.count() )
427             return QModelIndex();
428         else
429             return createIndex( row, column, m_playlists.at( row ) );
430     }
431 }
432
433 QModelIndex
434 Dynamic::DynamicModel::parent( int row, BiasedPlaylist* list, BiasPtr bias ) const
435 {
436     if( list->bias() == bias )
437         return createIndex( row, 0, list );
438     return parent( 0, list->bias(), bias );
439 }
440
441 QModelIndex
442 Dynamic::DynamicModel::parent( int row, BiasPtr parent, BiasPtr bias ) const
443 {
444     Dynamic::AndBias* andBias = qobject_cast<Dynamic::AndBias*>(parent.data());
445     if( !andBias )
446         return QModelIndex();
447
448     for( int i = 0; i < andBias->biases().count(); i++ )
449     {
450         Dynamic::BiasPtr child = andBias->biases().at( i );
451         if( child == bias )
452             return createIndex( row, 0, andBias );
453         QModelIndex res = this->parent( i, child, bias );
454         if( res.isValid() )
455             return res;
456     }
457     return QModelIndex();
458 }
459
460 QModelIndex
461 Dynamic::DynamicModel::parent(const QModelIndex& index) const
462 {
463     if( !index.isValid() )
464         return QModelIndex();
465
466     QObject* o = static_cast<QObject*>( index.internalPointer() );
467     BiasedPlaylist* indexPlaylist = qobject_cast<BiasedPlaylist*>(o);
468     BiasPtr indexBias( qobject_cast<AbstractBias*>(o) );
469
470     if( indexPlaylist )
471         return QModelIndex(); // abstract root
472     else if( indexBias )
473     {
474         // search for the parent
475         for( int i = 0; i < m_playlists.count(); i++ )
476         {
477             QModelIndex res = parent( i, qobject_cast<BiasedPlaylist*>(m_playlists[i]), indexBias );
478             if( res.isValid() )
479                 return res;
480         }
481     }
482     return QModelIndex();
483 }
484
485 int
486 Dynamic::DynamicModel::rowCount(const QModelIndex& parent) const
487 {
488     QObject* o = static_cast<QObject*>(parent.internalPointer());
489     BiasedPlaylist* parentPlaylist = qobject_cast<BiasedPlaylist*>(o);
490     AndBias* parentBias = qobject_cast<Dynamic::AndBias*>(o);
491     AbstractBias* bias = qobject_cast<Dynamic::AbstractBias*>(o);
492
493     // level 1
494     if( parentPlaylist )
495     {
496         return 1;
497     }
498     // level > 1
499     else if( parentBias )
500     {
501         return parentBias->biases().count();
502     }
503     // for all other biases that are no And-Bias
504     else if( bias )
505     {
506         return 0;
507     }
508     // level 0
509     else
510     {
511         return m_playlists.count();
512     }
513 }
514
515 int
516 Dynamic::DynamicModel::columnCount(const QModelIndex & parent) const
517 {
518     Q_UNUSED( parent )
519     return 1;
520 }
521
522 QStringList
523 Dynamic::DynamicModel::mimeTypes() const
524 {
525     QStringList types;
526     types << "application/amarok.biasModel.index";
527     return types;
528 }
529
530 QMimeData*
531 Dynamic::DynamicModel::mimeData(const QModelIndexList &indexes) const
532 {
533     // note: we only use the first index
534
535     if( indexes.isEmpty() )
536         return new QMimeData();
537
538     QModelIndex index = indexes.first();
539     if( !index.isValid() )
540         return new QMimeData();
541
542     // store the index in the mime data
543     QByteArray bytes;
544     QDataStream stream( &bytes, QIODevice::WriteOnly );
545     serializeIndex( &stream, index );
546     QMimeData *mimeData = new QMimeData();
547     mimeData->setData("application/amarok.biasModel.index", bytes);
548     return mimeData;
549 }
550
551 bool
552 Dynamic::DynamicModel::dropMimeData(const QMimeData *data,
553                                     Qt::DropAction action,
554                                     int row, int column, const QModelIndex &_parent)
555 {
556     QModelIndex parent = _parent;
557
558     if( action == Qt::IgnoreAction )
559         return true;
560
561 debug() << "dropMimeData";
562 debug() << toString();
563
564     if( data->hasFormat("application/amarok.biasModel.index") )
565     {
566         // get the source index from the mime data
567         QByteArray bytes = data->data("application/amarok.biasModel.index");
568         QDataStream stream( &bytes, QIODevice::ReadOnly );
569         QModelIndex index = unserializeIndex( &stream );
570
571 debug() << "dropMimeData at index" << index << row;
572
573         if( !index.isValid() )
574             return false;
575
576         QObject* o = static_cast<QObject*>(index.internalPointer());
577         BiasedPlaylist* indexPlaylist = qobject_cast<BiasedPlaylist*>(o);
578         BiasPtr indexBias( qobject_cast<Dynamic::AbstractBias*>(o) );
579
580         // in case of moving or inserting a playlist, we
581         // move to the top level
582         if( indexPlaylist )
583         {
584             while( parent.isValid() )
585             {
586                 row = parent.row() + 1;
587                 column = parent.column();
588                 parent = parent.parent();
589             }
590         }
591
592 debug() << "dropMimeData action" << action;
593
594         // -- insert
595         if( action == Qt::CopyAction )
596         {
597             // -- playlist
598             if( indexPlaylist )
599             {
600                 insertPlaylist( row, cloneList( indexPlaylist ) );
601                 return true;
602             }
603             // -- bias
604             else if( indexBias )
605             {
606                 insertBias( row, parent, cloneBias( indexBias ) );
607                 return true;
608             }
609         }
610         else if( action == Qt::MoveAction )
611         {
612             // -- playlist
613             if( indexPlaylist )
614             {
615                 insertPlaylist( row, indexPlaylist );
616                 return true;
617             }
618             // -- bias
619             else if( indexBias )
620             {
621                 indexBias->replace( Dynamic::BiasPtr() );
622                 insertBias( row, parent, Dynamic::BiasPtr(indexBias) );
623                 return true;
624             }
625         }
626     }
627
628     return false;
629 }
630
631
632 QModelIndex
633 Dynamic::DynamicModel::index( Dynamic::BiasPtr bias ) const
634 {
635     QModelIndex res;
636
637     // search for the parent
638     for( int i = 0; i < m_playlists.count(); i++ )
639     {
640         res = parent( i, qobject_cast<BiasedPlaylist*>(m_playlists[i]), bias );
641         if( res.isValid() )
642             break;
643     }
644
645     if( !res.isValid() )
646         return res;
647
648     QObject* o = static_cast<QObject*>(res.internalPointer());
649     BiasedPlaylist* parentPlaylist = qobject_cast<BiasedPlaylist*>(o);
650     AndBias* parentBias = qobject_cast<Dynamic::AndBias*>(o);
651
652     // level 1
653     if( parentPlaylist )
654     {
655         return createIndex( 0, 0, bias.data() );
656     }
657     // level > 1
658     else if( parentBias )
659     {
660         return createIndex( parentBias->biases().indexOf( bias ), 0, bias.data() );
661     }
662     else
663     {
664         return QModelIndex();
665     }
666 }
667
668 QModelIndex
669 Dynamic::DynamicModel::index( Dynamic::DynamicPlaylist* playlist ) const
670 {
671     return createIndex( playlistIndex( playlist ), 0, playlist );
672 }
673
674
675 void
676 Dynamic::DynamicModel::savePlaylists()
677 {
678     savePlaylists( "dynamic.xml" );
679 }
680
681 void
682 Dynamic::DynamicModel::loadPlaylists()
683 {
684     loadPlaylists( "dynamic.xml" );
685 }
686
687 void
688 Dynamic::DynamicModel::removeAt( const QModelIndex& index )
689 {
690     if( !index.isValid() )
691         return;
692
693     QObject* o = static_cast<QObject*>(index.internalPointer());
694     BiasedPlaylist* indexPlaylist = qobject_cast<BiasedPlaylist*>(o);
695     AbstractBias* indexBias = qobject_cast<Dynamic::AbstractBias*>(o);
696
697     // remove a playlist
698     if( indexPlaylist )
699     {
700         if( !indexPlaylist || !m_playlists.contains( indexPlaylist ) )
701             return;
702
703         int i = playlistIndex( indexPlaylist );
704
705         beginRemoveRows( QModelIndex(), i, i );
706         m_playlists.removeAt(i);
707         endRemoveRows();
708
709         delete indexPlaylist;
710
711         if( m_playlists.isEmpty() )
712         {
713             The::playlistActions()->enableDynamicMode( false );
714             m_activePlaylistIndex = 0;
715         }
716         else
717         {
718             setActivePlaylist( qBound(0, m_activePlaylistIndex, m_playlists.count() - 1 ) );
719         }
720     }
721     // remove a bias
722     else if( indexBias )
723     {
724         QModelIndex parentIndex = parent( index );
725
726         QObject* o2 = static_cast<QObject*>(parentIndex.internalPointer());
727         BiasedPlaylist* parentPlaylist = qobject_cast<BiasedPlaylist*>(o2);
728         AndBias* parentBias = qobject_cast<Dynamic::AndBias*>(o2);
729
730         // parent of the bias is a playlist
731         if( parentPlaylist )
732         {
733             // a playlist always needs a bias, so we can only remove this one
734             // if we can come up with a replacement
735             AndBias* andBias = qobject_cast<Dynamic::AndBias*>(indexBias);
736             if( andBias && !andBias->biases().isEmpty() )
737                 andBias->replace( andBias->biases().first() ); // replace by the first sub-bias
738             else
739             {
740                 ; // can't remove the last bias directly under a playlist
741             }
742         }
743         // parent of the bias is another bias
744         else if( parentBias )
745         {
746             indexBias->replace( Dynamic::BiasPtr() ); // replace by nothing
747         }
748     }
749
750     savePlaylists();
751 }
752
753
754 QModelIndex
755 Dynamic::DynamicModel::cloneAt( const QModelIndex& index )
756 {
757     DEBUG_BLOCK;
758
759     QObject* o = static_cast<QObject*>(index.internalPointer());
760     BiasedPlaylist* indexPlaylist = qobject_cast<BiasedPlaylist*>(o);
761     BiasPtr indexBias( qobject_cast<Dynamic::AbstractBias*>(o) );
762
763     if( indexPlaylist )
764     {
765         return insertPlaylist( m_playlists.count(), cloneList( indexPlaylist ) );
766     }
767     else if( indexBias )
768     {
769         return insertBias( -1, index.parent(), cloneBias( indexBias ) );
770     }
771
772     return QModelIndex();
773 }
774
775
776 QModelIndex
777 Dynamic::DynamicModel::newPlaylist()
778 {
779     Dynamic::BiasedPlaylist *playlist = new Dynamic::BiasedPlaylist( this );
780     Dynamic::BiasPtr bias( new Dynamic::SearchQueryBias() );
781     playlist->setTitle( i18n("New playlist") );
782     playlist->bias()->replace( bias );
783
784     return insertPlaylist( m_playlists.count(), playlist );
785 }
786
787
788 bool
789 Dynamic::DynamicModel::savePlaylists( const QString &filename )
790 {
791     DEBUG_BLOCK;
792
793     QFile xmlFile( Amarok::saveLocation() + filename );
794     if( !xmlFile.open( QIODevice::WriteOnly ) )
795     {
796         error() << "Can not write" << xmlFile.fileName();
797         return false;
798     }
799
800     QXmlStreamWriter xmlWriter( &xmlFile );
801     xmlWriter.setAutoFormatting( true );
802     xmlWriter.writeStartDocument();
803     xmlWriter.writeStartElement("biasedPlaylists");
804     xmlWriter.writeAttribute("version", "2" );
805     xmlWriter.writeAttribute("current", QString::number( m_activePlaylistIndex ) );
806
807     foreach( Dynamic::DynamicPlaylist *playlist, m_playlists )
808     {
809         xmlWriter.writeStartElement("playlist");
810         playlist->toXml( &xmlWriter );
811         xmlWriter.writeEndElement();
812     }
813
814     xmlWriter.writeEndElement();
815     xmlWriter.writeEndDocument();
816
817     return true;
818 }
819
820 bool
821 Dynamic::DynamicModel::loadPlaylists( const QString &filename )
822 {
823     // -- clear all the old playlists
824     beginResetModel();
825     foreach( Dynamic::DynamicPlaylist* playlist, m_playlists )
826         delete playlist;
827     m_playlists.clear();
828
829     // -- open the file
830     QFile xmlFile( Amarok::saveLocation() + filename );
831     if( !xmlFile.open( QIODevice::ReadOnly ) )
832     {
833         error() << "Can not read" << xmlFile.fileName();
834         initPlaylists();
835         return false;
836     }
837
838     QXmlStreamReader xmlReader( &xmlFile );
839
840     // -- check the version
841     xmlReader.readNextStartElement();
842     if( xmlReader.atEnd() ||
843         !xmlReader.isStartElement() ||
844         xmlReader.name() != QLatin1String("biasedPlaylists") ||
845         xmlReader.attributes().value( QLatin1String("version") ) != QLatin1String("2") )
846     {
847         error() << "Playlist file" << xmlFile.fileName() << "is invalid or has wrong version";
848         initPlaylists();
849         return false;
850     }
851
852     int newPlaylistIndex = xmlReader.attributes().value( QLatin1String("current") ).toString().toInt();
853
854     while (!xmlReader.atEnd()) {
855         xmlReader.readNext();
856
857         if( xmlReader.isStartElement() )
858         {
859             QStringRef name = xmlReader.name();
860             if( name == QLatin1String("playlist") )
861             {
862                 Dynamic::BiasedPlaylist *playlist =  new Dynamic::BiasedPlaylist( &xmlReader, this );
863                 if( playlist->bias() )
864                 {
865                     insertPlaylist( m_playlists.count(), playlist );
866                 }
867                 else
868                 {
869                     delete playlist;
870                     warning() << "Just read a playlist without bias from"<<xmlFile.fileName();
871                 }
872             }
873             else
874             {
875                 debug() << "Unexpected xml start element"<<name<<"in input";
876                 xmlReader.skipCurrentElement();
877             }
878         }
879
880         else if( xmlReader.isEndElement() )
881         {
882             break;
883         }
884     }
885
886     // -- validate the index
887     if( m_playlists.isEmpty() ) {
888         error() << "Could not read the default playlist from" << xmlFile.fileName();
889         initPlaylists();
890         return false;
891     }
892
893     m_activePlaylistIndex = qBound( 0, newPlaylistIndex, m_playlists.count()-1 );
894
895     emit activeChanged( m_activePlaylistIndex );
896     endResetModel();
897
898     return true;
899 }
900
901 void
902 Dynamic::DynamicModel::initPlaylists()
903 {
904     // -- clear all the old playlists
905     beginResetModel();
906     foreach( Dynamic::DynamicPlaylist* playlist, m_playlists )
907         delete playlist;
908     m_playlists.clear();
909
910     Dynamic::BiasedPlaylist *playlist;
911
912     // -- create the empty default random playlists
913
914     // - first one rantom playlist
915     playlist = new Dynamic::BiasedPlaylist( this );
916     insertPlaylist( 0, playlist );
917
918     // - a playlist demonstrating the SearchQueryBias
919     playlist = new Dynamic::BiasedPlaylist( this );
920     playlist->setTitle( "Rock and Pop" );
921     playlist->bias()->replace( Dynamic::BiasPtr( new Dynamic::SearchQueryBias( "genre:Rock OR genre:Pop" ) ) );
922     insertPlaylist( 1, playlist );
923
924     // - a complex playlist demonstrating AlbumPlay and IfElse
925     playlist = new Dynamic::BiasedPlaylist( this );
926     playlist->setTitle( "Album play" );
927     Dynamic::IfElseBias *ifElse = new Dynamic::IfElseBias();
928     playlist->bias()->replace( Dynamic::BiasPtr( ifElse ) );
929     ifElse->appendBias( Dynamic::BiasPtr( new Dynamic::AlbumPlayBias() ) );
930     ifElse->appendBias( Dynamic::BiasPtr( new Dynamic::SearchQueryBias( "tracknumber:1" ) ) );
931     insertPlaylist( 2, playlist );
932
933     // - a complex playlist demonstrating PartBias and TagMatchBias
934     playlist = new Dynamic::BiasedPlaylist( this );
935     playlist->setTitle( "Rating" );
936     Dynamic::PartBias *part = new Dynamic::PartBias();
937     playlist->bias()->replace( Dynamic::BiasPtr( part ) );
938
939     part->appendBias( Dynamic::BiasPtr( new Dynamic::RandomBias() ) );
940
941     MetaQueryWidget::Filter ratingFilter;
942     ratingFilter.field = Meta::valRating;
943     ratingFilter.numValue = 5;
944     ratingFilter.condition = MetaQueryWidget::GreaterThan;
945
946     Dynamic::TagMatchBias* ratingBias1 = new Dynamic::TagMatchBias();
947     Dynamic::BiasPtr ratingBias1Ptr( ratingBias1 );
948     ratingBias1->setFilter( ratingFilter );
949     part->appendBias( ratingBias1Ptr );
950
951     ratingFilter.numValue = 8;
952     Dynamic::TagMatchBias* ratingBias2 = new Dynamic::TagMatchBias();
953     Dynamic::BiasPtr ratingBias2Ptr( ratingBias2 );
954     ratingBias2->setFilter( ratingFilter );
955     part->appendBias( ratingBias2Ptr );
956
957     part->changeBiasWeight( 2, 0.2 );
958     part->changeBiasWeight( 1, 0.5 );
959
960     insertPlaylist( 3, playlist );
961
962
963     m_activePlaylistIndex = 0;
964
965     emit activeChanged( m_activePlaylistIndex );
966     endResetModel();
967 }
968
969 void
970 Dynamic::DynamicModel::serializeIndex( QDataStream *stream, const QModelIndex& index ) const
971 {
972     QList<int> rows;
973     QModelIndex current = index;
974     while( current.isValid() )
975     {
976         rows.prepend( current.row() );
977         current = current.parent();
978     }
979
980     foreach( int row, rows )
981         *stream << row;
982     *stream << -1;
983 }
984
985 QModelIndex
986 Dynamic::DynamicModel::unserializeIndex( QDataStream *stream ) const
987 {
988     QModelIndex result;
989     do
990     {
991         int row;
992         *stream >> row;
993         if( row < 0 )
994             break;
995         result = index( row, 0, result );
996     } while( result.isValid() );
997     return result;
998 }
999
1000 Dynamic::BiasedPlaylist*
1001 Dynamic::DynamicModel::cloneList( Dynamic::BiasedPlaylist* list )
1002 {
1003     QByteArray bytes;
1004     QBuffer buffer( &bytes, 0 );
1005     buffer.open( QIODevice::ReadWrite );
1006
1007     // write the list
1008     QXmlStreamWriter xmlWriter( &buffer );
1009     list->toXml( &xmlWriter );
1010
1011     // and read a new list
1012     buffer.seek( 0 );
1013     QXmlStreamReader xmlReader( &buffer );
1014     return new Dynamic::BiasedPlaylist( &xmlReader, this );
1015 }
1016
1017 Dynamic::BiasPtr
1018 Dynamic::DynamicModel::cloneBias( Dynamic::BiasPtr bias )
1019 {
1020     QByteArray bytes;
1021     QBuffer buffer( &bytes, 0 );
1022     buffer.open( QIODevice::ReadWrite );
1023
1024     // write the bias
1025     QXmlStreamWriter xmlWriter( &buffer );
1026     xmlWriter.writeStartElement( bias->name() );
1027     bias->toXml( &xmlWriter );
1028     xmlWriter.writeEndElement();
1029
1030     // and read a new list
1031     buffer.seek( 0 );
1032     QXmlStreamReader xmlReader( &buffer );
1033     while( !xmlReader.isStartElement() )
1034         xmlReader.readNext();
1035     return Dynamic::BiasFactory::fromXml( &xmlReader );
1036 }
1037
1038 void
1039 Dynamic::DynamicModel::playlistChanged( Dynamic::DynamicPlaylist* p )
1040 {
1041     DEBUG_BLOCK;
1042     QModelIndex index = this->index( p );
1043     emit dataChanged( index, index );
1044 }
1045
1046 void
1047 Dynamic::DynamicModel::biasChanged( Dynamic::BiasPtr b )
1048 {
1049     QModelIndex index = this->index( b );
1050     emit dataChanged( index, index );
1051 }
1052
1053 void
1054 Dynamic::DynamicModel::beginRemoveBias( Dynamic::BiasedPlaylist* parent )
1055 {
1056     QModelIndex index = this->index( parent );
1057     beginRemoveRows( index, 0, 0 );
1058 }
1059
1060 void
1061 Dynamic::DynamicModel::beginRemoveBias( Dynamic::BiasPtr parent, int index )
1062 {
1063     QModelIndex parentIndex = this->index( parent );
1064     beginRemoveRows( parentIndex, index, index );
1065 }
1066
1067 void
1068 Dynamic::DynamicModel::endRemoveBias()
1069 {
1070     endRemoveRows();
1071 }
1072
1073 void
1074 Dynamic::DynamicModel::beginInsertBias( Dynamic::BiasedPlaylist* parent )
1075 {
1076     QModelIndex index = this->index( parent );
1077     beginInsertRows( index, 0, 0 );
1078 }
1079
1080
1081 void
1082 Dynamic::DynamicModel::beginInsertBias( Dynamic::BiasPtr parent, int index )
1083 {
1084     QModelIndex parentIndex = this->index( parent );
1085     beginInsertRows( parentIndex, index, index );
1086 }
1087
1088 void
1089 Dynamic::DynamicModel::endInsertBias()
1090 {
1091     endInsertRows();
1092 }
1093
1094 void
1095 Dynamic::DynamicModel::beginMoveBias( Dynamic::BiasPtr parent, int from, int to )
1096 {
1097     QModelIndex parentIndex = this->index( parent );
1098     beginMoveRows( parentIndex, from, from, parentIndex, to );
1099 }
1100
1101 void
1102 Dynamic::DynamicModel::endMoveBias()
1103 {
1104     endMoveRows();
1105 }
1106
1107
1108 // --- debug methods
1109
1110 static QString
1111 biasToString( Dynamic::BiasPtr bias, int level )
1112 {
1113     QString result;
1114     result += QString(" ").repeated(level) + bias->toString() + " " + QString::number(ulong(bias.data()), 16) + "\n";
1115     if( Dynamic::AndBias* aBias = qobject_cast<Dynamic::AndBias*>(bias.data()) )
1116     {
1117         foreach( Dynamic::BiasPtr bias2, aBias->biases() )
1118             result += biasToString( bias2, level + 1 );
1119     }
1120     return result;
1121 }
1122
1123 QString
1124 Dynamic::DynamicModel::toString()
1125 {
1126     QString result;
1127
1128     foreach( Dynamic::DynamicPlaylist* playlist, m_playlists )
1129     {
1130         result += playlist->title() + " " + QString::number(ulong(playlist), 16) + "\n";
1131         if( Dynamic::BiasedPlaylist* bPlaylist = qobject_cast<Dynamic::BiasedPlaylist*>(playlist ) )
1132             result += biasToString( bPlaylist->bias(), 1 );
1133     }
1134     return result;
1135 }
1136