File indexing completed on 2024-05-05 04:49:28

0001 /****************************************************************************************
0002  * Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org>                                *
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 #include "PrettyTreeView.h"
0018 
0019 #include "PaletteHandler.h"
0020 #include "SvgHandler.h"
0021 #include "widgets/PrettyTreeRoles.h"
0022 #include "widgets/PrettyTreeDelegate.h"
0023 
0024 #include <QAction>
0025 #include <QMouseEvent>
0026 #include <QPainter>
0027 #include <QToolTip>
0028 #include <QApplication>
0029 
0030 Q_DECLARE_METATYPE( QAction* )
0031 Q_DECLARE_METATYPE( QList<QAction*> )
0032 
0033 using namespace Amarok;
0034 
0035 PrettyTreeView::PrettyTreeView( QWidget *parent )
0036     : QTreeView( parent )
0037     , m_decoratorActionPressed( nullptr )
0038 {
0039     setAlternatingRowColors( true );
0040     setFrameStyle( QFrame::StyledPanel | QFrame::Sunken );
0041 
0042     The::paletteHandler()->updateItemView( this );
0043     connect( The::paletteHandler(), &PaletteHandler::newPalette, this, &PrettyTreeView::newPalette );
0044 
0045 #ifdef Q_WS_MAC
0046     // for some bizarre reason w/ some styles on mac per-pixel scrolling is slower than
0047     // per-item
0048     setVerticalScrollMode( QAbstractItemView::ScrollPerItem );
0049     setHorizontalScrollMode( QAbstractItemView::ScrollPerItem );
0050 #else
0051     // Scrolling per item is really not smooth and looks terrible
0052     setVerticalScrollMode( QAbstractItemView::ScrollPerPixel );
0053     setHorizontalScrollMode( QAbstractItemView::ScrollPerPixel );
0054 #endif
0055 
0056     setAnimated( true );
0057 }
0058 
0059 PrettyTreeView::~PrettyTreeView()
0060 {
0061 }
0062 
0063 void
0064 PrettyTreeView::edit( const QModelIndex &index )
0065 {
0066     QTreeView::edit( index );
0067 }
0068 
0069 bool
0070 PrettyTreeView::edit( const QModelIndex &index, QAbstractItemView::EditTrigger trigger, QEvent *event )
0071 {
0072     QModelIndex parent = index.parent();
0073     while( parent.isValid() )
0074     {
0075         expand( parent );
0076         parent = parent.parent();
0077     }
0078     return QAbstractItemView::edit( index, trigger, event );
0079 }
0080 
0081 void
0082 PrettyTreeView::drawRow( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const
0083 {
0084     QTreeView::drawRow( painter, option, index );
0085 
0086     const int width = option.rect.width();
0087     const int height = option.rect.height();
0088 
0089     if( height > 0 )
0090     {
0091         QPixmap background = The::svgHandler()->renderSvgWithDividers(
0092                 "service_list_item", width, height, "service_list_item" );
0093 
0094         painter->save();
0095         painter->drawPixmap( option.rect.topLeft().x(), option.rect.topLeft().y(), background );
0096         painter->restore();
0097     }
0098 }
0099 
0100 void
0101 PrettyTreeView::mouseMoveEvent( QMouseEvent *event )
0102 {
0103     // swallow the mouse move event in case the press was started on decorator action icon
0104     if( m_decoratorActionPressed )
0105         event->accept();
0106     else
0107         QTreeView::mouseMoveEvent( event );
0108 
0109     // Make sure we repaint the item for the collection action buttons
0110     const QModelIndex index = indexAt( event->pos() );
0111     const int actionsCount = index.data( PrettyTreeRoles::DecoratorRoleCount ).toInt();
0112     if( actionsCount )
0113         update( index );
0114 }
0115 
0116 void
0117 PrettyTreeView::mousePressEvent( QMouseEvent *event )
0118 {
0119     const QModelIndex index = indexAt( event->pos() );
0120 
0121     // reset state variables on every mouse button press
0122     m_expandCollapsePressedAt.reset();
0123     m_decoratorActionPressed = nullptr;
0124 
0125     // if root is decorated, it doesn't show any actions
0126     QAction *action = rootIsDecorated() ? nullptr : decoratorActionAt( index, event->pos() );
0127     if( action &&
0128         event->button() == Qt::LeftButton &&
0129         event->modifiers() == Qt::NoModifier &&
0130         state() == QTreeView::NoState )
0131     {
0132         m_decoratorActionPressed = action;
0133         update( index ); // trigger repaint to change icon effect
0134         event->accept();
0135         return;
0136     }
0137 
0138     bool prevExpandState = isExpanded( index );
0139 
0140     // This will toggle the expansion of the current item when clicking
0141     // on the fold marker but not on the item itself. Required here to
0142     // enable dragging.
0143     QTreeView::mousePressEvent( event );
0144 
0145     // if we press left mouse button on valid item which did not cause the expansion,
0146     // set m_expandCollapsePressedAt so that mouseReleaseEvent can perform the
0147     // expansion/collapsing
0148     if( index.isValid() &&
0149         prevExpandState == isExpanded( index ) &&
0150         event->button() == Qt::LeftButton &&
0151         event->modifiers() == Qt::NoModifier &&
0152         state() == QTreeView::NoState )
0153     {
0154         m_expandCollapsePressedAt.reset( new QPoint( event->pos() ) );
0155     }
0156 }
0157 
0158 void
0159 PrettyTreeView::mouseReleaseEvent( QMouseEvent *event )
0160 {
0161     const QModelIndex index = indexAt( event->pos() );
0162     // we want to reset m_expandCollapsePressedAt in either case, but still need its value
0163     QScopedPointer<QPoint> expandCollapsePressedAt( m_expandCollapsePressedAt.take() );
0164     // ditto for m_decoratorActionPressed
0165     QAction *decoratorActionPressed = m_decoratorActionPressed;
0166     m_decoratorActionPressed = nullptr;
0167 
0168     // if root is decorated, it doesn't show any actions
0169     QAction *action = rootIsDecorated() ? nullptr : decoratorActionAt( index, event->pos() );
0170     if( action &&
0171         action == decoratorActionPressed &&
0172         event->button() == Qt::LeftButton &&
0173         event->modifiers() == Qt::NoModifier )
0174     {
0175         action->trigger();
0176         update( index ); // trigger repaint to change icon effect
0177         event->accept();
0178         return;
0179     }
0180 
0181     if( index.isValid() &&
0182         event->button() == Qt::LeftButton &&
0183         event->modifiers() == Qt::NoModifier &&
0184         state() == QTreeView::NoState &&
0185         expandCollapsePressedAt &&
0186         ( *expandCollapsePressedAt - event->pos() ).manhattanLength() < QApplication::startDragDistance() &&
0187         style()->styleHint( QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this ) &&
0188         model()->hasChildren( index ) )
0189     {
0190         setExpanded( index, !isExpanded( index ) );
0191         event->accept();
0192         return;
0193     }
0194 
0195     QTreeView::mouseReleaseEvent( event );
0196 }
0197 
0198 bool
0199 PrettyTreeView::viewportEvent( QEvent *event )
0200 {
0201     if( event->type() == QEvent::ToolTip )
0202     {
0203         QHelpEvent *helpEvent = static_cast<QHelpEvent *>( event );
0204         const QModelIndex index = indexAt( helpEvent->pos() );
0205         // if root is decorated, it doesn't show any actions
0206         QAction *action = rootIsDecorated() ? nullptr : decoratorActionAt( index, helpEvent->pos() );
0207         if( action )
0208         {
0209             QToolTip::showText( helpEvent->globalPos(), action->toolTip() );
0210             event->accept();
0211             return true;
0212         }
0213     }
0214 
0215     // swallow the mouse hover event in case the press was started on decorator action icon
0216     // friend mouse move event is handled in mouseMoveEvent and triggers repaints
0217     if( event->type() == QEvent::HoverMove && m_decoratorActionPressed )
0218     {
0219         event->accept();
0220         return true;
0221     }
0222 
0223     return QAbstractItemView::viewportEvent( event );
0224 }
0225 
0226 QAction *
0227 PrettyTreeView::decoratorActionAt( const QModelIndex &index, const QPoint &pos )
0228 {
0229     const int actionsCount = index.data( PrettyTreeRoles::DecoratorRoleCount ).toInt();
0230     if( actionsCount <= 0 )
0231         return nullptr;
0232 
0233     PrettyTreeDelegate* ptd = qobject_cast<PrettyTreeDelegate*>( itemDelegate( index ) );
0234     if( !ptd )
0235         return nullptr;
0236 
0237     const QList<QAction *> actions = index.data( PrettyTreeRoles::DecoratorRole ).value<QList<QAction *> >();
0238     QRect rect = visualRect( index );
0239 
0240     for( int i = 0; i < actions.count(); i++ )
0241         if( ptd->decoratorRect( rect, i ).contains( pos ) )
0242             return actions.at( i );
0243 
0244     return nullptr;
0245 }
0246 
0247 QAction *
0248 PrettyTreeView::pressedDecoratorAction() const
0249 {
0250     return m_decoratorActionPressed;
0251 }
0252 
0253 void
0254 PrettyTreeView::newPalette( const QPalette & palette )
0255 {
0256     Q_UNUSED( palette )
0257     The::paletteHandler()->updateItemView( this );
0258     reset(); // redraw all potential delegates
0259 }