File indexing completed on 2024-04-28 05:49:09

0001 /*  This file is part of the Kate project.
0002  *
0003  *  SPDX-FileCopyrightText: 2012 Christoph Cullmann <cullmann@kde.org>
0004  *
0005  *  SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 
0008 #include "kateprojectviewtree.h"
0009 #include "kateproject.h"
0010 #include "kateprojectfiltermodel.h"
0011 #include "kateprojectitem.h"
0012 #include "kateprojectpluginview.h"
0013 #include "kateprojecttreeviewcontextmenu.h"
0014 #include "ktexteditor_utils.h"
0015 
0016 #include <KTextEditor/Document>
0017 #include <KTextEditor/MainWindow>
0018 #include <KTextEditor/View>
0019 
0020 #include <QContextMenuEvent>
0021 #include <QDir>
0022 
0023 #include <KLocalizedString>
0024 
0025 KateProjectViewTree::KateProjectViewTree(KateProjectPluginView *pluginView, KateProject *project)
0026     : m_pluginView(pluginView)
0027     , m_project(project)
0028 {
0029     /**
0030      * default style
0031      */
0032     setHeaderHidden(true);
0033     setEditTriggers(QAbstractItemView::NoEditTriggers);
0034     setAllColumnsShowFocus(true);
0035     setIndentation(12);
0036 
0037     setDragDropMode(QAbstractItemView::DropOnly);
0038     setDragDropOverwriteMode(false);
0039 
0040     /**
0041      * attach view => project
0042      * do this once, model is stable for whole project life time
0043      * kill selection model
0044      * create sort proxy model
0045      */
0046     QItemSelectionModel *m = selectionModel();
0047 
0048     KateProjectFilterProxyModel *sortModel = new KateProjectFilterProxyModel(this);
0049 
0050     // sortModel->setFilterRole(SortFilterRole);
0051     // sortModel->setSortRole(SortFilterRole);
0052     sortModel->setRecursiveFilteringEnabled(true);
0053     sortModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
0054     sortModel->setSortCaseSensitivity(Qt::CaseInsensitive);
0055     sortModel->setSourceModel(m_project->model());
0056     setModel(sortModel);
0057     delete m;
0058 
0059     /**
0060      * connect needed signals
0061      * we use activated + clicked as we want "always" single click activation + keyboard focus / enter working
0062      */
0063     connect(this, &KateProjectViewTree::activated, this, &KateProjectViewTree::slotClicked);
0064     connect(this, &KateProjectViewTree::clicked, this, &KateProjectViewTree::slotClicked);
0065     connect(m_project, &KateProject::modelChanged, this, &KateProjectViewTree::slotModelChanged);
0066 
0067     /**
0068      * trigger once some slots
0069      */
0070     slotModelChanged();
0071 }
0072 
0073 KateProjectViewTree::~KateProjectViewTree()
0074 {
0075 }
0076 
0077 void KateProjectViewTree::selectFile(const QString &file)
0078 {
0079     /**
0080      * get item if any
0081      */
0082     QStandardItem *item = m_project->itemForFile(file);
0083     if (!item) {
0084         return;
0085     }
0086 
0087     /**
0088      * select it
0089      */
0090     QModelIndex index = static_cast<QSortFilterProxyModel *>(model())->mapFromSource(m_project->model()->indexFromItem(item));
0091     scrollTo(index, QAbstractItemView::EnsureVisible);
0092     selectionModel()->setCurrentIndex(index, QItemSelectionModel::Clear | QItemSelectionModel::Select);
0093 }
0094 
0095 void KateProjectViewTree::openSelectedDocument()
0096 {
0097     /**
0098      * anything selected?
0099      */
0100     QModelIndexList selecteStuff = selectedIndexes();
0101     if (selecteStuff.isEmpty()) {
0102         return;
0103     }
0104 
0105     /**
0106      * we only handle files here!
0107      */
0108     if (selecteStuff[0].data(KateProjectItem::TypeRole).toInt() != KateProjectItem::File) {
0109         return;
0110     }
0111 
0112     /**
0113      * open document for first element, if possible
0114      */
0115     QString filePath = selecteStuff[0].data(Qt::UserRole).toString();
0116     if (!filePath.isEmpty()) {
0117         m_pluginView->mainWindow()->openUrl(QUrl::fromLocalFile(filePath));
0118     }
0119 }
0120 
0121 void KateProjectViewTree::addFile(const QModelIndex &idx, const QString &fileName)
0122 {
0123     auto proxyModel = static_cast<QSortFilterProxyModel *>(model());
0124     auto index = proxyModel->mapToSource(idx);
0125     auto item = m_project->model()->itemFromIndex(index);
0126 
0127     const QString fullFileName = index.data(Qt::UserRole).toString() + QLatin1Char('/') + fileName;
0128 
0129     /**
0130      * Create an actual file on disk
0131      */
0132     QFile f(fullFileName);
0133     bool created = f.open(QIODevice::WriteOnly);
0134     if (!created) {
0135         const auto icon = QIcon::fromTheme(QStringLiteral("document-new"));
0136         Utils::showMessage(i18n("Failed to create file: %1, Error: %2", fileName, f.errorString()), icon, i18n("Project"), MessageType::Error);
0137         return;
0138     }
0139 
0140     KateProjectItem *i = new KateProjectItem(KateProjectItem::File, fileName);
0141     i->setData(fullFileName, Qt::UserRole);
0142     item->appendRow(i);
0143     m_project->addFile(fullFileName, i);
0144     item->sortChildren(0);
0145 }
0146 
0147 void KateProjectViewTree::addDirectory(const QModelIndex &idx, const QString &name)
0148 {
0149     auto proxyModel = static_cast<QSortFilterProxyModel *>(model());
0150     auto index = proxyModel->mapToSource(idx);
0151     auto item = m_project->model()->itemFromIndex(index);
0152     const QString fullDirName = index.data(Qt::UserRole).toString() + QLatin1Char('/') + name;
0153 
0154     QDir dir(index.data(Qt::UserRole).toString());
0155     if (!dir.mkdir(name)) {
0156         const auto icon = QIcon::fromTheme(QStringLiteral("folder-new"));
0157         Utils::showMessage(i18n("Failed to create dir: %1", name), icon, i18n("Project"), MessageType::Error);
0158         return;
0159     }
0160 
0161     KateProjectItem *i = new KateProjectItem(KateProjectItem::Directory, name);
0162     i->setData(fullDirName, Qt::UserRole);
0163     item->appendRow(i);
0164     item->sortChildren(0);
0165 }
0166 
0167 void KateProjectViewTree::removeFile(const QModelIndex &idx, const QString &fullFilePath)
0168 {
0169     auto proxyModel = static_cast<QSortFilterProxyModel *>(model());
0170     auto index = proxyModel->mapToSource(idx);
0171     auto item = m_project->model()->itemFromIndex(index);
0172     if (!item) {
0173         return;
0174     }
0175     QStandardItem *parent = item->parent();
0176 
0177     /**
0178      * Delete file
0179      */
0180     QFile file(fullFilePath);
0181     if (file.remove()) //.moveToTrash()
0182     {
0183         if (parent != nullptr) {
0184             parent->removeRow(item->row());
0185             parent->sortChildren(0);
0186         } else {
0187             m_project->model()->removeRow(item->row());
0188             m_project->model()->sort(0);
0189         }
0190         m_project->removeFile(fullFilePath);
0191     }
0192 }
0193 
0194 void KateProjectViewTree::openTerminal(const QString &dirPath)
0195 {
0196     m_pluginView->openTerminal(dirPath, m_project);
0197 }
0198 
0199 void KateProjectViewTree::slotClicked(const QModelIndex &index)
0200 {
0201     /**
0202      * open document, if any usable user data
0203      */
0204     const QString filePath = index.data(Qt::UserRole).toString();
0205     if (!filePath.isEmpty()) {
0206         /**
0207          * normal file? => just trigger open of it
0208          */
0209         if (index.data(KateProjectItem::TypeRole).toInt() == KateProjectItem::File) {
0210             m_pluginView->mainWindow()->openUrl(QUrl::fromLocalFile(filePath));
0211             selectionModel()->setCurrentIndex(index, QItemSelectionModel::Clear | QItemSelectionModel::Select);
0212             return;
0213         }
0214 
0215         /**
0216          * linked project? => switch the current active project
0217          */
0218         if (index.data(KateProjectItem::TypeRole).toInt() == KateProjectItem::LinkedProject) {
0219             m_pluginView->switchToProject(QDir(filePath));
0220             return;
0221         }
0222     }
0223 }
0224 
0225 void KateProjectViewTree::slotModelChanged()
0226 {
0227     /**
0228      * model was updated
0229      * perhaps we need to highlight again new file
0230      */
0231     KTextEditor::View *activeView = m_pluginView->mainWindow()->activeView();
0232     if (activeView && activeView->document()->url().isLocalFile()) {
0233         selectFile(activeView->document()->url().toLocalFile());
0234     }
0235 }
0236 
0237 void KateProjectViewTree::contextMenuEvent(QContextMenuEvent *event)
0238 {
0239     /**
0240      * get path file path or don't do anything
0241      */
0242     QModelIndex index = selectionModel()->currentIndex();
0243     QString filePath = index.data(Qt::UserRole).toString();
0244     if (filePath.isEmpty()) {
0245         QTreeView::contextMenuEvent(event);
0246         return;
0247     }
0248 
0249     KateProjectTreeViewContextMenu menu;
0250     menu.exec(filePath, index, viewport()->mapToGlobal(event->pos()), this);
0251 
0252     event->accept();
0253 }
0254 
0255 #include "moc_kateprojectviewtree.cpp"