From cf63a7af973b667a0bc1b51dea21cf6d9b55cba7 Mon Sep 17 00:00:00 2001
From: Nyall Dawson <nyall.dawson@gmail.com>
Date: Wed, 12 Jun 2019 13:01:40 +1000
Subject: [PATCH] [needs-docs] Allow dragging project layers from legend over
 map layer comboboxes to select

Greatly assists with usability of the combobox with large projects,
especially those with multiple copies of layers with the same name...
---
 .../auto_generated/qgsmaplayercombobox.sip.in | 11 +++
 src/gui/qgsmaplayercombobox.cpp               | 77 ++++++++++++++++++-
 src/gui/qgsmaplayercombobox.h                 | 14 ++++
 3 files changed, 101 insertions(+), 1 deletion(-)

diff --git a/python/gui/auto_generated/qgsmaplayercombobox.sip.in b/python/gui/auto_generated/qgsmaplayercombobox.sip.in
index 5545518e350..07dfca545d7 100644
--- a/python/gui/auto_generated/qgsmaplayercombobox.sip.in
+++ b/python/gui/auto_generated/qgsmaplayercombobox.sip.in
@@ -154,6 +154,17 @@ setLayer set the current layer selected in the combo
 Emitted whenever the currently selected layer changes.
 %End
 
+  protected:
+
+    virtual void dragEnterEvent( QDragEnterEvent *event );
+
+    virtual void dragLeaveEvent( QDragLeaveEvent *event );
+
+    virtual void dropEvent( QDropEvent *event );
+
+    virtual void paintEvent( QPaintEvent *e );
+
+
   protected slots:
     void indexChanged( int i );
     void rowsChanged();
diff --git a/src/gui/qgsmaplayercombobox.cpp b/src/gui/qgsmaplayercombobox.cpp
index 2995c0aa25f..cfae40c560e 100644
--- a/src/gui/qgsmaplayercombobox.cpp
+++ b/src/gui/qgsmaplayercombobox.cpp
@@ -15,7 +15,9 @@
 
 #include "qgsmaplayercombobox.h"
 #include "qgsmaplayermodel.h"
-
+#include "qgsmimedatautils.h"
+#include <QDragEnterEvent>
+#include <QPainter>
 
 QgsMapLayerComboBox::QgsMapLayerComboBox( QWidget *parent )
   : QComboBox( parent )
@@ -26,6 +28,8 @@ QgsMapLayerComboBox::QgsMapLayerComboBox( QWidget *parent )
   connect( this, static_cast < void ( QComboBox::* )( int ) > ( &QComboBox::activated ), this, &QgsMapLayerComboBox::indexChanged );
   connect( mProxyModel, &QAbstractItemModel::rowsInserted, this, &QgsMapLayerComboBox::rowsChanged );
   connect( mProxyModel, &QAbstractItemModel::rowsRemoved, this, &QgsMapLayerComboBox::rowsChanged );
+
+  setAcceptDrops( true );
 }
 
 void QgsMapLayerComboBox::setExcludedProviders( const QStringList &providers )
@@ -138,3 +142,74 @@ void QgsMapLayerComboBox::rowsChanged()
   }
 }
 
+QgsMapLayer *QgsMapLayerComboBox::compatibleMapLayerFromMimeData( const QMimeData *data ) const
+{
+  const QgsMimeDataUtils::UriList uriList = QgsMimeDataUtils::decodeUriList( data );
+  for ( const QgsMimeDataUtils::Uri &u : uriList )
+  {
+    // is this uri from the current project?
+    if ( QgsMapLayer *layer = u.mapLayer() )
+    {
+      if ( mProxyModel->acceptsLayer( layer ) )
+        return layer;
+    }
+  }
+  return nullptr;
+}
+
+void QgsMapLayerComboBox::dragEnterEvent( QDragEnterEvent *event )
+{
+  if ( !( event->possibleActions() & Qt::CopyAction ) )
+    return;
+
+  if ( compatibleMapLayerFromMimeData( event->mimeData() ) )
+  {
+    // dragged an acceptable layer, phew
+    event->setDropAction( Qt::CopyAction );
+    event->accept();
+    mDragActive = true;
+    update();
+  }
+}
+
+void QgsMapLayerComboBox::dragLeaveEvent( QDragLeaveEvent *event )
+{
+  QComboBox::dragLeaveEvent( event );
+  if ( mDragActive )
+  {
+    event->accept();
+    mDragActive = false;
+    update();
+  }
+}
+
+void QgsMapLayerComboBox::dropEvent( QDropEvent *event )
+{
+  if ( !( event->possibleActions() & Qt::CopyAction ) )
+    return;
+
+  if ( QgsMapLayer *layer = compatibleMapLayerFromMimeData( event->mimeData() ) )
+  {
+    // dropped an acceptable layer, phew
+    setFocus( Qt::MouseFocusReason );
+    event->setDropAction( Qt::CopyAction );
+    event->accept();
+
+    setLayer( layer );
+  }
+  mDragActive = false;
+  update();
+}
+
+void QgsMapLayerComboBox::paintEvent( QPaintEvent *e )
+{
+  QComboBox::paintEvent( e );
+  if ( mDragActive )
+  {
+    QPainter p( this );
+    int width = 2;  // width of highlight rectangle inside frame
+    p.setPen( QPen( palette().highlight(), width ) );
+    QRect r = rect().adjusted( width, width, -width, -width );
+    p.drawRect( r );
+  }
+}
diff --git a/src/gui/qgsmaplayercombobox.h b/src/gui/qgsmaplayercombobox.h
index c72959cf49c..3067be524c7 100644
--- a/src/gui/qgsmaplayercombobox.h
+++ b/src/gui/qgsmaplayercombobox.h
@@ -139,12 +139,26 @@ class GUI_EXPORT QgsMapLayerComboBox : public QComboBox
     //! Emitted whenever the currently selected layer changes.
     void layerChanged( QgsMapLayer *layer );
 
+  protected:
+
+    void dragEnterEvent( QDragEnterEvent *event ) override;
+    void dragLeaveEvent( QDragLeaveEvent *event ) override;
+    void dropEvent( QDropEvent *event ) override;
+    void paintEvent( QPaintEvent *e ) override;
+
   protected slots:
     void indexChanged( int i );
     void rowsChanged();
 
   private:
     QgsMapLayerProxyModel *mProxyModel = nullptr;
+    bool mDragActive = false;
+
+    /**
+     * Returns a map layer, compatible with the filters set for the combo box, from
+     * the specified mime \a data (if possible!).
+     */
+    QgsMapLayer *compatibleMapLayerFromMimeData( const QMimeData *data ) const;
 };
 
 #endif // QGSMAPLAYERCOMBOBOX_H