diff --git a/addons/close-except-like/close_except_plugin.cpp b/addons/close-except-like/close_except_plugin.cpp index d0a1b4286..566428ccd 100644 --- a/addons/close-except-like/close_except_plugin.cpp +++ b/addons/close-except-like/close_except_plugin.cpp @@ -1,371 +1,371 @@ /** * \file * * \brief Kate Close Except/Like plugin implementation * * Copyright (C) 2012 Alex Turbov * * \date Thu Mar 8 08:13:43 MSK 2012 -- Initial design */ /* * KateCloseExceptPlugin 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 3 of the License, or * (at your option) any later version. * * KateCloseExceptPlugin 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, see . */ // Project specific includes #include "config.h" #include "close_except_plugin.h" #include "close_confirm_dialog.h" // Standard includes #include #include #include #include #include #include #include #include -#include +#include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(CloseExceptPluginFactory, "katecloseexceptplugin.json", registerPlugin();) /*K_EXPORT_PLUGIN( CloseExceptPluginFactory( KAboutData( "katecloseexceptplugin" , "katecloseexceptplugin" , ki18n("Close Except/Like Plugin") , PLUGIN_VERSION , ki18n("Close all documents started from specified path") , KAboutData::License_LGPL_V3 ) ) )*/ namespace kate { //BEGIN CloseExceptPlugin CloseExceptPlugin::CloseExceptPlugin( QObject* application , const QList& ) : KTextEditor::Plugin(application) { } QObject* CloseExceptPlugin::createView(KTextEditor::MainWindow* parent) { return new CloseExceptPluginView(parent, this); } void CloseExceptPlugin::readSessionConfig(const KConfigGroup& config) { const KConfigGroup scg(&config, QStringLiteral("menu")); m_show_confirmation_needed = scg.readEntry(QStringLiteral("ShowConfirmation"), true); } void CloseExceptPlugin::writeSessionConfig(KConfigGroup& config) { KConfigGroup scg(&config, QStringLiteral("menu")); scg.writeEntry(QStringLiteral("ShowConfirmation"), m_show_confirmation_needed); scg.sync(); } //END CloseExceptPlugin //BEGIN CloseExceptPluginView CloseExceptPluginView::CloseExceptPluginView( KTextEditor::MainWindow* mw , CloseExceptPlugin* plugin ) : QObject(mw) , KXMLGUIClient() , m_plugin(plugin) , m_show_confirmation_action(new KToggleAction(i18nc("@action:inmenu", "Show Confirmation"), this)) , m_except_menu(new KActionMenu( i18nc("@action:inmenu close docs except the following...", "Close Except") , this )) , m_like_menu(new KActionMenu( i18nc("@action:inmenu close docs like the following...", "Close Like") , this )) , m_mainWindow(mw) { KXMLGUIClient::setComponentName (QStringLiteral("katecloseexceptplugin"), i18n("Close Except/Like Plugin")); setXMLFile( QStringLiteral("ui.rc") ); actionCollection()->addAction(QStringLiteral("file_close_except"), m_except_menu); actionCollection()->addAction(QStringLiteral("file_close_like"), m_like_menu); connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::documentCreated, this, &CloseExceptPluginView::documentCreated); // Configure toggle action and connect it to update state m_show_confirmation_action->setChecked(m_plugin->showConfirmationNeeded()); connect(m_show_confirmation_action.data(), &KToggleAction::toggled, m_plugin, &CloseExceptPlugin::toggleShowConfirmation); // connect(m_mainWindow, &KTextEditor::MainWindow::viewCreated, this, &CloseExceptPluginView::viewCreated); // Fill menu w/ currently opened document masks/groups updateMenu(); m_mainWindow->guiFactory()->addClient(this); } CloseExceptPluginView::~CloseExceptPluginView() { m_mainWindow->guiFactory()->removeClient(this); } void CloseExceptPluginView::viewCreated(KTextEditor::View* view) { connectToDocument(view->document()); updateMenu(); } void CloseExceptPluginView::documentCreated(KTextEditor::Editor*, KTextEditor::Document* document) { connectToDocument(document); updateMenu(); } void CloseExceptPluginView::connectToDocument(KTextEditor::Document* document) { // Subscribe self to document close and name changes connect(document, &KTextEditor::Document::aboutToClose, this, &CloseExceptPluginView::updateMenuSlotStub); connect(document, &KTextEditor::Document::documentNameChanged, this, &CloseExceptPluginView::updateMenuSlotStub); connect(document, &KTextEditor::Document::documentUrlChanged, this, &CloseExceptPluginView::updateMenuSlotStub); } void CloseExceptPluginView::updateMenuSlotStub(KTextEditor::Document*) { updateMenu(); } void CloseExceptPluginView::appendActionsFrom( const std::set& paths , actions_map_type& actions , KActionMenu* menu , QSignalMapper* mapper ) { Q_FOREACH(const QUrl& path, paths) { QString action = path.path() + QLatin1Char('*'); actions[action] = QPointer(new QAction(action, menu)); menu->addAction(actions[action]); //connect(actions[action], &QAction::triggered, mapper, &QSignalMapper::map); connect(actions[action].data(), &QAction::triggered, mapper, static_cast(&QSignalMapper::map)); mapper->setMapping(actions[action], action); } } void CloseExceptPluginView::appendActionsFrom( const std::set& masks , actions_map_type& actions , KActionMenu* menu , QSignalMapper* mapper ) { Q_FOREACH(const QString& mask, masks) { QString action = mask.startsWith(QLatin1Char('*')) ? mask : mask + QLatin1Char('*'); actions[action] = QPointer(new QAction(action, menu)); menu->addAction(actions[action]); connect(actions[action].data(), &QAction::triggered, mapper, static_cast(&QSignalMapper::map)); mapper->setMapping(actions[action], action); } } QPointer CloseExceptPluginView::updateMenu( const std::set& paths , const std::set& masks , actions_map_type& actions , KActionMenu* menu ) { // turn menu ON or OFF depending on collected results menu->setEnabled(!paths.empty()); // Clear previous menus for (actions_map_type::iterator it = actions.begin(), last = actions.end(); it !=last;) { menu->removeAction(*it); actions.erase(it++); } // Form a new one QPointer mapper = QPointer(new QSignalMapper(this)); appendActionsFrom(paths, actions, menu, mapper); if (!masks.empty()) { if (!paths.empty()) menu->addSeparator(); // Add separator between paths and file's ext filters appendActionsFrom(masks, actions, menu, mapper); } // Append 'Show Confirmation' toggle menu item menu->addSeparator(); // Add separator between paths and show confirmation menu->addAction(m_show_confirmation_action); return mapper; } void CloseExceptPluginView::updateMenu() { const QList& docs = KTextEditor::Editor::instance()->application()->documents(); if (docs.size() < 2) { qDebug() << "No docs r (or the only) opened right now --> disable menu"; m_except_menu->setEnabled(false); m_except_menu->addSeparator(); m_like_menu->setEnabled(false); m_like_menu->addSeparator(); /// \note It seems there is always a document present... it named \em 'Untitled' } else { // Iterate over documents and form a set of candidates typedef std::set paths_set_type; typedef std::set paths_set_type_masks; paths_set_type doc_paths; paths_set_type_masks masks; Q_FOREACH(KTextEditor::Document* document, docs) { const QString& ext = QFileInfo(document->url().path()).completeSuffix(); if (!ext.isEmpty()) masks.insert(QStringLiteral("*.") + ext); doc_paths.insert(KIO::upUrl(document->url())); } paths_set_type paths = doc_paths; qDebug() << "stage #1: Collected" << paths.size() << "paths and" << masks.size() << "masks"; // Add common paths to the collection for (paths_set_type::iterator it = doc_paths.begin(), last = doc_paths.end(); it != last; ++it) { for ( QUrl url = *it ; (!url.path().isEmpty()) && url.path() != QStringLiteral("/") ; url = KIO::upUrl(url) ) { paths_set_type::iterator not_it = it; for (++not_it; not_it != last; ++not_it) if (!not_it->path().startsWith(url.path())) break; if (not_it == last) { paths.insert(url); break; } } } qDebug() << "stage #2: Collected" << paths.size() << "paths and" << masks.size() << "masks"; // m_except_mapper = updateMenu(paths, masks, m_except_actions, m_except_menu); m_like_mapper = updateMenu(paths, masks, m_like_actions, m_like_menu); connect(m_except_mapper.data(), static_cast(&QSignalMapper::mapped), this, &CloseExceptPluginView::closeExcept); connect(m_like_mapper.data(), static_cast(&QSignalMapper::mapped), this, &CloseExceptPluginView::closeLike); } } void CloseExceptPluginView::close(const QString& item, const bool close_if_match) { QChar asterisk=QLatin1Char('*'); assert( "Parameter seems invalid! Is smth has changed in the code?" && !item.isEmpty() && (item[0] == asterisk || item[item.size() - 1] == asterisk) ); const bool is_path = item[0] != asterisk; const QString mask = is_path ? item.left(item.size() - 1) : item; qDebug() << "Going to close items [" << close_if_match << "/" << is_path << "]: " << mask; QList docs2close; const QList& docs = KTextEditor::Editor::instance()->application()->documents(); Q_FOREACH(KTextEditor::Document* document, docs) { const QString& path = KIO::upUrl(document->url()).path(); /// \note Take a dot in account, so \c *.c would not match for \c blah.kcfgc const QString& ext = QLatin1Char('.') + QFileInfo(document->url().fileName()).completeSuffix(); const bool match = (!is_path && mask.endsWith(ext)) || (is_path && path.startsWith(mask)) ; if (match == close_if_match) { qDebug() << "*** Will close: " << document->url(); docs2close.push_back(document); } } if (docs2close.isEmpty()) { displayMessage( i18nc("@title:window", "Error") , i18nc("@info:tooltip", "No files to close ...") , KTextEditor::Message::Error ); return; } // Show confirmation dialog if needed const bool removeNeeded = !m_plugin->showConfirmationNeeded() || CloseConfirmDialog(docs2close, m_show_confirmation_action, qobject_cast(this)).exec(); if (removeNeeded) { if (docs2close.isEmpty()) { displayMessage( i18nc("@title:window", "Error") , i18nc("@info:tooltip", "No files to close ...") , KTextEditor::Message::Error ); } else { // Close 'em all! KTextEditor::Editor::instance()->application()->closeDocuments(docs2close); updateMenu(); displayMessage( i18nc("@title:window", "Done") , i18np("%1 file closed", "%1 files closed", docs2close.size()) , KTextEditor::Message::Positive ); } } } void CloseExceptPluginView::displayMessage(const QString &title, const QString &msg, KTextEditor::Message::MessageType level) { KTextEditor::View *kv = m_mainWindow->activeView(); if (!kv) return; delete m_infoMessage; m_infoMessage = new KTextEditor::Message(xi18nc("@info", "%1%2", title, msg), level); m_infoMessage->setWordWrap(true); m_infoMessage->setPosition(KTextEditor::Message::TopInView); m_infoMessage->setAutoHide(5000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(kv); kv->document()->postMessage(m_infoMessage); } //END CloseExceptPluginView } // namespace kate #include "close_except_plugin.moc" // kate: hl C++11/Qt4; diff --git a/addons/lumen/dcd.cpp b/addons/lumen/dcd.cpp index 3019203f2..1c399ab59 100644 --- a/addons/lumen/dcd.cpp +++ b/addons/lumen/dcd.cpp @@ -1,358 +1,358 @@ /* * Copyright 2014-2015 David Herberth kde@dav1d.de * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . **/ #include "dcd.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include char DCDCompletionItemType::toChar(DCDCompletionItemType e) { switch (e) { case Invalid: return 0; case Calltip: return 1; case ClassName: return 'c'; case InterfaceName: return 'i'; case StructName: return 's'; case UnionName: return 'u'; case VariableName: return 'v'; case MemberVariableName: return 'm'; case Keyword: return 'k'; case FunctionName: return 'f'; case EnumName: return 'g'; case EnumMember: return 'e'; case PackageName: return 'p'; case ModuleName: return 'M'; } return 0; } DCDCompletionItemType::DCDCompletionItemType DCDCompletionItemType::fromChar(char c) { switch (c) { case 0: return Invalid; case 1: return Calltip; case 'c': return ClassName; case 'i': return InterfaceName; case 's': return StructName; case 'u': return UnionName; case 'v': return VariableName; case 'm': return MemberVariableName; case 'k': return Keyword; case 'f': return FunctionName; case 'g': return EnumName; case 'e': return EnumMember; case 'p': return PackageName; case 'M': return ModuleName; } return Invalid; } DCDCompletionItem::DCDCompletionItem(DCDCompletionItemType::DCDCompletionItemType t, QString s): type(t), name(s) { } #define RETURN_CACHED_ICON(name) {static QIcon icon(QIcon::fromTheme(QStringLiteral(name)).pixmap(QSize(16, 16))); return icon;} QIcon DCDCompletionItem::icon() const { using namespace DCDCompletionItemType; switch (type) { case Invalid: break; case Calltip: RETURN_CACHED_ICON("code-function") case ClassName: RETURN_CACHED_ICON("code-class") case InterfaceName: RETURN_CACHED_ICON("code-class") case StructName: RETURN_CACHED_ICON("struct") case UnionName: RETURN_CACHED_ICON("union") case VariableName: RETURN_CACHED_ICON("code-variable") case MemberVariableName: RETURN_CACHED_ICON("field") case Keyword: RETURN_CACHED_ICON("field") case FunctionName: RETURN_CACHED_ICON("code-function") case EnumName: RETURN_CACHED_ICON("enum") case EnumMember: RETURN_CACHED_ICON("enum") case PackageName: RETURN_CACHED_ICON("field") case ModuleName: RETURN_CACHED_ICON("field") } return QIcon(); } QString DCDCompletionItem::typeLong() const { using namespace DCDCompletionItemType; switch (type) { case Invalid: return QStringLiteral("invalid"); case Calltip: return QStringLiteral("calltip"); case ClassName: return QStringLiteral("class"); case InterfaceName: return QStringLiteral("interface"); case StructName: return QStringLiteral("struct"); case UnionName: return QStringLiteral("union"); case VariableName: return QStringLiteral("variable"); case MemberVariableName: return QStringLiteral("member"); case Keyword: return QStringLiteral("keyword"); case FunctionName: return QStringLiteral("function"); case EnumName: return QStringLiteral("enum"); case EnumMember: return QStringLiteral("enum member"); case PackageName: return QStringLiteral("package"); case ModuleName: return QStringLiteral("module"); } return QStringLiteral("completion"); } static const int TIMEOUT_START_SERVER = 200; static const int TIMEOUT_COMPLETE = 200; static const int TIMEOUT_IMPORTPATH = 200; static const int TIMEOUT_SHUTDOWN = 350; static const int TIMEOUT_SHUTDOWN_SERVER = 200; DCD::DCD(int port, const QString& server, const QString& client) { m_port = port; m_server = server; m_client = client; } int DCD::port() const { return m_port; } bool DCD::running() { return m_sproc.state() == QProcess::Running; } bool DCD::startServer() { m_sproc.setProcessChannelMode(QProcess::MergedChannels); m_sproc.start(m_server, QStringList(QStringLiteral("-p%1").arg(m_port))); bool started = m_sproc.waitForStarted(TIMEOUT_START_SERVER); bool finished = m_sproc.waitForFinished(TIMEOUT_START_SERVER); if (!started || finished || m_sproc.state() == QProcess::NotRunning) { qWarning() << "unable to start completion-server:" << m_sproc.exitCode(); qWarning() << m_sproc.readAllStandardOutput(); return false; } qDebug() << "started completion-server"; return true; } DCDCompletion DCD::complete(const QString& file, int offset) { QProcess proc; proc.setProcessChannelMode(QProcess::MergedChannels); proc.start(m_client, QStringList() << QStringLiteral("-p%1").arg(m_port) << QStringLiteral("-c%1").arg(offset) << file ); proc.waitForFinished(TIMEOUT_COMPLETE); proc.terminate(); if (proc.exitStatus() != QProcess::NormalExit || proc.exitCode() != 0) { qWarning() << "unable to complete:" << proc.exitCode(); qWarning() << proc.readAllStandardOutput(); return DCDCompletion(); } return processCompletion(QString::fromUtf8(proc.readAllStandardOutput())); } DCDCompletion DCD::complete(const QByteArray& data, int offset) { QProcess proc; proc.setProcessChannelMode(QProcess::MergedChannels); proc.start(m_client, QStringList() << QStringLiteral("-p%1").arg(m_port) << QStringLiteral("-c%1").arg(offset) ); proc.write(data); proc.closeWriteChannel(); if (!proc.waitForFinished(TIMEOUT_COMPLETE)) { qWarning() << "unable to complete: client didn't finish in time"; proc.close(); } else if (proc.exitCode() != 0) { qWarning() << "unable to complete:" << proc.exitCode(); qWarning() << proc.readAllStandardOutput(); } else { // everything Ok return processCompletion(QString::fromUtf8(proc.readAllStandardOutput())); } return DCDCompletion(); } QString DCD::doc(const QByteArray& data, int offset) { QProcess proc; proc.setProcessChannelMode(QProcess::MergedChannels); proc.start(m_client, QStringList() << QStringLiteral("-p%1").arg(m_port) << QStringLiteral("-c%1").arg(offset) << QStringLiteral("--doc") ); proc.write(data); proc.closeWriteChannel(); if (!proc.waitForFinished(TIMEOUT_COMPLETE)) { qWarning() << "unable to lookup documentation: client didn't finish in time"; proc.close(); } else if (proc.exitCode() != 0) { qWarning() << "unable to lookup documentation:" << proc.exitCode(); qWarning() << proc.readAllStandardOutput(); } else { return QString::fromUtf8(proc.readAllStandardOutput()); } return QString(); } DCDCompletion DCD::processCompletion(const QString& data) { DCDCompletion completion; QStringList lines = data.split(QRegularExpression(QStringLiteral("[\r\n]")), QString::SkipEmptyParts); if (lines.length() == 0) { return completion; } QString type = lines.front(); if (type == QStringLiteral("identifiers")) { completion.type = DCDCompletionType::Identifiers; } else if (type == QStringLiteral("calltips")) { completion.type = DCDCompletionType::Calltips; } else { qWarning() << "Invalid type:" << type; return completion; } lines.pop_front(); foreach(QString line, lines) { if (line.trimmed().length() == 0) { continue; } QStringList kv = line.split(QRegularExpression(QStringLiteral("\\s+")), QString::SkipEmptyParts); if (kv.length() != 2 && completion.type != DCDCompletionType::Calltips) { qWarning() << "invalid completion data:" << kv.length() << completion.type; continue; } if (completion.type == DCDCompletionType::Identifiers) { completion.completions.append(DCDCompletionItem( DCDCompletionItemType::fromChar(kv[1].at(0).toLatin1()), kv[0] )); } else { completion.completions.append(DCDCompletionItem( DCDCompletionItemType::Calltip, line )); } } return completion; } void DCD::addImportPath(const QString& path) { addImportPath(QStringList(path)); } void DCD::addImportPath(const QStringList& paths) { if (paths.isEmpty()) { return; } QStringList arguments = QStringList(QStringLiteral("-p%1").arg(m_port)); foreach(QString path, paths) { if (QFile::exists(path)) arguments << QStringLiteral("-I%1").arg(path); } QProcess proc; proc.setProcessChannelMode(QProcess::MergedChannels); proc.start(m_client, arguments); proc.waitForFinished(TIMEOUT_IMPORTPATH); if (proc.exitStatus() != QProcess::NormalExit || proc.exitCode() != 0) { qWarning() << "unable to add importpath(s)" << paths << ":" << proc.exitCode(); qWarning() << proc.readAll(); } } void DCD::shutdown() { QProcess proc; proc.setProcessChannelMode(QProcess::MergedChannels); proc.start(m_client, QStringList() << QStringLiteral("-p%1").arg(m_port) << QStringLiteral("--shutdown") ); proc.waitForFinished(TIMEOUT_SHUTDOWN); if (proc.exitStatus() != QProcess::NormalExit || proc.exitCode() != 0) { qWarning() << "unable to shutdown dcd:" << proc.exitCode(); qWarning() << proc.readAllStandardOutput(); } } bool DCD::stopServer() { if (m_sproc.state() == QProcess::Running) { qDebug() << "shutting down dcd"; shutdown(); if(!m_sproc.waitForFinished(TIMEOUT_SHUTDOWN_SERVER)) m_sproc.terminate(); if(!m_sproc.waitForFinished(TIMEOUT_SHUTDOWN_SERVER)) m_sproc.kill(); return true; } return false; } DCD::~DCD() { if (running()) { stopServer(); } } diff --git a/addons/lumen/dcd.h b/addons/lumen/dcd.h index e7cb778d6..96b318f66 100644 --- a/addons/lumen/dcd.h +++ b/addons/lumen/dcd.h @@ -1,95 +1,95 @@ /* * Copyright 2014-2015 David Herberth kde@dav1d.de * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . **/ #ifndef LUMEN_DCD_H #define LUMEN_DCD_H -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace DCDCompletionType { enum DCDCompletionType { Identifiers, Calltips }; } namespace DCDCompletionItemType { enum DCDCompletionItemType { Invalid, Calltip, ClassName, InterfaceName, StructName, UnionName, VariableName, MemberVariableName, Keyword, FunctionName, EnumName, EnumMember, PackageName, ModuleName, }; char toChar(DCDCompletionItemType e); DCDCompletionItemType fromChar(char c); } struct DCDCompletionItem { DCDCompletionItem(DCDCompletionItemType::DCDCompletionItemType, QString); DCDCompletionItemType::DCDCompletionItemType type; QString name; QIcon icon() const; QString typeLong() const; }; struct DCDCompletion { DCDCompletionType::DCDCompletionType type; QList completions; }; class DCD { public: DCD(int, const QString&, const QString&); virtual ~DCD(); int port() const; bool running(); bool startServer(); bool stopServer(); DCDCompletion complete(const QString&, int); DCDCompletion complete(const QByteArray&, int); QString doc(const QByteArray&, int); void shutdown(); void addImportPath(const QString&); void addImportPath(const QStringList&); private: DCDCompletion processCompletion(const QString&); int m_port; QString m_server; QString m_client; QProcess m_sproc; }; #endif diff --git a/addons/lumen/lumen.cpp b/addons/lumen/lumen.cpp index 31f328cfa..09609fd27 100644 --- a/addons/lumen/lumen.cpp +++ b/addons/lumen/lumen.cpp @@ -1,185 +1,184 @@ /* * Copyright 2014-2015 David Herberth kde@dav1d.de * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . **/ #include "lumen.h" #include #include #include #include #include -#include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(LumenPluginFactory, "ktexteditor_lumen.json", registerPlugin();) LumenPluginView::LumenPluginView(LumenPlugin *plugin, KTextEditor::MainWindow *mainWin) : QObject(mainWin) , m_plugin(plugin) , m_mainWin(mainWin) , m_registered(false) { m_model = new LumenCompletionModel((QObject*)m_mainWin, m_plugin->dcd()); m_hinter = new LumenHintProvider(m_plugin); connect(m_mainWin, &KTextEditor::MainWindow::viewCreated, this, &LumenPluginView::viewCreated); foreach(KTextEditor::View *view, m_mainWin->views()) { viewCreated(view); } } void LumenPluginView::viewCreated(KTextEditor::View *view) { connect(view->document(), &KTextEditor::Document::documentUrlChanged, this, &LumenPluginView::documentChanged, Qt::UniqueConnection); connect(view->document(), &KTextEditor::Document::highlightingModeChanged, this, &LumenPluginView::documentChanged, Qt::UniqueConnection); connect(view->document(), &Document::documentUrlChanged, this, &LumenPluginView::urlChanged); registerCompletion(view); } void LumenPluginView::viewDestroyed(QObject *view) { m_completionViews.remove(static_cast(view)); } void LumenPluginView::documentChanged(KTextEditor::Document *document) { foreach(KTextEditor::View *view, document->views()) { registerCompletion(view); registerTextHints(view); } } void LumenPluginView::registerCompletion(KTextEditor::View *view) { KTextEditor::CodeCompletionInterface *completion = qobject_cast(view); bool isD = view->document()->url().path().endsWith(QStringLiteral(".d")) || view->document()->highlightingMode() == QStringLiteral("D"); if (isD && !m_registered) { completion->registerCompletionModel(m_model); m_registered = true; } else if(!isD && m_registered) { completion->unregisterCompletionModel(m_model); m_registered = false; } } void LumenPluginView::registerTextHints(KTextEditor::View *view) { KTextEditor::TextHintInterface *th = qobject_cast(view); if (th) { th->setTextHintDelay(500); th->registerTextHintProvider(m_hinter); } } LumenPluginView::~LumenPluginView() { } void LumenPluginView::urlChanged(Document* document) { QStringList paths; QDir dir = QDir(document->url().toLocalFile()); for (; !dir.isRoot(); dir.cdUp()) { QFile file(dir.filePath(QStringLiteral(".lumenconfig"))); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { while (!file.atEnd()) { QString path = QString::fromUtf8(file.readLine().trimmed()); if (QDir::isRelativePath(path)){ path = QDir::cleanPath(dir.filePath(path)); } paths.append(path); } } } if (!paths.isEmpty()) { m_plugin->dcd()->addImportPath(paths); } } LumenHintProvider::LumenHintProvider(LumenPlugin* plugin) : m_plugin(plugin) { } QString LumenHintProvider::textHint(View* view, const Cursor& position) { KTextEditor::Document* document = view->document(); KTextEditor::Cursor cursorEnd = document->documentEnd(); KTextEditor::Range range0c = KTextEditor::Range(0, 0, position.line(), position.column()); KTextEditor::Range rangece = KTextEditor::Range(position.line(), position.column(), cursorEnd.line(), cursorEnd.column()); QString text0c = document->text(range0c, false); QByteArray utf8 = text0c.toUtf8(); int offset = utf8.length(); utf8.append(document->text(rangece, false).toUtf8()); return m_plugin->dcd()->doc(utf8, offset).trimmed().replace(QStringLiteral("\\n"), QStringLiteral("\n")); } LumenPlugin::LumenPlugin(QObject *parent, const QList &) : Plugin(parent) { m_dcd = new DCD(9166, QStringLiteral("dcd-server"), QStringLiteral("dcd-client")); m_dcd->startServer(); } LumenPlugin::~LumenPlugin() { m_dcd->stopServer(); delete m_dcd; } DCD* LumenPlugin::dcd() { return m_dcd; } QObject* LumenPlugin::createView(KTextEditor::MainWindow *mainWin) { return new LumenPluginView(this, mainWin); } #include "lumen.moc" diff --git a/addons/search/htmldelegate.cpp b/addons/search/htmldelegate.cpp index 73d687d5a..e663b6fe5 100644 --- a/addons/search/htmldelegate.cpp +++ b/addons/search/htmldelegate.cpp @@ -1,74 +1,74 @@ /*************************************************************************** * This file is part of Kate search plugin * * Copyright 2011 Kåre Särs * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library 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 "htmldelegate.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include SPHtmlDelegate::SPHtmlDelegate( QObject* parent ) : QStyledItemDelegate(parent) {} SPHtmlDelegate::~SPHtmlDelegate() {} void SPHtmlDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem options = option; initStyleOption(&options, index); QTextDocument doc; //doc.setDocumentMargin(0); doc.setHtml(index.data().toString()); painter->save(); options.text = QString(); // clear old text options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter, options.widget); // draw area QRect clip = options.widget->style()->subElementRect(QStyle::SE_ItemViewItemText, &options); QFontMetrics metrics(options.font); if (index.flags() == Qt::NoItemFlags) { painter->setBrush(QBrush(QWidget().palette().color(QPalette::Base))); painter->setPen(QWidget().palette().color(QPalette::Base)); painter->drawRect(QRect(clip.topLeft() - QPoint(20, metrics.descent()), clip.bottomRight())); painter->translate(clip.topLeft() - QPoint(20, metrics.descent())); } else { painter->translate(clip.topLeft() - QPoint(0, metrics.descent())); } QAbstractTextDocumentLayout::PaintContext pcontext; doc.documentLayout()->draw(painter, pcontext); painter->restore(); } QSize SPHtmlDelegate::sizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& index) const { QTextDocument doc; //doc.setDocumentMargin(0); doc.setHtml(index.data().toString()); //qDebug() << doc.toPlainText() << doc.size().toSize(); return doc.size().toSize() + QSize(30, 0); // add margin for the check-box } diff --git a/addons/xmlcheck/plugin_katexmlcheck.cpp b/addons/xmlcheck/plugin_katexmlcheck.cpp index a3f3a830a..e1551ec02 100644 --- a/addons/xmlcheck/plugin_katexmlcheck.cpp +++ b/addons/xmlcheck/plugin_katexmlcheck.cpp @@ -1,403 +1,402 @@ /*************************************************************************** plugin_katexmlcheck.cpp - checks XML files using xmllint ------------------- begin : 2002-07-06 copyright : (C) 2002 by Daniel Naber email : daniel.naber@t-online.de ***************************************************************************/ /*************************************************************************** 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. ***************************************************************************/ /* -fixme: show dock if "Validate XML" is selected (doesn't currently work when Kate was just started and the dockwidget isn't yet visible) -fixme(?): doesn't correctly disappear when deactivated in config */ //TODO: // Cleanup unneeded headers // Find resourses and translate i18n messages // all translations were deleted in https://websvn.kde.org/?limit_changes=0&view=revision&revision=1433517 // What to do with catalogs? What is it for? // Implement hot key shoutcut to do xml validation // Remove copyright above due to author orphoned this plugin? // Possibility to check only well-formdness without validation // Hide output in dock when switching to another tab // Make ability to validate agains xml schema and then edit docbook // Should del space in [km] strang in katexmlcheck.desktop? // Which variant should I choose? QUrl.adjusted(rm filename).path() or QUrl.toString(rm filename|rm schema) // What about replace xmllint xmlstarlet or something? // Maybe use QXmlReader to take dtds and xsds? #include "plugin_katexmlcheck.h" #include //#include "plugin_katexmlcheck.moc" this goes to end #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 #include #include #include K_PLUGIN_FACTORY_WITH_JSON(PluginKateXMLCheckFactory, "katexmlcheck.json", registerPlugin();) PluginKateXMLCheck::PluginKateXMLCheck( QObject * const parent, const QVariantList& ) : KTextEditor::Plugin(parent) { qDebug() << "PluginXmlCheck()"; } PluginKateXMLCheck::~PluginKateXMLCheck() { } QObject *PluginKateXMLCheck::createView(KTextEditor::MainWindow *mainWindow) { return new PluginKateXMLCheckView(this, mainWindow); } //--------------------------------- PluginKateXMLCheckView::PluginKateXMLCheckView( KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainwin) : QObject(mainwin) , KXMLGUIClient() , m_mainWindow(mainwin) { KXMLGUIClient::setComponentName(QLatin1String("katexmlcheck"), i18n ("Kate XML check")); // where i18n resources? setXMLFile(QLatin1String("ui.rc")); dock = m_mainWindow->createToolView(plugin, "kate_plugin_xmlcheck_ouputview", KTextEditor::MainWindow::Bottom, QIcon::fromTheme("misc"), i18n("XML Checker Output")); listview = new QTreeWidget( dock ); m_tmp_file=nullptr; QAction *a = actionCollection()->addAction("xml_check"); a->setText(i18n("Validate XML")); connect(a, &QAction::triggered, this, &PluginKateXMLCheckView::slotValidate); // TODO?: //(void) new KAction ( i18n("Indent XML"), KShortcut(), this, // SLOT(slotIndent()), actionCollection(), "xml_indent" ); listview->setFocusPolicy(Qt::NoFocus); QStringList headers; headers << i18n("#"); headers << i18n("Line"); headers << i18n("Column"); headers << i18n("Message"); listview->setHeaderLabels(headers); listview->setRootIsDecorated(false); connect(listview, &QTreeWidget::itemClicked, this, &PluginKateXMLCheckView::slotClicked); QHeaderView *header = listview->header(); header->setSectionResizeMode(0, QHeaderView::ResizeToContents); header->setSectionResizeMode(1, QHeaderView::ResizeToContents); header->setSectionResizeMode(2, QHeaderView::ResizeToContents); /* TODO?: invalidate the listview when document has changed Kate::View *kv = application()->activeMainWindow()->activeView(); if( ! kv ) { qDebug() << "Warning: no Kate::View"; return; } connect(kv, SIGNAL(modifiedChanged()), this, SLOT(slotUpdate())); */ connect(&m_proc, static_cast(&QProcess::finished), this, &PluginKateXMLCheckView::slotProcExited); // we currently only want errors: m_proc.setProcessChannelMode(QProcess::SeparateChannels); // m_proc.setProcessChannelMode(QProcess::ForwardedChannels); // For Debugging. Do not use this. mainwin->guiFactory()->addClient(this); } PluginKateXMLCheckView::~PluginKateXMLCheckView() { m_mainWindow->guiFactory()->removeClient( this ); delete m_tmp_file; delete dock; } void PluginKateXMLCheckView::slotProcExited(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitCode); // FIXME: doesn't work correct the first time: //if( m_dockwidget->isDockBackPossible() ) { // m_dockwidget->dockBack(); // } if (exitStatus != QProcess::NormalExit) { QTreeWidgetItem *item = new QTreeWidgetItem(); item->setText(0, QString("1").rightJustified(4,' ')); item->setText(3, "Validate process crashed."); listview->addTopLevelItem(item); return; } qDebug() << "slotProcExited()"; QApplication::restoreOverrideCursor(); delete m_tmp_file; QString proc_stderr = QString::fromLocal8Bit(m_proc.readAllStandardError()); m_tmp_file=nullptr; listview->clear(); uint list_count = 0; uint err_count = 0; if( ! m_validating ) { // no i18n here, so we don't get an ugly English<->Non-english mixup: QString msg; if( m_dtdname.isEmpty() ) { msg = "No DOCTYPE found, will only check well-formedness."; } else { msg = '\'' + m_dtdname + "' not found, will only check well-formedness."; } QTreeWidgetItem *item = new QTreeWidgetItem(); item->setText(0, QString("1").rightJustified(4,' ')); item->setText(3, msg); listview->addTopLevelItem(item); list_count++; } if( ! proc_stderr.isEmpty() ) { QStringList lines = proc_stderr.split('\n', QString::SkipEmptyParts); QString linenumber, msg; int line_count = 0; for(QStringList::Iterator it = lines.begin(); it != lines.end(); ++it) { QString line = *it; line_count++; int semicolon_1 = line.indexOf(':'); int semicolon_2 = line.indexOf(':', semicolon_1+1); int semicolon_3 = line.indexOf(':', semicolon_2+2); int caret_pos = line.indexOf('^'); if( semicolon_1 != -1 && semicolon_2 != -1 && semicolon_3 != -1 ) { linenumber = line.mid(semicolon_1+1, semicolon_2-semicolon_1-1).trimmed(); linenumber = linenumber.rightJustified(6, ' '); // for sorting numbers msg = line.mid(semicolon_3+1, line.length()-semicolon_3-1).trimmed(); } else if( caret_pos != -1 || line_count == lines.size() ) { // TODO: this fails if "^" occurs in the real text?! if( line_count == lines.size() && caret_pos == -1 ) { msg = msg+'\n'+line; } QString col = QString::number(caret_pos); if( col == "-1" ) { col = ""; } err_count++; list_count++; QTreeWidgetItem *item = new QTreeWidgetItem(); item->setText(0, QString::number(list_count).rightJustified(4,' ')); item->setText(1, linenumber); item->setTextAlignment(1, (item->textAlignment(1) & ~Qt::AlignHorizontal_Mask) | Qt::AlignRight); item->setText(2, col); item->setTextAlignment(2, (item->textAlignment(2) & ~Qt::AlignHorizontal_Mask) | Qt::AlignRight); item->setText(3, msg); listview->addTopLevelItem(item); } else { msg = msg+'\n'+line; } } } if( err_count == 0 ) { QString msg; if( m_validating ) { msg = "No errors found, document is valid."; // no i18n here } else { msg = "No errors found, document is well-formed."; // no i18n here } QTreeWidgetItem *item = new QTreeWidgetItem(); item->setText(0, QString::number(list_count+1).rightJustified(4,' ')); item->setText(3, msg); listview->addTopLevelItem(item); } } void PluginKateXMLCheckView::slotClicked(QTreeWidgetItem *item, int column) { Q_UNUSED(column); qDebug() << "slotClicked"; if( item ) { bool ok = true; uint line = item->text(1).toUInt(&ok); bool ok2 = true; uint column = item->text(2).toUInt(&ok); if( ok && ok2 ) { KTextEditor::View *kv = m_mainWindow->activeView(); if( ! kv ) return; kv->setCursorPosition(KTextEditor::Cursor (line-1, column)); } } } void PluginKateXMLCheckView::slotUpdate() { qDebug() << "slotUpdate() (not implemented yet)"; } bool PluginKateXMLCheckView::slotValidate() { qDebug() << "slotValidate()"; m_mainWindow->showToolView (dock); m_validating = false; m_dtdname = ""; KTextEditor::View *kv = m_mainWindow->activeView(); if( ! kv ) return false; delete m_tmp_file; m_tmp_file = new QTemporaryFile(); if( !m_tmp_file->open() ) { qDebug() << "Error (slotValidate()): could not create '" << m_tmp_file->fileName() << "': " << m_tmp_file->errorString(); KMessageBox::error(nullptr, i18n("Error: Could not create " "temporary file '%1'.", m_tmp_file->fileName())); delete m_tmp_file; m_tmp_file=nullptr; return false; } QTextStream s ( m_tmp_file ); s << kv->document()->text(); s.flush(); QString exe = QStandardPaths::findExecutable("xmllint"); if( exe.isEmpty() ) { exe = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, "xmllint"); } //qDebug() << "exe=" <findResource("data", "ksgmltools2/customization/catalog.xml"); // qDebug() << "catalogs: " << catalogs; // setenv("XML_CATALOG_FILES", QFile::encodeName( catalogs ).data(), 1); // } //qDebug() << "**catalogs: " << getenv("XML_CATALOG_FILES"); QStringList args; args << "--noout"; // tell xmllint the working path of the document's file, if possible. // otherweise it will not find relative DTDs // I should give path to location of file, but remove filename // I can make QUrl.adjusted(rm filename).path() // or QUrl.toString(rm filename|rm schema) // Result is the same. Which variant should I choose? //QString path = kv->document()->url().adjusted(QUrl::RemoveFilename).path(); // xmllint uses space- or colon-separated path option, so spaces should be encoded to %20. It is done with EncodeSpaces. // Now what about colons in file names or paths? // This way xmllint works normally: // xmllint --noout --path "/home/user/my/with:colon/" --valid "/home/user/my/with:colon/demo-1.xml" // but because this plugin makes temp file path to file is another and this way xmllint refuses to find dtd: // xmllint --noout --path "/home/user/my/with:colon/" --valid "/tmp/kate.X23725" // As workaround we can encode ':' with %3A QString path = kv->document()->url().toString(QUrl::RemoveFilename|QUrl::PreferLocalFile|QUrl::EncodeSpaces); path.replace(':',"%3A"); // because of such inconvinience with xmllint and pathes, maybe switch to xmlstarlet? qDebug() << "path=" << path; if (!path.isEmpty()) { args << "--path" << path; } // heuristic: assume that the doctype is in the first 10,000 bytes: QString text_start = kv->document()->text().left(10000); // remove comments before looking for doctype (as a doctype might be commented out // and needs to be ignored then): QRegExp re(""); re.setMinimal(true); text_start.remove(re); QRegExp re_doctype("fileName(); qDebug() << "m_tmp_file->fileName()=" << m_tmp_file->fileName(); m_proc.start(exe,args); qDebug() << "m_proc.program():" << m_proc.program(); // I want to see parmeters qDebug() << "args=" << args; qDebug() << "exit code:"<< m_proc.exitCode(); if( ! m_proc.waitForStarted(-1) ) { KMessageBox::error(nullptr, i18n("Error: Failed to execute xmllint. Please make " "sure that xmllint is installed. It is part of libxml2.")); return false; } QApplication::setOverrideCursor(Qt::WaitCursor); return true; } #include "plugin_katexmlcheck.moc" diff --git a/addons/xmltools/plugin_katexmltools.cpp b/addons/xmltools/plugin_katexmltools.cpp index 62be3b19d..c3fa73fe6 100644 --- a/addons/xmltools/plugin_katexmltools.cpp +++ b/addons/xmltools/plugin_katexmltools.cpp @@ -1,1115 +1,1115 @@ /*************************************************************************** pluginKatexmltools.cpp List elements, attributes, attribute values and entities allowed by DTD. Needs a DTD in XML format ( as produced by dtdparse ) for most features. copyright : ( C ) 2001-2002 by Daniel Naber email : daniel.naber@t-online.de Copyright (C) 2005 by Anders Lund KDE SC 4 version (C) 2010 Tomas Trnka ***************************************************************************/ /*************************************************************************** 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. ***************************************************************************/ /* README: The basic idea is this: certain keyEvents(), namely [<&" ], trigger a completion box. This is intended as a help for editing. There are some cases where the XML spec is not followed, e.g. one can add the same attribute twice to an element. Also see the user documentation. If backspace is pressed after a completion popup was closed, the popup will re-open. This way typos can be corrected and the popup will reappear, which is quite comfortable. FIXME: -( docbook ) : insert space between the quotes, press "de" and return -> only "d" inserted -The "Insert Element" dialog isn't case insensitive, but it should be -See the "fixme"'s in the code TODO: -check for mem leaks -add "Go to opening/parent tag"? -check doctype to get top-level element -can undo behaviour be improved?, e.g. the plugins internal deletions of text don't have to be an extra step -don't offer entities if inside tag but outside attribute value -Support for more than one namespace at the same time ( e.g. XSLT + XSL-FO )? =>This could also be handled in the XSLT DTD fragment, as described in the XSLT 1.0 spec, but then at it will only show you HTML elements! =>So better "Assign meta DTD" and "Add meta DTD", the latter will expand the current meta DTD -Option to insert empty element in form -Show expanded entities with QChar::QChar( int rc ) + unicode font -Don't ignore entities defined in the document's prologue -Only offer 'valid' elements, i.e. don't take the elements as a set but check if the DTD is matched ( order, number of occurrences, ... ) -Maybe only read the meta DTD file once, then store the resulting QMap on disk ( using QDataStream )? We'll then have to compare timeOf_cacheFile <-> timeOf_metaDtd. -Try to use libxml */ #include "plugin_katexmltools.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 K_PLUGIN_FACTORY_WITH_JSON(PluginKateXMLToolsFactory, "katexmltools.json", registerPlugin();) PluginKateXMLTools::PluginKateXMLTools(QObject *const parent, const QVariantList &) : KTextEditor::Plugin(parent) { } PluginKateXMLTools::~PluginKateXMLTools() { } QObject *PluginKateXMLTools::createView(KTextEditor::MainWindow *mainWindow) { return new PluginKateXMLToolsView(mainWindow); } PluginKateXMLToolsView::PluginKateXMLToolsView(KTextEditor::MainWindow *mainWin) : QObject(mainWin) , KXMLGUIClient() , m_mainWindow(mainWin) , m_model(this) { //qDebug() << "PluginKateXMLTools constructor called"; KXMLGUIClient::setComponentName(QLatin1String("katexmltools"), i18n("Kate XML Tools")); setXMLFile(QLatin1String("ui.rc")); QAction *actionInsert = new QAction(i18n("&Insert Element..."), this); connect(actionInsert, &QAction::triggered, &m_model, &PluginKateXMLToolsCompletionModel::slotInsertElement); actionCollection()->addAction("xml_tool_insert_element", actionInsert); actionCollection()->setDefaultShortcut(actionInsert, Qt::CTRL + Qt::Key_Return); QAction *actionClose = new QAction(i18n("&Close Element"), this); connect(actionClose, &QAction::triggered, &m_model, &PluginKateXMLToolsCompletionModel::slotCloseElement); actionCollection()->addAction("xml_tool_close_element", actionClose); actionCollection()->setDefaultShortcut(actionClose, Qt::CTRL + Qt::Key_Less); QAction *actionAssignDTD = new QAction(i18n("Assign Meta &DTD..."), this); connect(actionAssignDTD, &QAction::triggered, &m_model, &PluginKateXMLToolsCompletionModel::getDTD); actionCollection()->addAction("xml_tool_assign", actionAssignDTD); mainWin->guiFactory()->addClient(this); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentDeleted, &m_model, &PluginKateXMLToolsCompletionModel::slotDocumentDeleted); } PluginKateXMLToolsView::~PluginKateXMLToolsView() { m_mainWindow->guiFactory()->removeClient(this); //qDebug() << "xml tools descructor 1..."; //TODO: unregister the model } PluginKateXMLToolsCompletionModel::PluginKateXMLToolsCompletionModel(QObject *const parent) : CodeCompletionModel(parent) , m_viewToAssignTo(nullptr) , m_mode(none) , m_correctPos(0) { } PluginKateXMLToolsCompletionModel::~PluginKateXMLToolsCompletionModel() { qDeleteAll(m_dtds); m_dtds.clear(); } void PluginKateXMLToolsCompletionModel::slotDocumentDeleted(KTextEditor::Document *doc) { // Remove the document from m_DTDs, and also delete the PseudoDTD // if it becomes unused. if (m_docDtds.contains(doc)) { qDebug() << "XMLTools:slotDocumentDeleted: documents: " << m_docDtds.count() << ", DTDs: " << m_dtds.count(); PseudoDTD *dtd = m_docDtds.take(doc); if (m_docDtds.key(dtd)) { return; } QHash::iterator it; for (it = m_dtds.begin() ; it != m_dtds.end() ; ++it) { if (it.value() == dtd) { m_dtds.erase(it); delete dtd; return; } } } } void PluginKateXMLToolsCompletionModel::completionInvoked(KTextEditor::View *kv, const KTextEditor::Range &range, const InvocationType invocationType) { Q_UNUSED(range) Q_UNUSED(invocationType) qDebug() << "xml tools completionInvoked"; KTextEditor::Document *doc = kv->document(); if (! m_docDtds[ doc ]) // no meta DTD assigned yet { return; } // debug to test speed: //QTime t; t.start(); beginResetModel(); m_allowed.clear(); // get char on the left of the cursor: KTextEditor::Cursor curpos = kv->cursorPosition(); uint line = curpos.line(), col = curpos.column(); QString lineStr = kv->document()->line(line); QString leftCh = lineStr.mid(col - 1, 1); QString secondLeftCh = lineStr.mid(col - 2, 1); if (leftCh == "&") { qDebug() << "Getting entities"; m_allowed = m_docDtds[doc]->entities(QString()); m_mode = entities; } else if (leftCh == "<") { qDebug() << "*outside tag -> get elements"; QString parentElement = getParentElement(*kv, 1); qDebug() << "parent: " << parentElement; m_allowed = m_docDtds[doc]->allowedElements(parentElement); m_mode = elements; } else if (leftCh == "/" && secondLeftCh == "<") { qDebug() << "*close parent element"; QString parentElement = getParentElement(*kv, 2); if (! parentElement.isEmpty()) { m_mode = closingtag; m_allowed = QStringList(parentElement); } } else if (leftCh == " " || (isQuote(leftCh) && secondLeftCh == "=")) { // TODO: check secondLeftChar, too?! then you don't need to trigger // with space and we yet save CPU power QString currentElement = insideTag(*kv); QString currentAttribute; if (! currentElement.isEmpty()) { currentAttribute = insideAttribute(*kv); } qDebug() << "Tag: " << currentElement; qDebug() << "Attr: " << currentAttribute; if (! currentElement.isEmpty() && ! currentAttribute.isEmpty()) { qDebug() << "*inside attribute -> get attribute values"; m_allowed = m_docDtds[doc]->attributeValues(currentElement, currentAttribute); if (m_allowed.count() == 1 && (m_allowed[0] == "CDATA" || m_allowed[0] == "ID" || m_allowed[0] == "IDREF" || m_allowed[0] == "IDREFS" || m_allowed[0] == "ENTITY" || m_allowed[0] == "ENTITIES" || m_allowed[0] == "NMTOKEN" || m_allowed[0] == "NMTOKENS" || m_allowed[0] == "NAME")) { // these must not be taken literally, e.g. don't insert the string "CDATA" m_allowed.clear(); } else { m_mode = attributevalues; } } else if (! currentElement.isEmpty()) { qDebug() << "*inside tag -> get attributes"; m_allowed = m_docDtds[doc]->allowedAttributes(currentElement); m_mode = attributes; } } //qDebug() << "time elapsed (ms): " << t.elapsed(); qDebug() << "Allowed strings: " << m_allowed.count(); if (m_allowed.count() >= 1 && m_allowed[0] != "__EMPTY") { m_allowed = sortQStringList(m_allowed); } setRowCount(m_allowed.count()); endResetModel(); } int PluginKateXMLToolsCompletionModel::columnCount(const QModelIndex &) const { return 1; } int PluginKateXMLToolsCompletionModel::rowCount(const QModelIndex &parent) const { if (!m_allowed.isEmpty()) { // Is there smth to complete? if (!parent.isValid()) { // Return the only one group node for root return 1; } if (parent.internalId() == groupNode) { // Return available rows count for group level node return m_allowed.size(); } } return 0; } QModelIndex PluginKateXMLToolsCompletionModel::parent(const QModelIndex &index) const { if (!index.isValid()) { // Is root/invalid index? return QModelIndex(); // Nothing to return... } if (index.internalId() == groupNode) { // Return a root node for group return QModelIndex(); } // Otherwise, this is a leaf level, so return the only group as a parent return createIndex(0, 0, groupNode); } QModelIndex PluginKateXMLToolsCompletionModel::index(const int row, const int column, const QModelIndex &parent) const { if (!parent.isValid()) { // At 'top' level only 'header' present, so nothing else than row 0 can be here... return row == 0 ? createIndex(row, column, groupNode) : QModelIndex(); } if (parent.internalId() == groupNode) { // Is this a group node? if (0 <= row && row < m_allowed.size()) { // Make sure to return only valid indices return createIndex(row, column, (void *)nullptr); // Just return a leaf-level index } } // Leaf node has no children... nothing to return return QModelIndex(); } QVariant PluginKateXMLToolsCompletionModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { // Nothing to do w/ invalid index return QVariant(); } if (index.internalId() == groupNode) { // Return group level node data switch (role) { case KTextEditor::CodeCompletionModel::GroupRole: return QVariant(Qt::DisplayRole); case Qt::DisplayRole: return currentModeToString(); default: break; } return QVariant(); // Nothing to return for other roles } switch (role) { case Qt::DisplayRole: switch (index.column()) { case KTextEditor::CodeCompletionModel::Name: return m_allowed.at(index.row()); default: break; } default: break; } return QVariant(); } bool PluginKateXMLToolsCompletionModel::shouldStartCompletion(KTextEditor::View *view, const QString &insertedText, bool userInsertion, const KTextEditor::Cursor &position) { Q_UNUSED(view) Q_UNUSED(userInsertion) Q_UNUSED(position) const QString triggerChars = "&application()->activeMainWindow()) { return; } KTextEditor::View *kv = KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView(); if (! kv) { qDebug() << "Warning: no KTextEditor::View"; return; } // ### replace this with something more sane // Start where the supplied XML-DTDs are fed by default unless // user changed directory last time: QString defaultDir = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "katexmltools") + "/katexmltools/"; if (m_urlString.isNull()) { m_urlString = defaultDir; } // Guess the meta DTD by looking at the doctype's public identifier. // XML allows comments etc. before the doctype, so look further than // just the first line. // Example syntax: // uint checkMaxLines = 200; QString documentStart = kv->document()->text(KTextEditor::Range(0, 0, checkMaxLines + 1, 0)); QRegExp re(" */ filename = "xslt-1.0.dtd.xml"; doctype = "XSLT 1.0"; } else { qDebug() << "No doctype found"; } QUrl url; if (filename.isEmpty()) { // no meta dtd found for this file url = QFileDialog::getOpenFileUrl(KTextEditor::Editor::instance()->application()->activeMainWindow()->window(), i18n("Assign Meta DTD in XML Format"), QUrl::fromLocalFile(m_urlString), "*.xml"); } else { url.setUrl(defaultDir + filename); KMessageBox::information(nullptr, i18n("The current file has been identified " "as a document of type \"%1\". The meta DTD for this document type " "will now be loaded.", doctype), i18n("Loading XML Meta DTD"), QString::fromLatin1("DTDAssigned")); } if (url.isEmpty()) { return; } m_urlString = url.url(); // remember directory for next time if (m_dtds[ m_urlString ]) { assignDTD(m_dtds[ m_urlString ], kv); } else { m_dtdString.clear(); m_viewToAssignTo = kv; QGuiApplication::setOverrideCursor(Qt::WaitCursor); KIO::Job *job = KIO::get(url); connect(job, &KIO::Job::result, this, &PluginKateXMLToolsCompletionModel::slotFinished); connect(job, SIGNAL(data(KIO::Job *, QByteArray)), this, SLOT(slotData(KIO::Job *, QByteArray))); } qDebug() << "XMLTools::getDTD: Documents: " << m_docDtds.count() << ", DTDs: " << m_dtds.count(); } void PluginKateXMLToolsCompletionModel::slotFinished(KJob *job) { if (job->error()) { //qDebug() << "XML Plugin error: DTD in XML format (" << filename << " ) could not be loaded"; static_cast(job)->uiDelegate()->showErrorMessage(); } else if (static_cast(job)->isErrorPage()) { // catch failed loading loading via http: KMessageBox::error(nullptr, i18n("The file '%1' could not be opened. " "The server returned an error.", m_urlString), i18n("XML Plugin Error")); } else { PseudoDTD *dtd = new PseudoDTD(); dtd->analyzeDTD(m_urlString, m_dtdString); m_dtds.insert(m_urlString, dtd); assignDTD(dtd, m_viewToAssignTo); // clean up a bit m_viewToAssignTo = nullptr; m_dtdString.clear(); } QGuiApplication::restoreOverrideCursor(); } void PluginKateXMLToolsCompletionModel::slotData(KIO::Job *, const QByteArray &data) { m_dtdString += QString(data); } void PluginKateXMLToolsCompletionModel::assignDTD(PseudoDTD *dtd, KTextEditor::View *view) { m_docDtds.insert(view->document(), dtd); //TODO:perhaps foreach views()? KTextEditor::CodeCompletionInterface *cci = qobject_cast(view); if (cci) { cci->registerCompletionModel(this); cci->setAutomaticInvocationEnabled(true); qDebug() << "PluginKateXMLToolsView: completion model registered"; } else { qWarning() << "PluginKateXMLToolsView: completion interface unavailable"; } } /** * Offer a line edit with completion for possible elements at cursor position and insert the * tag one chosen/entered by the user, plus its closing tag. If there's a text selection, * add the markup around it. */ void PluginKateXMLToolsCompletionModel::slotInsertElement() { if (!KTextEditor::Editor::instance()->application()->activeMainWindow()) { return; } KTextEditor::View *kv = KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView(); if (! kv) { qDebug() << "Warning: no KTextEditor::View"; return; } KTextEditor::Document *doc = kv->document(); PseudoDTD *dtd = m_docDtds[doc]; QString parentElement = getParentElement(*kv, 0); QStringList allowed; if (dtd) { allowed = dtd->allowedElements(parentElement); } QString text; InsertElement dialog(allowed, kv); if (dialog.exec() == QDialog::Accepted) { text = dialog.text(); } if (!text.isEmpty()) { QStringList list = text.split(QChar(' ')); QString pre; QString post; // anders: use if the tag is required to be empty. // In that case maybe we should not remove the selection? or overwrite it? int adjust = 0; // how much to move cursor. // if we know that we have attributes, it goes // just after the tag name, otherwise between tags. if (dtd && dtd->allowedAttributes(list[0]).count()) { adjust++; // the ">" } if (dtd && dtd->allowedElements(list[0]).contains("__EMPTY")) { pre = '<' + text + "/>"; if (adjust) { adjust++; // for the "/" } } else { pre = '<' + text + '>'; post = "'; } QString marked; if (! post.isEmpty()) { marked = kv->selectionText(); } KTextEditor::Document::EditingTransaction transaction(doc); if (! marked.isEmpty()) { kv->removeSelectionText(); } // with the old selection now removed, curPos points to the start of pre KTextEditor::Cursor curPos = kv->cursorPosition(); curPos.setColumn(curPos.column() + pre.length() - adjust); kv->insertText(pre + marked + post); kv->setCursorPosition(curPos); } } /** * Insert a closing tag for the nearest not-closed parent element. */ void PluginKateXMLToolsCompletionModel::slotCloseElement() { if (!KTextEditor::Editor::instance()->application()->activeMainWindow()) { return; } KTextEditor::View *kv = KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView(); if (! kv) { qDebug() << "Warning: no KTextEditor::View"; return; } QString parentElement = getParentElement(*kv, 0); //qDebug() << "parentElement: '" << parentElement << "'"; QString closeTag = "'; if (! parentElement.isEmpty()) { kv->insertText(closeTag); } } // modify the completion string before it gets inserted void PluginKateXMLToolsCompletionModel::executeCompletionItem(KTextEditor::View *view, const KTextEditor::Range &word, const QModelIndex &index) const { KTextEditor::Range toReplace = word; KTextEditor::Document *document = view->document(); QString text = data(index.sibling(index.row(), Name), Qt::DisplayRole).toString(); qDebug() << "executeCompletionItem text: " << text; int line, col; view->cursorPosition().position(line, col); QString lineStr = document->line(line); QString leftCh = lineStr.mid(col - 1, 1); QString rightCh = lineStr.mid(col, 1); int posCorrection = 0; // where to move the cursor after completion ( >0 = move right ) if (m_mode == entities) { text = text + ';'; } else if (m_mode == attributes) { text = text + "=\"\""; posCorrection = -1; if (!rightCh.isEmpty() && rightCh != ">" && rightCh != "/" && rightCh != " ") { // TODO: other whitespaces // add space in front of the next attribute text = text + ' '; posCorrection--; } } else if (m_mode == attributevalues) { // TODO: support more than one line uint startAttValue = 0; uint endAttValue = 0; // find left quote: for (startAttValue = col; startAttValue > 0; startAttValue--) { QString ch = lineStr.mid(startAttValue - 1, 1); if (isQuote(ch)) { break; } } // find right quote: for (endAttValue = col; endAttValue <= (uint) lineStr.length(); endAttValue++) { QString ch = lineStr.mid(endAttValue - 1, 1); if (isQuote(ch)) { break; } } // replace the current contents of the attribute if (startAttValue < endAttValue) { toReplace = KTextEditor::Range(line, startAttValue, line, endAttValue - 1); } } else if (m_mode == elements) { // anders: if the tag is marked EMPTY, insert in form QString str; bool isEmptyTag = m_docDtds[document]->allowedElements(text).contains("__EMPTY"); if (isEmptyTag) { str = text + "/>"; } else { str = text + ">'; } // Place the cursor where it is most likely wanted: // always inside the tag if the tag is empty AND the DTD indicates that there are attribs) // outside for open tags, UNLESS there are mandatory attributes if (m_docDtds[document]->requiredAttributes(text).count() || (isEmptyTag && m_docDtds[document]->allowedAttributes(text).count())) { posCorrection = text.length() - str.length(); } else if (! isEmptyTag) { posCorrection = text.length() - str.length() + 1; } text = str; } else if (m_mode == closingtag) { text += '>'; } document->replaceText(toReplace, text); // move the cursor to desired position KTextEditor::Cursor curPos = view->cursorPosition(); curPos.setColumn(curPos.column() + posCorrection); view->setCursorPosition(curPos); } // ======================================================================== // Pseudo-XML stuff: /** * Check if cursor is inside a tag, that is * if "<" occurs before ">" occurs ( on the left side of the cursor ). * Return the tag name, return "" if we cursor is outside a tag. */ QString PluginKateXMLToolsCompletionModel::insideTag(KTextEditor::View &kv) { int line, col; kv.cursorPosition().position(line, col); int y = line; // another variable because uint <-> int do { QString lineStr = kv.document()->line(y); for (uint x = col; x > 0; x--) { QString ch = lineStr.mid(x - 1, 1); if (ch == ">") { // cursor is outside tag return QString(); } if (ch == "<") { QString tag; // look for white space on the right to get the tag name for (int z = x; z <= lineStr.length() ; ++z) { ch = lineStr.mid(z - 1, 1); if (ch.at(0).isSpace() || ch == "/" || ch == ">") { return tag.right(tag.length() - 1); } if (z == lineStr.length()) { tag += ch; return tag.right(tag.length() - 1); } tag += ch; } } } y--; col = kv.document()->line(y).length(); } while (y >= 0); return QString(); } /** * Check if cursor is inside an attribute value, that is * if '="' is on the left, and if it's nearer than "<" or ">". * * @Return the attribute name or "" if we're outside an attribute * value. * * Note: only call when insideTag() == true. * TODO: allow whitespace around "=" */ QString PluginKateXMLToolsCompletionModel::insideAttribute(KTextEditor::View &kv) { int line, col; kv.cursorPosition().position(line, col); int y = line; // another variable because uint <-> int uint x = 0; QString lineStr; QString ch; do { lineStr = kv.document()->line(y); for (x = col; x > 0; x--) { ch = lineStr.mid(x - 1, 1); QString chLeft = lineStr.mid(x - 2, 1); // TODO: allow whitespace if (isQuote(ch) && chLeft == "=") { break; } else if (isQuote(ch) && chLeft != "=") { return QString(); } else if (ch == "<" || ch == ">") { return QString(); } } y--; col = kv.document()->line(y).length(); } while (!isQuote(ch)); // look for next white space on the left to get the tag name QString attr; for (int z = x; z >= 0; z--) { ch = lineStr.mid(z - 1, 1); if (ch.at(0).isSpace()) { break; } if (z == 0) { // start of line == whitespace attr += ch; break; } attr = ch + attr; } return attr.left(attr.length() - 2); } /** * Find the parent element for the current cursor position. That is, * go left and find the first opening element that's not closed yet, * ignoring empty elements. * Examples: If cursor is at "X", the correct parent element is "p": *

foo test bar X *

foo bar X *

foo bar X *

foo bar X */ QString PluginKateXMLToolsCompletionModel::getParentElement(KTextEditor::View &kv, int skipCharacters) { enum { parsingText, parsingElement, parsingElementBoundary, parsingNonElement, parsingAttributeDquote, parsingAttributeSquote, parsingIgnore } parseState; parseState = (skipCharacters > 0) ? parsingIgnore : parsingText; int nestingLevel = 0; int line, col; kv.cursorPosition().position(line, col); QString str = kv.document()->line(line); while (true) { // move left a character if (!col--) { do { if (!line--) { return QString(); // reached start of document } str = kv.document()->line(line); col = str.length(); } while (!col); --col; } ushort ch = str.at(col).unicode(); switch (parseState) { case parsingIgnore: // ignore the specified number of characters parseState = (--skipCharacters > 0) ? parsingIgnore : parsingText; break; case parsingText: switch (ch) { case '<': // hmm... we were actually inside an element return QString(); case '>': // we just hit an element boundary parseState = parsingElementBoundary; break; } break; case parsingElement: switch (ch) { case '"': // attribute ( double quoted ) parseState = parsingAttributeDquote; break; case '\'': // attribute ( single quoted ) parseState = parsingAttributeSquote; break; case '/': // close tag parseState = parsingNonElement; ++nestingLevel; break; case '<': // we just hit the start of the element... if (nestingLevel--) { break; } QString tag = str.mid(col + 1); for (uint pos = 0, len = tag.length(); pos < len; ++pos) { ch = tag.at(pos).unicode(); if (ch == ' ' || ch == '\t' || ch == '>') { tag.truncate(pos); break; } } return tag; } break; case parsingElementBoundary: switch (ch) { case '?': // processing instruction case '-': // comment case '/': // empty element parseState = parsingNonElement; break; case '"': parseState = parsingAttributeDquote; break; case '\'': parseState = parsingAttributeSquote; break; case '<': // empty tag ( bad XML ) parseState = parsingText; break; default: parseState = parsingElement; } break; case parsingAttributeDquote: if (ch == '"') { parseState = parsingElement; } break; case parsingAttributeSquote: if (ch == '\'') { parseState = parsingElement; } break; case parsingNonElement: if (ch == '<') { parseState = parsingText; } break; } } } /** * Return true if the tag is neither a closing tag * nor an empty tag, nor a comment, nor processing instruction. */ bool PluginKateXMLToolsCompletionModel::isOpeningTag(const QString &tag) { return (!isClosingTag(tag) && !isEmptyTag(tag) && - !tag.startsWith(""); } /** * Return true if ch is a single or double quote. Expects ch to be of length 1. */ bool PluginKateXMLToolsCompletionModel::isQuote(const QString &ch) { return (ch == "\"" || ch == "'"); } // ======================================================================== // Tools: /// Get string describing current mode QString PluginKateXMLToolsCompletionModel::currentModeToString() const { switch (m_mode) { case entities: return i18n("XML entities"); case attributevalues: return i18n("XML attribute values"); case attributes: return i18n("XML attributes"); case elements: case closingtag: return i18n("XML elements"); default: break; } return QString(); } /** Sort a QStringList case-insensitively. Static. TODO: make it more simple. */ QStringList PluginKateXMLToolsCompletionModel::sortQStringList(QStringList list) { // Sort list case-insensitive. This looks complicated but using a QMap // is even suggested by the Qt documentation. QMap mapList; for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) { QString str = *it; if (mapList.contains(str.toLower())) { // do not override a previous value, e.g. "Auml" and "auml" are two different // entities, but they should be sorted next to each other. // TODO: currently it's undefined if e.g. "A" or "a" comes first, it depends on // the meta DTD ( really? it seems to work okay?!? ) mapList[str.toLower() + '_'] = str; } else { mapList[str.toLower()] = str; } } list.clear(); QMap::Iterator it; // Qt doc: "the items are alphabetically sorted [by key] when iterating over the map": for (it = mapList.begin(); it != mapList.end(); ++it) { list.append(it.value()); } return list; } //BEGIN InsertElement dialog InsertElement::InsertElement(const QStringList & completions, QWidget * parent) : QDialog(parent) { setWindowTitle(i18n("Insert XML Element")); QVBoxLayout *topLayout = new QVBoxLayout(this); // label QString text = i18n("Enter XML tag name and attributes (\"<\", \">\" and closing tag will be supplied):"); QLabel *label = new QLabel(text, this); label->setWordWrap(true); // combo box m_cmbElements = new KHistoryComboBox(this); static_cast(m_cmbElements)->setHistoryItems(completions, true); connect(m_cmbElements->lineEdit(), &QLineEdit::textChanged, this, &InsertElement::slotHistoryTextChanged); // button box QDialogButtonBox * box = new QDialogButtonBox(this); box->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); m_okButton = box->button(QDialogButtonBox::Ok); m_okButton->setDefault(true); connect(box, &QDialogButtonBox::accepted, this, &InsertElement::accept); connect(box, &QDialogButtonBox::rejected, this, &InsertElement::reject); // fill layout topLayout->addWidget(label); topLayout->addWidget(m_cmbElements); topLayout->addWidget(box); m_cmbElements->setFocus(); // make sure the ok button is enabled/disabled correctly slotHistoryTextChanged(m_cmbElements->lineEdit()->text()); } InsertElement::~InsertElement() { } void InsertElement::slotHistoryTextChanged(const QString &text) { m_okButton->setEnabled(!text.isEmpty()); } QString InsertElement::text() const { return m_cmbElements->currentText(); } //END InsertElement dialog #include "plugin_katexmltools.moc" // kate: space-indent on; indent-width 4; replace-tabs on; mixed-indent off; diff --git a/kate/kateconfigdialog.cpp b/kate/kateconfigdialog.cpp index 37884d4e4..efe811920 100644 --- a/kate/kateconfigdialog.cpp +++ b/kate/kateconfigdialog.cpp @@ -1,383 +1,383 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2002 Joseph Wenninger Copyright (C) 2007 Mirko Stocker 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 "kateconfigdialog.h" #include "ui_sessionconfigwidget.h" #include "katemainwindow.h" #include "katedocmanager.h" #include "katepluginmanager.h" #include "kateconfigplugindialogpage.h" #include "kateviewmanager.h" #include "kateapp.h" #include "katesessionmanager.h" #include "katedebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include KateConfigDialog::KateConfigDialog(KateMainWindow *parent, KTextEditor::View *view) : KPageDialog(parent) , m_mainWindow(parent) , m_view(view) { setFaceType(Tree); setWindowTitle(i18n("Configure")); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel | QDialogButtonBox::Help); setObjectName(QStringLiteral("configdialog")); KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup cgGeneral = KConfigGroup(config, "General"); buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(false); KPageWidgetItem *applicationItem = addPage(new QWidget, i18n("Application")); applicationItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other"))); applicationItem->setHeader(i18n("Application Options")); applicationItem->setCheckable(false); applicationItem->setEnabled(false); m_applicationPage = applicationItem; //BEGIN General page QFrame *generalFrame = new QFrame; KPageWidgetItem *item = addSubPage(applicationItem, generalFrame, i18n("General")); item->setHeader(i18n("General Options")); item->setIcon(QIcon::fromTheme(QStringLiteral("go-home"))); setCurrentPage(item); QVBoxLayout *layout = new QVBoxLayout(generalFrame); layout->setMargin(0); // GROUP with the one below: "Behavior" QGroupBox *buttonGroup = new QGroupBox(i18n("&Behavior"), generalFrame); QVBoxLayout *vbox = new QVBoxLayout; layout->addWidget(buttonGroup); // modified files notification m_modNotifications = new QCheckBox( i18n("Wa&rn about files modified by foreign processes"), buttonGroup); m_modNotifications->setChecked(parent->modNotificationEnabled()); m_modNotifications->setWhatsThis(i18n( "If enabled, when Kate receives focus you will be asked what to do with " "files that have been modified on the hard disk. If not enabled, you will " "be asked what to do with a file that has been modified on the hard disk only " "when that file is tried to be saved.")); connect(m_modNotifications, &QCheckBox::toggled, this, &KateConfigDialog::slotChanged); vbox->addWidget(m_modNotifications); buttonGroup->setLayout(vbox); // GROUP with the one below: "Meta-information" buttonGroup = new QGroupBox(i18n("Meta-Information"), generalFrame); vbox = new QVBoxLayout; layout->addWidget(buttonGroup); // save meta infos m_saveMetaInfos = new QCheckBox(buttonGroup); m_saveMetaInfos->setText(i18n("Keep &meta-information past sessions")); m_saveMetaInfos->setChecked(KateApp::self()->documentManager()->getSaveMetaInfos()); m_saveMetaInfos->setWhatsThis(i18n( "Check this if you want document configuration like for example " "bookmarks to be saved past editor sessions. The configuration will be " "restored if the document has not changed when reopened.")); connect(m_saveMetaInfos, &QCheckBox::toggled, this, &KateConfigDialog::slotChanged); vbox->addWidget(m_saveMetaInfos); // meta infos days QFrame *metaInfos = new QFrame(buttonGroup); QHBoxLayout *hlayout = new QHBoxLayout(metaInfos); metaInfos->setEnabled(KateApp::self()->documentManager()->getSaveMetaInfos()); QLabel *label = new QLabel(i18n("&Delete unused meta-information after:"), metaInfos); hlayout->addWidget(label); m_daysMetaInfos = new KPluralHandlingSpinBox(metaInfos); m_daysMetaInfos->setMaximum(180); m_daysMetaInfos->setSpecialValueText(i18nc("The special case of 'Delete unused meta-information after'", "(never)")); m_daysMetaInfos->setSuffix(ki18ncp("The suffix of 'Delete unused meta-information after'", " day", " days")); m_daysMetaInfos->setValue(KateApp::self()->documentManager()->getDaysMetaInfos()); hlayout->addWidget(m_daysMetaInfos); label->setBuddy(m_daysMetaInfos); connect(m_saveMetaInfos, &QCheckBox::toggled, metaInfos, &QFrame::setEnabled); connect(m_daysMetaInfos, static_cast(&KPluralHandlingSpinBox::valueChanged), this, &KateConfigDialog::slotChanged); vbox->addWidget(metaInfos); buttonGroup->setLayout(vbox); layout->addStretch(1); // :-] works correct without autoadd //END General page //BEGIN Session page QWidget *sessionsPage = new QWidget(); item = addSubPage(applicationItem, sessionsPage, i18n("Sessions")); item->setHeader(i18n("Session Management")); item->setIcon(QIcon::fromTheme(QStringLiteral("view-history"))); sessionConfigUi = new Ui::SessionConfigWidget(); sessionConfigUi->setupUi(sessionsPage); // restore view config sessionConfigUi->restoreVC->setChecked( cgGeneral.readEntry("Restore Window Configuration", true) ); connect(sessionConfigUi->restoreVC, &QCheckBox::toggled, this, &KateConfigDialog::slotChanged); sessionConfigUi->spinBoxRecentFilesCount->setValue(recentFilesMaxCount()); connect(sessionConfigUi->spinBoxRecentFilesCount, static_cast(&QSpinBox::valueChanged), this, &KateConfigDialog::slotChanged); QString sesStart (cgGeneral.readEntry ("Startup Session", "manual")); if (sesStart == QStringLiteral("new")) sessionConfigUi->startNewSessionRadioButton->setChecked (true); else if (sesStart == QStringLiteral("last")) sessionConfigUi->loadLastUserSessionRadioButton->setChecked (true); else sessionConfigUi->manuallyChooseSessionRadioButton->setChecked (true); connect(sessionConfigUi->startNewSessionRadioButton, &QRadioButton::toggled, this, &KateConfigDialog::slotChanged); connect(sessionConfigUi->loadLastUserSessionRadioButton, &QRadioButton::toggled, this, &KateConfigDialog::slotChanged); connect(sessionConfigUi->manuallyChooseSessionRadioButton, &QRadioButton::toggled, this, &KateConfigDialog::slotChanged); //END Session page //BEGIN Plugins page QFrame *page = new QFrame(this); QVBoxLayout *vlayout = new QVBoxLayout(page); vlayout->setMargin(0); vlayout->setSpacing(0); KateConfigPluginPage *configPluginPage = new KateConfigPluginPage(page, this); vlayout->addWidget(configPluginPage); connect(configPluginPage, &KateConfigPluginPage::changed, this, &KateConfigDialog::slotChanged); item = addSubPage(applicationItem, page, i18n("Plugins")); item->setHeader(i18n("Plugin Manager")); item->setIcon(QIcon::fromTheme(QStringLiteral("preferences-plugin"))); KatePluginList &pluginList(KateApp::self()->pluginManager()->pluginList()); foreach(const KatePluginInfo & plugin, pluginList) { if (plugin.load) { addPluginPage(plugin.plugin); } } //END Plugins page // editor widgets from kwrite/kwdialog m_editorPage = addPage(new QWidget, i18n("Editor Component")); m_editorPage->setIcon(QIcon::fromTheme(QStringLiteral("accessories-text-editor"))); m_editorPage->setHeader(i18n("Editor Component Options")); m_editorPage->setCheckable(false); m_editorPage->setEnabled(false); addEditorPages(); connect(this, &KateConfigDialog::accepted, this, &KateConfigDialog::slotApply); connect(buttonBox()->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &KateConfigDialog::slotApply); connect(buttonBox()->button(QDialogButtonBox::Help), &QPushButton::clicked, this, &KateConfigDialog::slotHelp); connect(this, &KateConfigDialog::currentPageChanged, this, &KateConfigDialog::slotCurrentPageChanged); m_dataChanged = false; resize(minimumSizeHint()); } KateConfigDialog::~KateConfigDialog() { delete sessionConfigUi; } void KateConfigDialog::addEditorPages() { for (int i = 0; i < KTextEditor::Editor::instance()->configPages(); ++i) { KTextEditor::ConfigPage *page = KTextEditor::Editor::instance()->configPage(i, this); connect(page, &KTextEditor::ConfigPage::changed, this, &KateConfigDialog::slotChanged); m_editorPages.push_back(page); KPageWidgetItem *item = addSubPage(m_editorPage, page, page->name()); item->setHeader(page->fullName()); item->setIcon(page->icon()); } } void KateConfigDialog::addPluginPage(KTextEditor::Plugin *plugin) { for (int i = 0; i < plugin->configPages(); i++) { QFrame *page = new QFrame(); QVBoxLayout *layout = new QVBoxLayout(page); layout->setSpacing(0); layout->setMargin(0); KTextEditor::ConfigPage *cp = plugin->configPage(i, page); page->layout()->addWidget(cp); KPageWidgetItem *item = addSubPage(m_applicationPage, page, cp->name()); item->setHeader(cp->fullName()); item->setIcon(cp->icon()); PluginPageListItem *info = new PluginPageListItem; info->plugin = plugin; info->pageParent = page; info->pluginPage = cp; info->idInPlugin = i; info->pageWidgetItem = item; connect(info->pluginPage, &KTextEditor::ConfigPage::changed, this, &KateConfigDialog::slotChanged); m_pluginPages.insert(item, info); } } void KateConfigDialog::slotCurrentPageChanged(KPageWidgetItem *current, KPageWidgetItem * /*before*/) { PluginPageListItem *info = m_pluginPages[current]; if (!info) { return; } if (info->pluginPage) { return; } - qCDebug(LOG_KATE) << "creating config page (shouldnt get here)"; + qCDebug(LOG_KATE) << "creating config page (should not get here)"; info->pluginPage = info->plugin->configPage(info->idInPlugin, info->pageParent); info->pageParent->layout()->addWidget(info->pluginPage); info->pluginPage->show(); connect(info->pluginPage, &KTextEditor::ConfigPage::changed, this, &KateConfigDialog::slotChanged); } void KateConfigDialog::removePluginPage(KTextEditor::Plugin *plugin) { QList remove; for (QHash::const_iterator it = m_pluginPages.constBegin(); it != m_pluginPages.constEnd(); ++it) { PluginPageListItem *pli = it.value(); if (!pli) { continue; } if (pli->plugin == plugin) { remove.append(it.key()); } } qCDebug(LOG_KATE) << remove.count(); while (!remove.isEmpty()) { KPageWidgetItem *wItem = remove.takeLast(); PluginPageListItem *pItem = m_pluginPages.take(wItem); delete pItem->pluginPage; delete pItem->pageParent; removePage(wItem); delete pItem; } } void KateConfigDialog::slotApply() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); // if data changed apply the kate app stuff if (m_dataChanged) { KConfigGroup cg = KConfigGroup(config, "General"); cg.writeEntry("Restore Window Configuration", sessionConfigUi->restoreVC->isChecked()); cg.writeEntry("Recent File List Entry Count", sessionConfigUi->spinBoxRecentFilesCount->value()); if (sessionConfigUi->startNewSessionRadioButton->isChecked()) { cg.writeEntry("Startup Session", "new"); } else if (sessionConfigUi->loadLastUserSessionRadioButton->isChecked()) { cg.writeEntry("Startup Session", "last"); } else { cg.writeEntry("Startup Session", "manual"); } cg.writeEntry("Save Meta Infos", m_saveMetaInfos->isChecked()); KateApp::self()->documentManager()->setSaveMetaInfos(m_saveMetaInfos->isChecked()); cg.writeEntry("Days Meta Infos", m_daysMetaInfos->value()); KateApp::self()->documentManager()->setDaysMetaInfos(m_daysMetaInfos->value()); cg.writeEntry("Modified Notification", m_modNotifications->isChecked()); m_mainWindow->setModNotificationEnabled(m_modNotifications->isChecked()); // patch document modified warn state const QList &docs = KateApp::self()->documentManager()->documentList(); foreach(KTextEditor::Document * doc, docs) if (qobject_cast(doc)) { qobject_cast(doc)->setModifiedOnDiskWarning(!m_modNotifications->isChecked()); } m_mainWindow->saveOptions(); // save plugin config !! KateSessionManager *sessionmanager = KateApp::self()->sessionManager(); KConfig *sessionConfig = sessionmanager->activeSession()->config(); KateApp::self()->pluginManager()->writeConfig(sessionConfig); } foreach(PluginPageListItem * plugin, m_pluginPages) { if (!plugin) { continue; } if (plugin->pluginPage) { plugin->pluginPage->apply(); } } // apply ktexteditor pages foreach(KTextEditor::ConfigPage * page, m_editorPages) page->apply(); config->sync(); m_dataChanged = false; buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(false); } void KateConfigDialog::slotChanged() { m_dataChanged = true; buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(true); } void KateConfigDialog::showAppPluginPage(KTextEditor::Plugin *p, uint id) { foreach(PluginPageListItem * plugin, m_pluginPages) { if ((plugin->plugin == p) && (id == plugin->idInPlugin)) { setCurrentPage(plugin->pageWidgetItem); break; } } } void KateConfigDialog::slotHelp() { QDesktopServices::openUrl(QUrl(QStringLiteral("help:/"))); } int KateConfigDialog::recentFilesMaxCount() { int maxItems = KConfigGroup(KSharedConfig::openConfig(), "General").readEntry("Recent File List Entry Count", 10); return maxItems; } diff --git a/urlinfo.h b/urlinfo.h index 31369dac8..c9e179a8c 100644 --- a/urlinfo.h +++ b/urlinfo.h @@ -1,106 +1,106 @@ /* This file is part of the KDE project Copyright (C) 2015 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef URLINFO_H #define URLINFO_H #include #include #include #include #include /** * Represents a file to be opened, consisting of its URL and the cursor to jump to. */ class UrlInfo { public: /** * Parses a file path argument and determines its line number and column and full path * @param path path passed on e.g. command line to parse into an URL */ UrlInfo(QString path) : cursor(KTextEditor::Cursor::invalid()) { /** * first try: just check if the path is an existing file */ if (QFile::exists(path)) { /** * create absolute file path, we will e.g. pass this over dbus to other processes * and then we are done, no cursor can be detected here! */ url = QUrl::fromLocalFile(QDir::current().absoluteFilePath(path)); return; } /** * ok, the path as is, is no existing file, now, cut away :xx:yy stuff as cursor * this will make test:50 to test with line 50 */ const auto match = QRegularExpression(QStringLiteral(":(\\d+)(?::(\\d+))?:?$")).match(path); if (match.isValid()) { /** * cut away the line/column specification from the path */ path.chop(match.capturedLength()); /** * set right cursor position * don't use an invalid column when the line is valid */ const int line = match.captured(1).toInt() - 1; const int column = qMax(0, match.captured(2).toInt() - 1); cursor.setPosition(line, column); } /** * construct url: * - make relative paths absolute using the current working directory * - prefer local file, if in doubt! */ url = QUrl::fromUserInput(path, QDir::currentPath(), QUrl::AssumeLocalFile); /** * in some cases, this will fail, e.g. if you have line/column specs like test.c:10:1 * => fallback: assume a local file and just convert it to an url */ if (!url.isValid()) { /** * create absolute file path, we will e.g. pass this over dbus to other processes */ url = QUrl::fromLocalFile(QDir::current().absoluteFilePath(path)); } } /** * url computed out of the passed path */ QUrl url; /** - * initial cursor position, if any found inside the path as line/colum specification at the end + * initial cursor position, if any found inside the path as line/column specification at the end */ KTextEditor::Cursor cursor; }; #endif // URLINFO_H