From e49123d6363b217741b98d2d23d26208812034f6 Mon Sep 17 00:00:00 2001 From: Simon Quigley Date: Sat, 14 Jul 2018 18:16:58 -0500 Subject: [PATCH] Polish the drag and drop experience by backporting some upstream commits. --- debian/changelog | 1 + debian/patches/polish-dnd-1.patch | 293 ++++++++++++++++++++++++++++++ debian/patches/polish-dnd-2.patch | 89 +++++++++ debian/patches/series | 2 + 4 files changed, 385 insertions(+) create mode 100644 debian/patches/polish-dnd-1.patch create mode 100644 debian/patches/polish-dnd-2.patch create mode 100644 debian/patches/series diff --git a/debian/changelog b/debian/changelog index b950e49..82a4f30 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,7 @@ pcmanfm-qt (0.13.0-2ubuntu1) UNRELEASED; urgency=medium * Update Vcs-* and Maintainer for maintaining in Lubuntu. + * Polish the drag and drop experience by backporting some upstream commits. -- Simon Quigley Sat, 14 Jul 2018 18:08:44 -0500 diff --git a/debian/patches/polish-dnd-1.patch b/debian/patches/polish-dnd-1.patch new file mode 100644 index 0000000..256862c --- /dev/null +++ b/debian/patches/polish-dnd-1.patch @@ -0,0 +1,293 @@ +Description: Fix DND and drop indicator on desktop + (1) When items are dragged and dropped on any desktop position other than + that of a directory, they will stick to it (and to its following positions), + moving next sticky items if needed. When desktop items are dropped on + themselves or on a directory, the DND menu will be shown. + . + (2) The drop rectangle (when dragging desktop items on desktop folders) is + also fixed by drawing it explicitly. +Author: Tsu Jan +Origin: upstream +Bug: https://github.com/lxqt/pcmanfm-qt/issues/706, https://github.com/lxqt/pcmanfm-qt/issues/722 +Applied-Upstream: commit:748a105 +Last-Update: 2018-07-14 +--- a/pcmanfm/desktopwindow.cpp ++++ b/pcmanfm/desktopwindow.cpp +@@ -91,6 +91,7 @@ DesktopWindow::DesktopWindow(int screenN + listView_->setMovement(QListView::Snap); + listView_->setResizeMode(QListView::Adjust); + listView_->setFlow(QListView::TopToBottom); ++ listView_->setDropIndicatorShown(false); // we draw the drop indicator ourself + + // This is to workaround Qt bug 54384 which affects Qt >= 5.6 + // https://bugreports.qt.io/browse/QTBUG-54384 +@@ -126,7 +127,6 @@ DesktopWindow::DesktopWindow(int screenN + connect(proxyModel_, &Fm::ProxyFolderModel::layoutChanged, this, &DesktopWindow::onLayoutChanged); + connect(proxyModel_, &Fm::ProxyFolderModel::sortFilterChanged, this, &DesktopWindow::onModelSortFilterChanged); + connect(proxyModel_, &Fm::ProxyFolderModel::dataChanged, this, &DesktopWindow::onDataChanged); +- connect(listView_, &QListView::indexesMoved, this, &DesktopWindow::onIndexesMoved); + } + + // remove frame +@@ -710,41 +710,6 @@ void DesktopWindow::onDataChanged(const + } + } + +-void DesktopWindow::onIndexesMoved(const QModelIndexList& indexes) { +- auto delegate = static_cast(listView_->itemDelegateForColumn(0)); +- auto itemSize = delegate->itemSize(); +- // remember the custom position for the items +- for(const QModelIndex& index : indexes) { +- // Under some circumstances, Qt might emit indexMoved for +- // every single cells in the same row. (when QAbstractItemView::SelectItems is set) +- // So indexes list may contain several indixes for the same row. +- // Since we only care about rows, not individual cells, +- // let's handle column 0 of every row here. +- if(index.column() == 0) { +- auto file = proxyModel_->fileInfoFromIndex(index); +- QRect itemRect = listView_->rectForIndex(index); +- QPoint tl = itemRect.topLeft(); +- QRect workArea = qApp->desktop()->availableGeometry(screenNum_); +- workArea.adjust(12, 12, -12, -12); +- +- // check if the position is occupied by another item +- auto existingItem = std::find_if(customItemPos_.cbegin(), customItemPos_.cend(), [tl](const std::pair& elem){ +- return elem.second == tl; +- }); +- +- if(existingItem == customItemPos_.cend() // don't put items on each other +- && tl.x() >= workArea.x() && tl.y() >= workArea.y() +- && tl.x() + itemSize.width() <= workArea.right() + 1 // for historical reasons (-> Qt doc) +- && tl.y() + itemSize.height() <= workArea.bottom() + 1) { // as above +- customItemPos_[file->name()] = tl; +- // qDebug() << "indexMoved:" << name << index << itemRect; +- } +- } +- } +- saveItemPositions(); +- queueRelayout(); +-} +- + void DesktopWindow::onFolderStartLoading() { // desktop may be reloaded + if(model_) { + disconnect(model_, &Fm::FolderModel::filesAdded, this, &DesktopWindow::onFilesAdded); +@@ -1247,6 +1212,11 @@ bool DesktopWindow::eventFilter(QObject* + } + } + break; ++ case QEvent::Paint: ++ // NOTE: The drop indicator isn't drawn/updated automatically, perhaps, ++ // because we paint desktop ourself. So, we draw it here. ++ paintDropIndicator(); ++ break; + default: + break; + } +@@ -1254,6 +1224,34 @@ bool DesktopWindow::eventFilter(QObject* + return Fm::FolderView::eventFilter(watched, event); + } + ++void DesktopWindow::childDragMoveEvent(QDragMoveEvent* e) { ++ // see DesktopWindow::eventFilter for an explanation ++ QRect oldDropRect = dropRect_; ++ dropRect_ = QRect(); ++ QModelIndex index = listView_->indexAt(e->pos()); ++ if(index.isValid() && index.model()) { ++ QVariant data = index.model()->data(index, Fm::FolderModel::Role::FileInfoRole); ++ auto info = data.value>(); ++ if(info && info->isDir()) { ++ dropRect_ = listView_->rectForIndex(index); ++ } ++ } ++ if(oldDropRect != dropRect_){ ++ listView_->viewport()->update(); ++ } ++} ++ ++void DesktopWindow::paintDropIndicator() ++{ ++ if(!dropRect_.isNull()) { ++ QPainter painter(listView_->viewport()); ++ QStyleOption opt; ++ opt.init(listView_->viewport()); ++ opt.rect = dropRect_; ++ style()->drawPrimitive(QStyle::PE_IndicatorItemViewItemDrop, &opt, &painter, listView_); ++ } ++} ++ + void DesktopWindow::childDropEvent(QDropEvent* e) { + const QMimeData* mimeData = e->mimeData(); + bool moveItem = false; +@@ -1264,45 +1262,59 @@ void DesktopWindow::childDropEvent(QDrop + QModelIndex dropIndex = listView_->indexAt(e->pos()); + if(dropIndex.isValid()) { // drop on an item + QModelIndexList selected = selectedIndexes(); // the dragged items +- if(selected.contains(dropIndex)) { // drop on self, ignore +- moveItem = true; ++ if(!selected.contains(dropIndex)) { // not a drop on self ++ if(auto file = proxyModel_->fileInfoFromIndex(dropIndex)) { ++ if(!file->isDir()) { // drop on a non-directory file ++ moveItem = true; ++ } ++ } + } + } +- else { // drop on a blank area ++ else { // drop on a blank area (maybe, between other items) + moveItem = true; + } + } + } + if(moveItem) { + e->accept(); +- } +- else { ++ // move selected items to the drop position, putting them successively + auto delegate = static_cast(listView_->itemDelegateForColumn(0)); + auto grid = delegate->itemSize(); ++ QRect workArea = qApp->desktop()->availableGeometry(screenNum_); ++ workArea.adjust(12, 12, -12, -12); ++ QPoint pos = mapFromGlobal(e->pos()); ++ const QModelIndexList indexes = selectedIndexes(); ++ for(const QModelIndex& indx : indexes) { ++ if(auto file = proxyModel_->fileInfoFromIndex(indx)) { ++ stickToPosition(QString::fromStdString(file->name()), pos, workArea, grid); ++ } ++ } ++ saveItemPositions(); ++ queueRelayout(); ++ } ++ else { + Fm::FolderView::childDropEvent(e); ++ // remove the drop indicator ++ dropRect_ = QRect(); ++ listView_->viewport()->update(); + // position dropped items successively, starting with the drop rectangle + if(mimeData->hasUrls() + && (e->dropAction() == Qt::CopyAction + || e->dropAction() == Qt::MoveAction + || e->dropAction() == Qt::LinkAction)) { +- QList urlList = mimeData->urls(); +- for(int i = 0; i < urlList.count(); ++i) { +- std::string name = urlList.at(i).fileName().toUtf8().constData(); +- if(!name.empty()) { // respect the positions of existing files +- QString desktopDir = XdgDir::readDesktopDir() + QString(QLatin1String("/")); +- if(!QFile::exists(desktopDir + QString::fromStdString(name))) { +- QRect workArea = qApp->desktop()->availableGeometry(screenNum_); +- workArea.adjust(12, 12, -12, -12); +- QPoint pos = mapFromGlobal(e->pos()); +- alignToGrid(pos, workArea.topLeft(), grid, listView_->spacing()); +- if(i > 0) +- pos.setY(pos.y() + grid.height() + listView_->spacing()); +- if(pos.y() + grid.height() > workArea.bottom() + 1) { +- pos.setX(pos.x() + grid.width() + listView_->spacing()); +- pos.setY(workArea.top()); +- } +- customItemPos_[name] = pos; +- } ++ auto delegate = static_cast(listView_->itemDelegateForColumn(0)); ++ auto grid = delegate->itemSize(); ++ QRect workArea = qApp->desktop()->availableGeometry(screenNum_); ++ workArea.adjust(12, 12, -12, -12); ++ const QString desktopDir = XdgDir::readDesktopDir() + QString(QLatin1String("/")); ++ QPoint pos = mapFromGlobal(e->pos()); ++ const QList urlList = mimeData->urls(); ++ for(const QUrl& url : urlList) { ++ QString name = url.fileName(); ++ if(!name.isEmpty() ++ // don't stick to the position if there is an overwrite prompt ++ && !QFile::exists(desktopDir + name)) { ++ stickToPosition(name, pos, workArea, grid); + } + } + saveItemPositions(); +@@ -1310,10 +1322,39 @@ void DesktopWindow::childDropEvent(QDrop + } + } + ++// This function recursively repositions next sticky items if needed. ++void DesktopWindow::stickToPosition(const QString& file, QPoint& pos, const QRect& workArea, const QSize& grid) { ++ // normalize the position ++ alignToGrid(pos, workArea.topLeft(), grid, listView_->spacing()); ++ if(pos.y() + grid.height() > workArea.bottom() + 1) { ++ pos.setX(pos.x() + grid.width() + listView_->spacing()); ++ pos.setY(workArea.top()); ++ } ++ // find if there is a sticky item at this position ++ QString otherFile; ++ auto oldItem = std::find_if(customItemPos_.cbegin(), ++ customItemPos_.cend(), ++ [pos](const std::pair& elem) { ++ return elem.second == pos; ++ }); ++ if(oldItem != customItemPos_.cend()) { ++ otherFile = QString::fromStdString(oldItem->first); ++ } ++ // stick to the position ++ customItemPos_[file.toStdString()] = pos; ++ // find the next position ++ pos.setY(pos.y() + grid.height() + listView_->spacing()); ++ // if there was another sticky item at the same position, move it to the next position ++ if(!otherFile.isEmpty() && otherFile != file) { ++ QPoint nextPos = pos; ++ stickToPosition(otherFile, nextPos, workArea, grid); ++ } ++} ++ + void DesktopWindow::alignToGrid(QPoint& pos, const QPoint& topLeft, const QSize& grid, const int spacing) { + qreal w = qAbs((qreal)pos.x() - (qreal)topLeft.x()) + / (qreal)(grid.width() + spacing); +- qreal h = qAbs(pos.y() - (qreal)topLeft.y()) ++ qreal h = qAbs((qreal)pos.y() - (qreal)topLeft.y()) + / (qreal)(grid.height() + spacing); + pos.setX(topLeft.x() + qRound(w) * (grid.width() + spacing)); + pos.setY(topLeft.y() + qRound(h) * (grid.height() + spacing)); +@@ -1324,7 +1365,7 @@ void DesktopWindow::closeEvent(QCloseEve + event->ignore(); + } + +-void DesktopWindow::paintEvent(QPaintEvent *event) { ++void DesktopWindow::paintEvent(QPaintEvent* event) { + paintBackground(event); + QWidget::paintEvent(event); + } +--- a/pcmanfm/desktopwindow.h ++++ b/pcmanfm/desktopwindow.h +@@ -98,9 +98,10 @@ protected: + virtual bool event(QEvent* event) override; + virtual bool eventFilter(QObject* watched, QEvent* event) override; + ++ virtual void childDragMoveEvent(QDragMoveEvent* e) override; + virtual void childDropEvent(QDropEvent* e) override; + virtual void closeEvent(QCloseEvent* event) override; +- virtual void paintEvent(QPaintEvent *event) override; ++ virtual void paintEvent(QPaintEvent* event) override; + + protected Q_SLOTS: + void onOpenDirRequested(const Fm::FilePath& path, int target); +@@ -112,7 +113,6 @@ protected Q_SLOTS: + void onRowsInserted(const QModelIndex& parent, int start, int end); + void onLayoutChanged(); + void onModelSortFilterChanged(); +- void onIndexesMoved(const QModelIndexList& indexes); + void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + void onFolderStartLoading(); + void onFolderFinishLoading(); +@@ -136,6 +136,8 @@ private: + void removeBottomGap(); + void addDesktopActions(QMenu* menu); + void paintBackground(QPaintEvent* event); ++ void paintDropIndicator(); ++ void stickToPosition(const QString& file, QPoint& pos, const QRect& workArea, const QSize& grid); + static void alignToGrid(QPoint& pos, const QPoint& topLeft, const QSize& grid, const int spacing); + + private: +@@ -164,6 +166,8 @@ private: + QHash displayNames_; // only for desktop entries and shortcuts + QTimer* relayoutTimer_; + QTimer* selectionTimer_; ++ ++ QRect dropRect_; + }; + + } diff --git a/debian/patches/polish-dnd-2.patch b/debian/patches/polish-dnd-2.patch new file mode 100644 index 0000000..5e220fc --- /dev/null +++ b/debian/patches/polish-dnd-2.patch @@ -0,0 +1,89 @@ +Description: Always drop into the cell behind cursor + Drop desktop items into the cell behind the cursor, regardless of the cursor + position inside the cell. + . + Previously, if the cursor was in the right half of the cell, the item would + be dropped into the next cell on the right, and a similar thing happened with + the right bottom half. +Author: Tsu Jan +Origin: upstream +Bug: https://github.com/lxqt/pcmanfm-qt/issues/728 +Applied-Upstream: commit:a7898c9 +Last-Update: 2018-07-14 +--- a/pcmanfm/desktopwindow.cpp ++++ b/pcmanfm/desktopwindow.cpp +@@ -59,6 +59,7 @@ + #include + #include + ++#define WORK_AREA_MARGIN 12 // margin of the work area + #define MIN_SLIDE_INTERVAL 5*60000 // 5 min + #define MAX_SLIDE_INTERVAL (24*60+55)*60000 // 24 h and 55 min + +@@ -751,7 +752,7 @@ void DesktopWindow::removeBottomGap() { + //qDebug() << "delegate:" << delegate->itemSize(); + QSize cellMargins = getMargins(); + int workAreaHeight = qApp->desktop()->availableGeometry(screenNum_).height() +- - 24; // a 12-pix margin will be considered everywhere ++ - 2 * WORK_AREA_MARGIN; + int cellHeight = itemSize.height() + listView_->spacing(); + int iconNumber = workAreaHeight / cellHeight; + int bottomGap = workAreaHeight % cellHeight; +@@ -827,7 +828,7 @@ void DesktopWindow::relayoutItems() { + screen = screenNum_; + } + QRect workArea = desktop->availableGeometry(screen); +- workArea.adjust(12, 12, -12, -12); // add a 12 pixel margin to the work area ++ workArea.adjust(WORK_AREA_MARGIN, WORK_AREA_MARGIN, -WORK_AREA_MARGIN, -WORK_AREA_MARGIN); + // qDebug() << "workArea" << screen << workArea; + // FIXME: we use an internal class declared in a private header here, which is pretty bad. + QPoint pos = workArea.topLeft(); +@@ -902,7 +903,7 @@ void DesktopWindow::loadItemPositions() + auto delegate = static_cast(listView_->itemDelegateForColumn(0)); + auto grid = delegate->itemSize(); + QRect workArea = qApp->desktop()->availableGeometry(screenNum_); +- workArea.adjust(12, 12, -12, -12); ++ workArea.adjust(WORK_AREA_MARGIN, WORK_AREA_MARGIN, -WORK_AREA_MARGIN, -WORK_AREA_MARGIN); + QString desktopDir = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + desktopDir += '/'; + std::vector usedPos; +@@ -1281,8 +1282,8 @@ void DesktopWindow::childDropEvent(QDrop + auto delegate = static_cast(listView_->itemDelegateForColumn(0)); + auto grid = delegate->itemSize(); + QRect workArea = qApp->desktop()->availableGeometry(screenNum_); +- workArea.adjust(12, 12, -12, -12); +- QPoint pos = mapFromGlobal(e->pos()); ++ workArea.adjust(WORK_AREA_MARGIN, WORK_AREA_MARGIN, -WORK_AREA_MARGIN, -WORK_AREA_MARGIN); ++ QPoint pos = e->pos(); + const QModelIndexList indexes = selectedIndexes(); + for(const QModelIndex& indx : indexes) { + if(auto file = proxyModel_->fileInfoFromIndex(indx)) { +@@ -1305,9 +1306,9 @@ void DesktopWindow::childDropEvent(QDrop + auto delegate = static_cast(listView_->itemDelegateForColumn(0)); + auto grid = delegate->itemSize(); + QRect workArea = qApp->desktop()->availableGeometry(screenNum_); +- workArea.adjust(12, 12, -12, -12); ++ workArea.adjust(WORK_AREA_MARGIN, WORK_AREA_MARGIN, -WORK_AREA_MARGIN, -WORK_AREA_MARGIN); + const QString desktopDir = XdgDir::readDesktopDir() + QString(QLatin1String("/")); +- QPoint pos = mapFromGlobal(e->pos()); ++ QPoint pos = e->pos(); + const QList urlList = mimeData->urls(); + for(const QUrl& url : urlList) { + QString name = url.fileName(); +@@ -1352,12 +1353,10 @@ void DesktopWindow::stickToPosition(cons + } + + void DesktopWindow::alignToGrid(QPoint& pos, const QPoint& topLeft, const QSize& grid, const int spacing) { +- qreal w = qAbs((qreal)pos.x() - (qreal)topLeft.x()) +- / (qreal)(grid.width() + spacing); +- qreal h = qAbs((qreal)pos.y() - (qreal)topLeft.y()) +- / (qreal)(grid.height() + spacing); +- pos.setX(topLeft.x() + qRound(w) * (grid.width() + spacing)); +- pos.setY(topLeft.y() + qRound(h) * (grid.height() + spacing)); ++ int w = qAbs(pos.x() - topLeft.x()) / (grid.width() + spacing); ++ int h = qAbs(pos.y() - topLeft.y()) / (grid.height() + spacing); ++ pos.setX(topLeft.x() + w * (grid.width() + spacing)); ++ pos.setY(topLeft.y() + h * (grid.height() + spacing)); + } + + void DesktopWindow::closeEvent(QCloseEvent* event) { diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..af4e225 --- /dev/null +++ b/debian/patches/series @@ -0,0 +1,2 @@ +polish-dnd-1.patch +polish-dnd-2.patch