File indexing completed on 2024-05-05 04:19:15

0001 // vim: set tabstop=4 shiftwidth=4 expandtab:
0002 /*
0003 Gwenview: an image viewer
0004 Copyright 2009 Aurélien Gâteau <agateau@kde.org>
0005 
0006 This program is free software; you can redistribute it and/or
0007 modify it under the terms of the GNU General Public License
0008 as published by the Free Software Foundation; either version 2
0009 of the License, or (at your option) any later version.
0010 
0011 This program is distributed in the hope that it will be useful,
0012 but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014 GNU General Public License for more details.
0015 
0016 You should have received a copy of the GNU General Public License
0017 along with this program; if not, write to the Free Software
0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
0019 
0020 */
0021 // Self
0022 #include "folderviewcontextmanageritem.h"
0023 
0024 // Qt
0025 #include <QDir>
0026 #include <QDragEnterEvent>
0027 #include <QHeaderView>
0028 #include <QMimeData>
0029 #include <QTreeView>
0030 
0031 // KF
0032 #include <KUrlMimeData>
0033 
0034 // Local
0035 #include "fileoperations.h"
0036 #include "gwenview_app_debug.h"
0037 #include "lib/touch/touch_helper.h"
0038 #include <lib/contextmanager.h>
0039 #include <lib/eventwatcher.h>
0040 #include <lib/scrollerutils.h>
0041 
0042 namespace Gwenview
0043 {
0044 /**
0045  * This treeview accepts url drops
0046  */
0047 class UrlDropTreeView : public QTreeView
0048 {
0049 public:
0050     explicit UrlDropTreeView(QWidget *parent = nullptr)
0051         : QTreeView(parent)
0052     {
0053     }
0054 
0055 protected:
0056     void dragEnterEvent(QDragEnterEvent *event) override
0057     {
0058         QAbstractItemView::dragEnterEvent(event);
0059         setDirtyRegion(mDropRect);
0060         if (event->mimeData()->hasUrls()) {
0061             event->acceptProposedAction();
0062         }
0063     }
0064 
0065     void dragMoveEvent(QDragMoveEvent *event) override
0066     {
0067         QAbstractItemView::dragMoveEvent(event);
0068 
0069         const QModelIndex index = indexAt(event->pos());
0070 
0071         // This code has been copied from Dolphin
0072         // (panels/folders/paneltreeview.cpp)
0073         setDirtyRegion(mDropRect);
0074         mDropRect = visualRect(index);
0075         setDirtyRegion(mDropRect);
0076 
0077         if (index.isValid()) {
0078             event->acceptProposedAction();
0079         } else {
0080             event->ignore();
0081         }
0082     }
0083 
0084     void dropEvent(QDropEvent *event) override
0085     {
0086         const QList<QUrl> urlList = KUrlMimeData::urlsFromMimeData(event->mimeData());
0087         const QModelIndex index = indexAt(event->pos());
0088         if (!index.isValid()) {
0089             qCWarning(GWENVIEW_APP_LOG) << "Invalid index!";
0090             return;
0091         }
0092         const QUrl destUrl = static_cast<MODEL_CLASS *>(model())->urlForIndex(index);
0093         FileOperations::showMenuForDroppedUrls(this, urlList, destUrl);
0094     }
0095 
0096     bool viewportEvent(QEvent *event) override
0097     {
0098         if (event->type() == QEvent::TouchBegin) {
0099             return true;
0100         }
0101         const QPoint pos = Touch_Helper::simpleTapPosition(event);
0102         if (pos != QPoint(-1, -1)) {
0103             expand(indexAt(pos));
0104             Q_EMIT activated(indexAt(pos));
0105         }
0106 
0107         return QTreeView::viewportEvent(event);
0108     }
0109 
0110 private:
0111     QRect mDropRect;
0112 };
0113 
0114 FolderViewContextManagerItem::FolderViewContextManagerItem(ContextManager *manager)
0115     : AbstractContextManagerItem(manager)
0116 {
0117     mModel = nullptr;
0118 
0119     setupView();
0120 
0121     connect(contextManager(), &ContextManager::currentDirUrlChanged, this, &FolderViewContextManagerItem::slotCurrentDirUrlChanged);
0122 }
0123 
0124 void FolderViewContextManagerItem::slotCurrentDirUrlChanged(const QUrl &url)
0125 {
0126     if (url.isValid() && mUrlToSelect != url) {
0127         mUrlToSelect = url.adjusted(QUrl::StripTrailingSlash | QUrl::NormalizePathSegments);
0128         mExpandingIndex = QModelIndex();
0129     }
0130     if (!mView->isVisible()) {
0131         return;
0132     }
0133 
0134     expandToSelectedUrl();
0135 }
0136 
0137 void FolderViewContextManagerItem::expandToSelectedUrl()
0138 {
0139     if (!mUrlToSelect.isValid()) {
0140         return;
0141     }
0142 
0143     if (!mModel) {
0144         setupModel();
0145     }
0146 
0147     const QModelIndex index = findClosestIndex(mExpandingIndex, mUrlToSelect);
0148     if (!index.isValid()) {
0149         return;
0150     }
0151     mExpandingIndex = index;
0152 
0153     QUrl url = mModel->urlForIndex(mExpandingIndex);
0154     if (mUrlToSelect == url) {
0155         // We found our url
0156         QItemSelectionModel *selModel = mView->selectionModel();
0157         selModel->setCurrentIndex(mExpandingIndex, QItemSelectionModel::ClearAndSelect);
0158         mView->scrollTo(mExpandingIndex);
0159         mUrlToSelect = QUrl();
0160         mExpandingIndex = QModelIndex();
0161     } else {
0162         // We found a parent of our url
0163         mView->setExpanded(mExpandingIndex, true);
0164     }
0165 }
0166 
0167 void FolderViewContextManagerItem::slotRowsInserted(const QModelIndex &parentIndex, int /*start*/, int /*end*/)
0168 {
0169     // Can't trigger the case where parentIndex is invalid, but it most
0170     // probably happen when root items are created. In this case we trigger
0171     // expandToSelectedUrl without checking the url.
0172     // See bug #191771
0173     if (!parentIndex.isValid() || mModel->urlForIndex(parentIndex).isParentOf(mUrlToSelect)) {
0174         mExpandingIndex = parentIndex;
0175         // Hack because otherwise indexes are not in correct order!
0176         QMetaObject::invokeMethod(this, &FolderViewContextManagerItem::expandToSelectedUrl, Qt::QueuedConnection);
0177     }
0178 }
0179 
0180 void FolderViewContextManagerItem::slotActivated(const QModelIndex &index)
0181 {
0182     if (!index.isValid()) {
0183         return;
0184     }
0185 
0186     QUrl url = mModel->urlForIndex(index);
0187     Q_EMIT urlChanged(url);
0188 }
0189 
0190 void FolderViewContextManagerItem::setupModel()
0191 {
0192     mModel = new MODEL_CLASS(this);
0193     mView->setModel(mModel);
0194 #ifndef USE_PLACETREE
0195     for (int col = 1; col <= mModel->columnCount(); ++col) {
0196         mView->header()->setSectionHidden(col, true);
0197     }
0198     mModel->dirLister()->openUrl(QUrl("/"));
0199 #endif
0200     QObject::connect(mModel, &MODEL_CLASS::rowsInserted, this, &FolderViewContextManagerItem::slotRowsInserted);
0201 }
0202 
0203 void FolderViewContextManagerItem::setupView()
0204 {
0205     mView = new UrlDropTreeView;
0206     mView->setEditTriggers(QAbstractItemView::NoEditTriggers);
0207     mView->setAcceptDrops(true);
0208     mView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
0209     mView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
0210 
0211     // Necessary to get the drop target highlighted
0212     mView->viewport()->setAttribute(Qt::WA_Hover);
0213 
0214     mView->setHeaderHidden(true);
0215 
0216     // This is tricky: QTreeView header has stretchLastSection set to true.
0217     // In this configuration, the header gets quite wide and cause an
0218     // horizontal scrollbar to appear.
0219     // To avoid this, set stretchLastSection to false and resizeMode to
0220     // Stretch (we still want the column to take the full width of the
0221     // widget).
0222     mView->header()->setStretchLastSection(false);
0223     mView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
0224 
0225     ScrollerUtils::setQScroller(mView->viewport());
0226 
0227     setWidget(mView);
0228     QObject::connect(mView, &QTreeView::activated, this, &FolderViewContextManagerItem::slotActivated);
0229     EventWatcher::install(mView, QEvent::Show, this, SLOT(expandToSelectedUrl()));
0230 }
0231 
0232 QModelIndex FolderViewContextManagerItem::findClosestIndex(const QModelIndex &parent, const QUrl &wantedUrl)
0233 {
0234     Q_ASSERT(mModel);
0235     QModelIndex index = parent;
0236     if (!index.isValid()) {
0237         index = findRootIndex(wantedUrl);
0238         if (!index.isValid()) {
0239             return {};
0240         }
0241     }
0242 
0243     QUrl url = mModel->urlForIndex(index);
0244     if (!url.isParentOf(wantedUrl)) {
0245         qCWarning(GWENVIEW_APP_LOG) << url << "is not a parent of" << wantedUrl << "!";
0246         return {};
0247     }
0248 
0249     QString relativePath = QDir(url.path()).relativeFilePath(wantedUrl.path());
0250     QModelIndex lastFoundIndex = index;
0251     const QStringList relativePathList = relativePath.split(QDir::separator(), Qt::SkipEmptyParts);
0252     for (const QString &pathPart : relativePathList) {
0253         bool found = false;
0254         for (int row = 0; row < mModel->rowCount(lastFoundIndex); ++row) {
0255             QModelIndex index = mModel->index(row, 0, lastFoundIndex);
0256             if (index.data().toString() == pathPart) {
0257                 // FIXME: Check encoding
0258                 found = true;
0259                 lastFoundIndex = index;
0260                 break;
0261             }
0262         }
0263         if (!found) {
0264             break;
0265         }
0266     }
0267     return lastFoundIndex;
0268 }
0269 
0270 QModelIndex FolderViewContextManagerItem::findRootIndex(const QUrl &wantedUrl)
0271 {
0272     QModelIndex matchIndex;
0273     int matchUrlLength = 0;
0274     for (int row = 0; row < mModel->rowCount(); ++row) {
0275         QModelIndex index = mModel->index(row, 0);
0276         QUrl url = mModel->urlForIndex(index);
0277         int urlLength = url.url().length();
0278         if (url.isParentOf(wantedUrl) && urlLength > matchUrlLength) {
0279             matchIndex = index;
0280             matchUrlLength = urlLength;
0281         }
0282     }
0283     if (!matchIndex.isValid()) {
0284         qCWarning(GWENVIEW_APP_LOG) << "Found no root index for" << wantedUrl;
0285     }
0286     return matchIndex;
0287 }
0288 
0289 } // namespace
0290 
0291 #include "moc_folderviewcontextmanageritem.cpp"