mirror of
https://github.com/tomahawk-player/tomahawk.git
synced 2025-10-04 09:31:41 +02:00
841 lines
32 KiB
C++
841 lines
32 KiB
C++
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
|
|
*
|
|
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
|
* Copyright 2011-2012, Leo Franchi <lfranchi@kde.org>
|
|
* Copyright 2011, Michael Zanetti <mzanetti@kde.org>
|
|
* Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
|
|
*
|
|
* Tomahawk is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Tomahawk is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "SourceDelegate.h"
|
|
|
|
#include "items/SourceTreeItem.h"
|
|
#include "items/SourceItem.h"
|
|
#include "items/PlaylistItems.h"
|
|
#include "items/CategoryItems.h"
|
|
#include "items/TemporaryPageItem.h"
|
|
|
|
#include "utils/TomahawkUtilsGui.h"
|
|
#include "audio/AudioEngine.h"
|
|
#include "AnimationHelper.h"
|
|
#include "Source.h"
|
|
#include "TomahawkSettings.h"
|
|
#include "ActionCollection.h"
|
|
#include "ViewManager.h"
|
|
|
|
#include <QApplication>
|
|
#include <QPainter>
|
|
#include <QMouseEvent>
|
|
|
|
|
|
#define TREEVIEW_INDENT_ADD 12
|
|
|
|
SourceDelegate::SourceDelegate( QAbstractItemView* parent )
|
|
: QStyledItemDelegate( parent )
|
|
, m_parent( parent )
|
|
, m_lastClicked( -1 )
|
|
{
|
|
m_dropTypeMap.insert( 0, SourceTreeItem::DropTypeThisTrack );
|
|
m_dropTypeMap.insert( 1, SourceTreeItem::DropTypeThisAlbum );
|
|
m_dropTypeMap.insert( 2, SourceTreeItem::DropTypeAllFromArtist );
|
|
m_dropTypeMap.insert( 3, SourceTreeItem::DropTypeLocalItems );
|
|
m_dropTypeMap.insert( 4, SourceTreeItem::DropTypeTop50 );
|
|
|
|
m_dropTypeTextMap.insert( 0, tr( "Track" ) );
|
|
m_dropTypeTextMap.insert( 1, tr( "Album" ) );
|
|
m_dropTypeTextMap.insert( 2, tr( "Artist" ) );
|
|
m_dropTypeTextMap.insert( 3, tr( "Local" ) );
|
|
m_dropTypeTextMap.insert( 4, tr( "Top 10" ) );
|
|
|
|
m_dropTypeImageMap.insert( 0, QPixmap( RESPATH "images/drop-song.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) );
|
|
m_dropTypeImageMap.insert( 1, QPixmap( RESPATH "images/drop-album.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) );
|
|
m_dropTypeImageMap.insert( 2, QPixmap( RESPATH "images/drop-all-songs.png" ).scaledToHeight( 32, Qt::SmoothTransformation ) );
|
|
m_dropTypeImageMap.insert( 3, QPixmap( RESPATH "images/drop-local-songs.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) );
|
|
m_dropTypeImageMap.insert( 4, QPixmap( RESPATH "images/drop-top-songs.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) );
|
|
|
|
m_dropMimeData = new QMimeData();
|
|
|
|
m_headphonesOff.load( RESPATH "images/headphones-off.png" );
|
|
m_headphonesOn.load( RESPATH "images/headphones-sidebar.png" );
|
|
m_realtimeLocked.load( RESPATH "images/closed-padlock.png" );
|
|
m_realtimeUnlocked.load( RESPATH "images/open-padlock.png" );
|
|
m_nowPlayingSpeaker.load( RESPATH "images/now-playing-speaker.png" );
|
|
m_nowPlayingSpeakerDark.load( RESPATH "images/now-playing-speaker-dark.png" );
|
|
m_collaborativeOn.load( RESPATH "images/green-dot.png" );
|
|
}
|
|
|
|
|
|
SourceDelegate::~SourceDelegate()
|
|
{
|
|
delete m_dropMimeData;
|
|
}
|
|
|
|
|
|
QSize
|
|
SourceDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const
|
|
{
|
|
SourceTreeItem* item = index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >();
|
|
SourcesModel::RowType type = static_cast< SourcesModel::RowType >( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() );
|
|
|
|
if ( type == SourcesModel::Collection )
|
|
{
|
|
return QSize( option.rect.width(), option.fontMetrics.height() * 3.0 );
|
|
}
|
|
else if ( type == SourcesModel::Divider )
|
|
{
|
|
return QSize( option.rect.width(), 6 );
|
|
}
|
|
else if ( type == SourcesModel::Group )
|
|
{
|
|
int groupSpacer = index.row() > 0 ? option.fontMetrics.height() * 0.6 : option.fontMetrics.height() * 0.2;
|
|
return QSize( option.rect.width(), option.fontMetrics.height() + groupSpacer );
|
|
}
|
|
else if ( m_expandedMap.contains( index ) )
|
|
{
|
|
if ( !m_expandedMap.value( index )->initialized() )
|
|
{
|
|
int dropTypes = dropTypeCount( item );
|
|
QSize originalSize = QSize( option.rect.width(), option.fontMetrics.height() * 1.4 );
|
|
QSize targetSize = originalSize + QSize( 0, dropTypes == 0 ? 0 : 38 + option.fontMetrics.height() * 1.4 );
|
|
m_expandedMap.value( index )->initialize( originalSize, targetSize, 300 );
|
|
m_expandedMap.value( index )->expand();
|
|
}
|
|
QMetaObject::invokeMethod( m_parent, "update", Qt::QueuedConnection, Q_ARG( QModelIndex, index ) );
|
|
return m_expandedMap.value( index )->size();
|
|
}
|
|
else
|
|
return QSize( option.rect.width(), option.fontMetrics.height() * 1.4 ); //QStyledItemDelegate::sizeHint( option, index ) );
|
|
}
|
|
|
|
|
|
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::GenericPage );
|
|
const bool playing = ( AudioEngine::instance()->isPlaying() || AudioEngine::instance()->isPaused() );
|
|
|
|
if ( playable && playing && item->isBeingPlayed() )
|
|
{
|
|
int iconW = option.rect.height() - 4;
|
|
if ( m_expandedMap.contains( index ) )
|
|
{
|
|
AnimationHelper* ah = m_expandedMap.value( index );
|
|
if ( ah->initialized() )
|
|
{
|
|
iconW = ah->originalSize().height() - 4;
|
|
}
|
|
}
|
|
|
|
QRect iconRect = QRect( 4, option.rect.y() + 2, iconW, iconW );
|
|
QPixmap speaker = option.state & QStyle::State_Selected ? m_nowPlayingSpeaker : m_nowPlayingSpeakerDark;
|
|
speaker = speaker.scaledToHeight( iconW, Qt::SmoothTransformation );
|
|
painter->drawPixmap( iconRect, speaker );
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
SourceDelegate::paintCollection( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
|
|
{
|
|
painter->save();
|
|
|
|
QFont normal = option.font;
|
|
QFont bold = option.font;
|
|
bold.setBold( true );
|
|
|
|
QFont figFont = bold;
|
|
figFont.setFamily( "Arial Bold" );
|
|
figFont.setWeight( QFont::Black );
|
|
figFont.setPointSize( normal.pointSize() - 1 );
|
|
|
|
SourceTreeItem* item = index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >();
|
|
SourceItem* colItem = qobject_cast< SourceItem* >( item );
|
|
Q_ASSERT( colItem );
|
|
bool status = !( !colItem || colItem->source().isNull() || !colItem->source()->isOnline() );
|
|
|
|
QString tracks;
|
|
QString name = index.data().toString();
|
|
int figWidth = 0;
|
|
|
|
if ( status && colItem && !colItem->source().isNull() )
|
|
{
|
|
tracks = QString::number( colItem->source()->trackCount() );
|
|
figWidth = QFontMetrics( figFont ).width( tracks );
|
|
name = colItem->source()->friendlyName();
|
|
}
|
|
|
|
QRect iconRect = option.rect.adjusted( 4, 6, -option.rect.width() + option.rect.height() - 12 + 4, -6 );
|
|
QPixmap avatar = colItem->pixmap( iconRect.size() );
|
|
painter->drawPixmap( iconRect, avatar );
|
|
|
|
if ( ( option.state & QStyle::State_Selected ) == QStyle::State_Selected )
|
|
{
|
|
painter->setPen( option.palette.color( QPalette::HighlightedText ) );
|
|
}
|
|
|
|
QRect textRect = option.rect.adjusted( iconRect.width() + 8, 6, -figWidth - 28, 0 );
|
|
if ( status || colItem->source().isNull() )
|
|
painter->setFont( bold );
|
|
QString text = painter->fontMetrics().elidedText( name, Qt::ElideRight, textRect.width() );
|
|
painter->drawText( textRect, text );
|
|
|
|
bool isPlaying = !( colItem->source()->currentTrack().isNull() );
|
|
QString desc = colItem->source()->textStatus();
|
|
if ( colItem->source().isNull() )
|
|
desc = tr( "All available tracks" );
|
|
|
|
painter->setFont( normal );
|
|
textRect = option.rect.adjusted( iconRect.width() + 8, option.rect.height() / 2, -figWidth - 24, -6 );
|
|
|
|
bool privacyOn = TomahawkSettings::instance()->privateListeningMode() == TomahawkSettings::FullyPrivate;
|
|
if ( !colItem->source().isNull() && colItem->source()->isLocal() && privacyOn )
|
|
{
|
|
QRect pmRect = textRect;
|
|
pmRect.setRight( pmRect.left() + pmRect.height() );
|
|
ActionCollection::instance()->getAction( "togglePrivacy" )->icon().paint( painter, pmRect );
|
|
textRect.adjust( pmRect.width() + 3, 0, 0, 0 );
|
|
}
|
|
if ( isPlaying || ( !colItem->source().isNull() && colItem->source()->isLocal() ) )
|
|
{
|
|
// Show a listen icon
|
|
QPixmap listenAlongPixmap;
|
|
QPixmap realtimeListeningAlongPixmap;
|
|
if ( index.data( SourcesModel::LatchedOnRole ).toBool() )
|
|
{
|
|
// Currently listening along
|
|
listenAlongPixmap = m_headphonesOn;
|
|
if ( !colItem->source()->isLocal() )
|
|
{
|
|
realtimeListeningAlongPixmap =
|
|
colItem->source()->playlistInterface()->latchMode() == Tomahawk::PlaylistModes::RealTime ?
|
|
m_realtimeLocked : m_realtimeUnlocked;
|
|
}
|
|
}
|
|
else if ( !colItem->source()->isLocal() )
|
|
{
|
|
listenAlongPixmap = m_headphonesOff;
|
|
}
|
|
|
|
if ( !listenAlongPixmap.isNull() )
|
|
{
|
|
QRect pmRect = textRect;
|
|
pmRect.setRight( pmRect.left() + pmRect.height() );
|
|
painter->drawPixmap( pmRect, listenAlongPixmap.scaledToHeight( pmRect.height(), Qt::SmoothTransformation ) );
|
|
textRect.adjust( pmRect.width() + 3, 0, 0, 0 );
|
|
|
|
m_headphoneRects[ index ] = pmRect;
|
|
}
|
|
else
|
|
m_headphoneRects.remove( index );
|
|
|
|
if ( !realtimeListeningAlongPixmap.isNull() )
|
|
{
|
|
QRect pmRect = textRect;
|
|
pmRect.setRight( pmRect.left() + pmRect.height() );
|
|
painter->drawPixmap( pmRect, realtimeListeningAlongPixmap.scaledToHeight( pmRect.height(), Qt::SmoothTransformation ) );
|
|
textRect.adjust( pmRect.width() + 3, 0, 0, 0 );
|
|
|
|
m_lockRects[ index ] = pmRect;
|
|
}
|
|
else
|
|
m_lockRects.remove( index );
|
|
}
|
|
|
|
textRect.adjust( 0, 0, 0, 2 );
|
|
text = painter->fontMetrics().elidedText( desc, Qt::ElideRight, textRect.width() - 8 );
|
|
QTextOption to( Qt::AlignVCenter );
|
|
to.setWrapMode( QTextOption::NoWrap );
|
|
painter->drawText( textRect, text, to );
|
|
|
|
if ( colItem->source() && colItem->source()->currentTrack() )
|
|
m_trackRects[ index ] = textRect;
|
|
else
|
|
m_trackRects.remove( index );
|
|
|
|
if ( status )
|
|
{
|
|
painter->setRenderHint( QPainter::Antialiasing );
|
|
|
|
QRect figRect = option.rect.adjusted( option.rect.width() - figWidth - 13, 0, -14, -option.rect.height() + option.fontMetrics.height() * 1.1 );
|
|
int hd = ( option.rect.height() - figRect.height() ) / 2;
|
|
figRect.adjust( 0, hd, 0, hd );
|
|
|
|
painter->setFont( figFont );
|
|
|
|
QColor figColor( 167, 183, 211 );
|
|
painter->setPen( figColor );
|
|
painter->setBrush( figColor );
|
|
|
|
TomahawkUtils::drawBackgroundAndNumbers( painter, tracks, figRect );
|
|
}
|
|
|
|
painter->restore();
|
|
}
|
|
|
|
|
|
void
|
|
SourceDelegate::paintCategory( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
|
|
{
|
|
painter->save();
|
|
|
|
QTextOption to( Qt::AlignVCenter );
|
|
|
|
painter->setPen( option.palette.color( QPalette::Base ) );
|
|
painter->setBrush( option.palette.color( QPalette::Base ) );
|
|
painter->drawRect( option.rect );
|
|
painter->setRenderHint( QPainter::Antialiasing );
|
|
|
|
painter->setPen( Qt::white );
|
|
painter->drawText( option.rect.translated( 4, 1 ), index.data().toString().toUpper(), to );
|
|
painter->setPen( TomahawkUtils::Colors::GROUP_HEADER );
|
|
painter->drawText( option.rect.translated( 4, 0 ), index.data().toString().toUpper(), to );
|
|
|
|
if ( option.state & QStyle::State_MouseOver )
|
|
{
|
|
QString text = tr( "Show" );
|
|
if ( option.state & QStyle::State_Open )
|
|
text = tr( "Hide" );
|
|
|
|
QFont font = option.font;
|
|
font.setBold( true );
|
|
painter->setFont( font );
|
|
QTextOption to( Qt::AlignVCenter | Qt::AlignRight );
|
|
|
|
// draw close icon
|
|
painter->setPen( Qt::white );
|
|
painter->drawText( option.rect.translated( -4, 1 ), text, to );
|
|
painter->setPen( TomahawkUtils::Colors::GROUP_HEADER );
|
|
painter->drawText( option.rect.translated( -4, 0 ), text, to );
|
|
}
|
|
|
|
painter->restore();
|
|
}
|
|
|
|
|
|
void
|
|
SourceDelegate::paintGroup( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
|
|
{
|
|
painter->save();
|
|
|
|
QFont font = painter->font();
|
|
font.setPointSize( option.font.pointSize() + 1 );
|
|
font.setBold( true );
|
|
painter->setFont( font );
|
|
|
|
QTextOption to( Qt::AlignBottom );
|
|
|
|
painter->setPen( option.palette.color( QPalette::Base ) );
|
|
painter->setBrush( option.palette.color( QPalette::Base ) );
|
|
painter->drawRect( option.rect );
|
|
painter->setRenderHint( QPainter::Antialiasing );
|
|
|
|
painter->setPen( Qt::white );
|
|
painter->drawText( option.rect.translated( 4, 1 ), index.data().toString().toUpper(), to );
|
|
painter->setPen( TomahawkUtils::Colors::GROUP_HEADER );
|
|
painter->drawText( option.rect.translated( 4, 0 ), index.data().toString().toUpper(), to );
|
|
|
|
if ( option.state & QStyle::State_MouseOver )
|
|
{
|
|
QString text = tr( "Show" );
|
|
if ( option.state & QStyle::State_Open )
|
|
text = tr( "Hide" );
|
|
|
|
QFont font = option.font;
|
|
font.setBold( true );
|
|
painter->setFont( font );
|
|
QTextOption to( Qt::AlignBottom | Qt::AlignRight );
|
|
|
|
// draw close icon
|
|
painter->setPen( Qt::white );
|
|
painter->drawText( option.rect.translated( -4, 1 ), text, to );
|
|
painter->setPen( TomahawkUtils::Colors::GROUP_HEADER );
|
|
painter->drawText( option.rect.translated( -4, 0 ), text, to );
|
|
}
|
|
|
|
painter->restore();
|
|
}
|
|
|
|
|
|
void
|
|
SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
|
|
{
|
|
QStyleOptionViewItem o = option;
|
|
QStyleOptionViewItemV4 o3 = option;
|
|
|
|
painter->save();
|
|
|
|
SourcesModel::RowType type = static_cast< SourcesModel::RowType >( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() );
|
|
SourceTreeItem* item = index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >();
|
|
Q_ASSERT( item );
|
|
|
|
if ( ( option.state & QStyle::State_Enabled ) == QStyle::State_Enabled )
|
|
{
|
|
o.state = QStyle::State_Enabled;
|
|
|
|
if ( ( option.state & QStyle::State_MouseOver ) == QStyle::State_MouseOver )
|
|
{
|
|
o.state |= QStyle::State_MouseOver;
|
|
o3.state |= QStyle::State_MouseOver;
|
|
}
|
|
|
|
if ( ( option.state & QStyle::State_Open ) == QStyle::State_Open )
|
|
{
|
|
o.state |= QStyle::State_Open;
|
|
}
|
|
|
|
if ( ( option.state & QStyle::State_Selected ) == QStyle::State_Selected )
|
|
{
|
|
if ( type != SourcesModel::Collection )
|
|
o3.state |= QStyle::State_Selected;
|
|
else
|
|
o3.state &= ~QStyle::State_Selected;
|
|
|
|
o.palette.setColor( QPalette::Base, QColor( 0, 0, 0, 0 ) );
|
|
o.palette.setColor( QPalette::Text, o.palette.color( QPalette::HighlightedText ) );
|
|
o3.palette.setColor( QPalette::Text, o.palette.color( QPalette::HighlightedText ) );
|
|
}
|
|
}
|
|
|
|
// shrink the indentations
|
|
{
|
|
int indentMult = 0;
|
|
QModelIndex counter = index;
|
|
while ( counter.parent().isValid() )
|
|
{
|
|
indentMult++;
|
|
counter = counter.parent();
|
|
}
|
|
|
|
int indentDelta = o.rect.x() - m_parent->viewport()->x();
|
|
o.rect.setX( o.rect.x() - indentDelta + indentMult * TREEVIEW_INDENT_ADD );
|
|
o3.rect.setX( 0 );
|
|
}
|
|
|
|
if ( type != SourcesModel::Group && type != SourcesModel::Category && type != SourcesModel::Divider )
|
|
QApplication::style()->drawControl( QStyle::CE_ItemViewItem, &o3, painter );
|
|
|
|
if ( type == SourcesModel::Collection )
|
|
{
|
|
paintCollection( painter, o, index );
|
|
}
|
|
else if ( ( type == SourcesModel::StaticPlaylist || type == SourcesModel::CategoryAdd ) &&
|
|
m_expandedMap.contains( index ) && m_expandedMap.value( index )->partlyExpanded() && dropTypeCount( item ) > 0 )
|
|
{
|
|
// Let Qt paint the original item. We add our stuff after it
|
|
o.state &= ~QStyle::State_Selected;
|
|
o.showDecorationSelected = false;
|
|
o.rect.adjust( 0, 0, 0, - option.rect.height() + m_expandedMap.value( index )->originalSize().height() );
|
|
QStyledItemDelegate::paint( painter, o, index );
|
|
|
|
// Get whole rect for the menu
|
|
QRect itemsRect = option.rect.adjusted( -option.rect.x(), m_expandedMap.value( index )->originalSize().height(), 0, 0 );
|
|
QPoint cursorPos = m_parent->mapFromGlobal( QCursor::pos() );
|
|
bool cursorInRect = itemsRect.contains( cursorPos );
|
|
|
|
// draw the background
|
|
if ( m_gradient.finalStop() != itemsRect.bottomLeft() )
|
|
{
|
|
m_gradient = QLinearGradient( itemsRect.topLeft(), itemsRect.bottomLeft() );
|
|
m_gradient.setColorAt( 0.0, Qt::white );
|
|
m_gradient.setColorAt( 0.9, QColor( 0x88, 0x88, 0x88 ) );
|
|
m_gradient.setColorAt( 1.0, QColor( 0x99, 0x99, 0x99 ) ); // dark grey
|
|
}
|
|
|
|
QPen pen = painter->pen();
|
|
painter->setPen( QPen( Qt::NoPen ) );
|
|
painter->setBrush( m_gradient );
|
|
painter->drawRect( itemsRect );
|
|
|
|
// calculate sizes for the icons
|
|
int totalCount = dropTypeCount( item );
|
|
int itemWidth = itemsRect.width() / totalCount;
|
|
int iconSpacing = ( itemWidth - 32 ) / 2;
|
|
|
|
// adjust to one single entry
|
|
itemsRect.adjust( 0, 0, -itemsRect.width() + itemWidth, 0 );
|
|
|
|
pen.setColor( Qt::white );
|
|
painter->setPen( pen );
|
|
|
|
QFont font = painter->font();
|
|
font.setPointSize( option.font.pointSize() - 1 );
|
|
painter->setFont( font );
|
|
QFont fontBold = painter->font();
|
|
fontBold.setBold( true );
|
|
|
|
QRect textRect;
|
|
QRect imageRect;
|
|
SourceTreeItem::DropTypes dropTypes = item->supportedDropTypes( m_dropMimeData );
|
|
|
|
int count = 0;
|
|
for ( int i = 0; i < 5; ++i )
|
|
{
|
|
if ( !dropTypes.testFlag( m_dropTypeMap.value( i ) ) )
|
|
continue;
|
|
|
|
if ( count > 0 )
|
|
itemsRect.adjust( itemWidth, 0, itemWidth, 0 );
|
|
|
|
if ( itemsRect.contains( cursorPos ) | !cursorInRect )
|
|
{
|
|
painter->setFont( fontBold );
|
|
m_hoveredDropType = m_dropTypeMap.value( i );
|
|
cursorInRect = true;
|
|
}
|
|
else
|
|
painter->setFont( font );
|
|
|
|
int textSpacing = ( itemWidth - painter->fontMetrics().width( m_dropTypeTextMap.value( i ) ) ) / 2;
|
|
textRect = itemsRect.adjusted( textSpacing - 1, itemsRect.height() - painter->fontMetrics().height() - 2, 0, 0 );
|
|
painter->drawText( textRect, m_dropTypeTextMap.value( i ) );
|
|
|
|
int maxHeight = itemsRect.height() - textRect.height() - 2;
|
|
int verticalOffset = qMax( 0, maxHeight - 32 );
|
|
if ( itemsRect.bottom() - textRect.height() - 2 > itemsRect.top() )
|
|
{
|
|
imageRect = itemsRect.adjusted( iconSpacing, verticalOffset, -iconSpacing, -textRect.height() - 2 );
|
|
painter->drawPixmap( imageRect.x(), imageRect.y(), m_dropTypeImageMap.value( i ).copy( 0, 32 - imageRect.height(), 32, imageRect.height() ) );
|
|
}
|
|
|
|
count++;
|
|
}
|
|
}
|
|
else if ( type == SourcesModel::Group )
|
|
{
|
|
paintGroup( painter, o3, index );
|
|
}
|
|
else if ( type == SourcesModel::Category )
|
|
{
|
|
paintCategory( painter, o, index );
|
|
}
|
|
else if ( type == SourcesModel::Divider )
|
|
{
|
|
QRect middle = o.rect.adjusted( 0, 2, 0, -2 );
|
|
painter->setRenderHint( QPainter::Antialiasing, false );
|
|
|
|
QColor bgcolor = o3.palette.color( QPalette::Base );
|
|
|
|
painter->setPen( bgcolor.darker( 120 ) );
|
|
painter->drawLine( middle.topLeft(), middle.topRight() );
|
|
painter->setPen( bgcolor.lighter( 120 ) );
|
|
painter->drawLine( middle.bottomLeft(), middle.bottomRight() );
|
|
}
|
|
else
|
|
{
|
|
o.state &= ~QStyle::State_MouseOver;
|
|
if ( !index.parent().parent().isValid() )
|
|
o.rect.adjust( 7, 0, 0, 0 );
|
|
|
|
if ( type == SourcesModel::TemporaryPage )
|
|
{
|
|
TemporaryPageItem* gpi = qobject_cast< TemporaryPageItem* >( item );
|
|
Q_ASSERT( gpi );
|
|
|
|
if ( gpi && o3.state & QStyle::State_MouseOver )
|
|
{
|
|
int padding = 3;
|
|
m_iconHeight = ( o3.rect.height() - 2 * padding );
|
|
|
|
o.rect.adjust( 0, 0, -( padding + m_iconHeight ), 0 );
|
|
QStyledItemDelegate::paint( painter, o, index );
|
|
|
|
// draw close icon
|
|
QPixmap p( RESPATH "images/list-remove.png" );
|
|
p = p.scaledToHeight( m_iconHeight, Qt::SmoothTransformation );
|
|
|
|
QRect r( o3.rect.right() - padding - m_iconHeight, padding + o3.rect.y(), m_iconHeight, m_iconHeight );
|
|
painter->drawPixmap( r, p );
|
|
}
|
|
else
|
|
QStyledItemDelegate::paint( painter, o, index );
|
|
}
|
|
else if ( type == SourcesModel::StaticPlaylist )
|
|
{
|
|
QStyledItemDelegate::paint( painter, o, index );
|
|
|
|
PlaylistItem* plItem = qobject_cast< PlaylistItem* >( item );
|
|
if ( plItem->canSubscribe() && !plItem->subscribedIcon().isNull() )
|
|
{
|
|
const int padding = 2;
|
|
const int imgWidth = o.rect.height() - 2*padding;
|
|
|
|
const QPixmap icon = plItem->subscribedIcon().scaled( imgWidth, imgWidth, Qt::KeepAspectRatio, Qt::SmoothTransformation );
|
|
|
|
const QRect subRect( o.rect.right() - padding - imgWidth, o.rect.top() + padding, imgWidth, imgWidth );
|
|
painter->drawPixmap( subRect, icon );
|
|
}
|
|
|
|
if ( plItem->collaborative() )
|
|
{
|
|
const int imgWidth = m_collaborativeOn.size().width();
|
|
const QRect subRect( o.rect.left(), o.rect.top(), imgWidth, imgWidth );
|
|
painter->drawPixmap( subRect, m_collaborativeOn );
|
|
|
|
}
|
|
}
|
|
else
|
|
QStyledItemDelegate::paint( painter, o, index );
|
|
}
|
|
|
|
paintDecorations( painter, o3, index );
|
|
|
|
painter->restore();
|
|
}
|
|
|
|
|
|
void
|
|
SourceDelegate::updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const
|
|
{
|
|
if ( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::StaticPlaylist )
|
|
editor->setGeometry( option.rect.adjusted( 20, 0, 0, 0 ) );
|
|
else
|
|
QStyledItemDelegate::updateEditorGeometry( editor, option, index );
|
|
|
|
editor->setGeometry( editor->geometry().adjusted( 2 * TREEVIEW_INDENT_ADD, 0, 0, 0 ) );
|
|
}
|
|
|
|
|
|
bool
|
|
SourceDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index )
|
|
{
|
|
bool hoveringTrack = false;
|
|
if ( m_trackRects.contains( index ) )
|
|
{
|
|
const QRect trackRect = m_trackRects[ index ];
|
|
const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
|
|
hoveringTrack = trackRect.contains( ev->pos() );
|
|
}
|
|
|
|
bool lockRectContainsClick = false, headphonesRectContainsClick = false;
|
|
if ( m_headphoneRects.contains( index ) )
|
|
{
|
|
const QRect headphoneRect = m_headphoneRects[ index ];
|
|
const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
|
|
headphonesRectContainsClick = headphoneRect.contains( ev->pos() );
|
|
}
|
|
if ( m_lockRects.contains( index ) )
|
|
{
|
|
const QRect lockRect = m_lockRects[ index ];
|
|
const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
|
|
lockRectContainsClick = lockRect.contains( ev->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 )
|
|
{
|
|
TemporaryPageItem* gpi = qobject_cast< TemporaryPageItem* >( index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >() );
|
|
Q_ASSERT( gpi );
|
|
QMouseEvent* ev = static_cast< QMouseEvent* >( event );
|
|
|
|
QStyleOptionViewItemV4 o = option;
|
|
initStyleOption( &o, index );
|
|
int padding = 3;
|
|
QRect r ( o.rect.right() - padding - m_iconHeight, padding + o.rect.y(), m_iconHeight, m_iconHeight );
|
|
|
|
if ( r.contains( ev->pos() ) )
|
|
{
|
|
if ( event->type() == QEvent::MouseButtonRelease )
|
|
{
|
|
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::Collection )
|
|
{
|
|
SourceItem* colItem = qobject_cast< SourceItem* >( index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >() );
|
|
Q_ASSERT( colItem );
|
|
|
|
if ( event->type() == QEvent::MouseButtonRelease && hoveringTrack && colItem->source() && colItem->source()->currentTrack() )
|
|
{
|
|
ViewManager::instance()->show( colItem->source()->currentTrack() );
|
|
return true;
|
|
}
|
|
|
|
if ( !colItem->source().isNull() && !colItem->source()->currentTrack().isNull() && !colItem->source()->isLocal() )
|
|
{
|
|
if ( headphonesRectContainsClick || lockRectContainsClick )
|
|
{
|
|
if ( event->type() == QEvent::MouseButtonRelease )
|
|
{
|
|
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 && type == SourcesModel::StaticPlaylist )
|
|
{
|
|
PlaylistItem* plItem = qobject_cast< PlaylistItem* >( index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >() );
|
|
Q_ASSERT( plItem );
|
|
|
|
QMouseEvent* mev = static_cast< QMouseEvent* >( event );
|
|
if ( plItem->canSubscribe() && !plItem->subscribedIcon().isNull() )
|
|
{
|
|
const int padding = 2;
|
|
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( mev->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 )
|
|
{
|
|
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 );
|
|
}
|
|
|
|
|
|
int
|
|
SourceDelegate::dropTypeCount( SourceTreeItem* item ) const
|
|
{
|
|
int menuCount = 0;
|
|
if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeThisTrack ) )
|
|
menuCount++;
|
|
|
|
if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeThisAlbum ) )
|
|
menuCount++;
|
|
|
|
if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeAllFromArtist ) )
|
|
menuCount++;
|
|
|
|
if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeLocalItems ) )
|
|
menuCount++;
|
|
|
|
if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeTop50 ) )
|
|
menuCount++;
|
|
|
|
return menuCount;
|
|
}
|
|
|
|
|
|
SourceTreeItem::DropType
|
|
SourceDelegate::hoveredDropType() const
|
|
{
|
|
return m_hoveredDropType;
|
|
}
|
|
|
|
|
|
void
|
|
SourceDelegate::hovered( const QModelIndex& index, const QMimeData* mimeData )
|
|
{
|
|
if ( !index.isValid() )
|
|
{
|
|
foreach ( AnimationHelper *helper, m_expandedMap )
|
|
{
|
|
helper->collapse();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( !m_expandedMap.contains( index ) )
|
|
{
|
|
foreach ( AnimationHelper *helper, m_expandedMap )
|
|
{
|
|
helper->collapse();
|
|
}
|
|
|
|
m_newDropHoverIndex = index;
|
|
m_dropMimeData->clear();
|
|
foreach ( const QString &mimeDataFormat, mimeData->formats() )
|
|
{
|
|
m_dropMimeData->setData( mimeDataFormat, mimeData->data( mimeDataFormat ) );
|
|
}
|
|
|
|
m_expandedMap.insert( m_newDropHoverIndex, new AnimationHelper( m_newDropHoverIndex ) );
|
|
connect( m_expandedMap.value( m_newDropHoverIndex ), SIGNAL( finished( QModelIndex ) ), SLOT( animationFinished( QModelIndex ) ) );
|
|
}
|
|
else
|
|
qDebug() << "expandedMap already contains index" << index;
|
|
}
|
|
|
|
|
|
void
|
|
SourceDelegate::dragLeaveEvent()
|
|
{
|
|
foreach ( AnimationHelper* helper, m_expandedMap )
|
|
{
|
|
helper->collapse( true );
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
SourceDelegate::animationFinished( const QModelIndex& index )
|
|
{
|
|
delete m_expandedMap.take( index );
|
|
}
|