File indexing completed on 2024-05-05 04:47:21
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 "BookmarkTreeView.h" 0018 0019 #include "BookmarkModel.h" 0020 #include "dialogs/TagDialog.h" 0021 #include "PaletteHandler.h" 0022 #include "AmarokUrl.h" 0023 #include "AmarokUrlHandler.h" 0024 #include "BookmarkGroup.h" 0025 #include "playlist/PlaylistController.h" 0026 #include "SvgHandler.h" 0027 #include "core-impl/meta/timecode/TimecodeMeta.h" 0028 0029 #include <QAction> 0030 #include <QMenu> 0031 #include <KLocalizedString> 0032 0033 #include <QHeaderView> 0034 #include <QHelpEvent> 0035 #include <QKeyEvent> 0036 #include <QMouseEvent> 0037 #include <QModelIndex> 0038 #include <QPoint> 0039 #include <QToolTip> 0040 0041 #include <typeinfo> 0042 0043 BookmarkTreeView::BookmarkTreeView( QWidget *parent ) 0044 : QTreeView( parent ) 0045 , m_loadAction( nullptr ) 0046 , m_deleteAction( nullptr ) 0047 , m_createTimecodeTrackAction( nullptr ) 0048 , m_addGroupAction( nullptr ) 0049 { 0050 0051 setEditTriggers( QAbstractItemView::SelectedClicked ); 0052 setSelectionMode( QAbstractItemView::ExtendedSelection ); 0053 0054 setDragEnabled( true ); 0055 setAcceptDrops( true ); 0056 setAlternatingRowColors( true ); 0057 setDropIndicatorShown( true ); 0058 0059 connect( header(), &QHeaderView::sectionCountChanged, 0060 this, &BookmarkTreeView::slotSectionCountChanged ); 0061 } 0062 0063 0064 BookmarkTreeView::~BookmarkTreeView() 0065 { 0066 } 0067 0068 void BookmarkTreeView::mouseDoubleClickEvent( QMouseEvent * event ) 0069 { 0070 QModelIndex index = m_proxyModel->mapToSource( indexAt( event->pos() ) ); 0071 0072 if( index.isValid() ) 0073 { 0074 BookmarkViewItemPtr item = BookmarkModel::instance()->data( index, 0xf00d ).value<BookmarkViewItemPtr>(); 0075 0076 if ( auto bookmark = AmarokUrlPtr::dynamicCast( item ) ) 0077 bookmark->run(); 0078 } 0079 } 0080 0081 0082 void 0083 BookmarkTreeView::keyPressEvent( QKeyEvent *event ) 0084 { 0085 switch( event->key() ) 0086 { 0087 case Qt::Key_Delete: 0088 slotDelete(); 0089 return; 0090 0091 case Qt::Key_F2: 0092 slotRename(); 0093 return; 0094 } 0095 QTreeView::keyPressEvent( event ); 0096 } 0097 0098 QList<QAction *> 0099 BookmarkTreeView::createCommonActions( const QModelIndexList &indices ) 0100 { 0101 DEBUG_BLOCK 0102 0103 //there are 4 columns, so for each selected row we get 4 indices... 0104 int selectedRowCount = indices.count() / 4; 0105 0106 QList< QAction * > actions; 0107 if ( m_loadAction == nullptr ) 0108 { 0109 m_loadAction = new QAction( QIcon::fromTheme( QStringLiteral("folder-open") ), i18nc( "Load the view represented by this bookmark", "&Load" ), this ); 0110 connect( m_loadAction, &QAction::triggered, this, &BookmarkTreeView::slotLoad ); 0111 } 0112 0113 if ( m_deleteAction == nullptr ) 0114 { 0115 m_deleteAction = new QAction( QIcon::fromTheme( QStringLiteral("media-track-remove-amarok") ), i18n( "&Delete" ), this ); 0116 connect( m_deleteAction, &QAction::triggered, this, &BookmarkTreeView::slotDelete ); 0117 } 0118 0119 if ( m_createTimecodeTrackAction == nullptr ) 0120 { 0121 debug() << "creating m_createTimecodeTrackAction"; 0122 m_createTimecodeTrackAction = new QAction( QIcon::fromTheme( QStringLiteral("media-track-edit-amarok") ), i18n( "&Create timecode track" ), this ); 0123 connect( m_createTimecodeTrackAction, &QAction::triggered, this, &BookmarkTreeView::slotCreateTimecodeTrack ); 0124 } 0125 0126 if ( selectedRowCount == 1 ) 0127 actions << m_loadAction; 0128 0129 if ( selectedRowCount > 0 ) 0130 actions << m_deleteAction; 0131 0132 if ( selectedRowCount == 2 ) { 0133 debug() << "adding m_createTimecodeTrackAction"; 0134 actions << m_createTimecodeTrackAction; 0135 } 0136 0137 return actions; 0138 } 0139 0140 void BookmarkTreeView::slotLoad() 0141 { 0142 DEBUG_BLOCK 0143 foreach( BookmarkViewItemPtr item, selectedItems() ) 0144 { 0145 if( auto bookmark = AmarokUrlPtr::dynamicCast( item ) ) 0146 bookmark->run(); 0147 } 0148 } 0149 0150 void BookmarkTreeView::slotDelete() 0151 { 0152 DEBUG_BLOCK 0153 0154 //TODO FIXME Confirmation of delete 0155 0156 foreach( BookmarkViewItemPtr item, selectedItems() ) 0157 { 0158 debug() << "deleting " << item->name(); 0159 item->removeFromDb(); 0160 item->parent()->deleteChild( item ); 0161 } 0162 BookmarkModel::instance()->reloadFromDb(); 0163 The::amarokUrlHandler()->updateTimecodes(); 0164 } 0165 0166 void BookmarkTreeView::slotRename() 0167 { 0168 DEBUG_BLOCK 0169 if ( selectionModel()->hasSelection() ) 0170 edit( selectionModel()->selectedIndexes().first() ); 0171 } 0172 0173 void BookmarkTreeView::contextMenuEvent( QContextMenuEvent * event ) 0174 { 0175 DEBUG_BLOCK 0176 0177 const QModelIndexList indices = selectionModel()->selectedIndexes(); 0178 0179 QMenu* menu = new QMenu( this ); 0180 const QList<QAction *> actions = createCommonActions( indices ); 0181 0182 for ( QAction * action : actions ) 0183 menu->addAction( action ); 0184 0185 if( indices.isEmpty() && m_addGroupAction) 0186 menu->addAction( m_addGroupAction ); 0187 0188 if (!menu->isEmpty()) { 0189 menu->exec( event->globalPos() ); 0190 } 0191 delete menu; 0192 } 0193 0194 void BookmarkTreeView::resizeEvent( QResizeEvent *event ) 0195 { 0196 QHeaderView *headerView = header(); 0197 0198 const int oldWidth = event->oldSize().width(); 0199 const int newWidth = event->size().width(); 0200 0201 if( oldWidth == newWidth || oldWidth < 0 || newWidth < 0 ) 0202 return; 0203 0204 disconnect( headerView, &QHeaderView::sectionResized, 0205 this, &BookmarkTreeView::slotSectionResized ); 0206 0207 QMap<BookmarkModel::Column, qreal>::const_iterator i = m_columnsSize.constBegin(); 0208 while( i != m_columnsSize.constEnd() ) 0209 { 0210 const BookmarkModel::Column col = i.key(); 0211 if( col != BookmarkModel::Command && col != BookmarkModel::Description ) 0212 headerView->resizeSection( col, static_cast<int>( i.value() * newWidth ) ); 0213 ++i; 0214 } 0215 0216 connect( headerView, &QHeaderView::sectionResized, 0217 this, &BookmarkTreeView::slotSectionResized ); 0218 0219 QWidget::resizeEvent( event ); 0220 } 0221 0222 bool BookmarkTreeView::viewportEvent( QEvent *event ) 0223 { 0224 if( event->type() == QEvent::ToolTip ) 0225 { 0226 QHelpEvent *he = static_cast<QHelpEvent*>( event ); 0227 QModelIndex idx = indexAt( he->pos() ); 0228 0229 if( idx.isValid() ) 0230 { 0231 QRect vr = visualRect( idx ); 0232 QSize shr = itemDelegate( idx )->sizeHint( viewOptions(), idx ); 0233 0234 if( shr.width() > vr.width() ) 0235 QToolTip::showText( he->globalPos(), idx.data( Qt::DisplayRole ).toString() ); 0236 } 0237 else 0238 { 0239 QToolTip::hideText(); 0240 event->ignore(); 0241 } 0242 return true; 0243 } 0244 return QTreeView::viewportEvent( event ); 0245 } 0246 0247 QSet<BookmarkViewItemPtr> 0248 BookmarkTreeView::selectedItems() const 0249 { 0250 DEBUG_BLOCK 0251 QSet<BookmarkViewItemPtr> selected; 0252 foreach( const QModelIndex &index, selectionModel()->selectedIndexes() ) 0253 { 0254 QModelIndex sourceIndex = m_proxyModel->mapToSource( index ); 0255 if( sourceIndex.isValid() && sourceIndex.internalPointer() && sourceIndex.column() == 0 ) 0256 { 0257 debug() << "inserting item " << sourceIndex.data( Qt::DisplayRole ).toString(); 0258 selected.insert( BookmarkModel::instance()->data( sourceIndex, 0xf00d ).value<BookmarkViewItemPtr>() ); 0259 } 0260 } 0261 return selected; 0262 } 0263 0264 void BookmarkTreeView::setNewGroupAction( QAction * action ) 0265 { 0266 m_addGroupAction = action; 0267 } 0268 0269 void BookmarkTreeView::selectionChanged( const QItemSelection & selected, const QItemSelection & deselected ) 0270 { 0271 DEBUG_BLOCK 0272 Q_UNUSED( deselected ) 0273 QModelIndexList indexes = selected.indexes(); 0274 debug() << indexes.size() << " items selected"; 0275 foreach( const QModelIndex &index, indexes ) 0276 { 0277 const QModelIndex sourceIndex = m_proxyModel->mapToSource( index ); 0278 if( sourceIndex.column() == 0 ) 0279 { 0280 BookmarkViewItemPtr item = BookmarkModel::instance()->data( sourceIndex, 0xf00d ).value<BookmarkViewItemPtr>(); 0281 0282 if ( auto bookmark = AmarokUrlPtr::dynamicCast( item ) ) 0283 { 0284 debug() << "a url was selected..."; 0285 Q_EMIT( bookmarkSelected( *bookmark ) ); 0286 } 0287 } 0288 } 0289 0290 } 0291 0292 QMenu* BookmarkTreeView::contextMenu( const QPoint& point ) 0293 { 0294 DEBUG_BLOCK 0295 QMenu* menu = new QMenu( nullptr ); 0296 0297 debug() << "getting menu for point:" << point; 0298 QModelIndex index = m_proxyModel->mapToSource( indexAt( point ) ); 0299 if( index.isValid() ) 0300 { 0301 0302 debug() << "got valid index"; 0303 0304 QModelIndexList indices = selectionModel()->selectedIndexes(); 0305 0306 QList<QAction *> actions = createCommonActions( indices ); 0307 0308 foreach( QAction * action, actions ) 0309 menu->addAction( action ); 0310 0311 if( indices.isEmpty() ) 0312 menu->addAction( m_addGroupAction ); 0313 0314 } 0315 0316 return menu; 0317 } 0318 0319 void BookmarkTreeView::slotCreateTimecodeTrack() const 0320 { 0321 0322 //TODO: Factor into separate class 0323 QList<BookmarkViewItemPtr> list = selectedItems().values(); 0324 if ( list.count() != 2 ) 0325 return; 0326 0327 const AmarokUrl * url1 = dynamic_cast<const AmarokUrl *>( list.at( 0 ).data() ); 0328 0329 if ( url1 == nullptr ) 0330 return; 0331 if ( url1->command() != QLatin1String("play") ) 0332 return; 0333 0334 const AmarokUrl * url2 = dynamic_cast<const AmarokUrl *>( list.at( 1 ).data() ); 0335 0336 if ( url2 == nullptr ) 0337 return; 0338 if ( url2->command() != QLatin1String("play") ) 0339 return; 0340 0341 if ( url1->path() != url2->path() ) 0342 return; 0343 0344 //ok, so we actually have to timecodes from the same base url, not get the 0345 //minimum and maximum time: 0346 qreal pos1 = 0; 0347 qreal pos2 = 0; 0348 0349 if ( url1->args().keys().contains( QStringLiteral("pos") ) ) 0350 { 0351 pos1 = url1->args().value( QStringLiteral("pos") ).toDouble(); 0352 } 0353 0354 if ( url2->args().keys().contains( QStringLiteral("pos") ) ) 0355 { 0356 pos2 = url2->args().value( QStringLiteral("pos") ).toDouble(); 0357 } 0358 0359 if ( pos1 == pos2 ) 0360 return; 0361 0362 qint64 start = qMin( pos1, pos2 ) * 1000; 0363 qint64 end = qMax( pos1, pos2 ) * 1000; 0364 0365 //Now we really should pop up a menu to get the user to enter some info about this 0366 //new track, but for now, just fake it as this is just for testing anyway 0367 0368 QUrl url = QUrl::fromEncoded ( QByteArray::fromBase64 ( url1->path().toUtf8() ) ); 0369 Meta::TimecodeTrackPtr track = Meta::TimecodeTrackPtr( new Meta::TimecodeTrack( i18n( "New Timecode Track" ), url, start, end ) ); 0370 Meta::TimecodeAlbumPtr album = Meta::TimecodeAlbumPtr( new Meta::TimecodeAlbum( i18n( "Unknown" ) ) ); 0371 Meta::TimecodeArtistPtr artist = Meta::TimecodeArtistPtr( new Meta::TimecodeArtist( i18n( "Unknown" ) ) ); 0372 Meta::TimecodeGenrePtr genre = Meta::TimecodeGenrePtr( new Meta::TimecodeGenre( i18n( "Unknown" ) ) ); 0373 0374 album->addTrack( track ); 0375 artist->addTrack( track ); 0376 genre->addTrack( track ); 0377 0378 track->setAlbum( album ); 0379 track->setArtist( artist ); 0380 track->setGenre( genre ); 0381 0382 album->setAlbumArtist( artist ); 0383 0384 //make the user give us some info about this item... 0385 0386 Meta::TrackList tl; 0387 tl.append( Meta::TrackPtr::staticCast( track ) ); 0388 TagDialog *dialog = new TagDialog( tl, nullptr ); 0389 dialog->show(); 0390 0391 //now add it to the playlist 0392 The::playlistController()->insertOptioned( Meta::TrackPtr::staticCast( track ) ); 0393 } 0394 0395 void BookmarkTreeView::setProxy( QSortFilterProxyModel *proxy ) 0396 { 0397 m_proxyModel = proxy; 0398 } 0399 0400 void BookmarkTreeView::slotEdit( const QModelIndex &index ) 0401 { 0402 0403 //translate to proxy terms 0404 edit( m_proxyModel->mapFromSource( index ) ); 0405 } 0406 0407 void BookmarkTreeView::slotSectionResized( int logicalIndex, int oldSize, int newSize ) 0408 { 0409 Q_UNUSED( oldSize ) 0410 BookmarkModel::Column col = BookmarkModel::Column( logicalIndex ); 0411 m_columnsSize[ col ] = static_cast<qreal>( newSize ) / header()->length(); 0412 } 0413 0414 void BookmarkTreeView::slotSectionCountChanged( int oldCount, int newCount ) 0415 { 0416 Q_UNUSED( oldCount ) 0417 0418 const QHeaderView *headerView = header(); 0419 for( int i = 0; i < newCount; ++i ) 0420 { 0421 const int index = headerView->logicalIndex( i ); 0422 const int width = columnWidth( index ); 0423 const qreal ratio = static_cast<qreal>( width ) / headerView->length(); 0424 0425 const BookmarkModel::Column col = BookmarkModel::Column( index ); 0426 0427 if( col == BookmarkModel::Command ) 0428 header()->setSectionResizeMode( index, QHeaderView::ResizeToContents ); 0429 0430 m_columnsSize[ col ] = ratio; 0431 } 0432 } 0433 0434 0435 0436 0437