From 89ab3344d4523bb030acd6461aba3d2d2ad159ef Mon Sep 17 00:00:00 2001
From: Leo Franchi <lfranchi@kde.org>
Date: Sun, 17 Jun 2012 00:09:03 +0200
Subject: [PATCH] Create copyable artist and album page links

---
 src/libtomahawk/GlobalActionManager.cpp       | 42 ++++++++++
 src/libtomahawk/GlobalActionManager.h         |  5 ++
 .../widgets/infowidgets/AlbumInfoWidget.h     |  2 +
 .../widgets/infowidgets/ArtistInfoWidget.h    |  2 +
 .../widgets/infowidgets/TrackInfoWidget.h     |  2 +
 src/sourcetree/SourceTreeView.cpp             |  8 ++
 src/sourcetree/SourcesModel.cpp               | 62 ++++++++-------
 src/sourcetree/SourcesModel.h                 |  8 +-
 src/sourcetree/items/SourceTreeItem.h         |  1 +
 src/sourcetree/items/TemporaryPageItem.cpp    | 76 +++++++++++++++++++
 src/sourcetree/items/TemporaryPageItem.h      |  9 +++
 11 files changed, 187 insertions(+), 30 deletions(-)

diff --git a/src/libtomahawk/GlobalActionManager.cpp b/src/libtomahawk/GlobalActionManager.cpp
index afbddc214..b51bab079 100644
--- a/src/libtomahawk/GlobalActionManager.cpp
+++ b/src/libtomahawk/GlobalActionManager.cpp
@@ -97,6 +97,48 @@ GlobalActionManager::openLinkFromQuery( const query_ptr& query ) const
 }
 
 
+QUrl
+GlobalActionManager::copyOpenLink( const query_ptr& query ) const
+{
+    const QUrl link = openLinkFromQuery( query );
+
+    QClipboard* cb = QApplication::clipboard();
+    QByteArray data = link.toEncoded();
+    data.replace( "'", "%27" ); // QUrl doesn't encode ', which it doesn't have to. Some apps don't like ' though, and want %27. Both are valid.
+    cb->setText( data );
+
+    return link;
+}
+
+
+QUrl
+GlobalActionManager::copyOpenLink( const artist_ptr& artist ) const
+{
+    const QUrl link( QString( "%1/artist/%2" ).arg( hostname() ).arg( artist->name() ) );
+
+    QClipboard* cb = QApplication::clipboard();
+    QByteArray data = link.toEncoded();
+    data.replace( "'", "%27" ); // QUrl doesn't encode ', which it doesn't have to. Some apps don't like ' though, and want %27. Both are valid.
+    cb->setText( data );
+
+    return link;
+}
+
+
+QUrl
+GlobalActionManager::copyOpenLink( const album_ptr& album ) const
+{
+    const QUrl link( QString( "%1/album/%2/%3" ).arg( hostname() ).arg( album->artist().isNull() ? QString() : album->artist()->name() ).arg( album->name()) );
+
+    QClipboard* cb = QApplication::clipboard();
+    QByteArray data = link.toEncoded();
+    data.replace( "'", "%27" ); // QUrl doesn't encode ', which it doesn't have to. Some apps don't like ' though, and want %27. Both are valid.
+    cb->setText( data );
+
+    return link;
+}
+
+
 QUrl
 GlobalActionManager::openLink( const QString& title, const QString& artist, const QString& album ) const
 {
diff --git a/src/libtomahawk/GlobalActionManager.h b/src/libtomahawk/GlobalActionManager.h
index a7d519ff3..00c639224 100644
--- a/src/libtomahawk/GlobalActionManager.h
+++ b/src/libtomahawk/GlobalActionManager.h
@@ -43,6 +43,11 @@ public:
     virtual ~GlobalActionManager();
 
     QUrl openLinkFromQuery( const Tomahawk::query_ptr& query ) const;
+
+    QUrl copyOpenLink( const Tomahawk::artist_ptr& artist ) const;
+    QUrl copyOpenLink( const Tomahawk::album_ptr& album ) const;
+    QUrl copyOpenLink( const Tomahawk::query_ptr& query ) const;
+
     QUrl openLink( const QString& title, const QString& artist, const QString& album ) const;
 
 public slots:
diff --git a/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.h b/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.h
index 9d00c2847..4bfe66fb0 100644
--- a/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.h
+++ b/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.h
@@ -55,6 +55,8 @@ public:
     AlbumInfoWidget( const Tomahawk::album_ptr& album, QWidget* parent = 0 );
     ~AlbumInfoWidget();
 
+    Tomahawk::album_ptr album() const { return m_album; }
+
     virtual QWidget* widget() { return this; }
     virtual Tomahawk::playlistinterface_ptr playlistInterface() const;
 
diff --git a/src/libtomahawk/widgets/infowidgets/ArtistInfoWidget.h b/src/libtomahawk/widgets/infowidgets/ArtistInfoWidget.h
index 9e1ac0279..91686e335 100644
--- a/src/libtomahawk/widgets/infowidgets/ArtistInfoWidget.h
+++ b/src/libtomahawk/widgets/infowidgets/ArtistInfoWidget.h
@@ -66,6 +66,8 @@ public:
      */
     void load( const Tomahawk::artist_ptr& artist );
 
+    Tomahawk::artist_ptr artist() const { return m_artist; }
+
     virtual QWidget* widget() { return this; }
     virtual Tomahawk::playlistinterface_ptr playlistInterface() const;
 
diff --git a/src/libtomahawk/widgets/infowidgets/TrackInfoWidget.h b/src/libtomahawk/widgets/infowidgets/TrackInfoWidget.h
index dc9c3de64..78ea6a174 100644
--- a/src/libtomahawk/widgets/infowidgets/TrackInfoWidget.h
+++ b/src/libtomahawk/widgets/infowidgets/TrackInfoWidget.h
@@ -54,6 +54,8 @@ public:
     TrackInfoWidget( const Tomahawk::query_ptr& query, QWidget* parent = 0 );
     ~TrackInfoWidget();
 
+    Tomahawk::query_ptr query() const { return m_query; }
+
     virtual QWidget* widget() { return this; }
     virtual Tomahawk::playlistinterface_ptr playlistInterface() const;
 
diff --git a/src/sourcetree/SourceTreeView.cpp b/src/sourcetree/SourceTreeView.cpp
index 23b1a0b81..b840bae33 100644
--- a/src/sourcetree/SourceTreeView.cpp
+++ b/src/sourcetree/SourceTreeView.cpp
@@ -568,6 +568,8 @@ SourceTreeView::onCustomContextMenu( const QPoint& pos )
 
     setupMenus();
 
+    const QList< QAction* > customActions = model()->data( m_contextMenuIndex, SourcesModel::CustomActionRole ).value< QList< QAction* > >();
+
     if ( model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::StaticPlaylist ||
          model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::AutomaticPlaylist ||
          model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::Station )
@@ -586,6 +588,12 @@ SourceTreeView::onCustomContextMenu( const QPoint& pos )
         else if ( !item->source().isNull() )
             m_privacyMenu.exec( mapToGlobal( pos ) );
     }
+    else if ( !customActions.isEmpty() )
+    {
+        QMenu customMenu;
+        customMenu.addActions( customActions );
+        customMenu.exec( mapToGlobal( pos ) );
+    }
 }
 
 
diff --git a/src/sourcetree/SourcesModel.cpp b/src/sourcetree/SourcesModel.cpp
index 1b532e3b8..a2fe261ae 100644
--- a/src/sourcetree/SourcesModel.cpp
+++ b/src/sourcetree/SourcesModel.cpp
@@ -107,39 +107,43 @@ SourcesModel::data( const QModelIndex& index, int role ) const
 
     switch ( role )
     {
-        case Qt::SizeHintRole:
-            return QSize( 0, 18 );
-        case SourceTreeItemRole:
-            return QVariant::fromValue< SourceTreeItem* >( item );
-        case SourceTreeItemTypeRole:
-            return item->type();
-        case Qt::DisplayRole:
-        case Qt::EditRole:
-            return item->text();
-        case Qt::DecorationRole:
-            return item->icon();
-        case SourcesModel::SortRole:
-            return item->peerSortValue();
-        case SourcesModel::IDRole:
-            return item->IDValue();
-        case SourcesModel::LatchedOnRole:
+    case Qt::SizeHintRole:
+        return QSize( 0, 18 );
+    case SourceTreeItemRole:
+        return QVariant::fromValue< SourceTreeItem* >( item );
+    case SourceTreeItemTypeRole:
+        return item->type();
+    case Qt::DisplayRole:
+    case Qt::EditRole:
+        return item->text();
+    case Qt::DecorationRole:
+        return item->icon();
+    case SourcesModel::SortRole:
+        return item->peerSortValue();
+    case SourcesModel::IDRole:
+        return item->IDValue();
+    case SourcesModel::LatchedOnRole:
+    {
+        if ( item->type() == Collection )
         {
-            if ( item->type() == Collection )
-            {
-                SourceItem* cItem = qobject_cast< SourceItem* >( item );
-                return cItem->localLatchedOn();
-            }
-            return false;
+            SourceItem* cItem = qobject_cast< SourceItem* >( item );
+            return cItem->localLatchedOn();
         }
-        case SourcesModel::LatchedRealtimeRole:
+        return false;
+    }
+    case SourcesModel::LatchedRealtimeRole:
+    {
+        if ( item->type() == Collection )
         {
-            if ( item->type() == Collection )
-            {
-                SourceItem* cItem = qobject_cast< SourceItem* >( item );
-                return cItem->localLatchMode() == Tomahawk::PlaylistModes::RealTime;
-            }
-            return false;
+            SourceItem* cItem = qobject_cast< SourceItem* >( item );
+            return cItem->localLatchMode() == Tomahawk::PlaylistModes::RealTime;
         }
+        return false;
+    }
+    case SourcesModel::CustomActionRole:
+    {
+        return QVariant::fromValue< QList< QAction* > >( item->customActions() );
+    }
     case Qt::ToolTipRole:
         if ( !item->tooltip().isEmpty() )
             return item->tooltip();
diff --git a/src/sourcetree/SourcesModel.h b/src/sourcetree/SourcesModel.h
index dade7577a..b570cf8a0 100644
--- a/src/sourcetree/SourcesModel.h
+++ b/src/sourcetree/SourcesModel.h
@@ -28,6 +28,9 @@
 #include "Typedefs.h"
 #include "Source.h"
 
+#include <QList>
+#include <QAction>
+
 class QMimeData;
 
 class SourceTreeItem;
@@ -72,7 +75,8 @@ public:
         SortRole                = Qt::UserRole + 12,
         IDRole                  = Qt::UserRole + 13,
         LatchedOnRole           = Qt::UserRole + 14,
-        LatchedRealtimeRole     = Qt::UserRole + 15
+        LatchedRealtimeRole     = Qt::UserRole + 15,
+        CustomActionRole        = Qt::UserRole + 16 // QList< QAction* >
     };
 
     SourcesModel( QObject* parent = 0 );
@@ -149,4 +153,6 @@ private:
     Tomahawk::ViewPage* m_viewPageDelayedCacheItem;
 };
 
+Q_DECLARE_METATYPE( QList< QAction* > )
+
 #endif // SOURCESMODEL_H
diff --git a/src/sourcetree/items/SourceTreeItem.h b/src/sourcetree/items/SourceTreeItem.h
index 410542e9e..628322458 100644
--- a/src/sourcetree/items/SourceTreeItem.h
+++ b/src/sourcetree/items/SourceTreeItem.h
@@ -70,6 +70,7 @@ public:
     virtual void setDropType( DropType type ) { m_dropType = type; }
     virtual DropType dropType() const { return m_dropType; }
     virtual bool isBeingPlayed() const { return false; }
+    virtual QList< QAction* > customActions() const { return QList< QAction* >(); }
 
     /// don't call me unless you are a sourcetreeitem. i prefer this to making everyone a friend
     void beginRowsAdded( int from, int to ) { emit beginChildRowsAdded( from, to ); }
diff --git a/src/sourcetree/items/TemporaryPageItem.cpp b/src/sourcetree/items/TemporaryPageItem.cpp
index dc9f4a169..8b9ea3a04 100644
--- a/src/sourcetree/items/TemporaryPageItem.cpp
+++ b/src/sourcetree/items/TemporaryPageItem.cpp
@@ -17,11 +17,24 @@
  */
 
 #include "TemporaryPageItem.h"
+
+#include "GlobalActionManager.h"
 #include "ViewManager.h"
 #include "widgets/infowidgets/AlbumInfoWidget.h"
 #include "widgets/infowidgets/ArtistInfoWidget.h"
 #include "widgets/infowidgets/TrackInfoWidget.h"
 #include "widgets/SearchWidget.h"
+#include "utils/Closure.h"
+
+#include <QAction>
+
+namespace {
+    enum LinkType {
+        ArtistLink,
+        AlbumLink,
+        TrackLink
+    };
+}
 
 using namespace Tomahawk;
 
@@ -31,14 +44,39 @@ TemporaryPageItem::TemporaryPageItem ( SourcesModel* mdl, SourceTreeItem* parent
     , m_icon( QIcon( RESPATH "images/playlist-icon.png" ) )
     , m_sortValue( sortValue )
 {
+    QAction* action = 0;
+
     if ( dynamic_cast< ArtistInfoWidget* >( page ) )
+    {
+        action = new QAction( tr( "Copy Artist Link" ), this );
+        action->setProperty( "linkType", (int)ArtistLink );
+
         m_icon = QIcon( RESPATH "images/artist-icon.png" );
+    }
     else if ( dynamic_cast< AlbumInfoWidget* >( page ) )
+    {
+        action = new QAction( tr( "Copy Album Link" ), this );
+        action->setProperty( "linkType", (int)AlbumLink );
+
         m_icon = QIcon( RESPATH "images/album-icon.png" );
+    }
     else if ( dynamic_cast< TrackInfoWidget* >( page ) )
+    {
+        action = new QAction( tr( "Copy Track Link" ), this );
+        action->setProperty( "linkType", (int)TrackLink );
+
         m_icon = QIcon( RESPATH "images/track-icon-sidebar.png" );
+    }
     else if ( dynamic_cast< SearchWidget* >( page ) )
+    {
         m_icon = QIcon( RESPATH "images/search-icon.png" );
+    }
+
+    if ( action )
+    {
+        m_customActions << action;
+        NewClosure( action, SIGNAL( triggered() ), this, SLOT( linkActionTriggered( QAction* ) ), action );
+    }
 
     model()->linkSourceItemToPage( this, page );
 }
@@ -95,3 +133,41 @@ TemporaryPageItem::removeFromList()
 
     deleteLater();
 }
+
+
+void
+TemporaryPageItem::linkActionTriggered( QAction* action )
+{
+    Q_ASSERT( action );
+    if ( !action )
+        return;
+
+    const LinkType type = (LinkType)action->property( "linkType" ).toInt();
+    switch( type )
+    {
+    case ArtistLink:
+    {
+        ArtistInfoWidget* aPage = dynamic_cast< ArtistInfoWidget* >( m_page );
+        Q_ASSERT( aPage );
+        GlobalActionManager::instance()->copyOpenLink( aPage->artist() );
+
+        break;
+    }
+    case AlbumLink:
+    {
+        AlbumInfoWidget* aPage = dynamic_cast< AlbumInfoWidget* >( m_page );
+        Q_ASSERT( aPage );
+        GlobalActionManager::instance()->copyOpenLink( aPage->album() );
+
+        break;
+    }
+    case TrackLink:
+    {
+        TrackInfoWidget* tPage = dynamic_cast< TrackInfoWidget* >( m_page );
+        Q_ASSERT( tPage );
+        GlobalActionManager::instance()->copyOpenLink( tPage->query() );
+
+        break;
+    }
+    }
+}
diff --git a/src/sourcetree/items/TemporaryPageItem.h b/src/sourcetree/items/TemporaryPageItem.h
index f0cfb8b27..72350d87f 100644
--- a/src/sourcetree/items/TemporaryPageItem.h
+++ b/src/sourcetree/items/TemporaryPageItem.h
@@ -22,6 +22,8 @@
 #include "items/SourceTreeItem.h"
 #include "ViewPage.h"
 
+class QAction;
+
 class TemporaryPageItem : public SourceTreeItem
 {
     Q_OBJECT
@@ -34,6 +36,7 @@ public:
     virtual QIcon icon() const;
     virtual int peerSortValue() const;
     virtual int IDValue() const;
+    virtual QList< QAction* > customActions() const { return m_customActions; }
 
     Tomahawk::ViewPage* page() const { return m_page; }
     virtual bool isBeingPlayed() const { return m_page->isBeingPlayed(); }
@@ -44,10 +47,16 @@ public slots:
 signals:
     bool removed();
 
+private slots:
+    void linkActionTriggered( QAction* );
+
 private:
     Tomahawk::ViewPage* m_page;
     QIcon m_icon;
     int m_sortValue;
+    QList< QAction* > m_customActions;
 };
 
+Q_DECLARE_METATYPE( QAction* )
+
 #endif // TEMPORARYPAGEITEM_H