Russian Qt Forum

Qt => Кладовая готовых решений => Тема начата: __Heaven__ от Март 09, 2017, 13:51



Название: Simple QTreeView internal drag n drop example
Отправлено: __Heaven__ от Март 09, 2017, 13:51
Привет друзья!
Предисловие:
Весьма долго осваивал драг и дроп в представлениях. Забагованный пример widgets/itemviews/puzzle только внёс неясности. Баг его заключается в том, что если я перетащу детальку не на поле, а просто внутри представления вверх, то при дропе затирается существующая деталька из списка, перед которой была сброшена текущая.
Также в ходе изучения был выявлен баг Qt (https://bugreports.qt.io/browse/QTBUG-59324).
Свои потуги решил зафиксировать здесь в качестве примера. Буду признателен за конструктивную критику кода, подхода.
Сам пример.
Привожу ссылку (https://github.com/dsbabkov/SimpleDragableTree) для скачивания проекта.
Для данного подхода важно, чтобы свойство представления dragDropOverwriteMode было установлено в true. Это позволит избежать лишнего для нас вызова removeRows, аргументы которого для меня тоже оказались какими-то магическими.
Код
C++ (Qt)
// TreeNode.h
#pragma once
 
#include <QString>
#include <QVector>
#include <memory>
 
class TreeNode: public std::enable_shared_from_this<TreeNode>
{
public:
   using ChildPtr = std::shared_ptr<TreeNode>;
   using ConstChildPtr = std::shared_ptr<const TreeNode>;
   using ParentPtr = std::weak_ptr<TreeNode>;
   using ConstParentPtr = std::weak_ptr<const TreeNode>;
   using LockedParentPtr = ChildPtr;
   using ConstLockedParentPtr = ConstChildPtr;
 
public:
   const QString name;
 
public:
   explicit TreeNode(const QString &name);
 
   ParentPtr parent() const;
   int row() const;
 
   int childrenCount() const;
   void insertChild(const ChildPtr &child, int position = -1);
   void removeChild(const ChildPtr &child);
   ChildPtr child(int row) const;
 
private:
   void setParent(const ParentPtr &parent);
   void moveChild(const ChildPtr &child, int newPosition);
 
private:
   ParentPtr parent_;
   QVector<ChildPtr> children_;
};
Код
C++ (Qt)
// TreeNode.cpp
#include "TreeNode.h"
#include <set>
 
TreeNode::TreeNode(const QString &name)
   : name{name}
   , parent_{}
{
}
 
TreeNode::ParentPtr TreeNode::parent() const
{
   return parent_;
}
 
int TreeNode::row() const
{
   if (parent_.expired()){
       return -1;
   }
 
   return parent_.lock()->children_
           .indexOf(std::const_pointer_cast<TreeNode>(shared_from_this()));
}
 
int TreeNode::childrenCount() const
{
   return children_.count();
}
 
void TreeNode::insertChild(const TreeNode::ChildPtr &child, int position)
{
   const ParentPtr lastParent = child->parent();
 
   if (!lastParent.expired()){
       const auto &lockedParent = lastParent.lock();
       if (lockedParent.get() == this){
           moveChild(child, position);
           return;
       }
 
       lockedParent->removeChild(child);
   }
   child->setParent(shared_from_this());
 
   if (position == -1){
       children_ << child;
   }
   else{
       children_.insert(position, child);
   }
 
}
 
void TreeNode::removeChild(const TreeNode::ChildPtr &child)
{
   const int pos = children_.indexOf(child);
   if (pos != -1){
       children_.takeAt(pos)->setParent({});
   }
}
 
TreeNode::ChildPtr TreeNode::child(int row) const
{
   return children_.at(row);
}
 
void TreeNode::setParent(const TreeNode::ParentPtr &parent)
{
   parent_ = parent;
}
 
void TreeNode::moveChild(const TreeNode::ChildPtr &child, int newPosition)
{
   int from = child->row();
   if (from == newPosition){
       return;
   }
 
   if (from < newPosition)
   {
       --newPosition;
   }
 
   children_.insert(newPosition, children_.takeAt(from));
}
Код
C++ (Qt)
// TreeModel.h
#pragma once
 
#include <QAbstractItemModel>
#include <memory>
 
class TreeNode;
 
class TreeModel : public QAbstractItemModel
{
   Q_OBJECT
 
public:
   explicit TreeModel(QObject *parent = {});
 
   virtual QModelIndex index(int row, int column,
                     const QModelIndex &parent = QModelIndex()) const override;
   virtual QModelIndex parent(const QModelIndex &index) const override;
 
   virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
   virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override;
 
   virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
   virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
 
   virtual Qt::DropActions supportedDropActions() const override;
   virtual QStringList mimeTypes() const override;
   virtual QMimeData *mimeData(const QModelIndexList &indexes) const override;
   virtual bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override;
   virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
 
private:
   void fillTreeWithData();
 
   static QByteArray saveIndexes(const QModelIndexList &indexes);
   QModelIndexList restoreIndexes(QByteArray data);
 
   static void sortIndexes(QModelIndexList &indexes);
 
private:
   std::shared_ptr<TreeNode> rootNode_;
};

Код
C++ (Qt)
// TreeModel.cpp
#include "TreeModel.h"
#include "TreeNode.h"
#include <QDataStream>
#include <QMimeData>
#include <QByteArray>
#include <QStack>
#include <algorithm>
 
using namespace std;
 
namespace {
static constexpr char mimeType[] = "MyNode";
 
struct MovableChild{
   TreeNode::ChildPtr ptr;
   QModelIndex parentIndex;
};
 
   QList<MovableChild> convertIndexesToMovableChildren(const QModelIndexList &indexes){
       QList<MovableChild> result;
       result.reserve(indexes.count());
 
       for (const QModelIndex &index: indexes){
           if (!index.isValid()){
               result.append({nullptr, {}});
               continue;
           }
 
           TreeNode *child = static_cast<TreeNode *>(index.internalPointer());
           result.append({child->shared_from_this(), index.parent()});
       }
 
       return result;
   }
}
 
 
TreeModel::TreeModel(QObject *parent)
   : QAbstractItemModel(parent)
   , rootNode_{std::make_shared<TreeNode>("ROOT_NODE")}
{
   fillTreeWithData();
}
 
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{
   TreeNode *parentNode = parent.isValid() ?
               static_cast<TreeNode *>(parent.internalPointer()) :
               rootNode_.get();
 
   return createIndex(row, column, parentNode->child(row).get());
}
 
QModelIndex TreeModel::parent(const QModelIndex &index) const
{
   if (!index.isValid()){
       return {};
   }
 
   TreeNode *node = static_cast<TreeNode *>(index.internalPointer());
   TreeNode::ParentPtr parentNode = node->parent();
 
   if (parentNode.expired()){
       return {};
   }
 
   std::shared_ptr<TreeNode> lockedParentNode = parentNode.lock();
   return createIndex(lockedParentNode->row(), 0, lockedParentNode.get());
}
 
int TreeModel::rowCount(const QModelIndex &parent) const
{
   if (!parent.isValid()){
       return rootNode_->childrenCount();
   }
 
   TreeNode *parentNode = static_cast<TreeNode *>(parent.internalPointer());
   return parentNode->childrenCount();
}
 
int TreeModel::columnCount(const QModelIndex &/*parent*/) const
{
   return 1;
}
 
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
   if (!index.isValid() ||
           role != Qt::DisplayRole) {
       return QVariant();
   }
 
   return static_cast<TreeNode *>(index.internalPointer())->name;
}
 
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
   if (!index.isValid()){
       return {};
   }
 
   Qt::ItemFlags result = Qt::ItemIsEnabled |
           Qt::ItemIsSelectable;
 
   if (!index.parent().isValid()){ // Group nodes cann accept drop
       result |= Qt::ItemIsDropEnabled;
   }
   else{   //leaves can be draged
       result |= Qt::ItemIsDragEnabled;
   }
 
   return result;
}
 
Qt::DropActions TreeModel::supportedDropActions() const
{
   return Qt::MoveAction;
}
 
QStringList TreeModel::mimeTypes() const
{
   return {mimeType};
}
 
QMimeData *TreeModel::mimeData(const QModelIndexList &indexes) const
{
   QMimeData *result = new QMimeData();
   result->setData(mimeType, saveIndexes(indexes));
   return result;
}
 
bool TreeModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int /*column*/, const QModelIndex &parent) const
{
   if (!parent.isValid() || // root is not drop enabled
           action != Qt::MoveAction ||
           !data->hasFormat(mimeType) ||
           row < 0 ||
           row > rowCount(parent)){
       return false;
   }
 
   return true;
}
 
bool TreeModel::dropMimeData(const QMimeData *data, Qt::DropAction /*action*/, int row, int /*column*/, const QModelIndex &parent)
{
   QModelIndexList indexes = restoreIndexes(data->data(mimeType));
   if (indexes.isEmpty()){
       return false;
   }
 
   sortIndexes(indexes);
   const QList<MovableChild> &childrenToMove = convertIndexesToMovableChildren(indexes);
 
   TreeNode *parentNode = static_cast<TreeNode *>(parent.internalPointer());
   for (const MovableChild &movableChild: childrenToMove){
       const int srcRow = movableChild.ptr->row();
       const bool interParentMove = movableChild.parentIndex == parent;
       const bool incrementRow = !(interParentMove && srcRow < row);
 
       beginMoveRows(movableChild.parentIndex, srcRow, srcRow, parent, row);
       parentNode->insertChild(movableChild.ptr, row);
       endMoveRows();
 
       row += incrementRow;
   }
 
   return true;
}
 
void TreeModel::fillTreeWithData()
{
   const std::vector<TreeNode::ChildPtr> groups = {
       make_shared<TreeNode>("Group 0"),
       make_shared<TreeNode>("Group 1"),
       make_shared<TreeNode>("Group 2")
   };
 
   for (TreeNode::ChildPtr group: groups){
       rootNode_->insertChild(group);
 
       for(int i = 0; i < 5; ++i){
           group->insertChild(
                       make_shared<TreeNode>(QStringLiteral("Item %1 of %2").arg(i).arg(group->name))
                       );
       }
   }
}
 
QByteArray TreeModel::saveIndexes(const QModelIndexList &indexes)
{
   QByteArray result;
   QDataStream stream(&result, QIODevice::WriteOnly);
 
   for (const QModelIndex &index: indexes){
       QModelIndex localIndex = index;
       QStack<int> indexParentStack;
       while (localIndex.isValid()){
           indexParentStack << localIndex.row();
           localIndex = localIndex.parent();
       }
 
       stream << indexParentStack.size();
       while (!indexParentStack.isEmpty()){
           stream << indexParentStack.pop();
       }
   }
   return result;
}
 
QModelIndexList TreeModel::restoreIndexes(QByteArray data)
{
   QModelIndexList result;
   QDataStream stream(&data, QIODevice::ReadOnly);
 
   while(!stream.atEnd()){
       int childDepth = 0;
       stream >> childDepth;
 
       QModelIndex currentIndex = {};
       for (int i = 0; i < childDepth; ++i){
           int row = 0;
           stream >> row;
           currentIndex = index(row, 0, currentIndex);
       }
       result << currentIndex;
   }
 
   return result;
}
 
void TreeModel::sortIndexes(QModelIndexList &indexes)
{
   std::sort(indexes.begin(), indexes.end(), [](const QModelIndex &left, const QModelIndex &right){
       if (left.parent() < right.parent()){
           return true;
       }
       else if (right.parent() < left.parent()){
           return false;
       }
       else{
           return left < right;
       }
   });
}