File indexing completed on 2025-03-09 04:25:11

0001 /****************************************************************************************
0002  * Copyright (c) 2013 Anmol Ahuja <darthcodus@gmail.com>                                *
0003  *                                                                                      *
0004  * This program is free software; you can redistribute it and/or modify it under        *
0005  * the terms of the GNU General Public License as published by the Free Software        *
0006  * Foundation; either version 2 of the License, or (at your option) any later           *
0007  * version.                                                                             *
0008  *                                                                                      *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0012  *                                                                                      *
0013  * You should have received a copy of the GNU General Public License along with         *
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0015  ****************************************************************************************/
0016 
0017 #define DEBUG_PREFIX "CollectionViewScript"
0018 #include "AmarokCollectionViewScript.h"
0019 
0020 #include "amarokconfig.h"
0021 #include "core/support/Debug.h"
0022 #include "browsers/CollectionTreeView.h"
0023 #include "browsers/collectionbrowser/CollectionWidget.h"
0024 #include "browsers/collectionbrowser/CollectionBrowserTreeView.h"
0025 #include "browsers/CollectionTreeItem.h"
0026 #include "browsers/CollectionTreeItemModelBase.h"
0027 #include "core-impl/collections/support/CollectionManager.h"
0028 #include "core-impl/collections/support/TextualQueryFilter.h"
0029 #include "MainWindow.h"
0030 #include "ScriptingDefines.h"
0031 #include "widgets/SearchWidget.h"
0032 
0033 #include <QMenu>
0034 #include <QMetaEnum>
0035 #include <QQmlEngine>
0036 #include <QJSEngine>
0037 #include <QSortFilterProxyModel>
0038 
0039 Q_DECLARE_METATYPE( QAction* )
0040 Q_DECLARE_METATYPE( QList<QAction*> )
0041 
0042 using namespace AmarokScript;
0043 
0044 QMap<QString, AmarokCollectionViewScript*> AmarokCollectionViewScript::s_instances;
0045 QPointer<Selection> AmarokCollectionViewScript::s_selection;
0046 
0047 AmarokCollectionViewScript::AmarokCollectionViewScript( AmarokScriptEngine *engine, const QString &scriptName )
0048     : QObject( engine )
0049     , m_collectionWidget( The::mainWindow()->collectionBrowser() )
0050     , m_engine( engine )
0051     , m_scriptName( scriptName )
0052     , m_categoryEnum( metaObject()->enumerator( metaObject()->indexOfEnumerator("Category") ) )
0053 {
0054     QJSValue scriptObject = engine->newQObject( this );
0055     QJSValue windowObject = engine->globalObject().property( QStringLiteral("Amarok") ).property( QStringLiteral("Window") );
0056     Q_ASSERT( !windowObject.isUndefined() );
0057     windowObject.setProperty( QStringLiteral("CollectionView"), scriptObject );
0058     const QMetaEnum typeEnum = CollectionTreeItem::staticMetaObject.enumerator( CollectionTreeItem::staticMetaObject.indexOfEnumerator( "Type" ) );
0059     Q_ASSERT( typeEnum.isValid() );
0060     scriptObject.setProperty( QStringLiteral("Type"), engine->enumObject( typeEnum ) );
0061     Q_ASSERT( m_categoryEnum.isValid() );
0062     scriptObject.setProperty( QStringLiteral("Category"), engine->enumObject( m_categoryEnum ) );
0063 
0064     qRegisterMetaType<CollectionTreeItem*>();
0065     QMetaType::registerConverter<CollectionTreeItem*, QJSValue>( [=] (CollectionTreeItem* item) { return CollectionViewItem::toScriptValue( m_engine, item ); } );
0066     QMetaType::registerConverter<QJSValue, CollectionTreeItem*>( [=] (QJSValue jsValue) {
0067         CollectionTreeItem* item;
0068         fromScriptValue<CollectionTreeItem*, CollectionViewItem>( jsValue, item );
0069         return item;
0070     } );
0071     engine->registerArrayType< QList<CollectionTreeItem*> >();
0072     engine->registerArrayType<QActionList>();
0073     s_instances[m_scriptName] = this;
0074     connect( The::mainWindow()->collectionBrowser()->searchWidget(), &SearchWidget::filterChanged, this, &AmarokCollectionViewScript::filterChanged );
0075 }
0076 
0077 AmarokCollectionViewScript::~AmarokCollectionViewScript()
0078 {
0079     s_instances.remove( m_scriptName );
0080     if( s_instances.isEmpty() )
0081         delete s_selection.data();
0082 }
0083 
0084 void
0085 AmarokCollectionViewScript::setFilter( const QString &filter )
0086 {
0087     m_collectionWidget->setFilter( filter );
0088 }
0089 
0090 QString
0091 AmarokCollectionViewScript::filter() const
0092 {
0093     return m_collectionWidget->filter();
0094 }
0095 
0096 QActionList
0097 AmarokCollectionViewScript::actions()
0098 {
0099     QJSValue actions = m_actionFunction.call( QJSValueList() << selectionScriptValue() );
0100     QActionList actionList = m_engine->fromScriptValue<QActionList>( actions );
0101     debug() << "Received " << actionList.size() << " actions";
0102     return actionList;
0103 }
0104 
0105 void
0106 AmarokCollectionViewScript::setAction( const QJSValue &value )
0107 {
0108     m_actionFunction = value;
0109 }
0110 
0111 void
0112 AmarokCollectionViewScript::createScriptedActions( QMenu &menu, const QModelIndexList &indices )
0113 {
0114     debug() << "Checking for scripted actions";
0115     if( s_selection )
0116         delete s_selection.data();
0117     if( s_instances.isEmpty() )
0118         return;
0119     s_selection = new Selection( indices );
0120 
0121     foreach( const QString &scriptName, s_instances.keys() )
0122     {
0123         if( s_instances[scriptName] )
0124         {
0125             debug() << "Adding actions for script " << scriptName;
0126             menu.addSeparator();
0127             foreach( QAction *action, s_instances[scriptName]->actions() )
0128             {
0129                 if( !action )
0130                 {
0131                     debug() << "Null action received from script " << scriptName;
0132                     continue;
0133                 }
0134                 action->setParent( &menu );
0135                 menu.addAction( action );
0136             }
0137         }
0138     }
0139 }
0140 
0141 QJSValue
0142 AmarokCollectionViewScript::selectionScriptValue()
0143 {
0144     QQmlEngine::setObjectOwnership( s_selection.data(), QQmlEngine::CppOwnership);
0145     return m_engine->newQObject( s_selection.data() );
0146 
0147 }
0148 
0149 Selection*
0150 AmarokCollectionViewScript::selection()
0151 {
0152     return s_selection.data();
0153 }
0154 
0155 void
0156 AmarokCollectionViewScript::setShowCovers( bool shown )
0157 {
0158     CollectionWidget::instance()->slotShowCovers( shown );
0159 }
0160 
0161 void
0162 AmarokCollectionViewScript::setShowTrackNumbers( bool shown )
0163 {
0164     CollectionWidget::instance()->slotShowTrackNumbers( shown );
0165 }
0166 
0167 void
0168 AmarokCollectionViewScript::setShowYears( bool shown )
0169 {
0170     CollectionWidget::instance()->slotShowYears( shown );
0171 }
0172 
0173 bool
0174 AmarokCollectionViewScript::showCovers()
0175 {
0176     return AmarokConfig::showAlbumArt();
0177 }
0178 
0179 bool
0180 AmarokCollectionViewScript::showTrackNumbers()
0181 {
0182     return AmarokConfig::showTrackNumbers();
0183 }
0184 
0185 bool
0186 AmarokCollectionViewScript::showYears()
0187 {
0188     return AmarokConfig::showYears();
0189 }
0190 
0191 bool
0192 AmarokCollectionViewScript::mergedView() const
0193 {
0194     return m_collectionWidget->viewMode() == CollectionWidget::UnifiedCollection;
0195 }
0196 
0197 void
0198 AmarokCollectionViewScript::setMergedView( bool merged )
0199 {
0200     CollectionWidget::instance()->toggleView( merged );
0201 }
0202 
0203 QList<int>
0204 AmarokCollectionViewScript::levels() const
0205 {
0206     QList<int> levels;
0207     foreach( CategoryId::CatMenuId level, m_collectionWidget->currentView()->levels() )
0208         levels << level;
0209     return levels;
0210 }
0211 
0212 void
0213 AmarokCollectionViewScript::setLevel( int level, int type )
0214 {
0215     if( m_categoryEnum.valueToKey( type ) ) {
0216         m_collectionWidget->currentView()->setLevel( level, CategoryId::CatMenuId( type ) );
0217         return;
0218     }
0219     /* TODO - Use commented code once QT versions >= 5.12
0220     m_engine->throwError( QJSValue::TypeError, QStringLiteral("Invalid category!") );
0221     */
0222     m_engine->evaluate("throw new TypeError('Invalid category!')");
0223 }
0224 
0225 void
0226 AmarokCollectionViewScript::setLevels( const QList<int> &levels )
0227 {
0228     QList<CategoryId::CatMenuId> catLevels;
0229     foreach( int level, levels )
0230     {
0231         if( !m_categoryEnum.valueToKey( level ) )
0232         {
0233             /* TODO - Use commented code once QT versions >= 5.12
0234             m_engine->throwError( QJSValue::TypeError, QStringLiteral("Invalid category!") );
0235             */
0236             m_engine->evaluate("throw new TypeError('Invalid category!')");
0237             return;
0238         }
0239         catLevels << CategoryId::CatMenuId( level );
0240     }
0241     m_collectionWidget->setLevels( catLevels );
0242 }
0243 
0244 ///////////////////////////////////////////////////////////
0245 // CollectionViewItem
0246 ///////////////////////////////////////////////////////////
0247 
0248 CollectionTreeItem*
0249 CollectionViewItem::child( int row )
0250 {
0251     return m_item->child( row );
0252 }
0253 
0254 int
0255 CollectionViewItem::childCount() const
0256 {
0257     return m_item->childCount();
0258 }
0259 
0260 QList<CollectionTreeItem*>
0261 CollectionViewItem::children() const
0262 {
0263     return m_item->children();
0264 }
0265 
0266 CollectionViewItem::CollectionViewItem( CollectionTreeItem *item, QObject *parent )
0267     : QObject( parent )
0268     , m_item( item )
0269 {}
0270 
0271 bool
0272 CollectionViewItem::isTrackItem() const
0273 {
0274     return m_item->isTrackItem();
0275 }
0276 
0277 int
0278 CollectionViewItem::level() const
0279 {
0280     return m_item->level();
0281 }
0282 
0283 CollectionTreeItem*
0284 CollectionViewItem::parent() const
0285 {
0286     return m_item->parent();
0287 }
0288 
0289 Collections::Collection*
0290 CollectionViewItem::parentCollection() const
0291 {
0292     return m_item->parentCollection();
0293 }
0294 
0295 int
0296 CollectionViewItem::row() const
0297 {
0298     return m_item->row();
0299 }
0300 
0301 bool
0302 CollectionViewItem::isCollection() const
0303 {
0304     return m_item->type() == CollectionTreeItem::Collection;
0305 }
0306 
0307 CollectionTreeItem*
0308 CollectionViewItem::data() const
0309 {
0310     return m_item;
0311 }
0312 
0313 QJSValue
0314 CollectionViewItem::toScriptValue( QJSEngine *engine, CollectionTreeItem* const &item )
0315 {
0316     CollectionViewItem *proto = new CollectionViewItem( item, AmarokCollectionViewScript::selection() );
0317     QJSValue val = engine->newQObject( proto );
0318     return val;
0319 }
0320 
0321 Meta::TrackPtr
0322 CollectionViewItem::track()
0323 {
0324     return Meta::TrackPtr::dynamicCast( m_item->data() );
0325 }
0326 
0327 bool
0328 CollectionViewItem::isAlbumItem() const
0329 {
0330     return m_item->isAlbumItem();
0331 }
0332 
0333 bool
0334 CollectionViewItem::isDataItem() const
0335 {
0336     return m_item->isDataItem();
0337 }
0338 
0339 bool
0340 CollectionViewItem::isNoLabelItem() const
0341 {
0342     return m_item->isNoLabelItem();
0343 }
0344 
0345 bool
0346 CollectionViewItem::isVariousArtistItem() const
0347 {
0348     return m_item->isVariousArtistItem();
0349 }
0350 
0351 bool
0352 CollectionViewItem::childrenLoaded() const
0353 {
0354     return m_item->isTrackItem() || !m_item->requiresUpdate();
0355 }
0356 
0357 void
0358 CollectionViewItem::loadChildren()
0359 {
0360     if( !m_item->requiresUpdate() )
0361         return;
0362     CollectionTreeItemModelBase *model = getModel();
0363     connect( model, &CollectionTreeItemModelBase::dataChanged,
0364              this, &CollectionViewItem::slotDataChanged );
0365     model->ensureChildrenLoaded( m_item );
0366 }
0367 
0368 void
0369 CollectionViewItem::slotDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight )
0370 {
0371     Q_UNUSED( bottomRight )
0372     if( static_cast<CollectionTreeItem*>( topLeft.internalPointer() ) != m_item )
0373         return;
0374     Q_EMIT loaded( m_item );
0375     Q_ASSERT( disconnect( qobject_cast<QAbstractItemModel*>(sender()), &QAbstractItemModel::dataChanged, this, nullptr ) );
0376 }
0377 
0378 Collections::QueryMaker*
0379 CollectionViewItem::queryMaker()
0380 {
0381     Collections::QueryMaker *qm = nullptr;
0382     if( The::mainWindow()->collectionBrowser()->viewMode() == CollectionWidget::NormalCollections )
0383         qm = m_item->queryMaker();
0384     else
0385         qm = CollectionManager::instance()->queryMaker();
0386     addFilter( qm );
0387     return qm;
0388 }
0389 
0390 void
0391 CollectionViewItem::addFilter( Collections::QueryMaker *queryMaker )
0392 {
0393     if( !queryMaker )
0394         return;
0395     CollectionTreeItemModelBase *model = getModel();
0396     for( CollectionTreeItem *tmp = m_item; tmp; tmp = tmp->parent() )
0397         tmp->addMatch( queryMaker, model->levelCategory( tmp->level() - 1 ) );
0398     Collections::addTextualFilter( queryMaker, model->currentFilter() );
0399 }
0400 
0401 CollectionTreeItemModelBase*
0402 CollectionViewItem::getModel()
0403 {
0404     QSortFilterProxyModel *proxyModel = dynamic_cast<QSortFilterProxyModel*>( The::mainWindow()->collectionBrowser()->currentView()->model() );
0405     return dynamic_cast<CollectionTreeItemModelBase*>( proxyModel ? proxyModel->sourceModel() : nullptr );
0406 }
0407 
0408 ///////////////////////////////////////////////////////////
0409 // Selection
0410 ///////////////////////////////////////////////////////////
0411 
0412 bool
0413 Selection::singleCollection() const
0414 {
0415     return CollectionTreeView::onlyOneCollection( m_indices );
0416 }
0417 
0418 QList<CollectionTreeItem*>
0419 Selection::selectedItems()
0420 {
0421     QList<CollectionTreeItem*> collectionItems;
0422     foreach( const QModelIndex &index, m_indices )
0423         collectionItems << static_cast<CollectionTreeItem*>( index.internalPointer() );
0424     return collectionItems;
0425 }
0426 
0427 Selection::Selection( const QModelIndexList &indices )
0428     : QObject( nullptr )
0429     , m_indices( indices )
0430 {}
0431 
0432 Collections::QueryMaker*
0433 Selection::queryMaker()
0434 {
0435     QList<CollectionTreeItem*> items=selectedItems();
0436     return The::mainWindow()->collectionBrowser()->currentView()->createMetaQueryFromItems( QSet<CollectionTreeItem*>(items.begin(), items.end()), true );
0437 }