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