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 <QIcon>
39
40 #include <QFile>
41 #include <QBuffer>
42 #include <QByteArray>
43 #include <QDataStream>
44 #include <QMimeData>
45 #include <QXmlStreamReader>
46 #include <QXmlStreamWriter>
47
48 /* general note:
49 For the sake of this file we are handling a modified active playlist as
50 a different one.
51 */
52
53 Dynamic::DynamicModel* Dynamic::DynamicModel::s_instance = nullptr;
54
55 Dynamic::DynamicModel*
instance()56 Dynamic::DynamicModel::instance()
57 {
58 if( !s_instance )
59 {
60 s_instance = new DynamicModel( pApp );
61 s_instance->loadPlaylists();
62 }
63 return s_instance;
64 }
65
66
DynamicModel(QObject * parent)67 Dynamic::DynamicModel::DynamicModel(QObject* parent)
68 : QAbstractItemModel( parent )
69 , m_activePlaylistIndex( 0 )
70 { }
71
~DynamicModel()72 Dynamic::DynamicModel::~DynamicModel()
73 {
74 savePlaylists();
75 }
76
77 Dynamic::DynamicPlaylist*
setActivePlaylist(int index)78 Dynamic::DynamicModel::setActivePlaylist( int index )
79 {
80 if( index < 0 || index >= m_playlists.count() )
81 return m_playlists[m_activePlaylistIndex];
82
83 if( m_activePlaylistIndex == index )
84 return m_playlists[m_activePlaylistIndex];
85
86 Q_EMIT dataChanged( this->index( m_activePlaylistIndex, 0 ),
87 this->index( m_activePlaylistIndex, 0 ) );
88 m_activePlaylistIndex = index;
89 Q_EMIT dataChanged( this->index( m_activePlaylistIndex, 0 ),
90 this->index( m_activePlaylistIndex, 0 ) );
91
92 Q_EMIT activeChanged( index );
93 savePlaylists(); // save in between to prevent loosing too much in case of a crash
94
95 return m_playlists[m_activePlaylistIndex];
96 }
97
98 Dynamic::DynamicPlaylist*
activePlaylist() const99 Dynamic::DynamicModel::activePlaylist() const
100 {
101 if( m_activePlaylistIndex < 0 || m_activePlaylistIndex >= m_playlists.count() )
102 return 0;
103
104 return m_playlists[m_activePlaylistIndex];
105 }
106
107 int
activePlaylistIndex() const108 Dynamic::DynamicModel::activePlaylistIndex() const
109 {
110 return m_activePlaylistIndex;
111 }
112
113 int
playlistIndex(Dynamic::DynamicPlaylist * playlist) const114 Dynamic::DynamicModel::playlistIndex( Dynamic::DynamicPlaylist* playlist ) const
115 {
116 return m_playlists.indexOf( playlist );
117 }
118
119 QModelIndex
insertPlaylist(int index,Dynamic::DynamicPlaylist * playlist)120 Dynamic::DynamicModel::insertPlaylist( int index, Dynamic::DynamicPlaylist* playlist )
121 {
122 if( !playlist )
123 return QModelIndex();
124
125 int oldIndex = playlistIndex( playlist );
126 bool wasActive = (oldIndex == m_activePlaylistIndex);
127
128 // -- remove the playlist if it was already in our model
129 if( oldIndex >= 0 )
130 {
131 beginRemoveRows( QModelIndex(), oldIndex, oldIndex );
132 m_playlists.removeAt( oldIndex );
133 endRemoveRows();
134
135 if( oldIndex < index )
136 index--;
137
138 if( m_activePlaylistIndex > oldIndex )
139 m_activePlaylistIndex--;
140 }
141
142 if( index < 0 )
143 index = 0;
144 if( index > m_playlists.count() )
145 index = m_playlists.count();
146
147 // -- insert it at the new position
148 beginInsertRows( QModelIndex(), index, index );
149
150 if( m_activePlaylistIndex > index )
151 m_activePlaylistIndex++;
152
153 if( wasActive )
154 m_activePlaylistIndex = index;
155
156 m_playlists.insert( index, playlist );
157
158 endInsertRows();
159
160 return this->index( index, 0 );
161 }
162
163 QModelIndex
insertBias(int row,const QModelIndex & parentIndex,const Dynamic::BiasPtr & bias)164 Dynamic::DynamicModel::insertBias( int row, const QModelIndex &parentIndex, const Dynamic::BiasPtr &bias )
165 {
166 QObject* o = static_cast<QObject*>(parentIndex.internalPointer());
167 BiasedPlaylist* parentPlaylist = qobject_cast<BiasedPlaylist*>(o);
168 AndBias* parentBias = qobject_cast<Dynamic::AndBias*>(o);
169 AbstractBias* aBias = qobject_cast<Dynamic::AbstractBias*>(o);
170
171 // Add something directly to the top
172 if( !parentIndex.isValid() )
173 {
174 if( row >= 0 && row < m_playlists.count() )
175 {
176 o = m_playlists[row];
177 parentPlaylist = qobject_cast<BiasedPlaylist*>(o);
178 }
179 else
180 {
181 return QModelIndex();
182 }
183 }
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
supportedDropActions() const217 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
data(const QModelIndex & i,int role) const231 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 QIcon::fromTheme( QStringLiteral("amarok_playlist") );
261 else
262 return QIcon::fromTheme( QStringLiteral("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(QStringLiteral("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
setData(const QModelIndex & index,const QVariant & value,int role)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
flags(const QModelIndex & index) const350 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
index(int row,int column,const QModelIndex & parent) const396 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
parent(int row,BiasedPlaylist * list,const BiasPtr & bias) const434 Dynamic::DynamicModel::parent( int row, BiasedPlaylist* list, const 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
parent(int row,const BiasPtr & parent,const BiasPtr & bias) const442 Dynamic::DynamicModel::parent( int row, const BiasPtr &parent, const 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
parent(const QModelIndex & index) const461 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
rowCount(const QModelIndex & parent) const486 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
columnCount(const QModelIndex & parent) const516 Dynamic::DynamicModel::columnCount(const QModelIndex & parent) const
517 {
518 Q_UNUSED( parent )
519 return 1;
520 }
521
522 QStringList
mimeTypes() const523 Dynamic::DynamicModel::mimeTypes() const
524 {
525 QStringList types;
526 types << QStringLiteral("application/amarok.biasModel.index");
527 return types;
528 }
529
530 QMimeData*
mimeData(const QModelIndexList & indexes) const531 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(QStringLiteral("application/amarok.biasModel.index"), bytes);
548 return mimeData;
549 }
550
551 bool
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & _parent)552 Dynamic::DynamicModel::dropMimeData(const QMimeData *data,
553 Qt::DropAction action,
554 int row, int column, const QModelIndex &_parent)
555 {
556 Q_UNUSED( column );
557
558 QModelIndex parent = _parent;
559
560 if( action == Qt::IgnoreAction )
561 return true;
562
563 if( data->hasFormat(QStringLiteral("application/amarok.biasModel.index")) )
564 {
565 // get the source index from the mime data
566 QByteArray bytes = data->data(QStringLiteral("application/amarok.biasModel.index"));
567 QDataStream stream( &bytes, QIODevice::ReadOnly );
568 QModelIndex index = unserializeIndex( &stream );
569
570 if( !index.isValid() )
571 return false;
572
573 QObject* o = static_cast<QObject*>(index.internalPointer());
574 BiasedPlaylist* indexPlaylist = qobject_cast<BiasedPlaylist*>(o);
575 BiasPtr indexBias( qobject_cast<Dynamic::AbstractBias*>(o) );
576
577 // in case of moving or inserting a playlist, we
578 // move to the top level
579 if( indexPlaylist )
580 {
581 while( parent.isValid() )
582 {
583 row = parent.row() + 1;
584 column = parent.column();
585 parent = parent.parent();
586 }
587 }
588
589 debug() << "dropMimeData action" << action;
590
591 // -- insert
592 if( action == Qt::CopyAction )
593 {
594 // -- playlist
595 if( indexPlaylist )
596 {
597 insertPlaylist( row, cloneList( indexPlaylist ) );
598 return true;
599 }
600 // -- bias
601 else if( indexBias )
602 {
603 insertBias( row, parent, cloneBias( indexBias ) );
604 return true;
605 }
606 }
607 else if( action == Qt::MoveAction )
608 {
609 // -- playlist
610 if( indexPlaylist )
611 {
612 insertPlaylist( row, indexPlaylist );
613 return true;
614 }
615 // -- bias
616 else if( indexBias )
617 {
618 indexBias->replace( Dynamic::BiasPtr() );
619 insertBias( row, parent, Dynamic::BiasPtr(indexBias) );
620 return true;
621 }
622 }
623 }
624
625 return false;
626 }
627
628
629 QModelIndex
index(const Dynamic::BiasPtr & bias) const630 Dynamic::DynamicModel::index( const Dynamic::BiasPtr &bias ) const
631 {
632 QModelIndex res;
633
634 // search for the parent
635 for( int i = 0; i < m_playlists.count(); i++ )
636 {
637 res = parent( i, qobject_cast<BiasedPlaylist*>(m_playlists[i]), bias );
638 if( res.isValid() )
639 break;
640 }
641
642 if( !res.isValid() )
643 return res;
644
645 QObject* o = static_cast<QObject*>(res.internalPointer());
646 BiasedPlaylist* parentPlaylist = qobject_cast<BiasedPlaylist*>(o);
647 AndBias* parentBias = qobject_cast<Dynamic::AndBias*>(o);
648
649 // level 1
650 if( parentPlaylist )
651 {
652 return createIndex( 0, 0, bias.data() );
653 }
654 // level > 1
655 else if( parentBias )
656 {
657 return createIndex( parentBias->biases().indexOf( bias ), 0, bias.data() );
658 }
659 else
660 {
661 return QModelIndex();
662 }
663 }
664
665 QModelIndex
index(Dynamic::DynamicPlaylist * playlist) const666 Dynamic::DynamicModel::index( Dynamic::DynamicPlaylist* playlist ) const
667 {
668 return createIndex( playlistIndex( playlist ), 0, playlist );
669 }
670
671
672 void
savePlaylists()673 Dynamic::DynamicModel::savePlaylists()
674 {
675 savePlaylists( QStringLiteral("dynamic.xml") );
676 }
677
678 void
loadPlaylists()679 Dynamic::DynamicModel::loadPlaylists()
680 {
681 loadPlaylists( QStringLiteral("dynamic.xml") );
682 }
683
684 void
removeAt(const QModelIndex & index)685 Dynamic::DynamicModel::removeAt( const QModelIndex& index )
686 {
687 if( !index.isValid() )
688 return;
689
690 QObject* o = static_cast<QObject*>(index.internalPointer());
691 BiasedPlaylist* indexPlaylist = qobject_cast<BiasedPlaylist*>(o);
692 AbstractBias* indexBias = qobject_cast<Dynamic::AbstractBias*>(o);
693
694 // remove a playlist
695 if( indexPlaylist )
696 {
697 if( !indexPlaylist || !m_playlists.contains( indexPlaylist ) )
698 return;
699
700 int i = playlistIndex( indexPlaylist );
701
702 beginRemoveRows( QModelIndex(), i, i );
703 m_playlists.removeAt(i);
704 endRemoveRows();
705
706 delete indexPlaylist;
707
708 if( m_playlists.isEmpty() )
709 {
710 The::playlistActions()->enableDynamicMode( false );
711 m_activePlaylistIndex = 0;
712 }
713 else
714 {
715 setActivePlaylist( qBound(0, m_activePlaylistIndex, m_playlists.count() - 1 ) );
716 }
717 }
718 // remove a bias
719 else if( indexBias )
720 {
721 QModelIndex parentIndex = parent( index );
722
723 QObject* o2 = static_cast<QObject*>(parentIndex.internalPointer());
724 BiasedPlaylist* parentPlaylist = qobject_cast<BiasedPlaylist*>(o2);
725 AndBias* parentBias = qobject_cast<Dynamic::AndBias*>(o2);
726
727 // parent of the bias is a playlist
728 if( parentPlaylist )
729 {
730 // a playlist always needs a bias, so we can only remove this one
731 // if we can come up with a replacement
732 AndBias* andBias = qobject_cast<Dynamic::AndBias*>(indexBias);
733 if( andBias && !andBias->biases().isEmpty() )
734 andBias->replace( andBias->biases().first() ); // replace by the first sub-bias
735 else
736 {
737 ; // can't remove the last bias directly under a playlist
738 }
739 }
740 // parent of the bias is another bias
741 else if( parentBias )
742 {
743 indexBias->replace( Dynamic::BiasPtr() ); // replace by nothing
744 }
745 }
746
747 savePlaylists();
748 }
749
750
751 QModelIndex
cloneAt(const QModelIndex & index)752 Dynamic::DynamicModel::cloneAt( const QModelIndex& index )
753 {
754 DEBUG_BLOCK;
755
756 QObject* o = static_cast<QObject*>(index.internalPointer());
757 BiasedPlaylist* indexPlaylist = qobject_cast<BiasedPlaylist*>(o);
758 BiasPtr indexBias( qobject_cast<Dynamic::AbstractBias*>(o) );
759
760 if( indexPlaylist )
761 {
762 return insertPlaylist( m_playlists.count(), cloneList( indexPlaylist ) );
763 }
764 else if( indexBias )
765 {
766 return insertBias( -1, index.parent(), cloneBias( indexBias ) );
767 }
768
769 return QModelIndex();
770 }
771
772
773 QModelIndex
newPlaylist()774 Dynamic::DynamicModel::newPlaylist()
775 {
776 Dynamic::BiasedPlaylist *playlist = new Dynamic::BiasedPlaylist( this );
777 Dynamic::BiasPtr bias( new Dynamic::SearchQueryBias() );
778 playlist->setTitle( i18nc( "Default name for new playlists", "New playlist") );
779 playlist->bias()->replace( bias );
780
781 return insertPlaylist( m_playlists.count(), playlist );
782 }
783
784
785 bool
savePlaylists(const QString & filename)786 Dynamic::DynamicModel::savePlaylists( const QString &filename )
787 {
788 DEBUG_BLOCK;
789
790 QFile xmlFile( Amarok::saveLocation() + filename );
791 if( !xmlFile.open( QIODevice::WriteOnly ) )
792 {
793 error() << "Can not write" << xmlFile.fileName();
794 return false;
795 }
796
797 QXmlStreamWriter xmlWriter( &xmlFile );
798 xmlWriter.setAutoFormatting( true );
799 xmlWriter.writeStartDocument();
800 xmlWriter.writeStartElement(QStringLiteral("biasedPlaylists"));
801 xmlWriter.writeAttribute(QStringLiteral("version"), QStringLiteral("2") );
802 xmlWriter.writeAttribute(QStringLiteral("current"), QString::number( m_activePlaylistIndex ) );
803
804 foreach( Dynamic::DynamicPlaylist *playlist, m_playlists )
805 {
806 xmlWriter.writeStartElement(QStringLiteral("playlist"));
807 playlist->toXml( &xmlWriter );
808 xmlWriter.writeEndElement();
809 }
810
811 xmlWriter.writeEndElement();
812 xmlWriter.writeEndDocument();
813
814 return true;
815 }
816
817 bool
loadPlaylists(const QString & filename)818 Dynamic::DynamicModel::loadPlaylists( const QString &filename )
819 {
820 // -- clear all the old playlists
821 beginResetModel();
822 foreach( Dynamic::DynamicPlaylist* playlist, m_playlists )
823 delete playlist;
824 m_playlists.clear();
825
826 // -- open the file
827 QFile xmlFile( Amarok::saveLocation() + filename );
828 if( !xmlFile.open( QIODevice::ReadOnly ) )
829 {
830 error() << "Can not read" << xmlFile.fileName();
831 initPlaylists();
832 return false;
833 }
834
835 QXmlStreamReader xmlReader( &xmlFile );
836
837 // -- check the version
838 xmlReader.readNextStartElement();
839 if( xmlReader.atEnd() ||
840 !xmlReader.isStartElement() ||
841 xmlReader.name() != QLatin1String("biasedPlaylists") ||
842 xmlReader.attributes().value( QLatin1String("version") ) != QLatin1String("2") )
843 {
844 error() << "Playlist file" << xmlFile.fileName() << "is invalid or has wrong version";
845 initPlaylists();
846 return false;
847 }
848
849 int newPlaylistIndex = xmlReader.attributes().value( QLatin1String("current") ).toString().toInt();
850
851 while (!xmlReader.atEnd()) {
852 xmlReader.readNext();
853
854 if( xmlReader.isStartElement() )
855 {
856 QStringRef name = xmlReader.name();
857 if( name == QLatin1String("playlist") )
858 {
859 Dynamic::BiasedPlaylist *playlist = new Dynamic::BiasedPlaylist( &xmlReader, this );
860 if( playlist->bias() )
861 {
862 insertPlaylist( m_playlists.count(), playlist );
863 }
864 else
865 {
866 delete playlist;
867 warning() << "Just read a playlist without bias from"<<xmlFile.fileName();
868 }
869 }
870 else
871 {
872 debug() << "Unexpected xml start element"<<name<<"in input";
873 xmlReader.skipCurrentElement();
874 }
875 }
876
877 else if( xmlReader.isEndElement() )
878 {
879 break;
880 }
881 }
882
883 // -- validate the index
884 if( m_playlists.isEmpty() ) {
885 error() << "Could not read the default playlist from" << xmlFile.fileName();
886 initPlaylists();
887 return false;
888 }
889
890 m_activePlaylistIndex = qBound( 0, newPlaylistIndex, m_playlists.count()-1 );
891
892 Q_EMIT activeChanged( m_activePlaylistIndex );
893 endResetModel();
894
895 return true;
896 }
897
898 void
initPlaylists()899 Dynamic::DynamicModel::initPlaylists()
900 {
901 // -- clear all the old playlists
902 beginResetModel();
903 foreach( Dynamic::DynamicPlaylist* playlist, m_playlists )
904 delete playlist;
905 m_playlists.clear();
906
907 Dynamic::BiasedPlaylist *playlist;
908
909 // -- create the empty default random playlists
910
911 // - first one random playlist
912 playlist = new Dynamic::BiasedPlaylist( this );
913 insertPlaylist( 0, playlist );
914
915 // - a playlist demonstrating the SearchQueryBias
916 playlist = new Dynamic::BiasedPlaylist( this );
917 playlist->setTitle( i18n("Rock and Pop") );
918 QString query = Meta::shortI18nForField( Meta::valGenre ) + QLatin1Char(':') + i18n( "Rock" );
919 /* following cannot be currently translated, see ExpressionParser::isAdvancedExpression()
920 * and ExpressionParser::finishedToken() */
921 query += QLatin1String(" AND ");
922 query += Meta::shortI18nForField( Meta::valGenre ) + QLatin1Char(':') + i18n( "Pop" );
923 playlist->bias()->replace( Dynamic::BiasPtr( new Dynamic::SearchQueryBias( query ) ) );
924 insertPlaylist( 1, playlist );
925
926 // - a complex playlist demonstrating AlbumPlay and IfElse
927 playlist = new Dynamic::BiasedPlaylist( this );
928 playlist->setTitle( i18n("Album play") );
929 Dynamic::IfElseBias *ifElse = new Dynamic::IfElseBias();
930 playlist->bias()->replace( Dynamic::BiasPtr( ifElse ) );
931 ifElse->appendBias( Dynamic::BiasPtr( new Dynamic::AlbumPlayBias() ) );
932 query = Meta::shortI18nForField( Meta::valTrackNr ) + ":1";
933 ifElse->appendBias( Dynamic::BiasPtr( new Dynamic::SearchQueryBias( query ) ) );
934 insertPlaylist( 2, playlist );
935
936 // - a complex playlist demonstrating PartBias and TagMatchBias
937 playlist = new Dynamic::BiasedPlaylist( this );
938 playlist->setTitle( i18nc( "Name of a dynamic playlist", "Rating" ) );
939 Dynamic::PartBias *part = new Dynamic::PartBias();
940 playlist->bias()->replace( Dynamic::BiasPtr( part ) );
941
942 part->appendBias( Dynamic::BiasPtr( new Dynamic::RandomBias() ) );
943
944 MetaQueryWidget::Filter ratingFilter;
945 ratingFilter.setField( Meta::valRating );
946 ratingFilter.numValue = 5;
947 ratingFilter.condition = MetaQueryWidget::GreaterThan;
948
949 Dynamic::TagMatchBias* ratingBias1 = new Dynamic::TagMatchBias();
950 Dynamic::BiasPtr ratingBias1Ptr( ratingBias1 );
951 ratingBias1->setFilter( ratingFilter );
952 part->appendBias( ratingBias1Ptr );
953
954 ratingFilter.numValue = 8;
955 Dynamic::TagMatchBias* ratingBias2 = new Dynamic::TagMatchBias();
956 Dynamic::BiasPtr ratingBias2Ptr( ratingBias2 );
957 ratingBias2->setFilter( ratingFilter );
958 part->appendBias( ratingBias2Ptr );
959
960 part->changeBiasWeight( 2, 0.2 );
961 part->changeBiasWeight( 1, 0.5 );
962
963 insertPlaylist( 3, playlist );
964
965
966 m_activePlaylistIndex = 0;
967
968 Q_EMIT activeChanged( m_activePlaylistIndex );
969 endResetModel();
970 }
971
972 void
serializeIndex(QDataStream * stream,const QModelIndex & index) const973 Dynamic::DynamicModel::serializeIndex( QDataStream *stream, const QModelIndex& index ) const
974 {
975 QList<int> rows;
976 QModelIndex current = index;
977 while( current.isValid() )
978 {
979 rows.prepend( current.row() );
980 current = current.parent();
981 }
982
983 foreach( int row, rows )
984 *stream << row;
985 *stream << -1;
986 }
987
988 QModelIndex
unserializeIndex(QDataStream * stream) const989 Dynamic::DynamicModel::unserializeIndex( QDataStream *stream ) const
990 {
991 QModelIndex result;
992 do
993 {
994 int row;
995 *stream >> row;
996 if( row < 0 )
997 break;
998 result = index( row, 0, result );
999 } while( result.isValid() );
1000 return result;
1001 }
1002
1003 Dynamic::BiasedPlaylist*
cloneList(Dynamic::BiasedPlaylist * list)1004 Dynamic::DynamicModel::cloneList( Dynamic::BiasedPlaylist* list )
1005 {
1006 QByteArray bytes;
1007 QBuffer buffer( &bytes, 0 );
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
cloneBias(Dynamic::BiasPtr bias)1025 Dynamic::DynamicModel::cloneBias( Dynamic::BiasPtr bias )
1026 {
1027 return bias->clone();
1028 }
1029
1030 void
playlistChanged(Dynamic::DynamicPlaylist * p)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
biasChanged(const Dynamic::BiasPtr & b)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
beginRemoveBias(Dynamic::BiasedPlaylist * parent)1046 Dynamic::DynamicModel::beginRemoveBias( Dynamic::BiasedPlaylist* parent )
1047 {
1048 QModelIndex index = this->index( parent );
1049 beginRemoveRows( index, 0, 0 );
1050 }
1051
1052 void
beginRemoveBias(const Dynamic::BiasPtr & parent,int index)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
endRemoveBias()1060 Dynamic::DynamicModel::endRemoveBias()
1061 {
1062 endRemoveRows();
1063 }
1064
1065 void
beginInsertBias(Dynamic::BiasedPlaylist * parent)1066 Dynamic::DynamicModel::beginInsertBias( Dynamic::BiasedPlaylist* parent )
1067 {
1068 QModelIndex index = this->index( parent );
1069 beginInsertRows( index, 0, 0 );
1070 }
1071
1072
1073 void
beginInsertBias(const Dynamic::BiasPtr & parent,int index)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
endInsertBias()1081 Dynamic::DynamicModel::endInsertBias()
1082 {
1083 endInsertRows();
1084 }
1085
1086 void
beginMoveBias(const Dynamic::BiasPtr & parent,int from,int to)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
endMoveBias()1094 Dynamic::DynamicModel::endMoveBias()
1095 {
1096 endMoveRows();
1097 }
1098
1099
1100 // --- debug methods
1101
1102 static QString
biasToString(Dynamic::BiasPtr bias,int level)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
toString()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
1129