File indexing completed on 2024-03-24 15:48:01

0001 /* This file is part of the KDEproject
0002    Copyright (C) 2000 David Faure <faure@kde.org>
0003                  2000 Carsten Pfeiffer <pfeiffer@kde.org>
0004 
0005    This library is free software; you can redistribute it and/or
0006    modify it under the terms of the GNU Library General Public
0007    License version 2 as published by the Free Software Foundation.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LIB.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017    Boston, MA 02110-1301, USA.
0018 */
0019 
0020 #include "filetreeview.h"
0021 
0022 #include <stdlib.h>
0023 
0024 #include <qevent.h>
0025 #include <qdir.h>
0026 #include <qapplication.h>
0027 #include <qtimer.h>
0028 #include <qmimedata.h>
0029 
0030 #include <kfileitem.h>
0031 
0032 #include "filetreeviewitem.h"
0033 #include "filetreebranch.h"
0034 #include "libfiletree_logging.h"
0035 
0036 
0037 #undef DEBUG_LISTING
0038 
0039 
0040 FileTreeView::FileTreeView(QWidget *parent)
0041     : QTreeWidget(parent)
0042 {
0043     setObjectName("FileTreeView");
0044     qCDebug(LIBFILETREE_LOG);
0045 
0046     setSelectionMode(QAbstractItemView::SingleSelection);
0047     setExpandsOnDoubleClick(false);         // we'll handle this ourselves
0048     setEditTriggers(QAbstractItemView::NoEditTriggers); // maybe changed later
0049 
0050     m_wantOpenFolderPixmaps = true;
0051     m_currentBeforeDropItem = nullptr;
0052     m_dropItem = nullptr;
0053     m_busyCount = 0;
0054 
0055     m_autoOpenTimer = new QTimer(this);
0056     m_autoOpenTimer->setInterval((QApplication::startDragTime() * 3) / 2);
0057     connect(m_autoOpenTimer, &QTimer::timeout, this, &FileTreeView::slotAutoOpenFolder);
0058 
0059     // The slotExecuted only opens a path, while the slotExpanded populates it
0060     connect(this, &QTreeWidget::itemActivated, this, &FileTreeView::slotExecuted);
0061     connect(this, &QTreeWidget::itemExpanded, this, &FileTreeView::slotExpanded);
0062     connect(this, &QTreeWidget::itemCollapsed, this, &FileTreeView::slotCollapsed);
0063     connect(this, &QTreeWidget::itemDoubleClicked, this, &FileTreeView::slotDoubleClicked);
0064     connect(this, &QTreeWidget::itemSelectionChanged, this, &FileTreeView::slotSelectionChanged);
0065     connect(this, &QTreeWidget::itemEntered, this, &FileTreeView::slotOnItem);
0066 
0067     connect(model(), &QAbstractItemModel::dataChanged, this, &FileTreeView::slotDataChanged);
0068 
0069     m_openFolderPixmap = QIcon::fromTheme("folder-open");
0070 }
0071 
0072 FileTreeView::~FileTreeView()
0073 {
0074     // We must make sure that the FileTreeTreeViewItems are deleted _before_ the
0075     // branches are deleted. Otherwise, the KFileItems would be destroyed
0076     // and the FileTreeViewItems will still hold dangling pointers to them.
0077     hide();
0078     clear();
0079 
0080     qDeleteAll(m_branches);
0081     m_branches.clear();
0082 }
0083 
0084 // This is used when dragging and dropping out of the view to somewhere else.
0085 QMimeData *FileTreeView::mimeData(const QList<QTreeWidgetItem *> items) const
0086 {
0087     QMimeData *mimeData = new QMimeData();
0088     QList<QUrl> urlList;
0089 
0090     for (QList<QTreeWidgetItem *>::const_iterator it = items.constBegin();
0091             it != items.constEnd(); ++it) {
0092         FileTreeViewItem *item = static_cast<FileTreeViewItem *>(*it);
0093 #ifdef DEBUG_LISTING
0094         qCDebug(LIBFILETREE_LOG) << item->url();
0095 #endif // DEBUG_LISTING
0096         urlList.append(item->url());
0097     }
0098 
0099     mimeData->setUrls(urlList);
0100     return (mimeData);
0101 }
0102 
0103 // Dragging and dropping into the view.
0104 void FileTreeView::setDropItem(QTreeWidgetItem *item)
0105 {
0106     if (item != nullptr) {
0107         m_dropItem = item;
0108         // TODO: make auto-open an option, don't start timer if not enabled
0109         m_autoOpenTimer->start();
0110     } else {
0111         m_dropItem = nullptr;
0112         m_autoOpenTimer->stop();
0113     }
0114 }
0115 
0116 void FileTreeView::dragEnterEvent(QDragEnterEvent *ev)
0117 {
0118     if (!ev->mimeData()->hasUrls()) {       // not an URL drag
0119         ev->ignore();
0120         return;
0121     }
0122 
0123     ev->acceptProposedAction();
0124 
0125     QList<QTreeWidgetItem *> items = selectedItems();
0126     m_currentBeforeDropItem = (items.count() > 0 ? items.first() : nullptr);
0127     setDropItem(itemAt(ev->pos()));
0128 }
0129 
0130 void FileTreeView::dragMoveEvent(QDragMoveEvent *ev)
0131 {
0132     if (!ev->mimeData()->hasUrls()) {       // not an URL drag
0133         ev->ignore();
0134         return;
0135     }
0136 
0137     QTreeWidgetItem *item = itemAt(ev->pos());
0138     if (item == nullptr || item->isDisabled()) {   // over a valid item?
0139         // no, ignore drops on it
0140         setDropItem(nullptr);              // clear drop item
0141         return;
0142     }
0143 
0144     //FileTreeViewItem *ftvi = static_cast<FileTreeViewItem *>(item);
0145     //if (!ftvi->isDir()) item = item->parent();    // if file, highlight parent dir
0146 
0147     setCurrentItem(item);               // temporarily select it
0148     if (item != m_dropItem) {
0149         setDropItem(item);    // changed, update drop item
0150     }
0151 
0152     ev->accept();
0153 }
0154 
0155 void FileTreeView::dragLeaveEvent(QDragLeaveEvent *ev)
0156 {
0157     if (m_currentBeforeDropItem != nullptr) {      // there was a current item
0158         // before the drag started
0159         setCurrentItem(m_currentBeforeDropItem);    // restore its selection
0160         scrollToItem(m_currentBeforeDropItem);
0161     } else if (m_dropItem != nullptr) {        // item selected by drag
0162         m_dropItem->setSelected(false);         // clear that selection
0163     }
0164 
0165     m_currentBeforeDropItem = nullptr;
0166     setDropItem(nullptr);
0167 }
0168 
0169 void FileTreeView::dropEvent(QDropEvent *ev)
0170 {
0171     if (!ev->mimeData()->hasUrls()) {       // not an URL drag
0172         ev->ignore();
0173         return;
0174     }
0175 
0176     if (m_dropItem == nullptr) {
0177         return;    // invalid drop target
0178     }
0179 
0180     FileTreeViewItem *item = static_cast<FileTreeViewItem *>(m_dropItem);
0181 #ifdef DEBUG_LISTING
0182     qCDebug(LIBFILETREE_LOG) << "onto" << item->url();
0183 #endif // DEBUG_LISTING
0184     setDropItem(nullptr);                   // stop timer now
0185                             // also clears m_dropItem!
0186     emit dropped(ev, item);
0187     ev->accept();
0188 }
0189 
0190 void FileTreeView::slotCollapsed(QTreeWidgetItem *tvi)
0191 {
0192     FileTreeViewItem *item = static_cast<FileTreeViewItem *>(tvi);
0193     if (item != nullptr && item->isDir()) {
0194         item->setIcon(0, itemIcon(item));
0195     }
0196 }
0197 
0198 void FileTreeView::slotExpanded(QTreeWidgetItem *tvi)
0199 {
0200     FileTreeViewItem *item = static_cast<FileTreeViewItem *>(tvi);
0201     if (item == nullptr) {
0202         return;
0203     }
0204 
0205 #ifdef DEBUG_LISTING
0206     qCDebug(LIBFILETREE_LOG) << item->text(0);
0207 #endif // DEBUG_LISTING
0208     FileTreeBranch *branch = item->branch();
0209 
0210     // Check if the branch needs to be populated now
0211     if (item->isDir() && branch != nullptr && item->childCount() == 0) {
0212 #ifdef DEBUG_LISTING
0213         qCDebug(LIBFILETREE_LOG) << "need to populate" << item->url();
0214 #endif // DEBUG_LISTING
0215         if (!branch->populate(item->url(), item)) {
0216             qCWarning(LIBFILETREE_LOG) << "Branch populate" << item->url() << "failed";
0217         }
0218     }
0219 
0220     // set pixmap for open folders
0221     if (item->isDir() && item->isExpanded()) {
0222         item->setIcon(0, itemIcon(item));
0223     }
0224 }
0225 
0226 // Called when an item is single- or double-clicked, according to the
0227 // configured selection model.
0228 //
0229 // If the item is a branch root, we don't want to expand/collapse it on
0230 // a single click, but just to select it.  An explicit double click will
0231 // do the expand/collapse.
0232 
0233 void FileTreeView::slotExecuted(QTreeWidgetItem *item)
0234 {
0235     if (item == nullptr) {
0236         return;
0237     }
0238 
0239     FileTreeViewItem *ftvi = static_cast<FileTreeViewItem *>(item);
0240     if (ftvi != nullptr && ftvi->isDir() && !ftvi->isRoot()) {
0241         item->setExpanded(!item->isExpanded());
0242     }
0243 }
0244 
0245 void FileTreeView::slotDoubleClicked(QTreeWidgetItem *item)
0246 {
0247     if (item == nullptr) {
0248         return;
0249     }
0250 
0251     FileTreeViewItem *ftvi = static_cast<FileTreeViewItem *>(item);
0252     if (ftvi != nullptr && ftvi->isRoot()) {
0253         item->setExpanded(!item->isExpanded());
0254     }
0255 }
0256 
0257 void FileTreeView::slotAutoOpenFolder()
0258 {
0259     m_autoOpenTimer->stop();
0260 
0261 #ifdef DEBUG_LISTING
0262     qCDebug(LIBFILETREE_LOG) << "children" << m_dropItem->childCount() << "expanded" << m_dropItem->isExpanded();
0263 #endif // DEBUG_LISTING
0264     if (m_dropItem->childCount() == 0) {
0265         return;                     // nothing to expand
0266     }
0267     if (m_dropItem->isExpanded()) {
0268         return;                     // already expanded
0269     }
0270 
0271     m_dropItem->setExpanded(true);          // expand the item
0272 }
0273 
0274 void FileTreeView::slotSelectionChanged()
0275 {
0276     if (m_dropItem != nullptr) {           // don't do this during the dragmove
0277     }
0278 }
0279 
0280 void FileTreeView::slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
0281 {
0282     if (topLeft.column() != 0) return;              // not the file name
0283     if (topLeft.row() != bottomRight.row()) return;     // not a single row
0284     if (topLeft.column() != bottomRight.column()) return;   // not a single column
0285 
0286     FileTreeViewItem *item = static_cast<FileTreeViewItem *>(itemFromIndex(topLeft));
0287     if (item->url().hasFragment()) return;      // ignore for sub-images
0288 
0289     QString oldName = item->url().fileName();
0290     QString newName = item->text(0);
0291 
0292     if (oldName == newName) return;         // no change of name
0293     if (newName.isEmpty()) return;          // no new name
0294 
0295     emit fileRenamed(item, newName);
0296     item->branch()->itemRenamed(item);          // update branch's item map
0297 }
0298 
0299 FileTreeBranch *FileTreeView::addBranch(const QUrl &path, const QString &name,
0300                                         bool showHidden)
0301 {
0302     const QIcon &folderPix = QIcon::fromTheme("inode-directory");
0303     return (addBranch(path, name, folderPix, showHidden));
0304 }
0305 
0306 FileTreeBranch *FileTreeView::addBranch(const QUrl &path, const QString &name,
0307                                         const QIcon &pix, bool showHidden)
0308 {
0309     qCDebug(LIBFILETREE_LOG) << path << name;
0310 
0311     /* Open a new branch */
0312     FileTreeBranch *newBranch = new FileTreeBranch(this, path, name, pix,
0313             showHidden);
0314     return (addBranch(newBranch));
0315 }
0316 
0317 FileTreeBranch *FileTreeView::addBranch(FileTreeBranch *newBranch)
0318 {
0319     connect(newBranch, &FileTreeBranch::populateStarted, this, &FileTreeView::slotStartAnimation);
0320     connect(newBranch, &FileTreeBranch::populateFinished, this, &FileTreeView::slotStopAnimation);
0321     connect(newBranch, &FileTreeBranch::newTreeViewItems, this, &FileTreeView::slotNewTreeViewItems);
0322 
0323     m_branches.append(newBranch);
0324     return (newBranch);
0325 }
0326 
0327 FileTreeBranch *FileTreeView::branch(const QString &searchName) const
0328 {
0329 #ifdef DEBUG_LISTING
0330     qCDebug(LIBFILETREE_LOG) << "searching for" << searchName;
0331 #endif // DEBUG_LISTING
0332     for (FileTreeBranchList::const_iterator it = m_branches.constBegin();
0333             it != m_branches.constEnd(); ++it) {
0334         FileTreeBranch *branch = (*it);
0335         QString bname = branch->name();
0336 #ifdef DEBUG_LISTING
0337         qCDebug(LIBFILETREE_LOG) << "branch" << bname;
0338 #endif // DEBUG_LISTING
0339         if (bname == searchName) {
0340 #ifdef DEBUG_LISTING
0341             qCDebug(LIBFILETREE_LOG) << "Found requested branch";
0342 #endif // DEBUG_LISTING
0343             return (branch);
0344         }
0345     }
0346 
0347     return (nullptr);
0348 }
0349 
0350 const FileTreeBranchList &FileTreeView::branches() const
0351 {
0352     return (m_branches);
0353 }
0354 
0355 bool FileTreeView::removeBranch(FileTreeBranch *branch)
0356 {
0357     if (m_branches.contains(branch)) {
0358         delete branch->root();
0359         m_branches.removeOne(branch);
0360         return (true);
0361     } else {
0362         return (false);
0363     }
0364 }
0365 
0366 void FileTreeView::setDirOnlyMode(FileTreeBranch *branch, bool bom)
0367 {
0368     if (branch != nullptr) {
0369         branch->setDirOnlyMode(bom);
0370     }
0371 }
0372 
0373 void FileTreeView::slotNewTreeViewItems(FileTreeBranch *branch, const FileTreeViewItemList &items)
0374 {
0375     if (branch == nullptr) {
0376         return;
0377     }
0378 #ifdef DEBUG_LISTING
0379     qCDebug(LIBFILETREE_LOG) << "items" << items.count();
0380 #endif // DEBUG_LISTING
0381 
0382     /* Sometimes it happens that new items should become selected, i.e. if the user
0383      * creates a new dir, he probably wants it to be selected. This can not be done
0384      * right after creating the directory or file, because it takes some time until
0385      * the item appears here in the treeview. Thus, the creation code sets the member
0386      * m_neUrlToSelect to the required url. If this url appears here, the item becomes
0387      * selected and the member nextUrlToSelect will be cleared.
0388      */
0389     if (!m_nextUrlToSelect.isEmpty()) {
0390         for (FileTreeViewItemList::const_iterator it = items.constBegin();
0391                 it != items.constEnd(); ++it) {
0392             QUrl url = (*it)->url();
0393 
0394             if (m_nextUrlToSelect.adjusted(QUrl::StripTrailingSlash|QUrl::NormalizePathSegments) ==
0395                 url.adjusted(QUrl::StripTrailingSlash|QUrl::NormalizePathSegments)) {
0396                 setCurrentItem(static_cast<QTreeWidgetItem *>(*it));
0397                 m_nextUrlToSelect = QUrl();
0398                 break;
0399             }
0400         }
0401     }
0402 }
0403 
0404 QIcon FileTreeView::itemIcon(FileTreeViewItem *item) const
0405 {
0406     QIcon pix;
0407 
0408     if (item != nullptr) {
0409         /* Check whether it is a branch root */
0410         FileTreeBranch *branch = item->branch();
0411         if (item == branch->root()) {
0412             pix = branch->pixmap();
0413             if (m_wantOpenFolderPixmaps && branch->root()->isExpanded()) {
0414                 pix = branch->openPixmap();
0415             }
0416         } else {
0417             // TODO: different modes, user Pixmaps ?
0418             pix = QIcon::fromTheme(item->fileItem()->iconName());
0419             /* Only if it is a dir and the user wants open dir pixmap and it is open,
0420              * change the fileitem's pixmap to the open folder pixmap. */
0421             if (item->isDir() && m_wantOpenFolderPixmaps) {
0422                 if (item->isExpanded()) {
0423                     pix = m_openFolderPixmap;
0424                 }
0425             }
0426         }
0427     }
0428 
0429     return (pix);
0430 }
0431 
0432 void FileTreeView::slotStartAnimation(FileTreeViewItem *item)
0433 {
0434     if (item == nullptr) {
0435         return;
0436     }
0437 #ifdef DEBUG_LISTING
0438     qCDebug(LIBFILETREE_LOG) << "for" << item->text(0);
0439 #endif // DEBUG_LISTING
0440 
0441     ++m_busyCount;
0442     setCursor(Qt::BusyCursor);
0443 }
0444 
0445 void FileTreeView::slotStopAnimation(FileTreeViewItem *item)
0446 {
0447     if (item == nullptr) {
0448         return;
0449     }
0450 #ifdef DEBUG_LISTING
0451     qCDebug(LIBFILETREE_LOG) << "for" << item->text(0);
0452 #endif // DEBUG_LISTING
0453     if (m_busyCount <= 0) {
0454         return;
0455     }
0456 
0457     --m_busyCount;
0458     if (m_busyCount == 0) {
0459         unsetCursor();
0460     }
0461 }
0462 
0463 FileTreeViewItem *FileTreeView::selectedFileTreeViewItem() const
0464 {
0465     QList<QTreeWidgetItem *> items = selectedItems();
0466     return (items.count() > 0 ? static_cast<FileTreeViewItem *>(items.first()) : nullptr);
0467 }
0468 
0469 const KFileItem *FileTreeView::selectedFileItem() const
0470 {
0471     FileTreeViewItem *item = selectedFileTreeViewItem();
0472     return (item == nullptr ? nullptr : item->fileItem());
0473 }
0474 
0475 QUrl FileTreeView::selectedUrl() const
0476 {
0477     FileTreeViewItem *item = selectedFileTreeViewItem();
0478     return (item != nullptr ? item->url() : QUrl());
0479 }
0480 
0481 FileTreeViewItem *FileTreeView::highlightedFileTreeViewItem() const
0482 {
0483     QList<QTreeWidgetItem *> items = selectedItems();
0484     if (items.isEmpty()) return (nullptr);
0485     return (static_cast<FileTreeViewItem *>(items.first()));
0486 }
0487 
0488 const KFileItem *FileTreeView::highlightedFileItem() const
0489 {
0490     FileTreeViewItem *item = highlightedFileTreeViewItem();
0491     return (item == nullptr ? nullptr : item->fileItem());
0492 }
0493 
0494 QUrl FileTreeView::highlightedUrl() const
0495 {
0496     FileTreeViewItem *item = highlightedFileTreeViewItem();
0497     return (item != nullptr ? item->url() : QUrl());
0498 }
0499 
0500 void FileTreeView::slotOnItem(QTreeWidgetItem *item)
0501 {
0502     FileTreeViewItem *i = static_cast<FileTreeViewItem *>(item);
0503     if (i != nullptr) emit onItem(i->url().url(QUrl::PreferLocalFile));
0504 }
0505 
0506 FileTreeViewItem *FileTreeView::findItemInBranch(const QString &branchName, const QString &relUrl) const
0507 {
0508     FileTreeBranch *br = branch(branchName);
0509     return (findItemInBranch(br, relUrl));
0510 }
0511 
0512 FileTreeViewItem *FileTreeView::findItemInBranch(FileTreeBranch *branch, const QString &relPath) const
0513 {
0514     if (branch==nullptr) return (nullptr);          // no branch to search
0515 
0516     FileTreeViewItem *ret;
0517     if (relPath.isEmpty() || relPath=="/") ret = branch->root();
0518     else ret = branch->findItemByPath(relPath);
0519     return (ret);
0520 }
0521 
0522 bool FileTreeView::showFolderOpenPixmap() const
0523 {
0524     return (m_wantOpenFolderPixmaps);
0525 }
0526 
0527 void FileTreeView::setShowFolderOpenPixmap(bool showIt)
0528 {
0529     m_wantOpenFolderPixmaps = showIt;
0530 }
0531 
0532 void FileTreeView::slotSetNextUrlToSelect(const QUrl &url)
0533 {
0534     m_nextUrlToSelect = url;
0535 }