void SourceDelegate::paintDecorations( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { SourcesModel::RowType type = static_cast< SourcesModel::RowType >( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() ); SourceTreeItem* item = index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >(); // Paint the speaker icon next to the currently-playing playlist const bool playable = ( type == SourcesModel::StaticPlaylist || type == SourcesModel::AutomaticPlaylist || type == SourcesModel::Station || type == SourcesModel::TemporaryPage || type == SourcesModel::LovedTracksPage || type == SourcesModel::Collection || type == SourcesModel::GenericPage ); const bool playing = ( AudioEngine::instance()->isPlaying() || AudioEngine::instance()->isPaused() ); if ( playable && playing && item->isBeingPlayed() ) { const int iconW = option.rect.height() - m_margin / 4; const QRect iconRect( m_margin / 4, option.rect.y() + m_margin / 8, iconW, iconW ); const QPixmap speaker = TomahawkUtils::defaultPixmap( TomahawkUtils::NowPlayingSpeakerDark, TomahawkUtils::Original, iconRect.size() ); painter->drawPixmap( iconRect, speaker ); } }
bool SourcesModel::removeItem( const source_ptr& source ) { // qDebug() << "Removing source item from SourceTree:" << source->username(); QModelIndex idx; int rows = rowCount(); for ( int row = 0; row < rows; row++ ) { QModelIndex idx = index( row, 0 ); SourceTreeItem* item = indexToTreeItem( idx ); if ( item ) { if ( item->source() == source ) { qDebug() << "Found removed source item:" << item->source()->userName(); invisibleRootItem()->removeRow( row ); onItemOffline( idx ); delete item; return true; } } } return false; }
void SourcesModel::onItemOffline( const QModelIndex& idx ) { qDebug() << Q_FUNC_INFO; SourceTreeItem* item = indexToTreeItem( idx ); if ( item ) item->onOffline(); }
void SourceTreeView::onItemActivated( const QModelIndex& index ) { if ( !index.isValid() || !index.flags().testFlag( Qt::ItemIsEnabled ) ) return; SourceTreeItem* item = itemFromIndex< SourceTreeItem >( index ); item->activate(); }
bool SourcesProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const { if ( !m_filtered ) return true; SourceTreeItem* sti = m_model->indexToTreeItem( sourceModel()->index( sourceRow, 0, sourceParent ) ); if ( sti ) { if ( sti->source().isNull() || sti->source()->isOnline() ) return true; } return false; }
QModelIndex SourcesModel::parent( const QModelIndex& child ) const { if( !child.isValid() ) { return QModelIndex(); } SourceTreeItem* node = itemFromIndex( child ); SourceTreeItem* parent = node->parent(); if( parent == m_rootItem ) return QModelIndex(); return createIndex( rowForItem( parent ), 0, parent ); }
void SourceTreeView::dropEvent( QDropEvent* event ) { bool accept = false; const QPoint pos = event->pos(); const QModelIndex index = indexAt( pos ); if ( event->mimeData()->hasFormat( "application/tomahawk.query.list" ) ) { const QPoint pos = event->pos(); const QModelIndex index = indexAt( pos ); if ( index.isValid() ) { if ( SourcesModel::indexType( index ) == SourcesModel::PlaylistSource ) { playlist_ptr playlist = SourcesModel::indexToPlaylist( index ); if ( !playlist.isNull() && playlist->author()->isLocal() ) { accept = true; QByteArray itemData = event->mimeData()->data( "application/tomahawk.query.list" ); QDataStream stream( &itemData, QIODevice::ReadOnly ); QList<Tomahawk::query_ptr> queries; while ( !stream.atEnd() ) { qlonglong qptr; stream >> qptr; Tomahawk::query_ptr* query = reinterpret_cast<Tomahawk::query_ptr*>(qptr); if ( query && !query->isNull() ) { qDebug() << "Dropped query item:" << query->data()->artist() << "-" << query->data()->track(); queries << *query; } } qDebug() << "on playlist:" << playlist->title() << playlist->guid(); SourceTreeItem* treeItem = SourcesModel::indexToTreeItem( index ); if ( treeItem ) { QString rev = treeItem->currentlyLoadedPlaylistRevision( playlist->guid() ); playlist->addEntries( queries, rev ); } } } }
QModelIndex SourcesModel::index( int row, int column, const QModelIndex& parent ) const { if( row < 0 || column < 0 ) return QModelIndex(); if( hasIndex( row, column, parent ) ) { SourceTreeItem *parentNode = itemFromIndex( parent ); SourceTreeItem *childNode = parentNode->children().at( row ); return createIndex( row, column, childNode ); } return QModelIndex(); }
QModelIndex SourcesModel::collectionToIndex( const Tomahawk::collection_ptr& collection ) { for ( int i = 0; i < rowCount(); i++ ) { QModelIndex idx = index( i, 0 ); SourcesModel::SourceType type = SourcesModel::indexType( idx ); if ( type == SourcesModel::CollectionSource ) { SourceTreeItem* sti = SourcesModel::indexToTreeItem( idx ); if ( sti && !sti->source().isNull() && sti->source()->collection().data() == collection.data() ) return idx; } } return QModelIndex(); }
bool SourcesModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent ) { SourceTreeItem* item = 0; // qDebug() << "Got mime data dropped:" << row << column << parent << itemFromIndex( parent )->text(); if( row == -1 && column == -1 ) item = itemFromIndex( parent ); else if( column == 0 ) item = itemFromIndex( index( row, column, parent ) ); else if( column == -1 ) // column is -1, that means the drop is happening "below" the indices. that means we actually want the one before it item = itemFromIndex( index( row - 1, 0, parent ) ); Q_ASSERT( item ); // qDebug() << "Dropping on:" << item->text(); return item->dropMimeData( data, action ); }
void SourceTreeView::onItemActivated( const QModelIndex& index ) { if ( !index.isValid() ) return; SourcesModel::SourceType type = SourcesModel::indexType( index ); if ( type == SourcesModel::CollectionSource ) { SourceTreeItem* item = SourcesModel::indexToTreeItem( index ); if ( item ) { if ( item->source().isNull() ) { PlaylistManager::instance()->showSuperCollection(); } else { qDebug() << "SourceTreeItem toggled:" << item->source()->userName(); PlaylistManager::instance()->show( item->source()->collection() ); } } } else if ( type == SourcesModel::PlaylistSource ) { playlist_ptr playlist = SourcesModel::indexToPlaylist( index ); if ( !playlist.isNull() ) { qDebug() << "Playlist activated:" << playlist->title(); PlaylistManager::instance()->show( playlist ); } } else if ( type == SourcesModel::DynamicPlaylistSource ) { dynplaylist_ptr playlist = SourcesModel::indexToDynamicPlaylist( index ); if ( !playlist.isNull() ) { qDebug() << "Dynamic Playlist activated:" << playlist->title(); PlaylistManager::instance()->show( playlist ); } } }
QModelIndex SourcesModel::indexFromItem( SourceTreeItem* item ) const { if( !item || !item->parent() ) // should never happen.. return QModelIndex(); // reconstructs a modelindex from a sourcetreeitem that is somewhere in the tree // traverses the item to the root node, then rebuilds the qmodeindices from there back down // each int is the row of that item in the parent. /** * In this diagram, if the \param item is G, childIndexList will contain [0, 2, 0] * * A * D * E * F * G * H * B * C * **/ QList< int > childIndexList; SourceTreeItem* curItem = item; while( curItem != m_rootItem ) { int row = rowForItem( curItem ); if( row < 0 ) // something went wrong, bail return QModelIndex(); childIndexList << row; curItem = curItem->parent(); } // qDebug() << "build child index list:" << childIndexList; // now rebuild the qmodelindex we need QModelIndex idx; for( int i = childIndexList.size() - 1; i >= 0 ; i-- ) { idx = index( childIndexList[ i ], 0, idx ); } // qDebug() << "Got index from item:" << idx << idx.data( Qt::DisplayRole ).toString(); // qDebug() << "parent:" << idx.parent(); return idx; }
bool SourcesModel::appendItem( const source_ptr& source ) { SourceTreeItem* item = new SourceTreeItem( source, this ); connect( item, SIGNAL( clicked( QModelIndex ) ), this, SIGNAL( clicked( QModelIndex ) ) ); // qDebug() << "Appending source item:" << item->source()->username(); invisibleRootItem()->appendRow( item->columns() ); if ( !source.isNull() ) { connect( source.data(), SIGNAL( offline() ), SLOT( onSourceChanged() ) ); connect( source.data(), SIGNAL( online() ), SLOT( onSourceChanged() ) ); connect( source.data(), SIGNAL( stats( QVariantMap ) ), SLOT( onSourceChanged() ) ); connect( source.data(), SIGNAL( playbackStarted( Tomahawk::query_ptr ) ), SLOT( onSourceChanged() ) ); connect( source.data(), SIGNAL( stateChanged() ), SLOT( onSourceChanged() ) ); } return true; // FIXME }
void SourcesModel::onSourceChanged() { Source* src = qobject_cast< Source* >( sender() ); for ( int i = 0; i < rowCount(); i++ ) { QModelIndex idx = index( i, 0 ); if ( indexType( idx ) == CollectionSource ) { SourceTreeItem* sti = indexToTreeItem( idx ); if ( sti ) { if ( sti->source().data() == src ) { emit dataChanged( idx, idx ); } } } } }
bool SourceDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index ) { QMouseEvent* mEvent = 0; switch ( event->type() ) { // case QEvent::MouseTrackingChange: case QEvent::MouseButtonDblClick: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseMove: mEvent = static_cast< QMouseEvent* >( event ); default: break; } bool hoveringTrack = false; if ( m_trackRects.contains( index ) && mEvent ) { const QRect trackRect = m_trackRects[ index ]; hoveringTrack = trackRect.contains( mEvent->pos() ); if ( hoveringTrack ) { if ( m_trackHovered != index ) { m_trackHovered = index; QMetaObject::invokeMethod( m_parent, "update", Qt::QueuedConnection, Q_ARG( QModelIndex, index ) ); } } } if ( !hoveringTrack ) { if ( m_trackHovered.isValid() ) QMetaObject::invokeMethod( m_parent, "update", Qt::QueuedConnection, Q_ARG( QModelIndex, m_trackHovered ) ); m_trackHovered = QPersistentModelIndex(); } bool lockRectContainsClick = false, headphonesRectContainsClick = false; if ( m_headphoneRects.contains( index ) && mEvent ) { const QRect headphoneRect = m_headphoneRects[ index ]; headphonesRectContainsClick = headphoneRect.contains( mEvent->pos() ); } if ( m_lockRects.contains( index ) && mEvent ) { const QRect lockRect = m_lockRects[ index ]; lockRectContainsClick = lockRect.contains( mEvent->pos() ); } if ( event->type() == QEvent::MouseMove ) { if ( hoveringTrack || lockRectContainsClick || headphonesRectContainsClick ) m_parent->setCursor( Qt::PointingHandCursor ); else m_parent->setCursor( Qt::ArrowCursor ); } if ( event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseButtonPress ) { SourcesModel::RowType type = static_cast< SourcesModel::RowType >( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() ); if ( type == SourcesModel::TemporaryPage || type == SourcesModel::DeletablePage || type == SourcesModel::RemovablePage ) { SourceTreeItem* gpi = index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >(); Q_ASSERT( gpi ); QStyleOptionViewItemV4 o = option; initStyleOption( &o, index ); const int padding = m_margin / 8; const QRect r( o.rect.right() - padding - m_iconHeight, padding + o.rect.y(), m_iconHeight, m_iconHeight ); if ( r.contains( mEvent->pos() ) ) { if ( event->type() == QEvent::MouseButtonRelease && mEvent->button() == Qt::LeftButton ) { gpi->removeFromList(); // Send a new mouse event to the view, since if the mouse is now over another item's delete area we want it to show up QMouseEvent* ev = new QMouseEvent( QEvent::MouseMove, m_parent->viewport()->mapFromGlobal( QCursor::pos() ), Qt::NoButton, Qt::NoButton, Qt::NoModifier ); QApplication::postEvent( m_parent->viewport(), ev ); } return true; } } else if ( type == SourcesModel::Source ) { SourceItem* colItem = qobject_cast< SourceItem* >( index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >() ); Q_ASSERT( colItem ); if ( hoveringTrack && colItem->source() && colItem->source()->currentTrack() ) { if ( event->type() == QEvent::MouseButtonRelease && mEvent->button() == Qt::LeftButton ) { ViewManager::instance()->show( colItem->source()->currentTrack() ); return true; } else if ( event->type() == QEvent::MouseButtonPress && mEvent->button() == Qt::RightButton ) { Tomahawk::ContextMenu* contextMenu = new Tomahawk::ContextMenu( m_parent ); contextMenu->setQuery( colItem->source()->currentTrack() ); contextMenu->exec( QCursor::pos() ); return true; } } if ( !colItem->source().isNull() && !colItem->source()->currentTrack().isNull() && !colItem->source()->isLocal() ) { if ( headphonesRectContainsClick || lockRectContainsClick ) { if ( event->type() == QEvent::MouseButtonRelease && mEvent->button() == Qt::LeftButton ) { if ( headphonesRectContainsClick ) { if ( index.data( SourcesModel::LatchedOnRole ).toBool() ) // unlatch emit latchOff( colItem->source() ); else emit latchOn( colItem->source() ); } else // it's in the lock rect emit toggleRealtimeLatch( colItem->source(), !index.data( SourcesModel::LatchedRealtimeRole ).toBool() ); } return true; } } } else if ( event->type() == QEvent::MouseButtonRelease && mEvent->button() == Qt::LeftButton && type == SourcesModel::StaticPlaylist ) { PlaylistItem* plItem = qobject_cast< PlaylistItem* >( index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >() ); Q_ASSERT( plItem ); if ( plItem->canSubscribe() && !plItem->subscribedIcon().isNull() ) { const int padding = m_margin / 16; const int imgWidth = option.rect.height() - 2 * padding; const QRect subRect( option.rect.right() - padding - imgWidth, option.rect.top() + padding, imgWidth, imgWidth ); if ( subRect.contains( mEvent->pos() ) ) { // Toggle playlist subscription plItem->setSubscribed( !plItem->subscribed() ); } } } } // We emit our own clicked() signal instead of relying on QTreeView's, because that is fired whether or not a delegate accepts // a mouse press event. Since we want to swallow click events when they are on headphones other action items, here we make sure we only // emit if we really want to if ( event->type() == QEvent::MouseButtonRelease && mEvent->button() == Qt::LeftButton ) { if ( m_lastClicked == -1 ) { m_lastClicked = QDateTime::currentMSecsSinceEpoch(); emit clicked( index ); } else { qint64 elapsed = QDateTime::currentMSecsSinceEpoch() - m_lastClicked; if ( elapsed < QApplication::doubleClickInterval() ) { m_lastClicked = -1; emit doubleClicked( index ); } else { m_lastClicked = QDateTime::currentMSecsSinceEpoch(); emit clicked( index ); } } } return QStyledItemDelegate::editorEvent( event, model, option, index ); }
bool SourcesModel::setData( const QModelIndex& index, const QVariant& value, int role ) { SourceTreeItem* item = itemFromIndex( index ); return item->setData( value, role ); }