diff --git a/CMakeLists.txt b/CMakeLists.txt index 2acc50236..539b4a32b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,40 +1,43 @@ # 3.1 is required for `target_sources`. cmake_minimum_required(VERSION 3.1 FATAL_ERROR) # KDE Applications version, managed by release script. set(KDE_APPLICATIONS_VERSION_MAJOR "19") set(KDE_APPLICATIONS_VERSION_MINOR "11") set(KDE_APPLICATIONS_VERSION_MICRO "70") set(KDE_APPLICATIONS_VERSION "${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}.${KDE_APPLICATIONS_VERSION_MICRO}") project(kate VERSION ${KDE_APPLICATIONS_VERSION}) set(QT_MIN_VERSION "5.4.0") set(KF5_DEP_VERSION "5.40.0") # We need some parts of the ECM CMake helpers. find_package(ECM ${KF5_DEP_VERSION} QUIET REQUIRED NO_MODULE) # We append to the module path so modules can be overriden from the command line. list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) # Allow adding Qt resource files with `add_executable` or `target_sources` instead of # `qt5_add_resources`. See https://cmake.org/cmake/help/v3.0/manual/cmake-qt.7.html#autorcc. set(CMAKE_AUTORCC ON) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDEInstallDirs) include(KDECMakeSettings) include(ECMOptionalAddSubdirectory) include(ECMAddAppIcon) include(ECMInstallIcons) include(FeatureSummary) +# forbid some old things +add_definitions(-DQT_NO_FOREACH) + ecm_optional_add_subdirectory(addons) ecm_optional_add_subdirectory(kwrite) ecm_optional_add_subdirectory(kate) ecm_optional_add_subdirectory(doc) feature_summary(INCLUDE_QUIET_PACKAGES WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/addons/katesql/dataoutputmodel.cpp b/addons/katesql/dataoutputmodel.cpp index c602275f7..97c01c5b0 100644 --- a/addons/katesql/dataoutputmodel.cpp +++ b/addons/katesql/dataoutputmodel.cpp @@ -1,198 +1,199 @@ /* Copyright (C) 2010 Marco Mentasti 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 "dataoutputmodel.h" #include "outputstyle.h" #include #include #include #include #include #include inline bool isNumeric(const QVariant::Type type) { return (type > 1 && type < 7); } DataOutputModel::DataOutputModel(QObject *parent) : CachedSqlQueryModel(parent, 1000) { m_useSystemLocale = false; m_styles.insert(QStringLiteral("text"), new OutputStyle()); m_styles.insert(QStringLiteral("number"), new OutputStyle()); m_styles.insert(QStringLiteral("null"), new OutputStyle()); m_styles.insert(QStringLiteral("blob"), new OutputStyle()); m_styles.insert(QStringLiteral("datetime"), new OutputStyle()); m_styles.insert(QStringLiteral("bool"), new OutputStyle()); readConfig(); } DataOutputModel::~DataOutputModel() { qDeleteAll(m_styles); } void DataOutputModel::clear() { beginResetModel(); CachedSqlQueryModel::clear(); endResetModel(); } void DataOutputModel::readConfig() { KConfigGroup config(KSharedConfig::openConfig(), "KateSQLPlugin"); KConfigGroup group = config.group("OutputCustomization"); KColorScheme scheme(QPalette::Active, KColorScheme::View); - foreach (const QString &k, m_styles.keys()) { + const auto styleKeys = m_styles.keys(); + for (const QString &k : styleKeys) { OutputStyle *s = m_styles[k]; KConfigGroup g = group.group(k); s->foreground = scheme.foreground(); s->background = scheme.background(); s->font = QFontDatabase::systemFont(QFontDatabase::GeneralFont); QFont dummy = g.readEntry("font", QFontDatabase::systemFont(QFontDatabase::GeneralFont)); s->font.setBold(dummy.bold()); s->font.setItalic(dummy.italic()); s->font.setUnderline(dummy.underline()); s->font.setStrikeOut(dummy.strikeOut()); s->foreground.setColor(g.readEntry("foregroundColor", s->foreground.color())); s->background.setColor(g.readEntry("backgroundColor", s->background.color())); } emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); } bool DataOutputModel::useSystemLocale() const { return m_useSystemLocale; } void DataOutputModel::setUseSystemLocale(bool useSystemLocale) { m_useSystemLocale = useSystemLocale; emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); } QVariant DataOutputModel::data(const QModelIndex &index, int role) const { if (role == Qt::EditRole) return CachedSqlQueryModel::data(index, role); const QVariant value(CachedSqlQueryModel::data(index, Qt::DisplayRole)); const QVariant::Type type = value.type(); if (value.isNull()) { if (role == Qt::FontRole) return QVariant(m_styles.value(QStringLiteral("null"))->font); if (role == Qt::ForegroundRole) return QVariant(m_styles.value(QStringLiteral("null"))->foreground); if (role == Qt::BackgroundRole) return QVariant(m_styles.value(QStringLiteral("null"))->background); if (role == Qt::DisplayRole) return QVariant(QLatin1String("NULL")); } if (type == QVariant::ByteArray) { if (role == Qt::FontRole) return QVariant(m_styles.value(QStringLiteral("blob"))->font); if (role == Qt::ForegroundRole) return QVariant(m_styles.value(QStringLiteral("blob"))->foreground); if (role == Qt::BackgroundRole) return QVariant(m_styles.value(QStringLiteral("blob"))->background); if (role == Qt::DisplayRole) return QVariant(value.toByteArray().left(255)); } if (isNumeric(type)) { if (role == Qt::FontRole) return QVariant(m_styles.value(QStringLiteral("number"))->font); if (role == Qt::ForegroundRole) return QVariant(m_styles.value(QStringLiteral("number"))->foreground); if (role == Qt::BackgroundRole) return QVariant(m_styles.value(QStringLiteral("number"))->background); if (role == Qt::TextAlignmentRole) return QVariant(Qt::AlignRight | Qt::AlignVCenter); if (role == Qt::DisplayRole || role == Qt::UserRole) { if (useSystemLocale()) return QVariant(value.toString()); // FIXME KF5 KGlobal::locale()->formatNumber(value.toString(), false)); else return QVariant(value.toString()); } } if (type == QVariant::Bool) { if (role == Qt::FontRole) return QVariant(m_styles.value(QStringLiteral("bool"))->font); if (role == Qt::ForegroundRole) return QVariant(m_styles.value(QStringLiteral("bool"))->foreground); if (role == Qt::BackgroundRole) return QVariant(m_styles.value(QStringLiteral("bool"))->background); if (role == Qt::DisplayRole) return QVariant(value.toBool() ? QLatin1String("True") : QLatin1String("False")); } if (type == QVariant::Date || type == QVariant::Time || type == QVariant::DateTime) { if (role == Qt::FontRole) return QVariant(m_styles.value(QStringLiteral("datetime"))->font); if (role == Qt::ForegroundRole) return QVariant(m_styles.value(QStringLiteral("datetime"))->foreground); if (role == Qt::BackgroundRole) return QVariant(m_styles.value(QStringLiteral("datetime"))->background); if (role == Qt::DisplayRole || role == Qt::UserRole) { if (useSystemLocale()) { if (type == QVariant::Date) return QVariant(QLocale().toString(value.toDate(), QLocale::ShortFormat)); if (type == QVariant::Time) return QVariant(QLocale().toString(value.toTime())); if (type == QVariant::DateTime) return QVariant(QLocale().toString(value.toDateTime(), QLocale::ShortFormat)); } else // return sql server format return QVariant(value.toString()); } } if (role == Qt::FontRole) return QVariant(m_styles.value(QStringLiteral("text"))->font); if (role == Qt::ForegroundRole) return QVariant(m_styles.value(QStringLiteral("text"))->foreground); if (role == Qt::BackgroundRole) return QVariant(m_styles.value(QStringLiteral("text"))->background); if (role == Qt::TextAlignmentRole) return QVariant(Qt::AlignVCenter); if (role == Qt::DisplayRole) return value.toString(); if (role == Qt::UserRole) return value; return CachedSqlQueryModel::data(index, role); } diff --git a/addons/katesql/sqlmanager.cpp b/addons/katesql/sqlmanager.cpp index 667f8bb11..b322cfd9d 100644 --- a/addons/katesql/sqlmanager.cpp +++ b/addons/katesql/sqlmanager.cpp @@ -1,356 +1,357 @@ /* Copyright (C) 2010 Marco Mentasti 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 "sqlmanager.h" #include "connectionmodel.h" #include #include #include #include #include #include #include #include using KWallet::Wallet; SQLManager::SQLManager(QObject *parent) : QObject(parent) , m_model(new ConnectionModel(this)) { } SQLManager::~SQLManager() { for (int i = 0; i < m_model->rowCount(); i++) { QString connection = m_model->data(m_model->index(i), Qt::DisplayRole).toString(); QSqlDatabase::removeDatabase(connection); } delete m_model; delete m_wallet; } void SQLManager::createConnection(const Connection &conn) { if (QSqlDatabase::contains(conn.name)) { qDebug() << "connection" << conn.name << "already exist"; QSqlDatabase::removeDatabase(conn.name); } QSqlDatabase db = QSqlDatabase::addDatabase(conn.driver, conn.name); if (!db.isValid()) { emit error(db.lastError().text()); QSqlDatabase::removeDatabase(conn.name); return; } db.setHostName(conn.hostname); db.setUserName(conn.username); db.setPassword(conn.password); db.setDatabaseName(conn.database); db.setConnectOptions(conn.options); if (conn.port > 0) db.setPort(conn.port); m_model->addConnection(conn); // try to open connection, with or without password if (db.open()) m_model->setStatus(conn.name, Connection::ONLINE); else { if (conn.status != Connection::REQUIRE_PASSWORD) { m_model->setStatus(conn.name, Connection::OFFLINE); emit error(db.lastError().text()); } } emit connectionCreated(conn.name); } bool SQLManager::testConnection(const Connection &conn, QSqlError &error) { QString connectionName = (conn.name.isEmpty()) ? QStringLiteral("katesql-test") : conn.name; QSqlDatabase db = QSqlDatabase::addDatabase(conn.driver, connectionName); if (!db.isValid()) { error = db.lastError(); QSqlDatabase::removeDatabase(connectionName); return false; } db.setHostName(conn.hostname); db.setUserName(conn.username); db.setPassword(conn.password); db.setDatabaseName(conn.database); db.setConnectOptions(conn.options); if (conn.port > 0) db.setPort(conn.port); if (!db.open()) { error = db.lastError(); QSqlDatabase::removeDatabase(connectionName); return false; } QSqlDatabase::removeDatabase(connectionName); return true; } bool SQLManager::isValidAndOpen(const QString &connection) { QSqlDatabase db = QSqlDatabase::database(connection); if (!db.isValid()) { m_model->setStatus(connection, Connection::OFFLINE); emit error(db.lastError().text()); return false; } if (!db.isOpen()) { qDebug() << "database connection is not open. trying to open it..."; if (m_model->status(connection) == Connection::REQUIRE_PASSWORD) { QString password; int ret = readCredentials(connection, password); if (ret != 0) qDebug() << "Can't retrieve password from kwallet. returned code" << ret; else { db.setPassword(password); m_model->setPassword(connection, password); } } if (!db.open()) { m_model->setStatus(connection, Connection::OFFLINE); emit error(db.lastError().text()); return false; } } m_model->setStatus(connection, Connection::ONLINE); return true; } void SQLManager::reopenConnection(const QString &name) { emit connectionAboutToBeClosed(name); QSqlDatabase db = QSqlDatabase::database(name); db.close(); isValidAndOpen(name); } Wallet *SQLManager::openWallet() { if (!m_wallet) /// FIXME get kate window id... m_wallet = Wallet::openWallet(KWallet::Wallet::NetworkWallet(), 0); if (!m_wallet) return nullptr; QString folder(QStringLiteral("SQL Connections")); if (!m_wallet->hasFolder(folder)) m_wallet->createFolder(folder); m_wallet->setFolder(folder); return m_wallet; } // return 0 on success, -1 on error, -2 on user reject int SQLManager::storeCredentials(const Connection &conn) { // Sqlite is without password, avoid to open wallet if (conn.driver.contains(QLatin1String("QSQLITE"))) return 0; Wallet *wallet = openWallet(); if (!wallet) // user reject return -2; QMap map; map[QStringLiteral("driver")] = conn.driver.toUpper(); map[QStringLiteral("hostname")] = conn.hostname.toUpper(); map[QStringLiteral("port")] = QString::number(conn.port); map[QStringLiteral("database")] = conn.database.toUpper(); map[QStringLiteral("username")] = conn.username; map[QStringLiteral("password")] = conn.password; return (wallet->writeMap(conn.name, map) == 0) ? 0 : -1; } // return 0 on success, -1 on error or not found, -2 on user reject // if success, password contain the password int SQLManager::readCredentials(const QString &name, QString &password) { Wallet *wallet = openWallet(); if (!wallet) // user reject return -2; QMap map; if (wallet->readMap(name, map) == 0) { if (!map.isEmpty()) { password = map.value(QStringLiteral("password")); return 0; } } return -1; } ConnectionModel *SQLManager::connectionModel() { return m_model; } void SQLManager::removeConnection(const QString &name) { emit connectionAboutToBeClosed(name); m_model->removeConnection(name); QSqlDatabase::removeDatabase(name); emit connectionRemoved(name); } /// TODO: read KUrl instead of QString for sqlite paths void SQLManager::loadConnections(KConfigGroup *connectionsGroup) { Connection c; - foreach (const QString &groupName, connectionsGroup->groupList()) { + const auto groupList = connectionsGroup->groupList(); + for (const QString &groupName : groupList) { qDebug() << "reading group:" << groupName; KConfigGroup group = connectionsGroup->group(groupName); c.name = groupName; c.driver = group.readEntry("driver"); c.database = group.readEntry("database"); c.options = group.readEntry("options"); if (!c.driver.contains(QLatin1String("QSQLITE"))) { c.hostname = group.readEntry("hostname"); c.username = group.readEntry("username"); c.port = group.readEntry("port", 0); // for compatibility with version 0.2, when passwords // were stored in config file instead of kwallet c.password = group.readEntry("password"); if (!c.password.isEmpty()) c.status = Connection::ONLINE; else c.status = Connection::REQUIRE_PASSWORD; } createConnection(c); } } void SQLManager::saveConnections(KConfigGroup *connectionsGroup) { for (int i = 0; i < m_model->rowCount(); i++) saveConnection(connectionsGroup, m_model->data(m_model->index(i), Qt::UserRole).value()); } /// TODO: write KUrl instead of QString for sqlite paths void SQLManager::saveConnection(KConfigGroup *connectionsGroup, const Connection &conn) { qDebug() << "saving connection" << conn.name; KConfigGroup group = connectionsGroup->group(conn.name); group.writeEntry("driver", conn.driver); group.writeEntry("database", conn.database); group.writeEntry("options", conn.options); if (!conn.driver.contains(QLatin1String("QSQLITE"))) { group.writeEntry("hostname", conn.hostname); group.writeEntry("username", conn.username); group.writeEntry("port", conn.port); } } void SQLManager::runQuery(const QString &text, const QString &connection) { qDebug() << "connection:" << connection; qDebug() << "text:" << text; if (text.isEmpty()) return; if (!isValidAndOpen(connection)) return; QSqlDatabase db = QSqlDatabase::database(connection); QSqlQuery query(db); if (!query.prepare(text)) { QSqlError err = query.lastError(); if (err.type() == QSqlError::ConnectionError) m_model->setStatus(connection, Connection::OFFLINE); emit error(err.text()); return; } if (!query.exec()) { QSqlError err = query.lastError(); if (err.type() == QSqlError::ConnectionError) m_model->setStatus(connection, Connection::OFFLINE); emit error(err.text()); return; } QString message; /// TODO: improve messages if (query.isSelect()) { if (!query.driver()->hasFeature(QSqlDriver::QuerySize)) message = i18nc("@info", "Query completed successfully"); else { int nRowsSelected = query.size(); message = i18ncp("@info", "%1 record selected", "%1 records selected", nRowsSelected); } } else { int nRowsAffected = query.numRowsAffected(); message = i18ncp("@info", "%1 row affected", "%1 rows affected", nRowsAffected); } emit success(message); emit queryActivated(query, connection); } diff --git a/addons/project/kateprojectpluginview.cpp b/addons/project/kateprojectpluginview.cpp index a3e3f416d..6eddcee25 100644 --- a/addons/project/kateprojectpluginview.cpp +++ b/addons/project/kateprojectpluginview.cpp @@ -1,472 +1,475 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * This library 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 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 "kateprojectpluginview.h" #include "kateprojectinfoviewindex.h" #include "fileutil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KateProjectPluginFactory, "kateprojectplugin.json", registerPlugin();) KateProjectPluginView::KateProjectPluginView(KateProjectPlugin *plugin, KTextEditor::MainWindow *mainWin) : QObject(mainWin) , m_plugin(plugin) , m_mainWindow(mainWin) , m_toolView(nullptr) , m_toolInfoView(nullptr) , m_lookupAction(nullptr) { KXMLGUIClient::setComponentName(QStringLiteral("kateproject"), i18n("Kate Project Manager")); setXMLFile(QStringLiteral("ui.rc")); /** * create toolviews */ m_toolView = m_mainWindow->createToolView(m_plugin, QStringLiteral("kateproject"), KTextEditor::MainWindow::Left, QIcon::fromTheme(QStringLiteral("project-open")), i18n("Projects")); m_toolInfoView = m_mainWindow->createToolView(m_plugin, QStringLiteral("kateprojectinfo"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("view-choose")), i18n("Current Project")); /** * create the combo + buttons for the toolViews + stacked widgets */ m_projectsCombo = new QComboBox(m_toolView); m_projectsCombo->setFrame(false); m_reloadButton = new QToolButton(m_toolView); m_reloadButton->setAutoRaise(true); m_reloadButton->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); QHBoxLayout *layout = new QHBoxLayout(); layout->setSpacing(0); layout->addWidget(m_projectsCombo); layout->addWidget(m_reloadButton); m_toolView->layout()->addItem(layout); m_toolView->layout()->setSpacing(0); m_stackedProjectViews = new QStackedWidget(m_toolView); m_stackedProjectInfoViews = new QStackedWidget(m_toolInfoView); connect(m_projectsCombo, static_cast(&QComboBox::currentIndexChanged), this, &KateProjectPluginView::slotCurrentChanged); connect(m_reloadButton, &QToolButton::clicked, this, &KateProjectPluginView::slotProjectReload); /** * create views for all already existing projects * will create toolviews on demand! */ - foreach (KateProject *project, m_plugin->projects()) + const auto projectList = m_plugin->projects(); + for (KateProject *project : projectList) viewForProject(project); /** * connect to important signals, e.g. for auto project view creation */ connect(m_plugin, &KateProjectPlugin::projectCreated, this, &KateProjectPluginView::viewForProject); connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &KateProjectPluginView::slotViewChanged); connect(m_mainWindow, &KTextEditor::MainWindow::viewCreated, this, &KateProjectPluginView::slotViewCreated); /** * connect for all already existing views */ - foreach (KTextEditor::View *view, m_mainWindow->views()) + const auto views = m_mainWindow->views(); + for (KTextEditor::View *view : views) slotViewCreated(view); /** * trigger once view change, to highlight right document */ slotViewChanged(); /** * back + forward */ auto a = actionCollection()->addAction(KStandardAction::Back, QStringLiteral("projects_prev_project"), this, SLOT(slotProjectPrev())); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Left)); a = actionCollection()->addAction(KStandardAction::Forward, QStringLiteral("projects_next_project"), this, SLOT(slotProjectNext())); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Right)); a = actionCollection()->addAction(KStandardAction::Goto, QStringLiteral("projects_goto_index"), this, SLOT(slotProjectIndex())); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::Key_1)); // popup menu auto popup = new KActionMenu(i18n("Project"), this); actionCollection()->addAction(QStringLiteral("popup_project"), popup); m_lookupAction = popup->menu()->addAction(i18n("Lookup: %1", QString()), this, &KateProjectPluginView::slotProjectIndex); connect(popup->menu(), &QMenu::aboutToShow, this, &KateProjectPluginView::slotContextMenuAboutToShow); /** * add us to gui */ m_mainWindow->guiFactory()->addClient(this); } KateProjectPluginView::~KateProjectPluginView() { /** * cleanup for all views */ for (QObject *view : qAsConst(m_textViews)) { KTextEditor::CodeCompletionInterface *cci = qobject_cast(view); if (cci) { cci->unregisterCompletionModel(m_plugin->completion()); } } /** * cu toolviews */ delete m_toolView; m_toolView = nullptr; delete m_toolInfoView; m_toolInfoView = nullptr; /** * cu gui client */ m_mainWindow->guiFactory()->removeClient(this); } QPair KateProjectPluginView::viewForProject(KateProject *project) { /** * needs valid project */ Q_ASSERT(project); /** * existing view? */ if (m_project2View.contains(project)) { return m_project2View.value(project); } /** * create new views */ KateProjectView *view = new KateProjectView(this, project); KateProjectInfoView *infoView = new KateProjectInfoView(this, project); /** * attach to toolboxes * first the views, then the combo, that triggers signals */ m_stackedProjectViews->addWidget(view); m_stackedProjectInfoViews->addWidget(infoView); m_projectsCombo->addItem(QIcon::fromTheme(QStringLiteral("project-open")), project->name(), project->fileName()); /** * remember and return it */ return (m_project2View[project] = QPair(view, infoView)); } QString KateProjectPluginView::projectFileName() const { QWidget *active = m_stackedProjectViews->currentWidget(); if (!active) { return QString(); } return static_cast(active)->project()->fileName(); } QString KateProjectPluginView::projectName() const { QWidget *active = m_stackedProjectViews->currentWidget(); if (!active) { return QString(); } return static_cast(active)->project()->name(); } QString KateProjectPluginView::projectBaseDir() const { QWidget *active = m_stackedProjectViews->currentWidget(); if (!active) { return QString(); } return static_cast(active)->project()->baseDir(); } QVariantMap KateProjectPluginView::projectMap() const { QWidget *active = m_stackedProjectViews->currentWidget(); if (!active) { return QVariantMap(); } return static_cast(active)->project()->projectMap(); } QStringList KateProjectPluginView::projectFiles() const { KateProjectView *active = static_cast(m_stackedProjectViews->currentWidget()); if (!active) { return QStringList(); } return active->project()->files(); } QString KateProjectPluginView::allProjectsCommonBaseDir() const { auto projects = m_plugin->projects(); if (projects.empty()) { return QString(); } if (projects.size() == 1) { return projects[0]->baseDir(); } QString commonParent1 = FileUtil::commonParent(projects[0]->baseDir(), projects[1]->baseDir()); for (int i = 2; i < projects.size(); i++) { commonParent1 = FileUtil::commonParent(commonParent1, projects[i]->baseDir()); } return commonParent1; } QStringList KateProjectPluginView::allProjectsFiles() const { QStringList fileList; - foreach (auto project, m_plugin->projects()) { + const auto projectList = m_plugin->projects(); + for (auto project : projectList) { fileList.append(project->files()); } return fileList; } void KateProjectPluginView::slotViewChanged() { /** * get active view */ KTextEditor::View *activeView = m_mainWindow->activeView(); /** * update pointer, maybe disconnect before */ if (m_activeTextEditorView) { m_activeTextEditorView->document()->disconnect(this); } m_activeTextEditorView = activeView; /** * no current active view, return */ if (!m_activeTextEditorView) { return; } /** * connect to url changed, for auto load */ connect(m_activeTextEditorView->document(), &KTextEditor::Document::documentUrlChanged, this, &KateProjectPluginView::slotDocumentUrlChanged); /** * trigger slot once */ slotDocumentUrlChanged(m_activeTextEditorView->document()); } void KateProjectPluginView::slotCurrentChanged(int index) { // trigger change of stacked widgets m_stackedProjectViews->setCurrentIndex(index); m_stackedProjectInfoViews->setCurrentIndex(index); // update focus proxy + open currently selected document if (QWidget *current = m_stackedProjectViews->currentWidget()) { m_stackedProjectViews->setFocusProxy(current); static_cast(current)->openSelectedDocument(); } // update focus proxy if (QWidget *current = m_stackedProjectInfoViews->currentWidget()) { m_stackedProjectInfoViews->setFocusProxy(current); } // project file name might have changed emit projectFileNameChanged(); emit projectMapChanged(); } void KateProjectPluginView::slotDocumentUrlChanged(KTextEditor::Document *document) { /** * abort if empty url or no local path */ if (document->url().isEmpty() || !document->url().isLocalFile()) { return; } /** * search matching project */ KateProject *project = m_plugin->projectForUrl(document->url()); if (!project) { return; } /** * select the file FIRST */ m_project2View.value(project).first->selectFile(document->url().toLocalFile()); /** * get active project view and switch it, if it is for a different project * do this AFTER file selection */ KateProjectView *active = static_cast(m_stackedProjectViews->currentWidget()); if (active != m_project2View.value(project).first) { int index = m_projectsCombo->findData(project->fileName()); if (index >= 0) { m_projectsCombo->setCurrentIndex(index); } } } void KateProjectPluginView::slotViewCreated(KTextEditor::View *view) { /** * connect to destroyed */ connect(view, &KTextEditor::View::destroyed, this, &KateProjectPluginView::slotViewDestroyed); /** * add completion model if possible */ KTextEditor::CodeCompletionInterface *cci = qobject_cast(view); if (cci) { cci->registerCompletionModel(m_plugin->completion()); } /** * remember for this view we need to cleanup! */ m_textViews.insert(view); } void KateProjectPluginView::slotViewDestroyed(QObject *view) { /** * remove remembered views for which we need to cleanup on exit! */ m_textViews.remove(view); } void KateProjectPluginView::slotProjectPrev() { if (!m_projectsCombo->count()) { return; } if (m_projectsCombo->currentIndex() == 0) { m_projectsCombo->setCurrentIndex(m_projectsCombo->count() - 1); } else { m_projectsCombo->setCurrentIndex(m_projectsCombo->currentIndex() - 1); } } void KateProjectPluginView::slotProjectNext() { if (!m_projectsCombo->count()) { return; } if (m_projectsCombo->currentIndex() + 1 == m_projectsCombo->count()) { m_projectsCombo->setCurrentIndex(0); } else { m_projectsCombo->setCurrentIndex(m_projectsCombo->currentIndex() + 1); } } void KateProjectPluginView::slotProjectReload() { /** * force reload if any active project */ if (QWidget *current = m_stackedProjectViews->currentWidget()) { static_cast(current)->project()->reload(true); } } QString KateProjectPluginView::currentWord() const { KTextEditor::View *kv = m_activeTextEditorView; if (!kv) { return QString(); } if (kv->selection() && kv->selectionRange().onSingleLine()) { return kv->selectionText(); } return kv->document()->wordAt(kv->cursorPosition()); } void KateProjectPluginView::slotProjectIndex() { const QString word = currentWord(); if (!word.isEmpty()) { auto tabView = qobject_cast(m_stackedProjectInfoViews->currentWidget()); if (tabView) { if (auto codeIndex = tabView->findChild()) { tabView->setCurrentWidget(codeIndex); } } m_mainWindow->showToolView(m_toolInfoView); emit projectLookupWord(word); } } void KateProjectPluginView::slotContextMenuAboutToShow() { const QString word = currentWord(); if (word.isEmpty()) { return; } const QString squeezed = KStringHandler::csqueeze(word, 30); m_lookupAction->setText(i18n("Lookup: %1", squeezed)); } #include "kateprojectpluginview.moc" diff --git a/addons/replicode/replicodeview.cpp b/addons/replicode/replicodeview.cpp index dc1298d9c..e658dd941 100644 --- a/addons/replicode/replicodeview.cpp +++ b/addons/replicode/replicodeview.cpp @@ -1,251 +1,253 @@ /* This file is part of the KDE project Copyright (C) 2014 Martin Sandsmark This library 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 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 "replicodeview.h" #include #include #include #include #include "replicodesettings.h" #include "replicodeconfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ReplicodeView::ReplicodeView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWindow) : QObject(mainWindow) , m_mainWindow(mainWindow) , m_executor(nullptr) { m_runAction = new QAction(QIcon(QStringLiteral("code-block")), i18n("Run replicode"), this); connect(m_runAction, &QAction::triggered, this, &ReplicodeView::runReplicode); actionCollection()->addAction(QStringLiteral("katereplicode_run"), m_runAction); m_stopAction = new QAction(QIcon(QStringLiteral("process-stop")), i18n("Stop replicode"), this); connect(m_stopAction, &QAction::triggered, this, &ReplicodeView::stopReplicode); actionCollection()->addAction(QStringLiteral("katereplicode_stop"), m_stopAction); m_stopAction->setEnabled(false); m_toolview = m_mainWindow->createToolView(plugin, QStringLiteral("kate_private_plugin_katereplicodeplugin_run"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("code-block")), i18n("Replicode Output")); m_replicodeOutput = new QListWidget(m_toolview); m_replicodeOutput->setSelectionMode(QAbstractItemView::ContiguousSelection); connect(m_replicodeOutput, &QListWidget::itemActivated, this, &ReplicodeView::outputClicked); m_mainWindow->hideToolView(m_toolview); m_configSidebar = m_mainWindow->createToolView(plugin, QStringLiteral("kate_private_plugin_katereplicodeplugin_config"), KTextEditor::MainWindow::Right, QIcon::fromTheme(QStringLiteral("code-block")), i18n("Replicode Config")); m_configView = new ReplicodeConfig(m_configSidebar); m_runButton = new QPushButton(i18nc("shortcut for action", "Run (%1)", m_runAction->shortcut().toString())); m_stopButton = new QPushButton(i18nc("shortcut for action", "Stop (%1)", m_stopAction->shortcut().toString())); m_stopButton->setEnabled(false); QFormLayout *l = qobject_cast(m_configView->widget(0)->layout()); Q_ASSERT(l); l->addRow(m_runButton, m_stopButton); connect(m_runButton, &QPushButton::clicked, m_runAction, &QAction::trigger); connect(m_stopButton, &QPushButton::clicked, m_stopAction, &QAction::trigger); m_mainWindow->guiFactory()->addClient(this); connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &ReplicodeView::viewChanged); } ReplicodeView::~ReplicodeView() { m_mainWindow->guiFactory()->removeClient(this); delete m_executor; } void ReplicodeView::viewChanged() { if (m_mainWindow->activeView() && m_mainWindow->activeView()->document() && m_mainWindow->activeView()->document()->url().fileName().endsWith(QLatin1String(".replicode"))) { m_mainWindow->showToolView(m_configSidebar); } else { m_mainWindow->hideToolView(m_configSidebar); m_mainWindow->hideToolView(m_toolview); } } void ReplicodeView::runReplicode() { m_mainWindow->showToolView(m_toolview); KTextEditor::View *editor = m_mainWindow->activeView(); if (!editor || !editor->document()) { QMessageBox::warning(m_mainWindow->window(), i18nc("@title:window", "Active Document Not Found"), i18n("Could not find an active document to run.")); return; } if (editor->document()->isEmpty()) { QMessageBox::warning(m_mainWindow->window(), i18nc("@title:window", "Empty Document"), i18n("Cannot execute an empty document.")); return; } QFileInfo sourceFile = QFileInfo(editor->document()->url().toLocalFile()); if (!sourceFile.isReadable()) { QMessageBox::warning(m_mainWindow->window(), i18nc("@title:window", "File Not Found"), i18n("Unable to open source file for reading.")); return; } KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("Replicode")); QString executorPath = config.readEntry("replicodePath", QString()); if (executorPath.isEmpty()) { QMessageBox::warning(m_mainWindow->window(), i18nc("@title:window", "Replicode Executable Not Found"), i18n("Unable to find replicode executor.\n" "Please go to settings and set the path to the Replicode executable.")); return; } if (m_configView->settingsObject()->userOperatorPath.isEmpty()) { QMessageBox::warning(m_mainWindow->window(), i18nc("@title:window", "User Operator Library Not Found"), i18n("Unable to find user operator library.\n" "Please go to settings and set the path to the library.")); } m_configView->settingsObject()->sourcePath = editor->document()->url().toLocalFile(); m_configView->load(); m_configView->settingsObject()->save(); m_replicodeOutput->clear(); if (m_executor) delete m_executor; m_executor = new QProcess(this); m_executor->setWorkingDirectory(sourceFile.canonicalPath()); connect(m_executor, &QProcess::readyReadStandardError, this, &ReplicodeView::gotStderr); connect(m_executor, &QProcess::readyReadStandardOutput, this, &ReplicodeView::gotStdout); connect(m_executor, static_cast(&QProcess::finished), this, &ReplicodeView::replicodeFinished); connect(m_executor, static_cast(&QProcess::error), this, &ReplicodeView::runErrored); qDebug() << executorPath << sourceFile.canonicalPath(); m_completed = false; m_executor->start(executorPath, QStringList(), QProcess::ReadOnly); m_runAction->setEnabled(false); m_runButton->setEnabled(false); m_stopAction->setEnabled(true); m_stopButton->setEnabled(true); } void ReplicodeView::stopReplicode() { if (m_executor) { m_executor->kill(); } } void ReplicodeView::outputClicked(QListWidgetItem *item) { QString output = item->text(); QStringList pieces = output.split(QLatin1Char(':')); if (pieces.length() < 2) return; QFileInfo file(pieces[0]); if (!file.isReadable()) return; bool ok = false; int lineNumber = pieces[1].toInt(&ok); qDebug() << lineNumber; if (!ok) return; KTextEditor::View *doc = m_mainWindow->openUrl(QUrl::fromLocalFile(pieces[0])); doc->setCursorPosition(KTextEditor::Cursor(lineNumber, 0)); qDebug() << doc->cursorPosition().line(); } void ReplicodeView::runErrored(QProcess::ProcessError error) { Q_UNUSED(error); QListWidgetItem *item = new QListWidgetItem(i18n("Replicode execution failed: %1", m_executor->errorString())); item->setForeground(Qt::red); m_replicodeOutput->addItem(item); m_replicodeOutput->scrollToBottom(); m_completed = true; } void ReplicodeView::replicodeFinished() { if (!m_completed) { QListWidgetItem *item = new QListWidgetItem(i18n("Replicode execution finished.")); item->setForeground(Qt::blue); m_replicodeOutput->addItem(item); m_replicodeOutput->scrollToBottom(); } m_runAction->setEnabled(true); m_runButton->setEnabled(true); m_stopAction->setEnabled(false); m_stopButton->setEnabled(false); // delete m_executor; // delete m_settingsFile; // m_executor = 0; // m_settingsFile = 0; } void ReplicodeView::gotStderr() { - QByteArray output = m_executor->readAllStandardError(); - foreach (QByteArray line, output.split('\n')) { + const QByteArray output = m_executor->readAllStandardError(); + const auto lines = output.split('\n'); + for (QByteArray line : lines) { line = line.simplified(); if (line.isEmpty()) continue; QListWidgetItem *item = new QListWidgetItem(QString::fromLocal8Bit(line)); item->setForeground(Qt::red); m_replicodeOutput->addItem(item); } m_replicodeOutput->scrollToBottom(); } void ReplicodeView::gotStdout() { - QByteArray output = m_executor->readAllStandardOutput(); - foreach (QByteArray line, output.split('\n')) { + const QByteArray output = m_executor->readAllStandardOutput(); + const auto lines = output.split('\n'); + for (QByteArray line : lines) { line = line.simplified(); if (line.isEmpty()) continue; QListWidgetItem *item = new QListWidgetItem(QString::fromLocal8Bit(' ' + line)); if (line[0] == '>') item->setForeground(Qt::gray); m_replicodeOutput->addItem(item); } m_replicodeOutput->scrollToBottom(); } diff --git a/addons/search/FolderFilesList.cpp b/addons/search/FolderFilesList.cpp index 5dfafdb65..e3aee444a 100644 --- a/addons/search/FolderFilesList.cpp +++ b/addons/search/FolderFilesList.cpp @@ -1,148 +1,149 @@ /* Kate search plugin * * Copyright (C) 2013 by Kåre Särs * * 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 in a file called COPYING; if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include "FolderFilesList.h" #include #include #include #include #include #include FolderFilesList::FolderFilesList(QObject *parent) : QThread(parent) { } FolderFilesList::~FolderFilesList() { m_cancelSearch = true; wait(); } void FolderFilesList::run() { m_files.clear(); QFileInfo folderInfo(m_folder); checkNextItem(folderInfo); if (m_cancelSearch) m_files.clear(); } void FolderFilesList::generateList(const QString &folder, bool recursive, bool hidden, bool symlinks, bool binary, const QString &types, const QString &excludes) { m_cancelSearch = false; m_folder = folder; if (!m_folder.endsWith(QLatin1Char('/'))) { m_folder += QLatin1Char('/'); } m_recursive = recursive; m_hidden = hidden; m_symlinks = symlinks; m_binary = binary; m_types.clear(); - foreach (QString type, types.split(QLatin1Char(','), QString::SkipEmptyParts)) { + const auto typesList = types.split(QLatin1Char(','), QString::SkipEmptyParts); + for (const QString &type : typesList) { m_types << type.trimmed(); } if (m_types.isEmpty()) { m_types << QStringLiteral("*"); } QStringList tmpExcludes = excludes.split(QLatin1Char(',')); m_excludeList.clear(); for (int i = 0; i < tmpExcludes.size(); i++) { QRegExp rx(tmpExcludes[i].trimmed()); rx.setPatternSyntax(QRegExp::Wildcard); m_excludeList << rx; } m_time.restart(); start(); } QStringList FolderFilesList::fileList() { return m_files; } void FolderFilesList::cancelSearch() { m_cancelSearch = true; } void FolderFilesList::checkNextItem(const QFileInfo &item) { if (m_cancelSearch) { return; } if (m_time.elapsed() > 100) { m_time.restart(); emit searching(item.absoluteFilePath()); } if (item.isFile()) { if (!m_binary) { QMimeType mimeType = QMimeDatabase().mimeTypeForFile(item); if (!mimeType.inherits(QStringLiteral("text/plain"))) { return; } } m_files << item.canonicalFilePath(); } else { QDir currentDir(item.absoluteFilePath()); if (!currentDir.isReadable()) { qDebug() << currentDir.absolutePath() << "Not readable"; return; } QDir::Filters filter = QDir::Files | QDir::NoDotAndDotDot | QDir::Readable; if (m_hidden) filter |= QDir::Hidden; if (m_recursive) filter |= QDir::AllDirs; if (!m_symlinks) filter |= QDir::NoSymLinks; // sort the items to have an deterministic order! const QFileInfoList currentItems = currentDir.entryInfoList(m_types, filter, QDir::Name | QDir::LocaleAware); bool skip; for (const auto ¤tItem : currentItems) { skip = false; for (const auto ®ex : qAsConst(m_excludeList)) { QString matchString = currentItem.filePath(); if (currentItem.filePath().startsWith(m_folder)) { matchString = currentItem.filePath().mid(m_folder.size()); } if (regex.exactMatch(matchString)) { skip = true; break; } } if (!skip) { checkNextItem(currentItem); } } } } diff --git a/addons/snippets/editrepository.cpp b/addons/snippets/editrepository.cpp index 847ec54cc..1b4ccf459 100644 --- a/addons/snippets/editrepository.cpp +++ b/addons/snippets/editrepository.cpp @@ -1,139 +1,143 @@ /* This file is part of the Kate project. * Based on the snippet plugin from KDevelop 4. * * Copyright (C) 2007 Robert Gruber * Copyright (C) 2010 Milian Wolff * Copyright (C) 2012 Christoph Cullmann * Copyright (C) 2014 Sven Brauch * * This library 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 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 "editrepository.h" #include "snippetrepository.h" #include "snippetstore.h" #include #include #include #include #include #include #include EditRepository::EditRepository(SnippetRepository *repository, QWidget *parent) : QDialog(parent) , Ui::EditRepositoryBase() , m_repo(repository) { setupUi(this); connect(repoNameEdit, &KLineEdit::textEdited, this, &EditRepository::validate); connect(this, &QDialog::accepted, this, &EditRepository::save); auto ok = buttonBox->button(QDialogButtonBox::Ok); KGuiItem::assign(ok, KStandardGuiItem::ok()); connect(ok, &QPushButton::clicked, this, &EditRepository::accept); auto cancel = buttonBox->button(QDialogButtonBox::Cancel); KGuiItem::assign(cancel, KStandardGuiItem::cancel()); connect(cancel, &QPushButton::clicked, this, &EditRepository::reject); // fill list of available modes QSharedPointer document(KTextEditor::Editor::instance()->createDocument(nullptr)); repoFileTypesList->addItems(document->highlightingModes()); repoFileTypesList->sortItems(); repoFileTypesList->setSelectionMode(QAbstractItemView::ExtendedSelection); connect(repoFileTypesList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &EditRepository::updateFileTypes); // add default licenses repoLicenseEdit->addItems(QStringList() << QStringLiteral("BSD") << QStringLiteral("Artistic") << QStringLiteral("LGPL v2+") << QStringLiteral("LGPL v3+")); repoLicenseEdit->setEditable(true); // if we edit a repo, add all existing data if (m_repo) { repoNameEdit->setText(m_repo->text()); repoAuthorsEdit->setText(m_repo->authors()); repoNamespaceEdit->setText(m_repo->completionNamespace()); if (!m_repo->license().isEmpty()) { int index = repoLicenseEdit->findText(m_repo->license()); if (index == -1) { repoLicenseEdit->addItem(m_repo->license()); repoLicenseEdit->model()->sort(0); index = repoLicenseEdit->findText(m_repo->license()); } repoLicenseEdit->setCurrentIndex(index); } - foreach (const QString &type, m_repo->fileTypes()) { - foreach (QListWidgetItem *item, repoFileTypesList->findItems(type, Qt::MatchExactly)) { + const auto fileTypes = m_repo->fileTypes(); + for (const QString &type : fileTypes) { + const auto items = repoFileTypesList->findItems(type, Qt::MatchExactly); + for (QListWidgetItem *item : items) { item->setSelected(true); } } setWindowTitle(i18n("Edit Snippet Repository %1", m_repo->text())); } else { setWindowTitle(i18n("Create New Snippet Repository")); KUser user; repoAuthorsEdit->setText(user.property(KUser::FullName).toString()); } validate(); updateFileTypes(); repoNameEdit->setFocus(); } void EditRepository::validate() { bool valid = !repoNameEdit->text().isEmpty() && !repoNameEdit->text().contains(QLatin1Char('/')); buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid); } void EditRepository::save() { Q_ASSERT(!repoNameEdit->text().isEmpty()); if (!m_repo) { // save as new repo m_repo = SnippetRepository::createRepoFromName(repoNameEdit->text()); } m_repo->setText(repoNameEdit->text()); m_repo->setAuthors(repoAuthorsEdit->text()); m_repo->setLicense(repoLicenseEdit->currentText()); m_repo->setCompletionNamespace(repoNamespaceEdit->text()); QStringList types; - foreach (QListWidgetItem *item, repoFileTypesList->selectedItems()) { + const auto selectedItems = repoFileTypesList->selectedItems(); + for (QListWidgetItem *item : selectedItems) { types << item->text(); } m_repo->setFileTypes(types); m_repo->save(); setWindowTitle(i18n("Edit Snippet Repository %1", m_repo->text())); } void EditRepository::updateFileTypes() { QStringList types; - foreach (QListWidgetItem *item, repoFileTypesList->selectedItems()) { + const auto selectedItems = repoFileTypesList->selectedItems(); + for (QListWidgetItem *item : selectedItems) { types << item->text(); } if (types.isEmpty()) { repoFileTypesListLabel->setText(i18n("leave empty for general purpose snippets")); } else { repoFileTypesListLabel->setText(types.join(QLatin1String(", "))); } } diff --git a/addons/snippets/katesnippets.cpp b/addons/snippets/katesnippets.cpp index 1363b4ac3..330ad74ca 100644 --- a/addons/snippets/katesnippets.cpp +++ b/addons/snippets/katesnippets.cpp @@ -1,131 +1,132 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * This library 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 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 "katesnippets.h" #include "snippetcompletionmodel.h" #include "katesnippetglobal.h" #include "snippetview.h" #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KateSnippetsPluginFactory, "katesnippetsplugin.json", registerPlugin();) KateSnippetsPlugin::KateSnippetsPlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent) , m_snippetGlobal(new KateSnippetGlobal(this)) { } KateSnippetsPlugin::~KateSnippetsPlugin() { } QObject *KateSnippetsPlugin::createView(KTextEditor::MainWindow *mainWindow) { KateSnippetsPluginView *view = new KateSnippetsPluginView(this, mainWindow); return view; } KateSnippetsPluginView::KateSnippetsPluginView(KateSnippetsPlugin *plugin, KTextEditor::MainWindow *mainWindow) : QObject(mainWindow) , m_plugin(plugin) , m_mainWindow(mainWindow) , m_toolView(nullptr) , m_snippets(nullptr) { KXMLGUIClient::setComponentName(QStringLiteral("katesnippets"), i18n("Snippets tool view")); setXMLFile(QStringLiteral("ui.rc")); // Toolview for snippets m_toolView = mainWindow->createToolView(plugin, QStringLiteral("kate_private_plugin_katesnippetsplugin"), KTextEditor::MainWindow::Right, QIcon::fromTheme(QStringLiteral("document-new")), i18n("Snippets")); // add snippets widget m_snippets = new SnippetView(KateSnippetGlobal::self(), mainWindow, m_toolView.data()); m_toolView->layout()->addWidget(m_snippets); m_snippets->setupActionsForWindow(mainWindow->window()); m_toolView->addActions(m_snippets->actions()); // create actions QAction *a = actionCollection()->addAction(QStringLiteral("tools_create_snippet")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); a->setText(i18n("Create Snippet")); connect(a, &QAction::triggered, this, &KateSnippetsPluginView::createSnippet); connect(mainWindow, &KTextEditor::MainWindow::viewCreated, this, &KateSnippetsPluginView::slotViewCreated); /** * connect for all already existing views */ - foreach (KTextEditor::View *view, mainWindow->views()) { + const auto views = mainWindow->views(); + for (KTextEditor::View *view : views) { slotViewCreated(view); } // register if factory around if (auto factory = m_mainWindow->guiFactory()) { factory->addClient(this); } } KateSnippetsPluginView::~KateSnippetsPluginView() { // cleanup for all views - Q_FOREACH (auto view, m_textViews) { + for (auto view : qAsConst(m_textViews)) { if (!view) { continue; } auto iface = qobject_cast(view); iface->unregisterCompletionModel(KateSnippetGlobal::self()->completionModel()); } // unregister if factory around if (auto factory = m_mainWindow->guiFactory()) { factory->removeClient(this); } if (m_toolView) { delete m_toolView; } } void KateSnippetsPluginView::slotViewCreated(KTextEditor::View *view) { m_textViews.append(QPointer(view)); // add snippet completion auto model = KateSnippetGlobal::self()->completionModel(); auto iface = qobject_cast(view); iface->unregisterCompletionModel(model); iface->registerCompletionModel(model); } void KateSnippetsPluginView::createSnippet() { KateSnippetGlobal::self()->createSnippet(m_mainWindow->activeView()); } #include "katesnippets.moc" diff --git a/addons/snippets/snippetrepository.cpp b/addons/snippets/snippetrepository.cpp index 96180fe79..0774f6869 100644 --- a/addons/snippets/snippetrepository.cpp +++ b/addons/snippets/snippetrepository.cpp @@ -1,403 +1,404 @@ /* This file is part of the Kate project. * Based on the snippet plugin from KDevelop 4. * * Copyright (C) 2007 Robert Gruber * Copyright (C) 2010 Milian Wolff * Copyright (C) 2012 Christoph Cullmann * * This library 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 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 "snippetrepository.h" #include "snippet.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "snippetstore.h" static const QString defaultScript = QStringLiteral( "\ function fileName() { return document.fileName(); }\n\ function fileUrl() { return document.url(); }\n\ function encoding() { return document.encoding(); }\n\ function selection() { return view.selectedText(); }\n\ function year() { return new Date().getFullYear(); }\n\ function upper(x) { return x.toUpperCase(); }\n\ function lower(x) { return x.toLowerCase(); }\n"); SnippetRepository::SnippetRepository(const QString &file) : QStandardItem(i18n("")) , m_file(file) , m_script(defaultScript) { setIcon(QIcon::fromTheme(QStringLiteral("folder"))); const auto &config = SnippetStore::self()->getConfig(); bool activated = config.readEntry("enabledRepositories", QStringList()).contains(file); setCheckState(activated ? Qt::Checked : Qt::Unchecked); if (QFile::exists(file)) { // Tell the new repository to load it's snippets QTimer::singleShot(0, this, &SnippetRepository::slotParseFile); } qDebug() << "created new snippet repo" << file << this; } SnippetRepository::~SnippetRepository() { // remove all our children from both the model and our internal data structures removeRows(0, rowCount()); } QDir SnippetRepository::dataPath() { auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)); const auto &subdir = QLatin1String("ktexteditor_snippets/data/"); bool success = dir.mkpath(dir.absoluteFilePath(subdir)); Q_ASSERT(success); dir.setPath(dir.path() + QLatin1String("/") + subdir); return dir; } SnippetRepository *SnippetRepository::createRepoFromName(const QString &name) { QString cleanName = name; cleanName.replace(QLatin1Char('/'), QLatin1Char('-')); const auto &dir = dataPath(); const auto &path = dir.absoluteFilePath(cleanName + QLatin1String(".xml")); qDebug() << "repo path:" << path << cleanName; SnippetRepository *repo = new SnippetRepository(path); repo->setText(name); repo->setCheckState(Qt::Checked); KUser user; repo->setAuthors(user.property(KUser::FullName).toString()); SnippetStore::self()->appendRow(repo); return repo; } const QString &SnippetRepository::file() const { return m_file; } QString SnippetRepository::authors() const { return m_authors; } void SnippetRepository::setAuthors(const QString &authors) { m_authors = authors; } QStringList SnippetRepository::fileTypes() const { return m_filetypes; } void SnippetRepository::setFileTypes(const QStringList &filetypes) { if (filetypes.contains(QLatin1String("*"))) { m_filetypes.clear(); } else { m_filetypes = filetypes; } } QString SnippetRepository::license() const { return m_license; } void SnippetRepository::setLicense(const QString &license) { m_license = license; } QString SnippetRepository::completionNamespace() const { return m_namespace; } void SnippetRepository::setCompletionNamespace(const QString &completionNamespace) { m_namespace = completionNamespace; } QString SnippetRepository::script() const { return m_script; } void SnippetRepository::setScript(const QString &script) { m_script = script; } void SnippetRepository::remove() { QFile::remove(m_file); setCheckState(Qt::Unchecked); model()->invisibleRootItem()->removeRow(row()); } /// copied code from snippets_tng/lib/completionmodel.cpp ///@copyright 2009 Joseph Wenninger static void addAndCreateElement(QDomDocument &doc, QDomElement &item, const QString &name, const QString &content) { QDomElement element = doc.createElement(name); element.appendChild(doc.createTextNode(content)); item.appendChild(element); } void SnippetRepository::save() { qDebug() << "*** called"; /// based on the code from snippets_tng/lib/completionmodel.cpp ///@copyright 2009 Joseph Wenninger /* prefix test1 postfix (param1, param2) This is a test testtemplate This is a test ${WHAT} template */ QDomDocument doc; QDomElement root = doc.createElement(QStringLiteral("snippets")); root.setAttribute(QStringLiteral("name"), text()); root.setAttribute(QStringLiteral("filetypes"), m_filetypes.isEmpty() ? QStringLiteral("*") : m_filetypes.join(QLatin1Char(';'))); root.setAttribute(QStringLiteral("authors"), m_authors); root.setAttribute(QStringLiteral("license"), m_license); root.setAttribute(QStringLiteral("namespace"), m_namespace); doc.appendChild(root); addAndCreateElement(doc, root, QStringLiteral("script"), m_script); for (int i = 0; i < rowCount(); ++i) { Snippet *snippet = dynamic_cast(child(i)); if (!snippet) { continue; } QDomElement item = doc.createElement(QStringLiteral("item")); addAndCreateElement(doc, item, QStringLiteral("match"), snippet->text()); addAndCreateElement(doc, item, QStringLiteral("fillin"), snippet->snippet()); root.appendChild(item); } // KMessageBox::information(0,doc.toString()); QFileInfo fi(m_file); QDir dir = dataPath(); QString outname = dir.absoluteFilePath(fi.fileName()); if (m_file != outname) { QFileInfo fiout(outname); // there could be cases that new new name clashes with a global file, but I guess it is not that often. int i = 0; while (QFile::exists(outname)) { i++; outname = dir.absoluteFilePath(QString::number(i) + fi.fileName()); } KMessageBox::information(QApplication::activeWindow(), i18n("You have edited a data file not located in your personal data directory; as such, a renamed clone of the original data file has been created within your personal data directory.")); } QFile outfile(outname); if (!outfile.open(QIODevice::WriteOnly)) { KMessageBox::error(nullptr, i18n("Output file '%1' could not be opened for writing", outname)); return; } outfile.write(doc.toByteArray()); outfile.close(); m_file = outname; // save shortcuts KConfigGroup config = SnippetStore::self()->getConfig().group(QLatin1String("repository ") + m_file); for (int i = 0; i < rowCount(); ++i) { Snippet *snippet = dynamic_cast(child(i)); if (!snippet) { continue; } QStringList shortcuts; - foreach (const QKeySequence &keys, snippet->action()->shortcuts()) { + const auto shortcutList = snippet->action()->shortcuts(); + for (const QKeySequence &keys : shortcutList) { shortcuts << keys.toString(); } config.writeEntry(QLatin1String("shortcut ") + snippet->text(), shortcuts); } config.sync(); } void SnippetRepository::slotParseFile() { /// based on the code from snippets_tng/lib/completionmodel.cpp ///@copyright 2009 Joseph Wenninger QFile f(m_file); if (!f.open(QIODevice::ReadOnly)) { KMessageBox::error(QApplication::activeWindow(), i18n("Cannot open snippet repository %1.", m_file)); return; } QDomDocument doc; QString errorMsg; int line, col; bool success = doc.setContent(&f, &errorMsg, &line, &col); f.close(); if (!success) { KMessageBox::error(QApplication::activeWindow(), i18n("The error %4
has been detected in the file %1 at %2/%3
", m_file, line, col, i18nc("QXml", errorMsg.toUtf8().data()))); return; } // parse root item const QDomElement &docElement = doc.documentElement(); if (docElement.tagName() != QLatin1String("snippets")) { KMessageBox::error(QApplication::activeWindow(), i18n("Invalid XML snippet file: %1", m_file)); return; } setLicense(docElement.attribute(QStringLiteral("license"))); setAuthors(docElement.attribute(QStringLiteral("authors"))); setFileTypes(docElement.attribute(QStringLiteral("filetypes")).split(QLatin1Char(';'), QString::SkipEmptyParts)); setText(docElement.attribute(QStringLiteral("name"))); setCompletionNamespace(docElement.attribute(QStringLiteral("namespace"))); // load shortcuts KConfigGroup config = SnippetStore::self()->getConfig().group(QLatin1String("repository ") + m_file); // parse children, i.e. 's const QDomNodeList &nodes = docElement.childNodes(); for (int i = 0; i < nodes.size(); ++i) { const QDomNode &node = nodes.at(i); if (!node.isElement()) { continue; } const QDomElement &item = node.toElement(); if (item.tagName() == QLatin1String("script")) { setScript(item.text()); } if (item.tagName() != QLatin1String("item")) { continue; } Snippet *snippet = new Snippet; const QDomNodeList &children = node.childNodes(); for (int j = 0; j < children.size(); ++j) { const QDomNode &childNode = children.at(j); if (!childNode.isElement()) { continue; } const QDomElement &child = childNode.toElement(); if (child.tagName() == QLatin1String("match")) { snippet->setText(child.text()); } else if (child.tagName() == QLatin1String("fillin")) { snippet->setSnippet(child.text()); } } // require at least a non-empty name and snippet if (snippet->text().isEmpty() || snippet->snippet().isEmpty()) { delete snippet; continue; } else { const QStringList shortcuts = config.readEntry(QLatin1String("shortcut ") + snippet->text(), QStringList()); QList sequences; for (const QString &shortcut : shortcuts) { sequences << QKeySequence::fromString(shortcut); } snippet->action()->setShortcuts(sequences); appendRow(snippet); } } } QVariant SnippetRepository::data(int role) const { if (role == Qt::ToolTipRole) { if (checkState() != Qt::Checked) { return i18n("Repository is disabled, the contained snippets will not be shown during code-completion."); } if (m_filetypes.isEmpty()) { return i18n("Applies to all filetypes"); } else { return i18n("Applies to the following filetypes: %1", m_filetypes.join(QLatin1String(", "))); } } else if (role == Qt::ForegroundRole && checkState() != Qt::Checked) { /// TODO: make the selected items also "disalbed" so the toggle action is seen directly KColorScheme scheme(QPalette::Disabled, KColorScheme::View); QColor c = scheme.foreground(KColorScheme::NormalText).color(); return QVariant(c); } return QStandardItem::data(role); } void SnippetRepository::setData(const QVariant &value, int role) { if (role == Qt::CheckStateRole) { const int state = value.toInt(); if (state != checkState()) { KConfigGroup config = SnippetStore::self()->getConfig(); QStringList currentlyEnabled = config.readEntry("enabledRepositories", QStringList()); bool shouldSave = false; if (state == Qt::Checked && !currentlyEnabled.contains(m_file)) { currentlyEnabled << m_file; shouldSave = true; } else if (state == Qt::Unchecked && currentlyEnabled.contains(m_file)) { currentlyEnabled.removeAll(m_file); shouldSave = true; } if (shouldSave) { config.writeEntry("enabledRepositories", currentlyEnabled); config.sync(); } } } QStandardItem::setData(value, role); } diff --git a/addons/snippets/snippetview.cpp b/addons/snippets/snippetview.cpp index 482bd350f..73c499a11 100644 --- a/addons/snippets/snippetview.cpp +++ b/addons/snippets/snippetview.cpp @@ -1,374 +1,377 @@ /* This file is part of the Kate project. * Based on the snippet plugin from KDevelop 4. * * Copyright (C) 2007 Robert Gruber * Copyright (C) 2010 Milian Wolff * Copyright (C) 2012 Christoph Cullmann * * This library 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 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 "snippetview.h" #include "snippet.h" #include "katesnippetglobal.h" #include "snippetrepository.h" #include "snippetstore.h" #include "editrepository.h" #include "editsnippet.h" #include #include #include #include #include #include #include #include #include class SnippetFilterModel : public QSortFilterProxyModel { public: SnippetFilterModel(QObject *parent = nullptr) : QSortFilterProxyModel(parent) {}; bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override { auto index = sourceModel()->index(sourceRow, 0, sourceParent); auto item = SnippetStore::self()->itemFromIndex(index); if (!item) { return false; } auto snippet = dynamic_cast(item); if (!snippet) { return true; } return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); } }; void SnippetView::setupActionsForWindow(QWidget *widget) { const auto &model = SnippetStore::self(); for (int i = 0; i < model->rowCount(); i++) { auto index = model->index(i, 0, QModelIndex()); auto item = model->itemFromIndex(index); auto repo = dynamic_cast(item); if (!repo) { continue; } for (int j = 0; j < model->rowCount(index); j++) { auto item = model->itemFromIndex(model->index(j, 0, index)); auto snippet = dynamic_cast(item); if (!snippet) { continue; } snippet->registerActionForView(widget); } } } SnippetView::SnippetView(KateSnippetGlobal *plugin, KTextEditor::MainWindow *mainWindow, QWidget *parent) : QWidget(parent) , Ui::SnippetViewBase() , m_plugin(plugin) { Ui::SnippetViewBase::setupUi(this); setWindowTitle(i18n("Snippets")); setWindowIcon(QIcon::fromTheme(QStringLiteral("document-new"), windowIcon())); snippetTree->setContextMenuPolicy(Qt::CustomContextMenu); snippetTree->viewport()->installEventFilter(this); connect(snippetTree, &QTreeView::customContextMenuRequested, this, &SnippetView::contextMenu); m_proxy = new SnippetFilterModel(this); m_proxy->setFilterKeyColumn(0); m_proxy->setSourceModel(SnippetStore::self()); connect(filterText, &KLineEdit::textChanged, m_proxy, &QSortFilterProxyModel::setFilterFixedString); snippetTree->setModel(m_proxy); snippetTree->header()->hide(); m_addRepoAction = new QAction(QIcon::fromTheme(QStringLiteral("folder-new")), i18n("Add Repository"), this); connect(m_addRepoAction, &QAction::triggered, this, &SnippetView::slotAddRepo); addAction(m_addRepoAction); m_editRepoAction = new QAction(QIcon::fromTheme(QStringLiteral("folder-txt")), i18n("Edit Repository"), this); connect(m_editRepoAction, &QAction::triggered, this, &SnippetView::slotEditRepo); addAction(m_editRepoAction); m_removeRepoAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Remove Repository"), this); connect(m_removeRepoAction, &QAction::triggered, this, &SnippetView::slotRemoveRepo); addAction(m_removeRepoAction); const bool newStuffAllowed = KAuthorized::authorize(QStringLiteral("ghns")); m_putNewStuffAction = new QAction(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff")), i18n("Publish Repository"), this); m_putNewStuffAction->setVisible(newStuffAllowed); connect(m_putNewStuffAction, &QAction::triggered, this, &SnippetView::slotSnippetToGHNS); addAction(m_putNewStuffAction); QAction *separator = new QAction(this); separator->setSeparator(true); addAction(separator); m_addSnippetAction = new QAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Add Snippet"), this); connect(m_addSnippetAction, &QAction::triggered, this, &SnippetView::slotAddSnippet); addAction(m_addSnippetAction); m_editSnippetAction = new QAction(QIcon::fromTheme(QStringLiteral("document-edit")), i18n("Edit Snippet"), this); connect(m_editSnippetAction, &QAction::triggered, this, &SnippetView::slotEditSnippet); addAction(m_editSnippetAction); m_removeSnippetAction = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n("Remove Snippet"), this); connect(m_removeSnippetAction, &QAction::triggered, this, &SnippetView::slotRemoveSnippet); addAction(m_removeSnippetAction); addAction(separator); m_getNewStuffAction = new QAction(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff")), i18n("Get New Snippets"), this); m_getNewStuffAction->setVisible(newStuffAllowed); connect(m_getNewStuffAction, &QAction::triggered, this, &SnippetView::slotGHNS); addAction(m_getNewStuffAction); connect(snippetTree->selectionModel(), &QItemSelectionModel::selectionChanged, this, &SnippetView::validateActions); validateActions(); connect(snippetTree->model(), &QAbstractItemModel::rowsInserted, this, [this, mainWindow]() { setupActionsForWindow(mainWindow->window()); }); m_proxy->setDynamicSortFilter(true); m_proxy->sort(0, Qt::AscendingOrder); } void SnippetView::validateActions() { QStandardItem *item = currentItem(); Snippet *selectedSnippet = dynamic_cast(item); SnippetRepository *selectedRepo = dynamic_cast(item); m_addRepoAction->setEnabled(true); m_editRepoAction->setEnabled(selectedRepo); m_removeRepoAction->setEnabled(selectedRepo); m_putNewStuffAction->setEnabled(selectedRepo); m_addSnippetAction->setEnabled(selectedRepo || selectedSnippet); m_editSnippetAction->setEnabled(selectedSnippet); m_removeSnippetAction->setEnabled(selectedSnippet); } QStandardItem *SnippetView::currentItem() { /// TODO: support multiple selected items QModelIndex index = snippetTree->currentIndex(); index = m_proxy->mapToSource(index); return SnippetStore::self()->itemFromIndex(index); } void SnippetView::slotSnippetClicked(const QModelIndex &index) { QStandardItem *item = SnippetStore::self()->itemFromIndex(m_proxy->mapToSource(index)); if (!item) return; Snippet *snippet = dynamic_cast(item); if (!snippet) return; m_plugin->insertSnippet(snippet); } void SnippetView::contextMenu(const QPoint &pos) { QModelIndex index = snippetTree->indexAt(pos); index = m_proxy->mapToSource(index); QStandardItem *item = SnippetStore::self()->itemFromIndex(index); if (!item) { // User clicked into an empty place of the tree QMenu menu(this); menu.addSection(i18n("Snippets")); menu.addAction(m_addRepoAction); menu.addAction(m_getNewStuffAction); menu.exec(snippetTree->mapToGlobal(pos)); } else if (Snippet *snippet = dynamic_cast(item)) { QMenu menu(this); menu.addSection(i18n("Snippet: %1", snippet->text())); menu.addAction(m_editSnippetAction); menu.addAction(m_removeSnippetAction); menu.exec(snippetTree->mapToGlobal(pos)); } else if (SnippetRepository *repo = dynamic_cast(item)) { QMenu menu(this); menu.addSection(i18n("Repository: %1", repo->text())); menu.addAction(m_addSnippetAction); menu.addSeparator(); menu.addAction(m_editRepoAction); menu.addAction(m_removeRepoAction); menu.addAction(m_putNewStuffAction); menu.exec(snippetTree->mapToGlobal(pos)); } } void SnippetView::slotEditSnippet() { QStandardItem *item = currentItem(); if (!item) return; Snippet *snippet = dynamic_cast(item); if (!snippet) return; SnippetRepository *repo = dynamic_cast(item->parent()); if (!repo) return; EditSnippet dlg(repo, snippet, this); dlg.exec(); } void SnippetView::slotAddSnippet() { QStandardItem *item = currentItem(); if (!item) return; SnippetRepository *repo = dynamic_cast(item); if (!repo) { repo = dynamic_cast(item->parent()); if (!repo) return; } EditSnippet dlg(repo, nullptr, this); dlg.exec(); } void SnippetView::slotRemoveSnippet() { QStandardItem *item = currentItem(); if (!item) return; SnippetRepository *repo = dynamic_cast(item->parent()); if (!repo) return; int ans = KMessageBox::warningContinueCancel(QApplication::activeWindow(), i18n("Do you really want to delete the snippet \"%1\"?", item->text())); if (ans == KMessageBox::Continue) { item->parent()->removeRow(item->row()); repo->save(); } } void SnippetView::slotAddRepo() { EditRepository dlg(nullptr, this); dlg.exec(); } void SnippetView::slotEditRepo() { QStandardItem *item = currentItem(); if (!item) return; SnippetRepository *repo = dynamic_cast(item); if (!repo) return; EditRepository dlg(repo, this); dlg.exec(); } void SnippetView::slotRemoveRepo() { QStandardItem *item = currentItem(); if (!item) return; SnippetRepository *repo = dynamic_cast(item); if (!repo) return; int ans = KMessageBox::warningContinueCancel(QApplication::activeWindow(), i18n("Do you really want to delete the repository \"%1\" with all its snippets?", repo->text())); if (ans == KMessageBox::Continue) { repo->remove(); } } void SnippetView::slotGHNS() { KNS3::DownloadDialog dialog(QStringLiteral(":/katesnippets/ktexteditor_codesnippets_core.knsrc"), this); dialog.exec(); - foreach (const KNS3::Entry &entry, dialog.changedEntries()) { - foreach (const QString &path, entry.uninstalledFiles()) { + const auto changedEntries = dialog.changedEntries(); + for (const KNS3::Entry &entry : changedEntries) { + const auto uninstalledFiles = entry.uninstalledFiles(); + for (const QString &path : uninstalledFiles) { if (path.endsWith(QLatin1String(".xml"))) { if (SnippetRepository *repo = SnippetStore::self()->repositoryForFile(path)) { repo->remove(); } } } - foreach (const QString &path, entry.installedFiles()) { + const auto installedFiles = entry.installedFiles(); + for (const QString &path : installedFiles) { if (path.endsWith(QLatin1String(".xml"))) { SnippetStore::self()->appendRow(new SnippetRepository(path)); } } } } void SnippetView::slotSnippetToGHNS() { QStandardItem *item = currentItem(); if (!item) return; SnippetRepository *repo = dynamic_cast(item); if (!repo) return; KNS3::UploadDialog dialog(QStringLiteral(":/katesnippets/ktexteditor_codesnippets_core.knsrc"), this); dialog.setUploadFile(QUrl::fromLocalFile(repo->file())); dialog.setUploadName(repo->text()); dialog.exec(); } bool SnippetView::eventFilter(QObject *obj, QEvent *e) { // no, listening to activated() is not enough since that would also trigger the edit mode which we _dont_ want here // users may still rename stuff via select + F2 though if (obj == snippetTree->viewport()) { const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this); if ((!singleClick && e->type() == QEvent::MouseButtonDblClick) || (singleClick && e->type() == QEvent::MouseButtonRelease)) { QMouseEvent *mouseEvent = dynamic_cast(e); Q_ASSERT(mouseEvent); QModelIndex clickedIndex = snippetTree->indexAt(mouseEvent->pos()); if (clickedIndex.isValid() && clickedIndex.parent().isValid()) { slotSnippetClicked(clickedIndex); e->accept(); return true; } } } return QObject::eventFilter(obj, e); } diff --git a/addons/xmltools/plugin_katexmltools.cpp b/addons/xmltools/plugin_katexmltools.cpp index c6e8e3bbf..e6f7a4d41 100644 --- a/addons/xmltools/plugin_katexmltools.cpp +++ b/addons/xmltools/plugin_katexmltools.cpp @@ -1,1083 +1,1083 @@ /*************************************************************************** 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(QStringLiteral("katexmltools"), i18n("Kate XML Tools")); setXMLFile(QStringLiteral("ui.rc")); QAction *actionInsert = new QAction(i18n("&Insert Element..."), this); connect(actionInsert, &QAction::triggered, &m_model, &PluginKateXMLToolsCompletionModel::slotInsertElement); actionCollection()->addAction(QStringLiteral("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(QStringLiteral("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(QStringLiteral("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 destructor 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 == QLatin1String("&")) { qDebug() << "Getting entities"; m_allowed = m_docDtds[doc]->entities(QString()); m_mode = entities; } else if (leftCh == QLatin1String("<")) { 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 == QLatin1String("/") && secondLeftCh == QLatin1String("<")) { qDebug() << "*close parent element"; QString parentElement = getParentElement(*kv, 2); if (!parentElement.isEmpty()) { m_mode = closingtag; m_allowed = QStringList(parentElement); } } else if (leftCh == QLatin1Char(' ') || (isQuote(leftCh) && secondLeftCh == QLatin1String("="))) { // 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] == QLatin1String("CDATA") || m_allowed[0] == QLatin1String("ID") || m_allowed[0] == QLatin1String("IDREF") || m_allowed[0] == QLatin1String("IDREFS") || m_allowed[0] == QLatin1String("ENTITY") || m_allowed[0] == QLatin1String("ENTITIES") || m_allowed[0] == QLatin1String("NMTOKEN") || m_allowed[0] == QLatin1String("NMTOKENS") || m_allowed[0] == QLatin1String("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] != QLatin1String("__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 = QStringLiteral("&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, QStringLiteral("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 = QStringLiteral("xslt-1.0.dtd.xml"); doctype = QStringLiteral("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), QStringLiteral("*.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"), QStringLiteral("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::TransferJob *job = KIO::get(url); connect(job, &KIO::TransferJob::result, this, &PluginKateXMLToolsCompletionModel::slotFinished); connect(job, &KIO::TransferJob::data, this, &PluginKateXMLToolsCompletionModel::slotData); } 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()? + // TODO:perhaps for all 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(QLatin1String("__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 != QLatin1String(">") && rightCh != QLatin1String("/") && rightCh != QLatin1String(" ")) { // 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(QLatin1String("__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 == QLatin1String(">")) { // cursor is outside tag return QString(); } if (ch == QLatin1String("<")) { 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 == QLatin1String("/") || ch == QLatin1String(">")) { 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 == QLatin1String("=")) { break; } else if (isQuote(ch) && chLeft != QLatin1String("=")) { return QString(); } else if (ch == QLatin1String("<") || ch == QLatin1String(">")) { 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(QLatin1String("")); } /** * 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 == QLatin1String("\"") || ch == QLatin1String("'")); } // ======================================================================== // 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 (const auto &str : qAsConst(list)) { 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/kateviewmanager.cpp b/kate/kateviewmanager.cpp index d23c5ac96..3130a5a77 100644 --- a/kate/kateviewmanager.cpp +++ b/kate/kateviewmanager.cpp @@ -1,1236 +1,1237 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund 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. */ // BEGIN Includes #include "kateviewmanager.h" #include "config.h" #include "kateapp.h" #include "katemainwindow.h" #include "kateviewspace.h" #include "kateupdatedisabler.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef KF5Activities_FOUND #include #endif #include #include // END Includes static const qint64 FileSizeAboveToAskUserIfProceedWithOpen = 10 * 1024 * 1024; // 10MB should suffice KateViewManager::KateViewManager(QWidget *parentW, KateMainWindow *parent) : QSplitter(parentW) , m_mainWindow(parent) , m_blockViewCreationAndActivation(false) , m_activeViewRunning(false) , m_minAge(0) , m_guiMergedView(nullptr) { // while init m_init = true; // we don't allow full collapse, see bug 366014 setChildrenCollapsible(false); // important, set them up, as we use them in other methodes setupActions(); KateViewSpace *vs = new KateViewSpace(this, nullptr); addWidget(vs); vs->setActive(true); m_viewSpaceList.append(vs); connect(this, &KateViewManager::viewChanged, this, &KateViewManager::slotViewChanged); connect(KateApp::self()->documentManager(), &KateDocManager::documentCreatedViewManager, this, &KateViewManager::documentCreated); /** * before document is really deleted: cleanup all views! */ connect(KateApp::self()->documentManager(), &KateDocManager::documentWillBeDeleted, this, &KateViewManager::documentWillBeDeleted); /** * handle document deletion transactions * disable view creation in between * afterwards ensure we have views ;) */ connect(KateApp::self()->documentManager(), &KateDocManager::aboutToDeleteDocuments, this, &KateViewManager::aboutToDeleteDocuments); connect(KateApp::self()->documentManager(), &KateDocManager::documentsDeleted, this, &KateViewManager::documentsDeleted); // register all already existing documents m_blockViewCreationAndActivation = true; const QList &docs = KateApp::self()->documentManager()->documentList(); for (KTextEditor::Document *doc : docs) { documentCreated(doc); } m_blockViewCreationAndActivation = false; // init done m_init = false; } KateViewManager::~KateViewManager() { /** * remove the single client that is registered at the factory, if any */ if (m_guiMergedView) { mainWindow()->guiFactory()->removeClient(m_guiMergedView); m_guiMergedView = nullptr; } } void KateViewManager::setupActions() { /** * view splitting */ m_splitViewVert = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_vert")); m_splitViewVert->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right"))); m_splitViewVert->setText(i18n("Split Ve&rtical")); m_mainWindow->actionCollection()->setDefaultShortcut(m_splitViewVert, Qt::CTRL + Qt::SHIFT + Qt::Key_L); connect(m_splitViewVert, &QAction::triggered, this, &KateViewManager::slotSplitViewSpaceVert); m_splitViewVert->setWhatsThis(i18n("Split the currently active view vertically into two views.")); m_splitViewHoriz = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_horiz")); m_splitViewHoriz->setIcon(QIcon::fromTheme(QStringLiteral("view-split-top-bottom"))); m_splitViewHoriz->setText(i18n("Split &Horizontal")); m_mainWindow->actionCollection()->setDefaultShortcut(m_splitViewHoriz, Qt::CTRL + Qt::SHIFT + Qt::Key_T); connect(m_splitViewHoriz, &QAction::triggered, this, &KateViewManager::slotSplitViewSpaceHoriz); m_splitViewHoriz->setWhatsThis(i18n("Split the currently active view horizontally into two views.")); m_closeView = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_close_current_space")); m_closeView->setIcon(QIcon::fromTheme(QStringLiteral("view-close"))); m_closeView->setText(i18n("Cl&ose Current View")); m_mainWindow->actionCollection()->setDefaultShortcut(m_closeView, Qt::CTRL + Qt::SHIFT + Qt::Key_R); connect(m_closeView, &QAction::triggered, this, &KateViewManager::slotCloseCurrentViewSpace, Qt::QueuedConnection); m_closeView->setWhatsThis(i18n("Close the currently active split view")); m_closeOtherViews = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_close_others")); m_closeOtherViews->setIcon(QIcon::fromTheme(QStringLiteral("view-close"))); m_closeOtherViews->setText(i18n("Close Inactive Views")); connect(m_closeOtherViews, &QAction::triggered, this, &KateViewManager::slotCloseOtherViews, Qt::QueuedConnection); m_closeOtherViews->setWhatsThis(i18n("Close every view but the active one")); m_hideOtherViews = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_hide_others")); m_hideOtherViews->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen"))); m_hideOtherViews->setText(i18n("Hide Inactive Views")); m_hideOtherViews->setCheckable(true); connect(m_hideOtherViews, &QAction::triggered, this, &KateViewManager::slotHideOtherViews, Qt::QueuedConnection); m_hideOtherViews->setWhatsThis(i18n("Hide every view but the active one")); m_toggleSplitterOrientation = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_toggle")); m_toggleSplitterOrientation->setText(i18n("Toggle Orientation")); connect(m_toggleSplitterOrientation, &QAction::triggered, this, &KateViewManager::toggleSplitterOrientation, Qt::QueuedConnection); m_toggleSplitterOrientation->setWhatsThis(i18n("Toggles the orientation of the current split view")); goNext = m_mainWindow->actionCollection()->addAction(QStringLiteral("go_next_split_view")); goNext->setText(i18n("Next Split View")); m_mainWindow->actionCollection()->setDefaultShortcut(goNext, Qt::Key_F8); connect(goNext, &QAction::triggered, this, &KateViewManager::activateNextView); goNext->setWhatsThis(i18n("Make the next split view the active one.")); goPrev = m_mainWindow->actionCollection()->addAction(QStringLiteral("go_prev_split_view")); goPrev->setText(i18n("Previous Split View")); m_mainWindow->actionCollection()->setDefaultShortcut(goPrev, Qt::SHIFT + Qt::Key_F8); connect(goPrev, &QAction::triggered, this, &KateViewManager::activatePrevView); goPrev->setWhatsThis(i18n("Make the previous split view the active one.")); QAction *a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_right")); a->setText(i18n("Move Splitter Right")); connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterRight); a->setWhatsThis(i18n("Move the splitter of the current view to the right")); a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_left")); a->setText(i18n("Move Splitter Left")); connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterLeft); a->setWhatsThis(i18n("Move the splitter of the current view to the left")); a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_up")); a->setText(i18n("Move Splitter Up")); connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterUp); a->setWhatsThis(i18n("Move the splitter of the current view up")); a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_down")); a->setText(i18n("Move Splitter Down")); connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterDown); a->setWhatsThis(i18n("Move the splitter of the current view down")); } void KateViewManager::updateViewSpaceActions() { m_closeView->setEnabled(m_viewSpaceList.count() > 1); m_closeOtherViews->setEnabled(m_viewSpaceList.count() > 1); m_toggleSplitterOrientation->setEnabled(m_viewSpaceList.count() > 1); goNext->setEnabled(m_viewSpaceList.count() > 1); goPrev->setEnabled(m_viewSpaceList.count() > 1); } void KateViewManager::slotDocumentNew() { createView(); } void KateViewManager::slotDocumentOpen() { // try to start dialog in useful dir: either dir of current doc or last used one KTextEditor::View *const cv = activeView(); QUrl startUrl = cv ? cv->document()->url() : QUrl(); if (startUrl.isValid()) { m_lastOpenDialogUrl = startUrl; } else { startUrl = m_lastOpenDialogUrl; } const QList urls = QFileDialog::getOpenFileUrls(m_mainWindow, i18n("Open File"), startUrl); /** * emit size warning, for local files */ QString fileListWithTooLargeFiles; for (const QUrl &url : urls) { if (!url.isLocalFile()) { continue; } const auto size = QFile(url.toLocalFile()).size(); if (size > FileSizeAboveToAskUserIfProceedWithOpen) { fileListWithTooLargeFiles += QStringLiteral("

  • %1 (%2MB)
  • ").arg(url.fileName()).arg(size / 1024 / 1024); } } if (!fileListWithTooLargeFiles.isEmpty()) { const QString text = i18n("

    You are attempting to open one or more large files:

      %1

    Do you want to proceed?

    Beware that kate may stop responding for some time when opening large files.

    ", fileListWithTooLargeFiles); const auto ret = KMessageBox::warningYesNo(this, text, i18n("Opening Large File"), KStandardGuiItem::cont(), KStandardGuiItem::stop()); if (ret == KMessageBox::No) { return; } } // activate view of last opened document KateDocumentInfo docInfo; docInfo.openedByUser = true; if (KTextEditor::Document *lastID = openUrls(urls, QString(), false, docInfo)) { activateView(lastID); } } void KateViewManager::slotDocumentClose(KTextEditor::Document *document) { bool shutdownKate = m_mainWindow->modCloseAfterLast() && KateApp::self()->documentManager()->documentList().size() == 1; // close document if (KateApp::self()->documentManager()->closeDocument(document) && shutdownKate) { KateApp::self()->shutdownKate(m_mainWindow); } } void KateViewManager::slotDocumentClose() { // no active view, do nothing if (!activeView()) { return; } slotDocumentClose(activeView()->document()); } KTextEditor::Document *KateViewManager::openUrl(const QUrl &url, const QString &encoding, bool activate, bool isTempFile, const KateDocumentInfo &docInfo) { KTextEditor::Document *doc = KateApp::self()->documentManager()->openUrl(url, encoding, isTempFile, docInfo); if (!doc->url().isEmpty()) { m_mainWindow->fileOpenRecent()->addUrl(doc->url()); } if (activate) { activateView(doc); } return doc; } KTextEditor::Document *KateViewManager::openUrls(const QList &urls, const QString &encoding, bool isTempFile, const KateDocumentInfo &docInfo) { const QList docs = KateApp::self()->documentManager()->openUrls(urls, encoding, isTempFile, docInfo); for (const KTextEditor::Document *doc : docs) { if (!doc->url().isEmpty()) { m_mainWindow->fileOpenRecent()->addUrl(doc->url()); } } return docs.isEmpty() ? nullptr : docs.last(); } KTextEditor::View *KateViewManager::openUrlWithView(const QUrl &url, const QString &encoding) { KTextEditor::Document *doc = KateApp::self()->documentManager()->openUrl(url, encoding); if (!doc) { return nullptr; } if (!doc->url().isEmpty()) { m_mainWindow->fileOpenRecent()->addUrl(doc->url()); } activateView(doc); return activeView(); } void KateViewManager::openUrl(const QUrl &url) { openUrl(url, QString()); } KateMainWindow *KateViewManager::mainWindow() { return m_mainWindow; } void KateViewManager::documentCreated(KTextEditor::Document *doc) { // forward to currently active view space activeViewSpace()->registerDocument(doc); // to update open recent files on saving connect(doc, &KTextEditor::Document::documentSavedOrUploaded, this, &KateViewManager::documentSavedOrUploaded); if (m_blockViewCreationAndActivation) { return; } if (!activeView()) { activateView(doc); } /** * check if we have any empty viewspaces and give them a view */ for (KateViewSpace *vs : qAsConst(m_viewSpaceList)) { if (!vs->currentView()) { createView(activeView()->document(), vs); } } } void KateViewManager::aboutToDeleteDocuments(const QList &) { /** * block view creation until the transaction is done * this shall not stack! */ Q_ASSERT(!m_blockViewCreationAndActivation); m_blockViewCreationAndActivation = true; /** * disable updates hard (we can't use KateUpdateDisabler here, we have delayed signal */ mainWindow()->setUpdatesEnabled(false); } void KateViewManager::documentsDeleted(const QList &) { /** * again allow view creation */ m_blockViewCreationAndActivation = false; /** * try to have active view around! */ if (!activeView() && !KateApp::self()->documentManager()->documentList().isEmpty()) { createView(KateApp::self()->documentManager()->documentList().last()); } /** * if we have one now, show them in all viewspaces that got empty! */ if (KTextEditor::View *const newActiveView = activeView()) { /** * check if we have any empty viewspaces and give them a view */ for (KateViewSpace *vs : qAsConst(m_viewSpaceList)) { if (!vs->currentView()) { createView(newActiveView->document(), vs); } } emit viewChanged(newActiveView); } /** * enable updates hard (we can't use KateUpdateDisabler here, we have delayed signal */ mainWindow()->setUpdatesEnabled(true); } void KateViewManager::documentSavedOrUploaded(KTextEditor::Document *doc, bool) { if (!doc->url().isEmpty()) { m_mainWindow->fileOpenRecent()->addUrl(doc->url()); } } KTextEditor::View *KateViewManager::createView(KTextEditor::Document *doc, KateViewSpace *vs) { if (m_blockViewCreationAndActivation) { return nullptr; } // create doc if (!doc) { doc = KateApp::self()->documentManager()->createDoc(); } /** * create view, registers its XML gui itself * pass the view the correct main window */ KTextEditor::View *view = (vs ? vs : activeViewSpace())->createView(doc); /** * remember this view, active == false, min age set * create activity resource */ m_views[view].active = false; m_views[view].lruAge = m_minAge--; #ifdef KF5Activities_FOUND m_views[view].activityResource = new KActivities::ResourceInstance(view->window()->winId(), view); m_views[view].activityResource->setUri(doc->url()); #endif // disable settings dialog action delete view->actionCollection()->action(QStringLiteral("set_confdlg")); delete view->actionCollection()->action(QStringLiteral("editor_options")); connect(view, SIGNAL(dropEventPass(QDropEvent *)), mainWindow(), SLOT(slotDropEvent(QDropEvent *))); connect(view, &KTextEditor::View::focusIn, this, &KateViewManager::activateSpace); viewCreated(view); if (!vs) { activateView(view); } return view; } bool KateViewManager::deleteView(KTextEditor::View *view) { if (!view) { return true; } KateViewSpace *viewspace = static_cast(view->parentWidget()->parentWidget()); viewspace->removeView(view); /** * deregister if needed */ if (m_guiMergedView == view) { mainWindow()->guiFactory()->removeClient(m_guiMergedView); m_guiMergedView = nullptr; } // remove view from mapping and memory !! m_views.remove(view); delete view; return true; } KateViewSpace *KateViewManager::activeViewSpace() { for (QList::const_iterator it = m_viewSpaceList.constBegin(); it != m_viewSpaceList.constEnd(); ++it) { if ((*it)->isActiveSpace()) { return *it; } } // none active, so use the first we grab if (!m_viewSpaceList.isEmpty()) { m_viewSpaceList.first()->setActive(true); return m_viewSpaceList.first(); } Q_ASSERT(false); return nullptr; } KTextEditor::View *KateViewManager::activeView() { if (m_activeViewRunning) { return nullptr; } m_activeViewRunning = true; QHashIterator it(m_views); while (it.hasNext()) { it.next(); if (it.value().active) { m_activeViewRunning = false; return it.key(); } } // if we get to here, no view isActive() // first, try to get one from activeViewSpace() KateViewSpace *vs = activeViewSpace(); if (vs && vs->currentView()) { activateView(vs->currentView()); m_activeViewRunning = false; return vs->currentView(); } // last attempt: pick MRU view auto views = sortedViews(); if (!views.isEmpty()) { KTextEditor::View *v = views.front(); activateView(v); m_activeViewRunning = false; return v; } m_activeViewRunning = false; // no views exists! return nullptr; } void KateViewManager::setActiveSpace(KateViewSpace *vs) { if (activeViewSpace()) { activeViewSpace()->setActive(false); } vs->setActive(true); } void KateViewManager::setActiveView(KTextEditor::View *view) { if (activeView()) { m_views[activeView()].active = false; } if (view) { m_views[view].active = true; } } void KateViewManager::activateSpace(KTextEditor::View *v) { if (!v) { return; } KateViewSpace *vs = static_cast(v->parentWidget()->parentWidget()); if (!vs->isActiveSpace()) { setActiveSpace(vs); activateView(v); } } void KateViewManager::reactivateActiveView() { KTextEditor::View *view = activeView(); if (view) { m_views[view].active = false; activateView(view); } } void KateViewManager::activateView(KTextEditor::View *view) { if (!view) { return; } Q_ASSERT(m_views.contains(view)); if (!m_views[view].active) { // avoid flicker KateUpdateDisabler disableUpdates(mainWindow()); if (!activeViewSpace()->showView(view)) { // since it wasn't found, give'em a new one createView(view->document()); return; } setActiveView(view); bool toolbarVisible = mainWindow()->toolBar()->isVisible(); if (toolbarVisible) { mainWindow()->toolBar()->hide(); // hide to avoid toolbar flickering } if (m_guiMergedView) { mainWindow()->guiFactory()->removeClient(m_guiMergedView); m_guiMergedView = nullptr; } if (!m_blockViewCreationAndActivation) { mainWindow()->guiFactory()->addClient(view); m_guiMergedView = view; } if (toolbarVisible) { mainWindow()->toolBar()->show(); } // remember age of this view m_views[view].lruAge = m_minAge--; emit viewChanged(view); #ifdef KF5Activities_FOUND // inform activity manager m_views[view].activityResource->setUri(view->document()->url()); m_views[view].activityResource->notifyFocusedIn(); #endif } } KTextEditor::View *KateViewManager::activateView(KTextEditor::Document *d) { // no doc with this id found if (!d) { return activeView(); } // activate existing view if possible if (activeViewSpace()->showView(d)) { activateView(activeViewSpace()->currentView()); return activeView(); } // create new view otherwise createView(d); return activeView(); } void KateViewManager::slotViewChanged() { if (activeView() && !activeView()->hasFocus()) { activeView()->setFocus(); } } void KateViewManager::activateNextView() { int i = m_viewSpaceList.indexOf(activeViewSpace()) + 1; if (i >= m_viewSpaceList.count()) { i = 0; } setActiveSpace(m_viewSpaceList.at(i)); activateView(m_viewSpaceList.at(i)->currentView()); } void KateViewManager::activatePrevView() { int i = m_viewSpaceList.indexOf(activeViewSpace()) - 1; if (i < 0) { i = m_viewSpaceList.count() - 1; } setActiveSpace(m_viewSpaceList.at(i)); activateView(m_viewSpaceList.at(i)->currentView()); } void KateViewManager::documentWillBeDeleted(KTextEditor::Document *doc) { /** * collect all views of that document that belong to this manager */ QList closeList; const auto views = doc->views(); for (KTextEditor::View *v : views) { if (m_views.contains(v)) { closeList.append(v); } } while (!closeList.isEmpty()) { deleteView(closeList.takeFirst()); } } void KateViewManager::closeView(KTextEditor::View *view) { /** * kill view we want to kill */ deleteView(view); /** * try to have active view around! */ if (!activeView() && !KateApp::self()->documentManager()->documentList().isEmpty()) { createView(KateApp::self()->documentManager()->documentList().last()); } /** * if we have one now, show them in all viewspaces that got empty! */ if (KTextEditor::View *const newActiveView = activeView()) { /** * check if we have any empty viewspaces and give them a view */ for (KateViewSpace *vs :qAsConst(m_viewSpaceList)) { if (!vs->currentView()) { createView(newActiveView->document(), vs); } } emit viewChanged(newActiveView); } } void KateViewManager::splitViewSpace(KateViewSpace *vs, // = 0 Qt::Orientation o) // = Qt::Horizontal { // emergency: fallback to activeViewSpace, and if still invalid, abort if (!vs) { vs = activeViewSpace(); } if (!vs) { return; } // get current splitter, and abort if null QSplitter *currentSplitter = qobject_cast(vs->parentWidget()); if (!currentSplitter) { return; } // avoid flicker KateUpdateDisabler disableUpdates(mainWindow()); // index where to insert new splitter/viewspace const int index = currentSplitter->indexOf(vs); // create new viewspace KateViewSpace *vsNew = new KateViewSpace(this); // only 1 children -> we are the root container. So simply set the orientation // and add the new view space, then correct the sizes to 50%:50% if (currentSplitter->count() == 1) { if (currentSplitter->orientation() != o) { currentSplitter->setOrientation(o); } QList sizes = currentSplitter->sizes(); sizes << (sizes.first() - currentSplitter->handleWidth()) / 2; sizes[0] = sizes[1]; currentSplitter->insertWidget(index, vsNew); currentSplitter->setSizes(sizes); } else { // create a new QSplitter and replace vs with the splitter. vs and newVS are // the new children of the new QSplitter QSplitter *newContainer = new QSplitter(o); // we don't allow full collapse, see bug 366014 newContainer->setChildrenCollapsible(false); QList currentSizes = currentSplitter->sizes(); newContainer->addWidget(vs); newContainer->addWidget(vsNew); currentSplitter->insertWidget(index, newContainer); newContainer->show(); // fix sizes of children of old and new splitter currentSplitter->setSizes(currentSizes); QList newSizes = newContainer->sizes(); newSizes[0] = (newSizes[0] + newSizes[1] - newContainer->handleWidth()) / 2; newSizes[1] = newSizes[0]; newContainer->setSizes(newSizes); } m_viewSpaceList.append(vsNew); activeViewSpace()->setActive(false); vsNew->setActive(true); vsNew->show(); createView((KTextEditor::Document *)activeView()->document()); updateViewSpaceActions(); } void KateViewManager::closeViewSpace(KTextEditor::View *view) { KateViewSpace *space; if (view) { space = static_cast(view->parentWidget()->parentWidget()); } else { space = activeViewSpace(); } removeViewSpace(space); } void KateViewManager::toggleSplitterOrientation() { KateViewSpace *vs = activeViewSpace(); if (!vs) { return; } // get current splitter, and abort if null QSplitter *currentSplitter = qobject_cast(vs->parentWidget()); if (!currentSplitter || (currentSplitter->count() == 1)) { return; } // avoid flicker KateUpdateDisabler disableUpdates(mainWindow()); // toggle orientation if (currentSplitter->orientation() == Qt::Horizontal) { currentSplitter->setOrientation(Qt::Vertical); } else { currentSplitter->setOrientation(Qt::Horizontal); } } bool KateViewManager::viewsInSameViewSpace(KTextEditor::View *view1, KTextEditor::View *view2) { if (!view1 || !view2) { return false; } if (m_viewSpaceList.size() == 1) { return true; } KateViewSpace *vs1 = static_cast(view1->parentWidget()->parentWidget()); KateViewSpace *vs2 = static_cast(view2->parentWidget()->parentWidget()); return vs1 && (vs1 == vs2); } void KateViewManager::removeViewSpace(KateViewSpace *viewspace) { // abort if viewspace is 0 if (!viewspace) { return; } // abort if this is the last viewspace if (m_viewSpaceList.count() < 2) { return; } // get current splitter QSplitter *currentSplitter = qobject_cast(viewspace->parentWidget()); // no splitter found, bah if (!currentSplitter) { return; } // // 1. get LRU document list from current viewspace // 2. delete current view space // 3. add LRU documents from deleted viewspace to new active viewspace // // backup LRU list const QVector lruDocumntsList = viewspace->lruDocumentList(); // avoid flicker KateUpdateDisabler disableUpdates(mainWindow()); // delete views of the viewspace while (viewspace->currentView()) { deleteView(viewspace->currentView()); } // cu viewspace m_viewSpaceList.removeAt(m_viewSpaceList.indexOf(viewspace)); delete viewspace; // at this point, the splitter has exactly 1 child Q_ASSERT(currentSplitter->count() == 1); // if we are not the root splitter, move the child one level up and delete // the splitter then. if (currentSplitter != this) { // get parent splitter QSplitter *parentSplitter = qobject_cast(currentSplitter->parentWidget()); // only do magic if found ;) if (parentSplitter) { int index = parentSplitter->indexOf(currentSplitter); // save current splitter size, as the removal of currentSplitter looses the info QList parentSizes = parentSplitter->sizes(); parentSplitter->insertWidget(index, currentSplitter->widget(0)); delete currentSplitter; // now restore the sizes again parentSplitter->setSizes(parentSizes); } } else if (QSplitter *splitter = qobject_cast(currentSplitter->widget(0))) { // we are the root splitter and have only one child, which is also a splitter // -> eliminate the redundant splitter and move both children into the root splitter QList sizes = splitter->sizes(); // adapt splitter orientation to the splitter we are about to delete currentSplitter->setOrientation(splitter->orientation()); currentSplitter->addWidget(splitter->widget(0)); currentSplitter->addWidget(splitter->widget(0)); delete splitter; currentSplitter->setSizes(sizes); } // merge docuemnts of closed view space activeViewSpace()->mergeLruList(lruDocumntsList); // find the view that is now active. KTextEditor::View *v = activeViewSpace()->currentView(); if (v) { activateView(v); } updateViewSpaceActions(); emit viewChanged(v); } void KateViewManager::slotCloseOtherViews() { // avoid flicker KateUpdateDisabler disableUpdates(mainWindow()); const KateViewSpace *active = activeViewSpace(); - foreach (KateViewSpace *v, m_viewSpaceList) { + const auto viewSpaces = m_viewSpaceList; + for (KateViewSpace *v : viewSpaces) { if (active != v) { removeViewSpace(v); } } } void KateViewManager::slotHideOtherViews(bool hideOthers) { // avoid flicker KateUpdateDisabler disableUpdates(mainWindow()); const KateViewSpace *active = activeViewSpace(); for (KateViewSpace *v : qAsConst(m_viewSpaceList)) { if (active != v) { v->setVisible(!hideOthers); } } // disable the split actions, if we are in single-view-mode m_splitViewVert->setDisabled(hideOthers); m_splitViewHoriz->setDisabled(hideOthers); m_closeView->setDisabled(hideOthers); m_closeOtherViews->setDisabled(hideOthers); m_toggleSplitterOrientation->setDisabled(hideOthers); } /** * session config functions */ void KateViewManager::saveViewConfiguration(KConfigGroup &config) { // set Active ViewSpace to 0, just in case there is none active (would be // strange) and config somehow has previous value set config.writeEntry("Active ViewSpace", 0); m_splitterIndex = 0; saveSplitterConfig(this, config.config(), config.name()); } void KateViewManager::restoreViewConfiguration(const KConfigGroup &config) { /** * remove the single client that is registered at the factory, if any */ if (m_guiMergedView) { mainWindow()->guiFactory()->removeClient(m_guiMergedView); m_guiMergedView = nullptr; } /** * delete viewspaces, they will delete the views */ qDeleteAll(m_viewSpaceList); m_viewSpaceList.clear(); /** * delete mapping of now deleted views */ m_views.clear(); /** * kill all previous existing sub-splitters, just to be sure * e.g. important if one restores a config in an existing window with some splitters */ while (count() > 0) { delete widget(0); } // reset lru history, too! m_minAge = 0; // start recursion for the root splitter (Splitter 0) restoreSplitter(config.config(), config.name() + QStringLiteral("-Splitter 0"), this, config.name()); // finally, make the correct view from the last session active int lastViewSpace = config.readEntry("Active ViewSpace", 0); if (lastViewSpace > m_viewSpaceList.size()) { lastViewSpace = 0; } if (lastViewSpace >= 0 && lastViewSpace < m_viewSpaceList.size()) { setActiveSpace(m_viewSpaceList.at(lastViewSpace)); // activate correct view (wish #195435, #188764) activateView(m_viewSpaceList.at(lastViewSpace)->currentView()); // give view the focus to avoid focus stealing by toolviews / plugins m_viewSpaceList.at(lastViewSpace)->currentView()->setFocus(); } // emergency if (m_viewSpaceList.empty()) { // kill bad children while (count()) { delete widget(0); } KateViewSpace *vs = new KateViewSpace(this, nullptr); addWidget(vs); vs->setActive(true); m_viewSpaceList.append(vs); /** * activate at least one document! */ activateView(KateApp::self()->documentManager()->documentList().last()); if (!vs->currentView()) { createView(activeView()->document(), vs); } } updateViewSpaceActions(); } QString KateViewManager::saveSplitterConfig(QSplitter *s, KConfigBase *configBase, const QString &viewConfGrp) { /** * avoid to export invisible view spaces * else they will stick around for ever in sessions * bug 358266 - code initially done during load * bug 381433 - moved code to save */ /** * create new splitter name, might be not used */ const auto grp = QString(viewConfGrp + QStringLiteral("-Splitter %1")).arg(m_splitterIndex); ++m_splitterIndex; // a QSplitter has two children, either QSplitters and/or KateViewSpaces // special case: root splitter might have only one child (just for info) QStringList childList; const auto sizes = s->sizes(); for (int it = 0; it < s->count(); ++it) { // skip empty sized invisible ones, if not last one, we need one thing at least if ((sizes[it] == 0) && ((it + 1 < s->count()) || !childList.empty())) continue; // For KateViewSpaces, ask them to save the file list. auto obj = s->widget(it); if (auto kvs = qobject_cast(obj)) { childList.append(QString(viewConfGrp + QStringLiteral("-ViewSpace %1")).arg(m_viewSpaceList.indexOf(kvs))); kvs->saveConfig(configBase, m_viewSpaceList.indexOf(kvs), viewConfGrp); // save active viewspace if (kvs->isActiveSpace()) { KConfigGroup viewConfGroup(configBase, viewConfGrp); viewConfGroup.writeEntry("Active ViewSpace", m_viewSpaceList.indexOf(kvs)); } } // for QSplitters, recurse else if (auto splitter = qobject_cast(obj)) { childList.append(saveSplitterConfig(splitter, configBase, viewConfGrp)); } } // if only one thing, skip splitter config export, if not top splitter if ((s != this) && (childList.size() == 1)) return childList.at(0); // Save sizes, orient, children for this splitter KConfigGroup config(configBase, grp); config.writeEntry("Sizes", sizes); config.writeEntry("Orientation", int(s->orientation())); config.writeEntry("Children", childList); return grp; } void KateViewManager::restoreSplitter(const KConfigBase *configBase, const QString &group, QSplitter *parent, const QString &viewConfGrp) { KConfigGroup config(configBase, group); parent->setOrientation((Qt::Orientation)config.readEntry("Orientation", int(Qt::Horizontal))); const QStringList children = config.readEntry("Children", QStringList()); for (const auto &str : children) { // for a viewspace, create it and open all documents therein. if (str.startsWith(viewConfGrp + QStringLiteral("-ViewSpace"))) { KateViewSpace *vs = new KateViewSpace(this, nullptr); m_viewSpaceList.append(vs); // make active so that the view created in restoreConfig has this // new view space as parent. setActiveSpace(vs); parent->addWidget(vs); vs->restoreConfig(this, configBase, str); vs->show(); } else { // for a splitter, recurse auto newContainer = new QSplitter(parent); // we don't allow full collapse, see bug 366014 newContainer->setChildrenCollapsible(false); restoreSplitter(configBase, str, newContainer, viewConfGrp); } } // set sizes parent->setSizes(config.readEntry("Sizes", QList())); parent->show(); } void KateViewManager::moveSplitter(Qt::Key key, int repeats) { if (repeats < 1) { return; } KateViewSpace *vs = activeViewSpace(); if (!vs) { return; } QSplitter *currentSplitter = qobject_cast(vs->parentWidget()); if (!currentSplitter) { return; } if (currentSplitter->count() == 1) { return; } int move = 4 * repeats; // try to use font height in pixel to move splitter { KTextEditor::Attribute::Ptr attrib(vs->currentView()->defaultStyleAttribute(KTextEditor::dsNormal)); QFontMetrics fm(attrib->font()); move = fm.height() * repeats; } QWidget *currentWidget = (QWidget *)vs; bool foundSplitter = false; // find correct splitter to be moved while (currentSplitter && currentSplitter->count() != 1) { if (currentSplitter->orientation() == Qt::Horizontal && (key == Qt::Key_Right || key == Qt::Key_Left)) { foundSplitter = true; } if (currentSplitter->orientation() == Qt::Vertical && (key == Qt::Key_Up || key == Qt::Key_Down)) { foundSplitter = true; } // if the views within the current splitter can be resized, resize them if (foundSplitter) { QList currentSizes = currentSplitter->sizes(); int index = currentSplitter->indexOf(currentWidget); if ((index == 0 && (key == Qt::Key_Left || key == Qt::Key_Up)) || (index == 1 && (key == Qt::Key_Right || key == Qt::Key_Down))) { currentSizes[index] -= move; } if ((index == 0 && (key == Qt::Key_Right || key == Qt::Key_Down)) || (index == 1 && (key == Qt::Key_Left || key == Qt::Key_Up))) { currentSizes[index] += move; } if (index == 0 && (key == Qt::Key_Right || key == Qt::Key_Down)) { currentSizes[index + 1] -= move; } if (index == 0 && (key == Qt::Key_Left || key == Qt::Key_Up)) { currentSizes[index + 1] += move; } if (index == 1 && (key == Qt::Key_Right || key == Qt::Key_Down)) { currentSizes[index - 1] += move; } if (index == 1 && (key == Qt::Key_Left || key == Qt::Key_Up)) { currentSizes[index - 1] -= move; } currentSplitter->setSizes(currentSizes); break; } currentWidget = (QWidget *)currentSplitter; // the parent of the current splitter will become the current splitter currentSplitter = qobject_cast(currentSplitter->parentWidget()); } } diff --git a/kwrite/main.cpp b/kwrite/main.cpp index f77518405..8601176bd 100644 --- a/kwrite/main.cpp +++ b/kwrite/main.cpp @@ -1,312 +1,313 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund 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 "config.h" #include "kwrite.h" #include "kwriteapplication.h" #include #include #include #include #include // for KAboutData::setDesktopFileName() #include #include #include #include #if KCrash_VERSION >= QT_VERSION_CHECK(5, 15, 0) #include #endif // KCrash >= 5.15 #include #include #include #include #include #include #include "../urlinfo.h" #ifndef Q_OS_WIN #include #endif #include extern "C" Q_DECL_EXPORT int main(int argc, char **argv) { #ifndef Q_OS_WIN // Prohibit using sudo or kdesu (but allow using the root user directly) if (getuid() == 0) { if (!qEnvironmentVariableIsEmpty("SUDO_USER")) { std::cout << "Executing KWrite with sudo is not possible due to unfixable security vulnerabilities." << std::endl; return EXIT_FAILURE; } else if (!qEnvironmentVariableIsEmpty("KDESU_USER")) { std::cout << "Executing KWrite with kdesu is not possible due to unfixable security vulnerabilities." << std::endl; return EXIT_FAILURE; } } #endif /** * Create application first * Enforce application name even if the executable is renamed */ QApplication app(argc, argv); app.setApplicationName(QStringLiteral("kwrite")); /** * enable high dpi support */ app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); /** * Enable crash handling through KCrash. */ #if KCrash_VERSION >= QT_VERSION_CHECK(5, 15, 0) KCrash::initialize(); #endif /** * Connect application with translation catalogs */ KLocalizedString::setApplicationDomain("kwrite"); /** * then use i18n and co */ KAboutData aboutData( QStringLiteral("kwrite"), i18n("KWrite"), QStringLiteral(KWRITE_VERSION), i18n("KWrite - Text Editor"), KAboutLicense::LGPL_V2, i18n("(c) 2000-2019 The Kate Authors"), QString(), QStringLiteral("https://kate-editor.org")); /** * right dbus prefix == org.kde. */ aboutData.setOrganizationDomain(QByteArray("kde.org")); /** * desktop file association to make application icon work (e.g. in Wayland window decoration) */ #if KCOREADDONS_VERSION >= QT_VERSION_CHECK(5, 16, 0) aboutData.setDesktopFileName(QStringLiteral("org.kde.kwrite")); #endif aboutData.addAuthor(i18n("Christoph Cullmann"), i18n("Maintainer"), QStringLiteral("cullmann@kde.org"), QStringLiteral("https://cullmann.io")); aboutData.addAuthor(i18n("Dominik Haumann"), i18n("Core Developer"), QStringLiteral("dhaumann@kde.org")); aboutData.addAuthor(i18n("Anders Lund"), i18n("Core Developer"), QStringLiteral("anders@alweb.dk"), QStringLiteral("http://www.alweb.dk")); aboutData.addAuthor(i18n("Joseph Wenninger"), i18n("Core Developer"), QStringLiteral("jowenn@kde.org"), QStringLiteral("http://stud3.tuwien.ac.at/~e9925371")); aboutData.addAuthor(i18n("Hamish Rodda"), i18n("Core Developer"), QStringLiteral("rodda@kde.org")); aboutData.addAuthor(i18n("Waldo Bastian"), i18n("The cool buffersystem"), QStringLiteral("bastian@kde.org")); aboutData.addAuthor(i18n("Charles Samuels"), i18n("The Editing Commands"), QStringLiteral("charles@kde.org")); aboutData.addAuthor(i18n("Matt Newell"), i18nc("Credit text for someone that did testing and some other similar things", "Testing, ..."), QStringLiteral("newellm@proaxis.com")); aboutData.addAuthor(i18n("Michael Bartl"), i18n("Former Core Developer"), QStringLiteral("michael.bartl1@chello.at")); aboutData.addAuthor(i18n("Michael McCallum"), i18n("Core Developer"), QStringLiteral("gholam@xtra.co.nz")); aboutData.addAuthor(i18n("Jochen Wilhemly"), i18n("KWrite Author"), QStringLiteral("digisnap@cs.tu-berlin.de")); aboutData.addAuthor(i18n("Michael Koch"), i18n("KWrite port to KParts"), QStringLiteral("koch@kde.org")); aboutData.addAuthor(i18n("Christian Gebauer"), QString(), QStringLiteral("gebauer@kde.org")); aboutData.addAuthor(i18n("Simon Hausmann"), QString(), QStringLiteral("hausmann@kde.org")); aboutData.addAuthor(i18n("Glen Parker"), i18n("KWrite Undo History, Kspell integration"), QStringLiteral("glenebob@nwlink.com")); aboutData.addAuthor(i18n("Scott Manson"), i18n("KWrite XML Syntax highlighting support"), QStringLiteral("sdmanson@alltel.net")); aboutData.addAuthor(i18n("John Firebaugh"), i18n("Patches and more"), QStringLiteral("jfirebaugh@kde.org")); aboutData.addAuthor(i18n("Gerald Senarclens de Grancy"), i18n("QA and Scripting"), QStringLiteral("oss@senarclens.eu"), QStringLiteral("http://find-santa.eu/")); aboutData.addCredit(i18n("Matteo Merli"), i18n("Highlighting for RPM Spec-Files, Perl, Diff and more"), QStringLiteral("merlim@libero.it")); aboutData.addCredit(i18n("Rocky Scaletta"), i18n("Highlighting for VHDL"), QStringLiteral("rocky@purdue.edu")); aboutData.addCredit(i18n("Yury Lebedev"), i18n("Highlighting for SQL")); aboutData.addCredit(i18n("Chris Ross"), i18n("Highlighting for Ferite")); aboutData.addCredit(i18n("Nick Roux"), i18n("Highlighting for ILERPG")); aboutData.addCredit(i18n("Carsten Niehaus"), i18n("Highlighting for LaTeX")); aboutData.addCredit(i18n("Per Wigren"), i18n("Highlighting for Makefiles, Python")); aboutData.addCredit(i18n("Jan Fritz"), i18n("Highlighting for Python")); aboutData.addCredit(i18n("Daniel Naber")); aboutData.addCredit(i18n("Roland Pabel"), i18n("Highlighting for Scheme")); aboutData.addCredit(i18n("Cristi Dumitrescu"), i18n("PHP Keyword/Datatype list")); aboutData.addCredit(i18n("Carsten Pfeiffer"), i18nc("Credit text for someone that helped a lot", "Very nice help")); aboutData.addCredit(i18n("All people who have contributed and I have forgotten to mention")); /** * bugzilla */ aboutData.setProductName(QByteArray("kate/kwrite")); /** * set and register app about data */ KAboutData::setApplicationData(aboutData); /** * set the program icon */ QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("accessories-text-editor"), app.windowIcon())); /** * Create command line parser and feed it with known options */ QCommandLineParser parser; aboutData.setupCommandLine(&parser); // -e/--encoding option const QCommandLineOption useEncoding(QStringList() << QStringLiteral("e") << QStringLiteral("encoding"), i18n("Set encoding for the file to open."), i18n("encoding")); parser.addOption(useEncoding); // -l/--line option const QCommandLineOption gotoLine(QStringList() << QStringLiteral("l") << QStringLiteral("line"), i18n("Navigate to this line."), i18n("line")); parser.addOption(gotoLine); // -c/--column option const QCommandLineOption gotoColumn(QStringList() << QStringLiteral("c") << QStringLiteral("column"), i18n("Navigate to this column."), i18n("column")); parser.addOption(gotoColumn); // -i/--stdin option const QCommandLineOption readStdIn(QStringList() << QStringLiteral("i") << QStringLiteral("stdin"), i18n("Read the contents of stdin.")); parser.addOption(readStdIn); // --tempfile option const QCommandLineOption tempfile(QStringList() << QStringLiteral("tempfile"), i18n("The files/URLs opened by the application will be deleted after use")); parser.addOption(tempfile); // urls to open parser.addPositionalArgument(QStringLiteral("urls"), i18n("Documents to open."), i18n("[urls...]")); /** * do the command line parsing */ parser.process(app); /** * handle standard options */ aboutData.processCommandLine(&parser); KWriteApplication kapp; if (app.isSessionRestored()) { kapp.restore(); } else { bool nav = false; int line = 0, column = 0; QTextCodec *codec = parser.isSet(QStringLiteral("encoding")) ? QTextCodec::codecForName(parser.value(QStringLiteral("encoding")).toLocal8Bit()) : nullptr; if (parser.isSet(QStringLiteral("line"))) { line = parser.value(QStringLiteral("line")).toInt() - 1; nav = true; } if (parser.isSet(QStringLiteral("column"))) { column = parser.value(QStringLiteral("column")).toInt() - 1; nav = true; } if (parser.positionalArguments().count() == 0) { KWrite *t = kapp.newWindow(); if (parser.isSet(QStringLiteral("stdin"))) { QTextStream input(stdin, QIODevice::ReadOnly); // set chosen codec if (codec) { input.setCodec(codec); } QString line; QString text; do { line = input.readLine(); text.append(line + QLatin1Char('\n')); } while (!line.isNull()); KTextEditor::Document *doc = t->activeView()->document(); if (doc) { // remember codec in document, e.g. to show the right one if (codec) { doc->setEncoding(QString::fromLatin1(codec->name())); } doc->setText(text); } } if (nav && t->activeView()) { t->activeView()->setCursorPosition(KTextEditor::Cursor(line, column)); } } else { int docs_opened = 0; - Q_FOREACH (const QString positionalArgument, parser.positionalArguments()) { + const auto positionalArguments = parser.positionalArguments(); + for (const QString &positionalArgument : positionalArguments) { UrlInfo info(positionalArgument); if (nav) { info.cursor = KTextEditor::Cursor(line, column); } // this file is no local dir, open it, else warn bool noDir = !info.url.isLocalFile() || !QFileInfo(info.url.toLocalFile()).isDir(); if (noDir) { ++docs_opened; KWrite *t = kapp.newWindow(); if (codec) { t->activeView()->document()->setEncoding(QString::fromLatin1(codec->name())); } t->loadURL(info.url); if (info.cursor.isValid()) { t->activeView()->setCursorPosition(info.cursor); } else if (info.url.hasQuery()) { QUrlQuery q(info.url); QString lineStr = q.queryItemValue(QStringLiteral("line")); QString columnStr = q.queryItemValue(QStringLiteral("column")); line = lineStr.toInt(); if (line > 0) line--; column = columnStr.toInt(); if (column > 0) column--; t->activeView()->setCursorPosition(KTextEditor::Cursor(line, column)); } } else { KMessageBox::sorry(nullptr, i18n("The file '%1' could not be opened: it is not a normal file, it is a folder.", info.url.toString())); } } if (!docs_opened) { ::exit(1); // see http://bugs.kde.org/show_bug.cgi?id=124708 } } } // no window there, uh, ohh, for example borked session config !!! // create at least one !! if (kapp.noWindows()) { kapp.newWindow(); } /** * finally register this kwrite instance for dbus, don't die if no dbus is around! */ const KDBusService dbusService(KDBusService::Multiple | KDBusService::NoExitOnFailure); /** * Run the event loop */ return app.exec(); }