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 }