File indexing completed on 2024-05-05 04:39:44

0001 /*
0002     SPDX-FileCopyrightText: 2005 Adam Treat <treat@kde.org>
0003     SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kdevdocumentview.h"
0009 #include "kdevdocumentviewplugin.h"
0010 #include "kdevdocumentmodel.h"
0011 
0012 #include <QAction>
0013 #include <QContextMenuEvent>
0014 #include <QFileInfo>
0015 #include <QHeaderView>
0016 #include <QMenu>
0017 #include <QSortFilterProxyModel>
0018 
0019 #include <KLocalizedString>
0020 #include <KStandardAction>
0021 
0022 #include "kdevdocumentselection.h"
0023 #include "kdevdocumentviewdelegate.h"
0024 
0025 #include <interfaces/contextmenuextension.h>
0026 #include <interfaces/icore.h>
0027 #include <interfaces/idocumentcontroller.h>
0028 #include <interfaces/iplugincontroller.h>
0029 #include <interfaces/iproject.h>
0030 #include <interfaces/iprojectcontroller.h>
0031 #include <interfaces/context.h>
0032 #include <interfaces/idocument.h>
0033 
0034 #include <widgetcolorizer.h>
0035 #include <path.h>
0036 
0037 using namespace KDevelop;
0038 
0039 KDevDocumentView::KDevDocumentView( KDevDocumentViewPlugin *plugin, QWidget *parent )
0040     : QTreeView( parent ),
0041         m_plugin( plugin )
0042 {
0043     connect(ICore::self()->projectController(), &IProjectController::projectOpened,
0044             this, &KDevDocumentView::updateProjectPaths);
0045     connect(ICore::self()->projectController(), &IProjectController::projectClosed,
0046             this, &KDevDocumentView::updateProjectPaths);
0047 
0048     m_documentModel = new KDevDocumentModel(this);
0049 
0050     m_delegate = new KDevDocumentViewDelegate( this );
0051 
0052     m_proxy = new QSortFilterProxyModel( this );
0053     m_proxy->setSourceModel( m_documentModel );
0054     m_proxy->setDynamicSortFilter( true );
0055     m_proxy->setSortCaseSensitivity( Qt::CaseInsensitive );
0056     m_proxy->sort(0);
0057 
0058     m_selectionModel = new KDevDocumentSelection( m_proxy );
0059 
0060     setModel( m_proxy );
0061     setSelectionModel( m_selectionModel );
0062     setItemDelegate( m_delegate );
0063 
0064     setObjectName( i18n( "Documents" ) );
0065 
0066     setWindowIcon( QIcon::fromTheme( QStringLiteral( "document-multiple" ), windowIcon() ) );
0067     setWindowTitle(i18nc("@title:window", "Documents"));
0068 
0069     setFocusPolicy( Qt::NoFocus );
0070     setIndentation(10);
0071 
0072     header()->hide();
0073 
0074     setSelectionBehavior( QAbstractItemView::SelectRows );
0075     setSelectionMode( QAbstractItemView::ExtendedSelection );
0076 
0077     updateProjectPaths();
0078 }
0079 
0080 KDevDocumentView::~KDevDocumentView()
0081 {}
0082 
0083 KDevDocumentViewPlugin *KDevDocumentView::plugin() const
0084 {
0085     return m_plugin;
0086 }
0087 
0088 void KDevDocumentView::mousePressEvent( QMouseEvent * event )
0089 {
0090     QModelIndex proxyIndex = indexAt( event->pos() );
0091     QModelIndex index = m_proxy->mapToSource( proxyIndex );
0092 
0093     if (event->modifiers() == Qt::NoModifier) {
0094         const bool actionOpen = event->button() == Qt::LeftButton;
0095         const bool actionClose = event->button() == Qt::MiddleButton;
0096         const bool action = actionOpen || actionClose;
0097 
0098         if (action) {
0099             if (proxyIndex.parent().isValid()) {
0100                 // this is a document item
0101                 KDevelop::IDocumentController* dc = m_plugin->core()->documentController();
0102                 QUrl documentUrl = static_cast<KDevDocumentItem*>(m_documentModel->itemFromIndex(index))->fileItem()->url();
0103                 KDevelop::IDocument *doc = dc->documentForUrl(documentUrl);
0104 
0105                 // Try to open a document by url.
0106                 if (actionOpen) {
0107                     if (doc != dc->activeDocument()) {
0108                         dc->openDocument(documentUrl);
0109                         return;
0110                     }
0111                 }
0112                 // Try to close a document.
0113                 else if (actionClose) {
0114                     if (doc) {
0115                         doc->close();
0116                         return;
0117                     }
0118                 }
0119             } else if (actionOpen) {
0120                 // this is a folder item
0121                 setExpanded(proxyIndex, !isExpanded(proxyIndex));
0122                 return;
0123             }
0124         }
0125     }
0126 
0127     QTreeView::mousePressEvent( event );
0128 }
0129 
0130 template<typename F> void KDevDocumentView::visitItems(F f, bool selectedItems)
0131 {
0132     KDevelop::IDocumentController* dc = m_plugin->core()->documentController();
0133     const QList<QUrl> docs = selectedItems ? m_selectedDocs : m_unselectedDocs;
0134 
0135     for (const QUrl& url : docs) {
0136        KDevelop::IDocument* doc = dc->documentForUrl(url);
0137        if (doc) f(doc);
0138     }
0139 }
0140 
0141 namespace
0142 {
0143 class DocSaver
0144 {
0145 public: void operator()(KDevelop::IDocument* doc) { doc->save(); }
0146 };
0147 class DocCloser
0148 {
0149 public: void operator()(KDevelop::IDocument* doc) { doc->close(); }
0150 };
0151 class DocReloader
0152 {
0153 public: void operator()(KDevelop::IDocument* doc) { doc->reload(); }
0154 };
0155 }
0156 
0157 void KDevDocumentView::saveSelected()
0158 {
0159     visitItems(DocSaver(), true);
0160 }
0161 
0162 void KDevDocumentView::closeSelected()
0163 {
0164     visitItems(DocCloser(), true);
0165 }
0166 
0167 void KDevDocumentView::closeUnselected()
0168 {
0169     visitItems(DocCloser(), false);
0170 }
0171 
0172 void KDevDocumentView::reloadSelected()
0173 {
0174     visitItems(DocReloader(), true);
0175 }
0176 
0177 void KDevDocumentView::contextMenuEvent( QContextMenuEvent * event )
0178 {
0179     QModelIndex proxyIndex = indexAt( event->pos() );
0180     // for now, ignore clicks on empty space or folder items
0181     if (!proxyIndex.isValid() || !proxyIndex.parent().isValid()) {
0182         return;
0183     }
0184 
0185     updateSelectedDocs();
0186 
0187     if (!m_selectedDocs.isEmpty())
0188     {
0189         auto* ctxMenu = new QMenu(this);
0190 
0191         KDevelop::FileContext context(m_selectedDocs);
0192         const QList<KDevelop::ContextMenuExtension> extensions =
0193             m_plugin->core()->pluginController()->queryPluginsForContextMenuExtensions(&context, ctxMenu);
0194 
0195         QList<QAction*> vcsActions;
0196         QList<QAction*> fileActions;
0197         QList<QAction*> editActions;
0198         QList<QAction*> extensionActions;
0199         for (const KDevelop::ContextMenuExtension& ext : extensions) {
0200             fileActions += ext.actions(KDevelop::ContextMenuExtension::FileGroup);
0201             vcsActions += ext.actions(KDevelop::ContextMenuExtension::VcsGroup);
0202             editActions += ext.actions(KDevelop::ContextMenuExtension::EditGroup);
0203             extensionActions += ext.actions(KDevelop::ContextMenuExtension::ExtensionGroup);
0204         }
0205 
0206         appendActions(ctxMenu, fileActions);
0207 
0208         QAction* save = KStandardAction::save(this, SLOT(saveSelected()), ctxMenu);
0209         save->setEnabled(selectedDocHasChanges());
0210         ctxMenu->addAction(save);
0211         ctxMenu->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@action:inmenu", "Reload"), this, SLOT(reloadSelected()));
0212 
0213         appendActions(ctxMenu, editActions);
0214         appendActions(ctxMenu, vcsActions);
0215         appendActions(ctxMenu, extensionActions);
0216 
0217         ctxMenu->addAction(QIcon::fromTheme(QStringLiteral("document-close")), i18nc("@action:inmenu", "Close"), this, SLOT(closeSelected()));
0218         QAction* closeUnselected = ctxMenu->addAction(QIcon::fromTheme(QStringLiteral("document-close")), i18nc("@action:inmenu", "Close All Other"), this, SLOT(closeUnselected()));
0219         closeUnselected->setEnabled(!m_unselectedDocs.isEmpty());
0220 
0221         ctxMenu->exec(event->globalPos());
0222         delete ctxMenu;
0223     }
0224 }
0225 
0226 void KDevDocumentView::appendActions(QMenu* menu, const QList<QAction*>& actions)
0227 {
0228     for (QAction* act : actions) {
0229         menu->addAction(act);
0230     }
0231     menu->addSeparator();
0232 }
0233 
0234 bool KDevDocumentView::selectedDocHasChanges()
0235 {
0236     KDevelop::IDocumentController* dc = m_plugin->core()->documentController();
0237     for (const QUrl& url : qAsConst(m_selectedDocs)) {
0238         KDevelop::IDocument* doc = dc->documentForUrl(url);
0239         if (!doc) continue;
0240         if (doc->state() != KDevelop::IDocument::Clean)
0241         {
0242             return true;
0243         }
0244     }
0245     return false;
0246 }
0247 
0248 void KDevDocumentView::updateSelectedDocs()
0249 {
0250     m_selectedDocs.clear();
0251     m_unselectedDocs.clear();
0252 
0253     const QList<QStandardItem*> allItems = m_documentModel->findItems(QStringLiteral("*"), Qt::MatchWildcard | Qt::MatchRecursive);
0254     for (QStandardItem* item : allItems) {
0255         if (KDevFileItem* fileItem = static_cast<KDevDocumentItem*>(item)->fileItem()) {
0256             if (m_selectionModel->isSelected(m_proxy->mapFromSource(m_documentModel->indexFromItem(fileItem))))
0257                 m_selectedDocs << fileItem->url();
0258             else
0259                 m_unselectedDocs << fileItem->url();
0260         }
0261     }
0262 }
0263 
0264 void KDevDocumentView::activated( KDevelop::IDocument* document )
0265 {
0266     setCurrentIndex( m_proxy->mapFromSource( m_documentModel->indexFromItem( m_doc2index[ document ] ) ) );
0267 }
0268 
0269 void KDevDocumentView::saved( KDevelop::IDocument* )
0270 {
0271 }
0272 
0273 void KDevDocumentView::opened( KDevelop::IDocument* document )
0274 {
0275     const QString path = QFileInfo( document->url().path() ).path();
0276 
0277     KDevCategoryItem *categoryItem = m_documentModel->category( path );
0278     if ( !categoryItem )
0279     {
0280         categoryItem = new KDevCategoryItem( path );
0281         categoryItem->setUrl( document->url() );
0282         m_documentModel->insertRow( m_documentModel->rowCount(), categoryItem );
0283         setExpanded( m_proxy->mapFromSource( m_documentModel->indexFromItem( categoryItem ) ), false);
0284         updateCategoryItem( categoryItem );
0285     }
0286 
0287     if ( !categoryItem->file( document->url() ) )
0288     {
0289         auto* fileItem = new KDevFileItem( document->url() );
0290         categoryItem->setChild( categoryItem->rowCount(), fileItem );
0291         setCurrentIndex( m_proxy->mapFromSource( m_documentModel->indexFromItem( fileItem ) ) );
0292         m_doc2index[ document ] = fileItem;
0293     }
0294 }
0295 
0296 void KDevDocumentView::closed( KDevelop::IDocument* document )
0297 {
0298     KDevFileItem* file = m_doc2index[ document ];
0299     if ( !file )
0300         return;
0301 
0302     QStandardItem* categoryItem = file->parent();
0303 
0304     qDeleteAll(categoryItem->takeRow(m_documentModel->indexFromItem(file).row()));
0305 
0306     m_doc2index.remove(document);
0307 
0308     if ( categoryItem->hasChildren() )
0309         return;
0310 
0311     qDeleteAll(m_documentModel->takeRow(m_documentModel->indexFromItem(categoryItem).row()));
0312 
0313     doItemsLayout();
0314 }
0315 
0316 void KDevDocumentView::updateCategoryItem( KDevCategoryItem *item )
0317 {
0318     QString text = KDevelop::ICore::self()->projectController()->prettyFilePath(item->url(), KDevelop::IProjectController::FormatPlain);
0319     // remove trailing slash
0320     if (text.length() > 1) {
0321         text.chop(1);
0322     }
0323     item->setText(text);
0324 }
0325 
0326 void KDevDocumentView::updateProjectPaths()
0327 {
0328     const auto categoryList = m_documentModel->categoryList();
0329     for (KDevCategoryItem* it : categoryList) {
0330         updateCategoryItem( it );
0331     }
0332 }
0333 
0334 void KDevDocumentView::contentChanged( KDevelop::IDocument* )
0335 {
0336 }
0337 
0338 void KDevDocumentView::stateChanged( KDevelop::IDocument* document )
0339 {
0340     KDevDocumentItem * documentItem = m_doc2index[ document ];
0341 
0342     if ( documentItem && documentItem->documentState() != document->state() )
0343         documentItem->setDocumentState( document->state() );
0344 
0345     doItemsLayout();
0346 }
0347 
0348 void KDevDocumentView::documentUrlChanged( KDevelop::IDocument* document )
0349 {
0350     closed(document);
0351     opened(document);
0352 }
0353 
0354 void KDevDocumentView::drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const
0355 {
0356     if (WidgetColorizer::colorizeByProject()) {
0357         const auto url = index.data(KDevDocumentItem::UrlRole).toUrl();
0358         const auto project = ICore::self()->projectController()->findProjectForUrl(url);
0359         if (project) {
0360             const QColor color = WidgetColorizer::colorForId(qHash(project->path()), palette(), true);
0361             WidgetColorizer::drawBranches(this, painter, rect, index, color);
0362         }
0363     }
0364 
0365     QTreeView::drawBranches(painter, rect, index);
0366 }
0367 
0368 #include "moc_kdevdocumentview.cpp"