File indexing completed on 2025-01-19 12:59:16
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 }