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"