diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 53a2a23848..535c739674 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,158 +1,159 @@ # BEGIN: Analyzers add_subdirectory(clangtidy) add_subdirectory(clazy) add_subdirectory(cppcheck) if(UNIX AND NOT (APPLE OR CYGWIN)) add_subdirectory(heaptrack) endif() # END: Analyzers # BEGIN: Debuggers add_subdirectory(debuggercommon) add_subdirectory(lldb) add_subdirectory(gdb) # END: Debuggers # BEGIN: Documentation find_package(Qt5Help CONFIG) set_package_properties(Qt5Help PROPERTIES PURPOSE "The Help module for the Qt toolkit, needed for the qthelp plugin" URL "https://www.qt.io/" TYPE OPTIONAL) if(Qt5Help_FOUND) ecm_optional_add_subdirectory(qthelp) endif() ecm_optional_add_subdirectory(manpage) # END: Documentation # BEGIN: Formatters add_subdirectory(astyle) add_subdirectory(customscript) # END: Formatters # BEGIN: Languages ecm_optional_add_subdirectory(custom-definesandincludes) # 3rdparty/qtcreator-libs uses QT_CONFIG which was added for 5.8 if(NOT Qt5_VERSION VERSION_LESS "5.8.0") ecm_optional_add_subdirectory(qmljs) else() message(WARNING "Will not build QML/JS plugin, needs Qt >= 5.8.0") endif() find_package(Clang 3.8) set(clangSearchHint "") if (NOT CLANG_FOUND) set(clangSearchHint "Please install a package providing libclang. Either pass -DLLVM_ROOT=/path/to/llvm-prefix or install the 'llvm-config' command-line utility for auto-detection.") endif() set_package_properties(Clang PROPERTIES DESCRIPTION "Clang libraries from the LLVM project. ${clangSearchHint}" PURPOSE "Used for KDevelop's C++/C support plugin." TYPE REQUIRED ) if (CLANG_FOUND) if (NOT CLANG_CLANG_LIB) message(FATAL_ERROR "Could not find the Clang C library: libclang") endif() add_library(Clang::clang UNKNOWN IMPORTED) set_property(TARGET Clang::clang PROPERTY IMPORTED_LOCATION ${CLANG_CLANG_LIB}) ecm_optional_add_subdirectory(clang) endif() # END: Languages # BEGIN: Project builders add_subdirectory(makebuilder) add_subdirectory(ninjabuilder) ecm_optional_add_subdirectory(cmakebuilder) if (KDevelop-PG-Qt_FOUND) ecm_optional_add_subdirectory(qmakebuilder) endif() # END: Project builders # BEGIN: Project managers ecm_optional_add_subdirectory(cmake) ecm_optional_add_subdirectory(custommake) ecm_optional_add_subdirectory(custom-buildsystem) if (KDevelop-PG-Qt_FOUND) ecm_optional_add_subdirectory(qmakemanager) endif() ecm_optional_add_subdirectory(genericprojectmanager) # END: Project managers # BEGIN: Runtimes add_subdirectory(android) if (UNIX) add_subdirectory(docker) add_subdirectory(flatpak) endif() # END: Runtimes # BEGIN: VCS ecm_optional_add_subdirectory(bazaar) ecm_optional_add_subdirectory(git) ecm_optional_add_subdirectory(perforce) find_package(SubversionLibrary) set_package_properties(SubversionLibrary PROPERTIES PURPOSE "Support for Subversion integration" URL "http://subversion.tigris.org" TYPE OPTIONAL) if(SubversionLibrary_FOUND) ecm_optional_add_subdirectory(subversion) endif() add_subdirectory(vcschangesview) # END: VCS # BEGIN: Others add_subdirectory(appwizard) add_subdirectory(codeutils) add_subdirectory(contextbrowser) add_subdirectory(documentswitcher) add_subdirectory(documentview) add_subdirectory(execute) add_subdirectory(executescript) add_subdirectory(externalscript) add_subdirectory(filemanager) add_subdirectory(filetemplates) add_subdirectory(grepview) add_subdirectory(openwith) add_subdirectory(outlineview) add_subdirectory(patchreview) add_subdirectory(problemreporter) add_subdirectory(projectfilter) add_subdirectory(projectmanagerview) add_subdirectory(quickopen) add_subdirectory(sourceformatter) add_subdirectory(standardoutputview) add_subdirectory(switchtobuddy) add_subdirectory(testview) +add_subdirectory(scratchpad) ecm_optional_add_subdirectory(classbrowser) ecm_optional_add_subdirectory(executeplasmoid) ecm_optional_add_subdirectory(ghprovider) ecm_optional_add_subdirectory(kdeprovider) ecm_optional_add_subdirectory(konsole) if (Qt5QuickWidgets_FOUND) add_subdirectory(welcomepage) endif() find_package(OktetaKastenControllers 0.3.1 CONFIG) set_package_properties(OktetaKastenControllers PROPERTIES PURPOSE "Required for building Okteta KDevelop plugin." URL "http://kde.org/" TYPE OPTIONAL) if (OktetaKastenControllers_FOUND) find_package(KastenControllers CONFIG) set_package_properties(KastenControllers PROPERTIES PURPOSE "Required for building Okteta KDevelop plugin." URL "http://kde.org/" TYPE OPTIONAL) endif() if (OktetaKastenControllers_FOUND AND KastenControllers_FOUND) add_subdirectory(okteta) endif() # END: Others diff --git a/plugins/scratchpad/CMakeLists.txt b/plugins/scratchpad/CMakeLists.txt new file mode 100644 index 0000000000..83dfc0703d --- /dev/null +++ b/plugins/scratchpad/CMakeLists.txt @@ -0,0 +1,25 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"kdevscratchpad\") +set(scratchpad_SRCS + scratchpad.cpp + scratchpadview.cpp + scratchpadjob.cpp +) + +ki18n_wrap_ui(scratchpad_SRCS scratchpadview.ui) + +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/emptymessagelistview.h b/plugins/scratchpad/emptymessagelistview.h new file mode 100644 index 0000000000..6c8ee45f6b --- /dev/null +++ b/plugins/scratchpad/emptymessagelistview.h @@ -0,0 +1,38 @@ +/* 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 EMPTYMESSAGELISTVIEW_H +#define EMPTYMESSAGELISTVIEW_H + +#include + +// subclass to show a message when the list is empty +class EmptyMessageListView + : public QListView +{ +public: + EmptyMessageListView(QWidget* parent); + void setEmptyMessage(const QString& message); +protected: + void paintEvent(QPaintEvent* event) override; +private: + QString m_message; +}; + +#endif // EMPTYMESSAGELISTVIEW_H diff --git a/plugins/scratchpad/scratchpad.cpp b/plugins/scratchpad/scratchpad.cpp new file mode 100644 index 0000000000..83ebe887e1 --- /dev/null +++ b/plugins/scratchpad/scratchpad.cpp @@ -0,0 +1,261 @@ +/* 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 + +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)) +{ + 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.open(QIODevice::NewOnly)) { // create a new file + 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); +} + +#include "scratchpad.moc" diff --git a/plugins/scratchpad/scratchpad.h b/plugins/scratchpad/scratchpad.h new file mode 100644 index 0000000000..5598c96f11 --- /dev/null +++ b/plugins/scratchpad/scratchpad.h @@ -0,0 +1,71 @@ +/* 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 Scratchpad + : public KDevelop::IPlugin +{ + Q_OBJECT + +public: + Scratchpad(QObject* parent, const QVariantList& args); + + QStandardItemModel* model() const; + + static QString dataDirectory(); + + 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; +}; + +#endif // SCRATCHPAD_H diff --git a/plugins/scratchpad/scratchpad.json b/plugins/scratchpad/scratchpad.json new file mode 100644 index 0000000000..2abfc51af9 --- /dev/null +++ b/plugins/scratchpad/scratchpad.json @@ -0,0 +1,12 @@ +{ + "KPlugin": { + "Description": "Scratchpad lets you quickly run and experiment with code without a full project, and even store todos.", + "Id": "scratchpad", + "Name": "Scratchpad", + "ServiceTypes": [ + "KDevelop/Plugin" + ] + }, + "X-KDevelop-Category": "Global", + "X-KDevelop-Mode": "GUI" +} diff --git a/plugins/scratchpad/scratchpadjob.cpp b/plugins/scratchpad/scratchpadjob.cpp new file mode 100644 index 0000000000..b963213da5 --- /dev/null +++ b/plugins/scratchpad/scratchpadjob.cpp @@ -0,0 +1,102 @@ +/* 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 "scratchpadjob.h" +#include "scratchpad.h" + +#include + +#include +#include + +#include +#include + +#include + +ScratchpadJob::ScratchpadJob(const QString& command, const QString& title, QObject* parent) + : KDevelop::OutputJob(parent) + , m_process(new KProcess(this)) + , m_lineMaker(new KDevelop::ProcessLineMaker(m_process, this)) +{ + qCDebug(PLUGIN_SCRATCHPAD) << "Creating job for" << title; + + setCapabilities(Killable); + + if (!command.isEmpty()) { + m_process->setShellCommand(command); + + setStandardToolView(KDevelop::IOutputView::RunView); + setTitle(i18nc("prefix to distinguish scratch tabs", "scratch:%1", title)); + + auto* model = new KDevelop::OutputModel(this); + setModel(model); + + connect(m_lineMaker, &KDevelop::ProcessLineMaker::receivedStdoutLines, + model, &KDevelop::OutputModel::appendLines); + connect(m_lineMaker, &KDevelop::ProcessLineMaker::receivedStderrLines, + model, &KDevelop::OutputModel::appendLines); + m_process->setOutputChannelMode(KProcess::MergedChannels); + connect(m_process, QOverload::of(&KProcess::finished), + this, &ScratchpadJob::processFinished); + connect(m_process, &KProcess::errorOccurred, this, &ScratchpadJob::processError); + } else { + qCCritical(PLUGIN_SCRATCHPAD) << "Empty command in scratch job."; + deleteLater(); + } +} + +void ScratchpadJob::start() +{ + const auto program = m_process->program().join(QLatin1Char(' ')); + if (!program.trimmed().isEmpty()) { + startOutput(); + outputModel()->appendLine(i18n("Running %1...", program)); + m_process->start(); + } +} + +bool ScratchpadJob::doKill() +{ + qCDebug(PLUGIN_SCRATCHPAD) << "killing process"; + m_process->kill(); + return true; +} + +void ScratchpadJob::processFinished(int exitCode, QProcess::ExitStatus) +{ + qCDebug(PLUGIN_SCRATCHPAD) << "finished process"; + m_lineMaker->flushBuffers(); + outputModel()->appendLine(i18n("Process finished with exit code %1.", exitCode)); + emitResult(); +} + +void ScratchpadJob::processError(QProcess::ProcessError error) +{ + qCDebug(PLUGIN_SCRATCHPAD) << "process encountered error" << error; + outputModel()->appendLine(i18n("Failed to run scratch: %1", + QLatin1String(QMetaEnum::fromType().valueToKey(error)))); + emitResult(); +} + +KDevelop::OutputModel* ScratchpadJob::outputModel() const +{ + return static_cast(model()); +} diff --git a/plugins/scratchpad/scratchpadjob.h b/plugins/scratchpad/scratchpadjob.h new file mode 100644 index 0000000000..57acc17326 --- /dev/null +++ b/plugins/scratchpad/scratchpadjob.h @@ -0,0 +1,56 @@ +/* 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 SCRATCHPADJOB_H +#define SCRATCHPADJOB_H + +#include + +#include + +namespace KDevelop { +class OutputModel; +class ProcessLineMaker; +} +class KProcess; + +class ScratchpadJob + : public KDevelop::OutputJob +{ + Q_OBJECT + +public: + ScratchpadJob(const QString& command, const QString& title, QObject* parent); + + void start() override; + bool doKill() override; + +private Q_SLOTS: + void processFinished(int exitCode, QProcess::ExitStatus status); + void processError(QProcess::ProcessError error); + +private: + KDevelop::OutputModel* outputModel() const; + + KProcess* m_process; + KDevelop::ProcessLineMaker* m_lineMaker; +}; + +#endif // SCRATCHPADJOB_H diff --git a/plugins/scratchpad/scratchpadview.cpp b/plugins/scratchpad/scratchpadview.cpp new file mode 100644 index 0000000000..8ca855a2d8 --- /dev/null +++ b/plugins/scratchpad/scratchpadview.cpp @@ -0,0 +1,225 @@ +/* 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); + 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(); +} diff --git a/plugins/scratchpad/scratchpadview.h b/plugins/scratchpad/scratchpadview.h new file mode 100644 index 0000000000..65a527580b --- /dev/null +++ b/plugins/scratchpad/scratchpadview.h @@ -0,0 +1,62 @@ +/* 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 SCRATCHPADVIEW_H +#define SCRATCHPADVIEW_H + +#include + +#include + +#include "ui_scratchpadview.h" + +class Scratchpad; + +class QModelIndex; +class QLineEdit; +class QAbstractProxyModel; + +class ScratchpadView + : public QWidget + , public Ui::ScratchpadBaseView +{ + Q_OBJECT + +public: + ScratchpadView(QWidget* parent, Scratchpad* scratchpad); + +private Q_SLOTS: + void runSelectedScratch(); + void scratchActivated(const QModelIndex& index); + void createScratch(); + +private: + QAbstractProxyModel* proxyModel() const; + QModelIndex currentIndex() const; + + void setupActions(); + void validateItemActions(); + + Scratchpad* m_scratchpad; + QLineEdit* m_filter = nullptr; + QVector m_itemActions; +}; + +#endif // SCRATCHPADVIEW_H diff --git a/plugins/scratchpad/scratchpadview.ui b/plugins/scratchpad/scratchpadview.ui new file mode 100644 index 0000000000..b6f91ceeb1 --- /dev/null +++ b/plugins/scratchpad/scratchpadview.ui @@ -0,0 +1,54 @@ + + + Robert Gruber <rgruber@users.salomon.at> + ScratchpadBaseView + + + + 0 + 0 + 232 + 389 + + + + + 2 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + EmptyMessageListView + QListView +
emptymessagelistview.h
+
+
+ + scratchTree + + + +