File indexing completed on 2024-05-05 05:51:23
0001 /* This file is part of the KDE project 0002 * 0003 * SPDX-FileCopyrightText: 2019 Dominik Haumann <dhaumann@kde.org> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 #include "kateexternaltoolsview.h" 0008 #include "externaltoolsplugin.h" 0009 #include "kateexternaltool.h" 0010 #include "ktexteditor_utils.h" 0011 #include "ui_toolview.h" 0012 0013 #include <KTextEditor/Application> 0014 #include <KTextEditor/Document> 0015 #include <KTextEditor/Editor> 0016 #include <KTextEditor/MainWindow> 0017 #include <KTextEditor/View> 0018 0019 #include <KActionCollection> 0020 #include <KAuthorized> 0021 #include <KConfig> 0022 #include <KConfigGroup> 0023 #include <KLocalizedString> 0024 #include <KSharedConfig> 0025 #include <KXMLGUIFactory> 0026 #include <QMenu> 0027 #include <QStandardPaths> 0028 #include <ktexteditor_version.h> 0029 0030 #include <QFontDatabase> 0031 #include <QKeyEvent> 0032 #include <QTextDocument> 0033 #include <QToolButton> 0034 0035 #include <map> 0036 #include <vector> 0037 0038 // BEGIN KateExternalToolsMenuAction 0039 KateExternalToolsMenuAction::KateExternalToolsMenuAction(const QString &text, 0040 KActionCollection *collection, 0041 KateExternalToolsPlugin *plugin, 0042 KTextEditor::MainWindow *mw) 0043 : KActionMenu(text, mw) 0044 , m_plugin(plugin) 0045 , m_mainwindow(mw) 0046 , m_actionCollection(collection) 0047 { 0048 reload(); 0049 0050 // track active view to adapt enabled tool actions 0051 connect(mw, &KTextEditor::MainWindow::viewChanged, this, &KateExternalToolsMenuAction::slotViewChanged); 0052 } 0053 0054 KateExternalToolsMenuAction::~KateExternalToolsMenuAction() = default; 0055 0056 void KateExternalToolsMenuAction::reload() 0057 { 0058 // clear action collection 0059 bool needs_readd = (m_actionCollection->takeAction(this) != nullptr); 0060 m_actionCollection->clear(); 0061 if (needs_readd) { 0062 m_actionCollection->addAction(QStringLiteral("tools_external"), this); 0063 } 0064 menu()->clear(); 0065 0066 // create tool actions 0067 std::map<QString, KActionMenu *> categories; 0068 std::vector<QAction *> uncategorizedActions; 0069 0070 // first add categorized actions, such that the submenus appear at the top 0071 for (auto tool : m_plugin->tools()) { 0072 // !tool->hasexec => tool exe has an expandable variable and thus cannot be checked reliably, consider it true 0073 if (tool->canExecute()) { 0074 auto a = new QAction(tool->translatedName().replace(QLatin1Char('&'), QLatin1String("&&")), this); 0075 a->setIcon(QIcon::fromTheme(tool->icon)); 0076 a->setData(QVariant::fromValue(tool)); 0077 0078 connect(a, &QAction::triggered, a, [this, a]() { 0079 m_plugin->runTool(*a->data().value<KateExternalTool *>(), m_mainwindow->activeView()); 0080 }); 0081 0082 m_actionCollection->addAction(tool->actionName, a); 0083 if (!tool->category.isEmpty()) { 0084 auto categoryMenu = categories[tool->category]; 0085 if (!categoryMenu) { 0086 categoryMenu = new KActionMenu(tool->translatedCategory(), this); 0087 categories[tool->category] = categoryMenu; 0088 addAction(categoryMenu); 0089 } 0090 categoryMenu->addAction(a); 0091 } else { 0092 uncategorizedActions.push_back(a); 0093 } 0094 } 0095 } 0096 0097 // now add uncategorized actions below 0098 for (auto uncategorizedAction : uncategorizedActions) { 0099 addAction(uncategorizedAction); 0100 } 0101 0102 addSeparator(); 0103 auto cfgAction = new QAction(i18n("Configure..."), this); 0104 addAction(cfgAction); 0105 connect(cfgAction, &QAction::triggered, this, &KateExternalToolsMenuAction::showConfigPage, Qt::QueuedConnection); 0106 0107 // load shortcuts 0108 KSharedConfigPtr pConfig = m_plugin->config(); 0109 KConfigGroup group(pConfig, QStringLiteral("Global")); 0110 group = KConfigGroup(pConfig, QStringLiteral("Shortcuts")); 0111 m_actionCollection->readSettings(&group); 0112 slotViewChanged(m_mainwindow->activeView()); 0113 } 0114 0115 void KateExternalToolsMenuAction::slotViewChanged(KTextEditor::View *view) 0116 { 0117 // no active view, oh oh 0118 disconnect(m_docUrlChangedConnection); 0119 if (!view) { 0120 updateActionState(nullptr); 0121 return; 0122 } 0123 0124 m_docUrlChangedConnection = connect(view->document(), &KTextEditor::Document::documentUrlChanged, this, [this](KTextEditor::Document *doc) { 0125 updateActionState(doc); 0126 }); 0127 0128 updateActionState(view->document()); 0129 } 0130 0131 void KateExternalToolsMenuAction::updateActionState(KTextEditor::Document *activeDoc) 0132 { 0133 // try to enable/disable to match current mime type or if we have no doc 0134 const QString mimeType = activeDoc ? activeDoc->mimeType() : QString(); 0135 const auto actions = m_actionCollection->actions(); 0136 for (QAction *action : actions) { 0137 if (action && action->data().value<KateExternalTool *>()) { 0138 auto tool = action->data().value<KateExternalTool *>(); 0139 action->setEnabled(activeDoc && (tool->matchesMimetype(mimeType) || tool->mimetypes.isEmpty())); 0140 } 0141 } 0142 } 0143 0144 void KateExternalToolsMenuAction::showConfigPage() 0145 { 0146 m_mainwindow->showPluginConfigPage(m_plugin, 0); 0147 } 0148 // END KateExternalToolsMenuAction 0149 0150 // BEGIN KateExternalToolsPluginView 0151 KateExternalToolsPluginView::KateExternalToolsPluginView(KTextEditor::MainWindow *mainWindow, KateExternalToolsPlugin *plugin) 0152 : QObject(mainWindow) 0153 , m_plugin(plugin) 0154 , m_mainWindow(mainWindow) 0155 , m_outputDoc(new QTextDocument(this)) 0156 { 0157 m_plugin->registerPluginView(this); 0158 0159 KXMLGUIClient::setComponentName(QLatin1String("externaltools"), i18n("External Tools")); 0160 setXMLFile(QLatin1String("ui.rc")); 0161 0162 if (KAuthorized::authorizeAction(QStringLiteral("shell_access"))) { 0163 m_externalToolsMenu = new KateExternalToolsMenuAction(i18n("External Tools"), actionCollection(), plugin, mainWindow); 0164 actionCollection()->addAction(QStringLiteral("tools_external"), m_externalToolsMenu); 0165 m_externalToolsMenu->setWhatsThis(i18n("Launch external helper applications")); 0166 } 0167 0168 mainWindow->guiFactory()->addClient(this); 0169 0170 // ESC should close & hide ToolView 0171 connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &KateExternalToolsPluginView::handleEsc); 0172 connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &KateExternalToolsPluginView::slotViewChanged); 0173 slotViewChanged(m_mainWindow->activeView()); 0174 } 0175 0176 KateExternalToolsPluginView::~KateExternalToolsPluginView() 0177 { 0178 m_plugin->unregisterPluginView(this); 0179 0180 m_mainWindow->guiFactory()->removeClient(this); 0181 0182 deleteToolView(); 0183 0184 delete m_externalToolsMenu; 0185 m_externalToolsMenu = nullptr; 0186 } 0187 0188 void KateExternalToolsPluginView::rebuildMenu() 0189 { 0190 if (m_externalToolsMenu) { 0191 KXMLGUIFactory *f = factory(); 0192 f->removeClient(this); 0193 reloadXML(); 0194 m_externalToolsMenu->reload(); 0195 f->addClient(this); 0196 } 0197 } 0198 0199 KTextEditor::MainWindow *KateExternalToolsPluginView::mainWindow() const 0200 { 0201 return m_mainWindow; 0202 } 0203 0204 void KateExternalToolsPluginView::createToolView() 0205 { 0206 if (!m_toolView) { 0207 m_toolView = mainWindow()->createToolView(m_plugin, 0208 QStringLiteral("ktexteditor_plugin_externaltools"), 0209 KTextEditor::MainWindow::Bottom, 0210 QIcon::fromTheme(QStringLiteral("system-run")), 0211 i18n("External Tools")); 0212 0213 m_ui = new Ui::ToolView(); 0214 m_ui->setupUi(m_toolView); 0215 0216 // set the documents 0217 m_ui->teOutput->setDocument(m_outputDoc); 0218 0219 // use fixed font for displaying status and output text 0220 const auto fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); 0221 m_ui->teOutput->setFont(fixedFont); 0222 0223 // close button to delete tool view 0224 auto btnClose = new QToolButton(); 0225 btnClose->setAutoRaise(true); 0226 btnClose->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); 0227 connect(btnClose, &QToolButton::clicked, this, &KateExternalToolsPluginView::deleteToolView); 0228 m_ui->tabWidget->setCornerWidget(btnClose); 0229 } 0230 } 0231 0232 void KateExternalToolsPluginView::showToolView() 0233 { 0234 createToolView(); 0235 m_ui->tabWidget->setCurrentWidget(m_ui->tabOutput); 0236 mainWindow()->showToolView(m_toolView); 0237 } 0238 0239 void KateExternalToolsPluginView::clearToolView() 0240 { 0241 m_outputDoc->clear(); 0242 } 0243 0244 void KateExternalToolsPluginView::setOutputData(const QString &data) 0245 { 0246 QTextCursor cursor(m_outputDoc); 0247 cursor.movePosition(QTextCursor::End); 0248 cursor.insertText(data); 0249 } 0250 0251 void KateExternalToolsPluginView::deleteToolView() 0252 { 0253 if (m_toolView) { 0254 delete m_ui; 0255 m_ui = nullptr; 0256 0257 delete m_toolView; 0258 m_toolView = nullptr; 0259 } 0260 } 0261 0262 void KateExternalToolsPluginView::handleEsc(QEvent *event) 0263 { 0264 if (event->type() != QEvent::ShortcutOverride) 0265 return; 0266 0267 auto keyEvent = static_cast<QKeyEvent *>(event); 0268 if (keyEvent && keyEvent->key() == Qt::Key_Escape && keyEvent->modifiers() == Qt::NoModifier) { 0269 deleteToolView(); 0270 } 0271 } 0272 0273 void KateExternalToolsPluginView::slotViewChanged(KTextEditor::View *v) 0274 { 0275 if (m_currentView) { 0276 disconnect(m_currentView->document(), &KTextEditor::Document::documentSavedOrUploaded, this, &KateExternalToolsPluginView::onDocumentSaved); 0277 disconnect(m_currentView->document(), &KTextEditor::Document::aboutToSave, this, &KateExternalToolsPluginView::onDocumentAboutToSave); 0278 } 0279 m_currentView = v; 0280 0281 if (!m_currentView) { 0282 return; 0283 } 0284 0285 connect(v->document(), &KTextEditor::Document::documentSavedOrUploaded, this, &KateExternalToolsPluginView::onDocumentSaved, Qt::UniqueConnection); 0286 connect(v->document(), &KTextEditor::Document::aboutToSave, this, &KateExternalToolsPluginView::onDocumentAboutToSave, Qt::UniqueConnection); 0287 } 0288 0289 void KateExternalToolsPluginView::onDocumentSaved(KTextEditor::Document *doc) 0290 { 0291 // We only want to run this in the current active mainwindow 0292 if (KTextEditor::Editor::instance()->application()->activeMainWindow() != m_mainWindow) { 0293 return; 0294 } 0295 0296 const auto tools = m_plugin->tools(); 0297 for (KateExternalTool *tool : tools) { 0298 const bool hasSaveTrigger = tool->trigger == KateExternalTool::Trigger::AfterSave; 0299 if (hasSaveTrigger && tool->matchesMimetype(doc->mimeType())) { 0300 m_plugin->runTool(*tool, m_currentView, /*exec save trigger=*/true); 0301 } 0302 } 0303 } 0304 0305 void KateExternalToolsPluginView::onDocumentAboutToSave(KTextEditor::Document *doc) 0306 { 0307 // We only want to run this in the current active mainwindow 0308 if (KTextEditor::Editor::instance()->application()->activeMainWindow() != m_mainWindow) { 0309 return; 0310 } 0311 0312 const auto tools = m_plugin->tools(); 0313 for (KateExternalTool *tool : tools) { 0314 const bool hasSaveTrigger = tool->trigger == KateExternalTool::Trigger::BeforeSave; 0315 if (hasSaveTrigger && tool->matchesMimetype(doc->mimeType())) { 0316 m_plugin->blockingRunTool(*tool, m_currentView, /*exec save trigger=*/true); 0317 } 0318 } 0319 } 0320 0321 // END KateExternalToolsPluginView 0322 0323 #include "moc_kateexternaltoolsview.cpp" 0324 0325 // kate: space-indent on; indent-width 4; replace-tabs on;