diff --git a/plugins/scratchpad/CMakeLists.txt b/plugins/scratchpad/CMakeLists.txt
index 83dfc0703d..31bd368800 100644
--- a/plugins/scratchpad/CMakeLists.txt
+++ b/plugins/scratchpad/CMakeLists.txt
@@ -1,25 +1,27 @@
add_definitions(-DTRANSLATION_DOMAIN=\"kdevscratchpad\")
set(scratchpad_SRCS
scratchpad.cpp
scratchpadview.cpp
scratchpadjob.cpp
)
ki18n_wrap_ui(scratchpad_SRCS scratchpadview.ui)
+qt5_add_resources(scratchpad_SRCS kdevscratchpad.qrc)
+
declare_qt_logging_category(scratchpad_SRCS
TYPE PLUGIN
IDENTIFIER PLUGIN_SCRATCHPAD
CATEGORY_BASENAME "scratchpad"
)
kdevplatform_add_plugin(kdevscratchpad
JSON scratchpad.json
SOURCES ${scratchpad_SRCS}
)
target_link_libraries(kdevscratchpad
KDev::Interfaces
KDev::Util
KDev::OutputView
)
diff --git a/plugins/scratchpad/kdevscratchpad.qrc b/plugins/scratchpad/kdevscratchpad.qrc
new file mode 100644
index 0000000000..2b524eda97
--- /dev/null
+++ b/plugins/scratchpad/kdevscratchpad.qrc
@@ -0,0 +1,6 @@
+
+
+
+ kdevscratchpad.rc
+
+
diff --git a/plugins/scratchpad/kdevscratchpad.rc b/plugins/scratchpad/kdevscratchpad.rc
new file mode 100644
index 0000000000..6e015d7eda
--- /dev/null
+++ b/plugins/scratchpad/kdevscratchpad.rc
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/plugins/scratchpad/scratchpad.cpp b/plugins/scratchpad/scratchpad.cpp
index 9257871d29..864d65f019 100644
--- a/plugins/scratchpad/scratchpad.cpp
+++ b/plugins/scratchpad/scratchpad.cpp
@@ -1,261 +1,280 @@
/* This file is part of KDevelop
*
* Copyright 2018 Amish K. Naidu
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include "scratchpad.h"
#include "scratchpadview.h"
#include "scratchpadjob.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
+#include
#include
#include
#include
#include
#include
#include
K_PLUGIN_FACTORY_WITH_JSON(ScratchpadFactory, "scratchpad.json", registerPlugin(); )
class ScratchpadToolViewFactory
: public KDevelop::IToolViewFactory
{
public:
explicit ScratchpadToolViewFactory(Scratchpad* plugin)
: m_plugin(plugin)
{}
QWidget* create(QWidget* parent = nullptr) override
{
return new ScratchpadView(parent, m_plugin);
}
Qt::DockWidgetArea defaultPosition() override
{
return Qt::LeftDockWidgetArea;
}
QString id() const override
{
return QStringLiteral("org.kdevelop.scratchpad");
}
private:
Scratchpad* const m_plugin;
};
namespace {
KConfigGroup scratchCommands()
{
return KSharedConfig::openConfig()->group("Scratchpad").group("Commands");
}
KConfigGroup mimeCommands()
{
return KSharedConfig::openConfig()->group("Scratchpad").group("Mime Commands");
}
QString commandForScratch(const QFileInfo& file)
{
if (scratchCommands().hasKey(file.fileName())) {
return scratchCommands().readEntry(file.fileName());
}
const auto suffix = file.suffix();
if (mimeCommands().hasKey(suffix)) {
return mimeCommands().readEntry(suffix);
}
const static QHash defaultCommands = {
{QStringLiteral("cpp"), QStringLiteral("g++ -std=c++11 -o /tmp/a.out $f && /tmp/a.out")},
{QStringLiteral("py"), QStringLiteral("python $f")},
{QStringLiteral("js"), QStringLiteral("node $f")},
{QStringLiteral("c"), QStringLiteral("gcc -o /tmp/a.out $f && /tmp/a.out")},
};
return defaultCommands.value(suffix);
}
}
Scratchpad::Scratchpad(QObject* parent, const QVariantList& args)
: KDevelop::IPlugin(QStringLiteral("scratchpad"), parent)
, m_factory(new ScratchpadToolViewFactory(this))
, m_model(new QStandardItemModel(this))
+ , m_runAction(new QAction(this))
{
Q_UNUSED(args);
qCDebug(PLUGIN_SCRATCHPAD) << "Scratchpad plugin is loaded!";
core()->uiController()->addToolView(i18n("Scratchpad"), m_factory);
const QDir dataDir(dataDirectory());
if (!dataDir.exists()) {
qCDebug(PLUGIN_SCRATCHPAD) << "Creating directory" << dataDir;
dataDir.mkpath(QStringLiteral("."));
}
const QFileInfoList scratches = dataDir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
for (const auto& fileInfo : scratches) {
addFileToModel(fileInfo);
// TODO if scratch is open (happens when restarting), set pretty name, below code doesn't work
// auto* document = core()->documentController()->documentForUrl(QUrl::fromLocalFile(fileInfo.absoluteFilePath()));
// if (document) {
// document->setPrettyName(i18n("scratch:%1", fileInfo.fileName()));
// }
}
}
QStandardItemModel* Scratchpad::model() const
{
return m_model;
}
QString Scratchpad::dataDirectory()
{
const static QString dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
+ QLatin1String("/kdevscratchpad/scratches/");
return dir;
}
void Scratchpad::openScratch(const QModelIndex& index)
{
const QUrl scratchUrl = QUrl::fromLocalFile(index.data(FullPathRole).toString());
auto* const document = core()->documentController()->openDocument(scratchUrl);
document->setPrettyName(i18nc("prefix to distinguish scratch tabs", "scratch:%1", index.data().toString()));
}
void Scratchpad::runScratch(const QModelIndex& index)
{
qCDebug(PLUGIN_SCRATCHPAD) << "run" << index.data().toString();
auto command = index.data(RunCommandRole).toString();
command.replace(QLatin1String("$f"), index.data(FullPathRole).toString());
if (!command.isEmpty()) {
auto* job = new ScratchpadJob(command, index.data().toString(), this);
core()->runController()->registerJob(job);
}
}
void Scratchpad::removeScratch(const QModelIndex& index)
{
const QString path = index.data(FullPathRole).toString();
if (auto* document = core()->documentController()->documentForUrl(QUrl::fromLocalFile(path))) {
document->close();
}
if (QFile::remove(path)) {
qCDebug(PLUGIN_SCRATCHPAD) << "removed" << index.data(FullPathRole);
scratchCommands().deleteEntry(index.data().toString());
m_model->removeRow(index.row());
} else {
emit actionFailed(i18n("Failed to remove scratch: %1", index.data().toString()));
}
}
void Scratchpad::createScratch(const QString& name)
{
if (!m_model->findItems(name).isEmpty()) {
emit actionFailed(i18n("Failed to create scratch: Name already in use"));
return;
}
QFile file(dataDirectory() + name);
if (!file.exists() && file.open(QIODevice::WriteOnly)) { // create a new file if it doesn't exist
file.close();
}
if (file.exists()) {
addFileToModel(file);
} else {
emit actionFailed(i18n("Failed to create new scratch"));
}
}
void Scratchpad::renameScratch(const QModelIndex& index, const QString& previousName)
{
const QString newName = index.data().toString();
if (newName.contains(QDir::separator())) {
m_model->setData(index, previousName); // undo
emit actionFailed(i18n("Failed to rename scratch: Names must not include path seperator"));
return;
}
const QString previousPath = dataDirectory() + previousName;
const QString newPath = dataDirectory() + index.data().toString();
if (previousPath == newPath) {
return;
}
if (QFile::rename(previousPath, newPath)) {
qCDebug(PLUGIN_SCRATCHPAD) << "renamed" << previousPath << "to" << newPath;
m_model->setData(index, newPath, Scratchpad::FullPathRole);
m_model->itemFromIndex(index)->setIcon(m_iconProvider.icon(QFileInfo(newPath)));
auto config = scratchCommands();
config.deleteEntry(previousName);
config.writeEntry(newName, index.data(Scratchpad::RunCommandRole));
// close old and re-open the closed document
if (auto* document = core()->documentController()->documentForUrl(QUrl::fromLocalFile(previousPath))) {
// FIXME is there a better way ? this feels hacky
document->close();
document = core()->documentController()->openDocument(QUrl::fromLocalFile(newPath));
document->setPrettyName(i18nc("prefix to distinguish scratch tabs", "scratch:%1", index.data().toString()));
}
} else {
qCWarning(PLUGIN_SCRATCHPAD) << "failed renaming" << previousPath << "to" << newPath;
// rollback
m_model->setData(index, previousName);
emit actionFailed(i18n("Failed renaming scratch."));
}
}
void Scratchpad::addFileToModel(const QFileInfo& fileInfo)
{
auto* const item = new QStandardItem(m_iconProvider.icon(fileInfo), fileInfo.fileName());
item->setData(fileInfo.absoluteFilePath(), FullPathRole);
const auto command = commandForScratch(fileInfo);
item->setData(command, RunCommandRole);
scratchCommands().writeEntry(item->text(), item->data(RunCommandRole));
m_model->appendRow(item);
}
void Scratchpad::setCommand(const QModelIndex& index, const QString& command)
{
qCDebug(PLUGIN_SCRATCHPAD) << "set command" << index.data();
m_model->setData(index, command, RunCommandRole);
scratchCommands().writeEntry(index.data().toString(), command);
mimeCommands().writeEntry(QFileInfo(index.data().toString()).suffix(), command);
}
+QAction* Scratchpad::runAction() const
+{
+ return m_runAction;
+}
+
+void Scratchpad::createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions)
+{
+ Q_UNUSED(window);
+
+ xmlFile = QStringLiteral("kdevscratchpad.rc");
+
+ // add to gui action collection, so that the shorcut is easily configurable
+ // action setup done in ScratchpadView
+ actions.addAction(QStringLiteral("run_scratch"), m_runAction);
+}
+
+
#include "scratchpad.moc"
diff --git a/plugins/scratchpad/scratchpad.h b/plugins/scratchpad/scratchpad.h
index 5598c96f11..3e8b9b9948 100644
--- a/plugins/scratchpad/scratchpad.h
+++ b/plugins/scratchpad/scratchpad.h
@@ -1,71 +1,78 @@
/* This file is part of KDevelop
*
* Copyright 2018 Amish K. Naidu
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#ifndef SCRATCHPAD_H
#define SCRATCHPAD_H
#include
#include
class ScratchpadToolViewFactory;
class QStandardItemModel;
class QModelIndex;
class QFileInfo;
class QString;
+class QAction;
class Scratchpad
: public KDevelop::IPlugin
{
Q_OBJECT
public:
Scratchpad(QObject* parent, const QVariantList& args);
QStandardItemModel* model() const;
+ QAction* runAction() const;
+
static QString dataDirectory();
+ void createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) override;
+
enum ExtraRoles {
FullPathRole = Qt::UserRole + 1,
RunCommandRole,
};
public Q_SLOTS:
void openScratch(const QModelIndex& index);
void runScratch(const QModelIndex& index);
void removeScratch(const QModelIndex& index);
void createScratch(const QString& name);
void renameScratch(const QModelIndex& index, const QString& previousName);
void setCommand(const QModelIndex& index, const QString& command);
Q_SIGNALS:
void actionFailed(const QString& message);
private:
void addFileToModel(const QFileInfo& fileInfo);
ScratchpadToolViewFactory* m_factory;
QStandardItemModel* m_model;
QFileIconProvider m_iconProvider;
+
+ QAction* const m_runAction;
};
#endif // SCRATCHPAD_H
diff --git a/plugins/scratchpad/scratchpadview.cpp b/plugins/scratchpad/scratchpadview.cpp
index 8ca855a2d8..3835ad64ef 100644
--- a/plugins/scratchpad/scratchpadview.cpp
+++ b/plugins/scratchpad/scratchpadview.cpp
@@ -1,225 +1,227 @@
/* This file is part of KDevelop
*
* Copyright 2018 Amish K. Naidu
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include "scratchpadview.h"
#include "scratchpad.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// Use a delegate because the dataChanged signal doesn't tell us the previous name
class FileRenameDelegate
: public QStyledItemDelegate
{
public:
FileRenameDelegate(QObject* parent, Scratchpad* scratchpad)
: QStyledItemDelegate(parent)
, m_scratchpad(scratchpad)
{
}
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override
{
const QString previousName = index.data().toString();
QStyledItemDelegate::setModelData(editor, model, index);
const auto* proxyModel = static_cast(model);
m_scratchpad->renameScratch(proxyModel->mapToSource(index), previousName);
}
private:
Scratchpad* m_scratchpad;
};
EmptyMessageListView::EmptyMessageListView(QWidget* parent)
: QListView(parent)
{
}
void EmptyMessageListView::paintEvent(QPaintEvent* event)
{
if (model() && model()->rowCount(rootIndex()) > 0) {
QListView::paintEvent(event);
} else {
QPainter painter(viewport());
const auto margin =
QMargins(parentWidget()->style()->pixelMetric(QStyle::PM_LayoutLeftMargin), 0,
parentWidget()->style()->pixelMetric(QStyle::PM_LayoutRightMargin), 0);
painter.drawText(rect() - margin, Qt::AlignCenter | Qt::TextWordWrap, m_message);
}
}
void EmptyMessageListView::setEmptyMessage(const QString& message)
{
m_message = message;
}
ScratchpadView::ScratchpadView(QWidget* parent, Scratchpad* scratchpad)
: QWidget(parent)
, m_scratchpad(scratchpad)
{
setupUi(this);
setupActions();
setWindowTitle(i18n("Scratchpad"));
setWindowIcon(QIcon::fromTheme(QStringLiteral("note")));
auto* const modelProxy = new QSortFilterProxyModel(this);
modelProxy->setSourceModel(m_scratchpad->model());
modelProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
modelProxy->setSortCaseSensitivity(Qt::CaseInsensitive);
modelProxy->setSortRole(Qt::DisplayRole);
connect(m_filter, &QLineEdit::textEdited,
modelProxy, &QSortFilterProxyModel::setFilterWildcard);
scratchView->setModel(modelProxy);
scratchView->setItemDelegate(new FileRenameDelegate(this, m_scratchpad));
scratchView->setEmptyMessage(i18n("Scratchpad lets you quickly run and experiment with code without a full project, and even store todos. Create a new scratch to start."));
connect(scratchView, &QListView::activated, this, &ScratchpadView::scratchActivated);
connect(m_scratchpad, &Scratchpad::actionFailed, [this](const QString& message) {
KMessageBox::sorry(this, message);
});
connect(commandWidget, &QLineEdit::returnPressed, this, &ScratchpadView::runSelectedScratch);
connect(commandWidget, &QLineEdit::returnPressed, [this] {
m_scratchpad->setCommand(proxyModel()->mapToSource(currentIndex()), commandWidget->text());
});
commandWidget->setToolTip(i18n("Command to run this scratch. $f will expand to the scratch path"));
commandWidget->setPlaceholderText(commandWidget->toolTip());
// change active scratch when changing document
connect(KDevelop::ICore::self()->documentController(), &KDevelop::IDocumentController::documentActivated,
[this](const KDevelop::IDocument* document) {
if (document->url().isLocalFile()) {
const auto* model = scratchView->model();
const auto index = model->match(model->index(0, 0), Scratchpad::FullPathRole,
document->url().toLocalFile()).value({});
if (index.isValid()) {
scratchView->setCurrentIndex(index);
}
}
});
validateItemActions();
}
void ScratchpadView::setupActions()
{
QAction* action = new QAction(QIcon::fromTheme(QStringLiteral("list-add")), i18n("New Scratch"), this);
connect(action, &QAction::triggered, this, &ScratchpadView::createScratch);
addAction(action);
action = new QAction(QIcon::fromTheme(QStringLiteral("list-remove")), i18n("Remove Scratch"), this);
connect(action, &QAction::triggered, [this] {
m_scratchpad->removeScratch(proxyModel()->mapToSource(currentIndex()));
validateItemActions();
});
addAction(action);
m_itemActions.push_back(action);
action = new QAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Rename Scratch"), this);
connect(action, &QAction::triggered, [this] {
scratchView->edit(scratchView->currentIndex());
});
addAction(action);
m_itemActions.push_back(action);
- action = new QAction(QIcon::fromTheme(QStringLiteral("media-playback-start")), i18n("Run Scratch"), this);
+ action = m_scratchpad->runAction();
+ action->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
+ action->setText(i18n("Run Scratch"));
connect(action, &QAction::triggered, this, &ScratchpadView::runSelectedScratch);
addAction(action);
m_itemActions.push_back(action);
m_filter = new QLineEdit(this);
m_filter->setPlaceholderText(i18n("Filter..."));
auto filterAction = new QWidgetAction(this);
filterAction->setDefaultWidget(m_filter);
addAction(filterAction);
}
void ScratchpadView::validateItemActions()
{
bool enable = currentIndex().isValid();
for (auto* action : m_itemActions) {
action->setEnabled(enable);
}
commandWidget->setReadOnly(!enable);
if (!enable) {
commandWidget->clear();
}
}
void ScratchpadView::runSelectedScratch()
{
const auto sourceIndex = proxyModel()->mapToSource(currentIndex());
if (auto* document = KDevelop::ICore::self()->documentController()->documentForUrl(
QUrl::fromLocalFile(sourceIndex.data(Scratchpad::FullPathRole).toString()))) {
document->save();
}
m_scratchpad->setCommand(sourceIndex, commandWidget->text());
m_scratchpad->runScratch(sourceIndex);
}
void ScratchpadView::scratchActivated(const QModelIndex& index)
{
validateItemActions();
m_scratchpad->openScratch(proxyModel()->mapToSource(index));
commandWidget->setText(index.data(Scratchpad::RunCommandRole).toString());
}
void ScratchpadView::createScratch()
{
QString name = QInputDialog::getText(this, i18n("Create New Scratch"),
i18n("Enter name for scratch file:"),
QLineEdit::Normal,
QStringLiteral("example.cpp"));
if (!name.isEmpty()) {
m_scratchpad->createScratch(name);
}
}
QAbstractProxyModel* ScratchpadView::proxyModel() const
{
return static_cast(scratchView->model());
}
QModelIndex ScratchpadView::currentIndex() const
{
return scratchView->currentIndex();
}