File indexing completed on 2025-01-05 04:26:38
0001 /**************************************************************************************** 0002 * Copyright (c) 2008 Soren Harward <stharward@gmail.com> * 0003 * Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> * 0004 * Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> * 0005 * Copyright (c) 2009 John Atkinson <john@fauxnetic.co.uk> * 0006 * Copyright (c) 2009-2010 Oleksandr Khayrullin <saniokh@gmail.com> * 0007 * Copyright (c) 2010 Nanno Langstraat <langstr@gmail.com> * 0008 * * 0009 * This program is free software; you can redistribute it and/or modify it under * 0010 * the terms of the GNU General Public License as published by the Free Software * 0011 * Foundation; either version 2 of the License, or (at your option) version 3 or * 0012 * any later version accepted by the membership of KDE e.V. (or its successor approved * 0013 * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of * 0014 * version 3 of the license. * 0015 * * 0016 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0017 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0018 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0019 * * 0020 * You should have received a copy of the GNU General Public License along with * 0021 * this program. If not, see <http://www.gnu.org/licenses/>. * 0022 ****************************************************************************************/ 0023 0024 #define DEBUG_PREFIX "Playlist::PrettyListView" 0025 0026 #include "PrettyListView.h" 0027 0028 #include "amarokconfig.h" 0029 #include "AmarokMimeData.h" 0030 #include "context/ContextView.h" 0031 #include "context/popupdropper/libpud/PopupDropper.h" 0032 #include "core/support/Debug.h" 0033 #include "EngineController.h" 0034 #include "dialogs/TagDialog.h" 0035 #include "GlobalCurrentTrackActions.h" 0036 #include "core/capabilities/ActionsCapability.h" 0037 #include "core/capabilities/FindInSourceCapability.h" 0038 #include "core/capabilities/MultiSourceCapability.h" 0039 #include "core/meta/Meta.h" 0040 #include "PaletteHandler.h" 0041 #include "playlist/layouts/LayoutManager.h" 0042 #include "playlist/proxymodels/GroupingProxy.h" 0043 #include "playlist/PlaylistActions.h" 0044 #include "playlist/PlaylistModelStack.h" 0045 #include "playlist/PlaylistController.h" 0046 #include "playlist/view/PlaylistViewCommon.h" 0047 #include "playlist/PlaylistDefines.h" 0048 #include "PopupDropperFactory.h" 0049 #include "SvgHandler.h" 0050 #include "SourceSelectionPopup.h" 0051 0052 #include <QApplication> 0053 #include <QClipboard> 0054 #include <QContextMenuEvent> 0055 #include <QDropEvent> 0056 #include <QItemSelection> 0057 #include <QKeyEvent> 0058 #include <QListView> 0059 #include <QMenu> 0060 #include <QModelIndex> 0061 #include <QMouseEvent> 0062 #include <QPainter> 0063 #include <QPalette> 0064 #include <QPersistentModelIndex> 0065 #include <QScrollBar> 0066 #include <QSvgRenderer> 0067 #include <QTimer> 0068 #include <QUrl> 0069 0070 #include <KLocalizedString> 0071 0072 0073 Playlist::PrettyListView::PrettyListView( QWidget* parent ) 0074 : QListView( parent ) 0075 , ViewCommon() 0076 , m_headerPressIndex( QModelIndex() ) 0077 , m_mousePressInHeader( false ) 0078 , m_skipAutoScroll( false ) 0079 , m_firstScrollToActiveTrack( true ) 0080 , m_rowsInsertedScrollItem( 0 ) 0081 , m_showOnlyMatches( false ) 0082 , m_pd( nullptr ) 0083 { 0084 // QAbstractItemView basics 0085 setModel( The::playlist()->qaim() ); 0086 0087 m_prettyDelegate = new PrettyItemDelegate( this ); 0088 connect( m_prettyDelegate, &PrettyItemDelegate::redrawRequested, this, &Playlist::PrettyListView::redrawActive ); 0089 setItemDelegate( m_prettyDelegate ); 0090 0091 setSelectionMode( ExtendedSelection ); 0092 setDragDropMode( DragDrop ); 0093 setDropIndicatorShown( false ); // we draw our own drop indicator 0094 setEditTriggers ( SelectedClicked | EditKeyPressed ); 0095 setAutoScroll( true ); 0096 0097 setVerticalScrollMode( ScrollPerPixel ); 0098 0099 setMouseTracking( true ); 0100 0101 // Rendering adjustments 0102 setFrameShape( QFrame::NoFrame ); 0103 setAlternatingRowColors( true) ; 0104 The::paletteHandler()->updateItemView( this ); 0105 connect( The::paletteHandler(), &PaletteHandler::newPalette, this, &PrettyListView::newPalette ); 0106 0107 setAutoFillBackground( false ); 0108 0109 0110 // Signal connections 0111 connect( this, &Playlist::PrettyListView::doubleClicked, 0112 this, &Playlist::PrettyListView::trackActivated ); 0113 connect( selectionModel(), &QItemSelectionModel::selectionChanged, 0114 this, &Playlist::PrettyListView::slotSelectionChanged ); 0115 0116 connect( LayoutManager::instance(), &LayoutManager::activeLayoutChanged, this, &PrettyListView::playlistLayoutChanged ); 0117 0118 if (auto m = static_cast<Playlist::Model*>(model())) 0119 { 0120 connect( m, &Playlist::Model::activeTrackChanged, this, &Playlist::PrettyListView::slotPlaylistActiveTrackChanged ); 0121 connect( m, &Playlist::Model::queueChanged, viewport(), QOverload<>::of(&QWidget::update) ); 0122 } 0123 else 0124 warning() << "Model is not a Playlist::Model"; 0125 0126 // Warning, this one doesn't connect to the normal 'model()' (i.e. '->top()'), but to '->bottom()'. 0127 connect( Playlist::ModelStack::instance()->bottom(), &Playlist::Model::rowsInserted, this, &Playlist::PrettyListView::bottomModelRowsInserted ); 0128 0129 // Timers 0130 m_proxyUpdateTimer = new QTimer( this ); 0131 m_proxyUpdateTimer->setSingleShot( true ); 0132 connect( m_proxyUpdateTimer, &QTimer::timeout, this, &Playlist::PrettyListView::updateProxyTimeout ); 0133 0134 m_animationTimer = new QTimer(this); 0135 connect( m_animationTimer, &QTimer::timeout, this, &Playlist::PrettyListView::redrawActive ); 0136 m_animationTimer->setInterval( 250 ); 0137 0138 playlistLayoutChanged(); 0139 0140 // We do the following call here to be formally correct, but note: 0141 // - It happens to be redundant, because 'playlistLayoutChanged()' already schedules 0142 // another one, via a QTimer( 0 ). 0143 // - Both that one and this one don't work right (they scroll like 'PositionAtTop', 0144 // not 'PositionAtCenter'). This is probably because MainWindow changes its 0145 // geometry in a QTimer( 0 )? As a fix, MainWindow does a 'slotShowActiveTrack()' 0146 // at the end of its QTimer slot, which will finally scroll to the right spot. 0147 slotPlaylistActiveTrackChanged(); 0148 } 0149 0150 Playlist::PrettyListView::~PrettyListView() 0151 {} 0152 0153 int 0154 Playlist::PrettyListView::verticalOffset() const 0155 { 0156 int ret = QListView::verticalOffset(); 0157 if ( verticalScrollBar() && verticalScrollBar()->maximum() ) 0158 ret += verticalScrollBar()->value() * 10 / verticalScrollBar()->maximum(); 0159 return ret; 0160 } 0161 0162 void 0163 Playlist::PrettyListView::editTrackInformation() 0164 { 0165 Meta::TrackList tl; 0166 foreach( const QModelIndex &index, selectedIndexes() ) 0167 { 0168 tl.append( index.data( TrackRole ).value<Meta::TrackPtr>() ); 0169 } 0170 0171 if( !tl.isEmpty() ) 0172 { 0173 TagDialog *dialog = new TagDialog( tl, this ); 0174 dialog->show(); 0175 } 0176 } 0177 0178 void 0179 Playlist::PrettyListView::playFirstSelected() 0180 { 0181 QModelIndexList selected = selectedIndexes(); 0182 if( !selected.isEmpty() ) 0183 trackActivated( selected.first() ); 0184 } 0185 0186 void 0187 Playlist::PrettyListView::removeSelection() 0188 { 0189 QList<int> sr = selectedRows(); 0190 if( !sr.isEmpty() ) 0191 { 0192 // Now that we have the list of selected rows in the topmost proxy, we can perform the 0193 // removal. 0194 The::playlistController()->removeRows( sr ); 0195 0196 // Next, we look for the first row. 0197 int firstRow = sr.first(); 0198 foreach( int i, sr ) 0199 { 0200 if( i < firstRow ) 0201 firstRow = i; 0202 } 0203 0204 //Select the track occupied by the first deleted track. Also move the current item to here as 0205 //button presses up or down wil otherwise not behave as expected. 0206 firstRow = qBound( 0, firstRow, model()->rowCount() - 1 ); 0207 QModelIndex newSelectionIndex = model()->index( firstRow, 0 ); 0208 setCurrentIndex( newSelectionIndex ); 0209 selectionModel()->select( newSelectionIndex, QItemSelectionModel::Select ); 0210 } 0211 } 0212 0213 void 0214 Playlist::PrettyListView::queueSelection() 0215 { 0216 Actions::instance()->queue( selectedRows() ); 0217 } 0218 0219 void 0220 Playlist::PrettyListView::dequeueSelection() 0221 { 0222 Actions::instance()->dequeue( selectedRows() ); 0223 } 0224 0225 void 0226 Playlist::PrettyListView::switchQueueState() // slot 0227 { 0228 DEBUG_BLOCK 0229 const bool isQueued = currentIndex().data( Playlist::QueuePositionRole ).toInt() != 0; 0230 isQueued ? dequeueSelection() : queueSelection(); 0231 } 0232 0233 void Playlist::PrettyListView::selectSource() 0234 { 0235 DEBUG_BLOCK 0236 0237 QList<int> rows = selectedRows(); 0238 0239 //for now, bail out of more than 1 row... 0240 if ( rows.count() != 1 ) 0241 return; 0242 0243 //get the track... 0244 QModelIndex index = model()->index( rows.at( 0 ), 0 ); 0245 Meta::TrackPtr track = index.data( Playlist::TrackRole ).value< Meta::TrackPtr >(); 0246 0247 //get multiSource capability: 0248 0249 Capabilities::MultiSourceCapability *msc = track->create<Capabilities::MultiSourceCapability>(); 0250 if ( msc ) 0251 { 0252 debug() << "sources: " << msc->sources(); 0253 SourceSelectionPopup * sourceSelector = new SourceSelectionPopup( this, msc ); 0254 sourceSelector->show(); 0255 //dialog deletes msc when done with it. 0256 } 0257 } 0258 0259 void 0260 Playlist::PrettyListView::scrollToActiveTrack() 0261 { 0262 DEBUG_BLOCK 0263 0264 if( m_skipAutoScroll ) 0265 { 0266 m_skipAutoScroll = false; 0267 return; 0268 } 0269 0270 QModelIndex activeIndex = model()->index( The::playlist()->activeRow(), 0, QModelIndex() ); 0271 if ( activeIndex.isValid() ) 0272 { 0273 scrollTo( activeIndex, QAbstractItemView::PositionAtCenter ); 0274 m_firstScrollToActiveTrack = false; 0275 m_rowsInsertedScrollItem = 0; // This "new active track" scroll supersedes a pending "rows inserted" scroll. 0276 } 0277 } 0278 0279 void 0280 Playlist::PrettyListView::downOneTrack() 0281 { 0282 DEBUG_BLOCK 0283 0284 moveTrackSelection( 1 ); 0285 } 0286 0287 void 0288 Playlist::PrettyListView::upOneTrack() 0289 { 0290 DEBUG_BLOCK 0291 0292 moveTrackSelection( -1 ); 0293 } 0294 0295 void 0296 Playlist::PrettyListView::moveTrackSelection( int offset ) 0297 { 0298 if ( offset == 0 ) 0299 return; 0300 0301 int finalRow = model()->rowCount() - 1; 0302 int target = 0; 0303 0304 if ( offset < 0 ) 0305 target = finalRow; 0306 0307 QList<int> rows = selectedRows(); 0308 if ( rows.count() > 0 ) 0309 target = rows.at( 0 ) + offset; 0310 0311 target = qBound(0, target, finalRow); 0312 QModelIndex index = model()->index( target, 0 ); 0313 setCurrentIndex( index ); 0314 } 0315 0316 void 0317 Playlist::PrettyListView::slotPlaylistActiveTrackChanged() 0318 { 0319 DEBUG_BLOCK 0320 0321 // A playlist 'activeTrackChanged' signal happens: 0322 // - During startup, on "saved playlist" load. (Might happen before this view exists) 0323 // - When Amarok starts playing a new item in the playlist. 0324 // In that case, don't auto-scroll if the user doesn't like us to. 0325 0326 if( AmarokConfig::autoScrollPlaylist() || m_firstScrollToActiveTrack ) 0327 scrollToActiveTrack(); 0328 } 0329 0330 void 0331 Playlist::PrettyListView::slotSelectionChanged() 0332 { 0333 m_lastTimeSelectionChanged = QDateTime::currentDateTime(); 0334 } 0335 0336 void 0337 Playlist::PrettyListView::trackActivated( const QModelIndex& idx ) 0338 { 0339 DEBUG_BLOCK 0340 m_skipAutoScroll = true; // we don't want to do crazy view changes when selecting an item in the view 0341 Actions::instance()->play( idx ); 0342 0343 //make sure that the track we just activated is also set as the current index or 0344 //the selected index will get moved to the first row, making keyboard navigation difficult (BUG 225791) 0345 selectionModel_setCurrentIndex( idx, QItemSelectionModel::ClearAndSelect ); 0346 0347 setFocus(); 0348 } 0349 0350 0351 // The following 2 functions are a workaround for crash BUG 222961 and BUG 229240: 0352 // There appears to be a bad interaction between Qt 'setCurrentIndex()' and 0353 // Qt 'selectedIndexes()' / 'selectionModel()->select()' / 'scrollTo()'. 0354 // 0355 // 'setCurrentIndex()' appears to do something bad with its QModelIndex parameter, 0356 // leading to a crash deep within Qt. 0357 // 0358 // It might be our fault, but we suspect a bug in Qt. (Qt 4.6 at least) 0359 // 0360 // The problem goes away if we use a fresh QModelIndex, which we also don't re-use 0361 // afterwards. 0362 void 0363 Playlist::PrettyListView::setCurrentIndex( const QModelIndex &index ) 0364 { 0365 QModelIndex indexCopy = model()->index( index.row(), index.column() ); 0366 QListView::setCurrentIndex( indexCopy ); 0367 } 0368 0369 void 0370 Playlist::PrettyListView::selectionModel_setCurrentIndex( const QModelIndex &index, QItemSelectionModel::SelectionFlags command ) 0371 { 0372 QModelIndex indexCopy = model()->index( index.row(), index.column() ); 0373 selectionModel()->setCurrentIndex( indexCopy, command ); 0374 } 0375 0376 void 0377 Playlist::PrettyListView::showEvent( QShowEvent* event ) 0378 { 0379 QTimer::singleShot( 0, this, &Playlist::PrettyListView::fixInvisible ); 0380 0381 QListView::showEvent( event ); 0382 } 0383 0384 // This method is a workaround for BUG 184714. 0385 // 0386 // It prevents the playlist from becoming invisible (clear) after changing the model, while Amarok is hidden in the tray. 0387 // Without this workaround the playlist stays invisible when the application is restored from the tray. 0388 // This is especially a problem with the Dynamic Playlist mode, which modifies the model without user interaction. 0389 // 0390 // The bug only seems to happen with Qt 4.5.x, so it might actually be a bug in Qt. 0391 void 0392 Playlist::PrettyListView::fixInvisible() //SLOT 0393 { 0394 // DEBUG_BLOCK 0395 0396 // Part 1: Palette change 0397 newPalette( palette() ); 0398 0399 // Part 2: Change item selection 0400 const QItemSelection oldSelection( selectionModel()->selection() ); 0401 selectionModel()->clear(); 0402 selectionModel()->select( oldSelection, QItemSelectionModel::SelectCurrent ); 0403 0404 // NOTE: A simple update() call is not sufficient, but in fact the above two steps are required. 0405 } 0406 0407 void 0408 Playlist::PrettyListView::contextMenuEvent( QContextMenuEvent* event ) 0409 { 0410 DEBUG_BLOCK 0411 QModelIndex index = indexAt( event->pos() ); 0412 0413 if ( !index.isValid() ) 0414 return; 0415 0416 //Ctrl + Right Click is used for queuing 0417 if( event->modifiers() & Qt::ControlModifier ) 0418 return; 0419 0420 trackMenu( this, &index, event->globalPos() ); 0421 event->accept(); 0422 } 0423 0424 void 0425 Playlist::PrettyListView::dragLeaveEvent( QDragLeaveEvent* event ) 0426 { 0427 m_mousePressInHeader = false; 0428 m_dropIndicator = QRect( 0, 0, 0, 0 ); 0429 QListView::dragLeaveEvent( event ); 0430 } 0431 0432 void 0433 Playlist::PrettyListView::stopAfterTrack() 0434 { 0435 const quint64 id = currentIndex().data( UniqueIdRole ).value<quint64>(); 0436 if( Actions::instance()->willStopAfterTrack( id ) ) 0437 Actions::instance()->stopAfterPlayingTrack( 0 ); // disable stopping 0438 else 0439 Actions::instance()->stopAfterPlayingTrack( id ); 0440 } 0441 0442 void 0443 Playlist::PrettyListView::findInSource() 0444 { 0445 DEBUG_BLOCK 0446 0447 Meta::TrackPtr track = currentIndex().data( TrackRole ).value<Meta::TrackPtr>(); 0448 if ( track ) 0449 { 0450 if( track->has<Capabilities::FindInSourceCapability>() ) 0451 { 0452 Capabilities::FindInSourceCapability *fis = track->create<Capabilities::FindInSourceCapability>(); 0453 if ( fis ) 0454 { 0455 fis->findInSource(); 0456 } 0457 delete fis; 0458 } 0459 } 0460 } 0461 0462 void 0463 Playlist::PrettyListView::dragEnterEvent( QDragEnterEvent *event ) 0464 { 0465 const QMimeData *mime = event->mimeData(); 0466 if( mime->hasUrls() || 0467 mime->hasFormat( AmarokMimeData::TRACK_MIME ) || 0468 mime->hasFormat( AmarokMimeData::PLAYLIST_MIME ) || 0469 mime->hasFormat( AmarokMimeData::PODCASTEPISODE_MIME ) || 0470 mime->hasFormat( AmarokMimeData::PODCASTCHANNEL_MIME ) ) 0471 { 0472 event->acceptProposedAction(); 0473 } 0474 } 0475 0476 void 0477 Playlist::PrettyListView::dragMoveEvent( QDragMoveEvent* event ) 0478 { 0479 QModelIndex index = indexAt( event->pos() ); 0480 if ( index.isValid() ) 0481 { 0482 m_dropIndicator = visualRect( index ); 0483 } 0484 else 0485 { 0486 // draw it on the bottom of the last item 0487 index = model()->index( model()->rowCount() - 1, 0, QModelIndex() ); 0488 m_dropIndicator = visualRect( index ); 0489 m_dropIndicator = m_dropIndicator.translated( 0, m_dropIndicator.height() ); 0490 } 0491 QListView::dragMoveEvent( event ); 0492 } 0493 0494 void 0495 Playlist::PrettyListView::dropEvent( QDropEvent* event ) 0496 { 0497 DEBUG_BLOCK 0498 QRect oldDrop = m_dropIndicator; 0499 m_dropIndicator = QRect( 0, 0, 0, 0 ); 0500 if ( qobject_cast<PrettyListView*>( event->source() ) == this ) 0501 { 0502 QAbstractItemModel* plModel = model(); 0503 int targetRow = indexAt( event->pos() ).row(); 0504 targetRow = ( targetRow < 0 ) ? plModel->rowCount() : targetRow; // target of < 0 means we dropped on the end of the playlist 0505 QList<int> sr = selectedRows(); 0506 int realtarget = The::playlistController()->moveRows( sr, targetRow ); 0507 QItemSelection selItems; 0508 foreach( int row, sr ) 0509 { 0510 Q_UNUSED( row ) 0511 selItems.select( plModel->index( realtarget, 0 ), plModel->index( realtarget, 0 ) ); 0512 realtarget++; 0513 } 0514 selectionModel()->select( selItems, QItemSelectionModel::ClearAndSelect ); 0515 event->accept(); 0516 } 0517 else 0518 { 0519 QListView::dropEvent( event ); 0520 } 0521 // add some padding around the old drop area which to repaint, as we add offsets when painting. See paintEvent(). 0522 oldDrop.adjust( -6, -6, 6, 6 ); 0523 repaint( oldDrop ); 0524 } 0525 0526 void 0527 Playlist::PrettyListView::keyPressEvent( QKeyEvent *event ) 0528 { 0529 if( event->matches( QKeySequence::Delete ) ) 0530 { 0531 removeSelection(); 0532 event->accept(); 0533 } 0534 else if( event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return ) 0535 { 0536 trackActivated( currentIndex() ); 0537 event->accept(); 0538 } 0539 else if( event->matches( QKeySequence::SelectAll ) ) 0540 { 0541 QModelIndex topIndex = model()->index( 0, 0 ); 0542 QModelIndex bottomIndex = model()->index( model()->rowCount() - 1, 0 ); 0543 QItemSelection selItems( topIndex, bottomIndex ); 0544 selectionModel()->select( selItems, QItemSelectionModel::ClearAndSelect ); 0545 event->accept(); 0546 } 0547 else 0548 QListView::keyPressEvent( event ); 0549 } 0550 0551 void 0552 Playlist::PrettyListView::mousePressEvent( QMouseEvent* event ) 0553 { 0554 //get the item that was clicked 0555 QModelIndex index = indexAt( event->pos() ); 0556 0557 //first of all, if a left click, check if the delegate wants to do something about this click 0558 if( event->button() == Qt::LeftButton ) 0559 { 0560 //we need to translate the position of the click into something relative to the item that was clicked. 0561 QRect itemRect = visualRect( index ); 0562 QPoint relPos = event->pos() - itemRect.topLeft(); 0563 0564 if( m_prettyDelegate->clicked( relPos, itemRect, index ) ) 0565 { 0566 event->accept(); 0567 return; //click already handled... 0568 } 0569 } 0570 0571 if ( mouseEventInHeader( event ) && ( event->button() == Qt::LeftButton ) ) 0572 { 0573 m_mousePressInHeader = true; 0574 m_headerPressIndex = QPersistentModelIndex( index ); 0575 int rows = index.data( GroupedTracksRole ).toInt(); 0576 QModelIndex bottomIndex = model()->index( index.row() + rows - 1, 0 ); 0577 0578 //offset by 1 as the actual header item is selected in QListView::mousePressEvent( event ); and is otherwise deselected again 0579 QItemSelection selItems( model()->index( index.row() + 1, 0 ), bottomIndex ); 0580 QItemSelectionModel::SelectionFlags command = headerPressSelectionCommand( index, event ); 0581 selectionModel()->select( selItems, command ); 0582 // TODO: if you're doing shift-select on rows above the header, then the rows following the header will be lost from the selection 0583 selectionModel_setCurrentIndex( index, QItemSelectionModel::NoUpdate ); 0584 } 0585 else 0586 { 0587 m_mousePressInHeader = false; 0588 } 0589 0590 if ( event->button() == Qt::MidButton ) 0591 { 0592 QUrl url( QApplication::clipboard()->text() ); 0593 if ( url.isValid() ) 0594 { 0595 QList<QUrl> urls = QList<QUrl>() << url; 0596 if( index.isValid() ) 0597 The::playlistController()->insertUrls( index.row() + 1, urls ); 0598 else 0599 The::playlistController()->insertOptioned( urls, Playlist::OnAppendToPlaylistAction ); 0600 } 0601 } 0602 0603 // This should always be forwarded, as it is used to determine the offset 0604 // relative to the mouse of the selection we are dragging! 0605 QListView::mousePressEvent( event ); 0606 0607 // This must go after the call to the super class as the current index is not yet selected otherwise 0608 // Queueing support for Ctrl Right click 0609 if( event->button() == Qt::RightButton && event->modifiers() & Qt::ControlModifier ) 0610 { 0611 QList<int> list; 0612 if (selectedRows().contains( index.row()) ) 0613 { 0614 // select all selected rows if mouse is over selection area 0615 list = selectedRows(); 0616 } 0617 else 0618 { 0619 // select only current mouse-over-index if mouse is out of selection area 0620 list.append( index.row() ); 0621 } 0622 0623 if( index.data( Playlist::QueuePositionRole ).toInt() != 0 ) 0624 Actions::instance()->dequeue( list ); 0625 else 0626 Actions::instance()->queue( list ); 0627 } 0628 } 0629 0630 void 0631 Playlist::PrettyListView::mouseReleaseEvent( QMouseEvent* event ) 0632 { 0633 if ( mouseEventInHeader( event ) && ( event->button() == Qt::LeftButton ) && m_mousePressInHeader && m_headerPressIndex.isValid() ) 0634 { 0635 QModelIndex index = indexAt( event->pos() ); 0636 if ( index == m_headerPressIndex ) 0637 { 0638 int rows = index.data( GroupedTracksRole ).toInt(); 0639 QModelIndex bottomIndex = model()->index( index.row() + rows - 1, 0 ); 0640 QItemSelection selItems( index, bottomIndex ); 0641 QItemSelectionModel::SelectionFlags command = headerReleaseSelectionCommand( index, event ); 0642 selectionModel()->select( selItems, command ); 0643 } 0644 event->accept(); 0645 } 0646 else 0647 { 0648 QListView::mouseReleaseEvent( event ); 0649 } 0650 m_mousePressInHeader = false; 0651 } 0652 0653 bool 0654 Playlist::PrettyListView::mouseEventInHeader( const QMouseEvent* event ) const 0655 { 0656 QModelIndex index = indexAt( event->pos() ); 0657 if ( index.data( GroupRole ).toInt() == Grouping::Head ) 0658 { 0659 QPoint mousePressPos = event->pos(); 0660 mousePressPos.rx() += horizontalOffset(); 0661 mousePressPos.ry() += verticalOffset(); 0662 return m_prettyDelegate->insideItemHeader( mousePressPos, rectForIndex( index ) ); 0663 } 0664 return false; 0665 } 0666 0667 void 0668 Playlist::PrettyListView::paintEvent( QPaintEvent *event ) 0669 { 0670 if( m_dropIndicator.isValid() || 0671 model()->rowCount( rootIndex() ) == 0 ) 0672 { 0673 QPainter painter( viewport() ); 0674 0675 if( m_dropIndicator.isValid() ) 0676 { 0677 const QPoint offset( 6, 0 ); 0678 QColor c = QApplication::palette().color( QPalette::Highlight ); 0679 painter.setPen( QPen( c, 6, Qt::SolidLine, Qt::RoundCap ) ); 0680 painter.drawLine( m_dropIndicator.topLeft() + offset, 0681 m_dropIndicator.topRight() - offset ); 0682 } 0683 0684 if( model()->rowCount( rootIndex() ) == 0 ) 0685 { 0686 // here we assume that an empty list is caused by the filter if it's active 0687 QString emptyText; 0688 if( m_showOnlyMatches && Playlist::ModelStack::instance()->bottom()->rowCount() > 0 ) 0689 emptyText = i18n( "Tracks have been hidden due to the active search." ); 0690 else 0691 emptyText = i18n( "Add some songs here by dragging them from all around." ); 0692 0693 QColor c = QApplication::palette().color( foregroundRole() ); 0694 c.setAlpha( c.alpha() / 2 ); 0695 painter.setPen( c ); 0696 painter.drawText( rect(), 0697 Qt::AlignCenter | Qt::TextWordWrap, 0698 emptyText ); 0699 } 0700 } 0701 0702 QListView::paintEvent( event ); 0703 } 0704 0705 void 0706 Playlist::PrettyListView::startDrag( Qt::DropActions supportedActions ) 0707 { 0708 DEBUG_BLOCK 0709 0710 QModelIndexList indices = selectedIndexes(); 0711 if( indices.isEmpty() ) 0712 return; // no items selected in the view, abort. See bug 226167 0713 0714 //Waah? when a parent item is dragged, startDrag is called a bunch of times 0715 static bool ongoingDrags = false; 0716 if( ongoingDrags ) 0717 return; 0718 ongoingDrags = true; 0719 0720 if( !m_pd ) 0721 m_pd = The::popupDropperFactory()->createPopupDropper( Context::ContextView::self() ); 0722 0723 if( m_pd && m_pd->isHidden() ) 0724 { 0725 m_pd->setSvgRenderer( The::svgHandler()->getRenderer( QStringLiteral("amarok/images/pud_items.svg") ) ); 0726 qDebug() << "svgHandler SVG renderer is " << (QObject*)(The::svgHandler()->getRenderer( QStringLiteral("amarok/images/pud_items.svg") )); 0727 qDebug() << "m_pd SVG renderer is " << (QObject*)(m_pd->svgRenderer()); 0728 qDebug() << "does play exist in renderer? " << ( The::svgHandler()->getRenderer( QStringLiteral("amarok/images/pud_items.svg") )->elementExists( QStringLiteral("load") ) ); 0729 0730 QList<QAction*> actions = actionsFor( this, &indices.first() ); 0731 foreach( QAction * action, actions ) 0732 m_pd->addItem( The::popupDropperFactory()->createItem( action ), true ); 0733 0734 m_pd->show(); 0735 } 0736 0737 QListView::startDrag( supportedActions ); 0738 debug() << "After the drag!"; 0739 0740 if( m_pd ) 0741 { 0742 debug() << "clearing PUD"; 0743 connect( m_pd, &PopupDropper::fadeHideFinished, m_pd, &PopupDropper::clear ); 0744 m_pd->hide(); 0745 } 0746 ongoingDrags = false; 0747 } 0748 0749 bool 0750 Playlist::PrettyListView::edit( const QModelIndex &index, EditTrigger trigger, QEvent *event ) 0751 { 0752 // we want to prevent a click to change the selection and open the editor (BR 220818) 0753 if( m_lastTimeSelectionChanged.msecsTo( QDateTime::currentDateTime() ) < qApp->doubleClickInterval() + 50 ) 0754 return false; 0755 return QListView::edit( index, trigger, event ); 0756 } 0757 0758 QItemSelectionModel::SelectionFlags 0759 Playlist::PrettyListView::headerPressSelectionCommand( const QModelIndex& index, const QMouseEvent* event ) const 0760 { 0761 if ( !index.isValid() ) 0762 return QItemSelectionModel::NoUpdate; 0763 0764 const bool shiftKeyPressed = event->modifiers() & Qt::ShiftModifier; 0765 //const bool controlKeyPressed = event->modifiers() & Qt::ControlModifier; 0766 const bool indexIsSelected = selectionModel()->isSelected( index ); 0767 const bool controlKeyPressed = event->modifiers() & Qt::ControlModifier; 0768 0769 if ( shiftKeyPressed ) 0770 return QItemSelectionModel::SelectCurrent; 0771 0772 if ( indexIsSelected && controlKeyPressed ) //make this consistent with how single items work. This also makes it possible to drag the header 0773 return QItemSelectionModel::Deselect; 0774 0775 return QItemSelectionModel::Select; 0776 } 0777 0778 QItemSelectionModel::SelectionFlags 0779 Playlist::PrettyListView::headerReleaseSelectionCommand( const QModelIndex& index, const QMouseEvent* event ) const 0780 { 0781 if ( !index.isValid() ) 0782 return QItemSelectionModel::NoUpdate; 0783 0784 const bool shiftKeyPressed = event->modifiers() & Qt::ShiftModifier; 0785 const bool controlKeyPressed = event->modifiers() & Qt::ControlModifier; 0786 0787 if ( !controlKeyPressed && !shiftKeyPressed ) 0788 return QItemSelectionModel::ClearAndSelect; 0789 return QItemSelectionModel::NoUpdate; 0790 } 0791 0792 QList<int> 0793 Playlist::PrettyListView::selectedRows() const 0794 { 0795 QList<int> rows; 0796 foreach( const QModelIndex &idx, selectedIndexes() ) 0797 rows.append( idx.row() ); 0798 return rows; 0799 } 0800 0801 void Playlist::PrettyListView::newPalette( const QPalette & palette ) 0802 { 0803 Q_UNUSED( palette ) 0804 The::paletteHandler()->updateItemView( this ); 0805 reset(); 0806 } 0807 0808 void Playlist::PrettyListView::find( const QString &searchTerm, int fields, bool filter ) 0809 { 0810 bool updateProxy = false; 0811 if ( ( The::playlist()->currentSearchFields() != fields ) || ( The::playlist()->currentSearchTerm() != searchTerm ) ) 0812 updateProxy = true; 0813 0814 m_searchTerm = searchTerm; 0815 m_fields = fields; 0816 m_filter = filter; 0817 0818 int row = The::playlist()->find( m_searchTerm, m_fields ); 0819 if( row != -1 ) 0820 { 0821 //select this track 0822 QModelIndex index = model()->index( row, 0 ); 0823 QItemSelection selItems( index, index ); 0824 selectionModel()->select( selItems, QItemSelectionModel::SelectCurrent ); 0825 } 0826 0827 //instead of kicking the proxy right away, start a small timeout. 0828 //this stops us from updating it for each letter of a long search term, 0829 //and since it does not affect any views, this is fine. Worst case is that 0830 //a navigator skips to a track form the old search if the track change happens 0831 //before this timeout. Only start count if values have actually changed! 0832 if ( updateProxy ) 0833 startProxyUpdateTimeout(); 0834 } 0835 0836 void Playlist::PrettyListView::findNext( const QString & searchTerm, int fields ) 0837 { 0838 DEBUG_BLOCK 0839 QList<int> selected = selectedRows(); 0840 0841 bool updateProxy = false; 0842 if ( ( The::playlist()->currentSearchFields() != fields ) || ( The::playlist()->currentSearchTerm() != searchTerm ) ) 0843 updateProxy = true; 0844 0845 int currentRow = -1; 0846 if( selected.size() > 0 ) 0847 currentRow = selected.last(); 0848 0849 int row = The::playlist()->findNext( searchTerm, currentRow, fields ); 0850 if( row != -1 ) 0851 { 0852 //select this track 0853 0854 QModelIndex index = model()->index( row, 0 ); 0855 QItemSelection selItems( index, index ); 0856 selectionModel()->select( selItems, QItemSelectionModel::SelectCurrent ); 0857 0858 QModelIndex foundIndex = model()->index( row, 0, QModelIndex() ); 0859 setCurrentIndex( foundIndex ); 0860 if ( foundIndex.isValid() ) 0861 scrollTo( foundIndex, QAbstractItemView::PositionAtCenter ); 0862 0863 Q_EMIT( found() ); 0864 } 0865 else 0866 Q_EMIT( notFound() ); 0867 0868 if ( updateProxy ) 0869 The::playlist()->filterUpdated(); 0870 } 0871 0872 void Playlist::PrettyListView::findPrevious( const QString & searchTerm, int fields ) 0873 { 0874 DEBUG_BLOCK 0875 QList<int> selected = selectedRows(); 0876 0877 bool updateProxy = false; 0878 if ( ( The::playlist()->currentSearchFields() != fields ) || ( The::playlist()->currentSearchTerm() != searchTerm ) ) 0879 updateProxy = true; 0880 0881 int currentRow = model()->rowCount(); 0882 if( selected.size() > 0 ) 0883 currentRow = selected.first(); 0884 0885 int row = The::playlist()->findPrevious( searchTerm, currentRow, fields ); 0886 if( row != -1 ) 0887 { 0888 //select this track 0889 0890 QModelIndex index = model()->index( row, 0 ); 0891 QItemSelection selItems( index, index ); 0892 selectionModel()->select( selItems, QItemSelectionModel::SelectCurrent ); 0893 0894 QModelIndex foundIndex = model()->index( row, 0, QModelIndex() ); 0895 setCurrentIndex( foundIndex ); 0896 if ( foundIndex.isValid() ) 0897 scrollTo( foundIndex, QAbstractItemView::PositionAtCenter ); 0898 0899 Q_EMIT( found() ); 0900 } 0901 else 0902 Q_EMIT( notFound() ); 0903 0904 if ( updateProxy ) 0905 The::playlist()->filterUpdated(); 0906 } 0907 0908 void Playlist::PrettyListView::clearSearchTerm() 0909 { 0910 DEBUG_BLOCK 0911 0912 // Choose a focus item, to scroll to later. 0913 QModelIndex focusIndex; 0914 QModelIndexList selected = selectedIndexes(); 0915 if( !selected.isEmpty() ) 0916 focusIndex = selected.first(); 0917 else 0918 focusIndex = indexAt( QPoint( 0, 0 ) ); 0919 0920 // Remember the focus item id, because the row numbers change when we reset the filter. 0921 quint64 focusItemId = The::playlist()->idAt( focusIndex.row() ); 0922 0923 The::playlist()->clearSearchTerm(); 0924 The::playlist()->filterUpdated(); 0925 0926 // Now scroll to the focus item. 0927 QModelIndex newIndex = model()->index( The::playlist()->rowForId( focusItemId ), 0, QModelIndex() ); 0928 if ( newIndex.isValid() ) 0929 scrollTo( newIndex, QAbstractItemView::PositionAtCenter ); 0930 } 0931 0932 void Playlist::PrettyListView::startProxyUpdateTimeout() 0933 { 0934 DEBUG_BLOCK 0935 if ( m_proxyUpdateTimer->isActive() ) 0936 m_proxyUpdateTimer->stop(); 0937 0938 m_proxyUpdateTimer->setInterval( 200 ); 0939 m_proxyUpdateTimer->start(); 0940 } 0941 0942 void Playlist::PrettyListView::updateProxyTimeout() 0943 { 0944 DEBUG_BLOCK 0945 The::playlist()->filterUpdated(); 0946 0947 int row = The::playlist()->find( m_searchTerm, m_fields ); 0948 if( row != -1 ) 0949 { 0950 QModelIndex foundIndex = model()->index( row, 0, QModelIndex() ); 0951 setCurrentIndex( foundIndex ); 0952 0953 if ( !m_filter ) 0954 { 0955 if ( foundIndex.isValid() ) 0956 scrollTo( foundIndex, QAbstractItemView::PositionAtCenter ); 0957 } 0958 0959 Q_EMIT( found() ); 0960 } 0961 else 0962 Q_EMIT( notFound() ); 0963 } 0964 0965 void Playlist::PrettyListView::showOnlyMatches( bool onlyMatches ) 0966 { 0967 m_showOnlyMatches = onlyMatches; 0968 0969 The::playlist()->showOnlyMatches( onlyMatches ); 0970 } 0971 0972 // Handle scrolling to newly inserted playlist items. 0973 // Warning, this slot is connected to the 'rowsInserted' signal of the *bottom* model, 0974 // not the normal top model. 0975 // The reason: FilterProxy can Q_EMIT *A LOT* (thousands) of 'rowsInserted' signals when its 0976 // search string changes. For that case we don't want to do any scrollTo() at all. 0977 void 0978 Playlist::PrettyListView::bottomModelRowsInserted( const QModelIndex& parent, int start, int end ) 0979 { 0980 Q_UNUSED( parent ) 0981 Q_UNUSED( end ) 0982 0983 // skip scrolling if tracks were added while playlist is in dynamicMode 0984 if( m_rowsInsertedScrollItem == 0 && !AmarokConfig::dynamicMode() ) 0985 { 0986 m_rowsInsertedScrollItem = Playlist::ModelStack::instance()->bottom()->idAt( start ); 0987 QTimer::singleShot( 0, this, &Playlist::PrettyListView::bottomModelRowsInsertedScroll ); 0988 } 0989 } 0990 0991 void Playlist::PrettyListView::bottomModelRowsInsertedScroll() 0992 { 0993 DEBUG_BLOCK 0994 0995 if( m_rowsInsertedScrollItem ) 0996 { // Note: we don't bother handling the case "first inserted item in bottom model 0997 // does not have a row in the top 'model()' due to FilterProxy" nicely. 0998 int firstRowInserted = The::playlist()->rowForId( m_rowsInsertedScrollItem ); // In the *top* model. 0999 QModelIndex index = model()->index( firstRowInserted, 0 ); 1000 1001 if( index.isValid() ) 1002 scrollTo( index, QAbstractItemView::PositionAtCenter ); 1003 1004 m_rowsInsertedScrollItem = 0; 1005 } 1006 } 1007 1008 void Playlist::PrettyListView::redrawActive() 1009 { 1010 int activeRow = The::playlist()->activeRow(); 1011 QModelIndex index = model()->index( activeRow, 0, QModelIndex() ); 1012 update( index ); 1013 } 1014 1015 void Playlist::PrettyListView::playlistLayoutChanged() 1016 { 1017 if ( LayoutManager::instance()->activeLayout().inlineControls() ) 1018 m_animationTimer->start(); 1019 else 1020 m_animationTimer->stop(); 1021 1022 // -- update the tooltip columns in the playlist model 1023 bool tooltipColumns[Playlist::NUM_COLUMNS]; 1024 for( int i=0; i<Playlist::NUM_COLUMNS; ++i ) 1025 tooltipColumns[i] = true; 1026 1027 // bool excludeCover = false; 1028 1029 for( int part = 0; part < PlaylistLayout::NumParts; part++ ) 1030 { 1031 // bool single = ( part == PlaylistLayout::Single ); 1032 Playlist::PlaylistLayout layout = Playlist::LayoutManager::instance()->activeLayout(); 1033 Playlist::LayoutItemConfig item = layout.layoutForPart( (PlaylistLayout::Part)part ); 1034 1035 for (int activeRow = 0; activeRow < item.rows(); activeRow++) 1036 { 1037 for (int activeElement = 0; activeElement < item.row(activeRow).count();activeElement++) 1038 { 1039 Playlist::Column column = (Playlist::Column)item.row(activeRow).element(activeElement).value(); 1040 tooltipColumns[column] = false; 1041 } 1042 } 1043 // excludeCover |= item.showCover(); 1044 } 1045 Playlist::Model::setTooltipColumns( tooltipColumns ); 1046 Playlist::Model::enableToolTip( Playlist::LayoutManager::instance()->activeLayout().tooltips() ); 1047 1048 update(); 1049 1050 // Schedule a re-scroll to the active playlist row. Assumption: Qt will run this *after* the repaint. 1051 QTimer::singleShot( 0, this, &Playlist::PrettyListView::slotPlaylistActiveTrackChanged ); 1052 } 1053