File indexing completed on 2024-05-19 12:23:50
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"