diff --git a/addons/CMakeLists.txt b/addons/CMakeLists.txt --- a/addons/CMakeLists.txt +++ b/addons/CMakeLists.txt @@ -49,6 +49,9 @@ # pipe text through some external command ecm_optional_add_subdirectory (textfilter) +# external tools +ecm_optional_add_subdirectory (externaltools) + # Rust complection plugin ecm_optional_add_subdirectory (rustcompletion) diff --git a/addons/externaltools/CMakeLists.txt b/addons/externaltools/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/addons/externaltools/CMakeLists.txt @@ -0,0 +1,32 @@ +project(externaltoolsplugin) +add_definitions(-DTRANSLATION_DOMAIN=\"externaltoolsplugin\") + +include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) + +set(externaltoolsplugin_PART_SRCS + externaltoolsplugin.cpp + externaltools.cpp +) + +# resource for ui file and stuff +qt5_add_resources(externaltoolsplugin_PART_SRCS plugin.qrc) + +set(externaltoolsplugin_PART_UI + configwidget.ui + tooldialog.ui +) +ki18n_wrap_ui(externaltoolsplugin_PART_SRCS ${externaltoolsplugin_PART_UI} ) +add_library(externaltoolsplugin MODULE ${externaltoolsplugin_PART_SRCS}) + +# we compile in the .desktop file +kcoreaddons_desktop_to_json (externaltoolsplugin externaltoolsplugin.desktop) + +target_link_libraries(externaltoolsplugin + KF5::CoreAddons + KF5::IconThemes + KF5::TextEditor + KF5::I18n +) + +########### install files ############### +install(TARGETS externaltoolsplugin DESTINATION ${PLUGIN_INSTALL_DIR}/ktexteditor ) diff --git a/addons/externaltools/Messages.sh b/addons/externaltools/Messages.sh new file mode 100644 --- /dev/null +++ b/addons/externaltools/Messages.sh @@ -0,0 +1,3 @@ +#! /bin/sh +$EXTRACTRC *.rc *.ui >> rc.cpp +$XGETTEXT *.cpp -o $podir/externaltoolsplugin.pot diff --git a/addons/externaltools/configwidget.ui b/addons/externaltools/configwidget.ui new file mode 100644 --- /dev/null +++ b/addons/externaltools/configwidget.ui @@ -0,0 +1,118 @@ + + + ExternalToolsConfigWidget + + + + 0 + 0 + 504 + 296 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Qt::Vertical + + + + 20 + 98 + + + + + + + + Qt::Vertical + + + + 17 + 108 + + + + + + + + + + New... + + + + + + + Edit... + + + + + + + Remove + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Insert Separator + + + + + + + + + + + + + + + This list shows all the configured tools, represented by their menu text. + + + + + + + + + + diff --git a/addons/externaltools/externaltools b/addons/externaltools/externaltools new file mode 100644 --- /dev/null +++ b/addons/externaltools/externaltools @@ -0,0 +1,23 @@ +[Global] +tools=externaltool_RunScript,---,externaltool_GoogleSelection +version=1 + +[externaltool_GoogleSelection] +acname=externaltool_GoogleSelection +cmdname=google-selection +command=[ -n "%selection" ] && kfmclient openURL "gg:%selection" +executable=kfmclient +icon=globe +mimetypes= +name=Google Selection +save=0 + +[externaltool_RunScript] +acname=externaltool_RunScript +cmdname=runscript +command=konsole -e sh -c "cd %directory && pwd && chmod -vc a+x %filename && ./%filename ; echo Press any key to continue. && read -n 1" +executable=konsole +icon=run +mimetypes= +name=Run Script +save=1 diff --git a/addons/externaltools/externaltools.h b/addons/externaltools/externaltools.h new file mode 100644 --- /dev/null +++ b/addons/externaltools/externaltools.h @@ -0,0 +1,248 @@ +/* + This file is part of the Kate text editor of the KDE project. + It describes a "external tools" action for kate and provides a dialog + page to configure that. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + --- + Copyright (C) 2004 Anders Lund + Copyright (C) 2019 Dominik Haumann +*/ + +#ifndef KTEXTEDITOR_EXTERNALTOOLS_H +#define KTEXTEDITOR_EXTERNALTOOLS_H + +#include "ui_configwidget.h" +#include "ui_tooldialog.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +class KateExternalToolsPlugin; + +/** + * The external tools action + * This action creates a menu, in which each item will launch a process + * with the provided arguments, which may include the following macros: + * - %URLS: the URLs of all open documents. + * - %URL: The URL of the active document. + * - %filedir: The directory of the current document, if that is a local file. + * - %selection: The selection of the active document. + * - %text: The text of the active document. + * - %line: The line number of the cursor in the active view. + * - %column: The column of the cursor in the active view. + * + * Each item has the following properties: + * - Name: The friendly text for the menu + * - Exec: The command to execute, including arguments. + * - TryExec: the name of the executable, if not available, the + * item will not be displayed. + * - MimeTypes: An optional list of mimetypes. The item will be disabled or + * hidden if the current file is not of one of the indicated types. + * + */ +class KateExternalToolsMenuAction : public KActionMenu +{ + friend class KateExternalToolAction; + + Q_OBJECT +public: + KateExternalToolsMenuAction(const QString& text, class KActionCollection* collection, QObject* parent, + class KTextEditor::MainWindow* mw = nullptr); + virtual ~KateExternalToolsMenuAction(); + + /** + * This will load all the configured services. + */ + void reload(); + + class KActionCollection* actionCollection() { return m_actionCollection; } + +private Q_SLOTS: + void slotViewChanged(KTextEditor::View* view); + +private: + class KActionCollection* m_actionCollection; + KTextEditor::MainWindow* mainwindow; // for the actions to access view/doc managers +}; + +/** + * This Action contains a KateExternalTool + */ +class KateExternalToolAction : public QAction, public KWordMacroExpander +{ + Q_OBJECT +public: + KateExternalToolAction(QObject* parent, class KateExternalTool* t); + ~KateExternalToolAction(); + +protected: + bool expandMacro(const QString& str, QStringList& ret) override; + +private Q_SLOTS: + void slotRun(); + +public: + class KateExternalTool* tool; +}; + +/** + * This class defines a single external tool. + */ +class KateExternalTool +{ +public: + explicit KateExternalTool(const QString& name = QString(), const QString& command = QString(), + const QString& icon = QString(), const QString& executable = QString(), + const QStringList& mimetypes = QStringList(), const QString& acname = QString(), + const QString& cmdname = QString(), int save = 0); + ~KateExternalTool() {} + + QString name; ///< The name used in the menu. + QString executable; ///< The name or path of the executable. + QString arguments; ///< The command line arguments. + QString command; ///< The command to execute. + QString icon; ///< the icon to use in the menu. + QStringList mimetypes; ///< Optional list of mimetypes for which this action is valid. + bool hasexec; ///< This is set by the constructor by calling checkExec(), if a + ///< value is present. + QString acname; ///< The name for the action. This is generated first time the + ///< action is is created. + QString cmdname; ///< The name for the commandline. + int save; ///< We can save documents prior to activating the tool command: 0 = + ///< nothing, 1 = current document, 2 = all documents. + + /** + * @return true if mimetypes is empty, or the @p mimetype matches. + */ + bool valid(const QString& mimetype) const; + /** + * @return true if "executable" exists and has the executable bit set, or is + * empty. + * This is run at least once, and the tool is disabled if it fails. + */ + bool checkExec(); + +private: + QString m_exec; ///< The fully qualified path of the executable. +}; + +/** + * The config widget. + * The config widget allows the user to view a list of services of the type + * "Kate/ExternalTool" and add, remove or edit them. + */ +class KateExternalToolsConfigWidget : public KTextEditor::ConfigPage, public Ui::ExternalToolsConfigWidget +{ + Q_OBJECT +public: + KateExternalToolsConfigWidget(QWidget* parent, KateExternalToolsPlugin* plugin); + virtual ~KateExternalToolsConfigWidget(); + + QString name() const override; + QString fullName() const override; + QIcon icon() const override; + +public Q_SLOTS: + void apply() override; + void reset() override; + void defaults() override { reset(); } // double sigh + +private Q_SLOTS: + void slotNew(); + void slotEdit(); + void slotRemove(); + void slotInsertSeparator(); + + void slotMoveUp(); + void slotMoveDown(); + + void slotSelectionChanged(); + +private: + QPixmap blankIcon(); + + QStringList m_removed; + + class KConfig* config; + + bool m_changed; + KateExternalToolsPlugin* m_plugin; +}; + +/** + * A Singleton class for invoking external tools with the view command line + */ +class KateExternalToolsCommand : public KTextEditor::Command +{ +public: + KateExternalToolsCommand(KateExternalToolsPlugin* plugin); + virtual ~KateExternalToolsCommand() {} + void reload(); + +public: + // const QStringList& cmds() override; // FIXME + bool exec(KTextEditor::View* view, const QString& cmd, QString& msg, + const KTextEditor::Range& range = KTextEditor::Range::invalid()) override; + bool help(KTextEditor::View* view, const QString& cmd, QString& msg) override; + +private: + QStringList m_list; + QHash m_map; + QHash m_name; + KateExternalToolsPlugin* m_plugin; +}; + +/** + * A Dialog to edit a single KateExternalTool object + */ +class KateExternalToolServiceEditor : public QDialog +{ + Q_OBJECT + +public: + explicit KateExternalToolServiceEditor(KateExternalTool* tool = nullptr, QWidget* parent = nullptr); + +private Q_SLOTS: + /** + * Run when the OK button is clicked, to ensure critical values are provided. + */ + void slotOKClicked(); + + /** + * show a mimetype chooser dialog + */ + void showMTDlg(); + +public: + Ui::ToolDialog * ui; +private: + KateExternalTool* tool; +}; + +#endif // KTEXTEDITOR_EXTERNALTOOLS_H + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/externaltools.cpp b/addons/externaltools/externaltools.cpp new file mode 100644 --- /dev/null +++ b/addons/externaltools/externaltools.cpp @@ -0,0 +1,720 @@ +/* + This file is part of the Kate text editor of the KDE project. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + --- + Copyright (C) 2004, Anders Lund + Copyright (C) 2019 Dominik Haumann +*/ +// TODO +// Icons +// Direct shortcut setting +#include "externaltools.h" +#include "externaltoolsplugin.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// BEGIN KateExternalTool +KateExternalTool::KateExternalTool(const QString& name, const QString& command, const QString& icon, + const QString& executable, const QStringList& mimetypes, const QString& acname, + const QString& cmdname, int save) + : name(name) + , command(command) + , icon(icon) + , executable(executable) + , mimetypes(mimetypes) + , acname(acname) + , cmdname(cmdname) + , save(save) +{ + // if ( ! executable.isEmpty() ) + hasexec = checkExec(); +} + +bool KateExternalTool::checkExec() +{ + m_exec = QStandardPaths::findExecutable(executable); + return !m_exec.isEmpty(); +} + +bool KateExternalTool::valid(const QString& mt) const +{ + return mimetypes.isEmpty() || mimetypes.contains(mt); +} +// END KateExternalTool + +// BEGIN KateExternalToolsCommand +KateExternalToolsCommand::KateExternalToolsCommand(KateExternalToolsPlugin* plugin) + : KTextEditor::Command({/* FIXME */}) + , m_plugin(plugin) +{ + reload(); +} + +// FIXME +// const QStringList& KateExternalToolsCommand::cmds() +// { +// return m_list; +// } + +void KateExternalToolsCommand::reload() +{ + m_list.clear(); + m_map.clear(); + m_name.clear(); + + KConfig _config(QStringLiteral("externaltools"), KConfig::NoGlobals, QStandardPaths::ApplicationsLocation); + KConfigGroup config(&_config, "Global"); + const QStringList tools = config.readEntry("tools", QStringList()); + + for (QStringList::const_iterator it = tools.begin(); it != tools.end(); ++it) { + if (*it == QStringLiteral("---")) + continue; + + config = KConfigGroup(&_config, *it); + + KateExternalTool t + = KateExternalTool(config.readEntry(QStringLiteral("name"), ""), config.readEntry("command", ""), + config.readEntry(QStringLiteral("icon"), ""), config.readEntry("executable", ""), + config.readEntry(QStringLiteral("mimetypes"), QStringList()), + config.readEntry(QStringLiteral("acname"), ""), config.readEntry("cmdname", "")); + // FIXME test for a command name first! + if (t.hasexec && (!t.cmdname.isEmpty())) { + m_list.append(QStringLiteral("exttool-") + t.cmdname); + m_map.insert(QStringLiteral("exttool-") + t.cmdname, t.acname); + m_name.insert(QStringLiteral("exttool-") + t.cmdname, t.name); + } + } +} + +bool KateExternalToolsCommand::exec(KTextEditor::View* view, const QString& cmd, QString& msg, + const KTextEditor::Range& range) +{ + Q_UNUSED(msg) + Q_UNUSED(range) + + auto wv = dynamic_cast(view); + if (!wv) { + // qDebug()<<"KateExternalToolsCommand::exec: Could not get view widget"; + return false; + } + + // qDebug()<<"cmd="<(dmw->action("tools_external")); + if (!a) return false;*/ + KateExternalToolsPluginView* extview = m_plugin->extView(wv->window()); + if (!extview) + return false; + if (!extview->externalTools) + return false; + // qDebug()<<"trying to find action"; + QAction* a1 = extview->externalTools->actionCollection()->action(actionName); + if (!a1) + return false; + // qDebug()<<"activating action"; + a1->trigger(); + return true; +} + +bool KateExternalToolsCommand::help(KTextEditor::View*, const QString&, QString&) +{ + return false; +} +// END KateExternalToolsCommand + +// BEGIN KateExternalToolAction +KateExternalToolAction::KateExternalToolAction(QObject* parent, KateExternalTool* t) + : QAction(QIcon::fromTheme(t->icon), t->name, parent) + , tool(t) +{ + setText(t->name); + if (!t->icon.isEmpty()) + setIcon(QIcon::fromTheme(t->icon)); + + connect(this, SIGNAL(triggered(bool)), SLOT(slotRun())); +} + +bool KateExternalToolAction::expandMacro(const QString& str, QStringList& ret) +{ + KTextEditor::MainWindow* mw = qobject_cast(parent()->parent()); + Q_ASSERT(mw); + + KTextEditor::View* view = mw->activeView(); + if (!view) + return false; + + KTextEditor::Document* doc = view->document(); + QUrl url = doc->url(); + + if (str == QStringLiteral("URL")) + ret += url.url(); + else if (str == QStringLiteral("directory")) // directory of current doc + ret += url.toString(QUrl::RemoveScheme | QUrl::RemoveFilename); + else if (str == QStringLiteral("filename")) + ret += url.fileName(); + else if (str == QStringLiteral("line")) // cursor line of current doc + ret += QString::number(view->cursorPosition().line()); + else if (str == QStringLiteral("col")) // cursor col of current doc + ret += QString::number(view->cursorPosition().column()); + else if (str == QStringLiteral("selection")) // selection of current doc if any + ret += view->selectionText(); + else if (str == QStringLiteral("text")) // text of current doc + ret += doc->text(); + else if (str == QStringLiteral("URLs")) { + foreach (KTextEditor::Document* it, KTextEditor::Editor::instance()->application()->documents()) + if (!it->url().isEmpty()) + ret += it->url().url(); + } else + return false; + + return true; +} + +KateExternalToolAction::~KateExternalToolAction() +{ + delete (tool); +} + +void KateExternalToolAction::slotRun() +{ + // expand the macros in command if any, + // and construct a command with an absolute path + QString cmd = tool->command; + + auto mw = qobject_cast(parent()->parent()); + if (!expandMacrosShellQuote(cmd)) { + KMessageBox::sorry(mw->window(), i18n("Failed to expand the command '%1'.", cmd), i18n("Kate External Tools")); + return; + } + qDebug() << "externaltools: Running command: " << cmd; + + // save documents if requested + if (tool->save == 1) + mw->activeView()->document()->save(); + else if (tool->save == 2) { + foreach (KXMLGUIClient* client, mw->guiFactory()->clients()) { + if (QAction* a = client->actionCollection()->action(QStringLiteral("file_save_all"))) { + a->trigger(); + break; + } + } + } + + KRun::runCommand(cmd, tool->executable, tool->icon, mw->window()); +} +// END KateExternalToolAction + +// BEGIN KateExternalToolsMenuAction +KateExternalToolsMenuAction::KateExternalToolsMenuAction(const QString& text, KActionCollection* collection, + QObject* parent, KTextEditor::MainWindow* mw) + : KActionMenu(text, parent) + , mainwindow(mw) +{ + + m_actionCollection = collection; + + // connect to view changed... + connect(mw, &KTextEditor::MainWindow::viewChanged, this, &KateExternalToolsMenuAction::slotViewChanged); + + reload(); +} + +KateExternalToolsMenuAction::~KateExternalToolsMenuAction() +{ + // kDebug() << "deleted KateExternalToolsMenuAction"; +} + +void KateExternalToolsMenuAction::reload() +{ + bool needs_readd = (m_actionCollection->takeAction(this) != nullptr); + m_actionCollection->clear(); + if (needs_readd) + m_actionCollection->addAction(QStringLiteral("tools_external"), this); + menu()->clear(); + + // load all the tools, and create a action for each of them + KSharedConfig::Ptr pConfig = KSharedConfig::openConfig(QStringLiteral("externaltools"), KConfig::NoGlobals, + QStandardPaths::ApplicationsLocation); + KConfigGroup config(pConfig, "Global"); + QStringList tools = config.readEntry("tools", QStringList()); + + // if there are tools that are present but not explicitly removed, + // add them to the end of the list + pConfig->setReadDefaults(true); + QStringList dtools = config.readEntry("tools", QStringList()); + int gver = config.readEntry("version", 1); + pConfig->setReadDefaults(false); + + int ver = config.readEntry("version", 0); + if (ver <= gver) { + QStringList removed = config.readEntry("removed", QStringList()); + bool sepadded = false; + for (QStringList::iterator itg = dtools.begin(); itg != dtools.end(); ++itg) { + if (!tools.contains(*itg) && !removed.contains(*itg)) { + if (!sepadded) { + tools << QStringLiteral("---"); + sepadded = true; + } + tools << *itg; + } + } + + config.writeEntry("tools", tools); + config.sync(); + config.writeEntry("version", gver); + } + + for (QStringList::const_iterator it = tools.constBegin(); it != tools.constEnd(); ++it) { + if (*it == QStringLiteral("---")) { + menu()->addSeparator(); + // a separator + continue; + } + + config = KConfigGroup(pConfig, *it); + + KateExternalTool* t = new KateExternalTool( + config.readEntry("name", ""), config.readEntry("command", ""), config.readEntry("icon", ""), + config.readEntry("executable", ""), config.readEntry("mimetypes", QStringList()), + config.readEntry("acname", ""), config.readEntry("cmdname", ""), config.readEntry("save", 0)); + + if (t->hasexec) { + QAction* a = new KateExternalToolAction(this, t); + m_actionCollection->addAction(t->acname, a); + addAction(a); + } else + delete t; + } + + config = KConfigGroup(pConfig, "Shortcuts"); + m_actionCollection->readSettings(&config); + slotViewChanged(mainwindow->activeView()); +} + +void KateExternalToolsMenuAction::slotViewChanged(KTextEditor::View* view) +{ + // no active view, oh oh + if (!view) { + return; + } + + // try to enable/disable to match current mime type + KTextEditor::Document* doc = view->document(); + if (doc) { + const QString mimeType = doc->mimeType(); + foreach (QAction* kaction, m_actionCollection->actions()) { + KateExternalToolAction* action = dynamic_cast(kaction); + if (action) { + const QStringList l = action->tool->mimetypes; + const bool b = (!l.count() || l.contains(mimeType)); + action->setEnabled(b); + } + } + } +} +// END KateExternalToolsMenuAction + +// BEGIN ToolItem +/** + * This is a QListBoxItem, that has a KateExternalTool. The text is the Name + * of the tool. + */ +class ToolItem : public QListWidgetItem +{ +public: + ToolItem(QListWidget* lb, const QPixmap& icon, KateExternalTool* tool) + : QListWidgetItem(icon, tool->name, lb) + , tool(tool) + { + } + + ~ToolItem() {} + + KateExternalTool* tool; +}; +// END ToolItem + +// BEGIN KateExternalToolServiceEditor +KateExternalToolServiceEditor::KateExternalToolServiceEditor(KateExternalTool* tool, QWidget* parent) + : QDialog(parent) + , tool(tool) +{ + setWindowTitle(i18n("Edit External Tool")); + + ui = new Ui::ToolDialog(); + ui->setupUi(this); + ui->btnIcon->setIconSize(KIconLoader::SizeSmall); + + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &KateExternalToolServiceEditor::slotOKClicked); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(ui->btnMimeType, &QToolButton::clicked, this, &KateExternalToolServiceEditor::showMTDlg); + + if (tool) { + ui->edtName->setText(tool->name); + if (!tool->icon.isEmpty()) + ui->btnIcon->setIcon(tool->icon); + + ui->edtExecutable->setText(tool->executable); + ui->edtInput->setText(tool->command); + ui->edtMimeType->setText(tool->mimetypes.join(QStringLiteral("; "))); + ui->cmbSave->setCurrentIndex(tool->save); + ui->edtCommand->setText(tool->cmdname); + } +} + +void KateExternalToolServiceEditor::slotOKClicked() +{ + if (ui->edtName->text().isEmpty() || ui->edtInput->document()->isEmpty()) { + QMessageBox::information(this, i18n("External Tool"), i18n("You must specify at least a name and a command")); + return; + } + accept(); +} + +void KateExternalToolServiceEditor::showMTDlg() +{ + QString text = i18n("Select the MimeTypes for which to enable this tool."); + QStringList list = ui->edtMimeType->text().split(QRegExp(QStringLiteral("\\s*;\\s*")), QString::SkipEmptyParts); + KMimeTypeChooserDialog d(i18n("Select Mime Types"), text, list, QStringLiteral("text"), this); + if (d.exec() == QDialog::Accepted) { + ui->edtMimeType->setText(d.chooser()->mimeTypes().join(QStringLiteral(";"))); + } +} +// END KateExternalToolServiceEditor + +// BEGIN KateExternalToolsConfigWidget +KateExternalToolsConfigWidget::KateExternalToolsConfigWidget(QWidget* parent, KateExternalToolsPlugin* plugin) + : KTextEditor::ConfigPage(parent) + , m_changed(false) + , m_plugin(plugin) +{ + setupUi(this); + + btnMoveUp->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); + btnMoveDown->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); + + connect(lbTools, &QListWidget::itemSelectionChanged, this, &KateExternalToolsConfigWidget::slotSelectionChanged); + connect(lbTools, &QListWidget::itemDoubleClicked, this, &KateExternalToolsConfigWidget::slotEdit); + connect(btnNew, &QPushButton::clicked, this, &KateExternalToolsConfigWidget::slotNew); + connect(btnRemove, &QPushButton::clicked, this, &KateExternalToolsConfigWidget::slotRemove); + connect(btnEdit, &QPushButton::clicked, this, &KateExternalToolsConfigWidget::slotEdit); + connect(btnSeparator, &QPushButton::clicked, this, &KateExternalToolsConfigWidget::slotInsertSeparator); + connect(btnMoveUp, &QPushButton::clicked, this, &KateExternalToolsConfigWidget::slotMoveUp); + connect(btnMoveDown, &QPushButton::clicked, this, &KateExternalToolsConfigWidget::slotMoveDown); + + config = new KConfig(QStringLiteral("externaltools"), KConfig::NoGlobals, QStandardPaths::ApplicationsLocation); + reset(); + slotSelectionChanged(); +} + +KateExternalToolsConfigWidget::~KateExternalToolsConfigWidget() +{ + delete config; +} + +QString KateExternalToolsConfigWidget::name() const +{ + return i18n("External Tools"); +} + +QString KateExternalToolsConfigWidget::fullName() const +{ + return i18n("External Tools"); +} + +QIcon KateExternalToolsConfigWidget::icon() const +{ + return QIcon(); +} + +void KateExternalToolsConfigWidget::reset() +{ + // m_tools.clear(); + lbTools->clear(); + + // load the files from a KConfig + const QStringList tools = config->group("Global").readEntry("tools", QStringList()); + + for (QStringList::const_iterator it = tools.begin(); it != tools.end(); ++it) { + if (*it == QStringLiteral("---")) { + new QListWidgetItem(QStringLiteral("---"), lbTools); + } else { + KConfigGroup cg(config, *it); + + KateExternalTool* t + = new KateExternalTool(cg.readEntry("name", ""), cg.readEntry("command", ""), cg.readEntry("icon", ""), + cg.readEntry("executable", ""), cg.readEntry("mimetypes", QStringList()), + cg.readEntry("acname"), cg.readEntry("cmdname"), cg.readEntry("save", 0)); + + if (t->hasexec) // we only show tools that are also in the menu. + new ToolItem(lbTools, t->icon.isEmpty() ? blankIcon() : SmallIcon(t->icon), t); + else + delete t; + } + } + m_changed = false; +} + +QPixmap KateExternalToolsConfigWidget::blankIcon() +{ + QPixmap pm(KIconLoader::SizeSmall, KIconLoader::SizeSmall); + pm.fill(); + pm.setMask(pm.createHeuristicMask()); + return pm; +} + +void KateExternalToolsConfigWidget::apply() +{ + if (!m_changed) + return; + m_changed = false; + + // save a new list + // save each item + QStringList tools; + for (int i = 0; i < lbTools->count(); i++) { + if (lbTools->item(i)->text() == QStringLiteral("---")) { + tools << QStringLiteral("---"); + continue; + } + KateExternalTool* t = static_cast(lbTools->item(i))->tool; + // qDebug()<<"adding tool: "<name; + tools << t->acname; + + KConfigGroup cg(config, t->acname); + + cg.writeEntry("name", t->name); + cg.writeEntry("command", t->command); + cg.writeEntry("icon", t->icon); + cg.writeEntry("executable", t->executable); + cg.writeEntry("mimetypes", t->mimetypes); + cg.writeEntry("acname", t->acname); + cg.writeEntry("cmdname", t->cmdname); + cg.writeEntry("save", t->save); + } + + config->group("Global").writeEntry("tools", tools); + + // if any tools was removed, try to delete their groups, and + // add the group names to the list of removed items. + if (m_removed.count()) { + for (QStringList::iterator it = m_removed.begin(); it != m_removed.end(); ++it) { + if (config->hasGroup(*it)) + config->deleteGroup(*it); + } + QStringList removed = config->group("Global").readEntry("removed", QStringList()); + removed += m_removed; + + // clean up the list of removed items, so that it does not contain + // non-existing groups (we can't remove groups from a non-owned global file). + config->sync(); + QStringList::iterator it1 = removed.begin(); + while (it1 != removed.end()) { + if (!config->hasGroup(*it1)) + it1 = removed.erase(it1); + else + ++it1; + } + config->group("Global").writeEntry("removed", removed); + } + + config->sync(); + m_plugin->reload(); +} + +void KateExternalToolsConfigWidget::slotSelectionChanged() +{ + // update button state + bool hs = lbTools->currentItem() != nullptr; + btnEdit->setEnabled(hs && dynamic_cast(lbTools->currentItem())); + btnRemove->setEnabled(hs); + btnMoveUp->setEnabled((lbTools->currentRow() > 0) && hs); + btnMoveDown->setEnabled((lbTools->currentRow() < (int)lbTools->count() - 1) && hs); +} + +void KateExternalToolsConfigWidget::slotNew() +{ + // display a editor, and if it is OK'd, create a new tool and + // create a listbox item for it + KateExternalToolServiceEditor editor(nullptr, this); + + if (editor.exec() == QDialog::Accepted) { + KateExternalTool* t = new KateExternalTool( + editor.ui->edtName->text(), editor.ui->edtInput->toPlainText(), editor.ui->btnIcon->icon(), editor.ui->edtExecutable->text(), + editor.ui->edtMimeType->text().split(QRegExp(QStringLiteral("\\s*;\\s*")), QString::SkipEmptyParts)); + + // This is sticky, it does not change again, so that shortcuts sticks + // TODO check for dups + t->acname = QStringLiteral("externaltool_") + QString(t->name).remove(QRegExp(QStringLiteral("\\W+"))); + + new ToolItem(lbTools, t->icon.isEmpty() ? blankIcon() : SmallIcon(t->icon), t); + + emit changed(); + m_changed = true; + } +} + +void KateExternalToolsConfigWidget::slotRemove() +{ + // add the tool action name to a list of removed items, + // remove the current listbox item + if (lbTools->currentRow() > -1) { + ToolItem* i = dynamic_cast(lbTools->currentItem()); + if (i) + m_removed << i->tool->acname; + + delete lbTools->takeItem(lbTools->currentRow()); + emit changed(); + m_changed = true; + } +} + +void KateExternalToolsConfigWidget::slotEdit() +{ + if (!dynamic_cast(lbTools->currentItem())) + return; + // show the item in an editor + KateExternalTool* t = static_cast(lbTools->currentItem())->tool; + KateExternalToolServiceEditor editor(t, this); + editor.resize(config->group("Editor").readEntry("Size", QSize())); + if (editor.exec() /*== KDialog::Ok*/) { + + bool elementChanged = ((editor.ui->btnIcon->icon() != t->icon) || (editor.ui->edtName->text() != t->name)); + + t->name = editor.ui->edtName->text(); + t->cmdname = editor.ui->edtCommand->text(); + t->command = editor.ui->edtInput->toPlainText(); + t->icon = editor.ui->btnIcon->icon(); + t->executable = editor.ui->edtExecutable->text(); + t->mimetypes = editor.ui->edtMimeType->text().split(QRegExp(QStringLiteral("\\s*;\\s*")), QString::SkipEmptyParts); + t->save = editor.ui->cmbSave->currentIndex(); + + // if the icon has changed or name changed, I have to renew the listbox item :S + if (elementChanged) { + int idx = lbTools->row(lbTools->currentItem()); + delete lbTools->takeItem(idx); + lbTools->insertItem(idx, new ToolItem(nullptr, t->icon.isEmpty() ? blankIcon() : SmallIcon(t->icon), t)); + } + + emit changed(); + m_changed = true; + } + + config->group("Editor").writeEntry("Size", editor.size()); + config->sync(); +} + +void KateExternalToolsConfigWidget::slotInsertSeparator() +{ + lbTools->insertItem(lbTools->currentRow() + 1, QStringLiteral("---")); + emit changed(); + m_changed = true; +} + +void KateExternalToolsConfigWidget::slotMoveUp() +{ + // move the current item in the listbox upwards if possible + QListWidgetItem* item = lbTools->currentItem(); + if (!item) + return; + + int idx = lbTools->row(item); + + if (idx < 1) + return; + + if (dynamic_cast(item)) { + KateExternalTool* tool = static_cast(item)->tool; + delete lbTools->takeItem(idx); + lbTools->insertItem(idx - 1, + new ToolItem(nullptr, tool->icon.isEmpty() ? blankIcon() : SmallIcon(tool->icon), tool)); + } else // a separator! + { + delete lbTools->takeItem(idx); + lbTools->insertItem(idx - 1, new QListWidgetItem(QStringLiteral("---"))); + } + + lbTools->setCurrentRow(idx - 1); + slotSelectionChanged(); + emit changed(); + m_changed = true; +} + +void KateExternalToolsConfigWidget::slotMoveDown() +{ + // move the current item in the listbox downwards if possible + QListWidgetItem* item = lbTools->currentItem(); + if (!item) + return; + + int idx = lbTools->row(item); + + if (idx > lbTools->count() - 1) + return; + + if (dynamic_cast(item)) { + KateExternalTool* tool = static_cast(item)->tool; + delete lbTools->takeItem(idx); + lbTools->insertItem(idx + 1, + new ToolItem(nullptr, tool->icon.isEmpty() ? blankIcon() : SmallIcon(tool->icon), tool)); + } else // a separator! + { + delete lbTools->takeItem(idx); + lbTools->insertItem(idx + 1, new QListWidgetItem(QStringLiteral("---"))); + } + + lbTools->setCurrentRow(idx + 1); + slotSelectionChanged(); + emit changed(); + m_changed = true; +} +// END KateExternalToolsConfigWidget +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/externaltoolsplugin.h b/addons/externaltools/externaltoolsplugin.h new file mode 100644 --- /dev/null +++ b/addons/externaltools/externaltoolsplugin.h @@ -0,0 +1,87 @@ +/* This file is part of the KDE project + Copyright (C) 2001 Christoph Cullmann + Copyright (C) 2002 Joseph Wenninger + Copyright (C) 2002 Anders Lund + Copyright (C) 2019 Dominik Haumann + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KTEXTEDITOR_EXTERNALTOOLS_PLUGIN_H +#define KTEXTEDITOR_EXTERNALTOOLS_PLUGIN_H + +#include +#include + +#include + +#include "externaltools.h" + +class KateExternalToolsPluginView; + +class KateExternalToolsPlugin : public KTextEditor::Plugin +{ + Q_OBJECT + +public: + explicit KateExternalToolsPlugin(QObject* parent = nullptr, const QList& = QList()); + virtual ~KateExternalToolsPlugin(); + + int configPages() const override; + KTextEditor::ConfigPage* configPage(int number = 0, QWidget* parent = nullptr) override; + + void reload(); + + QObject* createView(KTextEditor::MainWindow* mainWindow) override; + KateExternalToolsPluginView* extView(QWidget* widget); + +private: + QList m_views; + KateExternalToolsCommand* m_command = nullptr; +private + Q_SLOT : void viewDestroyed(QObject* view); +}; + +class KateExternalToolsPluginView : public QObject, public KXMLGUIClient +{ + Q_OBJECT + +public: + /** + * Constructor. + */ + KateExternalToolsPluginView(KTextEditor::MainWindow* mainWindow); + + /** + * Virtual destructor. + */ + ~KateExternalToolsPluginView(); + + /** + * Returns the associated mainWindow + */ + KTextEditor::MainWindow* mainWindow() const; + + void rebuildMenu(); + + KateExternalToolsMenuAction* externalTools = nullptr; + +private: + KTextEditor::MainWindow* m_mainWindow; +}; + +#endif + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/externaltoolsplugin.cpp b/addons/externaltools/externaltoolsplugin.cpp new file mode 100644 --- /dev/null +++ b/addons/externaltools/externaltoolsplugin.cpp @@ -0,0 +1,153 @@ +/* This file is part of the KDE project + Copyright (C) 2001 Christoph Cullmann + Copyright (C) 2002 Joseph Wenninger + Copyright (C) 2002 Anders Lund + Copyright (C) 2019 Dominik Haumann + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "externaltoolsplugin.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +K_PLUGIN_FACTORY_WITH_JSON(KateExternalToolsFactory, "externaltoolsplugin.json", + registerPlugin();) + +KateExternalToolsPlugin::KateExternalToolsPlugin(QObject* parent, const QList&) + : KTextEditor::Plugin(parent) +{ + if (KAuthorized::authorizeAction(QStringLiteral("shell_access"))) { + m_command = new KateExternalToolsCommand(this); + } +} + +KateExternalToolsPlugin::~KateExternalToolsPlugin() +{ + if (KAuthorized::authorizeAction(QStringLiteral("shell_access"))) { + if (m_command) { + delete m_command; + } + } +} + +QObject* KateExternalToolsPlugin::createView(KTextEditor::MainWindow* mainWindow) +{ + KateExternalToolsPluginView* view = new KateExternalToolsPluginView(mainWindow); + connect(view, SIGNAL(destroyed(QObject*)), this, SLOT(viewDestroyed(QObject*))); + m_views.append(view); + return view; +} + +KateExternalToolsPluginView* KateExternalToolsPlugin::extView(QWidget* widget) +{ + foreach (KateExternalToolsPluginView* view, m_views) { + if (view->mainWindow()->window() == widget) + return view; + } + return nullptr; +} + +void KateExternalToolsPlugin::viewDestroyed(QObject* view) +{ + m_views.removeAll(dynamic_cast(view)); +} + +void KateExternalToolsPlugin::reload() +{ + if (KAuthorized::authorizeAction(QStringLiteral("shell_access"))) { + if (m_command) + m_command->reload(); + } + foreach (KateExternalToolsPluginView* view, m_views) { + view->rebuildMenu(); + } +} + +int KateExternalToolsPlugin::configPages() const +{ + return 1; +} + +KTextEditor::ConfigPage* KateExternalToolsPlugin::configPage(int number, QWidget* parent) +{ + if (number == 0) { + return new KateExternalToolsConfigWidget(parent, this); + } + return nullptr; +} + +KateExternalToolsPluginView::KateExternalToolsPluginView(KTextEditor::MainWindow* mainWindow) + : QObject(mainWindow) + , m_mainWindow(mainWindow) +{ + KXMLGUIClient::setComponentName(QLatin1String("externaltools"), i18n("External Tools")); + setXMLFile(QLatin1String("ui.rc")); + + if (KAuthorized::authorizeAction(QStringLiteral("shell_access"))) { + externalTools + = new KateExternalToolsMenuAction(i18n("External Tools"), actionCollection(), mainWindow, mainWindow); + actionCollection()->addAction(QStringLiteral("tools_external"), externalTools); + externalTools->setWhatsThis(i18n("Launch external helper applications")); + } + + mainWindow->guiFactory()->addClient(this); +} + +void KateExternalToolsPluginView::rebuildMenu() +{ + if (externalTools) { + KXMLGUIFactory* f = factory(); + f->removeClient(this); + reloadXML(); + externalTools->reload(); + qDebug() << "has just returned from externalTools->reload()"; + f->addClient(this); + } +} + +KateExternalToolsPluginView::~KateExternalToolsPluginView() +{ + m_mainWindow->guiFactory()->removeClient(this); + + delete externalTools; + externalTools = nullptr; +} + +KTextEditor::MainWindow* KateExternalToolsPluginView::mainWindow() const +{ + return m_mainWindow; +} + +#include "externaltoolsplugin.moc" + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/externaltoolsplugin.desktop b/addons/externaltools/externaltoolsplugin.desktop new file mode 100644 --- /dev/null +++ b/addons/externaltools/externaltoolsplugin.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=Service +ServiceTypes=KTextEditor/Plugin +X-KDE-Library=externaltoolsplugin +Name=External Tools +Comment=External Tools diff --git a/addons/externaltools/plugin.qrc b/addons/externaltools/plugin.qrc new file mode 100644 --- /dev/null +++ b/addons/externaltools/plugin.qrc @@ -0,0 +1,6 @@ + + + + ui.rc + + diff --git a/addons/externaltools/tooldialog.ui b/addons/externaltools/tooldialog.ui new file mode 100644 --- /dev/null +++ b/addons/externaltools/tooldialog.ui @@ -0,0 +1,353 @@ + + + ToolDialog + + + + 0 + 0 + 470 + 491 + + + + Edit Tool + + + + + + Name: + + + + + + + + + The name will be displayed in the 'Tools->External Tools' menu. + + + Short name of the tool + + + + + + + + + + + + Executable: + + + + + + + + + The executable used by the command. This is used to check if a tool should be displayed; if not set, the first word of <em>command</em> will be used. + + + Application or interpreter + + + + + + + + ../../../../../../../../.designer/backup../../../../../../../../.designer/backup + + + QToolButton::InstantPopup + + + + + + + + + Arguments: + + + + + + + Input: + + + + + + + + + <p>The script to execute to invoke the tool. The script is passed to /bin/sh for execution. The following macros will be expanded:</p><ul><li><code>%URL</code> - the URL of the current document.</li><li><code>%URLs</code> - a list of the URLs of all open documents.</li><li><code>%directory</code> - the URL of the directory containing the current document.</li><li><code>%filename</code> - the filename of the current document.</li><li><code>%line</code> - the current line of the text cursor in the current view.</li><li><code>%column</code> - the column of the text cursor in the current view.</li><li><code>%selection</code> - the selected text in the current view.</li><li><code>%text</code> - the text of the current document.</li></ul> + + + Optional standard input + + + + + + + + ../../../../../../../../.designer/backup../../../../../../../../.designer/backup + + + QToolButton::InstantPopup + + + + + + + + + Mime types: + + + + + + + + + A semicolon-separated list of mime types for which this tool should be available; if this is left empty, the tool is always available. To choose from known mimetypes, press the button on the right. + + + Show tool only for given mime types + + + + + + + Click for a dialog that can help you create a list of mimetypes. + + + + ../../../../../../../../.designer/backup../../../../../../../../.designer/backup + + + + + + + + + Save: + + + + + + + You can choose to save the current or all [modified] documents prior to running the command. This is helpful if you want to pass URLs to an application like, for example, an FTP client. + + + + None + + + + + Current Document + + + + + All Documents + + + + + + + + Reload current document after execution + + + + + + + Output: + + + + + + + + Ignore + + + + + Insert at Cursor Position + + + + + Replace Selected Text + + + + + Replace Current Document + + + + + Append to Current Document + + + + + Insert in New Document + + + + + Display in Pane + + + + + Embedded Console + + + + + + + + Editor command: + + + + + + + If you specify a name here, you can invoke the command from the view command line with exttool-the_name_you_specified_here. Please do not use spaces or tabs in the name. + + + Optional command bar name + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Working directory: + + + + + + + + + Uses current document path if empty + + + + + + + + ../../../../../../../../.designer/backup../../../../../../../../.designer/backup + + + QToolButton::InstantPopup + + + + + + + + + + + Command line arguments + + + + + + + + ../../../../../../../../.designer/backup../../../../../../../../.designer/backup + + + QToolButton::InstantPopup + + + + + + + + + + KIconButton + QPushButton +
kiconbutton.h
+
+
+ + + + buttonBox + accepted() + ToolDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ToolDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/addons/externaltools/ui.rc b/addons/externaltools/ui.rc new file mode 100644 --- /dev/null +++ b/addons/externaltools/ui.rc @@ -0,0 +1,11 @@ + + + + &File + + + &Tools + + + +