PageRenderTime 372ms CodeModel.GetById 101ms app.highlight 231ms RepoModel.GetById 31ms app.codeStats 0ms

/src/sourcetree/sourcetreeview.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 874 lines | 669 code | 168 blank | 37 comment | 126 complexity | 5b86d3f120ca1d16e67e82a7e266b04e MD5 | raw file
  1/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2 *
  3 *   Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  4 *   Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
  5 *   Copyright 2010-2012, Leo Franchi   <lfranchi@kde.org>
  6 *
  7 *   Tomahawk is free software: you can redistribute it and/or modify
  8 *   it under the terms of the GNU General Public License as published by
  9 *   the Free Software Foundation, either version 3 of the License, or
 10 *   (at your option) any later version.
 11 *
 12 *   Tomahawk is distributed in the hope that it will be useful,
 13 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 14 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 15 *   GNU General Public License for more details.
 16 *
 17 *   You should have received a copy of the GNU General Public License
 18 *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
 19 */
 20
 21#include "SourceTreeView.h"
 22
 23#include "ActionCollection.h"
 24#include "Playlist.h"
 25#include "ViewManager.h"
 26#include "SourcesProxyModel.h"
 27#include "SourceList.h"
 28#include "SourceDelegate.h"
 29#include "sourcetree/items/PlaylistItems.h"
 30#include "sourcetree/items/SourceItem.h"
 31#include "audio/AudioEngine.h"
 32#include "SourcePlaylistInterface.h"
 33#include "TomahawkSettings.h"
 34#include "GlobalActionManager.h"
 35#include "DropJob.h"
 36#include "items/GenericPageItems.h"
 37#include "items/TemporaryPageItem.h"
 38#include "database/DatabaseCommand_SocialAction.h"
 39#include "database/Database.h"
 40#include "LatchManager.h"
 41#include "utils/TomahawkUtilsGui.h"
 42#include "utils/Logger.h"
 43#include "utils/Closure.h"
 44#include "widgets/SourceTreePopupDialog.h"
 45
 46#include <QAction>
 47#include <QApplication>
 48#include <QContextMenuEvent>
 49#include <QDragEnterEvent>
 50#include <QHeaderView>
 51#include <QPainter>
 52#include <QStyledItemDelegate>
 53#include <QFileDialog>
 54#include <QMessageBox>
 55#include <QSize>
 56
 57using namespace Tomahawk;
 58
 59
 60SourceTreeView::SourceTreeView( QWidget* parent )
 61    : QTreeView( parent )
 62    , m_latchManager( new LatchManager( this ) )
 63    , m_dragging( false )
 64{
 65    setProperty( "flattenBranches", QVariant( true ) );
 66
 67    setFrameShape( QFrame::NoFrame );
 68    setAttribute( Qt::WA_MacShowFocusRect, 0 );
 69    setContentsMargins( 0, 0, 0, 0 );
 70
 71    QFont fnt;
 72    QFontMetrics fm( fnt );
 73    // This is sort of the longest string in there. With translations
 74    // we will never get it right so setting it to something reasonable for the average case
 75    setMinimumWidth( fm.width( "Track Album Artist Local Top10" ) );
 76
 77    setHeaderHidden( true );
 78    setRootIsDecorated( true );
 79    setExpandsOnDoubleClick( false );
 80
 81    setSelectionBehavior( QAbstractItemView::SelectRows );
 82    setDragDropMode( QAbstractItemView::DropOnly );
 83    setAcceptDrops( true );
 84    setDropIndicatorShown( false );
 85    setAllColumnsShowFocus( true );
 86    setUniformRowHeights( false );
 87    setIndentation( 0 );
 88    setSortingEnabled( true );
 89    sortByColumn( 0, Qt::AscendingOrder );
 90    setVerticalScrollMode( QTreeView::ScrollPerPixel );
 91    setMouseTracking( true );
 92    setEditTriggers( NoEditTriggers );
 93    setAutoExpandDelay( 500 );
 94
 95    // TODO animation conflicts with the expanding-playlists-when-collection-is-null
 96    // so investigate
 97//     setAnimated( true );
 98
 99    m_delegate = new SourceDelegate( this );
100    connect( m_delegate, SIGNAL( latchOn( Tomahawk::source_ptr ) ), SLOT( latchOnOrCatchUp( Tomahawk::source_ptr ) ) );
101    connect( m_delegate, SIGNAL( latchOff( Tomahawk::source_ptr ) ), SLOT( latchOff( Tomahawk::source_ptr ) ) );
102    connect( m_delegate, SIGNAL( toggleRealtimeLatch( Tomahawk::source_ptr, bool ) ), m_latchManager, SLOT( latchModeChangeRequest( Tomahawk::source_ptr,bool ) ) );
103    connect( m_delegate, SIGNAL( clicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) );
104    connect( m_delegate, SIGNAL( doubleClicked( QModelIndex ) ), SLOT( onItemDoubleClicked( QModelIndex ) ) );
105
106    setItemDelegate( m_delegate );
107
108    setContextMenuPolicy( Qt::CustomContextMenu );
109    connect( this, SIGNAL( customContextMenuRequested( QPoint ) ), SLOT( onCustomContextMenu( QPoint ) ) );
110
111    m_model = new SourcesModel( this );
112    m_proxyModel = new SourcesProxyModel( m_model, this );
113    connect( m_proxyModel, SIGNAL( selectRequest( QPersistentModelIndex ) ), SLOT( selectRequest( QPersistentModelIndex ) ), Qt::QueuedConnection );
114    connect( m_proxyModel, SIGNAL( expandRequest( QPersistentModelIndex ) ), SLOT( expandRequest( QPersistentModelIndex ) ) );
115    connect( m_proxyModel, SIGNAL( toggleExpandRequest( QPersistentModelIndex ) ), SLOT( toggleExpandRequest( QPersistentModelIndex ) ) );
116
117    setModel( m_proxyModel );
118
119    header()->setStretchLastSection( false );
120    header()->setResizeMode( 0, QHeaderView::Stretch );
121
122    connect( this, SIGNAL( expanded( QModelIndex ) ), SLOT( onItemExpanded( QModelIndex ) ) );
123    connect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), SLOT( onSelectionChanged() ) );
124
125    showOfflineSources( TomahawkSettings::instance()->showOfflineSources() );
126
127    // Light-blue sourcetree on osx
128#ifdef Q_WS_MAC
129    setStyleSheet( "SourceTreeView:active { background: #DDE4EB; } "
130                   "SourceTreeView        { background: #EDEDED; } " );
131#endif
132
133    connect( this, SIGNAL( latchRequest( Tomahawk::source_ptr ) ), m_latchManager, SLOT( latchRequest( Tomahawk::source_ptr ) ) );
134    connect( this, SIGNAL( unlatchRequest( Tomahawk::source_ptr ) ), m_latchManager, SLOT( unlatchRequest( Tomahawk::source_ptr ) ) );
135    connect( this, SIGNAL( catchUpRequest() ), m_latchManager, SLOT( catchUpRequest() ) );
136    connect( this, SIGNAL( latchModeChangeRequest( Tomahawk::source_ptr, bool ) ), m_latchManager, SLOT( latchModeChangeRequest( Tomahawk::source_ptr, bool ) ) );
137
138    connect( ActionCollection::instance(), SIGNAL( privacyModeChanged() ), SLOT( repaint() ) );
139}
140
141
142SourceTreeView::~SourceTreeView()
143{
144}
145
146
147void
148SourceTreeView::setupMenus()
149{
150    m_playlistMenu.clear();
151    m_roPlaylistMenu.clear();
152    m_latchMenu.clear();
153    m_privacyMenu.clear();
154
155    bool readonly = true;
156    SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt();
157
158    if ( type == SourcesModel::StaticPlaylist || type == SourcesModel::AutomaticPlaylist || type == SourcesModel::Station )
159    {
160        const PlaylistItem* item = itemFromIndex< PlaylistItem >( m_contextMenuIndex );
161        const playlist_ptr playlist = item->playlist();
162
163        if ( !playlist.isNull() )
164        {
165            readonly = !playlist->author()->isLocal();
166        }
167    }
168
169    QAction* latchOnAction = ActionCollection::instance()->getAction( "latchOn" );
170    m_latchMenu.addAction( latchOnAction );
171
172    m_privacyMenu.addAction( ActionCollection::instance()->getAction( "togglePrivacy" ) );
173
174    if ( type == SourcesModel::Collection )
175    {
176        SourceItem* item = itemFromIndex< SourceItem >( m_contextMenuIndex );
177        source_ptr source = item->source();
178        if ( !source.isNull() )
179        {
180            if ( m_latchManager->isLatched( source ) )
181            {
182                QAction* latchOffAction = ActionCollection::instance()->getAction( "latchOff" );
183                m_latchMenu.addAction( latchOffAction );
184                connect( latchOffAction, SIGNAL( triggered() ), SLOT( latchOff() ) );
185                m_latchMenu.addSeparator();
186                QAction* latchRealtimeAction = ActionCollection::instance()->getAction( "realtimeFollowingAlong" );
187                latchRealtimeAction->setChecked( source->playlistInterface()->latchMode() == Tomahawk::PlaylistModes::RealTime );
188                m_latchMenu.addAction( latchRealtimeAction );
189                connect( latchRealtimeAction, SIGNAL( toggled( bool ) ), SLOT( latchModeToggled( bool ) ) );
190            }
191        }
192    }
193
194    QAction* loadPlaylistAction = ActionCollection::instance()->getAction( "loadPlaylist" );
195    m_playlistMenu.addAction( loadPlaylistAction );
196    QAction* renamePlaylistAction = ActionCollection::instance()->getAction( "renamePlaylist" );
197    m_playlistMenu.addAction( renamePlaylistAction );
198    m_playlistMenu.addSeparator();
199
200    QAction* copyPlaylistAction = m_playlistMenu.addAction( tr( "&Copy Link" ) );
201
202    if ( type == SourcesModel::StaticPlaylist )
203    {
204        QAction* exportPlaylist = m_playlistMenu.addAction( tr( "&Export Playlist") );
205        connect( exportPlaylist, SIGNAL( triggered() ), this, SLOT( exportPlaylist() ) );
206    }
207
208    QAction* deletePlaylistAction = m_playlistMenu.addAction( tr( "&Delete %1" ).arg( SourcesModel::rowTypeToString( type ) ) );
209
210    QString addToText;
211    if ( type == SourcesModel::StaticPlaylist )
212        addToText = tr( "Add to my Playlists" );
213    if ( type == SourcesModel::AutomaticPlaylist )
214        addToText = tr( "Add to my Automatic Playlists" );
215    else if ( type == SourcesModel::Station )
216        addToText = tr( "Add to my Stations" );
217
218    QAction* addToLocalAction = m_roPlaylistMenu.addAction( addToText );
219
220    m_roPlaylistMenu.addAction( copyPlaylistAction );
221    deletePlaylistAction->setEnabled( !readonly );
222    renamePlaylistAction->setEnabled( !readonly );
223    addToLocalAction->setEnabled( readonly );
224
225    // Handle any custom actions registered for playlists
226    if ( type == SourcesModel::StaticPlaylist && !readonly &&
227        !ActionCollection::instance()->getAction( ActionCollection::LocalPlaylists ).isEmpty() )
228    {
229        m_playlistMenu.addSeparator();
230
231        const PlaylistItem* item = itemFromIndex< PlaylistItem >( m_contextMenuIndex );
232        const playlist_ptr playlist = item->playlist();
233        foreach ( QAction* action, ActionCollection::instance()->getAction( ActionCollection::LocalPlaylists ) )
234        {
235            if ( QObject* notifier = ActionCollection::instance()->actionNotifier( action ) )
236            {
237                QMetaObject::invokeMethod( notifier, "aboutToShow", Qt::DirectConnection, Q_ARG( QAction*, action ), Q_ARG( Tomahawk::playlist_ptr, playlist ) );
238            }
239
240            action->setProperty( "payload", QVariant::fromValue< playlist_ptr >( playlist ) );
241            m_playlistMenu.addAction( action );
242        }
243    }
244
245    connect( loadPlaylistAction,   SIGNAL( triggered() ), SLOT( loadPlaylist() ) );
246    connect( renamePlaylistAction, SIGNAL( triggered() ), SLOT( renamePlaylist() ) );
247    connect( deletePlaylistAction, SIGNAL( triggered() ), SLOT( deletePlaylist() ) );
248    connect( copyPlaylistAction,   SIGNAL( triggered() ), SLOT( copyPlaylistLink() ) );
249    connect( addToLocalAction,     SIGNAL( triggered() ), SLOT( addToLocal() ) );
250    connect( latchOnAction,        SIGNAL( triggered() ), SLOT( latchOnOrCatchUp() ), Qt::QueuedConnection );
251}
252
253
254void
255SourceTreeView::showOfflineSources( bool offlineSourcesShown )
256{
257    m_proxyModel->showOfflineSources( offlineSourcesShown );
258}
259
260
261void
262SourceTreeView::onSelectionChanged()
263{
264    if ( currentIndex() != m_selectedIndex )
265    {
266        selectionModel()->blockSignals( true );
267        selectionModel()->select( m_selectedIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current );
268        selectionModel()->blockSignals( false );
269    }
270}
271
272
273void
274SourceTreeView::onItemActivated( const QModelIndex& index )
275{
276    if ( !index.isValid() || !index.flags().testFlag( Qt::ItemIsEnabled ) )
277        return;
278
279    SourceTreeItem* item = itemFromIndex< SourceTreeItem >( index );
280    item->activate();
281}
282
283
284void
285SourceTreeView::onItemDoubleClicked( const QModelIndex& idx )
286{
287    if ( !selectionModel()->selectedIndexes().contains( idx ) )
288        onItemActivated( idx );
289
290    SourceTreeItem* item = itemFromIndex< SourceTreeItem >( idx );
291    item->doubleClicked();
292}
293
294
295void
296SourceTreeView::onItemExpanded( const QModelIndex& idx )
297{
298    // make sure to expand children nodes for collections
299    if ( idx.data( SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::Collection )
300    {
301       for( int i = 0; i < model()->rowCount( idx ); i++ )
302       {
303           setExpanded( model()->index( i, 0, idx ), true );
304       }
305    }
306}
307
308
309void
310SourceTreeView::selectRequest( const QPersistentModelIndex& idx )
311{
312    m_selectedIndex = idx;
313
314    if ( !selectionModel()->selectedIndexes().contains( idx ) )
315    {
316        scrollTo( idx, QTreeView::EnsureVisible );
317        selectionModel()->select( idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current );
318    }
319}
320
321
322void
323SourceTreeView::expandRequest( const QPersistentModelIndex& idx )
324{
325    expand( idx );
326}
327
328
329void
330SourceTreeView::toggleExpandRequest( const QPersistentModelIndex& idx )
331{
332    if ( isExpanded( idx ) )
333        collapse( idx );
334    else
335        expand( idx );
336}
337
338
339void
340SourceTreeView::loadPlaylist()
341{
342    onItemActivated( m_contextMenuIndex );
343}
344
345
346void
347SourceTreeView::deletePlaylist( const QModelIndex& idxIn )
348{
349    QModelIndex idx = idxIn.isValid() ? idxIn : m_contextMenuIndex;
350    if ( !idx.isValid() )
351        return;
352
353    SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( idx, SourcesModel::SourceTreeItemTypeRole ).toInt();
354    QString typeDesc;
355    switch ( type )
356    {
357        case SourcesModel::StaticPlaylist:
358            typeDesc = tr( "playlist" );
359            break;
360
361        case SourcesModel::AutomaticPlaylist:
362            typeDesc = tr( "automatic playlist" );
363            break;
364
365        case SourcesModel::Station:
366            typeDesc = tr( "station" );
367            break;
368
369        default:
370            Q_ASSERT( false );
371    }
372
373    PlaylistItem* item = itemFromIndex< PlaylistItem >( idx );
374    playlist_ptr playlist = item->playlist();
375    QPoint rightCenter = viewport()->mapToGlobal( visualRect( idx ).topRight() + QPoint( 0, visualRect( idx ).height() / 2 ) );
376#ifdef Q_OS_WIN
377    rightCenter = QApplication::activeWindow()->mapFromGlobal( rightCenter );
378#endif
379
380    if ( playlist->hasCustomDeleter() )
381    {
382        playlist->customDelete( rightCenter );
383    }
384    else
385    {
386        if ( m_popupDialog.isNull() )
387        {
388            m_popupDialog = QWeakPointer< SourceTreePopupDialog >( new SourceTreePopupDialog() );
389            connect( m_popupDialog.data(), SIGNAL( result( bool ) ), this, SLOT( onDeletePlaylistResult( bool ) ) );
390        }
391
392        m_popupDialog.data()->setMainText( tr( "Would you like to delete the %1 <b>\"%2\"</b>?", "e.g. Would you like to delete the playlist named Foobar?" )
393                                .arg( typeDesc ).arg( idx.data().toString() ) );
394        m_popupDialog.data()->setOkButtonText( tr( "Delete" ) );
395        m_popupDialog.data()->setProperty( "idx", QVariant::fromValue< QModelIndex >( idx ) );
396
397        m_popupDialog.data()->move( rightCenter.x() - m_popupDialog.data()->offset(), rightCenter.y() - m_popupDialog.data()->sizeHint().height() / 2. );
398        m_popupDialog.data()->show();
399    }
400
401}
402
403
404void
405SourceTreeView::onDeletePlaylistResult( bool result )
406{
407    Q_ASSERT( !m_popupDialog.isNull() );
408
409    const QModelIndex idx = m_popupDialog.data()->property( "idx" ).value< QModelIndex >();
410    Q_ASSERT( idx.isValid() );
411
412    if ( !result )
413        return;
414
415    SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( idx, SourcesModel::SourceTreeItemTypeRole ).toInt();
416
417    if ( type == SourcesModel::StaticPlaylist )
418    {
419        PlaylistItem* item = itemFromIndex< PlaylistItem >( idx );
420        playlist_ptr playlist = item->playlist();
421        qDebug() << "Doing delete of playlist:" << playlist->title();
422        Playlist::remove( playlist );
423    }
424    else if ( type == SourcesModel::AutomaticPlaylist || type == SourcesModel::Station )
425    {
426        DynamicPlaylistItem* item = itemFromIndex< DynamicPlaylistItem >( idx );
427        dynplaylist_ptr playlist = item->dynPlaylist();
428        qDebug() << "Doing delete of playlist:" << playlist->title();
429        DynamicPlaylist::remove( playlist );
430    }
431}
432
433
434void
435SourceTreeView::copyPlaylistLink()
436{
437    QModelIndex idx = m_contextMenuIndex;
438    if ( !idx.isValid() )
439        return;
440
441    SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt();
442    if ( type == SourcesModel::AutomaticPlaylist || type == SourcesModel::Station )
443    {
444        DynamicPlaylistItem* item = itemFromIndex< DynamicPlaylistItem >( m_contextMenuIndex );
445        dynplaylist_ptr playlist = item->dynPlaylist();
446        GlobalActionManager::instance()->copyPlaylistToClipboard( playlist );
447    }
448    else if ( type == SourcesModel::StaticPlaylist )
449    {
450       const PlaylistItem* item = itemFromIndex< PlaylistItem >( m_contextMenuIndex );
451       const playlist_ptr playlist = item->playlist();
452
453       GlobalActionManager::instance()->getShortLink( playlist );
454    }
455}
456
457
458void
459SourceTreeView::exportPlaylist()
460{
461    const QModelIndex idx = m_contextMenuIndex;
462    if ( !idx.isValid() )
463        return;
464
465    const SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt();
466    Q_ASSERT( type == SourcesModel::StaticPlaylist );
467    if ( type != SourcesModel::StaticPlaylist )
468        return;
469
470    const PlaylistItem* item = itemFromIndex< PlaylistItem >( m_contextMenuIndex );
471    const playlist_ptr playlist = item->playlist();
472
473    const QString suggestedFilename = TomahawkSettings::instance()->playlistDefaultPath() + "/" + playlist->title();
474    const QString filename = QFileDialog::getSaveFileName( TomahawkUtils::tomahawkWindow(), tr( "Save XSPF" ),
475                                                     suggestedFilename, tr( "Playlists (*.xspf)" ) );
476    if ( !filename.isEmpty() )
477    {
478        const  QFileInfo playlistAbsoluteFilePath( filename );
479        TomahawkSettings::instance()->setPlaylistDefaultPath( playlistAbsoluteFilePath.absolutePath() );
480        GlobalActionManager::instance()->savePlaylistToFile( playlist, filename );
481    }
482}
483
484
485void
486SourceTreeView::addToLocal()
487{
488    QModelIndex idx = m_contextMenuIndex;
489    if ( !idx.isValid() )
490        return;
491
492    SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt();
493    if ( type == SourcesModel::AutomaticPlaylist || type == SourcesModel::Station )
494    {
495        DynamicPlaylistItem* item = itemFromIndex< DynamicPlaylistItem >( m_contextMenuIndex );
496        dynplaylist_ptr playlist = item->dynPlaylist();
497
498        // copy to a link and then generate a new playlist from that
499        // this way we cheaply regenerate the needed controls
500        QString link = GlobalActionManager::instance()->copyPlaylistToClipboard( playlist );
501        GlobalActionManager::instance()->parseTomahawkLink( link );
502    }
503    else if ( type == SourcesModel::StaticPlaylist )
504    {
505        PlaylistItem* item = itemFromIndex< PlaylistItem >( m_contextMenuIndex );
506        playlist_ptr playlist = item->playlist();
507
508        // just create the new playlist with the same values
509        QList< query_ptr > queries;
510        foreach( const plentry_ptr& e, playlist->entries() )
511            queries << e->query();
512
513        playlist_ptr newpl = Playlist::create( SourceList::instance()->getLocal(), uuid(), playlist->title(), playlist->info(), playlist->creator(), playlist->shared(), queries );
514    }
515}
516
517
518void
519SourceTreeView::latchOnOrCatchUp()
520{
521    disconnect( this, SLOT( latchOnOrCatchUp() ) );
522    if ( !m_contextMenuIndex.isValid() )
523        return;
524
525    SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt();
526    if ( type != SourcesModel::Collection )
527        return;
528
529    SourceItem* item = itemFromIndex< SourceItem >( m_contextMenuIndex );
530    source_ptr source = item->source();
531
532    latchOnOrCatchUp( source );
533}
534
535
536void
537SourceTreeView::latchOff()
538{
539    disconnect( this, SLOT( latchOff() ) );
540    qDebug() << Q_FUNC_INFO;
541    if ( !m_contextMenuIndex.isValid() )
542        return;
543
544    SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt();
545    if ( type != SourcesModel::Collection )
546        return;
547
548    const SourceItem* item = itemFromIndex< SourceItem >( m_contextMenuIndex );
549    const source_ptr source = item->source();
550
551    latchOff( source );
552}
553
554
555void
556SourceTreeView::latchOnOrCatchUp( const Tomahawk::source_ptr& source )
557{
558    if ( m_latchManager->isLatched( source ) )
559        emit catchUpRequest();
560    else
561        emit latchRequest( source );
562}
563
564
565void
566SourceTreeView::latchOff( const Tomahawk::source_ptr& source )
567{
568    emit unlatchRequest( source );
569}
570
571
572void
573SourceTreeView::latchModeToggled( bool checked )
574{
575
576    disconnect( this, SLOT( latchOff() ) );
577    qDebug() << Q_FUNC_INFO;
578    if ( !m_contextMenuIndex.isValid() )
579        return;
580
581    SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt();
582    if ( type != SourcesModel::Collection )
583        return;
584
585    const SourceItem* item = itemFromIndex< SourceItem >( m_contextMenuIndex );
586    const source_ptr source = item->source();
587    emit latchModeChangeRequest( source, checked );
588}
589
590
591void
592SourceTreeView::renamePlaylist()
593{
594    if ( !m_contextMenuIndex.isValid() && !selectionModel()->selectedIndexes().isEmpty() )
595        edit( selectionModel()->selectedIndexes().first() );
596    else
597        edit( m_contextMenuIndex );
598}
599
600
601void
602SourceTreeView::onCustomContextMenu( const QPoint& pos )
603{
604    qDebug() << Q_FUNC_INFO;
605
606    QModelIndex idx = m_contextMenuIndex = indexAt( pos );
607    if ( !idx.isValid() )
608        return;
609
610    setupMenus();
611
612    const QList< QAction* > customActions = model()->data( m_contextMenuIndex, SourcesModel::CustomActionRole ).value< QList< QAction* > >();
613
614    if ( model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::StaticPlaylist ||
615         model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::AutomaticPlaylist ||
616         model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::Station )
617    {
618        PlaylistItem* item = itemFromIndex< PlaylistItem >( m_contextMenuIndex );
619        if ( item->playlist()->author()->isLocal() )
620            m_playlistMenu.exec( mapToGlobal( pos ) );
621        else
622            m_roPlaylistMenu.exec( mapToGlobal( pos ) );
623    }
624    else if ( model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::Collection )
625    {
626        SourceItem* item = itemFromIndex< SourceItem >( m_contextMenuIndex );
627        if ( !item->source().isNull() && !item->source()->isLocal() )
628            m_latchMenu.exec( mapToGlobal( pos ) );
629        else if ( !item->source().isNull() )
630            m_privacyMenu.exec( mapToGlobal( pos ) );
631    }
632    else if ( !customActions.isEmpty() )
633    {
634        QMenu customMenu;
635        customMenu.addActions( customActions );
636        customMenu.exec( mapToGlobal( pos ) );
637    }
638}
639
640
641void
642SourceTreeView::dragEnterEvent( QDragEnterEvent* event )
643{
644    qDebug() << Q_FUNC_INFO;
645    QTreeView::dragEnterEvent( event );
646
647    if ( DropJob::acceptsMimeData( event->mimeData(), DropJob::Track | DropJob::Artist | DropJob::Album | DropJob::Playlist,  DropJob::Create ) )
648    {
649           m_dragging = true;
650           m_dropRect = QRect();
651           m_dropIndex = QPersistentModelIndex();
652
653           event->setDropAction( Qt::CopyAction );
654           event->acceptProposedAction();
655     }
656}
657
658
659void
660SourceTreeView::dragLeaveEvent( QDragLeaveEvent* event )
661{
662    QTreeView::dragLeaveEvent( event );
663
664    m_dragging = false;
665    setDirtyRegion( m_dropRect );
666
667    m_delegate->dragLeaveEvent();
668    dataChanged( m_dropIndex, m_dropIndex );
669    m_dropIndex = QPersistentModelIndex();
670}
671
672
673void
674SourceTreeView::dragMoveEvent( QDragMoveEvent* event )
675{
676    bool accept = false;
677
678    // Don't highlight the drop for a playlist, as it won't get added to the playlist but created generally
679    if ( DropJob::isDropType( DropJob::Playlist, event->mimeData()  ) )
680    {
681        event->accept();
682        return;
683    }
684
685    QTreeView::dragMoveEvent( event );
686
687    if ( DropJob::acceptsMimeData( event->mimeData(),  DropJob::Track, DropJob::Append ) )
688    {
689        setDirtyRegion( m_dropRect );
690        const QPoint pos = event->pos();
691        const QModelIndex index = indexAt( pos );
692        dataChanged( m_dropIndex, m_dropIndex );
693        m_dropIndex = QPersistentModelIndex( index );
694
695        if ( index.isValid() )
696        {
697            const QRect rect = visualRect( index );
698            m_dropRect = rect;
699
700            SourceTreeItem* item = itemFromIndex< SourceTreeItem >( index );
701
702            if ( item->willAcceptDrag( event->mimeData() ) )
703            {
704                accept = true;
705
706                switch ( model()->data( index, SourcesModel::SourceTreeItemTypeRole ).toInt() )
707                {
708                    case SourcesModel::StaticPlaylist:
709                    case SourcesModel::CategoryAdd:
710                        m_delegate->hovered( index, event->mimeData() );
711                        dataChanged( index, index );
712                        break;
713
714                    default:
715                        break;
716                }
717
718            }
719            else
720                m_delegate->hovered( QModelIndex(), 0 );
721        }
722        else
723        {
724            m_dropRect = QRect();
725        }
726
727        if ( accept || DropJob::isDropType( DropJob::Playlist, event->mimeData() ) )
728        {
729            // Playlists are accepted always since they can be dropped anywhere
730            //tDebug() << Q_FUNC_INFO << "Accepting";
731            event->setDropAction( Qt::CopyAction );
732            event->accept();
733        }
734        else
735        {
736//             tDebug() << Q_FUNC_INFO << "Ignoring";
737            event->ignore();
738        }
739    }
740    else if ( DropJob::acceptsMimeData( event->mimeData(),  DropJob::Playlist | DropJob::Artist | DropJob::Album, DropJob::Create ) )
741    {
742        event->setDropAction( Qt::CopyAction );
743        event->accept();
744    }
745    setDirtyRegion( m_dropRect );
746}
747
748
749void
750SourceTreeView::dropEvent( QDropEvent* event )
751{
752    const QPoint pos = event->pos();
753    const QModelIndex index = indexAt( pos );
754
755    if ( model()->data( index, SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::StaticPlaylist
756         || model()->data( index, SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::LovedTracksPage
757         || model()->data( index, SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::CategoryAdd )
758    {
759        SourceTreeItem* item = itemFromIndex< SourceTreeItem >( index );
760        Q_ASSERT( item );
761
762        item->setDropType( m_delegate->hoveredDropType() );
763        qDebug() << Q_FUNC_INFO << "dropType is " << m_delegate->hoveredDropType();
764    }
765
766    // if it's a playlist drop, accept it anywhere in the sourcetree by manually parsing it.
767    if ( DropJob::isDropType( DropJob::Playlist, event->mimeData()  ) )
768    {
769        qDebug() << Q_FUNC_INFO << "Current Event";
770        DropJob* dropThis = new DropJob;
771        dropThis->setDropTypes( DropJob::Playlist );
772        dropThis->setDropAction( DropJob::Create );
773        dropThis->parseMimeData( event->mimeData() );
774
775        // Don't add it to the playlist under drop, it's a new playlist now
776        return;
777    }
778
779    // Need to fake the dropevent because the treeview would reject it if it is outside the item (on the tree)
780    if ( pos.x() < 100 )
781    {
782        QDropEvent* newEvent = new QDropEvent( pos + QPoint( 100, 0 ), event->possibleActions(), event->mimeData(), event->mouseButtons(), event->keyboardModifiers(), event->type() );
783        QTreeView::dropEvent( newEvent );
784        delete newEvent;
785    }
786    else
787    {
788        QTreeView::dropEvent( event );
789    }
790
791    m_dragging = false;
792    m_dropIndex = QPersistentModelIndex();
793    m_delegate->dragLeaveEvent();
794    dataChanged( index, index );
795}
796
797
798void
799SourceTreeView::keyPressEvent( QKeyEvent* event )
800{
801    if ( ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace ) && !selectionModel()->selectedIndexes().isEmpty() )
802    {
803        QModelIndex idx = selectionModel()->selectedIndexes().first();
804        if ( model()->data( idx, SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::StaticPlaylist ||
805             model()->data( idx, SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::AutomaticPlaylist ||
806             model()->data( idx, SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::Station )
807        {
808            PlaylistItem* item = itemFromIndex< PlaylistItem >( idx );
809            Q_ASSERT( item );
810
811            if ( item->playlist()->author()->isLocal() )
812                deletePlaylist( idx );
813        }
814        event->accept();
815    }
816    else
817    {
818        event->ignore();
819    }
820    QTreeView::keyPressEvent( event );
821}
822
823
824void
825SourceTreeView::paintEvent( QPaintEvent* event )
826{
827    if ( m_dragging && !m_dropRect.isEmpty() )
828    {
829        QPainter painter( viewport() );
830        const QRect itemRect = visualRect( m_dropIndex );
831
832        QStyleOptionViewItemV4 opt;
833        opt.initFrom( this );
834        opt.rect = itemRect;
835        opt.state = QStyle::State_Enabled | QStyle::State_Selected;
836
837        style()->drawPrimitive( QStyle::PE_PanelItemViewRow, &opt, &painter, this );
838    }
839
840    QTreeView::paintEvent( event );
841}
842
843
844void
845SourceTreeView::drawRow( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
846{
847    QTreeView::drawRow( painter, option, index );
848}
849
850void
851SourceTreeView::drawBranches( QPainter *painter, const QRect &rect, const QModelIndex &index ) const
852{
853    if( !QString( qApp->style()->metaObject()->className() ).toLower().contains( "qtcurve" ) )
854        QTreeView::drawBranches( painter, rect, index );
855}
856
857
858template< typename T > T*
859SourceTreeView::itemFromIndex( const QModelIndex& index ) const
860{
861    Q_ASSERT( model()->data( index, SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >() );
862
863    T* item = qobject_cast< T* >( model()->data( index, SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >() );
864    Q_ASSERT( item );
865
866    return item;
867}
868
869
870void
871SourceTreeView::update( const QModelIndex &index )
872{
873    dataChanged( index, index );
874}