File indexing completed on 2024-05-05 05:52:19
0001 /* This file is part of the Kate project. 0002 * Based on the snippet plugin from KDevelop 4. 0003 * 0004 * SPDX-FileCopyrightText: 2007 Robert Gruber <rgruber@users.sourceforge.net> 0005 * SPDX-FileCopyrightText: 2010 Milian Wolff <mail@milianw.de> 0006 * SPDX-FileCopyrightText: 2012 Christoph Cullmann <cullmann@kde.org> 0007 * 0008 * SPDX-License-Identifier: LGPL-2.0-or-later 0009 */ 0010 0011 #include "snippetview.h" 0012 0013 #include "editrepository.h" 0014 #include "editsnippet.h" 0015 #include "katesnippetglobal.h" 0016 #include "snippet.h" 0017 #include "snippetrepository.h" 0018 #include "snippetstore.h" 0019 0020 #include <KAuthorized> 0021 #include <KLocalizedString> 0022 #include <KMessageBox> 0023 0024 #include <QContextMenuEvent> 0025 #include <QMenu> 0026 #include <QSortFilterProxyModel> 0027 0028 class SnippetFilterModel : public QSortFilterProxyModel 0029 { 0030 public: 0031 explicit SnippetFilterModel(QObject *parent = nullptr) 0032 : QSortFilterProxyModel(parent){}; 0033 bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override 0034 { 0035 auto index = sourceModel()->index(sourceRow, 0, sourceParent); 0036 auto item = SnippetStore::self()->itemFromIndex(index); 0037 if (!item) { 0038 return false; 0039 } 0040 auto snippet = Snippet::fromItem(item); 0041 if (!snippet) { 0042 return true; 0043 } 0044 return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); 0045 } 0046 }; 0047 0048 void SnippetView::setupActionsForWindow(QWidget *widget) 0049 { 0050 const auto &model = SnippetStore::self(); 0051 for (int i = 0; i < model->rowCount(); i++) { 0052 auto index = model->index(i, 0, QModelIndex()); 0053 auto item = model->itemFromIndex(index); 0054 auto repo = SnippetRepository::fromItem(item); 0055 if (!repo) { 0056 continue; 0057 } 0058 for (int j = 0; j < model->rowCount(index); j++) { 0059 auto item = model->itemFromIndex(model->index(j, 0, index)); 0060 auto snippet = Snippet::fromItem(item); 0061 if (!snippet) { 0062 continue; 0063 } 0064 snippet->registerActionForView(widget); 0065 } 0066 } 0067 } 0068 0069 SnippetView::SnippetView(KateSnippetGlobal *plugin, KTextEditor::MainWindow *mainWindow, QWidget *parent) 0070 : QWidget(parent) 0071 , Ui::SnippetViewBase() 0072 , m_plugin(plugin) 0073 { 0074 Ui::SnippetViewBase::setupUi(this); 0075 0076 setWindowTitle(i18n("Snippets")); 0077 setWindowIcon(QIcon::fromTheme(QStringLiteral("document-new"), windowIcon())); 0078 0079 snippetTree->setContextMenuPolicy(Qt::CustomContextMenu); 0080 snippetTree->viewport()->installEventFilter(this); 0081 connect(snippetTree, &QTreeView::customContextMenuRequested, this, &SnippetView::contextMenu); 0082 0083 m_proxy = new SnippetFilterModel(this); 0084 m_proxy->setFilterKeyColumn(0); 0085 m_proxy->setFilterCaseSensitivity(Qt::CaseInsensitive); 0086 m_proxy->setSourceModel(SnippetStore::self()); 0087 0088 connect(filterText, &KLineEdit::textChanged, m_proxy, &QSortFilterProxyModel::setFilterFixedString); 0089 0090 snippetTree->setModel(m_proxy); 0091 snippetTree->header()->hide(); 0092 0093 m_addRepoAction = new QAction(QIcon::fromTheme(QStringLiteral("folder-new")), i18n("Add Repository"), this); 0094 connect(m_addRepoAction, &QAction::triggered, this, &SnippetView::slotAddRepo); 0095 addAction(m_addRepoAction); 0096 m_editRepoAction = new QAction(QIcon::fromTheme(QStringLiteral("folder-txt")), i18n("Edit Repository"), this); 0097 connect(m_editRepoAction, &QAction::triggered, this, &SnippetView::slotEditRepo); 0098 addAction(m_editRepoAction); 0099 m_removeRepoAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Remove Repository"), this); 0100 connect(m_removeRepoAction, &QAction::triggered, this, &SnippetView::slotRemoveRepo); 0101 addAction(m_removeRepoAction); 0102 0103 const bool newStuffAllowed = KAuthorized::authorize(QStringLiteral("ghns")); 0104 0105 QAction *separator = new QAction(this); 0106 separator->setSeparator(true); 0107 addAction(separator); 0108 0109 m_addSnippetAction = new QAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Add Snippet"), this); 0110 connect(m_addSnippetAction, &QAction::triggered, this, &SnippetView::slotAddSnippet); 0111 addAction(m_addSnippetAction); 0112 m_editSnippetAction = new QAction(QIcon::fromTheme(QStringLiteral("document-edit")), i18n("Edit Snippet"), this); 0113 connect(m_editSnippetAction, &QAction::triggered, this, &SnippetView::slotEditSnippet); 0114 addAction(m_editSnippetAction); 0115 m_removeSnippetAction = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n("Remove Snippet"), this); 0116 connect(m_removeSnippetAction, &QAction::triggered, this, &SnippetView::slotRemoveSnippet); 0117 addAction(m_removeSnippetAction); 0118 0119 addAction(separator); 0120 0121 m_getNewStuffAction = new KNSWidgets::Action(i18n("Get New Snippets"), QStringLiteral(":/katesnippets/ktexteditor_codesnippets_core.knsrc"), this); 0122 m_getNewStuffAction->setVisible(newStuffAllowed); 0123 connect(m_getNewStuffAction, &KNSWidgets::Action::dialogFinished, this, [](const auto &changedEntries) { 0124 for (const auto &entry : changedEntries) { 0125 const auto uninstalledFiles = entry.uninstalledFiles(); 0126 for (const QString &path : uninstalledFiles) { 0127 if (path.endsWith(QLatin1String(".xml"))) { 0128 if (SnippetRepository *repo = SnippetStore::self()->repositoryForFile(path)) { 0129 repo->remove(); 0130 } 0131 } 0132 } 0133 const auto installedFiles = entry.installedFiles(); 0134 for (const QString &path : installedFiles) { 0135 if (path.endsWith(QLatin1String(".xml"))) { 0136 SnippetStore::self()->appendRow(new SnippetRepository(path)); 0137 } 0138 } 0139 } 0140 }); 0141 addAction(m_getNewStuffAction); 0142 0143 connect(snippetTree->selectionModel(), &QItemSelectionModel::selectionChanged, this, &SnippetView::validateActions); 0144 validateActions(); 0145 0146 connect(snippetTree->model(), &QAbstractItemModel::rowsInserted, this, [this, mainWindow]() { 0147 setupActionsForWindow(mainWindow->window()); 0148 }); 0149 0150 m_proxy->setDynamicSortFilter(true); 0151 m_proxy->sort(0, Qt::AscendingOrder); 0152 } 0153 0154 void SnippetView::validateActions() 0155 { 0156 QStandardItem *item = currentItem(); 0157 0158 Snippet *selectedSnippet = Snippet::fromItem(item); 0159 SnippetRepository *selectedRepo = SnippetRepository::fromItem(item); 0160 0161 m_addRepoAction->setEnabled(true); 0162 m_editRepoAction->setEnabled(selectedRepo); 0163 m_removeRepoAction->setEnabled(selectedRepo); 0164 0165 m_addSnippetAction->setEnabled(selectedRepo || selectedSnippet); 0166 m_editSnippetAction->setEnabled(selectedSnippet); 0167 m_removeSnippetAction->setEnabled(selectedSnippet); 0168 } 0169 0170 QStandardItem *SnippetView::currentItem() 0171 { 0172 /// TODO: support multiple selected items 0173 QModelIndex index = snippetTree->currentIndex(); 0174 index = m_proxy->mapToSource(index); 0175 return SnippetStore::self()->itemFromIndex(index); 0176 } 0177 0178 void SnippetView::slotSnippetClicked(const QModelIndex &index) 0179 { 0180 QStandardItem *item = SnippetStore::self()->itemFromIndex(m_proxy->mapToSource(index)); 0181 if (!item) { 0182 return; 0183 } 0184 0185 Snippet *snippet = Snippet::fromItem(item); 0186 if (!snippet) { 0187 return; 0188 } 0189 0190 m_plugin->insertSnippet(snippet); 0191 } 0192 0193 void SnippetView::contextMenu(const QPoint &pos) 0194 { 0195 QModelIndex index = snippetTree->indexAt(pos); 0196 index = m_proxy->mapToSource(index); 0197 QStandardItem *item = SnippetStore::self()->itemFromIndex(index); 0198 if (!item) { 0199 // User clicked into an empty place of the tree 0200 QMenu menu(this); 0201 0202 menu.addSection(i18n("Snippets")); 0203 0204 menu.addAction(m_addRepoAction); 0205 menu.addAction(m_getNewStuffAction); 0206 0207 menu.exec(snippetTree->mapToGlobal(pos)); 0208 } else if (Snippet *snippet = Snippet::fromItem(item)) { 0209 QMenu menu(this); 0210 menu.addSection(i18n("Snippet: %1", snippet->text())); 0211 0212 menu.addAction(m_editSnippetAction); 0213 menu.addAction(m_removeSnippetAction); 0214 0215 menu.exec(snippetTree->mapToGlobal(pos)); 0216 } else if (SnippetRepository *repo = SnippetRepository::fromItem(item)) { 0217 QMenu menu(this); 0218 menu.addSection(i18n("Repository: %1", repo->text())); 0219 0220 menu.addAction(m_addSnippetAction); 0221 menu.addSeparator(); 0222 0223 menu.addAction(m_editRepoAction); 0224 menu.addAction(m_removeRepoAction); 0225 0226 menu.exec(snippetTree->mapToGlobal(pos)); 0227 } 0228 } 0229 0230 void SnippetView::slotEditSnippet() 0231 { 0232 QStandardItem *item = currentItem(); 0233 if (!item) { 0234 return; 0235 } 0236 0237 Snippet *snippet = Snippet::fromItem(item); 0238 if (!snippet) { 0239 return; 0240 } 0241 0242 SnippetRepository *repo = SnippetRepository::fromItem(item->parent()); 0243 if (!repo) { 0244 return; 0245 } 0246 0247 EditSnippet dlg(repo, snippet, this); 0248 dlg.exec(); 0249 } 0250 0251 void SnippetView::slotAddSnippet() 0252 { 0253 QStandardItem *item = currentItem(); 0254 if (!item) { 0255 return; 0256 } 0257 0258 SnippetRepository *repo = SnippetRepository::fromItem(item); 0259 if (!repo) { 0260 repo = SnippetRepository::fromItem(item->parent()); 0261 if (!repo) { 0262 return; 0263 } 0264 } 0265 0266 EditSnippet dlg(repo, nullptr, this); 0267 dlg.exec(); 0268 } 0269 0270 void SnippetView::slotRemoveSnippet() 0271 { 0272 QStandardItem *item = currentItem(); 0273 if (!item) { 0274 return; 0275 } 0276 0277 SnippetRepository *repo = SnippetRepository::fromItem(item->parent()); 0278 if (!repo) { 0279 return; 0280 } 0281 0282 int ans = KMessageBox::warningContinueCancel(QApplication::activeWindow(), i18n("Do you really want to delete the snippet \"%1\"?", item->text())); 0283 if (ans == KMessageBox::Continue) { 0284 item->parent()->removeRow(item->row()); 0285 repo->save(); 0286 } 0287 } 0288 0289 void SnippetView::slotAddRepo() 0290 { 0291 EditRepository dlg(nullptr, this); 0292 dlg.exec(); 0293 } 0294 0295 void SnippetView::slotEditRepo() 0296 { 0297 QStandardItem *item = currentItem(); 0298 if (!item) { 0299 return; 0300 } 0301 0302 SnippetRepository *repo = SnippetRepository::fromItem(item); 0303 if (!repo) { 0304 return; 0305 } 0306 0307 EditRepository dlg(repo, this); 0308 dlg.exec(); 0309 } 0310 0311 void SnippetView::slotRemoveRepo() 0312 { 0313 QStandardItem *item = currentItem(); 0314 if (!item) { 0315 return; 0316 } 0317 0318 SnippetRepository *repo = SnippetRepository::fromItem(item); 0319 if (!repo) { 0320 return; 0321 } 0322 0323 int ans = KMessageBox::warningContinueCancel(QApplication::activeWindow(), 0324 i18n("Do you really want to delete the repository \"%1\" with all its snippets?", repo->text())); 0325 if (ans == KMessageBox::Continue) { 0326 repo->remove(); 0327 } 0328 } 0329 0330 bool SnippetView::eventFilter(QObject *obj, QEvent *e) 0331 { 0332 // no, listening to activated() is not enough since that would also trigger the edit mode which we _dont_ want here 0333 // users may still rename stuff via select + F2 though 0334 if (obj == snippetTree->viewport()) { 0335 const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this); 0336 if ((!singleClick && e->type() == QEvent::MouseButtonDblClick) || (singleClick && e->type() == QEvent::MouseButtonRelease)) { 0337 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(e); 0338 Q_ASSERT(mouseEvent); 0339 QModelIndex clickedIndex = snippetTree->indexAt(mouseEvent->pos()); 0340 if (clickedIndex.isValid() && clickedIndex.parent().isValid()) { 0341 slotSnippetClicked(clickedIndex); 0342 e->accept(); 0343 return true; 0344 } 0345 } 0346 } 0347 return QObject::eventFilter(obj, e); 0348 } 0349 0350 #include "moc_snippetview.cpp"