diff --git a/addons/gdbplugin/configview.cpp b/addons/gdbplugin/configview.cpp index 511530629..1a6d293d2 100644 --- a/addons/gdbplugin/configview.cpp +++ b/addons/gdbplugin/configview.cpp @@ -1,529 +1,525 @@ // // configview.cpp // // Description: View for configuring the set of targets to be used with the debugger // // // Copyright (c) 2010 Ian Wakeling // Copyright (c) 2012 Kåre Särs // // 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 "configview.h" #include #include #include #include #include #include #include #include #include #include #ifdef WIN32 static const QLatin1Char pathSeparator(';'); #else static const QLatin1Char pathSeparator(':'); #endif ConfigView::ConfigView(QWidget *parent, KTextEditor::MainWindow *mainWin) : QWidget(parent) , m_mainWindow(mainWin) { m_targetCombo = new QComboBox(); m_targetCombo->setEditable(true); // don't let Qt insert items when the user edits; new targets are only // added when the user explicitly says so m_targetCombo->setInsertPolicy(QComboBox::NoInsert); m_targetCombo->setDuplicatesEnabled(true); m_addTarget = new QToolButton(); m_addTarget->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); m_addTarget->setToolTip(i18n("Add new target")); m_copyTarget = new QToolButton(); m_copyTarget->setIcon(QIcon::fromTheme(QStringLiteral("document-copy"))); m_copyTarget->setToolTip(i18n("Copy target")); m_deleteTarget = new QToolButton(); m_deleteTarget->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); m_deleteTarget->setToolTip(i18n("Delete target")); m_line = new QFrame(this); m_line->setFrameShadow(QFrame::Sunken); m_execLabel = new QLabel(i18n("Executable:")); m_execLabel->setBuddy(m_targetCombo); m_executable = new QLineEdit(); QCompleter *completer1 = new QCompleter(this); QFileSystemModel *model = new QFileSystemModel(this); model->setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); completer1->setModel(model); m_executable->setCompleter(completer1); m_executable->setClearButtonEnabled(true); m_browseExe = new QToolButton(this); m_browseExe->setIcon(QIcon::fromTheme(QStringLiteral("application-x-executable"))); m_workingDirectory = new QLineEdit(); QCompleter *completer2 = new QCompleter(this); QFileSystemModel *model2 = new QFileSystemModel(completer2); completer2->setModel(model2); m_workingDirectory->setCompleter(completer2); m_workingDirectory->setClearButtonEnabled(true); m_workDirLabel = new QLabel(i18n("Working Directory:")); m_workDirLabel->setBuddy(m_workingDirectory); m_browseDir = new QToolButton(this); m_browseDir->setIcon(QIcon::fromTheme(QStringLiteral("inode-directory"))); m_arguments = new QLineEdit(); m_arguments->setClearButtonEnabled(true); m_argumentsLabel = new QLabel(i18nc("Program argument list", "Arguments:")); m_argumentsLabel->setBuddy(m_arguments); m_takeFocus = new QCheckBox(i18nc("Checkbox to for keeping focus on the command line", "Keep focus")); m_takeFocus->setToolTip(i18n("Keep the focus on the command line")); m_redirectTerminal = new QCheckBox(i18n("Redirect IO")); m_redirectTerminal->setToolTip(i18n("Redirect the debugged programs IO to a separate tab")); m_advancedSettings = new QPushButton(i18n("Advanced Settings")); m_checBoxLayout = nullptr; // first false then true to make sure a layout is set m_useBottomLayout = false; resizeEvent(nullptr); m_useBottomLayout = true; resizeEvent(nullptr); m_advanced = new AdvancedGDBSettings(this); m_advanced->hide(); connect(m_targetCombo, &QComboBox::editTextChanged, this, &ConfigView::slotTargetEdited); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) connect(m_targetCombo, static_cast(&QComboBox::currentIndexChanged), this, &ConfigView::slotTargetSelected); -#else - connect(m_targetCombo, static_cast(&QComboBox::currentIndexChanged) , this, &ConfigView::slotTargetSelected); -#endif connect(m_addTarget, &QToolButton::clicked, this, &ConfigView::slotAddTarget); connect(m_copyTarget, &QToolButton::clicked, this, &ConfigView::slotCopyTarget); connect(m_deleteTarget, &QToolButton::clicked, this, &ConfigView::slotDeleteTarget); connect(m_browseExe, &QToolButton::clicked, this, &ConfigView::slotBrowseExec); connect(m_browseDir, &QToolButton::clicked, this, &ConfigView::slotBrowseDir); connect(m_redirectTerminal, &QCheckBox::toggled, this, &ConfigView::showIO); connect(m_advancedSettings, &QPushButton::clicked, this, &ConfigView::slotAdvancedClicked); } ConfigView::~ConfigView() { } void ConfigView::registerActions(KActionCollection *actionCollection) { m_targetSelectAction = actionCollection->add(QStringLiteral("targets")); m_targetSelectAction->setText(i18n("Targets")); connect(m_targetSelectAction, static_cast(&KSelectAction::triggered), this, &ConfigView::slotTargetSelected); } void ConfigView::readConfig(const KConfigGroup &group) { m_targetCombo->clear(); int version = group.readEntry(QStringLiteral("version"), 4); int targetCount = group.readEntry(QStringLiteral("targetCount"), 1); int lastTarget = group.readEntry(QStringLiteral("lastTarget"), 0); QString targetKey(QStringLiteral("target_%1")); QStringList targetConfStrs; for (int i = 0; i < targetCount; i++) { targetConfStrs = group.readEntry(targetKey.arg(i), QStringList()); if (targetConfStrs.count() == 0) continue; if ((version == 1) && (targetConfStrs.count() == 3)) { // valid old style config, translate it now; note the // reordering happening here! QStringList temp; temp << targetConfStrs[2]; temp << targetConfStrs[1]; targetConfStrs = temp; } if (version < 4) { targetConfStrs.prepend(targetConfStrs[0].right(15)); } if (targetConfStrs.count() > NameIndex) { m_targetCombo->addItem(targetConfStrs[NameIndex], targetConfStrs); } } if (version < 4) { // all targets now have only one argument string int argListsCount = group.readEntry(QStringLiteral("argsCount"), 0); QString argsKey(QStringLiteral("args_%1")); QString targetName(QStringLiteral("%1<%2>")); QString argStr; int count = m_targetCombo->count(); for (int i = 0; i < argListsCount; i++) { argStr = group.readEntry(argsKey.arg(i), QString()); for (int j = 0; j < count; j++) { targetConfStrs = m_targetCombo->itemData(j).toStringList(); if (i > 0) { // copy the firsts and change the arguments targetConfStrs[0] = targetName.arg(targetConfStrs[0]).arg(i + 1); if (targetConfStrs.count() > 3) targetConfStrs[3] = argStr; m_targetCombo->addItem(targetConfStrs[0], targetConfStrs); } } } } // make sure there is at least one item. if (m_targetCombo->count() == 0) { slotAddTarget(); } QStringList targetNames; for (int i = 0; i < m_targetCombo->count(); i++) { targetNames << m_targetCombo->itemText(i); } m_targetSelectAction->setItems(targetNames); if (lastTarget < 0 || lastTarget >= m_targetCombo->count()) lastTarget = 0; m_targetCombo->setCurrentIndex(lastTarget); m_takeFocus->setChecked(group.readEntry("alwaysFocusOnInput", false)); m_redirectTerminal->setChecked(group.readEntry("redirectTerminal", false)); } void ConfigView::writeConfig(KConfigGroup &group) { // make sure the data is up to date before writing saveCurrentToIndex(m_currentTarget); group.writeEntry("version", 4); QString targetKey(QStringLiteral("target_%1")); QStringList targetConfStrs; group.writeEntry("targetCount", m_targetCombo->count()); group.writeEntry("lastTarget", m_targetCombo->currentIndex()); for (int i = 0; i < m_targetCombo->count(); i++) { targetConfStrs = m_targetCombo->itemData(i).toStringList(); group.writeEntry(targetKey.arg(i), targetConfStrs); } group.writeEntry("alwaysFocusOnInput", m_takeFocus->isChecked()); group.writeEntry("redirectTerminal", m_redirectTerminal->isChecked()); } const GDBTargetConf ConfigView::currentTarget() const { GDBTargetConf cfg; cfg.executable = m_executable->text(); cfg.workDir = m_workingDirectory->text(); cfg.arguments = m_arguments->text(); cfg.customInit = m_advanced->configs(); // Note: AdvancedGDBSettings::GDBIndex == 0 if ((cfg.customInit.size() >= 0) && !cfg.customInit[0].isEmpty()) { cfg.gdbCmd = cfg.customInit[0]; cfg.customInit.removeFirst(); } else { cfg.gdbCmd = QStringLiteral("gdb"); } // remove empty strings in the customInit int i = cfg.customInit.size() - 1; while (i >= 0) { if (cfg.customInit[i].isEmpty()) { cfg.customInit.removeAt(i); } else if (cfg.customInit[i].startsWith(QLatin1String("set directories "))) { QString paths = cfg.customInit[i]; paths.remove(QStringLiteral("set directories ")); cfg.srcPaths = paths.split(pathSeparator, QString::SkipEmptyParts); } i--; } return cfg; } bool ConfigView::takeFocusAlways() const { return m_takeFocus->isChecked(); } bool ConfigView::showIOTab() const { return m_redirectTerminal->isChecked(); } void ConfigView::slotTargetEdited(const QString &newText) { int cursorPosition = m_targetCombo->lineEdit()->cursorPosition(); m_targetCombo->setItemText(m_targetCombo->currentIndex(), newText); m_targetCombo->lineEdit()->setCursorPosition(cursorPosition); // rebuild the target menu QStringList targets; for (int i = 0; i < m_targetCombo->count(); ++i) { targets.append(m_targetCombo->itemText(i)); } m_targetSelectAction->setItems(targets); m_targetSelectAction->setCurrentItem(m_targetCombo->currentIndex()); } void ConfigView::slotTargetSelected(int index) { if ((index < 0) || (index >= m_targetCombo->count())) { return; } if ((m_currentTarget > 0) && (m_currentTarget < m_targetCombo->count())) { saveCurrentToIndex(m_currentTarget); } loadFromIndex(index); m_currentTarget = index; setAdvancedOptions(); // Keep combo box and menu in sync m_targetSelectAction->setCurrentItem(index); } void ConfigView::slotAddTarget() { QStringList targetConfStrs; targetConfStrs << i18n("Target %1", m_targetCombo->count() + 1); targetConfStrs << QString(); targetConfStrs << QString(); targetConfStrs << QString(); m_targetCombo->addItem(targetConfStrs[NameIndex], targetConfStrs); m_targetCombo->setCurrentIndex(m_targetCombo->count() - 1); } void ConfigView::slotCopyTarget() { QStringList tmp = m_targetCombo->itemData(m_targetCombo->currentIndex()).toStringList(); if (tmp.empty()) { slotAddTarget(); return; } tmp[NameIndex] = i18n("Target %1", m_targetCombo->count() + 1); m_targetCombo->addItem(tmp[NameIndex], tmp); m_targetCombo->setCurrentIndex(m_targetCombo->count() - 1); } void ConfigView::slotDeleteTarget() { m_targetCombo->blockSignals(true); int currentIndex = m_targetCombo->currentIndex(); m_targetCombo->removeItem(currentIndex); if (m_targetCombo->count() == 0) { slotAddTarget(); } loadFromIndex(m_targetCombo->currentIndex()); m_targetCombo->blockSignals(false); } void ConfigView::resizeEvent(QResizeEvent *) { if (m_useBottomLayout && size().height() > size().width()) { // Set layout for the side delete m_checBoxLayout; m_checBoxLayout = nullptr; delete layout(); QGridLayout *layout = new QGridLayout(this); layout->addWidget(m_targetCombo, 0, 0); layout->addWidget(m_addTarget, 0, 1); layout->addWidget(m_copyTarget, 0, 2); layout->addWidget(m_deleteTarget, 0, 3); m_line->setFrameShape(QFrame::HLine); layout->addWidget(m_line, 1, 0, 1, 4); layout->addWidget(m_execLabel, 3, 0, Qt::AlignLeft); layout->addWidget(m_executable, 4, 0, 1, 3); layout->addWidget(m_browseExe, 4, 3); layout->addWidget(m_workDirLabel, 5, 0, Qt::AlignLeft); layout->addWidget(m_workingDirectory, 6, 0, 1, 3); layout->addWidget(m_browseDir, 6, 3); layout->addWidget(m_argumentsLabel, 7, 0, Qt::AlignLeft); layout->addWidget(m_arguments, 8, 0, 1, 4); layout->addWidget(m_takeFocus, 9, 0, 1, 4); layout->addWidget(m_redirectTerminal, 10, 0, 1, 4); layout->addWidget(m_advancedSettings, 11, 0, 1, 4); layout->addItem(new QSpacerItem(1, 1), 12, 0); layout->setColumnStretch(0, 1); layout->setRowStretch(12, 1); m_useBottomLayout = false; } else if (!m_useBottomLayout && (size().height() < size().width())) { // Set layout for the bottom delete m_checBoxLayout; delete layout(); m_checBoxLayout = new QHBoxLayout(); m_checBoxLayout->addWidget(m_takeFocus, 10); m_checBoxLayout->addWidget(m_redirectTerminal, 10); m_checBoxLayout->addWidget(m_advancedSettings, 0); QGridLayout *layout = new QGridLayout(this); layout->addWidget(m_targetCombo, 0, 0, 1, 3); layout->addWidget(m_addTarget, 1, 0); layout->addWidget(m_copyTarget, 1, 1); layout->addWidget(m_deleteTarget, 1, 2); m_line->setFrameShape(QFrame::VLine); layout->addWidget(m_line, 0, 3, 4, 1); layout->addWidget(m_execLabel, 0, 5, Qt::AlignRight); layout->addWidget(m_executable, 0, 6); layout->addWidget(m_browseExe, 0, 7); layout->addWidget(m_workDirLabel, 1, 5, Qt::AlignRight); layout->addWidget(m_workingDirectory, 1, 6); layout->addWidget(m_browseDir, 1, 7); layout->addWidget(m_argumentsLabel, 2, 5, Qt::AlignRight); layout->addWidget(m_arguments, 2, 6, 1, 2); layout->addLayout(m_checBoxLayout, 3, 5, 1, 3); layout->addItem(new QSpacerItem(1, 1), 4, 0); layout->setColumnStretch(6, 100); layout->setRowStretch(4, 100); m_useBottomLayout = true; } } void ConfigView::setAdvancedOptions() { QStringList tmp = m_targetCombo->itemData(m_targetCombo->currentIndex()).toStringList(); // make sure we have enough strings; while (tmp.count() < CustomStartIndex) tmp << QString(); if (tmp[GDBIndex].isEmpty()) { tmp[GDBIndex] = QStringLiteral("gdb"); } // Remove the strings that are not part of the advanced settings for (int i = 0; i < GDBIndex; i++) tmp.takeFirst(); m_advanced->setConfigs(tmp); } void ConfigView::slotAdvancedClicked() { setAdvancedOptions(); QStringList newList = m_targetCombo->itemData(m_targetCombo->currentIndex()).toStringList(); // make sure we have enough strings; while (newList.count() < GDBIndex) newList << QString(); // Remove old advanced settings while (newList.count() > GDBIndex) newList.takeLast(); if (m_advanced->exec() == QDialog::Accepted) { // save the new values newList << m_advanced->configs(); m_targetCombo->setItemData(m_targetCombo->currentIndex(), newList); } } void ConfigView::slotBrowseExec() { QString exe = m_executable->text(); if (m_executable->text().isEmpty()) { // try current document dir KTextEditor::View *view = m_mainWindow->activeView(); if (view != nullptr) { exe = view->document()->url().toLocalFile(); } } m_executable->setText(QFileDialog::getOpenFileName(nullptr, QString(), exe, QStringLiteral("application/x-executable"))); } void ConfigView::slotBrowseDir() { QString dir = m_workingDirectory->text(); if (m_workingDirectory->text().isEmpty()) { // try current document dir KTextEditor::View *view = m_mainWindow->activeView(); if (view != nullptr) { dir = view->document()->url().toLocalFile(); } } m_workingDirectory->setText(QFileDialog::getExistingDirectory(this, QString(), dir)); } void ConfigView::saveCurrentToIndex(int index) { if ((index < 0) || (index >= m_targetCombo->count())) { return; } QStringList tmp = m_targetCombo->itemData(index).toStringList(); // make sure we have enough strings. The custom init strings are set in slotAdvancedClicked(). while (tmp.count() < CustomStartIndex) tmp << QString(); tmp[NameIndex] = m_targetCombo->itemText(index); tmp[ExecIndex] = m_executable->text(); tmp[WorkDirIndex] = m_workingDirectory->text(); tmp[ArgsIndex] = m_arguments->text(); m_targetCombo->setItemData(index, tmp); } void ConfigView::loadFromIndex(int index) { if ((index < 0) || (index >= m_targetCombo->count())) { return; } QStringList tmp = m_targetCombo->itemData(index).toStringList(); // make sure we have enough strings. The custom init strings are set in slotAdvancedClicked(). while (tmp.count() < CustomStartIndex) tmp << QString(); m_executable->setText(tmp[ExecIndex]); m_workingDirectory->setText(tmp[WorkDirIndex]); m_arguments->setText(tmp[ArgsIndex]); } diff --git a/addons/gdbplugin/plugin_kategdb.cpp b/addons/gdbplugin/plugin_kategdb.cpp index cad80c4b0..7a827cc73 100644 --- a/addons/gdbplugin/plugin_kategdb.cpp +++ b/addons/gdbplugin/plugin_kategdb.cpp @@ -1,736 +1,732 @@ // // Description: Kate Plugin for GDB integration // // // Copyright (c) 2010 Ian Wakeling // Copyright (c) 2010-2014 Kåre Särs // // 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 "plugin_kategdb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KatePluginGDBFactory, "kategdbplugin.json", registerPlugin();) KatePluginGDB::KatePluginGDB(QObject *parent, const VariantList &) : KTextEditor::Plugin(parent) { // FIXME KF5 KGlobal::locale()->insertCatalog("kategdbplugin"); } KatePluginGDB::~KatePluginGDB() { } QObject *KatePluginGDB::createView(KTextEditor::MainWindow *mainWindow) { return new KatePluginGDBView(this, mainWindow); } KatePluginGDBView::KatePluginGDBView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWin) : QObject(mainWin) , m_mainWin(mainWin) { m_lastExecUrl = QUrl(); m_lastExecLine = -1; m_lastExecFrame = 0; m_kateApplication = KTextEditor::Editor::instance()->application(); m_focusOnInput = true; m_activeThread = -1; KXMLGUIClient::setComponentName(QStringLiteral("kategdb"), i18n("Kate GDB")); setXMLFile(QStringLiteral("ui.rc")); m_toolView = m_mainWin->createToolView(plugin, i18n("Debug View"), KTextEditor::MainWindow::Bottom, QIcon(QStringLiteral(":/kategdb/22-actions-debug-kategdb.png")), i18n("Debug View")); m_localsStackToolView = m_mainWin->createToolView(plugin, i18n("Locals and Stack"), KTextEditor::MainWindow::Right, QIcon(QStringLiteral(":/kategdb/22-actions-debug-kategdb.png")), i18n("Locals and Stack")); m_tabWidget = new QTabWidget(m_toolView); // Output m_outputArea = new QTextEdit(); m_outputArea->setAcceptRichText(false); m_outputArea->setReadOnly(true); m_outputArea->setUndoRedoEnabled(false); // fixed wide font, like konsole m_outputArea->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); // alternate color scheme, like konsole KColorScheme schemeView(QPalette::Active, KColorScheme::View); m_outputArea->setTextBackgroundColor(schemeView.foreground().color()); m_outputArea->setTextColor(schemeView.background().color()); QPalette p = m_outputArea->palette(); p.setColor(QPalette::Base, schemeView.foreground().color()); m_outputArea->setPalette(p); // input m_inputArea = new KHistoryComboBox(true); connect(m_inputArea, static_cast(&KHistoryComboBox::returnPressed), this, &KatePluginGDBView::slotSendCommand); QHBoxLayout *inputLayout = new QHBoxLayout(); inputLayout->addWidget(m_inputArea, 10); inputLayout->setContentsMargins(0, 0, 0, 0); m_outputArea->setFocusProxy(m_inputArea); // take the focus from the outputArea m_gdbPage = new QWidget(); QVBoxLayout *layout = new QVBoxLayout(m_gdbPage); layout->addWidget(m_outputArea); layout->addLayout(inputLayout); layout->setStretch(0, 10); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); // stack page QWidget *stackContainer = new QWidget(); QVBoxLayout *stackLayout = new QVBoxLayout(stackContainer); m_threadCombo = new QComboBox(); m_stackTree = new QTreeWidget(); stackLayout->addWidget(m_threadCombo); stackLayout->addWidget(m_stackTree); stackLayout->setStretch(0, 10); stackLayout->setContentsMargins(0, 0, 0, 0); stackLayout->setSpacing(0); QStringList headers; headers << QStringLiteral(" ") << i18nc("Column label (frame number)", "Nr") << i18nc("Column label", "Frame"); m_stackTree->setHeaderLabels(headers); m_stackTree->setRootIsDecorated(false); m_stackTree->resizeColumnToContents(0); m_stackTree->resizeColumnToContents(1); m_stackTree->setAutoScroll(false); connect(m_stackTree, &QTreeWidget::itemActivated, this, &KatePluginGDBView::stackFrameSelected); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) connect(m_threadCombo, static_cast(&QComboBox::currentIndexChanged), this, &KatePluginGDBView::threadSelected); -#else - connect(m_threadCombo, static_cast(&QComboBox::currentIndexChanged) , this, &KatePluginGDBView::threadSelected); -#endif m_localsView = new LocalsView(); QSplitter *locStackSplitter = new QSplitter(m_localsStackToolView); locStackSplitter->addWidget(m_localsView); locStackSplitter->addWidget(stackContainer); locStackSplitter->setOrientation(Qt::Vertical); // config page m_configView = new ConfigView(nullptr, mainWin); m_ioView = new IOView(); connect(m_configView, &ConfigView::showIO, this, &KatePluginGDBView::showIO); m_tabWidget->addTab(m_gdbPage, i18nc("Tab label", "GDB Output")); m_tabWidget->addTab(m_configView, i18nc("Tab label", "Settings")); m_debugView = new DebugView(this); connect(m_debugView, &DebugView::readyForInput, this, &KatePluginGDBView::enableDebugActions); connect(m_debugView, &DebugView::outputText, this, &KatePluginGDBView::addOutputText); connect(m_debugView, &DebugView::outputError, this, &KatePluginGDBView::addErrorText); connect(m_debugView, &DebugView::debugLocationChanged, this, &KatePluginGDBView::slotGoTo); connect(m_debugView, &DebugView::breakPointSet, this, &KatePluginGDBView::slotBreakpointSet); connect(m_debugView, &DebugView::breakPointCleared, this, &KatePluginGDBView::slotBreakpointCleared); connect(m_debugView, &DebugView::clearBreakpointMarks, this, &KatePluginGDBView::clearMarks); connect(m_debugView, &DebugView::programEnded, this, &KatePluginGDBView::programEnded); connect(m_debugView, &DebugView::gdbEnded, this, &KatePluginGDBView::programEnded); connect(m_debugView, &DebugView::gdbEnded, this, &KatePluginGDBView::gdbEnded); connect(m_debugView, &DebugView::stackFrameInfo, this, &KatePluginGDBView::insertStackFrame); connect(m_debugView, &DebugView::stackFrameChanged, this, &KatePluginGDBView::stackFrameChanged); connect(m_debugView, &DebugView::infoLocal, m_localsView, &LocalsView::addLocal); connect(m_debugView, &DebugView::threadInfo, this, &KatePluginGDBView::insertThread); connect(m_localsView, &LocalsView::localsVisible, m_debugView, &DebugView::slotQueryLocals); // Actions m_configView->registerActions(actionCollection()); QAction *a = actionCollection()->addAction(QStringLiteral("debug")); a->setText(i18n("Start Debugging")); a->setIcon(QIcon(QStringLiteral(":/kategdb/22-actions-debug-kategdb.png"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotDebug); a = actionCollection()->addAction(QStringLiteral("kill")); a->setText(i18n("Kill / Stop Debugging")); a->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-stop"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotKill); a = actionCollection()->addAction(QStringLiteral("rerun")); a->setText(i18n("Restart Debugging")); a->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotRestart); a = actionCollection()->addAction(QStringLiteral("toggle_breakpoint")); a->setText(i18n("Toggle Breakpoint / Break")); a->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotToggleBreakpoint); a = actionCollection()->addAction(QStringLiteral("step_in")); a->setText(i18n("Step In")); a->setIcon(QIcon::fromTheme(QStringLiteral("debug-step-into"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotStepInto); a = actionCollection()->addAction(QStringLiteral("step_over")); a->setText(i18n("Step Over")); a->setIcon(QIcon::fromTheme(QStringLiteral("debug-step-over"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotStepOver); a = actionCollection()->addAction(QStringLiteral("step_out")); a->setText(i18n("Step Out")); a->setIcon(QIcon::fromTheme(QStringLiteral("debug-step-out"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotStepOut); a = actionCollection()->addAction(QStringLiteral("move_pc")); a->setText(i18nc("Move Program Counter (next execution)", "Move PC")); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotMovePC); a = actionCollection()->addAction(QStringLiteral("run_to_cursor")); a->setText(i18n("Run To Cursor")); a->setIcon(QIcon::fromTheme(QStringLiteral("debug-run-cursor"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotRunToCursor); a = actionCollection()->addAction(QStringLiteral("continue")); a->setText(i18n("Continue")); a->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotContinue); a = actionCollection()->addAction(QStringLiteral("print_value")); a->setText(i18n("Print Value")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-preview"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotValue); // popup context m_menu m_menu = new KActionMenu(i18n("Debug"), this); actionCollection()->addAction(QStringLiteral("popup_gdb"), m_menu); connect(m_menu->menu(), &QMenu::aboutToShow, this, &KatePluginGDBView::aboutToShowMenu); m_breakpoint = m_menu->menu()->addAction(i18n("popup_breakpoint"), this, &KatePluginGDBView::slotToggleBreakpoint); QAction *popupAction = m_menu->menu()->addAction(i18n("popup_run_to_cursor"), this, &KatePluginGDBView::slotRunToCursor); popupAction->setText(i18n("Run To Cursor")); popupAction = m_menu->menu()->addAction(QStringLiteral("move_pc"), this, &KatePluginGDBView::slotMovePC); popupAction->setText(i18nc("Move Program Counter (next execution)", "Move PC")); enableDebugActions(false); connect(m_mainWin, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &KatePluginGDBView::handleEsc); m_toolView->installEventFilter(this); m_mainWin->guiFactory()->addClient(this); } KatePluginGDBView::~KatePluginGDBView() { m_mainWin->guiFactory()->removeClient(this); delete m_toolView; delete m_localsStackToolView; } void KatePluginGDBView::readSessionConfig(const KConfigGroup &config) { m_configView->readConfig(config); } void KatePluginGDBView::writeSessionConfig(KConfigGroup &config) { m_configView->writeConfig(config); } void KatePluginGDBView::slotDebug() { disconnect(m_ioView, &IOView::stdOutText, nullptr, nullptr); disconnect(m_ioView, &IOView::stdErrText, nullptr, nullptr); if (m_configView->showIOTab()) { connect(m_ioView, &IOView::stdOutText, m_ioView, &IOView::addStdOutText); connect(m_ioView, &IOView::stdErrText, m_ioView, &IOView::addStdErrText); } else { connect(m_ioView, &IOView::stdOutText, this, &KatePluginGDBView::addOutputText); connect(m_ioView, &IOView::stdErrText, this, &KatePluginGDBView::addErrorText); } QStringList ioFifos; ioFifos << m_ioView->stdinFifo(); ioFifos << m_ioView->stdoutFifo(); ioFifos << m_ioView->stderrFifo(); enableDebugActions(true); m_mainWin->showToolView(m_toolView); m_tabWidget->setCurrentWidget(m_gdbPage); QScrollBar *sb = m_outputArea->verticalScrollBar(); sb->setValue(sb->maximum()); m_localsView->clear(); m_debugView->runDebugger(m_configView->currentTarget(), ioFifos); } void KatePluginGDBView::slotRestart() { m_mainWin->showToolView(m_toolView); m_tabWidget->setCurrentWidget(m_gdbPage); QScrollBar *sb = m_outputArea->verticalScrollBar(); sb->setValue(sb->maximum()); m_localsView->clear(); m_debugView->slotReRun(); } void KatePluginGDBView::aboutToShowMenu() { if (!m_debugView->debuggerRunning() || m_debugView->debuggerBusy()) { m_breakpoint->setText(i18n("Insert breakpoint")); m_breakpoint->setDisabled(true); return; } m_breakpoint->setDisabled(false); KTextEditor::View *editView = m_mainWin->activeView(); QUrl url = editView->document()->url(); int line = editView->cursorPosition().line(); line++; // GDB uses 1 based line numbers, kate uses 0 based... if (m_debugView->hasBreakpoint(url, line)) { m_breakpoint->setText(i18n("Remove breakpoint")); } else { m_breakpoint->setText(i18n("Insert breakpoint")); } } void KatePluginGDBView::slotToggleBreakpoint() { if (!actionCollection()->action(QStringLiteral("continue"))->isEnabled()) { m_debugView->slotInterrupt(); } else { KTextEditor::View *editView = m_mainWin->activeView(); QUrl currURL = editView->document()->url(); int line = editView->cursorPosition().line(); m_debugView->toggleBreakpoint(currURL, line + 1); } } void KatePluginGDBView::slotBreakpointSet(const QUrl &file, int line) { #if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5,69,0) KTextEditor::MarkInterfaceV2 *iface = qobject_cast(m_kateApplication->findUrl(file)); #else KTextEditor::MarkInterface *iface = qobject_cast(m_kateApplication->findUrl(file)); #endif if (iface) { iface->setMarkDescription(KTextEditor::MarkInterface::BreakpointActive, i18n("Breakpoint")); #if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5,69,0) iface->setMarkIcon(KTextEditor::MarkInterface::BreakpointActive, QIcon::fromTheme(QStringLiteral("media-playback-pause"))); #else iface->setMarkPixmap(KTextEditor::MarkInterface::BreakpointActive, QIcon::fromTheme(QStringLiteral("media-playback-pause")).pixmap(10, 10)); #endif iface->addMark(line, KTextEditor::MarkInterface::BreakpointActive); } } void KatePluginGDBView::slotBreakpointCleared(const QUrl &file, int line) { KTextEditor::MarkInterface *iface = qobject_cast(m_kateApplication->findUrl(file)); if (iface) { iface->removeMark(line, KTextEditor::MarkInterface::BreakpointActive); } } void KatePluginGDBView::slotMovePC() { KTextEditor::View *editView = m_mainWin->activeView(); QUrl currURL = editView->document()->url(); KTextEditor::Cursor cursor = editView->cursorPosition(); m_debugView->movePC(currURL, cursor.line() + 1); } void KatePluginGDBView::slotRunToCursor() { KTextEditor::View *editView = m_mainWin->activeView(); QUrl currURL = editView->document()->url(); KTextEditor::Cursor cursor = editView->cursorPosition(); // GDB starts lines from 1, kate returns lines starting from 0 (displaying 1) m_debugView->runToCursor(currURL, cursor.line() + 1); } void KatePluginGDBView::slotGoTo(const QUrl &url, int lineNum) { // skip not existing files if (!QFile::exists(url.toLocalFile())) { m_lastExecLine = -1; return; } m_lastExecUrl = url; m_lastExecLine = lineNum; KTextEditor::View *editView = m_mainWin->openUrl(m_lastExecUrl); editView->setCursorPosition(KTextEditor::Cursor(m_lastExecLine, 0)); m_mainWin->window()->raise(); m_mainWin->window()->setFocus(); } void KatePluginGDBView::enableDebugActions(bool enable) { actionCollection()->action(QStringLiteral("step_in"))->setEnabled(enable); actionCollection()->action(QStringLiteral("step_over"))->setEnabled(enable); actionCollection()->action(QStringLiteral("step_out"))->setEnabled(enable); actionCollection()->action(QStringLiteral("move_pc"))->setEnabled(enable); actionCollection()->action(QStringLiteral("run_to_cursor"))->setEnabled(enable); actionCollection()->action(QStringLiteral("popup_gdb"))->setEnabled(enable); actionCollection()->action(QStringLiteral("continue"))->setEnabled(enable); actionCollection()->action(QStringLiteral("print_value"))->setEnabled(enable); // "toggle breakpoint" doubles as interrupt while the program is running actionCollection()->action(QStringLiteral("toggle_breakpoint"))->setEnabled(m_debugView->debuggerRunning()); actionCollection()->action(QStringLiteral("kill"))->setEnabled(m_debugView->debuggerRunning()); actionCollection()->action(QStringLiteral("rerun"))->setEnabled(m_debugView->debuggerRunning()); m_inputArea->setEnabled(enable); m_threadCombo->setEnabled(enable); m_stackTree->setEnabled(enable); m_localsView->setEnabled(enable); if (enable) { m_inputArea->setFocusPolicy(Qt::WheelFocus); if (m_focusOnInput || m_configView->takeFocusAlways()) { m_inputArea->setFocus(); m_focusOnInput = false; } else { m_mainWin->activeView()->setFocus(); } } else { m_inputArea->setFocusPolicy(Qt::NoFocus); if (m_mainWin->activeView()) m_mainWin->activeView()->setFocus(); } m_ioView->enableInput(!enable && m_debugView->debuggerRunning()); if ((m_lastExecLine > -1)) { #if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5,69,0) KTextEditor::MarkInterfaceV2 *iface = qobject_cast(m_kateApplication->findUrl(m_lastExecUrl)); #else KTextEditor::MarkInterface *iface = qobject_cast(m_kateApplication->findUrl(m_lastExecUrl)); #endif if (iface) { if (enable) { iface->setMarkDescription(KTextEditor::MarkInterface::Execution, i18n("Execution point")); #if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5,69,0) iface->setMarkIcon(KTextEditor::MarkInterface::Execution, QIcon::fromTheme(QStringLiteral("arrow-right"))); #else iface->setMarkPixmap(KTextEditor::MarkInterface::Execution, QIcon::fromTheme(QStringLiteral("arrow-right")).pixmap(10, 10)); #endif iface->addMark(m_lastExecLine, KTextEditor::MarkInterface::Execution); } else { iface->removeMark(m_lastExecLine, KTextEditor::MarkInterface::Execution); } } } } void KatePluginGDBView::programEnded() { // don't set the execution mark on exit m_lastExecLine = -1; m_stackTree->clear(); m_localsView->clear(); m_threadCombo->clear(); // Indicate the state change by showing the debug outputArea m_mainWin->showToolView(m_toolView); m_tabWidget->setCurrentWidget(m_gdbPage); } void KatePluginGDBView::gdbEnded() { m_outputArea->clear(); m_localsView->clear(); m_ioView->clearOutput(); clearMarks(); } void KatePluginGDBView::clearMarks() { KTextEditor::MarkInterface *iface; const auto documents = m_kateApplication->documents(); for (KTextEditor::Document *doc : documents) { iface = qobject_cast(doc); if (iface) { const QHash marks = iface->marks(); QHashIterator i(marks); while (i.hasNext()) { i.next(); if ((i.value()->type == KTextEditor::MarkInterface::Execution) || (i.value()->type == KTextEditor::MarkInterface::BreakpointActive)) { iface->removeMark(i.value()->line, i.value()->type); } } } } } void KatePluginGDBView::slotSendCommand() { QString cmd = m_inputArea->currentText(); if (cmd.isEmpty()) cmd = m_lastCommand; m_inputArea->addToHistory(cmd); m_inputArea->setCurrentItem(QString()); m_focusOnInput = true; m_lastCommand = cmd; m_debugView->issueCommand(cmd); QScrollBar *sb = m_outputArea->verticalScrollBar(); sb->setValue(sb->maximum()); } void KatePluginGDBView::insertStackFrame(QString const &level, QString const &info) { if (level.isEmpty() && info.isEmpty()) { m_stackTree->resizeColumnToContents(2); return; } if (level == QLatin1Char('0')) { m_stackTree->clear(); } QStringList columns; columns << QStringLiteral(" "); // icon place holder columns << level; int lastSpace = info.lastIndexOf(QLatin1Char(' ')); QString shortInfo = info.mid(lastSpace); columns << shortInfo; QTreeWidgetItem *item = new QTreeWidgetItem(columns); item->setToolTip(2, QStringLiteral("%1").arg(info)); m_stackTree->insertTopLevelItem(level.toInt(), item); } void KatePluginGDBView::stackFrameSelected() { m_debugView->issueCommand(QStringLiteral("(Q)f %1").arg(m_stackTree->currentIndex().row())); } void KatePluginGDBView::stackFrameChanged(int level) { QTreeWidgetItem *current = m_stackTree->topLevelItem(m_lastExecFrame); QTreeWidgetItem *next = m_stackTree->topLevelItem(level); if (current) current->setIcon(0, QIcon()); if (next) next->setIcon(0, QIcon::fromTheme(QStringLiteral("arrow-right"))); m_lastExecFrame = level; } void KatePluginGDBView::insertThread(int number, bool active) { if (number < 0) { m_threadCombo->clear(); m_activeThread = -1; return; } if (!active) { m_threadCombo->addItem(QIcon::fromTheme(QStringLiteral("")).pixmap(10, 10), i18n("Thread %1", number), number); } else { m_threadCombo->addItem(QIcon::fromTheme(QStringLiteral("arrow-right")).pixmap(10, 10), i18n("Thread %1", number), number); m_activeThread = m_threadCombo->count() - 1; } m_threadCombo->setCurrentIndex(m_activeThread); } void KatePluginGDBView::threadSelected(int thread) { m_debugView->issueCommand(QStringLiteral("thread %1").arg(m_threadCombo->itemData(thread).toInt())); } QString KatePluginGDBView::currentWord() { KTextEditor::View *kv = m_mainWin->activeView(); if (!kv) { qDebug() << "no KTextEditor::View"; return QString(); } if (!kv->cursorPosition().isValid()) { qDebug() << "cursor not valid!"; return QString(); } int line = kv->cursorPosition().line(); int col = kv->cursorPosition().column(); QString linestr = kv->document()->line(line); int startPos = qMax(qMin(col, linestr.length() - 1), 0); int lindex = linestr.length() - 1; int endPos = startPos; while (startPos >= 0 && (linestr[startPos].isLetterOrNumber() || linestr[startPos] == QLatin1Char('_') || linestr[startPos] == QLatin1Char('~') || ((startPos > 1) && (linestr[startPos] == QLatin1Char('.')) && !linestr[startPos - 1].isSpace()) || ((startPos > 2) && (linestr[startPos] == QLatin1Char('>')) && (linestr[startPos - 1] == QLatin1Char('-')) && !linestr[startPos - 2].isSpace()))) { if (linestr[startPos] == QLatin1Char('>')) { startPos--; } startPos--; } while (endPos < linestr.length() && (linestr[endPos].isLetterOrNumber() || linestr[endPos] == QLatin1Char('_') || ((endPos < lindex - 1) && (linestr[endPos] == QLatin1Char('.')) && !linestr[endPos + 1].isSpace()) || ((endPos < lindex - 2) && (linestr[endPos] == QLatin1Char('-')) && (linestr[endPos + 1] == QLatin1Char('>')) && !linestr[endPos + 2].isSpace()) || ((endPos > 1) && (linestr[endPos - 1] == QLatin1Char('-')) && (linestr[endPos] == QLatin1Char('>'))))) { if (linestr[endPos] == QLatin1Char('-')) { endPos++; } endPos++; } if (startPos == endPos) { qDebug() << "no word found!"; return QString(); } // qDebug() << linestr.mid(startPos+1, endPos-startPos-1); return linestr.mid(startPos + 1, endPos - startPos - 1); } void KatePluginGDBView::slotValue() { QString variable; KTextEditor::View *editView = m_mainWin->activeView(); if (editView && editView->selection() && editView->selectionRange().onSingleLine()) { variable = editView->selectionText(); } if (variable.isEmpty()) variable = currentWord(); if (variable.isEmpty()) return; QString cmd = QStringLiteral("print %1").arg(variable); m_debugView->issueCommand(cmd); m_inputArea->addToHistory(cmd); m_inputArea->setCurrentItem(QString()); m_mainWin->showToolView(m_toolView); m_tabWidget->setCurrentWidget(m_gdbPage); QScrollBar *sb = m_outputArea->verticalScrollBar(); sb->setValue(sb->maximum()); } void KatePluginGDBView::showIO(bool show) { if (show) { m_tabWidget->addTab(m_ioView, i18n("IO")); } else { m_tabWidget->removeTab(m_tabWidget->indexOf(m_ioView)); } } void KatePluginGDBView::addOutputText(QString const &text) { QScrollBar *scrollb = m_outputArea->verticalScrollBar(); if (!scrollb) return; bool atEnd = (scrollb->value() == scrollb->maximum()); QTextCursor cursor = m_outputArea->textCursor(); if (!cursor.atEnd()) cursor.movePosition(QTextCursor::End); cursor.insertText(text); if (atEnd) { scrollb->setValue(scrollb->maximum()); } } void KatePluginGDBView::addErrorText(QString const &text) { m_outputArea->setFontItalic(true); addOutputText(text); m_outputArea->setFontItalic(false); } bool KatePluginGDBView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); if ((obj == m_toolView) && (ke->key() == Qt::Key_Escape)) { m_mainWin->hideToolView(m_toolView); event->accept(); return true; } } return QObject::eventFilter(obj, event); } void KatePluginGDBView::handleEsc(QEvent *e) { if (!m_mainWin) return; QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { if (m_toolView->isVisible()) { m_mainWin->hideToolView(m_toolView); } } } #include "plugin_kategdb.moc" diff --git a/addons/katesql/katesqlview.cpp b/addons/katesql/katesqlview.cpp index 453796436..f36e5e340 100644 --- a/addons/katesql/katesqlview.cpp +++ b/addons/katesql/katesqlview.cpp @@ -1,365 +1,359 @@ /* 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 "katesqlview.h" #include "connectionmodel.h" #include "connectionwizard.h" #include "dataoutputmodel.h" #include "dataoutputview.h" #include "dataoutputwidget.h" #include "katesqlplugin.h" #include "outputwidget.h" #include "schemabrowserwidget.h" #include "schemawidget.h" #include "sqlmanager.h" #include "textoutputwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KateSQLView::KateSQLView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mw) : QObject(mw) , KXMLGUIClient() , m_manager(new SQLManager(this)) , m_mainWindow(mw) { KXMLGUIClient::setComponentName(QStringLiteral("katesql"), i18n("Kate SQL Plugin")); setXMLFile(QStringLiteral("ui.rc")); m_outputToolView = mw->createToolView(plugin, QStringLiteral("kate_private_plugin_katesql_output"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("view-form-table")), i18nc("@title:window", "SQL Results")); m_schemaBrowserToolView = mw->createToolView(plugin, QStringLiteral("kate_private_plugin_katesql_schemabrowser"), KTextEditor::MainWindow::Left, QIcon::fromTheme(QStringLiteral("view-list-tree")), i18nc("@title:window", "SQL Schema Browser")); m_outputWidget = new KateSQLOutputWidget(m_outputToolView); m_schemaBrowserWidget = new SchemaBrowserWidget(m_schemaBrowserToolView, m_manager); m_connectionsComboBox = new KComboBox(false); m_connectionsComboBox->setEditable(false); m_connectionsComboBox->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); m_connectionsComboBox->setModel(m_manager->connectionModel()); setupActions(); m_mainWindow->guiFactory()->addClient(this); QMenu *sqlMenu = static_cast(factory()->container(QStringLiteral("SQL"), this)); m_connectionsGroup = new QActionGroup(sqlMenu); m_connectionsGroup->setExclusive(true); connect(sqlMenu, &QMenu::aboutToShow, this, &KateSQLView::slotSQLMenuAboutToShow); connect(m_connectionsGroup, &QActionGroup::triggered, this, &KateSQLView::slotConnectionSelectedFromMenu); connect(m_manager, &SQLManager::error, this, &KateSQLView::slotError); connect(m_manager, &SQLManager::success, this, &KateSQLView::slotSuccess); connect(m_manager, &SQLManager::queryActivated, this, &KateSQLView::slotQueryActivated); connect(m_manager, &SQLManager::connectionCreated, this, &KateSQLView::slotConnectionCreated); connect(m_manager, &SQLManager::connectionAboutToBeClosed, this, &KateSQLView::slotConnectionAboutToBeClosed); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) connect(m_connectionsComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &KateSQLView::slotConnectionChanged); -#else - connect(m_connectionsComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index, const QString &connection) { - Q_UNUSED(index); - slotConnectionChanged(connection); - }); -#endif + stateChanged(QStringLiteral("has_connection_selected"), KXMLGUIClient::StateReverse); } KateSQLView::~KateSQLView() { m_mainWindow->guiFactory()->removeClient(this); delete m_outputToolView; delete m_schemaBrowserToolView; delete m_manager; } void KateSQLView::setupActions() { QAction *action; KActionCollection *collection = actionCollection(); action = collection->addAction(QStringLiteral("connection_create")); action->setText(i18nc("@action:inmenu", "Add connection...")); action->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); connect(action, &QAction::triggered, this, &KateSQLView::slotConnectionCreate); action = collection->addAction(QStringLiteral("connection_remove")); action->setText(i18nc("@action:inmenu", "Remove connection")); action->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); connect(action, &QAction::triggered, this, &KateSQLView::slotConnectionRemove); action = collection->addAction(QStringLiteral("connection_edit")); action->setText(i18nc("@action:inmenu", "Edit connection...")); action->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); connect(action, &QAction::triggered, this, &KateSQLView::slotConnectionEdit); action = collection->addAction(QStringLiteral("connection_reconnect")); action->setText(i18nc("@action:inmenu", "Reconnect")); action->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect(action, &QAction::triggered, this, &KateSQLView::slotConnectionReconnect); QWidgetAction *wa = new QWidgetAction(this); collection->addAction(QStringLiteral("connection_chooser"), wa); wa->setText(i18nc("@action:intoolbar", "Connection")); wa->setDefaultWidget(m_connectionsComboBox); action = collection->addAction(QStringLiteral("query_run")); action->setText(i18nc("@action:inmenu", "Run query")); action->setIcon(QIcon::fromTheme(QStringLiteral("quickopen"))); collection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_E)); connect(action, &QAction::triggered, this, &KateSQLView::slotRunQuery); /// TODO: stop sql query // action = collection->addAction("sql_stop"); // action->setText( i18n("Stop query") ); // action->setIcon( KIcon("process-stop") ); // action->setShortcut( QKeySequence(Qt::ALT + Qt::Key_F5) ); // connect( action , SIGNAL(triggered()) , this , SLOT(stopQuery())); } void KateSQLView::slotSQLMenuAboutToShow() { qDeleteAll(m_connectionsGroup->actions()); QMenu *sqlMenu = static_cast(factory()->container(QStringLiteral("SQL"), this)); QAction *before = action("query_run"); QAbstractItemModel *model = m_manager->connectionModel(); int rows = model->rowCount(QModelIndex()); for (int row = 0; row < rows; row++) { QModelIndex index = model->index(row, 0, QModelIndex()); Q_ASSERT(index.isValid()); QString connectionName = index.data(Qt::DisplayRole).toString(); QAction *act = new QAction(connectionName, m_connectionsGroup); act->setCheckable(true); if (m_connectionsComboBox->currentText() == connectionName) act->setChecked(true); sqlMenu->insertAction(before, act); } sqlMenu->insertSeparator(before); } void KateSQLView::slotConnectionSelectedFromMenu(QAction *action) { m_connectionsComboBox->setCurrentItem(action->text()); } void KateSQLView::slotConnectionChanged(const QString &connection) { stateChanged(QStringLiteral("has_connection_selected"), (connection.isEmpty()) ? KXMLGUIClient::StateReverse : KXMLGUIClient::StateNoReverse); m_schemaBrowserWidget->schemaWidget()->buildTree(connection); } void KateSQLView::slotGlobalSettingsChanged() { m_outputWidget->dataOutputWidget()->model()->readConfig(); } void KateSQLView::readSessionConfig(KConfigBase *config, const QString &groupPrefix) { KConfigGroup globalConfig(KSharedConfig::openConfig(), "KateSQLPlugin"); bool saveConnections = globalConfig.readEntry("SaveConnections", true); if (!saveConnections) return; KConfigGroup group(config, groupPrefix + QLatin1String(":connections")); m_manager->loadConnections(&group); QString lastConnection = group.readEntry("LastUsed"); if (m_connectionsComboBox->contains(lastConnection)) m_connectionsComboBox->setCurrentItem(lastConnection); } void KateSQLView::writeSessionConfig(KConfigBase *config, const QString &groupPrefix) { KConfigGroup group(config, groupPrefix + QLatin1String(":connections")); group.deleteGroup(); KConfigGroup globalConfig(KSharedConfig::openConfig(), "KateSQLPlugin"); bool saveConnections = globalConfig.readEntry("SaveConnections", true); if (saveConnections) { m_manager->saveConnections(&group); group.writeEntry("LastUsed", m_connectionsComboBox->currentText()); } config->sync(); } void KateSQLView::slotConnectionCreate() { Connection c; ConnectionWizard wizard(m_manager, &c); if (wizard.exec() != QDialog::Accepted) return; for (int i = 1; QSqlDatabase::contains(c.name); i++) c.name = QStringLiteral("%1 (%2)").arg(c.name).arg(i); m_manager->createConnection(c); if (m_manager->storeCredentials(c) != 0) qDebug() << "Connection credentials not saved"; } void KateSQLView::slotConnectionEdit() { int i = m_connectionsComboBox->currentIndex(); if (i == -1) return; ConnectionModel *model = m_manager->connectionModel(); Connection c = model->data(model->index(i), Qt::UserRole).value(); QString previousName = c.name; ConnectionWizard wizard(m_manager, &c); if (wizard.exec() != QDialog::Accepted) return; m_manager->removeConnection(previousName); m_manager->createConnection(c); if (m_manager->storeCredentials(c) != 0) qDebug() << "Connection credentials not saved"; } void KateSQLView::slotConnectionRemove() { QString connection = m_connectionsComboBox->currentText(); if (!connection.isEmpty()) m_manager->removeConnection(connection); } void KateSQLView::slotConnectionReconnect() { QString connection = m_connectionsComboBox->currentText(); if (!connection.isEmpty()) m_manager->reopenConnection(connection); } void KateSQLView::slotConnectionAboutToBeClosed(const QString &name) { /// must delete the QSqlQuery object inside the model before closing connection if (name == m_currentResultsetConnection) m_outputWidget->dataOutputWidget()->clearResults(); } void KateSQLView::slotRunQuery() { /// TODO: /// bind parameters dialog? QString connection = m_connectionsComboBox->currentText(); if (connection.isEmpty()) { slotConnectionCreate(); return; } KTextEditor::View *view = m_mainWindow->activeView(); if (!view) return; QString text = (view->selection()) ? view->selectionText() : view->document()->text(); text = text.trimmed(); if (text.isEmpty()) return; m_manager->runQuery(text, connection); } void KateSQLView::slotError(const QString &message) { m_outputWidget->textOutputWidget()->showErrorMessage(message); m_outputWidget->setCurrentWidget(m_outputWidget->textOutputWidget()); m_mainWindow->showToolView(m_outputToolView); } void KateSQLView::slotSuccess(const QString &message) { m_outputWidget->textOutputWidget()->showSuccessMessage(message); m_outputWidget->setCurrentWidget(m_outputWidget->textOutputWidget()); m_mainWindow->showToolView(m_outputToolView); } void KateSQLView::slotQueryActivated(QSqlQuery &query, const QString &connection) { if (query.isSelect()) { m_currentResultsetConnection = connection; m_outputWidget->dataOutputWidget()->showQueryResultSets(query); m_outputWidget->setCurrentWidget(m_outputWidget->dataOutputWidget()); m_mainWindow->showToolView(m_outputToolView); } } void KateSQLView::slotConnectionCreated(const QString &name) { m_connectionsComboBox->setCurrentItem(name); m_schemaBrowserWidget->schemaWidget()->buildTree(name); } // END KateSQLView diff --git a/addons/project/kateprojectinfoviewcodeanalysis.cpp b/addons/project/kateprojectinfoviewcodeanalysis.cpp index e1727d03a..f5d57b802 100644 --- a/addons/project/kateprojectinfoviewcodeanalysis.cpp +++ b/addons/project/kateprojectinfoviewcodeanalysis.cpp @@ -1,253 +1,249 @@ /* This file is part of the Kate project. * * 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 "kateprojectinfoviewcodeanalysis.h" #include "kateprojectcodeanalysistool.h" #include "kateprojectpluginview.h" #include "tools/kateprojectcodeanalysisselector.h" #include #include #include #include #include #include KateProjectInfoViewCodeAnalysis::KateProjectInfoViewCodeAnalysis(KateProjectPluginView *pluginView, KateProject *project) : QWidget() , m_pluginView(pluginView) , m_project(project) , m_messageWidget(nullptr) , m_startStopAnalysis(new QPushButton(i18n("Start Analysis..."))) , m_treeView(new QTreeView(this)) , m_model(new QStandardItemModel(m_treeView)) , m_analyzer(nullptr) , m_analysisTool(nullptr) , m_toolSelector(new QComboBox()) { /** * default style */ m_treeView->setEditTriggers(QAbstractItemView::NoEditTriggers); m_treeView->setUniformRowHeights(true); m_treeView->setRootIsDecorated(false); m_model->setHorizontalHeaderLabels(QStringList() << i18n("File") << i18n("Line") << i18n("Severity") << i18n("Message")); /** * attach model * kill selection model */ QItemSelectionModel *m = m_treeView->selectionModel(); m_treeView->setModel(m_model); delete m; m_treeView->setSortingEnabled(true); m_treeView->sortByColumn(1, Qt::AscendingOrder); m_treeView->sortByColumn(2, Qt::AscendingOrder); /** * Connect selection change callback * and attach model to code analysis selector */ -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) connect(m_toolSelector, static_cast(&QComboBox::currentIndexChanged), this, &KateProjectInfoViewCodeAnalysis::slotToolSelectionChanged); -#else - connect(m_toolSelector, static_cast(&QComboBox::currentIndexChanged) , this, &KateProjectInfoViewCodeAnalysis::slotToolSelectionChanged); -#endif m_toolSelector->setModel(KateProjectCodeAnalysisSelector::model(this)); /** * layout widget */ QVBoxLayout *layout = new QVBoxLayout; layout->setSpacing(0); // top: selector and buttons... QHBoxLayout *hlayout = new QHBoxLayout; layout->addLayout(hlayout); hlayout->setSpacing(0); hlayout->addWidget(m_toolSelector); auto infoButton = new QPushButton(QIcon::fromTheme(QStringLiteral("documentinfo")), QString(), this); infoButton->setFocusPolicy(Qt::FocusPolicy::TabFocus); connect(infoButton, &QPushButton::clicked, this, [this]() { QToolTip::showText(QCursor::pos(), m_toolInfoText); }); hlayout->addWidget(infoButton); hlayout->addWidget(m_startStopAnalysis); hlayout->addStretch(); // below: result list... layout->addWidget(m_treeView); setLayout(layout); /** * connect needed signals */ connect(m_startStopAnalysis, &QPushButton::clicked, this, &KateProjectInfoViewCodeAnalysis::slotStartStopClicked); connect(m_treeView, &QTreeView::clicked, this, &KateProjectInfoViewCodeAnalysis::slotClicked); } KateProjectInfoViewCodeAnalysis::~KateProjectInfoViewCodeAnalysis() { delete m_analyzer; } void KateProjectInfoViewCodeAnalysis::slotToolSelectionChanged(int) { m_analysisTool = m_toolSelector->currentData(Qt::UserRole + 1).value(); m_toolInfoText = i18n("%1

The tool will be run on all project files which match this list of file extensions:

%2", m_analysisTool->description(), m_analysisTool->fileExtensions()); } void KateProjectInfoViewCodeAnalysis::slotStartStopClicked() { /** * get files for the external tool */ m_analysisTool = m_toolSelector->currentData(Qt::UserRole + 1).value(); m_analysisTool->setProject(m_project); /** * clear existing entries */ m_model->removeRows(0, m_model->rowCount(), QModelIndex()); /** * launch selected tool */ delete m_analyzer; m_analyzer = new QProcess; m_analyzer->setProcessChannelMode(QProcess::MergedChannels); connect(m_analyzer, &QProcess::readyRead, this, &KateProjectInfoViewCodeAnalysis::slotReadyRead); connect(m_analyzer, static_cast(&QProcess::finished), this, &KateProjectInfoViewCodeAnalysis::finished); m_analyzer->start(m_analysisTool->path(), m_analysisTool->arguments()); if (m_messageWidget) { delete m_messageWidget; m_messageWidget = nullptr; } if (!m_analyzer->waitForStarted()) { m_messageWidget = new KMessageWidget(this); m_messageWidget->setCloseButtonVisible(true); m_messageWidget->setMessageType(KMessageWidget::Warning); m_messageWidget->setWordWrap(false); m_messageWidget->setText(m_analysisTool->notInstalledMessage()); static_cast(layout())->addWidget(m_messageWidget); m_messageWidget->animatedShow(); return; } m_startStopAnalysis->setEnabled(false); /** * write files list and close write channel */ const QString stdinMessage = m_analysisTool->stdinMessages(); if (!stdinMessage.isEmpty()) { m_analyzer->write(stdinMessage.toLocal8Bit()); } m_analyzer->closeWriteChannel(); } void KateProjectInfoViewCodeAnalysis::slotReadyRead() { /** * get results of analysis */ while (m_analyzer->canReadLine()) { /** * get one line, split it, skip it, if too few elements */ QString line = QString::fromLocal8Bit(m_analyzer->readLine()); QStringList elements = m_analysisTool->parseLine(line); if (elements.size() < 4) { continue; } /** * feed into model */ QList items; QStandardItem *fileNameItem = new QStandardItem(QFileInfo(elements[0]).fileName()); fileNameItem->setToolTip(elements[0]); items << fileNameItem; items << new QStandardItem(elements[1]); items << new QStandardItem(elements[2]); const auto message = elements[3].simplified(); auto messageItem = new QStandardItem(message); messageItem->setToolTip(message); items << messageItem; m_model->appendRow(items); } /** * tree view polish ;) */ m_treeView->resizeColumnToContents(2); m_treeView->resizeColumnToContents(1); m_treeView->resizeColumnToContents(0); } void KateProjectInfoViewCodeAnalysis::slotClicked(const QModelIndex &index) { /** * get path */ QString filePath = m_model->item(index.row(), 0)->toolTip(); if (filePath.isEmpty()) { return; } /** * create view */ KTextEditor::View *view = m_pluginView->mainWindow()->openUrl(QUrl::fromLocalFile(filePath)); if (!view) { return; } /** * set cursor, if possible */ int line = m_model->item(index.row(), 1)->text().toInt(); if (line >= 1) { view->setCursorPosition(KTextEditor::Cursor(line - 1, 0)); } } void KateProjectInfoViewCodeAnalysis::finished(int exitCode, QProcess::ExitStatus) { m_startStopAnalysis->setEnabled(true); m_messageWidget = new KMessageWidget(this); m_messageWidget->setCloseButtonVisible(true); m_messageWidget->setWordWrap(false); if (m_analysisTool->isSuccessfulExitCode(exitCode)) { // normally 0 is successful but there are exceptions m_messageWidget->setMessageType(KMessageWidget::Information); m_messageWidget->setText(i18np("Analysis on %1 file finished.", "Analysis on %1 files finished.", m_analysisTool->getActualFilesCount())); } else { // unfortunately, output was eaten by slotReadyRead() // TODO: get stderr output, show it here m_messageWidget->setMessageType(KMessageWidget::Warning); m_messageWidget->setText(i18np("Analysis on %1 file failed with exit code %2.", "Analysis on %1 files failed with exit code %2.", m_analysisTool->getActualFilesCount(), exitCode)); } static_cast(layout())->addWidget(m_messageWidget); m_messageWidget->animatedShow(); } diff --git a/addons/project/kateprojectpluginview.cpp b/addons/project/kateprojectpluginview.cpp index 85f960734..5dcd65ecf 100644 --- a/addons/project/kateprojectpluginview.cpp +++ b/addons/project/kateprojectpluginview.cpp @@ -1,526 +1,522 @@ /* 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 "fileutil.h" #include "kateprojectinfoviewindex.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_toolMultiView(nullptr) , m_lookupAction(nullptr) , m_gotoSymbolAction(nullptr) , m_gotoSymbolActionAppMenu(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); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) connect(m_projectsCombo, static_cast(&QComboBox::currentIndexChanged), this, &KateProjectPluginView::slotCurrentChanged); -#else - connect(m_projectsCombo, static_cast(&QComboBox::currentIndexChanged) , this, &KateProjectPluginView::slotCurrentChanged); -#endif connect(m_reloadButton, &QToolButton::clicked, this, &KateProjectPluginView::slotProjectReload); /** * create views for all already existing projects * will create toolviews on demand! */ 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_plugin, &KateProjectPlugin::configUpdated, this, &KateProjectPluginView::slotConfigUpdated); connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &KateProjectPluginView::slotViewChanged); connect(m_mainWindow, &KTextEditor::MainWindow::viewCreated, this, &KateProjectPluginView::slotViewCreated); /** * connect for all already existing 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(QStringLiteral("projects_goto_index"), this, SLOT(slotProjectIndex())); a->setText(i18n("Lookup")); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::Key_1)); m_gotoSymbolActionAppMenu = a = actionCollection()->addAction(KStandardAction::Goto, QStringLiteral("projects_goto_symbol"), this, SLOT(slotGotoSymbol())); // 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); m_gotoSymbolAction = popup->menu()->addAction(i18n("Goto: %1", QString()), this, &KateProjectPluginView::slotGotoSymbol); connect(popup->menu(), &QMenu::aboutToShow, this, &KateProjectPluginView::slotContextMenuAboutToShow); /** * add us to gui */ m_mainWindow->guiFactory()->addClient(this); /** * align to current config */ slotConfigUpdated(); } 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; delete m_toolMultiView; m_toolMultiView = nullptr; /** * cu gui client */ m_mainWindow->guiFactory()->removeClient(this); } void KateProjectPluginView::slotConfigUpdated() { if (!m_plugin->multiProjectGoto()) { delete m_toolMultiView; m_toolMultiView = nullptr; } else if (!m_toolMultiView) { m_toolMultiView = m_mainWindow->createToolView(m_plugin, QStringLiteral("kateprojectmulti"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("view-choose")), i18n("Projects Index")); auto gotoindex = new KateProjectInfoViewIndex(this, nullptr, m_toolMultiView); m_toolMultiView->layout()->addWidget(gotoindex); } // update action state m_gotoSymbolActionAppMenu->setEnabled(m_toolMultiView); m_gotoSymbolAction->setEnabled(m_toolMultiView); } 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; 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::slotGotoSymbol() { if (!m_toolMultiView) { return; } const QString word = currentWord(); if (!word.isEmpty()) { int results = 0; emit gotoSymbol(word, results); if (results > 1) { m_mainWindow->showToolView(m_toolMultiView); } } } 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)); m_gotoSymbolAction->setText(i18n("Goto: %1", squeezed)); } #include "kateprojectpluginview.moc" diff --git a/addons/search/plugin_search.cpp b/addons/search/plugin_search.cpp index 862c0a836..89dfb9048 100644 --- a/addons/search/plugin_search.cpp +++ b/addons/search/plugin_search.cpp @@ -1,2345 +1,2337 @@ /* Kate search plugin * * Copyright (C) 2011-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 "plugin_search.h" #include "htmldelegate.h" #include #include #include #include #include #include #include #include #include #include "kacceleratormanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static QUrl localFileDirUp(const QUrl &url) { if (!url.isLocalFile()) return url; // else go up return QUrl::fromLocalFile(QFileInfo(url.toLocalFile()).dir().absolutePath()); } static QAction *menuEntry(QMenu *menu, const QString &before, const QString &after, const QString &desc, QString menuBefore = QString(), QString menuAfter = QString()); /** * When the action is triggered the cursor will be placed between @p before and @p after. */ static QAction *menuEntry(QMenu *menu, const QString &before, const QString &after, const QString &desc, QString menuBefore, QString menuAfter) { if (menuBefore.isEmpty()) menuBefore = before; if (menuAfter.isEmpty()) menuAfter = after; QAction *const action = menu->addAction(menuBefore + menuAfter + QLatin1Char('\t') + desc); if (!action) return nullptr; action->setData(QString(before + QLatin1Char(' ') + after)); return action; } /** * adds items and separators for special chars in "replace" field */ static void addSpecialCharsHelperActionsForReplace(QSet *actionList, QMenu *menu) { QSet &actionPointers = *actionList; QString emptyQSTring; actionPointers << menuEntry(menu, QStringLiteral("\\n"), emptyQSTring, i18n("Line break")); actionPointers << menuEntry(menu, QStringLiteral("\\t"), emptyQSTring, i18n("Tab")); } /** * adds items and separators for regex in "search" field */ static void addRegexHelperActionsForSearch(QSet *actionList, QMenu *menu) { QSet &actionPointers = *actionList; QString emptyQSTring; actionPointers << menuEntry(menu, QStringLiteral("^"), emptyQSTring, i18n("Beginning of line")); actionPointers << menuEntry(menu, QStringLiteral("$"), emptyQSTring, i18n("End of line")); menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("."), emptyQSTring, i18n("Any single character (excluding line breaks)")); actionPointers << menuEntry(menu, QStringLiteral("[.]"), emptyQSTring, i18n("Literal dot")); menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("+"), emptyQSTring, i18n("One or more occurrences")); actionPointers << menuEntry(menu, QStringLiteral("*"), emptyQSTring, i18n("Zero or more occurrences")); actionPointers << menuEntry(menu, QStringLiteral("?"), emptyQSTring, i18n("Zero or one occurrences")); actionPointers << menuEntry(menu, QStringLiteral("{"), QStringLiteral(",}"), i18n(" through occurrences"), QStringLiteral("{a"), QStringLiteral(",b}")); menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("("), QStringLiteral(")"), i18n("Group, capturing")); actionPointers << menuEntry(menu, QStringLiteral("|"), emptyQSTring, i18n("Or")); actionPointers << menuEntry(menu, QStringLiteral("["), QStringLiteral("]"), i18n("Set of characters")); actionPointers << menuEntry(menu, QStringLiteral("[^"), QStringLiteral("]"), i18n("Negative set of characters")); actionPointers << menuEntry(menu, QStringLiteral("(?:"), QStringLiteral(")"), i18n("Group, non-capturing"), QStringLiteral("(?:E")); actionPointers << menuEntry(menu, QStringLiteral("(?="), QStringLiteral(")"), i18n("Lookahead"), QStringLiteral("(?=E")); actionPointers << menuEntry(menu, QStringLiteral("(?!"), QStringLiteral(")"), i18n("Negative lookahead"), QStringLiteral("(?!E")); menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("\\n"), emptyQSTring, i18n("Line break")); actionPointers << menuEntry(menu, QStringLiteral("\\t"), emptyQSTring, i18n("Tab")); actionPointers << menuEntry(menu, QStringLiteral("\\b"), emptyQSTring, i18n("Word boundary")); actionPointers << menuEntry(menu, QStringLiteral("\\B"), emptyQSTring, i18n("Not word boundary")); actionPointers << menuEntry(menu, QStringLiteral("\\d"), emptyQSTring, i18n("Digit")); actionPointers << menuEntry(menu, QStringLiteral("\\D"), emptyQSTring, i18n("Non-digit")); actionPointers << menuEntry(menu, QStringLiteral("\\s"), emptyQSTring, i18n("Whitespace (excluding line breaks)")); actionPointers << menuEntry(menu, QStringLiteral("\\S"), emptyQSTring, i18n("Non-whitespace (excluding line breaks)")); actionPointers << menuEntry(menu, QStringLiteral("\\w"), emptyQSTring, i18n("Word character (alphanumerics plus '_')")); actionPointers << menuEntry(menu, QStringLiteral("\\W"), emptyQSTring, i18n("Non-word character")); } /** * adds items and separators for regex in "replace" field */ static void addRegexHelperActionsForReplace(QSet *actionList, QMenu *menu) { QSet &actionPointers = *actionList; QString emptyQSTring; menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("\\0"), emptyQSTring, i18n("Regular expression capture 0 (whole match)")); actionPointers << menuEntry(menu, QStringLiteral("\\"), emptyQSTring, i18n("Regular expression capture 1-9"), QStringLiteral("\\#")); actionPointers << menuEntry(menu, QStringLiteral("\\{"), QStringLiteral("}"), i18n("Regular expression capture 0-999"), QStringLiteral("\\{#")); menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("\\U\\"), emptyQSTring, i18n("Upper-cased capture 0-9"), QStringLiteral("\\U\\#")); actionPointers << menuEntry(menu, QStringLiteral("\\U\\{"), QStringLiteral("}"), i18n("Upper-cased capture 0-999"), QStringLiteral("\\U\\{###")); actionPointers << menuEntry(menu, QStringLiteral("\\L\\"), emptyQSTring, i18n("Lower-cased capture 0-9"), QStringLiteral("\\L\\#")); actionPointers << menuEntry(menu, QStringLiteral("\\L\\{"), QStringLiteral("}"), i18n("Lower-cased capture 0-999"), QStringLiteral("\\L\\{###")); } /** * inserts text and sets cursor position */ static void regexHelperActOnAction(QAction *resultAction, const QSet &actionList, QLineEdit *lineEdit) { if (resultAction && actionList.contains(resultAction)) { const int cursorPos = lineEdit->cursorPosition(); QStringList beforeAfter = resultAction->data().toString().split(QLatin1Char(' ')); if (beforeAfter.size() != 2) return; lineEdit->insert(beforeAfter[0] + beforeAfter[1]); lineEdit->setCursorPosition(cursorPos + beforeAfter[0].count()); lineEdit->setFocus(); } } class TreeWidgetItem : public QTreeWidgetItem { public: TreeWidgetItem(QTreeWidget *parent) : QTreeWidgetItem(parent) { } TreeWidgetItem(QTreeWidget *parent, const QStringList &list) : QTreeWidgetItem(parent, list) { } TreeWidgetItem(QTreeWidgetItem *parent, const QStringList &list) : QTreeWidgetItem(parent, list) { } private: bool operator<(const QTreeWidgetItem &other) const override { if (childCount() == 0) { int line = data(0, ReplaceMatches::StartLineRole).toInt(); int column = data(0, ReplaceMatches::StartColumnRole).toInt(); int oLine = other.data(0, ReplaceMatches::StartLineRole).toInt(); int oColumn = other.data(0, ReplaceMatches::StartColumnRole).toInt(); if (line < oLine) { return true; } if ((line == oLine) && (column < oColumn)) { return true; } return false; } int sepCount = data(0, ReplaceMatches::FileUrlRole).toString().count(QDir::separator()); int oSepCount = other.data(0, ReplaceMatches::FileUrlRole).toString().count(QDir::separator()); if (sepCount < oSepCount) return true; if (sepCount > oSepCount) return false; return data(0, ReplaceMatches::FileUrlRole).toString().toLower() < other.data(0, ReplaceMatches::FileUrlRole).toString().toLower(); } }; Results::Results(QWidget *parent) : QWidget(parent) { setupUi(this); tree->setItemDelegate(new SPHtmlDelegate(tree)); } K_PLUGIN_FACTORY_WITH_JSON(KatePluginSearchFactory, "katesearch.json", registerPlugin();) KatePluginSearch::KatePluginSearch(QObject *parent, const QList &) : KTextEditor::Plugin(parent) { m_searchCommand = new KateSearchCommand(this); } KatePluginSearch::~KatePluginSearch() { delete m_searchCommand; } QObject *KatePluginSearch::createView(KTextEditor::MainWindow *mainWindow) { KatePluginSearchView *view = new KatePluginSearchView(this, mainWindow, KTextEditor::Editor::instance()->application()); connect(m_searchCommand, &KateSearchCommand::setSearchPlace, view, &KatePluginSearchView::setSearchPlace); connect(m_searchCommand, &KateSearchCommand::setCurrentFolder, view, &KatePluginSearchView::setCurrentFolder); connect(m_searchCommand, &KateSearchCommand::setSearchString, view, &KatePluginSearchView::setSearchString); connect(m_searchCommand, &KateSearchCommand::startSearch, view, &KatePluginSearchView::startSearch); connect(m_searchCommand, SIGNAL(newTab()), view, SLOT(addTab())); return view; } bool ContainerWidget::focusNextPrevChild(bool next) { QWidget *fw = focusWidget(); bool found = false; emit nextFocus(fw, &found, next); if (found) { return true; } return QWidget::focusNextPrevChild(next); } void KatePluginSearchView::nextFocus(QWidget *currentWidget, bool *found, bool next) { *found = false; if (!currentWidget) { return; } // we use the object names here because there can be multiple replaceButtons (on multiple result tabs) if (next) { if (currentWidget->objectName() == QLatin1String("tree") || currentWidget == m_ui.binaryCheckBox) { m_ui.newTabButton->setFocus(); *found = true; return; } if (currentWidget == m_ui.displayOptions) { if (m_ui.displayOptions->isChecked()) { m_ui.folderRequester->setFocus(); *found = true; return; } else { Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!res) { return; } res->tree->setFocus(); *found = true; return; } } } else { if (currentWidget == m_ui.newTabButton) { if (m_ui.displayOptions->isChecked()) { m_ui.binaryCheckBox->setFocus(); } else { Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!res) { return; } res->tree->setFocus(); } *found = true; return; } else { if (currentWidget->objectName() == QLatin1String("tree")) { m_ui.displayOptions->setFocus(); *found = true; return; } } } } KatePluginSearchView::KatePluginSearchView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWin, KTextEditor::Application *application) : QObject(mainWin) , m_kateApp(application) , m_curResults(nullptr) , m_searchJustOpened(false) , m_projectSearchPlaceIndex(0) , m_searchDiskFilesDone(true) , m_searchOpenFilesDone(true) , m_isSearchAsYouType(false) , m_isLeftRight(false) , m_projectPluginView(nullptr) , m_mainWindow(mainWin) { KXMLGUIClient::setComponentName(QStringLiteral("katesearch"), i18n("Kate Search & Replace")); setXMLFile(QStringLiteral("ui.rc")); m_toolView = mainWin->createToolView(plugin, QStringLiteral("kate_plugin_katesearch"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("edit-find")), i18n("Search and Replace")); ContainerWidget *container = new ContainerWidget(m_toolView); m_ui.setupUi(container); container->setFocusProxy(m_ui.searchCombo); connect(container, &ContainerWidget::nextFocus, this, &KatePluginSearchView::nextFocus); QAction *a = actionCollection()->addAction(QStringLiteral("search_in_files")); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_F)); a->setText(i18n("Search in Files")); connect(a, &QAction::triggered, this, &KatePluginSearchView::openSearchView); a = actionCollection()->addAction(QStringLiteral("search_in_files_new_tab")); a->setText(i18n("Search in Files (in new tab)")); // first add tab, then open search view, since open search view switches to show the search options connect(a, &QAction::triggered, this, &KatePluginSearchView::addTab); connect(a, &QAction::triggered, this, &KatePluginSearchView::openSearchView); a = actionCollection()->addAction(QStringLiteral("go_to_next_match")); a->setText(i18n("Go to Next Match")); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::Key_F6)); connect(a, &QAction::triggered, this, &KatePluginSearchView::goToNextMatch); a = actionCollection()->addAction(QStringLiteral("go_to_prev_match")); a->setText(i18n("Go to Previous Match")); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_F6)); connect(a, &QAction::triggered, this, &KatePluginSearchView::goToPreviousMatch); m_ui.resultTabWidget->tabBar()->setSelectionBehaviorOnRemove(QTabBar::SelectLeftTab); KAcceleratorManager::setNoAccel(m_ui.resultTabWidget); // Gnome does not seem to have all icons we want, so we use fall-back icons for those that are missing. QIcon dispOptIcon = QIcon::fromTheme(QStringLiteral("games-config-options"), QIcon::fromTheme(QStringLiteral("preferences-system"))); QIcon matchCaseIcon = QIcon::fromTheme(QStringLiteral("format-text-superscript"), QIcon::fromTheme(QStringLiteral("format-text-bold"))); QIcon useRegExpIcon = QIcon::fromTheme(QStringLiteral("code-context"), QIcon::fromTheme(QStringLiteral("edit-find-replace"))); QIcon expandResultsIcon = QIcon::fromTheme(QStringLiteral("view-list-tree"), QIcon::fromTheme(QStringLiteral("format-indent-more"))); m_ui.displayOptions->setIcon(dispOptIcon); m_ui.searchButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); m_ui.nextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search"))); m_ui.stopButton->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); m_ui.matchCase->setIcon(matchCaseIcon); m_ui.useRegExp->setIcon(useRegExpIcon); m_ui.expandResults->setIcon(expandResultsIcon); m_ui.searchPlaceCombo->setItemIcon(CurrentFile, QIcon::fromTheme(QStringLiteral("text-plain"))); m_ui.searchPlaceCombo->setItemIcon(OpenFiles, QIcon::fromTheme(QStringLiteral("text-plain"))); m_ui.searchPlaceCombo->setItemIcon(Folder, QIcon::fromTheme(QStringLiteral("folder"))); m_ui.folderUpButton->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); m_ui.currentFolderButton->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); m_ui.newTabButton->setIcon(QIcon::fromTheme(QStringLiteral("tab-new"))); m_ui.filterCombo->setToolTip(i18n("Comma separated list of file types to search in. Example: \"*.cpp,*.h\"\n")); m_ui.excludeCombo->setToolTip(i18n("Comma separated list of files and directories to exclude from the search. Example: \"build*\"")); // the order here is important to get the tabBar hidden for only one tab addTab(); m_ui.resultTabWidget->tabBar()->hide(); // get url-requester's combo box and sanely initialize KComboBox *cmbUrl = m_ui.folderRequester->comboBox(); cmbUrl->setDuplicatesEnabled(false); cmbUrl->setEditable(true); m_ui.folderRequester->setMode(KFile::Directory | KFile::LocalOnly); KUrlCompletion *cmpl = new KUrlCompletion(KUrlCompletion::DirCompletion); cmbUrl->setCompletionObject(cmpl); cmbUrl->setAutoDeleteCompletionObject(true); connect(m_ui.newTabButton, &QToolButton::clicked, this, &KatePluginSearchView::addTab); connect(m_ui.resultTabWidget, &QTabWidget::tabCloseRequested, this, &KatePluginSearchView::tabCloseRequested); connect(m_ui.resultTabWidget, &QTabWidget::currentChanged, this, &KatePluginSearchView::resultTabChanged); connect(m_ui.folderUpButton, &QToolButton::clicked, this, &KatePluginSearchView::navigateFolderUp); connect(m_ui.currentFolderButton, &QToolButton::clicked, this, &KatePluginSearchView::setCurrentFolder); connect(m_ui.expandResults, &QToolButton::clicked, this, &KatePluginSearchView::expandResults); connect(m_ui.searchCombo, &QComboBox::editTextChanged, &m_changeTimer, static_cast(&QTimer::start)); connect(m_ui.matchCase, &QToolButton::toggled, &m_changeTimer, static_cast(&QTimer::start)); connect(m_ui.matchCase, &QToolButton::toggled, this, [=] { Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (res) { res->matchCase = m_ui.matchCase->isChecked(); } }); connect(m_ui.searchCombo->lineEdit(), &QLineEdit::returnPressed, this, &KatePluginSearchView::startSearch); // connecting to returnPressed() of the folderRequester doesn't work, I haven't found out why yet. But connecting to the linedit works: connect(m_ui.folderRequester->comboBox()->lineEdit(), &QLineEdit::returnPressed, this, &KatePluginSearchView::startSearch); connect(m_ui.filterCombo, static_cast(&KComboBox::returnPressed), this, &KatePluginSearchView::startSearch); connect(m_ui.excludeCombo, static_cast(&KComboBox::returnPressed), this, &KatePluginSearchView::startSearch); connect(m_ui.searchButton, &QPushButton::clicked, this, &KatePluginSearchView::startSearch); connect(m_ui.displayOptions, &QToolButton::toggled, this, &KatePluginSearchView::toggleOptions); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) connect(m_ui.searchPlaceCombo, static_cast(&QComboBox::currentIndexChanged), this, &KatePluginSearchView::searchPlaceChanged); -#else - connect(m_ui.searchPlaceCombo, static_cast(&QComboBox::currentIndexChanged) , this, &KatePluginSearchView::searchPlaceChanged); -#endif -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) connect(m_ui.searchPlaceCombo, static_cast(&QComboBox::currentIndexChanged), this, [this](int) { -#else - connect(m_ui.searchPlaceCombo, static_cast(&QComboBox::currentIndexChanged) , this, [this](int) { -#endif if (m_ui.searchPlaceCombo->currentIndex() == Folder) { m_ui.displayOptions->setChecked(true); } }); connect(m_ui.stopButton, &QPushButton::clicked, &m_searchOpenFiles, &SearchOpenFiles::cancelSearch); connect(m_ui.stopButton, &QPushButton::clicked, &m_searchDiskFiles, &SearchDiskFiles::cancelSearch); connect(m_ui.stopButton, &QPushButton::clicked, &m_folderFilesList, &FolderFilesList::cancelSearch); connect(m_ui.stopButton, &QPushButton::clicked, &m_replacer, &ReplaceMatches::cancelReplace); connect(m_ui.nextButton, &QToolButton::clicked, this, &KatePluginSearchView::goToNextMatch); connect(m_ui.replaceButton, &QPushButton::clicked, this, &KatePluginSearchView::replaceSingleMatch); connect(m_ui.replaceCheckedBtn, &QPushButton::clicked, this, &KatePluginSearchView::replaceChecked); connect(m_ui.replaceCombo->lineEdit(), &QLineEdit::returnPressed, this, &KatePluginSearchView::replaceChecked); m_ui.displayOptions->setChecked(true); connect(&m_searchOpenFiles, &SearchOpenFiles::matchFound, this, &KatePluginSearchView::matchFound); connect(&m_searchOpenFiles, &SearchOpenFiles::searchDone, this, &KatePluginSearchView::searchDone); connect(&m_searchOpenFiles, static_cast(&SearchOpenFiles::searching), this, &KatePluginSearchView::searching); connect(&m_folderFilesList, &FolderFilesList::finished, this, &KatePluginSearchView::folderFileListChanged); connect(&m_folderFilesList, &FolderFilesList::searching, this, &KatePluginSearchView::searching); connect(&m_searchDiskFiles, &SearchDiskFiles::matchFound, this, &KatePluginSearchView::matchFound); connect(&m_searchDiskFiles, &SearchDiskFiles::searchDone, this, &KatePluginSearchView::searchDone); connect(&m_searchDiskFiles, static_cast(&SearchDiskFiles::searching), this, &KatePluginSearchView::searching); connect(m_kateApp, &KTextEditor::Application::documentWillBeDeleted, &m_searchOpenFiles, &SearchOpenFiles::cancelSearch); connect(m_kateApp, &KTextEditor::Application::documentWillBeDeleted, &m_replacer, &ReplaceMatches::cancelReplace); connect(m_kateApp, &KTextEditor::Application::documentWillBeDeleted, this, &KatePluginSearchView::clearDocMarks); connect(&m_replacer, &ReplaceMatches::replaceStatus, this, &KatePluginSearchView::replaceStatus); // Hook into line edit context menus m_ui.searchCombo->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_ui.searchCombo, &QComboBox::customContextMenuRequested, this, &KatePluginSearchView::searchContextMenu); m_ui.searchCombo->completer()->setCompletionMode(QCompleter::PopupCompletion); m_ui.searchCombo->completer()->setCaseSensitivity(Qt::CaseSensitive); m_ui.searchCombo->setInsertPolicy(QComboBox::NoInsert); m_ui.searchCombo->lineEdit()->setClearButtonEnabled(true); m_ui.searchCombo->setMaxCount(25); QAction *searchComboActionForInsertRegexButton = m_ui.searchCombo->lineEdit()->addAction(QIcon::fromTheme(QStringLiteral("code-context"), QIcon::fromTheme(QStringLiteral("edit-find-replace"))), QLineEdit::TrailingPosition); connect(searchComboActionForInsertRegexButton, &QAction::triggered, this, [this]() { QMenu menu; QSet actionList; addRegexHelperActionsForSearch(&actionList, &menu); auto &&action = menu.exec(QCursor::pos()); regexHelperActOnAction(action, actionList, m_ui.searchCombo->lineEdit()); }); m_ui.replaceCombo->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_ui.replaceCombo, &QComboBox::customContextMenuRequested, this, &KatePluginSearchView::replaceContextMenu); m_ui.replaceCombo->completer()->setCompletionMode(QCompleter::PopupCompletion); m_ui.replaceCombo->completer()->setCaseSensitivity(Qt::CaseSensitive); m_ui.replaceCombo->setInsertPolicy(QComboBox::NoInsert); m_ui.replaceCombo->lineEdit()->setClearButtonEnabled(true); m_ui.replaceCombo->setMaxCount(25); QAction *replaceComboActionForInsertRegexButton = m_ui.replaceCombo->lineEdit()->addAction(QIcon::fromTheme(QStringLiteral("code-context")), QLineEdit::TrailingPosition); connect(replaceComboActionForInsertRegexButton, &QAction::triggered, this, [this]() { QMenu menu; QSet actionList; addRegexHelperActionsForReplace(&actionList, &menu); auto &&action = menu.exec(QCursor::pos()); regexHelperActOnAction(action, actionList, m_ui.replaceCombo->lineEdit()); }); QAction *replaceComboActionForInsertSpecialButton = m_ui.replaceCombo->lineEdit()->addAction(QIcon::fromTheme(QStringLiteral("insert-text")), QLineEdit::TrailingPosition); connect(replaceComboActionForInsertSpecialButton, &QAction::triggered, this, [this]() { QMenu menu; QSet actionList; addSpecialCharsHelperActionsForReplace(&actionList, &menu); auto &&action = menu.exec(QCursor::pos()); regexHelperActOnAction(action, actionList, m_ui.replaceCombo->lineEdit()); }); connect(m_ui.useRegExp, &QToolButton::toggled, &m_changeTimer, static_cast(&QTimer::start)); auto onRegexToggleChanged = [=] { Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (res) { bool useRegExp = m_ui.useRegExp->isChecked(); res->useRegExp = useRegExp; searchComboActionForInsertRegexButton->setVisible(useRegExp); replaceComboActionForInsertRegexButton->setVisible(useRegExp); } }; connect(m_ui.useRegExp, &QToolButton::toggled, this, onRegexToggleChanged); onRegexToggleChanged(); // invoke initially m_changeTimer.setInterval(300); m_changeTimer.setSingleShot(true); connect(&m_changeTimer, &QTimer::timeout, this, &KatePluginSearchView::startSearchWhileTyping); m_toolView->setMinimumHeight(container->sizeHint().height()); connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &KatePluginSearchView::handleEsc); // watch for project plugin view creation/deletion connect(m_mainWindow, &KTextEditor::MainWindow::pluginViewCreated, this, &KatePluginSearchView::slotPluginViewCreated); connect(m_mainWindow, &KTextEditor::MainWindow::pluginViewDeleted, this, &KatePluginSearchView::slotPluginViewDeleted); connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &KatePluginSearchView::docViewChanged); // Connect signals from project plugin to our slots m_projectPluginView = m_mainWindow->pluginView(QStringLiteral("kateprojectplugin")); slotPluginViewCreated(QStringLiteral("kateprojectplugin"), m_projectPluginView); m_replacer.setDocumentManager(m_kateApp); connect(&m_replacer, &ReplaceMatches::replaceDone, this, &KatePluginSearchView::replaceDone); searchPlaceChanged(); m_toolView->installEventFilter(this); m_mainWindow->guiFactory()->addClient(this); m_updateSumaryTimer.setInterval(1); m_updateSumaryTimer.setSingleShot(true); connect(&m_updateSumaryTimer, &QTimer::timeout, this, &KatePluginSearchView::updateResultsRootItem); } KatePluginSearchView::~KatePluginSearchView() { clearMarks(); m_mainWindow->guiFactory()->removeClient(this); delete m_toolView; } void KatePluginSearchView::navigateFolderUp() { // navigate one folder up m_ui.folderRequester->setUrl(localFileDirUp(m_ui.folderRequester->url())); } void KatePluginSearchView::setCurrentFolder() { if (!m_mainWindow) { return; } KTextEditor::View *editView = m_mainWindow->activeView(); if (editView && editView->document()) { // upUrl as we want the folder not the file m_ui.folderRequester->setUrl(localFileDirUp(editView->document()->url())); } m_ui.displayOptions->setChecked(true); } void KatePluginSearchView::openSearchView() { if (!m_mainWindow) { return; } if (!m_toolView->isVisible()) { m_mainWindow->showToolView(m_toolView); } m_ui.searchCombo->setFocus(Qt::OtherFocusReason); if (m_ui.searchPlaceCombo->currentIndex() == Folder) { m_ui.displayOptions->setChecked(true); } KTextEditor::View *editView = m_mainWindow->activeView(); if (editView && editView->document()) { if (m_ui.folderRequester->text().isEmpty()) { // upUrl as we want the folder not the file m_ui.folderRequester->setUrl(localFileDirUp(editView->document()->url())); } QString selection; if (editView->selection()) { selection = editView->selectionText(); // remove possible trailing '\n' if (selection.endsWith(QLatin1Char('\n'))) { selection = selection.left(selection.size() - 1); } } if (selection.isEmpty()) { selection = editView->document()->wordAt(editView->cursorPosition()); } if (!selection.isEmpty() && !selection.contains(QLatin1Char('\n'))) { m_ui.searchCombo->blockSignals(true); m_ui.searchCombo->lineEdit()->setText(selection); m_ui.searchCombo->blockSignals(false); } m_ui.searchCombo->lineEdit()->selectAll(); m_searchJustOpened = true; startSearchWhileTyping(); } } void KatePluginSearchView::handleEsc(QEvent *e) { if (!m_mainWindow) return; QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { static ulong lastTimeStamp; if (lastTimeStamp == k->timestamp()) { // Same as previous... This looks like a bug somewhere... return; } lastTimeStamp = k->timestamp(); if (!m_matchRanges.isEmpty()) { clearMarks(); } else if (m_toolView->isVisible()) { m_mainWindow->hideToolView(m_toolView); } // Remove check marks Results *curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!curResults) { qWarning() << "This is a bug"; return; } QTreeWidgetItemIterator it(curResults->tree); while (*it) { (*it)->setCheckState(0, Qt::Unchecked); ++it; } } } void KatePluginSearchView::setSearchString(const QString &pattern) { m_ui.searchCombo->lineEdit()->setText(pattern); } void KatePluginSearchView::toggleOptions(bool show) { m_ui.stackedWidget->setCurrentIndex((show) ? 1 : 0); } void KatePluginSearchView::setSearchPlace(int place) { m_ui.searchPlaceCombo->setCurrentIndex(place); } QStringList KatePluginSearchView::filterFiles(const QStringList &files) const { QString types = m_ui.filterCombo->currentText(); QString excludes = m_ui.excludeCombo->currentText(); if (((types.isEmpty() || types == QLatin1String("*"))) && (excludes.isEmpty())) { // shortcut for use all files return files; } QStringList tmpTypes = types.split(QLatin1Char(',')); QVector typeList(tmpTypes.size()); for (int i = 0; i < tmpTypes.size(); i++) { QRegExp rx(tmpTypes[i].trimmed()); rx.setPatternSyntax(QRegExp::Wildcard); typeList << rx; } QStringList tmpExcludes = excludes.split(QLatin1Char(',')); QVector excludeList(tmpExcludes.size()); for (int i = 0; i < tmpExcludes.size(); i++) { QRegExp rx(tmpExcludes[i].trimmed()); rx.setPatternSyntax(QRegExp::Wildcard); excludeList << rx; } QStringList filteredFiles; for (const QString &fileName : files) { bool isInSubDir = fileName.startsWith(m_resultBaseDir); QString nameToCheck = fileName; if (isInSubDir) { nameToCheck = fileName.mid(m_resultBaseDir.size()); } bool skip = false; for (const auto ®ex : qAsConst(excludeList)) { if (regex.exactMatch(nameToCheck)) { skip = true; break; } } if (skip) { continue; } for (const auto ®ex : qAsConst(typeList)) { if (regex.exactMatch(nameToCheck)) { filteredFiles << fileName; break; } } } return filteredFiles; } void KatePluginSearchView::folderFileListChanged() { m_searchDiskFilesDone = false; m_searchOpenFilesDone = false; if (!m_curResults) { qWarning() << "This is a bug"; m_searchDiskFilesDone = true; m_searchOpenFilesDone = true; searchDone(); return; } QStringList fileList = m_folderFilesList.fileList(); QList openList; for (int i = 0; i < m_kateApp->documents().size(); i++) { int index = fileList.indexOf(m_kateApp->documents()[i]->url().toLocalFile()); if (index != -1) { openList << m_kateApp->documents()[i]; fileList.removeAt(index); } } // search order is important: Open files starts immediately and should finish // earliest after first event loop. // The DiskFile might finish immediately if (!openList.empty()) { m_searchOpenFiles.startSearch(openList, m_curResults->regExp); } else { m_searchOpenFilesDone = true; } m_searchDiskFiles.startSearch(fileList, m_curResults->regExp); } void KatePluginSearchView::searchPlaceChanged() { int searchPlace = m_ui.searchPlaceCombo->currentIndex(); const bool inFolder = (searchPlace == Folder); m_ui.filterCombo->setEnabled(searchPlace >= Folder); m_ui.excludeCombo->setEnabled(searchPlace >= Folder); m_ui.folderRequester->setEnabled(inFolder); m_ui.folderUpButton->setEnabled(inFolder); m_ui.currentFolderButton->setEnabled(inFolder); m_ui.recursiveCheckBox->setEnabled(inFolder); m_ui.hiddenCheckBox->setEnabled(inFolder); m_ui.symLinkCheckBox->setEnabled(inFolder); m_ui.binaryCheckBox->setEnabled(inFolder); if (inFolder && sender() == m_ui.searchPlaceCombo) { setCurrentFolder(); } // ... and the labels: m_ui.folderLabel->setEnabled(m_ui.folderRequester->isEnabled()); m_ui.filterLabel->setEnabled(m_ui.filterCombo->isEnabled()); m_ui.excludeLabel->setEnabled(m_ui.excludeCombo->isEnabled()); Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (res) { res->searchPlaceIndex = searchPlace; } } void KatePluginSearchView::addHeaderItem() { QTreeWidgetItem *item = new QTreeWidgetItem(m_curResults->tree, QStringList()); item->setCheckState(0, Qt::Checked); item->setFlags(item->flags() | Qt::ItemIsAutoTristate); m_curResults->tree->expandItem(item); } QTreeWidgetItem *KatePluginSearchView::rootFileItem(const QString &url, const QString &fName) { if (!m_curResults) { return nullptr; } QUrl fullUrl = QUrl::fromUserInput(url); QString path = fullUrl.isLocalFile() ? localFileDirUp(fullUrl).path() : fullUrl.url(); if (!path.isEmpty() && !path.endsWith(QLatin1Char('/'))) { path += QLatin1Char('/'); } path.remove(m_resultBaseDir); QString name = fullUrl.fileName(); if (url.isEmpty()) { name = fName; } // make sure we have a root item if (m_curResults->tree->topLevelItemCount() == 0) { addHeaderItem(); } QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (m_isSearchAsYouType) { return root; } for (int i = 0; i < root->childCount(); i++) { // qDebug() << root->child(i)->data(0, ReplaceMatches::FileNameRole).toString() << fName; if ((root->child(i)->data(0, ReplaceMatches::FileUrlRole).toString() == url) && (root->child(i)->data(0, ReplaceMatches::FileNameRole).toString() == fName)) { int matches = root->child(i)->data(0, ReplaceMatches::StartLineRole).toInt() + 1; QString tmpUrl = QStringLiteral("%1%2: %3").arg(path, name).arg(matches); root->child(i)->setData(0, Qt::DisplayRole, tmpUrl); root->child(i)->setData(0, ReplaceMatches::StartLineRole, matches); return root->child(i); } } // file item not found create a new one QString tmpUrl = QStringLiteral("%1%2: %3").arg(path, name).arg(1); TreeWidgetItem *item = new TreeWidgetItem(root, QStringList(tmpUrl)); item->setData(0, ReplaceMatches::FileUrlRole, url); item->setData(0, ReplaceMatches::FileNameRole, fName); item->setData(0, ReplaceMatches::StartLineRole, 1); item->setCheckState(0, Qt::Checked); item->setFlags(item->flags() | Qt::ItemIsAutoTristate); return item; } void KatePluginSearchView::addMatchMark(KTextEditor::Document *doc, QTreeWidgetItem *item) { if (!doc || !item) { return; } KTextEditor::View *activeView = m_mainWindow->activeView(); KTextEditor::MovingInterface *miface = qobject_cast(doc); KTextEditor::ConfigInterface *ciface = qobject_cast(activeView); KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); int line = item->data(0, ReplaceMatches::StartLineRole).toInt(); int column = item->data(0, ReplaceMatches::StartColumnRole).toInt(); int endLine = item->data(0, ReplaceMatches::EndLineRole).toInt(); int endColumn = item->data(0, ReplaceMatches::EndColumnRole).toInt(); bool isReplaced = item->data(0, ReplaceMatches::ReplacedRole).toBool(); if (isReplaced) { QColor replaceColor(Qt::green); if (ciface) replaceColor = ciface->configValue(QStringLiteral("replace-highlight-color")).value(); attr->setBackground(replaceColor); if (activeView) { attr->setForeground(activeView->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color()); } } else { QColor searchColor(Qt::yellow); if (ciface) searchColor = ciface->configValue(QStringLiteral("search-highlight-color")).value(); attr->setBackground(searchColor); if (activeView) { attr->setForeground(activeView->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color()); } } KTextEditor::Range range(line, column, endLine, endColumn); // Check that the match still matches if (m_curResults) { if (!isReplaced) { // special handling for "(?=\\n)" in multi-line search QRegularExpression tmpReg = m_curResults->regExp; if (m_curResults->regExp.pattern().endsWith(QLatin1String("(?=\\n)"))) { QString newPatern = tmpReg.pattern(); newPatern.replace(QStringLiteral("(?=\\n)"), QStringLiteral("$")); tmpReg.setPattern(newPatern); } // Check that the match still matches ;) if (tmpReg.match(doc->text(range)).capturedStart() != 0) { // qDebug() << doc->text(range) << "Does not match" << m_curResults->regExp.pattern(); return; } } else { if (doc->text(range) != item->data(0, ReplaceMatches::ReplacedTextRole).toString()) { // qDebug() << doc->text(range) << "Does not match" << item->data(0, ReplaceMatches::ReplacedTextRole).toString(); return; } } } // Highlight the match KTextEditor::MovingRange *mr = miface->newMovingRange(range); mr->setAttribute(attr); mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection mr->setAttributeOnlyForViews(true); m_matchRanges.append(mr); // Add a match mark #if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5,69,0) KTextEditor::MarkInterfaceV2 *iface = qobject_cast(doc); #else KTextEditor::MarkInterface *iface = qobject_cast(doc); #endif if (!iface) return; iface->setMarkDescription(KTextEditor::MarkInterface::markType32, i18n("SearchHighLight")); #if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5,69,0) iface->setMarkIcon(KTextEditor::MarkInterface::markType32, QIcon()); #else iface->setMarkPixmap(KTextEditor::MarkInterface::markType32, QIcon().pixmap(0, 0)); #endif iface->addMark(line, KTextEditor::MarkInterface::markType32); connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(clearMarks()), Qt::UniqueConnection); } static const int contextLen = 70; void KatePluginSearchView::matchFound(const QString &url, const QString &fName, const QString &lineContent, int matchLen, int startLine, int startColumn, int endLine, int endColumn) { if (!m_curResults) { return; } int preLen = contextLen; int preStart = startColumn - preLen; if (preStart < 0) { preLen += preStart; preStart = 0; } QString pre; if (preLen == contextLen) { pre = QStringLiteral("..."); } pre += lineContent.mid(preStart, preLen).toHtmlEscaped(); QString match = lineContent.mid(startColumn, matchLen).toHtmlEscaped(); match.replace(QLatin1Char('\n'), QStringLiteral("\\n")); QString post = lineContent.mid(startColumn + matchLen, contextLen); if (post.size() >= contextLen) { post += QStringLiteral("..."); } post = post.toHtmlEscaped(); QStringList row; row << i18n("Line: %1 Column: %2: %3", startLine + 1, startColumn + 1, pre + QStringLiteral("") + match + QStringLiteral("") + post); TreeWidgetItem *item = new TreeWidgetItem(rootFileItem(url, fName), row); item->setData(0, ReplaceMatches::FileUrlRole, url); item->setData(0, Qt::ToolTipRole, url); item->setData(0, ReplaceMatches::FileNameRole, fName); item->setData(0, ReplaceMatches::StartLineRole, startLine); item->setData(0, ReplaceMatches::StartColumnRole, startColumn); item->setData(0, ReplaceMatches::MatchLenRole, matchLen); item->setData(0, ReplaceMatches::PreMatchRole, pre); item->setData(0, ReplaceMatches::MatchRole, match); item->setData(0, ReplaceMatches::PostMatchRole, post); item->setData(0, ReplaceMatches::EndLineRole, endLine); item->setData(0, ReplaceMatches::EndColumnRole, endColumn); item->setCheckState(0, Qt::Checked); m_curResults->matches++; } void KatePluginSearchView::clearMarks() { const auto docs = m_kateApp->documents(); for (KTextEditor::Document *doc : docs) { clearDocMarks(doc); } qDeleteAll(m_matchRanges); m_matchRanges.clear(); } void KatePluginSearchView::clearDocMarks(KTextEditor::Document *doc) { KTextEditor::MarkInterface *iface; iface = qobject_cast(doc); if (iface) { const QHash marks = iface->marks(); QHashIterator i(marks); while (i.hasNext()) { i.next(); if (i.value()->type & KTextEditor::MarkInterface::markType32) { iface->removeMark(i.value()->line, KTextEditor::MarkInterface::markType32); } } } int i = 0; while (i < m_matchRanges.size()) { if (m_matchRanges.at(i)->document() == doc) { delete m_matchRanges.at(i); m_matchRanges.removeAt(i); } else { i++; } } m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { qWarning() << "This is a bug"; return; } } void KatePluginSearchView::startSearch() { m_changeTimer.stop(); // make sure not to start a "while you type" search now m_mainWindow->showToolView(m_toolView); // in case we are invoked from the command interface m_projectSearchPlaceIndex = 0; // now that we started, don't switch back automatically if (m_ui.searchCombo->currentText().isEmpty()) { // return pressed in the folder combo or filter combo return; } m_isSearchAsYouType = false; QString currentSearchText = m_ui.searchCombo->currentText(); m_ui.searchCombo->setItemText(0, QString()); // remove the text from index 0 on enter/search int index = m_ui.searchCombo->findText(currentSearchText); if (index > 0) { m_ui.searchCombo->removeItem(index); } m_ui.searchCombo->insertItem(1, currentSearchText); m_ui.searchCombo->setCurrentIndex(1); if (m_ui.filterCombo->findText(m_ui.filterCombo->currentText()) == -1) { m_ui.filterCombo->insertItem(0, m_ui.filterCombo->currentText()); m_ui.filterCombo->setCurrentIndex(0); } if (m_ui.excludeCombo->findText(m_ui.excludeCombo->currentText()) == -1) { m_ui.excludeCombo->insertItem(0, m_ui.excludeCombo->currentText()); m_ui.excludeCombo->setCurrentIndex(0); } if (m_ui.folderRequester->comboBox()->findText(m_ui.folderRequester->comboBox()->currentText()) == -1) { m_ui.folderRequester->comboBox()->insertItem(0, m_ui.folderRequester->comboBox()->currentText()); m_ui.folderRequester->comboBox()->setCurrentIndex(0); } m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { qWarning() << "This is a bug"; return; } QRegularExpression::PatternOptions patternOptions = (m_ui.matchCase->isChecked() ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption); QString pattern = (m_ui.useRegExp->isChecked() ? currentSearchText : QRegularExpression::escape(currentSearchText)); QRegularExpression reg(pattern, patternOptions); if (!reg.isValid()) { // qDebug() << "invalid regexp"; indicateMatch(false); return; } m_curResults->regExp = reg; m_curResults->useRegExp = m_ui.useRegExp->isChecked(); m_curResults->matchCase = m_ui.matchCase->isChecked(); m_curResults->searchPlaceIndex = m_ui.searchPlaceCombo->currentIndex(); m_ui.newTabButton->setDisabled(true); m_ui.searchCombo->setDisabled(true); m_ui.searchButton->setDisabled(true); m_ui.displayOptions->setChecked(false); m_ui.displayOptions->setDisabled(true); m_ui.replaceCheckedBtn->setDisabled(true); m_ui.replaceButton->setDisabled(true); m_ui.stopAndNext->setCurrentWidget(m_ui.stopButton); m_ui.replaceCombo->setDisabled(true); m_ui.searchPlaceCombo->setDisabled(true); m_ui.useRegExp->setDisabled(true); m_ui.matchCase->setDisabled(true); m_ui.expandResults->setDisabled(true); m_ui.currentFolderButton->setDisabled(true); clearMarks(); m_curResults->tree->clear(); m_curResults->tree->setCurrentItem(nullptr); m_curResults->matches = 0; disconnect(m_curResults->tree, &QTreeWidget::itemChanged, &m_updateSumaryTimer, nullptr); m_ui.resultTabWidget->setTabText(m_ui.resultTabWidget->currentIndex(), m_ui.searchCombo->currentText()); m_toolView->setCursor(Qt::WaitCursor); m_searchDiskFilesDone = false; m_searchOpenFilesDone = false; const bool inCurrentProject = m_ui.searchPlaceCombo->currentIndex() == Project; const bool inAllOpenProjects = m_ui.searchPlaceCombo->currentIndex() == AllProjects; if (m_ui.searchPlaceCombo->currentIndex() == CurrentFile) { m_searchDiskFilesDone = true; m_resultBaseDir.clear(); QList documents; KTextEditor::View *activeView = m_mainWindow->activeView(); if (activeView) { documents << activeView->document(); } addHeaderItem(); m_searchOpenFiles.startSearch(documents, reg); } else if (m_ui.searchPlaceCombo->currentIndex() == OpenFiles) { m_searchDiskFilesDone = true; m_resultBaseDir.clear(); const QList documents = m_kateApp->documents(); addHeaderItem(); m_searchOpenFiles.startSearch(documents, reg); } else if (m_ui.searchPlaceCombo->currentIndex() == Folder) { m_resultBaseDir = m_ui.folderRequester->url().path(); if (!m_resultBaseDir.isEmpty() && !m_resultBaseDir.endsWith(QLatin1Char('/'))) m_resultBaseDir += QLatin1Char('/'); addHeaderItem(); m_folderFilesList.generateList(m_ui.folderRequester->text(), m_ui.recursiveCheckBox->isChecked(), m_ui.hiddenCheckBox->isChecked(), m_ui.symLinkCheckBox->isChecked(), m_ui.binaryCheckBox->isChecked(), m_ui.filterCombo->currentText(), m_ui.excludeCombo->currentText()); // the file list will be ready when the thread returns (connected to folderFileListChanged) } else if (inCurrentProject || inAllOpenProjects) { /** * init search with file list from current project, if any */ m_resultBaseDir.clear(); QStringList files; if (m_projectPluginView) { if (inCurrentProject) { m_resultBaseDir = m_projectPluginView->property("projectBaseDir").toString(); } else { m_resultBaseDir = m_projectPluginView->property("allProjectsCommonBaseDir").toString(); } if (!m_resultBaseDir.endsWith(QLatin1Char('/'))) m_resultBaseDir += QLatin1Char('/'); QStringList projectFiles; if (inCurrentProject) { projectFiles = m_projectPluginView->property("projectFiles").toStringList(); } else { projectFiles = m_projectPluginView->property("allProjectsFiles").toStringList(); } files = filterFiles(projectFiles); } addHeaderItem(); QList openList; const auto docs = m_kateApp->documents(); for (const auto doc : docs) { // match project file's list toLocalFile() int index = files.indexOf(doc->url().toLocalFile()); if (index != -1) { openList << doc; files.removeAt(index); } } // search order is important: Open files starts immediately and should finish // earliest after first event loop. // The DiskFile might finish immediately if (!openList.empty()) { m_searchOpenFiles.startSearch(openList, m_curResults->regExp); } else { m_searchOpenFilesDone = true; } m_searchDiskFiles.startSearch(files, reg); } else { Q_ASSERT_X(false, "KatePluginSearchView::startSearch", "case not handled"); } } void KatePluginSearchView::startSearchWhileTyping() { if (!m_searchDiskFilesDone || !m_searchOpenFilesDone) { return; } m_isSearchAsYouType = true; QString currentSearchText = m_ui.searchCombo->currentText(); m_ui.searchButton->setDisabled(currentSearchText.isEmpty()); // Do not clear the search results if you press up by mistake if (currentSearchText.isEmpty()) return; if (!m_mainWindow->activeView()) return; KTextEditor::Document *doc = m_mainWindow->activeView()->document(); if (!doc) return; m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { qWarning() << "This is a bug"; return; } // check if we typed something or just changed combobox index // changing index should not trigger a search-as-you-type if (m_ui.searchCombo->currentIndex() > 0 && currentSearchText == m_ui.searchCombo->itemText(m_ui.searchCombo->currentIndex())) { return; } // Now we should have a true typed text change QRegularExpression::PatternOptions patternOptions = (m_ui.matchCase->isChecked() ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption); QString pattern = (m_ui.useRegExp->isChecked() ? currentSearchText : QRegularExpression::escape(currentSearchText)); QRegularExpression reg(pattern, patternOptions); if (!reg.isValid()) { // qDebug() << "invalid regexp"; indicateMatch(false); return; } disconnect(m_curResults->tree, &QTreeWidget::itemChanged, &m_updateSumaryTimer, nullptr); m_curResults->regExp = reg; m_curResults->useRegExp = m_ui.useRegExp->isChecked(); m_ui.replaceCheckedBtn->setDisabled(true); m_ui.replaceButton->setDisabled(true); m_ui.nextButton->setDisabled(true); int cursorPosition = m_ui.searchCombo->lineEdit()->cursorPosition(); bool hasSelected = m_ui.searchCombo->lineEdit()->hasSelectedText(); m_ui.searchCombo->blockSignals(true); m_ui.searchCombo->setItemText(0, currentSearchText); m_ui.searchCombo->setCurrentIndex(0); m_ui.searchCombo->lineEdit()->setCursorPosition(cursorPosition); if (hasSelected) { // This restores the select all from invoking openSearchView // This selects too much if we have a partial selection and toggle match-case/regexp m_ui.searchCombo->lineEdit()->selectAll(); } m_ui.searchCombo->blockSignals(false); // Prepare for the new search content clearMarks(); m_resultBaseDir.clear(); m_curResults->tree->clear(); m_curResults->tree->setCurrentItem(nullptr); m_curResults->matches = 0; // Add the search-as-you-type header item TreeWidgetItem *item = new TreeWidgetItem(m_curResults->tree, QStringList()); item->setData(0, ReplaceMatches::FileUrlRole, doc->url().toString()); item->setData(0, ReplaceMatches::FileNameRole, doc->documentName()); item->setData(0, ReplaceMatches::StartLineRole, 0); item->setCheckState(0, Qt::Checked); item->setFlags(item->flags() | Qt::ItemIsAutoTristate); // Do the search int searchStoppedAt = m_searchOpenFiles.searchOpenFile(doc, reg, 0); searchWhileTypingDone(); if (searchStoppedAt != 0) { delete m_infoMessage; const QString msg = i18n("Searching while you type was interrupted. It would have taken too long."); m_infoMessage = new KTextEditor::Message(msg, KTextEditor::Message::Warning); m_infoMessage->setPosition(KTextEditor::Message::TopInView); m_infoMessage->setAutoHide(3000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(m_mainWindow->activeView()); m_mainWindow->activeView()->document()->postMessage(m_infoMessage); } } void KatePluginSearchView::searchDone() { m_changeTimer.stop(); // avoid "while you type" search directly after if (sender() == &m_searchDiskFiles) { m_searchDiskFilesDone = true; } if (sender() == &m_searchOpenFiles) { m_searchOpenFilesDone = true; } if (!m_searchDiskFilesDone || !m_searchOpenFilesDone) { return; } QWidget *fw = QApplication::focusWidget(); // NOTE: we take the focus widget here before the enabling/disabling // moves the focus around. m_ui.newTabButton->setDisabled(false); m_ui.searchCombo->setDisabled(false); m_ui.searchButton->setDisabled(false); m_ui.stopAndNext->setCurrentWidget(m_ui.nextButton); m_ui.displayOptions->setDisabled(false); m_ui.replaceCombo->setDisabled(false); m_ui.searchPlaceCombo->setDisabled(false); m_ui.useRegExp->setDisabled(false); m_ui.matchCase->setDisabled(false); m_ui.expandResults->setDisabled(false); m_ui.currentFolderButton->setDisabled(false); if (!m_curResults) { return; } m_ui.replaceCheckedBtn->setDisabled(m_curResults->matches < 1); m_ui.replaceButton->setDisabled(m_curResults->matches < 1); m_ui.nextButton->setDisabled(m_curResults->matches < 1); m_curResults->tree->sortItems(0, Qt::AscendingOrder); m_curResults->tree->expandAll(); m_curResults->tree->resizeColumnToContents(0); if (m_curResults->tree->columnWidth(0) < m_curResults->tree->width() - 30) { m_curResults->tree->setColumnWidth(0, m_curResults->tree->width() - 30); } // expand the "header item " to display all files and all results if configured expandResults(); updateResultsRootItem(); connect(m_curResults->tree, &QTreeWidget::itemChanged, &m_updateSumaryTimer, static_cast(&QTimer::start)); indicateMatch(m_curResults->matches > 0); m_curResults = nullptr; m_toolView->unsetCursor(); if (fw == m_ui.stopButton) { m_ui.searchCombo->setFocus(); } m_searchJustOpened = false; } void KatePluginSearchView::searchWhileTypingDone() { if (!m_curResults) { return; } bool popupVisible = m_ui.searchCombo->lineEdit()->completer()->popup()->isVisible(); m_ui.replaceCheckedBtn->setDisabled(m_curResults->matches < 1); m_ui.replaceButton->setDisabled(m_curResults->matches < 1); m_ui.nextButton->setDisabled(m_curResults->matches < 1); m_curResults->tree->expandAll(); m_curResults->tree->resizeColumnToContents(0); if (m_curResults->tree->columnWidth(0) < m_curResults->tree->width() - 30) { m_curResults->tree->setColumnWidth(0, m_curResults->tree->width() - 30); } QWidget *focusObject = nullptr; QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (root) { QTreeWidgetItem *child = root->child(0); if (!m_searchJustOpened) { focusObject = qobject_cast(QGuiApplication::focusObject()); } indicateMatch(child); updateResultsRootItem(); connect(m_curResults->tree, &QTreeWidget::itemChanged, &m_updateSumaryTimer, static_cast(&QTimer::start)); } m_curResults = nullptr; if (focusObject) { focusObject->setFocus(); } if (popupVisible) { m_ui.searchCombo->lineEdit()->completer()->complete(); } m_searchJustOpened = false; } void KatePluginSearchView::searching(const QString &file) { if (!m_curResults) { return; } QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (root) { if (file.size() > 70) { root->setData(0, Qt::DisplayRole, i18n("Searching: ...%1", file.right(70))); } else { root->setData(0, Qt::DisplayRole, i18n("Searching: %1", file)); } } } void KatePluginSearchView::indicateMatch(bool hasMatch) { QLineEdit *const lineEdit = m_ui.searchCombo->lineEdit(); QPalette background(lineEdit->palette()); if (hasMatch) { // Green background for line edit KColorScheme::adjustBackground(background, KColorScheme::PositiveBackground); } else { // Reset background of line edit background = QPalette(); } // Red background for line edit // KColorScheme::adjustBackground(background, KColorScheme::NegativeBackground); // Neutral background // KColorScheme::adjustBackground(background, KColorScheme::NeutralBackground); lineEdit->setPalette(background); } void KatePluginSearchView::replaceSingleMatch() { // Save the search text if (m_ui.searchCombo->findText(m_ui.searchCombo->currentText()) == -1) { m_ui.searchCombo->insertItem(1, m_ui.searchCombo->currentText()); m_ui.searchCombo->setCurrentIndex(1); } // Save the replace text if (m_ui.replaceCombo->findText(m_ui.replaceCombo->currentText()) == -1) { m_ui.replaceCombo->insertItem(1, m_ui.replaceCombo->currentText()); m_ui.replaceCombo->setCurrentIndex(1); } // Check if the cursor is at the current item if not jump there Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!res) { return; // Security measure } QTreeWidgetItem *item = res->tree->currentItem(); if (!item || !item->parent()) { // Nothing was selected goToNextMatch(); return; } if (!m_mainWindow->activeView() || !m_mainWindow->activeView()->cursorPosition().isValid()) { itemSelected(item); // Correct any bad cursor positions return; } int cursorLine = m_mainWindow->activeView()->cursorPosition().line(); int cursorColumn = m_mainWindow->activeView()->cursorPosition().column(); int startLine = item->data(0, ReplaceMatches::StartLineRole).toInt(); int startColumn = item->data(0, ReplaceMatches::StartColumnRole).toInt(); if ((cursorLine != startLine) || (cursorColumn != startColumn)) { itemSelected(item); return; } KTextEditor::Document *doc = m_mainWindow->activeView()->document(); // Find the corresponding range int i; for (i = 0; i < m_matchRanges.size(); i++) { if (m_matchRanges[i]->document() != doc) continue; if (m_matchRanges[i]->start().line() != startLine) continue; if (m_matchRanges[i]->start().column() != startColumn) continue; break; } if (i >= m_matchRanges.size()) { goToNextMatch(); return; } m_replacer.replaceSingleMatch(doc, item, res->regExp, m_ui.replaceCombo->currentText()); goToNextMatch(); } void KatePluginSearchView::replaceChecked() { if (m_ui.searchCombo->findText(m_ui.searchCombo->currentText()) == -1) { m_ui.searchCombo->insertItem(1, m_ui.searchCombo->currentText()); m_ui.searchCombo->setCurrentIndex(1); } if (m_ui.replaceCombo->findText(m_ui.replaceCombo->currentText()) == -1) { m_ui.replaceCombo->insertItem(1, m_ui.replaceCombo->currentText()); m_ui.replaceCombo->setCurrentIndex(1); } m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { qWarning() << "Results not found"; return; } m_ui.stopAndNext->setCurrentWidget(m_ui.stopButton); m_ui.displayOptions->setChecked(false); m_ui.displayOptions->setDisabled(true); m_ui.newTabButton->setDisabled(true); m_ui.searchCombo->setDisabled(true); m_ui.searchButton->setDisabled(true); m_ui.replaceCheckedBtn->setDisabled(true); m_ui.replaceButton->setDisabled(true); m_ui.replaceCombo->setDisabled(true); m_ui.searchPlaceCombo->setDisabled(true); m_ui.useRegExp->setDisabled(true); m_ui.matchCase->setDisabled(true); m_ui.expandResults->setDisabled(true); m_ui.currentFolderButton->setDisabled(true); m_curResults->replaceStr = m_ui.replaceCombo->currentText(); QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (root) { m_curResults->treeRootText = root->data(0, Qt::DisplayRole).toString(); } m_replacer.replaceChecked(m_curResults->tree, m_curResults->regExp, m_curResults->replaceStr); } void KatePluginSearchView::replaceStatus(const QUrl &url, int replacedInFile, int matchesInFile) { if (!m_curResults) { // qDebug() << "m_curResults == nullptr"; return; } QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (root) { QString file = url.toString(QUrl::PreferLocalFile); if (file.size() > 70) { root->setData(0, Qt::DisplayRole, i18n("Processed %1 of %2 matches in: ...%3", replacedInFile, matchesInFile, file.right(70))); } else { root->setData(0, Qt::DisplayRole, i18n("Processed %1 of %2 matches in: %3", replacedInFile, matchesInFile, file)); } } } void KatePluginSearchView::replaceDone() { m_ui.stopAndNext->setCurrentWidget(m_ui.nextButton); m_ui.replaceCombo->setDisabled(false); m_ui.newTabButton->setDisabled(false); m_ui.searchCombo->setDisabled(false); m_ui.searchButton->setDisabled(false); m_ui.replaceCheckedBtn->setDisabled(false); m_ui.replaceButton->setDisabled(false); m_ui.displayOptions->setDisabled(false); m_ui.searchPlaceCombo->setDisabled(false); m_ui.useRegExp->setDisabled(false); m_ui.matchCase->setDisabled(false); m_ui.expandResults->setDisabled(false); m_ui.currentFolderButton->setDisabled(false); if (!m_curResults) { // qDebug() << "m_curResults == nullptr"; return; } QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (root) { root->setData(0, Qt::DisplayRole, m_curResults->treeRootText); } } void KatePluginSearchView::docViewChanged() { if (!m_mainWindow->activeView()) { return; } Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!res) { // qDebug() << "No res"; return; } m_curResults = res; // add the marks if it is not already open KTextEditor::Document *doc = m_mainWindow->activeView()->document(); if (doc && res->tree->topLevelItemCount() > 0) { // There is always one root item with match count // and X children with files or matches in case of search while typing QTreeWidgetItem *rootItem = res->tree->topLevelItem(0); QTreeWidgetItem *fileItem = nullptr; for (int i = 0; i < rootItem->childCount(); i++) { QString url = rootItem->child(i)->data(0, ReplaceMatches::FileUrlRole).toString(); QString fName = rootItem->child(i)->data(0, ReplaceMatches::FileNameRole).toString(); if (url == doc->url().toString() && fName == doc->documentName()) { fileItem = rootItem->child(i); break; } } if (fileItem) { clearDocMarks(doc); if (m_isSearchAsYouType) { fileItem = fileItem->parent(); } for (int i = 0; i < fileItem->childCount(); i++) { if (fileItem->child(i)->checkState(0) == Qt::Unchecked) { continue; } addMatchMark(doc, fileItem->child(i)); } } // Re-add the highlighting on document reload connect(doc, &KTextEditor::Document::reloaded, this, &KatePluginSearchView::docViewChanged, Qt::UniqueConnection); } } void KatePluginSearchView::expandResults() { m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { qWarning() << "Results not found"; return; } if (m_ui.expandResults->isChecked()) { m_curResults->tree->expandAll(); } else { QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); m_curResults->tree->expandItem(root); if (root && (root->childCount() > 1)) { for (int i = 0; i < root->childCount(); i++) { m_curResults->tree->collapseItem(root->child(i)); } } } } void KatePluginSearchView::updateResultsRootItem() { m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { return; } QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (!root) { // nothing to update return; } int checkedItemCount = 0; if (m_curResults->matches > 0) { for (QTreeWidgetItemIterator it(m_curResults->tree, QTreeWidgetItemIterator::Checked | QTreeWidgetItemIterator::NoChildren); *it; ++it) { checkedItemCount++; } } QString checkedStr = i18np("One checked", "%1 checked", checkedItemCount); int searchPlace = m_ui.searchPlaceCombo->currentIndex(); if (m_isSearchAsYouType) { searchPlace = CurrentFile; } switch (searchPlace) { case CurrentFile: root->setData(0, Qt::DisplayRole, i18np("One match (%2) found in file", "%1 matches (%2) found in file", m_curResults->matches, checkedStr)); break; case OpenFiles: root->setData(0, Qt::DisplayRole, i18np("One match (%2) found in open files", "%1 matches (%2) found in open files", m_curResults->matches, checkedStr)); break; case Folder: root->setData(0, Qt::DisplayRole, i18np("One match (%3) found in folder %2", "%1 matches (%3) found in folder %2", m_curResults->matches, m_resultBaseDir, checkedStr)); break; case Project: { QString projectName; if (m_projectPluginView) { projectName = m_projectPluginView->property("projectName").toString(); } root->setData(0, Qt::DisplayRole, i18np("One match (%4) found in project %2 (%3)", "%1 matches (%4) found in project %2 (%3)", m_curResults->matches, projectName, m_resultBaseDir, checkedStr)); break; } case AllProjects: // "in Open Projects" root->setData(0, Qt::DisplayRole, i18np("One match (%3) found in all open projects (common parent: %2)", "%1 matches (%3) found in all open projects (common parent: %2)", m_curResults->matches, m_resultBaseDir, checkedStr)); break; } docViewChanged(); } void KatePluginSearchView::itemSelected(QTreeWidgetItem *item) { if (!item) return; m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { return; } while (item->data(0, ReplaceMatches::StartColumnRole).toString().isEmpty()) { item->treeWidget()->expandItem(item); item = item->child(0); if (!item) return; } item->treeWidget()->setCurrentItem(item); // get stuff int toLine = item->data(0, ReplaceMatches::StartLineRole).toInt(); int toColumn = item->data(0, ReplaceMatches::StartColumnRole).toInt(); KTextEditor::Document *doc; QString url = item->data(0, ReplaceMatches::FileUrlRole).toString(); if (!url.isEmpty()) { doc = m_kateApp->findUrl(QUrl::fromUserInput(url)); } else { doc = m_replacer.findNamed(item->data(0, ReplaceMatches::FileNameRole).toString()); } // add the marks to the document if it is not already open if (!doc) { doc = m_kateApp->openUrl(QUrl::fromUserInput(url)); } if (!doc) return; // open the right view... m_mainWindow->activateView(doc); // any view active? if (!m_mainWindow->activeView()) { return; } // set the cursor to the correct position m_mainWindow->activeView()->setCursorPosition(KTextEditor::Cursor(toLine, toColumn)); m_mainWindow->activeView()->setFocus(); } void KatePluginSearchView::goToNextMatch() { bool wrapFromFirst = false; bool startFromFirst = false; bool startFromCursor = false; Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!res) { return; } QTreeWidgetItem *curr = res->tree->currentItem(); bool focusInView = m_mainWindow->activeView() && m_mainWindow->activeView()->hasFocus(); if (!curr && focusInView) { // no item has been visited && focus is not in searchCombo (probably in the view) -> // jump to the closest match after current cursor position // check if current file is in the file list curr = res->tree->topLevelItem(0); while (curr && curr->data(0, ReplaceMatches::FileUrlRole).toString() != m_mainWindow->activeView()->document()->url().toString()) { curr = res->tree->itemBelow(curr); } // now we are either in this file or !curr if (curr) { QTreeWidgetItem *fileBefore = curr; res->tree->expandItem(curr); int lineNr = 0; int columnNr = 0; if (m_mainWindow->activeView()->cursorPosition().isValid()) { lineNr = m_mainWindow->activeView()->cursorPosition().line(); columnNr = m_mainWindow->activeView()->cursorPosition().column(); } if (!curr->data(0, ReplaceMatches::StartColumnRole).isValid()) { curr = res->tree->itemBelow(curr); }; while (curr && curr->data(0, ReplaceMatches::StartLineRole).toInt() <= lineNr && curr->data(0, ReplaceMatches::FileUrlRole).toString() == m_mainWindow->activeView()->document()->url().toString()) { if (curr->data(0, ReplaceMatches::StartLineRole).toInt() == lineNr && curr->data(0, ReplaceMatches::StartColumnRole).toInt() >= columnNr - curr->data(0, ReplaceMatches::MatchLenRole).toInt()) { break; } fileBefore = curr; curr = res->tree->itemBelow(curr); } curr = fileBefore; startFromCursor = true; } } if (!curr) { curr = res->tree->topLevelItem(0); startFromFirst = true; } if (!curr) return; if (!curr->data(0, ReplaceMatches::StartColumnRole).toString().isEmpty()) { curr = res->tree->itemBelow(curr); if (!curr) { wrapFromFirst = true; curr = res->tree->topLevelItem(0); } } itemSelected(curr); if (startFromFirst) { delete m_infoMessage; const QString msg = i18n("Starting from first match"); m_infoMessage = new KTextEditor::Message(msg, KTextEditor::Message::Information); m_infoMessage->setPosition(KTextEditor::Message::TopInView); m_infoMessage->setAutoHide(2000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(m_mainWindow->activeView()); m_mainWindow->activeView()->document()->postMessage(m_infoMessage); } else if (startFromCursor) { delete m_infoMessage; const QString msg = i18n("Next from cursor"); m_infoMessage = new KTextEditor::Message(msg, KTextEditor::Message::Information); m_infoMessage->setPosition(KTextEditor::Message::BottomInView); m_infoMessage->setAutoHide(2000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(m_mainWindow->activeView()); m_mainWindow->activeView()->document()->postMessage(m_infoMessage); } else if (wrapFromFirst) { delete m_infoMessage; const QString msg = i18n("Continuing from first match"); m_infoMessage = new KTextEditor::Message(msg, KTextEditor::Message::Information); m_infoMessage->setPosition(KTextEditor::Message::TopInView); m_infoMessage->setAutoHide(2000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(m_mainWindow->activeView()); m_mainWindow->activeView()->document()->postMessage(m_infoMessage); } } void KatePluginSearchView::goToPreviousMatch() { bool fromLast = false; Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!res) { return; } if (res->tree->topLevelItemCount() == 0) { return; } QTreeWidgetItem *curr = res->tree->currentItem(); if (!curr) { // no item has been visited -> jump to the closest match before current cursor position // check if current file is in the file curr = res->tree->topLevelItem(0); while (curr && curr->data(0, ReplaceMatches::FileUrlRole).toString() != m_mainWindow->activeView()->document()->url().toString()) { curr = res->tree->itemBelow(curr); } // now we are either in this file or !curr if (curr) { res->tree->expandItem(curr); int lineNr = 0; int columnNr = 0; if (m_mainWindow->activeView()->cursorPosition().isValid()) { lineNr = m_mainWindow->activeView()->cursorPosition().line(); columnNr = m_mainWindow->activeView()->cursorPosition().column() - 1; } if (!curr->data(0, ReplaceMatches::StartColumnRole).isValid()) { curr = res->tree->itemBelow(curr); }; while (curr && curr->data(0, ReplaceMatches::StartLineRole).toInt() <= lineNr && curr->data(0, ReplaceMatches::FileUrlRole).toString() == m_mainWindow->activeView()->document()->url().toString()) { if (curr->data(0, ReplaceMatches::StartLineRole).toInt() == lineNr && curr->data(0, ReplaceMatches::StartColumnRole).toInt() > columnNr) { break; } curr = res->tree->itemBelow(curr); } } } QTreeWidgetItem *startChild = curr; // go to the item above. (curr == null is not a problem) curr = res->tree->itemAbove(curr); // expand the items above if needed if (curr && curr->data(0, ReplaceMatches::StartColumnRole).toString().isEmpty()) { res->tree->expandItem(curr); // probably this file item curr = res->tree->itemAbove(curr); if (curr && curr->data(0, ReplaceMatches::StartColumnRole).toString().isEmpty()) { res->tree->expandItem(curr); // probably file above if this is reached } curr = res->tree->itemAbove(startChild); } // skip file name items and the root item while (curr && curr->data(0, ReplaceMatches::StartColumnRole).toString().isEmpty()) { curr = res->tree->itemAbove(curr); } if (!curr) { // select the last child of the last next-to-top-level item QTreeWidgetItem *root = res->tree->topLevelItem(0); // select the last "root item" if (!root || (root->childCount() < 1)) return; root = root->child(root->childCount() - 1); // select the last match of the "root item" if (!root || (root->childCount() < 1)) return; curr = root->child(root->childCount() - 1); fromLast = true; } itemSelected(curr); if (fromLast) { delete m_infoMessage; const QString msg = i18n("Continuing from last match"); m_infoMessage = new KTextEditor::Message(msg, KTextEditor::Message::Information); m_infoMessage->setPosition(KTextEditor::Message::BottomInView); m_infoMessage->setAutoHide(2000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(m_mainWindow->activeView()); m_mainWindow->activeView()->document()->postMessage(m_infoMessage); } } void KatePluginSearchView::readSessionConfig(const KConfigGroup &cg) { m_ui.searchCombo->clear(); m_ui.searchCombo->addItem(QString()); // Add empty Item m_ui.searchCombo->addItems(cg.readEntry("Search", QStringList())); m_ui.replaceCombo->clear(); m_ui.replaceCombo->addItem(QString()); // Add empty Item m_ui.replaceCombo->addItems(cg.readEntry("Replaces", QStringList())); m_ui.matchCase->setChecked(cg.readEntry("MatchCase", false)); m_ui.useRegExp->setChecked(cg.readEntry("UseRegExp", false)); m_ui.expandResults->setChecked(cg.readEntry("ExpandSearchResults", false)); int searchPlaceIndex = cg.readEntry("Place", 1); if (searchPlaceIndex < 0) { searchPlaceIndex = Folder; // for the case we happen to read -1 as Place } if ((searchPlaceIndex >= Project) && (searchPlaceIndex >= m_ui.searchPlaceCombo->count())) { // handle the case that project mode was selected, but not yet available m_projectSearchPlaceIndex = searchPlaceIndex; searchPlaceIndex = Folder; } m_ui.searchPlaceCombo->setCurrentIndex(searchPlaceIndex); m_ui.recursiveCheckBox->setChecked(cg.readEntry("Recursive", true)); m_ui.hiddenCheckBox->setChecked(cg.readEntry("HiddenFiles", false)); m_ui.symLinkCheckBox->setChecked(cg.readEntry("FollowSymLink", false)); m_ui.binaryCheckBox->setChecked(cg.readEntry("BinaryFiles", false)); m_ui.folderRequester->comboBox()->clear(); m_ui.folderRequester->comboBox()->addItems(cg.readEntry("SearchDiskFiless", QStringList())); m_ui.folderRequester->setText(cg.readEntry("SearchDiskFiles", QString())); m_ui.filterCombo->clear(); m_ui.filterCombo->addItems(cg.readEntry("Filters", QStringList())); m_ui.filterCombo->setCurrentIndex(cg.readEntry("CurrentFilter", -1)); m_ui.excludeCombo->clear(); m_ui.excludeCombo->addItems(cg.readEntry("ExcludeFilters", QStringList())); m_ui.excludeCombo->setCurrentIndex(cg.readEntry("CurrentExcludeFilter", -1)); m_ui.displayOptions->setChecked(searchPlaceIndex == Folder); } void KatePluginSearchView::writeSessionConfig(KConfigGroup &cg) { QStringList searchHistoy; for (int i = 1; i < m_ui.searchCombo->count(); i++) { searchHistoy << m_ui.searchCombo->itemText(i); } cg.writeEntry("Search", searchHistoy); QStringList replaceHistoy; for (int i = 1; i < m_ui.replaceCombo->count(); i++) { replaceHistoy << m_ui.replaceCombo->itemText(i); } cg.writeEntry("Replaces", replaceHistoy); cg.writeEntry("MatchCase", m_ui.matchCase->isChecked()); cg.writeEntry("UseRegExp", m_ui.useRegExp->isChecked()); cg.writeEntry("ExpandSearchResults", m_ui.expandResults->isChecked()); cg.writeEntry("Place", m_ui.searchPlaceCombo->currentIndex()); cg.writeEntry("Recursive", m_ui.recursiveCheckBox->isChecked()); cg.writeEntry("HiddenFiles", m_ui.hiddenCheckBox->isChecked()); cg.writeEntry("FollowSymLink", m_ui.symLinkCheckBox->isChecked()); cg.writeEntry("BinaryFiles", m_ui.binaryCheckBox->isChecked()); QStringList folders; for (int i = 0; i < qMin(m_ui.folderRequester->comboBox()->count(), 10); i++) { folders << m_ui.folderRequester->comboBox()->itemText(i); } cg.writeEntry("SearchDiskFiless", folders); cg.writeEntry("SearchDiskFiles", m_ui.folderRequester->text()); QStringList filterItems; for (int i = 0; i < qMin(m_ui.filterCombo->count(), 10); i++) { filterItems << m_ui.filterCombo->itemText(i); } cg.writeEntry("Filters", filterItems); cg.writeEntry("CurrentFilter", m_ui.filterCombo->findText(m_ui.filterCombo->currentText())); QStringList excludeFilterItems; for (int i = 0; i < qMin(m_ui.excludeCombo->count(), 10); i++) { excludeFilterItems << m_ui.excludeCombo->itemText(i); } cg.writeEntry("ExcludeFilters", excludeFilterItems); cg.writeEntry("CurrentExcludeFilter", m_ui.excludeCombo->findText(m_ui.excludeCombo->currentText())); } void KatePluginSearchView::addTab() { if ((sender() != m_ui.newTabButton) && (m_ui.resultTabWidget->count() > 0) && m_ui.resultTabWidget->tabText(m_ui.resultTabWidget->currentIndex()).isEmpty()) { return; } Results *res = new Results(); res->tree->setRootIsDecorated(false); connect(res->tree, &QTreeWidget::itemDoubleClicked, this, &KatePluginSearchView::itemSelected, Qt::UniqueConnection); res->searchPlaceIndex = m_ui.searchPlaceCombo->currentIndex(); res->useRegExp = m_ui.useRegExp->isChecked(); res->matchCase = m_ui.matchCase->isChecked(); m_ui.resultTabWidget->addTab(res, QString()); m_ui.resultTabWidget->setCurrentIndex(m_ui.resultTabWidget->count() - 1); m_ui.stackedWidget->setCurrentIndex(0); m_ui.resultTabWidget->tabBar()->show(); m_ui.displayOptions->setChecked(false); res->tree->installEventFilter(this); } void KatePluginSearchView::tabCloseRequested(int index) { Results *tmp = qobject_cast(m_ui.resultTabWidget->widget(index)); if (m_curResults == tmp) { m_searchOpenFiles.cancelSearch(); m_searchDiskFiles.cancelSearch(); } if (m_ui.resultTabWidget->count() > 1) { delete tmp; // remove the tab m_curResults = nullptr; } if (m_ui.resultTabWidget->count() == 1) { m_ui.resultTabWidget->tabBar()->hide(); } } void KatePluginSearchView::resultTabChanged(int index) { if (index < 0) { return; } Results *res = qobject_cast(m_ui.resultTabWidget->widget(index)); if (!res) { // qDebug() << "No res found"; return; } m_ui.searchCombo->blockSignals(true); m_ui.matchCase->blockSignals(true); m_ui.useRegExp->blockSignals(true); m_ui.searchPlaceCombo->blockSignals(true); m_ui.searchCombo->lineEdit()->setText(m_ui.resultTabWidget->tabText(index)); m_ui.useRegExp->setChecked(res->useRegExp); m_ui.matchCase->setChecked(res->matchCase); m_ui.searchPlaceCombo->setCurrentIndex(res->searchPlaceIndex); m_ui.searchCombo->blockSignals(false); m_ui.matchCase->blockSignals(false); m_ui.useRegExp->blockSignals(false); m_ui.searchPlaceCombo->blockSignals(false); searchPlaceChanged(); } void KatePluginSearchView::onResize(const QSize &size) { bool vertical = size.width() < size.height(); if (!m_isLeftRight && vertical) { m_isLeftRight = true; m_ui.gridLayout->addWidget(m_ui.searchCombo, 0, 1, 1, 8); m_ui.gridLayout->addWidget(m_ui.findLabel, 0, 0); m_ui.gridLayout->addWidget(m_ui.searchButton, 1, 0, 1, 2); m_ui.gridLayout->addWidget(m_ui.stopAndNext, 1, 2); m_ui.gridLayout->addWidget(m_ui.searchPlaceCombo, 1, 3, 1, 3); m_ui.gridLayout->addWidget(m_ui.displayOptions, 1, 6); m_ui.gridLayout->addWidget(m_ui.matchCase, 1, 7); m_ui.gridLayout->addWidget(m_ui.useRegExp, 1, 8); m_ui.gridLayout->addWidget(m_ui.replaceCombo, 2, 1, 1, 8); m_ui.gridLayout->addWidget(m_ui.replaceLabel, 2, 0); m_ui.gridLayout->addWidget(m_ui.replaceButton, 3, 0, 1, 2); m_ui.gridLayout->addWidget(m_ui.replaceCheckedBtn, 3, 2); m_ui.gridLayout->addWidget(m_ui.expandResults, 3, 7); m_ui.gridLayout->addWidget(m_ui.newTabButton, 3, 8); m_ui.gridLayout->setColumnStretch(4, 2); m_ui.gridLayout->setColumnStretch(2, 0); } else if (m_isLeftRight && !vertical) { m_isLeftRight = false; m_ui.gridLayout->addWidget(m_ui.searchCombo, 0, 2); m_ui.gridLayout->addWidget(m_ui.findLabel, 0, 1); m_ui.gridLayout->addWidget(m_ui.searchButton, 0, 3); m_ui.gridLayout->addWidget(m_ui.stopAndNext, 0, 4); m_ui.gridLayout->addWidget(m_ui.searchPlaceCombo, 0, 5, 1, 4); m_ui.gridLayout->addWidget(m_ui.matchCase, 1, 5); m_ui.gridLayout->addWidget(m_ui.useRegExp, 1, 6); m_ui.gridLayout->addWidget(m_ui.replaceCombo, 1, 2); m_ui.gridLayout->addWidget(m_ui.replaceLabel, 1, 1); m_ui.gridLayout->addWidget(m_ui.replaceButton, 1, 3); m_ui.gridLayout->addWidget(m_ui.replaceCheckedBtn, 1, 4); m_ui.gridLayout->addWidget(m_ui.expandResults, 1, 8); m_ui.gridLayout->addWidget(m_ui.newTabButton, 0, 0); m_ui.gridLayout->addWidget(m_ui.displayOptions, 1, 0); m_ui.gridLayout->setColumnStretch(4, 0); m_ui.gridLayout->setColumnStretch(2, 2); m_ui.findLabel->setAlignment(Qt::AlignRight); m_ui.replaceLabel->setAlignment(Qt::AlignRight); } } bool KatePluginSearchView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); QTreeWidget *tree = qobject_cast(obj); if (tree) { if (ke->matches(QKeySequence::Copy)) { // user pressed ctrl+c -> copy full URL to the clipboard QVariant variant = tree->currentItem()->data(0, ReplaceMatches::FileUrlRole); QApplication::clipboard()->setText(variant.toString()); event->accept(); return true; } if (ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Return) { if (tree->currentItem()) { itemSelected(tree->currentItem()); event->accept(); return true; } } } // NOTE: Qt::Key_Escape is handled by handleEsc } if (event->type() == QEvent::Resize) { QResizeEvent *re = static_cast(event); if (obj == m_toolView) { onResize(re->size()); } } return QObject::eventFilter(obj, event); } void KatePluginSearchView::searchContextMenu(const QPoint &pos) { QSet actionPointers; QMenu *const contextMenu = m_ui.searchCombo->lineEdit()->createStandardContextMenu(); if (!contextMenu) return; if (m_ui.useRegExp->isChecked()) { QMenu *menu = contextMenu->addMenu(i18n("Add...")); if (!menu) return; menu->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); addRegexHelperActionsForSearch(&actionPointers, menu); } // Show menu and act QAction *const result = contextMenu->exec(m_ui.searchCombo->mapToGlobal(pos)); regexHelperActOnAction(result, actionPointers, m_ui.searchCombo->lineEdit()); } void KatePluginSearchView::replaceContextMenu(const QPoint &pos) { QMenu *const contextMenu = m_ui.replaceCombo->lineEdit()->createStandardContextMenu(); if (!contextMenu) return; QMenu *menu = contextMenu->addMenu(i18n("Add...")); if (!menu) return; menu->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); QSet actionPointers; addSpecialCharsHelperActionsForReplace(&actionPointers, menu); if (m_ui.useRegExp->isChecked()) { addRegexHelperActionsForReplace(&actionPointers, menu); } // Show menu and act QAction *const result = contextMenu->exec(m_ui.replaceCombo->mapToGlobal(pos)); regexHelperActOnAction(result, actionPointers, m_ui.replaceCombo->lineEdit()); } void KatePluginSearchView::slotPluginViewCreated(const QString &name, QObject *pluginView) { // add view if (pluginView && name == QLatin1String("kateprojectplugin")) { m_projectPluginView = pluginView; slotProjectFileNameChanged(); connect(pluginView, SIGNAL(projectFileNameChanged()), this, SLOT(slotProjectFileNameChanged())); } } void KatePluginSearchView::slotPluginViewDeleted(const QString &name, QObject *) { // remove view if (name == QLatin1String("kateprojectplugin")) { m_projectPluginView = nullptr; slotProjectFileNameChanged(); } } void KatePluginSearchView::slotProjectFileNameChanged() { // query new project file name QString projectFileName; if (m_projectPluginView) { projectFileName = m_projectPluginView->property("projectFileName").toString(); } // have project, enable gui for it if (!projectFileName.isEmpty()) { if (m_ui.searchPlaceCombo->count() <= Project) { // add "in Project" m_ui.searchPlaceCombo->addItem(QIcon::fromTheme(QStringLiteral("project-open")), i18n("In Current Project")); // add "in Open Projects" m_ui.searchPlaceCombo->addItem(QIcon::fromTheme(QStringLiteral("project-open")), i18n("In All Open Projects")); if (m_projectSearchPlaceIndex >= Project) { // switch to search "in (all) Project" setSearchPlace(m_projectSearchPlaceIndex); m_projectSearchPlaceIndex = 0; } } } // else: disable gui for it else { if (m_ui.searchPlaceCombo->count() >= Project) { // switch to search "in Open files", if "in Project" is active int searchPlaceIndex = m_ui.searchPlaceCombo->currentIndex(); if (searchPlaceIndex >= Project) { m_projectSearchPlaceIndex = searchPlaceIndex; setSearchPlace(OpenFiles); } // remove "in Project" and "in all projects" while (m_ui.searchPlaceCombo->count() > Project) { m_ui.searchPlaceCombo->removeItem(m_ui.searchPlaceCombo->count() - 1); } } } } KateSearchCommand::KateSearchCommand(QObject *parent) : KTextEditor::Command(QStringList() << QStringLiteral("grep") << QStringLiteral("newGrep") << QStringLiteral("search") << QStringLiteral("newSearch") << QStringLiteral("pgrep") << QStringLiteral("newPGrep"), parent) { } bool KateSearchCommand::exec(KTextEditor::View * /*view*/, const QString &cmd, QString & /*msg*/, const KTextEditor::Range &) { // create a list of args QStringList args(cmd.split(QLatin1Char(' '), QString::KeepEmptyParts)); QString command = args.takeFirst(); QString searchText = args.join(QLatin1Char(' ')); if (command == QLatin1String("grep") || command == QLatin1String("newGrep")) { emit setSearchPlace(KatePluginSearchView::Folder); emit setCurrentFolder(); if (command == QLatin1String("newGrep")) emit newTab(); } else if (command == QLatin1String("search") || command == QLatin1String("newSearch")) { emit setSearchPlace(KatePluginSearchView::OpenFiles); if (command == QLatin1String("newSearch")) emit newTab(); } else if (command == QLatin1String("pgrep") || command == QLatin1String("newPGrep")) { emit setSearchPlace(KatePluginSearchView::Project); if (command == QLatin1String("newPGrep")) emit newTab(); } emit setSearchString(searchText); emit startSearch(); return true; } bool KateSearchCommand::help(KTextEditor::View * /*view*/, const QString &cmd, QString &msg) { if (cmd.startsWith(QLatin1String("grep"))) { msg = i18n("Usage: grep [pattern to search for in folder]"); } else if (cmd.startsWith(QLatin1String("newGrep"))) { msg = i18n("Usage: newGrep [pattern to search for in folder]"); } else if (cmd.startsWith(QLatin1String("search"))) { msg = i18n("Usage: search [pattern to search for in open files]"); } else if (cmd.startsWith(QLatin1String("newSearch"))) { msg = i18n("Usage: search [pattern to search for in open files]"); } else if (cmd.startsWith(QLatin1String("pgrep"))) { msg = i18n("Usage: pgrep [pattern to search for in current project]"); } else if (cmd.startsWith(QLatin1String("newPGrep"))) { msg = i18n("Usage: newPGrep [pattern to search for in current project]"); } return true; } #include "plugin_search.moc" // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/kate/kateconfigdialog.cpp b/kate/kateconfigdialog.cpp index 4fd8cab3c..42813207f 100644 --- a/kate/kateconfigdialog.cpp +++ b/kate/kateconfigdialog.cpp @@ -1,491 +1,483 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2002 Joseph Wenninger Copyright (C) 2007 Mirko Stocker This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kateconfigdialog.h" #include "ui_sessionconfigwidget.h" #include "kateapp.h" #include "kateconfigplugindialogpage.h" #include "katedebug.h" #include "katedocmanager.h" #include "katemainwindow.h" #include "katepluginmanager.h" #include "katequickopenmodel.h" #include "katesessionmanager.h" #include "kateviewmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KateConfigDialog::KateConfigDialog(KateMainWindow *parent, KTextEditor::View *view) : KPageDialog(parent) , m_mainWindow(parent) , m_view(view) { setFaceType(Tree); setWindowTitle(i18n("Configure")); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel | QDialogButtonBox::Help); setObjectName(QStringLiteral("configdialog")); KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup cgGeneral = KConfigGroup(config, "General"); buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(false); KPageWidgetItem *applicationItem = addPage(new QWidget, i18n("Application")); applicationItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other"))); applicationItem->setHeader(i18n("Application Options")); applicationItem->setCheckable(false); applicationItem->setEnabled(false); m_applicationPage = applicationItem; // BEGIN General page QFrame *generalFrame = new QFrame; KPageWidgetItem *item = addSubPage(applicationItem, generalFrame, i18n("General")); item->setHeader(i18n("General Options")); item->setIcon(QIcon::fromTheme(QStringLiteral("go-home"))); setCurrentPage(item); QVBoxLayout *layout = new QVBoxLayout(generalFrame); layout->setContentsMargins(0, 0, 0, 0); // GROUP with the one below: "Behavior" QGroupBox *buttonGroup = new QGroupBox(i18n("&Behavior"), generalFrame); QVBoxLayout *vbox = new QVBoxLayout; layout->addWidget(buttonGroup); // modified files notification m_modNotifications = new QCheckBox(i18n("Wa&rn about files modified by foreign processes"), buttonGroup); m_modNotifications->setChecked(parent->modNotificationEnabled()); m_modNotifications->setWhatsThis( i18n("If enabled, when Kate receives focus you will be asked what to do with " "files that have been modified on the hard disk. If not enabled, you will " "be asked what to do with a file that has been modified on the hard disk only " "when that file is tried to be saved.")); connect(m_modNotifications, &QCheckBox::toggled, this, &KateConfigDialog::slotChanged); vbox->addWidget(m_modNotifications); // Closing last file closes Kate m_modCloseAfterLast = new QCheckBox(i18n("Close Kate entirely when the last file is closed"), buttonGroup); m_modCloseAfterLast->setChecked(parent->modCloseAfterLast()); m_modCloseAfterLast->setWhatsThis( i18n("If enabled, Kate will shutdown when the last file being edited is closed, " "otherwise a blank page will open so that you can start a new file.")); connect(m_modCloseAfterLast, &QCheckBox::toggled, this, &KateConfigDialog::slotChanged); vbox->addWidget(m_modCloseAfterLast); buttonGroup->setLayout(vbox); // GROUP with the one below: "Meta-information" buttonGroup = new QGroupBox(i18n("Meta-Information"), generalFrame); vbox = new QVBoxLayout; layout->addWidget(buttonGroup); // save meta infos m_saveMetaInfos = new QCheckBox(buttonGroup); m_saveMetaInfos->setText(i18n("Keep &meta-information past sessions")); m_saveMetaInfos->setChecked(KateApp::self()->documentManager()->getSaveMetaInfos()); m_saveMetaInfos->setWhatsThis( i18n("Check this if you want document configuration like for example " "bookmarks to be saved past editor sessions. The configuration will be " "restored if the document has not changed when reopened.")); connect(m_saveMetaInfos, &QCheckBox::toggled, this, &KateConfigDialog::slotChanged); vbox->addWidget(m_saveMetaInfos); // meta infos days QFrame *metaInfos = new QFrame(buttonGroup); QHBoxLayout *hlayout = new QHBoxLayout(metaInfos); metaInfos->setEnabled(KateApp::self()->documentManager()->getSaveMetaInfos()); QLabel *label = new QLabel(i18n("&Delete unused meta-information after:"), metaInfos); hlayout->addWidget(label); m_daysMetaInfos = new KPluralHandlingSpinBox(metaInfos); m_daysMetaInfos->setMaximum(180); m_daysMetaInfos->setSpecialValueText(i18nc("The special case of 'Delete unused meta-information after'", "(never)")); m_daysMetaInfos->setSuffix(ki18ncp("The suffix of 'Delete unused meta-information after'", " day", " days")); m_daysMetaInfos->setValue(KateApp::self()->documentManager()->getDaysMetaInfos()); hlayout->addWidget(m_daysMetaInfos); label->setBuddy(m_daysMetaInfos); connect(m_saveMetaInfos, &QCheckBox::toggled, metaInfos, &QFrame::setEnabled); connect(m_daysMetaInfos, static_cast(&KPluralHandlingSpinBox::valueChanged), this, &KateConfigDialog::slotChanged); vbox->addWidget(metaInfos); buttonGroup->setLayout(vbox); // quick search buttonGroup = new QGroupBox(i18n("&Quick Open"), generalFrame); vbox = new QVBoxLayout; buttonGroup->setLayout(vbox); // quick open match mode hlayout = new QHBoxLayout; label = new QLabel(i18n("&Match Mode:"), buttonGroup); hlayout->addWidget(label); m_cmbQuickOpenMatchMode = new QComboBox(buttonGroup); hlayout->addWidget(m_cmbQuickOpenMatchMode); label->setBuddy(m_cmbQuickOpenMatchMode); m_cmbQuickOpenMatchMode->addItem(i18n("Filename"), QVariant(KateQuickOpenModel::Columns::FileName)); m_cmbQuickOpenMatchMode->addItem(i18n("Filepath"), QVariant(KateQuickOpenModel::Columns::FilePath)); m_cmbQuickOpenMatchMode->setCurrentIndex(m_cmbQuickOpenMatchMode->findData(m_mainWindow->quickOpenMatchMode())); m_mainWindow->setQuickOpenMatchMode(m_cmbQuickOpenMatchMode->currentData().toInt()); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) connect(m_cmbQuickOpenMatchMode, static_cast(&QComboBox::currentIndexChanged), this, &KateConfigDialog::slotChanged); -#else - connect(m_cmbQuickOpenMatchMode, static_cast(&QComboBox::currentIndexChanged) , this, &KateConfigDialog::slotChanged); -#endif vbox->addLayout(hlayout); // quick open list mode hlayout = new QHBoxLayout; label = new QLabel(i18n("&List Mode:"), buttonGroup); hlayout->addWidget(label); m_cmbQuickOpenListMode = new QComboBox(buttonGroup); hlayout->addWidget(m_cmbQuickOpenListMode); label->setBuddy(m_cmbQuickOpenListMode); m_cmbQuickOpenListMode->addItem(i18n("Current Project Files"), QVariant(KateQuickOpenModel::List::CurrentProject)); m_cmbQuickOpenListMode->addItem(i18n("All Projects Files"), QVariant(KateQuickOpenModel::List::AllProjects)); m_cmbQuickOpenListMode->setCurrentIndex(m_cmbQuickOpenListMode->findData(m_mainWindow->quickOpenListMode())); m_mainWindow->setQuickOpenListMode(static_cast(m_cmbQuickOpenListMode->currentData().toInt())); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) connect(m_cmbQuickOpenListMode, static_cast(&QComboBox::currentIndexChanged), this, &KateConfigDialog::slotChanged); -#else - connect(m_cmbQuickOpenListMode, static_cast(&QComboBox::currentIndexChanged) , this, &KateConfigDialog::slotChanged); -#endif vbox->addLayout(hlayout); layout->addWidget(buttonGroup); layout->addStretch(1); // :-] works correct without autoadd // END General page // BEGIN Session page QWidget *sessionsPage = new QWidget(); item = addSubPage(applicationItem, sessionsPage, i18n("Sessions")); item->setHeader(i18n("Session Management")); item->setIcon(QIcon::fromTheme(QStringLiteral("view-history"))); sessionConfigUi = new Ui::SessionConfigWidget(); sessionConfigUi->setupUi(sessionsPage); // restore view config sessionConfigUi->restoreVC->setChecked(cgGeneral.readEntry("Restore Window Configuration", true)); connect(sessionConfigUi->restoreVC, &QCheckBox::toggled, this, &KateConfigDialog::slotChanged); sessionConfigUi->spinBoxRecentFilesCount->setValue(recentFilesMaxCount()); connect(sessionConfigUi->spinBoxRecentFilesCount, static_cast(&QSpinBox::valueChanged), this, &KateConfigDialog::slotChanged); QString sesStart(cgGeneral.readEntry("Startup Session", "manual")); if (sesStart == QLatin1String("new")) sessionConfigUi->startNewSessionRadioButton->setChecked(true); else if (sesStart == QLatin1String("last")) sessionConfigUi->loadLastUserSessionRadioButton->setChecked(true); else sessionConfigUi->manuallyChooseSessionRadioButton->setChecked(true); connect(sessionConfigUi->startNewSessionRadioButton, &QRadioButton::toggled, this, &KateConfigDialog::slotChanged); connect(sessionConfigUi->loadLastUserSessionRadioButton, &QRadioButton::toggled, this, &KateConfigDialog::slotChanged); connect(sessionConfigUi->manuallyChooseSessionRadioButton, &QRadioButton::toggled, this, &KateConfigDialog::slotChanged); // END Session page // BEGIN Plugins page QFrame *page = new QFrame(this); QVBoxLayout *vlayout = new QVBoxLayout(page); vlayout->setContentsMargins(0, 0, 0, 0); vlayout->setSpacing(0); KateConfigPluginPage *configPluginPage = new KateConfigPluginPage(page, this); vlayout->addWidget(configPluginPage); connect(configPluginPage, &KateConfigPluginPage::changed, this, &KateConfigDialog::slotChanged); item = addSubPage(applicationItem, page, i18n("Plugins")); item->setHeader(i18n("Plugin Manager")); item->setIcon(QIcon::fromTheme(QStringLiteral("preferences-plugin"))); const KatePluginList &pluginList(KateApp::self()->pluginManager()->pluginList()); for (const KatePluginInfo &plugin : pluginList) { if (plugin.load) { addPluginPage(plugin.plugin); } } // END Plugins page #ifdef WITH_KUSERFEEDBACK // KUserFeedback Config page = new QFrame(this); vlayout = new QVBoxLayout(page); vlayout->setContentsMargins(0, 0, 0, 0); vlayout->setSpacing(0); m_userFeedbackWidget = new KUserFeedback::FeedbackConfigWidget(page); m_userFeedbackWidget->setFeedbackProvider(&KateApp::self()->userFeedbackProvider()); connect(m_userFeedbackWidget, &KUserFeedback::FeedbackConfigWidget::configurationChanged, this, &KateConfigDialog::slotChanged); vlayout->addWidget(m_userFeedbackWidget); item = addSubPage(applicationItem, page, i18n("User Feedback")); item->setHeader(i18n("User Feedback")); item->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-locale"))); #endif // editor widgets from kwrite/kwdialog m_editorPage = addPage(new QWidget, i18n("Editor Component")); m_editorPage->setIcon(QIcon::fromTheme(QStringLiteral("accessories-text-editor"))); m_editorPage->setHeader(i18n("Editor Component Options")); m_editorPage->setCheckable(false); m_editorPage->setEnabled(false); addEditorPages(); connect(this, &KateConfigDialog::accepted, this, &KateConfigDialog::slotApply); connect(buttonBox()->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &KateConfigDialog::slotApply); connect(buttonBox()->button(QDialogButtonBox::Help), &QPushButton::clicked, this, &KateConfigDialog::slotHelp); connect(this, &KateConfigDialog::currentPageChanged, this, &KateConfigDialog::slotCurrentPageChanged); resize(minimumSizeHint()); // ensure no stray signals already set this! m_dataChanged = false; } KateConfigDialog::~KateConfigDialog() { delete sessionConfigUi; } void KateConfigDialog::addEditorPages() { for (int i = 0; i < KTextEditor::Editor::instance()->configPages(); ++i) { KTextEditor::ConfigPage *page = KTextEditor::Editor::instance()->configPage(i, this); connect(page, &KTextEditor::ConfigPage::changed, this, &KateConfigDialog::slotChanged); m_editorPages.push_back(page); KPageWidgetItem *item = addSubPage(m_editorPage, page, page->name()); item->setHeader(page->fullName()); item->setIcon(page->icon()); } } void KateConfigDialog::addPluginPage(KTextEditor::Plugin *plugin) { for (int i = 0; i < plugin->configPages(); i++) { QFrame *page = new QFrame(); QVBoxLayout *layout = new QVBoxLayout(page); layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); KTextEditor::ConfigPage *cp = plugin->configPage(i, page); page->layout()->addWidget(cp); KPageWidgetItem *item = addSubPage(m_applicationPage, page, cp->name()); item->setHeader(cp->fullName()); item->setIcon(cp->icon()); PluginPageListItem *info = new PluginPageListItem; info->plugin = plugin; info->pageParent = page; info->pluginPage = cp; info->idInPlugin = i; info->pageWidgetItem = item; connect(info->pluginPage, &KTextEditor::ConfigPage::changed, this, &KateConfigDialog::slotChanged); m_pluginPages.insert(item, info); } } void KateConfigDialog::slotCurrentPageChanged(KPageWidgetItem *current, KPageWidgetItem * /*before*/) { PluginPageListItem *info = m_pluginPages[current]; if (!info) { return; } if (info->pluginPage) { return; } qCDebug(LOG_KATE) << "creating config page (should not get here)"; info->pluginPage = info->plugin->configPage(info->idInPlugin, info->pageParent); info->pageParent->layout()->addWidget(info->pluginPage); info->pluginPage->show(); connect(info->pluginPage, &KTextEditor::ConfigPage::changed, this, &KateConfigDialog::slotChanged); } void KateConfigDialog::removePluginPage(KTextEditor::Plugin *plugin) { QList remove; for (QHash::const_iterator it = m_pluginPages.constBegin(); it != m_pluginPages.constEnd(); ++it) { PluginPageListItem *pli = it.value(); if (!pli) { continue; } if (pli->plugin == plugin) { remove.append(it.key()); } } qCDebug(LOG_KATE) << remove.count(); while (!remove.isEmpty()) { KPageWidgetItem *wItem = remove.takeLast(); PluginPageListItem *pItem = m_pluginPages.take(wItem); delete pItem->pluginPage; delete pItem->pageParent; removePage(wItem); delete pItem; } } void KateConfigDialog::slotApply() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); // if data changed apply the kate app stuff if (m_dataChanged) { KConfigGroup cg = KConfigGroup(config, "General"); cg.writeEntry("Restore Window Configuration", sessionConfigUi->restoreVC->isChecked()); cg.writeEntry("Recent File List Entry Count", sessionConfigUi->spinBoxRecentFilesCount->value()); if (sessionConfigUi->startNewSessionRadioButton->isChecked()) { cg.writeEntry("Startup Session", "new"); } else if (sessionConfigUi->loadLastUserSessionRadioButton->isChecked()) { cg.writeEntry("Startup Session", "last"); } else { cg.writeEntry("Startup Session", "manual"); } cg.writeEntry("Save Meta Infos", m_saveMetaInfos->isChecked()); KateApp::self()->documentManager()->setSaveMetaInfos(m_saveMetaInfos->isChecked()); cg.writeEntry("Days Meta Infos", m_daysMetaInfos->value()); KateApp::self()->documentManager()->setDaysMetaInfos(m_daysMetaInfos->value()); cg.writeEntry("Modified Notification", m_modNotifications->isChecked()); m_mainWindow->setModNotificationEnabled(m_modNotifications->isChecked()); cg.writeEntry("Close After Last", m_modCloseAfterLast->isChecked()); m_mainWindow->setModCloseAfterLast(m_modCloseAfterLast->isChecked()); cg.writeEntry("Quick Open Search Mode", m_cmbQuickOpenMatchMode->currentData().toInt()); m_mainWindow->setQuickOpenMatchMode(m_cmbQuickOpenMatchMode->currentData().toInt()); cg.writeEntry("Quick Open List Mode", m_cmbQuickOpenListMode->currentData().toInt()); m_mainWindow->setQuickOpenListMode(static_cast(m_cmbQuickOpenListMode->currentData().toInt())); // patch document modified warn state const QList &docs = KateApp::self()->documentManager()->documentList(); for (KTextEditor::Document *doc : docs) if (qobject_cast(doc)) { qobject_cast(doc)->setModifiedOnDiskWarning(!m_modNotifications->isChecked()); } m_mainWindow->saveOptions(); // save plugin config !! KateSessionManager *sessionmanager = KateApp::self()->sessionManager(); KConfig *sessionConfig = sessionmanager->activeSession()->config(); KateApp::self()->pluginManager()->writeConfig(sessionConfig); #ifdef WITH_KUSERFEEDBACK // set current active mode + write back the config for future starts KateApp::self()->userFeedbackProvider().setTelemetryMode(m_userFeedbackWidget->telemetryMode()); KateApp::self()->userFeedbackProvider().setSurveyInterval(m_userFeedbackWidget->surveyInterval()); #endif } for (PluginPageListItem *plugin : qAsConst(m_pluginPages)) { if (!plugin) { continue; } if (plugin->pluginPage) { plugin->pluginPage->apply(); } } // apply ktexteditor pages for (KTextEditor::ConfigPage *page : qAsConst(m_editorPages)) page->apply(); config->sync(); m_dataChanged = false; buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(false); } void KateConfigDialog::slotChanged() { m_dataChanged = true; buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(true); } void KateConfigDialog::showAppPluginPage(KTextEditor::Plugin *p, int id) { for (PluginPageListItem *plugin : qAsConst(m_pluginPages)) { if ((plugin->plugin == p) && (id == plugin->idInPlugin)) { setCurrentPage(plugin->pageWidgetItem); break; } } } void KateConfigDialog::slotHelp() { QDesktopServices::openUrl(QUrl(QStringLiteral("help:/"))); } int KateConfigDialog::recentFilesMaxCount() { int maxItems = KConfigGroup(KSharedConfig::openConfig(), "General").readEntry("Recent File List Entry Count", 10); return maxItems; } void KateConfigDialog::closeEvent(QCloseEvent *event) { if (!m_dataChanged) { event->accept(); return; } const auto response = KMessageBox::warningYesNoCancel(this, i18n("You have unsaved changes. Do you want to apply the changes or discard them?"), i18n("Warning"), KStandardGuiItem::save(), KStandardGuiItem::discard(), KStandardGuiItem::cancel()); switch (response) { case KMessageBox::Yes: slotApply(); Q_FALLTHROUGH(); case KMessageBox::No: event->accept(); break; case KMessageBox::Cancel: event->ignore(); break; default: break; } }