diff --git a/plugins/appwizard/appwizarddialog.cpp b/plugins/appwizard/appwizarddialog.cpp index 7b84646e2..12f0fb439 100644 --- a/plugins/appwizard/appwizarddialog.cpp +++ b/plugins/appwizard/appwizarddialog.cpp @@ -1,107 +1,107 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * 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. * * * ***************************************************************************/ #include "appwizarddialog.h" #include #include #include #include #include #include "projecttemplatesmodel.h" #include "projectselectionpage.h" #include "projectvcspage.h" -AppWizardDialog::AppWizardDialog(KDevelop::IPluginController* pluginController, ProjectTemplatesModel* templatesModel, QWidget *parent, Qt::WindowFlags flags) - :KAssistantDialog(parent, flags) +AppWizardDialog::AppWizardDialog(KDevelop::IPluginController* pluginController, ProjectTemplatesModel* templatesModel, QWidget *parent) + : KAssistantDialog(parent) { setWindowTitle(i18n("Create New Project")); // KAssistantDialog creates a help button by default, no option to prevent that QPushButton *helpButton = button(QDialogButtonBox::Help); if (helpButton) { buttonBox()->removeButton(helpButton); delete helpButton; } m_selectionPage = new ProjectSelectionPage(templatesModel, this); m_vcsPage = new ProjectVcsPage( pluginController, this ); m_vcsPage->setSourceLocation( m_selectionPage->location() ); connect( m_selectionPage, &ProjectSelectionPage::locationChanged, m_vcsPage, &ProjectVcsPage::setSourceLocation ); m_pageItems[m_selectionPage] = addPage(m_selectionPage, i18nc("Page for general configuration options", "General")); m_pageItems[m_vcsPage] = addPage(m_vcsPage, i18nc("Page for version control options", "Version Control") ); setValid( m_pageItems[m_selectionPage], false ); m_invalidMapper = new QSignalMapper(this); m_invalidMapper->setMapping(m_selectionPage, m_selectionPage); m_invalidMapper->setMapping(m_vcsPage, m_vcsPage); m_validMapper = new QSignalMapper(this); m_validMapper->setMapping(m_selectionPage, m_selectionPage); m_validMapper->setMapping(m_vcsPage, m_vcsPage); connect( m_selectionPage, &ProjectSelectionPage::invalid, m_invalidMapper, static_cast(&QSignalMapper::map) ); connect( m_selectionPage, &ProjectSelectionPage::valid, m_validMapper, static_cast(&QSignalMapper::map) ); connect( m_vcsPage, &ProjectVcsPage::invalid, m_invalidMapper, static_cast(&QSignalMapper::map) ); connect( m_vcsPage, &ProjectVcsPage::valid, m_validMapper, static_cast(&QSignalMapper::map) ); connect( m_validMapper, static_cast(&QSignalMapper::mapped), this, &AppWizardDialog::pageValid ); connect( m_invalidMapper, static_cast(&QSignalMapper::mapped), this, &AppWizardDialog::pageInValid ); } ApplicationInfo AppWizardDialog::appInfo() const { ApplicationInfo a; a.name = m_selectionPage->projectName(); a.location = m_selectionPage->location(); a.appTemplate = m_selectionPage->selectedTemplate(); a.vcsPluginName = m_vcsPage->pluginName(); if( !m_vcsPage->pluginName().isEmpty() ) { a.repository = m_vcsPage->destination(); a.sourceLocation = m_vcsPage->source(); a.importCommitMessage = m_vcsPage->commitMessage(); } else { a.repository = KDevelop::VcsLocation(); a.sourceLocation.clear(); a.importCommitMessage.clear(); } return a; } void AppWizardDialog::pageValid( QWidget* w ) { if( m_pageItems.contains(w) ) setValid( m_pageItems[w], true ); } void AppWizardDialog::pageInValid( QWidget* w ) { if( m_pageItems.contains(w) ) setValid( m_pageItems[w], false ); } void AppWizardDialog::next() { AppWizardPageWidget* w = qobject_cast(currentPage()->widget()); if (!w || w->shouldContinue()) { KAssistantDialog::next(); } } diff --git a/plugins/appwizard/appwizarddialog.h b/plugins/appwizard/appwizarddialog.h index 89ac34dff..22ace6d6d 100644 --- a/plugins/appwizard/appwizarddialog.h +++ b/plugins/appwizard/appwizarddialog.h @@ -1,64 +1,64 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * 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. * * * ***************************************************************************/ #ifndef _APPWIZARDDIALOG_H_ #define _APPWIZARDDIALOG_H_ #include #include #include #include class KPageWidgetItem; class QSignalMapper; class ProjectSelectionPage; class ProjectVcsPage; class ProjectTemplatesModel; namespace KDevelop { class IPluginController; } class ApplicationInfo { public: QString name; QUrl location; QString vcsPluginName; QUrl sourceLocation; KDevelop::VcsLocation repository; QString importCommitMessage; QString appTemplate; }; class AppWizardDialog: public KAssistantDialog { Q_OBJECT public: AppWizardDialog( KDevelop::IPluginController*, ProjectTemplatesModel*, - QWidget *parent = nullptr, Qt::WindowFlags flags = nullptr); + QWidget *parent = nullptr); ApplicationInfo appInfo() const; private slots: void pageInValid( QWidget* w ); void pageValid( QWidget* w ); void next() override; private: QMap m_pageItems; QSignalMapper* m_invalidMapper; QSignalMapper* m_validMapper; ProjectSelectionPage* m_selectionPage; ProjectVcsPage* m_vcsPage; }; #endif diff --git a/plugins/externalscript/editexternalscript.cpp b/plugins/externalscript/editexternalscript.cpp index b8c0b3433..5ec756b2a 100644 --- a/plugins/externalscript/editexternalscript.cpp +++ b/plugins/externalscript/editexternalscript.cpp @@ -1,195 +1,196 @@ /* This plugin is part of KDevelop. Copyright (C) 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "editexternalscript.h" #include "externalscriptitem.h" #include #include #include #include -EditExternalScript::EditExternalScript( ExternalScriptItem* item, QWidget* parent, Qt::WindowFlags flags ) - : QDialog( parent, flags ), m_item( item ) +EditExternalScript::EditExternalScript(ExternalScriptItem* item, QWidget* parent) + : QDialog(parent) + , m_item(item) { setupUi(this); connect(buttonBox, &QDialogButtonBox::accepted, this, &EditExternalScript::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &EditExternalScript::reject); shortcutWidget->layout()->setMargin(0); //BEGIN setup tooltips QString tooltip = i18n( "

Defines the command that should be executed when this script is run. Basic shell features of your platform should be available.

\n" "

There are a few placeholders you can use in the command:

\n" "
\n" "
%u
\n" "
Gets replaced by the URL of the active document.
\n" "
%f
\n" "
Gets replaced by the local filepath to the active document.
\n" "
%n
\n" "
Gets replaced by the name of the active document, including its extension.
\n" "
%b
\n" "
Gets replaced by the name of the active document without its extension.
\n" "
%d
\n" "
Gets replaced by the path to the directory of the active document.
\n" "
%p
\n" "
Gets replaced by the URL to the project of the active document.
\n" "
%s
\n" "
Gets replaced with the shell escaped contents of the selection in the active document.
\n" "
%i
\n" "
Gets replaced with the PID of the currently running KDevelop process.
\n" "
\n" "

NOTE: It is your responsibility to prevent running hazardous commands that could lead to data loss.

\n" ); commandEdit->setToolTip( tooltip ); commandLabel->setToolTip( tooltip ); tooltip = i18n( "

Defines what the external script should get as input (via STDIN).

" ); stdinCombo->setToolTip( tooltip ); stdinLabel->setToolTip( tooltip ); tooltip = i18n( "

Defines what should be done with the output (i.e. STDOUT) of the script.

" ); stdoutCombo->setToolTip( tooltip ); stdoutLabel->setToolTip( tooltip ); tooltip = i18n( "

Defines what should be done with the errors (i.e. STDERR) of the script.

" "

Note: if the action is the same as that chosen for the output, the channels will be merged " "and handled together.

" ); stderrCombo->setToolTip( tooltip ); stderrLabel->setToolTip( tooltip ); tooltip = i18n( "

Defines the name of the script. Just for displaying purposes.

" ); nameEdit->setToolTip( tooltip ); nameLabel->setToolTip( tooltip ); tooltip = i18n( "

Defines the shortcut(s) you can use to execute this external script.

" ); shortcutLabel->setToolTip( tooltip ); shortcutWidget->setToolTip( tooltip ); tooltip = i18n( "

Defines whether documents should be saved before the script gets executed.

" ); saveLabel->setToolTip( tooltip ); saveCombo->setToolTip( tooltip ); tooltip = i18n( "

Defines whether the output of the script should be shown in a toolview.

" ); showOutputBox->setToolTip( tooltip ); tooltip = i18n( "

Defines what type of filtering should be applied to the output. E.g. to indicate errors by red text.

" ); outputFilterLabel->setToolTip( tooltip ); outputFilterCombo->setToolTip( tooltip ); //END setup tooltips //BEGIN item to UI copying if ( item->text().isEmpty() ) { setWindowTitle( i18n("Create new external script") ); } else { setWindowTitle( i18n("Edit external script '%1'", item->text()) ); } nameEdit->setText( item->text() ); commandEdit->setText( item->command() ); stdinCombo->setCurrentIndex( item->inputMode() ); stdoutCombo->setCurrentIndex( item->outputMode() ); stderrCombo->setCurrentIndex( item->errorMode() ); saveCombo->setCurrentIndex( item->saveMode() ); shortcutWidget->setShortcut( item->action()->shortcuts() ); showOutputBox->setChecked( item->showOutput() ); outputFilterCombo->setCurrentIndex( item->filterMode() ); //END item to UI copying validate(); nameEdit->setFocus(); connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &EditExternalScript::save); connect(buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &EditExternalScript::save); connect(nameEdit, &QLineEdit::textEdited, this, &EditExternalScript::validate); connect(commandEdit, &QLineEdit::textEdited, this, &EditExternalScript::validate); } EditExternalScript::~EditExternalScript() { } void EditExternalScript::save() { m_item->setText( nameEdit->text() ); m_item->setCommand( commandEdit->text() ); ExternalScriptItem::InputMode inputMode = static_cast( stdinCombo->currentIndex() ); m_item->setInputMode( inputMode ); ExternalScriptItem::OutputMode outputMode = static_cast( stdoutCombo->currentIndex() ); m_item->setOutputMode( outputMode ); ExternalScriptItem::ErrorMode errorMode = static_cast( stderrCombo->currentIndex() ); m_item->setErrorMode( errorMode ); ExternalScriptItem::SaveMode saveMode = static_cast( saveCombo->currentIndex() ); m_item->setSaveMode( saveMode ); m_item->setShowOutput( showOutputBox->isChecked() ); m_item->setFilterMode( outputFilterCombo->currentIndex() ); m_item->action()->setShortcuts( shortcutWidget->shortcut() ); } void EditExternalScript::validate() { bool valid = !nameEdit->text().isEmpty() && !commandEdit->text().isEmpty(); if ( valid ) { KShell::Errors errors = KShell::NoError; KShell::splitArgs( commandEdit->text(), KShell::TildeExpand, &errors ); valid = errors == KShell::NoError; } buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid); buttonBox->button(QDialogButtonBox::Apply)->setEnabled(valid); } // kate: indent-mode cstyle; space-indent on; indent-width 2; replace-tabs on; diff --git a/plugins/externalscript/editexternalscript.h b/plugins/externalscript/editexternalscript.h index 7972bc163..4c6e5c1cf 100644 --- a/plugins/externalscript/editexternalscript.h +++ b/plugins/externalscript/editexternalscript.h @@ -1,49 +1,49 @@ /* This plugin is part of KDevelop. Copyright (C) 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KDEVPLATFORM_PLUGIN_EDITEXTERNALSCRIPT_H #define KDEVPLATFORM_PLUGIN_EDITEXTERNALSCRIPT_H #include #include "ui_editexternalscript.h" class ExternalScriptItem; class EditExternalScript : public QDialog, private Ui::EditExternalScriptBase { Q_OBJECT public: - explicit EditExternalScript( ExternalScriptItem* item, QWidget* parent = nullptr, Qt::WindowFlags flags = nullptr ); + explicit EditExternalScript(ExternalScriptItem* item, QWidget* parent = nullptr); ~EditExternalScript() override; private slots: void save(); void validate(); private: ExternalScriptItem* m_item; }; #endif // KDEVPLATFORM_PLUGIN_EDITEXTERNALSCRIPT_H // kate: indent-mode cstyle; space-indent on; indent-width 2; replace-tabs on; diff --git a/plugins/filetemplates/templateoptionspage.cpp b/plugins/filetemplates/templateoptionspage.cpp index a218e1b59..74d3a6147 100644 --- a/plugins/filetemplates/templateoptionspage.cpp +++ b/plugins/filetemplates/templateoptionspage.cpp @@ -1,173 +1,173 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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 "templateoptionspage.h" #include "templateclassassistant.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; class KDevelop::TemplateOptionsPagePrivate { public: QVector entries; QHash controls; QHash typeProperties; QWidget *firstEditWidget; QList groupBoxes; }; -TemplateOptionsPage::TemplateOptionsPage(QWidget* parent, Qt::WindowFlags f) -: QWidget(parent, f) +TemplateOptionsPage::TemplateOptionsPage(QWidget* parent) +: QWidget(parent) , d(new TemplateOptionsPagePrivate) { d->firstEditWidget = nullptr; d->typeProperties.insert(QStringLiteral("String"), "text"); d->typeProperties.insert(QStringLiteral("Enum"), "currentText"); d->typeProperties.insert(QStringLiteral("Int"), "value"); d->typeProperties.insert(QStringLiteral("Bool"), "checked"); } TemplateOptionsPage::~TemplateOptionsPage() { delete d; } void TemplateOptionsPage::load(const SourceFileTemplate& fileTemplate, TemplateRenderer* renderer) { // TODO: keep any old changed values, as it comes by surprise to have them lost // when going back and forward // clear anything as there is on reentering the page d->entries.clear(); d->controls.clear(); // clear any old option group boxes & the base layout d->firstEditWidget = nullptr; qDeleteAll(d->groupBoxes); d->groupBoxes.clear(); delete layout(); QVBoxLayout* layout = new QVBoxLayout(); for (const auto& optionGroup : fileTemplate.customOptions(renderer)) { QGroupBox* box = new QGroupBox(this); d->groupBoxes.append(box); box->setTitle(optionGroup.name); QFormLayout* formLayout = new QFormLayout; d->entries << optionGroup.options; for (const auto& entry : optionGroup.options) { QWidget* control = nullptr; const QString type = entry.type; if (type == QLatin1String("String")) { control = new QLineEdit(entry.value.toString(), box); } else if (type == QLatin1String("Enum")) { auto input = new QComboBox(box); input->addItems(entry.values); input->setCurrentText(entry.value.toString()); control = input; } else if (type == QLatin1String("Int")) { auto input = new QSpinBox(box); input->setValue(entry.value.toInt()); if (!entry.minValue.isEmpty()) { input->setMinimum(entry.minValue.toInt()); } if (!entry.maxValue.isEmpty()) { input->setMaximum(entry.maxValue.toInt()); } control = input; } else if (type == QLatin1String("Bool")) { bool checked = (QString::compare(entry.value.toString(), QStringLiteral("true"), Qt::CaseInsensitive) == 0); QCheckBox* checkBox = new QCheckBox(box); checkBox->setCheckState(checked ? Qt::Checked : Qt::Unchecked); control = checkBox; } else { qCDebug(PLUGIN_FILETEMPLATES) << "Unrecognized option type" << entry.type; } if (control) { const QString entryLabelText = i18n("%1:", entry.label); QLabel* label = new QLabel(entryLabelText, box); formLayout->addRow(label, control); d->controls.insert(entry.name, control); if (d->firstEditWidget == nullptr) { d->firstEditWidget = control; } } } box->setLayout(formLayout); layout->addWidget(box); } layout->addStretch(); setLayout(layout); } QVariantHash TemplateOptionsPage::templateOptions() const { QVariantHash values; foreach (const SourceFileTemplate::ConfigOption& entry, d->entries) { Q_ASSERT(d->controls.contains(entry.name)); Q_ASSERT(d->typeProperties.contains(entry.type)); values.insert(entry.name, d->controls[entry.name]->property(d->typeProperties[entry.type])); } qCDebug(PLUGIN_FILETEMPLATES) << values.size() << d->entries.size(); return values; } void TemplateOptionsPage::setFocusToFirstEditWidget() { if (d->firstEditWidget) { d->firstEditWidget->setFocus(); } } diff --git a/plugins/filetemplates/templateoptionspage.h b/plugins/filetemplates/templateoptionspage.h index 0de4452fe..f6e2cb5c6 100644 --- a/plugins/filetemplates/templateoptionspage.h +++ b/plugins/filetemplates/templateoptionspage.h @@ -1,87 +1,87 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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. */ #ifndef KDEVPLATFORM_PLUGIN_TEMPLATEOPTIONSPAGE_H #define KDEVPLATFORM_PLUGIN_TEMPLATEOPTIONSPAGE_H #include #include #include "ipagefocus.h" namespace KDevelop { class TemplateRenderer; class SourceFileTemplate; class TemplateClassAssistant; /** * @brief Shows a page for configuring options specified by a class template * * Templates can include a file that specify configuration options. * These can be set by the user before creating the class and are passed to the template. * * @sa SourceFileTemplate::customOptions() **/ class TemplateOptionsPage : public QWidget, public IPageFocus { Q_OBJECT Q_PROPERTY(QVariantHash templateOptions READ templateOptions) public: /** * @brief Create a new template options page * * @param parent the parent template class assistant * @param f window flags, passed to QWidget **/ - explicit TemplateOptionsPage(QWidget* parent, Qt::WindowFlags f = nullptr); + explicit TemplateOptionsPage(QWidget* parent); /** * Destructor **/ ~TemplateOptionsPage() override; /** * Parses template archive file and creates the UI for setting template options. * * @param fileTemplate The template archive file * @param renderer A Grantlee wrapper used to render all the templates **/ void load(const SourceFileTemplate& fileTemplate, TemplateRenderer* renderer); /** * @property templateOptions * * The user-set options. Keys are the options' names, and values are their values. * **/ QVariantHash templateOptions() const; void setFocusToFirstEditWidget() override; private: class TemplateOptionsPagePrivate* const d; }; } #endif // KDEVPLATFORM_PLUGIN_TEMPLATEOPTIONSPAGE_H diff --git a/plugins/filetemplates/templatepreview.cpp b/plugins/filetemplates/templatepreview.cpp index 21e5af60d..7cba5d39c 100644 --- a/plugins/filetemplates/templatepreview.cpp +++ b/plugins/filetemplates/templatepreview.cpp @@ -1,152 +1,152 @@ /* * This file is part of KDevelop * Copyright 2012 Milian Wolff * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "templatepreview.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; TemplatePreviewRenderer::TemplatePreviewRenderer() { QVariantHash vars; vars[QStringLiteral("name")] = "Example"; vars[QStringLiteral("license")] = "This file is licensed under the ExampleLicense 3.0"; // TODO: More variables, preferably the ones from TemplateClassGenerator VariableDescriptionList publicMembers; VariableDescriptionList protectedMembers; VariableDescriptionList privateMembers; publicMembers << VariableDescription(QStringLiteral("int"), QStringLiteral("number")); protectedMembers << VariableDescription(QStringLiteral("string"), QStringLiteral("name")); privateMembers << VariableDescription(QStringLiteral("float"), QStringLiteral("variable")); vars[QStringLiteral("members")] = CodeDescription::toVariantList(publicMembers + protectedMembers + privateMembers); vars[QStringLiteral("public_members")] = CodeDescription::toVariantList(publicMembers); vars[QStringLiteral("protected_members")] = CodeDescription::toVariantList(protectedMembers); vars[QStringLiteral("private_members")] = CodeDescription::toVariantList(privateMembers); FunctionDescriptionList publicFunctions; FunctionDescriptionList protectedFunctions; FunctionDescriptionList privateFunctions; FunctionDescription complexFunction(QStringLiteral("doBar"), VariableDescriptionList(), VariableDescriptionList()); complexFunction.arguments << VariableDescription(QStringLiteral("bool"), QStringLiteral("really")); complexFunction.arguments << VariableDescription(QStringLiteral("int"), QStringLiteral("howMuch")); complexFunction.returnArguments << VariableDescription(QStringLiteral("double"), QString()); publicFunctions << FunctionDescription(QStringLiteral("doFoo"), VariableDescriptionList(), VariableDescriptionList()); publicFunctions << complexFunction; protectedFunctions << FunctionDescription(QStringLiteral("onUpdate"), VariableDescriptionList(), VariableDescriptionList()); vars[QStringLiteral("functions")] = CodeDescription::toVariantList(publicFunctions + protectedFunctions + privateFunctions); vars[QStringLiteral("public_functions")] = CodeDescription::toVariantList(publicFunctions); vars[QStringLiteral("protected_functions")] = CodeDescription::toVariantList(protectedFunctions); vars[QStringLiteral("private_functions")] = CodeDescription::toVariantList(privateFunctions); vars[QStringLiteral("testCases")] = QStringList { QStringLiteral("doFoo"), QStringLiteral("doBar"), QStringLiteral("doMore") }; addVariables(vars); } TemplatePreviewRenderer::~TemplatePreviewRenderer() { } -TemplatePreview::TemplatePreview(QWidget* parent, Qt::WindowFlags f) -: QWidget(parent, f) +TemplatePreview::TemplatePreview(QWidget* parent) + : QWidget(parent) { m_variables[QStringLiteral("APPNAME")] = QStringLiteral("Example"); m_variables[QStringLiteral("APPNAMELC")] = QStringLiteral("example"); m_variables[QStringLiteral("APPNAMEUC")] = QStringLiteral("EXAMPLE"); m_variables[QStringLiteral("APPNAMEID")] = QStringLiteral("Example"); m_variables[QStringLiteral("PROJECTDIR")] = QDir::homePath() + "/projects/ExampleProjectDir"; m_variables[QStringLiteral("PROJECTDIRNAME")] = QStringLiteral("ExampleProjectDir"); m_variables[QStringLiteral("VERSIONCONTROLPLUGIN")] = QStringLiteral("kdevgit"); KTextEditor::Document* doc = KTextEditor::Editor::instance()->createDocument(this); m_preview.reset(doc); m_preview->setReadWrite(false); QVBoxLayout* layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); m_view = m_preview->createView(this); m_view->setStatusBarEnabled(false); if (KTextEditor::ConfigInterface* config = qobject_cast(m_view)) { config->setConfigValue(QStringLiteral("icon-bar"), false); config->setConfigValue(QStringLiteral("folding-bar"), false); config->setConfigValue(QStringLiteral("line-numbers"), false); config->setConfigValue(QStringLiteral("dynamic-word-wrap"), true); } layout->addWidget(m_view); } TemplatePreview::~TemplatePreview() { } QString TemplatePreview::setText(const QString& text, bool isProject, TemplateRenderer::EmptyLinesPolicy policy) { QString rendered; QString errorString; if (!text.isEmpty()) { if (isProject) { rendered = KMacroExpander::expandMacros(text, m_variables); } else { TemplatePreviewRenderer renderer; renderer.setEmptyLinesPolicy(policy); rendered = renderer.render(text); errorString = renderer.errorString(); } } m_preview->setReadWrite(true); m_preview->setText(rendered); m_view->setCursorPosition(KTextEditor::Cursor(0, 0)); m_preview->setReadWrite(false); return errorString; } KTextEditor::Document* TemplatePreview::document() const { return m_preview.data(); } diff --git a/plugins/filetemplates/templatepreview.h b/plugins/filetemplates/templatepreview.h index 90cdbf70e..e5649dd1d 100644 --- a/plugins/filetemplates/templatepreview.h +++ b/plugins/filetemplates/templatepreview.h @@ -1,79 +1,79 @@ /* * This file is part of KDevelop * Copyright 2012 Milian Wolff * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KDEVPLATFORM_PLUGIN_TEMPLATEPREVIEW_H #define KDEVPLATFORM_PLUGIN_TEMPLATEPREVIEW_H #include #include #include namespace KTextEditor { class Document; class View; } /** * A renderer that adds some common variables for previewing purposes. */ class TemplatePreviewRenderer : public KDevelop::TemplateRenderer { public: TemplatePreviewRenderer(); ~TemplatePreviewRenderer() override; }; /** * A KTextEditor::View wrapper to show a preview of a template. */ class TemplatePreview : public QWidget { Q_OBJECT public: - explicit TemplatePreview(QWidget* parent, Qt::WindowFlags f = nullptr); + explicit TemplatePreview(QWidget* parent); ~TemplatePreview() override; /** * Set the template contents which will be rendered. * * @p text the template contents * @p isProject set to true if the contents resemble a project template * @return an error message, or an empty string if everything worked */ QString setText(const QString& text, bool isProject = false, KDevelop::TemplateRenderer::EmptyLinesPolicy policy = KDevelop::TemplateRenderer::TrimEmptyLines); /** * @return The read-only document. */ KTextEditor::Document* document() const; private: Q_DISABLE_COPY(TemplatePreview) QHash m_variables; QScopedPointer m_preview; KTextEditor::View* m_view; }; #endif // KDEVPLATFORM_PLUGIN_TEMPLATEPREVIEW_H diff --git a/plugins/filetemplates/templatepreviewtoolview.cpp b/plugins/filetemplates/templatepreviewtoolview.cpp index 0f43a1808..aea6909b7 100644 --- a/plugins/filetemplates/templatepreviewtoolview.cpp +++ b/plugins/filetemplates/templatepreviewtoolview.cpp @@ -1,178 +1,178 @@ /* * This file is part of KDevelop * Copyright 2012 Miha Čančula * * 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 "templatepreviewtoolview.h" #include "ui_templatepreviewtoolview.h" #include "filetemplatesplugin.h" #include "templatepreview.h" #include #include #include #include #include using namespace KDevelop; -TemplatePreviewToolView::TemplatePreviewToolView(FileTemplatesPlugin* plugin, QWidget* parent, Qt::WindowFlags f) -: QWidget(parent, f) +TemplatePreviewToolView::TemplatePreviewToolView(FileTemplatesPlugin* plugin, QWidget* parent) +: QWidget(parent) , ui(new Ui::TemplatePreviewToolView) , m_original(nullptr) , m_plugin(plugin) { ui->setupUi(this); ui->messageWidget->hide(); ui->emptyLinesPolicyComboBox->setCurrentIndex(1); IDocumentController* dc = ICore::self()->documentController(); if (dc->activeDocument()) { m_original = dc->activeDocument()->textDocument(); } if (m_original) { documentActivated(dc->activeDocument()); } connect(ui->projectRadioButton, &QRadioButton::toggled, this, &TemplatePreviewToolView::selectedRendererChanged); connect(ui->emptyLinesPolicyComboBox, static_cast(&QComboBox::currentIndexChanged), this, &TemplatePreviewToolView::selectedRendererChanged); selectedRendererChanged(); connect(dc, &IDocumentController::documentActivated, this, &TemplatePreviewToolView::documentActivated); connect(dc, &IDocumentController::documentClosed, this, &TemplatePreviewToolView::documentClosed); } TemplatePreviewToolView::~TemplatePreviewToolView() { delete ui; } void TemplatePreviewToolView::documentActivated(KDevelop::IDocument* document) { documentChanged(document->textDocument()); } void TemplatePreviewToolView::documentChanged(KTextEditor::Document* document) { if (!isVisible()) { return; } if (m_original) { disconnect(m_original, &KTextEditor::Document::textChanged, this, &TemplatePreviewToolView::documentChanged); } m_original = document; FileTemplatesPlugin::TemplateType type = FileTemplatesPlugin::NoTemplate; if (m_original) { connect(m_original, &KTextEditor::Document::textChanged, this, &TemplatePreviewToolView::documentChanged); type = m_plugin->determineTemplateType(document->url()); } switch (type) { case FileTemplatesPlugin::NoTemplate: ui->messageWidget->setMessageType(KMessageWidget::Information); if (m_original) { ui->messageWidget->setText(xi18n("The active text document is not a KDevelop template")); } else { ui->messageWidget->setText(i18n("No active text document.")); } ui->messageWidget->animatedShow(); ui->preview->setText(QString()); break; case FileTemplatesPlugin::FileTemplate: ui->classRadioButton->setChecked(true); sourceTextChanged(m_original->text()); break; case FileTemplatesPlugin::ProjectTemplate: ui->projectRadioButton->setChecked(true); sourceTextChanged(m_original->text()); break; } } void TemplatePreviewToolView::showEvent(QShowEvent*) { IDocument* doc = ICore::self()->documentController()->activeDocument(); documentChanged(doc ? doc->textDocument() : nullptr); } void TemplatePreviewToolView::documentClosed(IDocument* document) { m_original = nullptr; if (document && document->textDocument() == m_original) { documentChanged(nullptr); } } void TemplatePreviewToolView::sourceTextChanged(const QString& text) { QString errorString = ui->preview->setText(text, ui->projectRadioButton->isChecked(), m_policy); if (!errorString.isEmpty()) { ui->messageWidget->setMessageType(KMessageWidget::Error); ui->messageWidget->setText(errorString); ui->messageWidget->animatedShow(); } else { ui->messageWidget->animatedHide(); } if (m_original) { ui->preview->document()->setMode(m_original->mode()); } } void TemplatePreviewToolView::selectedRendererChanged() { if (ui->classRadioButton->isChecked()) { TemplateRenderer::EmptyLinesPolicy policy = TemplateRenderer::KeepEmptyLines; switch (ui->emptyLinesPolicyComboBox->currentIndex()) { case 0: policy = TemplateRenderer::KeepEmptyLines; break; case 1: policy = TemplateRenderer::TrimEmptyLines; break; case 2: policy = TemplateRenderer::RemoveEmptyLines; break; } m_policy = policy; } documentChanged(m_original); } diff --git a/plugins/filetemplates/templatepreviewtoolview.h b/plugins/filetemplates/templatepreviewtoolview.h index f711eaa73..6eaf3b501 100644 --- a/plugins/filetemplates/templatepreviewtoolview.h +++ b/plugins/filetemplates/templatepreviewtoolview.h @@ -1,72 +1,72 @@ /* * This file is part of KDevelop * Copyright 2012 Miha Čančula * * 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. */ #ifndef KDEVPLATFORM_PLUGIN_TEMPLATEPREVIEWTOOLVIEW_H #define KDEVPLATFORM_PLUGIN_TEMPLATEPREVIEWTOOLVIEW_H #include #include namespace KTextEditor { class Document; } namespace KDevelop { class IDocument; } namespace Ui { class TemplatePreviewToolView; } class FileTemplatesPlugin; class TemplatePreview; class TemplatePreviewToolView : public QWidget { Q_OBJECT public: - explicit TemplatePreviewToolView(FileTemplatesPlugin* plugin, QWidget* parent, Qt::WindowFlags f = nullptr); + explicit TemplatePreviewToolView(FileTemplatesPlugin* plugin, QWidget* parent); ~TemplatePreviewToolView() override; private: Ui::TemplatePreviewToolView* ui; KTextEditor::Document* m_original; FileTemplatesPlugin* m_plugin; KDevelop::TemplateRenderer::EmptyLinesPolicy m_policy; private slots: void sourceTextChanged(const QString& text); protected: void showEvent(QShowEvent*) override; public slots: void documentActivated(KDevelop::IDocument* document); void documentChanged(KTextEditor::Document* textDocument); void documentClosed(KDevelop::IDocument* document); void selectedRendererChanged(); }; #endif // KDEVPLATFORM_PLUGIN_TEMPLATEPREVIEWTOOLVIEW_H diff --git a/plugins/filetemplates/templateselectionpage.cpp b/plugins/filetemplates/templateselectionpage.cpp index 3a5c43ddd..d47710921 100644 --- a/plugins/filetemplates/templateselectionpage.cpp +++ b/plugins/filetemplates/templateselectionpage.cpp @@ -1,274 +1,274 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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 "templateselectionpage.h" #include "templateclassassistant.h" #include "templatepreview.h" #include #include #include #include #include #include #include #include #include #include "ui_templateselection.h" #include #include #include #include #include #include #include using namespace KDevelop; static const char LastUsedTemplateEntry[] = "LastUsedTemplate"; static const char FileTemplatesGroup[] = "SourceFileTemplates"; class KDevelop::TemplateSelectionPagePrivate { public: explicit TemplateSelectionPagePrivate(TemplateSelectionPage* page_) : page(page_) {} TemplateSelectionPage* page; Ui::TemplateSelection* ui; QString selectedTemplate; TemplateClassAssistant* assistant; TemplatesModel* model; void currentTemplateChanged(const QModelIndex& index); void getMoreClicked(); void loadFileClicked(); void previewTemplate(const QString& templateFile); }; void TemplateSelectionPagePrivate::currentTemplateChanged(const QModelIndex& index) { // delete preview tabs if (!index.isValid() || index.model()->hasChildren(index)) { // invalid or has child assistant->setValid(assistant->currentPage(), false); ui->previewLabel->setVisible(false); ui->tabWidget->setVisible(false); } else { selectedTemplate = model->data(index, TemplatesModel::DescriptionFileRole).toString(); assistant->setValid(assistant->currentPage(), true); previewTemplate(selectedTemplate); ui->previewLabel->setVisible(true); ui->tabWidget->setVisible(true); ui->previewLabel->setText(i18nc("%1: template comment", "Preview: %1", index.data(TemplatesModel::CommentRole).toString())); } } void TemplateSelectionPagePrivate::previewTemplate(const QString& file) { SourceFileTemplate fileTemplate(file); if (!fileTemplate.isValid() || fileTemplate.outputFiles().isEmpty()) { return; } TemplatePreviewRenderer renderer; // set default option values if (fileTemplate.hasCustomOptions()) { QVariantHash extraVars; for (const auto& optionGroup : fileTemplate.customOptions(&renderer)) { for (const auto& entry : optionGroup.options) { extraVars[entry.name] = entry.value; } } renderer.addVariables(extraVars); } renderer.setEmptyLinesPolicy(TemplateRenderer::TrimEmptyLines); QTemporaryDir dir; QUrl base = QUrl::fromLocalFile(dir.path() + QLatin1Char('/')); QHash fileUrls; foreach(const SourceFileTemplate::OutputFile& out, fileTemplate.outputFiles()) { QUrl url = base.resolved(QUrl(renderer.render(out.outputName))); fileUrls.insert(out.identifier, url); } DocumentChangeSet changes = renderer.renderFileTemplate(fileTemplate, base, fileUrls); changes.setActivationPolicy(DocumentChangeSet::DoNotActivate); changes.setUpdateHandling(DocumentChangeSet::NoUpdate); DocumentChangeSet::ChangeResult result = changes.applyAllChanges(); if (!result) { return; } int idx = 0; foreach(const SourceFileTemplate::OutputFile& out, fileTemplate.outputFiles()) { TemplatePreview* preview = nullptr; if (ui->tabWidget->count() > idx) { // reuse existing tab preview = qobject_cast(ui->tabWidget->widget(idx)); ui->tabWidget->setTabText(idx, out.label); Q_ASSERT(preview); } else { // create new tabs on demand preview = new TemplatePreview(page); ui->tabWidget->addTab(preview, out.label); } preview->document()->openUrl(fileUrls.value(out.identifier)); ++idx; } // remove superfluous tabs from last time while (ui->tabWidget->count() > fileUrls.size()) { delete ui->tabWidget->widget(fileUrls.size()); } return; } void TemplateSelectionPagePrivate::getMoreClicked() { KNS3::DownloadDialog(QStringLiteral("kdevfiletemplates.knsrc"), ui->view).exec(); model->refresh(); } void TemplateSelectionPagePrivate::loadFileClicked() { const QStringList filters{ QStringLiteral("application/x-desktop"), QStringLiteral("application/x-bzip-compressed-tar"), QStringLiteral("application/zip") }; QFileDialog dlg(page); dlg.setMimeTypeFilters(filters); dlg.setFileMode(QFileDialog::ExistingFiles); if (!dlg.exec()) { return; } foreach(const QString& fileName, dlg.selectedFiles()) { QString destination = model->loadTemplateFile(fileName); QModelIndexList indexes = model->templateIndexes(destination); int n = indexes.size(); if (n > 1) { ui->view->setCurrentIndex(indexes[1]); } } } void TemplateSelectionPage::saveConfig() { KSharedConfigPtr config; if (IProject* project = ICore::self()->projectController()->findProjectForUrl(d->assistant->baseUrl())) { config = project->projectConfiguration(); } else { config = ICore::self()->activeSession()->config(); } KConfigGroup group(config, FileTemplatesGroup); group.writeEntry(LastUsedTemplateEntry, d->selectedTemplate); group.sync(); } -TemplateSelectionPage::TemplateSelectionPage(TemplateClassAssistant* parent, Qt::WindowFlags f) -: QWidget(parent, f) +TemplateSelectionPage::TemplateSelectionPage(TemplateClassAssistant* parent) +: QWidget(parent) , d(new TemplateSelectionPagePrivate(this)) { d->assistant = parent; d->ui = new Ui::TemplateSelection; d->ui->setupUi(this); d->model = new TemplatesModel(QStringLiteral("kdevfiletemplates"), this); d->model->refresh(); d->ui->view->setLevels(3); d->ui->view->setHeaderLabels(QStringList() << i18n("Language") << i18n("Framework") << i18n("Template")); d->ui->view->setModel(d->model); connect(d->ui->view, &MultiLevelListView::currentIndexChanged, this, [&] (const QModelIndex& index) { d->currentTemplateChanged(index); }); QModelIndex templateIndex; while (d->model->hasIndex(0, 0, templateIndex)) { templateIndex = d->model->index(0, 0, templateIndex); } KSharedConfigPtr config; if (IProject* project = ICore::self()->projectController()->findProjectForUrl(d->assistant->baseUrl())) { config = project->projectConfiguration(); } else { config = ICore::self()->activeSession()->config(); } KConfigGroup group(config, FileTemplatesGroup); QString lastTemplate = group.readEntry(LastUsedTemplateEntry); QModelIndexList indexes = d->model->match(d->model->index(0, 0), TemplatesModel::DescriptionFileRole, lastTemplate, 1, Qt::MatchRecursive); if (!indexes.isEmpty()) { templateIndex = indexes.first(); } d->ui->view->setCurrentIndex(templateIndex); QPushButton* getMoreButton = new QPushButton(i18n("Get More Templates..."), d->ui->view); getMoreButton->setIcon(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff"))); connect (getMoreButton, &QPushButton::clicked, this, [&] { d->getMoreClicked(); }); d->ui->view->addWidget(0, getMoreButton); QPushButton* loadButton = new QPushButton(QIcon::fromTheme(QStringLiteral("application-x-archive")), i18n("Load Template From File"), d->ui->view); connect (loadButton, &QPushButton::clicked, this, [&] { d->loadFileClicked(); }); d->ui->view->addWidget(0, loadButton); d->ui->view->setContentsMargins(0, 0, 0, 0); } TemplateSelectionPage::~TemplateSelectionPage() { delete d->ui; delete d; } QSize TemplateSelectionPage::minimumSizeHint() const { return QSize(400, 600); } QString TemplateSelectionPage::selectedTemplate() const { return d->selectedTemplate; } #include "moc_templateselectionpage.cpp" diff --git a/plugins/filetemplates/templateselectionpage.h b/plugins/filetemplates/templateselectionpage.h index 2449ddea5..2e0d2d7a4 100644 --- a/plugins/filetemplates/templateselectionpage.h +++ b/plugins/filetemplates/templateselectionpage.h @@ -1,66 +1,66 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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. */ #ifndef KDEVPLATFORM_PLUGIN_TEMPLATESELECTIONPAGE_H #define KDEVPLATFORM_PLUGIN_TEMPLATESELECTIONPAGE_H #include namespace KDevelop { class TemplateClassAssistant; /** * An assistant page for selecting a class template **/ class TemplateSelectionPage : public QWidget { Q_OBJECT Q_PROPERTY(QString selectedTemplate READ selectedTemplate) public: - explicit TemplateSelectionPage (TemplateClassAssistant* parent, Qt::WindowFlags f = nullptr); + explicit TemplateSelectionPage (TemplateClassAssistant* parent); ~TemplateSelectionPage() override; /** * @property selectedTemplate * * The class template, selected by the user. * This property stores the full path to the template description (.desktop) file **/ QString selectedTemplate() const; QSize minimumSizeHint() const override; public Q_SLOTS: /** * Saves the selected template setting into the current project's configuration. * * If the assistant's base URL does not point to any project, this function does nothing. */ void saveConfig(); private: class TemplateSelectionPagePrivate* const d; }; } #endif // KDEVPLATFORM_PLUGIN_TEMPLATESELECTIONPAGE_H diff --git a/plugins/filetemplates/testcasespage.cpp b/plugins/filetemplates/testcasespage.cpp index ef06df9ad..3065ccd3d 100644 --- a/plugins/filetemplates/testcasespage.cpp +++ b/plugins/filetemplates/testcasespage.cpp @@ -1,88 +1,88 @@ /* * This file is part of KDevelop * Copyright 2012 Miha Čančula * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "testcasespage.h" #include "ui_testcases.h" #include #include using namespace KDevelop; class KDevelop::TestCasesPagePrivate { public: Ui::TestCasesPage* ui; }; -TestCasesPage::TestCasesPage(QWidget* parent, Qt::WindowFlags f) -: QWidget (parent, f) +TestCasesPage::TestCasesPage(QWidget* parent) +: QWidget(parent) , d(new TestCasesPagePrivate) { d->ui = new Ui::TestCasesPage(); d->ui->setupUi(this); d->ui->testCasesLabel->setBuddy(d->ui->keditlistwidget->lineEdit()); #if KWIDGETSADDONS_VERSION < QT_VERSION_CHECK(5,32,0) // workaround for KEditListWidget bug: // ensure keyboard focus is returned to edit line connect(d->ui->keditlistwidget, &KEditListWidget::added, d->ui->keditlistwidget->lineEdit(), static_cast(&QWidget::setFocus)); connect(d->ui->keditlistwidget, &KEditListWidget::removed, d->ui->keditlistwidget->lineEdit(), static_cast(&QWidget::setFocus)); #endif connect(d->ui->identifierLineEdit, &QLineEdit::textChanged, this, &TestCasesPage::identifierChanged); } TestCasesPage::~TestCasesPage() { delete d->ui; delete d; } QString TestCasesPage::name() const { return d->ui->identifierLineEdit->text(); } void TestCasesPage::setTestCases(const QStringList& testCases) { d->ui->keditlistwidget->setItems(testCases); } QStringList TestCasesPage::testCases() const { return d->ui->keditlistwidget->items(); } void TestCasesPage::setFocusToFirstEditWidget() { d->ui->identifierLineEdit->setFocus(); } void TestCasesPage::identifierChanged(const QString& identifier) { emit isValid(!identifier.isEmpty()); } diff --git a/plugins/filetemplates/testcasespage.h b/plugins/filetemplates/testcasespage.h index 6bd337b56..9604fd28c 100644 --- a/plugins/filetemplates/testcasespage.h +++ b/plugins/filetemplates/testcasespage.h @@ -1,72 +1,72 @@ /* * This file is part of KDevelop * Copyright 2012 Miha Čančula * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_TESTCASESWIDGET_H #define KDEVPLATFORM_PLUGIN_TESTCASESWIDGET_H #include #include "ipagefocus.h" namespace KDevelop { /** * Assistant page for specifying the list of test cases * */ class TestCasesPage : public QWidget, public IPageFocus { Q_OBJECT Q_PROPERTY(QStringList testCases READ testCases WRITE setTestCases) public: - explicit TestCasesPage(QWidget* parent = nullptr, Qt::WindowFlags f = nullptr); + explicit TestCasesPage(QWidget* parent = nullptr); ~TestCasesPage() override; /** * The name of the new test, as set by the user */ QString name() const; /** * Returns the list of test case names */ QStringList testCases() const; /** * Sets the current list of test case names to @p testCases */ void setTestCases(const QStringList& testCases); void setFocusToFirstEditWidget() override; Q_SIGNALS: void isValid(bool valid); private: class TestCasesPagePrivate* const d; private Q_SLOTS: void identifierChanged(const QString& identifier); }; } #endif // KDEVPLATFORM_PLUGIN_TESTCASESWIDGET_H diff --git a/plugins/git/gitplugin.cpp b/plugins/git/gitplugin.cpp index 25d54f5e4..07a39ebf6 100644 --- a/plugins/git/gitplugin.cpp +++ b/plugins/git/gitplugin.cpp @@ -1,1560 +1,1561 @@ /*************************************************************************** * Copyright 2008 Evgeniy Ivanov * * Copyright 2009 Hugo Parente Lima * * Copyright 2010 Aleix Pol Gonzalez * * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "gitplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gitclonejob.h" #include #include #include "stashmanagerdialog.h" #include #include #include #include #include #include #include "gitjob.h" #include "gitmessagehighlighter.h" #include "gitplugincheckinrepositoryjob.h" #include "gitnameemaildialog.h" #include "debug.h" Q_LOGGING_CATEGORY(PLUGIN_GIT, "kdevplatform.plugins.git") using namespace KDevelop; QVariant runSynchronously(KDevelop::VcsJob* job) { QVariant ret; if(job->exec() && job->status()==KDevelop::VcsJob::JobSucceeded) { ret = job->fetchResults(); } delete job; return ret; } namespace { QDir dotGitDirectory(const QUrl& dirPath) { const QFileInfo finfo(dirPath.toLocalFile()); QDir dir = finfo.isDir() ? QDir(finfo.filePath()): finfo.absoluteDir(); static const QString gitDir = QStringLiteral(".git"); while (!dir.exists(gitDir) && dir.cdUp()) {} // cdUp, until there is a sub-directory called .git if (dir.isRoot()) { qCWarning(PLUGIN_GIT) << "couldn't find the git root for" << dirPath; } return dir; } /** * Whenever a directory is provided, change it for all the files in it but not inner directories, * that way we make sure we won't get into recursion, */ static QList preventRecursion(const QList& urls) { QList ret; foreach(const QUrl& url, urls) { QDir d(url.toLocalFile()); if(d.exists()) { QStringList entries = d.entryList(QDir::Files | QDir::NoDotAndDotDot); foreach(const QString& entry, entries) { QUrl entryUrl = QUrl::fromLocalFile(d.absoluteFilePath(entry)); ret += entryUrl; } } else ret += url; } return ret; } QString toRevisionName(const KDevelop::VcsRevision& rev, QString currentRevision=QString()) { switch(rev.revisionType()) { case VcsRevision::Special: switch(rev.revisionValue().value()) { case VcsRevision::Head: return QStringLiteral("^HEAD"); case VcsRevision::Base: return QString(); case VcsRevision::Working: return QString(); case VcsRevision::Previous: Q_ASSERT(!currentRevision.isEmpty()); return currentRevision + "^1"; case VcsRevision::Start: return QString(); case VcsRevision::UserSpecialType: //Not used Q_ASSERT(false && "i don't know how to do that"); } break; case VcsRevision::GlobalNumber: return rev.revisionValue().toString(); case VcsRevision::Date: case VcsRevision::FileNumber: case VcsRevision::Invalid: case VcsRevision::UserSpecialType: Q_ASSERT(false); } return QString(); } QString revisionInterval(const KDevelop::VcsRevision& rev, const KDevelop::VcsRevision& limit) { QString ret; if(rev.revisionType()==VcsRevision::Special && rev.revisionValue().value()==VcsRevision::Start) //if we want it to the beginning just put the revisionInterval ret = toRevisionName(limit, QString()); else { QString dst = toRevisionName(limit); if(dst.isEmpty()) ret = dst; else { QString src = toRevisionName(rev, dst); if(src.isEmpty()) ret = src; else ret = src+".."+dst; } } return ret; } QDir urlDir(const QUrl& url) { QFileInfo f(url.toLocalFile()); if(f.isDir()) return QDir(url.toLocalFile()); else return f.absoluteDir(); } QDir urlDir(const QList& urls) { return urlDir(urls.first()); } //TODO: could be improved } GitPlugin::GitPlugin( QObject *parent, const QVariantList & ) : DistributedVersionControlPlugin(parent, QStringLiteral("kdevgit")), m_oldVersion(false), m_usePrefix(true) { if (QStandardPaths::findExecutable(QStringLiteral("git")).isEmpty()) { setErrorDescription(i18n("Unable to find git executable. Is it installed on the system?")); return; } setObjectName(QStringLiteral("Git")); DVcsJob* versionJob = new DVcsJob(QDir::tempPath(), this, KDevelop::OutputJob::Silent); *versionJob << "git" << "--version"; connect(versionJob, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitVersionOutput); ICore::self()->runController()->registerJob(versionJob); m_watcher = new KDirWatch(this); connect(m_watcher, &KDirWatch::dirty, this, &GitPlugin::fileChanged); connect(m_watcher, &KDirWatch::created, this, &GitPlugin::fileChanged); } GitPlugin::~GitPlugin() {} bool emptyOutput(DVcsJob* job) { QScopedPointer _job(job); if(job->exec() && job->status()==VcsJob::JobSucceeded) return job->rawOutput().trimmed().isEmpty(); return false; } bool GitPlugin::hasStashes(const QDir& repository) { return !emptyOutput(gitStash(repository, QStringList(QStringLiteral("list")), KDevelop::OutputJob::Silent)); } bool GitPlugin::hasModifications(const QDir& d) { return !emptyOutput(lsFiles(d, QStringList(QStringLiteral("-m")), OutputJob::Silent)); } bool GitPlugin::hasModifications(const QDir& repo, const QUrl& file) { return !emptyOutput(lsFiles(repo, QStringList() << QStringLiteral("-m") << file.path(), OutputJob::Silent)); } void GitPlugin::additionalMenuEntries(QMenu* menu, const QList& urls) { m_urls = urls; QDir dir=urlDir(urls); bool hasSt = hasStashes(dir); menu->addSeparator()->setText(i18n("Git Stashes")); menu->addAction(i18n("Stash Manager"), this, SLOT(ctxStashManager()))->setEnabled(hasSt); menu->addAction(i18n("Push Stash"), this, SLOT(ctxPushStash())); menu->addAction(i18n("Pop Stash"), this, SLOT(ctxPopStash()))->setEnabled(hasSt); } void GitPlugin::ctxPushStash() { VcsJob* job = gitStash(urlDir(m_urls), QStringList(), KDevelop::OutputJob::Verbose); ICore::self()->runController()->registerJob(job); } void GitPlugin::ctxPopStash() { VcsJob* job = gitStash(urlDir(m_urls), QStringList(QStringLiteral("pop")), KDevelop::OutputJob::Verbose); ICore::self()->runController()->registerJob(job); } void GitPlugin::ctxStashManager() { QPointer d = new StashManagerDialog(urlDir(m_urls), this, nullptr); d->exec(); delete d; } DVcsJob* GitPlugin::errorsFound(const QString& error, KDevelop::OutputJob::OutputJobVerbosity verbosity=OutputJob::Verbose) { DVcsJob* j = new DVcsJob(QDir::temp(), this, verbosity); *j << "echo" << i18n("error: %1", error) << "-n"; return j; } QString GitPlugin::name() const { return QStringLiteral("Git"); } QUrl GitPlugin::repositoryRoot(const QUrl& path) { return QUrl::fromLocalFile(dotGitDirectory(path).absolutePath()); } bool GitPlugin::isValidDirectory(const QUrl & dirPath) { QDir dir=dotGitDirectory(dirPath); QFile dotGitPotentialFile(dir.filePath(".git")); // if .git is a file, we may be in a git worktree QFileInfo dotGitPotentialFileInfo(dotGitPotentialFile); if (!dotGitPotentialFileInfo.isDir() && dotGitPotentialFile.exists()) { QString gitWorktreeFileContent; if (dotGitPotentialFile.open(QFile::ReadOnly)) { // the content should be gitdir: /path/to/the/.git/worktree gitWorktreeFileContent = QString::fromUtf8(dotGitPotentialFile.readAll()); dotGitPotentialFile.close(); } else { return false; } const auto items = gitWorktreeFileContent.split(' '); if (items.size() == 2 && items.at(0) == "gitdir:") { qCDebug(PLUGIN_GIT) << "we are in a git worktree" << items.at(1); return true; } } return dir.exists(QStringLiteral(".git/HEAD")); } bool GitPlugin::isValidRemoteRepositoryUrl(const QUrl& remoteLocation) { if (remoteLocation.isLocalFile()) { QFileInfo fileInfo(remoteLocation.toLocalFile()); if (fileInfo.isDir()) { QDir dir(fileInfo.filePath()); if (dir.exists(QStringLiteral(".git/HEAD"))) { return true; } // TODO: check also for bare repo } } else { const QString scheme = remoteLocation.scheme(); if (scheme == QLatin1String("git")) { return true; } // heuristic check, anything better we can do here without talking to server? if ((scheme == QLatin1String("http") || scheme == QLatin1String("https")) && remoteLocation.path().endsWith(QLatin1String(".git"))) { return true; } } return false; } bool GitPlugin::isVersionControlled(const QUrl &path) { QFileInfo fsObject(path.toLocalFile()); if (!fsObject.exists()) { return false; } if (fsObject.isDir()) { return isValidDirectory(path); } QString filename = fsObject.fileName(); QStringList otherFiles = getLsFiles(fsObject.dir(), QStringList(QStringLiteral("--")) << filename, KDevelop::OutputJob::Silent); return !otherFiles.empty(); } VcsJob* GitPlugin::init(const QUrl &directory) { DVcsJob* job = new DVcsJob(urlDir(directory), this); job->setType(VcsJob::Import); *job << "git" << "init"; return job; } VcsJob* GitPlugin::createWorkingCopy(const KDevelop::VcsLocation & source, const QUrl& dest, KDevelop::IBasicVersionControl::RecursionMode) { DVcsJob* job = new GitCloneJob(urlDir(dest), this); job->setType(VcsJob::Import); *job << "git" << "clone" << "--progress" << "--" << source.localUrl().url() << dest; return job; } VcsJob* GitPlugin::add(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (localLocations.empty()) return errorsFound(i18n("Did not specify the list of files"), OutputJob::Verbose); DVcsJob* job = new GitJob(dotGitDirectory(localLocations.front()), this); job->setType(VcsJob::Add); *job << "git" << "add" << "--" << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } KDevelop::VcsJob* GitPlugin::status(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (localLocations.empty()) return errorsFound(i18n("Did not specify the list of files"), OutputJob::Verbose); DVcsJob* job = new GitJob(urlDir(localLocations), this, OutputJob::Silent); job->setType(VcsJob::Status); if(m_oldVersion) { *job << "git" << "ls-files" << "-t" << "-m" << "-c" << "-o" << "-d" << "-k" << "--directory"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitStatusOutput_old); } else { *job << "git" << "status" << "--porcelain"; job->setIgnoreError(true); connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitStatusOutput); } *job << "--" << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } VcsJob* GitPlugin::diff(const QUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, VcsDiff::Type /*type*/, IBasicVersionControl::RecursionMode recursion) { //TODO: control different types DVcsJob* job = new GitJob(dotGitDirectory(fileOrDirectory), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Diff); *job << "git" << "diff" << "--no-color" << "--no-ext-diff"; if (!usePrefix()) { // KDE's ReviewBoard now requires p1 patchfiles, so `git diff --no-prefix` to generate p0 patches // has become optional. *job << "--no-prefix"; } if (dstRevision.revisionType() == VcsRevision::Special && dstRevision.specialType() == VcsRevision::Working) { if (srcRevision.revisionType() == VcsRevision::Special && srcRevision.specialType() == VcsRevision::Base) { *job << "HEAD"; } else { *job << "--cached" << srcRevision.revisionValue().toString(); } } else { QString revstr = revisionInterval(srcRevision, dstRevision); if(!revstr.isEmpty()) *job << revstr; } *job << "--"; if (recursion == IBasicVersionControl::Recursive) { *job << fileOrDirectory; } else { *job << preventRecursion(QList() << fileOrDirectory); } connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitDiffOutput); return job; } VcsJob* GitPlugin::revert(const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { if(localLocations.isEmpty() ) return errorsFound(i18n("Could not revert changes"), OutputJob::Verbose); QDir repo = urlDir(repositoryRoot(localLocations.first())); QString modified; for (const auto& file: localLocations) { if (hasModifications(repo, file)) { modified.append(file.toDisplayString(QUrl::PreferLocalFile) + "
"); } } if (!modified.isEmpty()) { auto res = KMessageBox::questionYesNo(nullptr, i18n("The following files have uncommited changes, " "which will be lost. Continue?") + "

" + modified); if (res != KMessageBox::Yes) { return errorsFound(QString(), OutputJob::Silent); } } DVcsJob* job = new GitJob(dotGitDirectory(localLocations.front()), this); job->setType(VcsJob::Revert); *job << "git" << "checkout" << "--"; *job << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } //TODO: git doesn't like empty messages, but "KDevelop didn't provide any message, it may be a bug" looks ugly... //If no files specified then commit already added files VcsJob* GitPlugin::commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (localLocations.empty() || message.isEmpty()) return errorsFound(i18n("No files or message specified")); const QDir dir = dotGitDirectory(localLocations.front()); if (!ensureValidGitIdentity(dir)) { return errorsFound(i18n("Email or name for Git not specified")); } DVcsJob* job = new DVcsJob(dir, this); job->setType(VcsJob::Commit); QList files = (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); addNotVersionedFiles(dir, files); *job << "git" << "commit" << "-m" << message; *job << "--" << files; return job; } bool GitPlugin::ensureValidGitIdentity(const QDir& dir) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath()); const QString name = readConfigOption(url, QStringLiteral("user.name")); const QString email = readConfigOption(url, QStringLiteral("user.email")); if (!email.isEmpty() && !name.isEmpty()) { return true; // already okay } GitNameEmailDialog dialog; dialog.setName(name); dialog.setEmail(email); if (!dialog.exec()) { return false; } runSynchronously(setConfigOption(url, QStringLiteral("user.name"), dialog.name(), dialog.isGlobal())); runSynchronously(setConfigOption(url, QStringLiteral("user.email"), dialog.email(), dialog.isGlobal())); return true; } void GitPlugin::addNotVersionedFiles(const QDir& dir, const QList& files) { QStringList otherStr = getLsFiles(dir, QStringList() << QStringLiteral("--others"), KDevelop::OutputJob::Silent); QList toadd, otherFiles; foreach(const QString& file, otherStr) { QUrl v = QUrl::fromLocalFile(dir.absoluteFilePath(file)); otherFiles += v; } //We add the files that are not versioned foreach(const QUrl& file, files) { if(otherFiles.contains(file) && QFileInfo(file.toLocalFile()).isFile()) toadd += file; } if(!toadd.isEmpty()) { VcsJob* job = add(toadd); job->exec(); } } bool isEmptyDirStructure(const QDir &dir) { foreach (const QFileInfo &i, dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)) { if (i.isDir()) { if (!isEmptyDirStructure(QDir(i.filePath()))) return false; } else if (i.isFile()) { return false; } } return true; } VcsJob* GitPlugin::remove(const QList& files) { if (files.isEmpty()) return errorsFound(i18n("No files to remove")); QDir dotGitDir = dotGitDirectory(files.front()); QList files_(files); QMutableListIterator i(files_); while (i.hasNext()) { QUrl file = i.next(); QFileInfo fileInfo(file.toLocalFile()); QStringList otherStr = getLsFiles(dotGitDir, QStringList() << QStringLiteral("--others") << QStringLiteral("--") << file.toLocalFile(), KDevelop::OutputJob::Silent); if(!otherStr.isEmpty()) { //remove files not under version control QList otherFiles; foreach(const QString &f, otherStr) { otherFiles << QUrl::fromLocalFile(dotGitDir.path()+'/'+f); } if (fileInfo.isFile()) { //if it's an unversioned file we are done, don't use git rm on it i.remove(); } auto trashJob = KIO::trash(otherFiles); trashJob->exec(); qCDebug(PLUGIN_GIT) << "other files" << otherFiles; } if (fileInfo.isDir()) { if (isEmptyDirStructure(QDir(file.toLocalFile()))) { //remove empty folders, git doesn't do that auto trashJob = KIO::trash(file); trashJob->exec(); qCDebug(PLUGIN_GIT) << "empty folder, removing" << file; //we already deleted it, don't use git rm on it i.remove(); } } } if (files_.isEmpty()) return nullptr; DVcsJob* job = new GitJob(dotGitDir, this); job->setType(VcsJob::Remove); // git refuses to delete files with local modifications // use --force to overcome this *job << "git" << "rm" << "-r" << "--force"; *job << "--" << files_; return job; } VcsJob* GitPlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& src, const KDevelop::VcsRevision& dst) { DVcsJob* job = new GitJob(dotGitDirectory(localLocation), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Log); *job << "git" << "log" << "--date=raw" << "--name-status" << "-M80%" << "--follow"; QString rev = revisionInterval(dst, src); if(!rev.isEmpty()) *job << rev; *job << "--" << localLocation; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitLogOutput); return job; } VcsJob* GitPlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, unsigned long int limit) { DVcsJob* job = new GitJob(dotGitDirectory(localLocation), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Log); *job << "git" << "log" << "--date=raw" << "--name-status" << "-M80%" << "--follow"; QString revStr = toRevisionName(rev, QString()); if(!revStr.isEmpty()) *job << revStr; if(limit>0) *job << QStringLiteral("-%1").arg(limit); *job << "--" << localLocation; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitLogOutput); return job; } KDevelop::VcsJob* GitPlugin::annotate(const QUrl &localLocation, const KDevelop::VcsRevision&) { DVcsJob* job = new GitJob(dotGitDirectory(localLocation), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Annotate); *job << "git" << "blame" << "--porcelain" << "-w"; *job << "--" << localLocation; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitBlameOutput); return job; } void GitPlugin::parseGitBlameOutput(DVcsJob *job) { QVariantList results; VcsAnnotationLine* annotation = nullptr; const auto output = job->output(); const auto lines = output.splitRef('\n'); bool skipNext=false; QMap definedRevisions; for(QVector::const_iterator it=lines.constBegin(), itEnd=lines.constEnd(); it!=itEnd; ++it) { if(skipNext) { skipNext=false; results += qVariantFromValue(*annotation); continue; } if(it->isEmpty()) continue; QStringRef name = it->left(it->indexOf(' ')); QStringRef value = it->right(it->size()-name.size()-1); if(name==QLatin1String("author")) annotation->setAuthor(value.toString()); else if(name==QLatin1String("author-mail")) {} //TODO: do smth with the e-mail? else if(name==QLatin1String("author-tz")) {} //TODO: does it really matter? else if(name==QLatin1String("author-time")) annotation->setDate(QDateTime::fromTime_t(value.toUInt())); else if(name==QLatin1String("summary")) annotation->setCommitMessage(value.toString()); else if(name.startsWith(QStringLiteral("committer"))) {} //We will just store the authors else if(name==QLatin1String("previous")) {} //We don't need that either else if(name==QLatin1String("filename")) { skipNext=true; } else if(name==QLatin1String("boundary")) { definedRevisions.insert(QStringLiteral("boundary"), VcsAnnotationLine()); } else { const auto values = value.split(' '); VcsRevision rev; rev.setRevisionValue(name.left(8).toString(), KDevelop::VcsRevision::GlobalNumber); skipNext = definedRevisions.contains(name.toString()); if(!skipNext) definedRevisions.insert(name.toString(), VcsAnnotationLine()); annotation = &definedRevisions[name.toString()]; annotation->setLineNumber(values[1].toInt() - 1); annotation->setRevision(rev); } } job->setResults(results); } DVcsJob* GitPlugin::lsFiles(const QDir &repository, const QStringList &args, OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(repository, this, verbosity); *job << "git" << "ls-files" << args; return job; } DVcsJob* GitPlugin::gitStash(const QDir& repository, const QStringList& args, OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(repository, this, verbosity); *job << "git" << "stash" << args; return job; } VcsJob* GitPlugin::tag(const QUrl& repository, const QString& commitMessage, const VcsRevision& rev, const QString& tagName) { DVcsJob* job = new DVcsJob(urlDir(repository), this); *job << "git" << "tag" << "-m" << commitMessage << tagName; if(rev.revisionValue().isValid()) *job << rev.revisionValue().toString(); return job; } VcsJob* GitPlugin::switchBranch(const QUrl &repository, const QString &branch) { QDir d=urlDir(repository); if(hasModifications(d) && KMessageBox::questionYesNo(nullptr, i18n("There are pending changes, do you want to stash them first?"))==KMessageBox::Yes) { QScopedPointer stash(gitStash(d, QStringList(), KDevelop::OutputJob::Verbose)); stash->exec(); } DVcsJob* job = new DVcsJob(d, this); *job << "git" << "checkout" << branch; return job; } VcsJob* GitPlugin::branch(const QUrl& repository, const KDevelop::VcsRevision& rev, const QString& branchName) { Q_ASSERT(!branchName.isEmpty()); DVcsJob* job = new DVcsJob(urlDir(repository), this); *job << "git" << "branch" << "--" << branchName; if(!rev.prettyValue().isEmpty()) *job << rev.revisionValue().toString(); return job; } VcsJob* GitPlugin::deleteBranch(const QUrl& repository, const QString& branchName) { DVcsJob* job = new DVcsJob(urlDir(repository), this, OutputJob::Silent); *job << "git" << "branch" << "-D" << branchName; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitCurrentBranch); return job; } VcsJob* GitPlugin::renameBranch(const QUrl& repository, const QString& oldBranchName, const QString& newBranchName) { DVcsJob* job = new DVcsJob(urlDir(repository), this, OutputJob::Silent); *job << "git" << "branch" << "-m" << newBranchName << oldBranchName; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitCurrentBranch); return job; } VcsJob* GitPlugin::mergeBranch(const QUrl& repository, const QString& branchName) { Q_ASSERT(!branchName.isEmpty()); DVcsJob* job = new DVcsJob(urlDir(repository), this); *job << "git" << "merge" << branchName; return job; } VcsJob* GitPlugin::currentBranch(const QUrl& repository) { DVcsJob* job = new DVcsJob(urlDir(repository), this, OutputJob::Silent); job->setIgnoreError(true); *job << "git" << "symbolic-ref" << "-q" << "--short" << "HEAD"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitCurrentBranch); return job; } void GitPlugin::parseGitCurrentBranch(DVcsJob* job) { QString out = job->output().trimmed(); job->setResults(out); } VcsJob* GitPlugin::branches(const QUrl &repository) { DVcsJob* job=new DVcsJob(urlDir(repository)); *job << "git" << "branch" << "-a"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitBranchOutput); return job; } void GitPlugin::parseGitBranchOutput(DVcsJob* job) { const auto output = job->output(); const auto branchListDirty = output.splitRef('\n', QString::SkipEmptyParts); QStringList branchList; foreach(const auto & branch, branchListDirty) { // Skip pointers to another branches (one example of this is "origin/HEAD -> origin/master"); // "git rev-list" chokes on these entries and we do not need duplicate branches altogether. if (branch.contains(QStringLiteral("->"))) continue; // Skip entries such as '(no branch)' if (branch.contains(QStringLiteral("(no branch)"))) continue; QStringRef name = branch; if (name.startsWith('*')) name = branch.right(branch.size()-2); branchList << name.trimmed().toString(); } job->setResults(branchList); } /* Few words about how this hardcore works: 1. get all commits (with --paretns) 2. select master (root) branch and get all unicial commits for branches (git-rev-list br2 ^master ^br3) 3. parse allCommits. While parsing set mask (columns state for every row) for BRANCH, INITIAL, CROSS, MERGE and INITIAL are also set in DVCScommit::setParents (depending on parents count) another setType(INITIAL) is used for "bottom/root/first" commits of branches 4. find and set merges, HEADS. It's an ittaration through all commits. - first we check if parent is from the same branch, if no then we go through all commits searching parent's index and set CROSS/HCROSS for rows (in 3 rows are set EMPTY after commit with parent from another tree met) - then we check branchesShas[i][0] to mark heads 4 can be a seporate function. TODO: All this porn require refactoring (rewriting is better)! It's a very dirty implementation. FIXME: 1. HEAD which is head has extra line to connect it with further commit 2. If you menrge branch2 to master, only new commits of branch2 will be visible (it's fine, but there will be extra merge rectangle in master. If there are no extra commits in branch2, but there are another branches, then the place for branch2 will be empty (instead of be used for branch3). 3. Commits that have additional commit-data (not only history merging, but changes to fix conflicts) are shown incorrectly */ QList GitPlugin::getAllCommits(const QString &repo) { initBranchHash(repo); QStringList args; args << QStringLiteral("--all") << QStringLiteral("--pretty") << QStringLiteral("--parents"); QScopedPointer job(gitRevList(repo, args)); bool ret = job->exec(); Q_ASSERT(ret && job->status()==VcsJob::JobSucceeded && "TODO: provide a fall back in case of failing"); Q_UNUSED(ret); QStringList commits = job->output().split('\n', QString::SkipEmptyParts); static QRegExp rx_com("commit \\w{40,40}"); QListcommitList; DVcsEvent item; //used to keep where we have empty/cross/branch entry //true if it's an active branch (then cross or branch) and false if not QVector additionalFlags(branchesShas.count()); additionalFlags.fill(false); //parse output for(int i = 0; i < commits.count(); ++i) { if (commits[i].contains(rx_com)) { qCDebug(PLUGIN_GIT) << "commit found in " << commits[i]; item.setCommit(commits[i].section(' ', 1, 1).trimmed()); // qCDebug(PLUGIN_GIT) << "commit is: " << commits[i].section(' ', 1); QStringList parents; QString parent = commits[i].section(' ', 2); int section = 2; while (!parent.isEmpty()) { /* qCDebug(PLUGIN_GIT) << "Parent is: " << parent;*/ parents.append(parent.trimmed()); section++; parent = commits[i].section(' ', section); } item.setParents(parents); //Avoid Merge string while (!commits[i].contains(QStringLiteral("Author: "))) ++i; item.setAuthor(commits[i].section(QStringLiteral("Author: "), 1).trimmed()); // qCDebug(PLUGIN_GIT) << "author is: " << commits[i].section("Author: ", 1); item.setDate(commits[++i].section(QStringLiteral("Date: "), 1).trimmed()); // qCDebug(PLUGIN_GIT) << "date is: " << commits[i].section("Date: ", 1); QString log; i++; //next line! while (i < commits.count() && !commits[i].contains(rx_com)) log += commits[i++]; --i; //while took commit line item.setLog(log.trimmed()); // qCDebug(PLUGIN_GIT) << "log is: " << log; //mask is used in CommitViewDelegate to understand what we should draw for each branch QList mask; //set mask (properties for each graph column in row) for(int i = 0; i < branchesShas.count(); ++i) { qCDebug(PLUGIN_GIT)<<"commit: " << item.getCommit(); if (branchesShas[i].contains(item.getCommit())) { mask.append(item.getType()); //we set type in setParents //check if parent from the same branch, if not then we have found a root of the branch //and will use empty column for all futher (from top to bottom) revisions //FIXME: we should set CROSS between parent and child (and do it when find merge point) additionalFlags[i] = false; foreach(const QString &sha, item.getParents()) { if (branchesShas[i].contains(sha)) additionalFlags[i] = true; } if (additionalFlags[i] == false) item.setType(DVcsEvent::INITIAL); //hasn't parents from the same branch, used in drawing } else { if (additionalFlags[i] == false) mask.append(DVcsEvent::EMPTY); else mask.append(DVcsEvent::CROSS); } qCDebug(PLUGIN_GIT) << "mask " << i << "is " << mask[i]; } item.setProperties(mask); commitList.append(item); } } //find and set merges, HEADS, require refactoring! for(QList::iterator iter = commitList.begin(); iter != commitList.end(); ++iter) { QStringList parents = iter->getParents(); //we need only only child branches if (parents.count() != 1) break; QString parent = parents[0]; QString commit = iter->getCommit(); bool parent_checked = false; int heads_checked = 0; for(int i = 0; i < branchesShas.count(); ++i) { //check parent if (branchesShas[i].contains(commit)) { if (!branchesShas[i].contains(parent)) { //parent and child are not in same branch //since it is list, than parent has i+1 index //set CROSS and HCROSS for(QList::iterator f_iter = iter; f_iter != commitList.end(); ++f_iter) { if (parent == f_iter->getCommit()) { for(int j = 0; j < i; ++j) { if(branchesShas[j].contains(parent)) f_iter->setPropetry(j, DVcsEvent::MERGE); else f_iter->setPropetry(j, DVcsEvent::HCROSS); } f_iter->setType(DVcsEvent::MERGE); f_iter->setPropetry(i, DVcsEvent::MERGE_RIGHT); qCDebug(PLUGIN_GIT) << parent << " is parent of " << commit; qCDebug(PLUGIN_GIT) << f_iter->getCommit() << " is merge"; parent_checked = true; break; } else f_iter->setPropetry(i, DVcsEvent::CROSS); } } } //mark HEADs if (!branchesShas[i].empty() && commit == branchesShas[i][0]) { iter->setType(DVcsEvent::HEAD); iter->setPropetry(i, DVcsEvent::HEAD); heads_checked++; qCDebug(PLUGIN_GIT) << "HEAD found"; } //some optimization if (heads_checked == branchesShas.count() && parent_checked) break; } } return commitList; } void GitPlugin::initBranchHash(const QString &repo) { const QUrl repoUrl = QUrl::fromLocalFile(repo); QStringList gitBranches = runSynchronously(branches(repoUrl)).toStringList(); qCDebug(PLUGIN_GIT) << "BRANCHES: " << gitBranches; //Now root branch is the current branch. In future it should be the longest branch //other commitLists are got with git-rev-lits branch ^br1 ^ br2 QString root = runSynchronously(currentBranch(repoUrl)).toString(); QScopedPointer job(gitRevList(repo, QStringList(root))); bool ret = job->exec(); Q_ASSERT(ret && job->status()==VcsJob::JobSucceeded && "TODO: provide a fall back in case of failing"); Q_UNUSED(ret); QStringList commits = job->output().split('\n', QString::SkipEmptyParts); // qCDebug(PLUGIN_GIT) << "\n\n\n commits" << commits << "\n\n\n"; branchesShas.append(commits); foreach(const QString &branch, gitBranches) { if (branch == root) continue; QStringList args(branch); foreach(const QString &branch_arg, gitBranches) { if (branch_arg != branch) //man gitRevList for '^' args<<'^' + branch_arg; } QScopedPointer job(gitRevList(repo, args)); bool ret = job->exec(); Q_ASSERT(ret && job->status()==VcsJob::JobSucceeded && "TODO: provide a fall back in case of failing"); Q_UNUSED(ret); QStringList commits = job->output().split('\n', QString::SkipEmptyParts); // qCDebug(PLUGIN_GIT) << "\n\n\n commits" << commits << "\n\n\n"; branchesShas.append(commits); } } //Actually we can just copy the output without parsing. So it's a kind of draft for future void GitPlugin::parseLogOutput(const DVcsJob * job, QList& commits) const { // static QRegExp rx_sep( "[-=]+" ); // static QRegExp rx_date( "date:\\s+([^;]*);\\s+author:\\s+([^;]*).*" ); static QRegularExpression rx_com( "commit \\w{1,40}" ); const auto output = job->output(); const auto lines = output.splitRef('\n', QString::SkipEmptyParts); DVcsEvent item; QString commitLog; for (int i=0; i= 0x050500 if (rx_com.match(lines[i]).hasMatch()) { #else if (rx_com.match(lines[i].toString()).hasMatch()) { #endif // qCDebug(PLUGIN_GIT) << "MATCH COMMIT"; item.setCommit(lines[++i].toString()); item.setAuthor(lines[++i].toString()); item.setDate(lines[++i].toString()); item.setLog(commitLog); commits.append(item); } else { //FIXME: add this in a loop to the if, like in getAllCommits() commitLog += lines[i].toString() +'\n'; } } } VcsItemEvent::Actions actionsFromString(char c) { switch(c) { case 'A': return VcsItemEvent::Added; case 'D': return VcsItemEvent::Deleted; case 'R': return VcsItemEvent::Replaced; case 'M': return VcsItemEvent::Modified; } return VcsItemEvent::Modified; } void GitPlugin::parseGitLogOutput(DVcsJob * job) { static QRegExp commitRegex( "^commit (\\w{8})\\w{32}" ); static QRegExp infoRegex( "^(\\w+):(.*)" ); static QRegExp modificationsRegex("^([A-Z])[0-9]*\t([^\t]+)\t?(.*)", Qt::CaseSensitive, QRegExp::RegExp2); //R099 plugins/git/kdevgit.desktop plugins/git/kdevgit.desktop.cmake //M plugins/grepview/CMakeLists.txt QList commits; QString contents = job->output(); // check if git-log returned anything if (contents.isEmpty()) { job->setResults(commits); // empty list return; } // start parsing the output QTextStream s(&contents); VcsEvent item; QString message; bool pushCommit = false; while (!s.atEnd()) { QString line = s.readLine(); if (commitRegex.exactMatch(line)) { if (pushCommit) { item.setMessage(message.trimmed()); commits.append(QVariant::fromValue(item)); item.setItems(QList()); } else { pushCommit = true; } VcsRevision rev; rev.setRevisionValue(commitRegex.cap(1), KDevelop::VcsRevision::GlobalNumber); item.setRevision(rev); message.clear(); } else if (infoRegex.exactMatch(line)) { QString cap1 = infoRegex.cap(1); if (cap1 == QLatin1String("Author")) { item.setAuthor(infoRegex.cap(2).trimmed()); } else if (cap1 == QLatin1String("Date")) { item.setDate(QDateTime::fromTime_t(infoRegex.cap(2).trimmed().split(' ')[0].toUInt())); } } else if (modificationsRegex.exactMatch(line)) { VcsItemEvent::Actions a = actionsFromString(modificationsRegex.cap(1).at(0).toLatin1()); QString filenameA = modificationsRegex.cap(2); VcsItemEvent itemEvent; itemEvent.setActions(a); itemEvent.setRepositoryLocation(filenameA); if(a==VcsItemEvent::Replaced) { QString filenameB = modificationsRegex.cap(3); itemEvent.setRepositoryCopySourceLocation(filenameB); } item.addItem(itemEvent); } else if (line.startsWith(QLatin1String(" "))) { message += line.remove(0, 4); message += '\n'; } } item.setMessage(message.trimmed()); commits.append(QVariant::fromValue(item)); job->setResults(commits); } void GitPlugin::parseGitDiffOutput(DVcsJob* job) { VcsDiff diff; diff.setDiff(job->output()); diff.setBaseDiff(repositoryRoot(QUrl::fromLocalFile(job->directory().absolutePath()))); diff.setDepth(usePrefix()? 1 : 0); job->setResults(qVariantFromValue(diff)); } static VcsStatusInfo::State lsfilesToState(char id) { switch(id) { case 'H': return VcsStatusInfo::ItemUpToDate; //Cached case 'S': return VcsStatusInfo::ItemUpToDate; //Skip work tree case 'M': return VcsStatusInfo::ItemHasConflicts; //unmerged case 'R': return VcsStatusInfo::ItemDeleted; //removed/deleted case 'C': return VcsStatusInfo::ItemModified; //modified/changed case 'K': return VcsStatusInfo::ItemDeleted; //to be killed case '?': return VcsStatusInfo::ItemUnknown; //other } Q_ASSERT(false); return VcsStatusInfo::ItemUnknown; } void GitPlugin::parseGitStatusOutput_old(DVcsJob* job) { QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); QDir dir = job->directory(); QMap allStatus; foreach(const QString& line, outputLines) { VcsStatusInfo::State status = lsfilesToState(line[0].toLatin1()); QUrl url = QUrl::fromLocalFile(dir.absoluteFilePath(line.right(line.size()-2))); allStatus[url] = status; } QVariantList statuses; QMap< QUrl, VcsStatusInfo::State >::const_iterator it = allStatus.constBegin(), itEnd=allStatus.constEnd(); for(; it!=itEnd; ++it) { VcsStatusInfo status; status.setUrl(it.key()); status.setState(it.value()); statuses.append(qVariantFromValue(status)); } job->setResults(statuses); } void GitPlugin::parseGitStatusOutput(DVcsJob* job) { const auto output = job->output(); const auto outputLines = output.splitRef('\n', QString::SkipEmptyParts); QDir workingDir = job->directory(); QDir dotGit = dotGitDirectory(QUrl::fromLocalFile(workingDir.absolutePath())); QVariantList statuses; QList processedFiles; foreach(const QStringRef& line, outputLines) { //every line is 2 chars for the status, 1 space then the file desc QStringRef curr=line.right(line.size()-3); QStringRef state = line.left(2); int arrow = curr.indexOf(QStringLiteral(" -> ")); if(arrow>=0) { VcsStatusInfo status; status.setUrl(QUrl::fromLocalFile(dotGit.absoluteFilePath(curr.toString().left(arrow)))); status.setState(VcsStatusInfo::ItemDeleted); statuses.append(qVariantFromValue(status)); processedFiles += status.url(); curr = curr.mid(arrow+4); } if(curr.startsWith('\"') && curr.endsWith('\"')) { //if the path is quoted, unquote curr = curr.mid(1, curr.size()-2); } VcsStatusInfo status; status.setUrl(QUrl::fromLocalFile(dotGit.absoluteFilePath(curr.toString()))); status.setState(messageToState(state)); processedFiles.append(status.url()); qCDebug(PLUGIN_GIT) << "Checking git status for " << line << curr << status.state(); statuses.append(qVariantFromValue(status)); } QStringList paths; QStringList oldcmd=job->dvcsCommand(); QStringList::const_iterator it=oldcmd.constBegin()+oldcmd.indexOf(QStringLiteral("--"))+1, itEnd=oldcmd.constEnd(); for(; it!=itEnd; ++it) paths += *it; //here we add the already up to date files QStringList files = getLsFiles(job->directory(), QStringList() << QStringLiteral("-c") << QStringLiteral("--") << paths, OutputJob::Silent); foreach(const QString& file, files) { QUrl fileUrl = QUrl::fromLocalFile(workingDir.absoluteFilePath(file)); if(!processedFiles.contains(fileUrl)) { VcsStatusInfo status; status.setUrl(fileUrl); status.setState(VcsStatusInfo::ItemUpToDate); statuses.append(qVariantFromValue(status)); } } job->setResults(statuses); } void GitPlugin::parseGitVersionOutput(DVcsJob* job) { const auto output = job->output().trimmed(); auto versionString = output.midRef(output.lastIndexOf(' ')).split('.'); static const QList minimumVersion = QList() << 1 << 7; qCDebug(PLUGIN_GIT) << "checking git version" << versionString << "against" << minimumVersion; m_oldVersion = false; if (versionString.size() < minimumVersion.size()) { m_oldVersion = true; qCWarning(PLUGIN_GIT) << "invalid git version string:" << job->output().trimmed(); return; } foreach(int num, minimumVersion) { QStringRef curr = versionString.takeFirst(); int valcurr = curr.toInt(); if (valcurr < num) { m_oldVersion = true; break; } if (valcurr > num) { m_oldVersion = false; break; } } qCDebug(PLUGIN_GIT) << "the current git version is old: " << m_oldVersion; } QStringList GitPlugin::getLsFiles(const QDir &directory, const QStringList &args, KDevelop::OutputJob::OutputJobVerbosity verbosity) { QScopedPointer job(lsFiles(directory, args, verbosity)); if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) return job->output().split('\n', QString::SkipEmptyParts); return QStringList(); } DVcsJob* GitPlugin::gitRevParse(const QString &repository, const QStringList &args, KDevelop::OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(QDir(repository), this, verbosity); *job << "git" << "rev-parse" << args; return job; } DVcsJob* GitPlugin::gitRevList(const QString& directory, const QStringList& args) { DVcsJob* job = new DVcsJob(urlDir(QUrl::fromLocalFile(directory)), this, KDevelop::OutputJob::Silent); { *job << "git" << "rev-list" << args; return job; } } VcsStatusInfo::State GitPlugin::messageToState(const QStringRef& msg) { Q_ASSERT(msg.size()==1 || msg.size()==2); VcsStatusInfo::State ret = VcsStatusInfo::ItemUnknown; if(msg.contains('U') || msg == QLatin1String("AA") || msg == QLatin1String("DD")) ret = VcsStatusInfo::ItemHasConflicts; else switch(msg.at(0).toLatin1()) { case 'M': ret = VcsStatusInfo::ItemModified; break; case 'A': ret = VcsStatusInfo::ItemAdded; break; case 'R': ret = VcsStatusInfo::ItemModified; break; case 'C': ret = VcsStatusInfo::ItemHasConflicts; break; case ' ': ret = msg.at(1) == 'M' ? VcsStatusInfo::ItemModified : VcsStatusInfo::ItemDeleted; break; case 'D': ret = VcsStatusInfo::ItemDeleted; break; case '?': ret = VcsStatusInfo::ItemUnknown; break; default: qCDebug(PLUGIN_GIT) << "Git status not identified:" << msg; break; } return ret; } StandardJob::StandardJob(IPlugin* parent, KJob* job, OutputJob::OutputJobVerbosity verbosity) : VcsJob(parent, verbosity) , m_job(job) , m_plugin(parent) , m_status(JobNotStarted) {} void StandardJob::start() { connect(m_job, &KJob::result, this, &StandardJob::result); m_job->start(); m_status=JobRunning; } void StandardJob::result(KJob* job) { if (job->error() == 0) { m_status = JobSucceeded; setError(NoError); } else { m_status = JobFailed; setError(UserDefinedError); } emitResult(); } VcsJob* GitPlugin::copy(const QUrl& localLocationSrc, const QUrl& localLocationDstn) { //TODO: Probably we should "git add" after return new StandardJob(this, KIO::copy(localLocationSrc, localLocationDstn), KDevelop::OutputJob::Silent); } VcsJob* GitPlugin::move(const QUrl& source, const QUrl& destination) { QDir dir = urlDir(source); QFileInfo fileInfo(source.toLocalFile()); if (fileInfo.isDir()) { if (isEmptyDirStructure(QDir(source.toLocalFile()))) { //move empty folder, git doesn't do that qCDebug(PLUGIN_GIT) << "empty folder" << source; return new StandardJob(this, KIO::move(source, destination), KDevelop::OutputJob::Silent); } } QStringList otherStr = getLsFiles(dir, QStringList() << QStringLiteral("--others") << QStringLiteral("--") << source.toLocalFile(), KDevelop::OutputJob::Silent); if(otherStr.isEmpty()) { DVcsJob* job = new DVcsJob(dir, this, KDevelop::OutputJob::Verbose); *job << "git" << "mv" << source.toLocalFile() << destination.toLocalFile(); return job; } else { return new StandardJob(this, KIO::move(source, destination), KDevelop::OutputJob::Silent); } } void GitPlugin::parseGitRepoLocationOutput(DVcsJob* job) { job->setResults(QVariant::fromValue(QUrl::fromLocalFile(job->output()))); } VcsJob* GitPlugin::repositoryLocation(const QUrl& localLocation) { DVcsJob* job = new DVcsJob(urlDir(localLocation), this); //Probably we should check first if origin is the proper remote we have to use but as a first attempt it works *job << "git" << "config" << "remote.origin.url"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitRepoLocationOutput); return job; } VcsJob* GitPlugin::pull(const KDevelop::VcsLocation& localOrRepoLocationSrc, const QUrl& localRepositoryLocation) { DVcsJob* job = new DVcsJob(urlDir(localRepositoryLocation), this); job->setCommunicationMode(KProcess::MergedChannels); *job << "git" << "pull"; if(!localOrRepoLocationSrc.localUrl().isEmpty()) *job << localOrRepoLocationSrc.localUrl().url(); return job; } VcsJob* GitPlugin::push(const QUrl& localRepositoryLocation, const KDevelop::VcsLocation& localOrRepoLocationDst) { DVcsJob* job = new DVcsJob(urlDir(localRepositoryLocation), this); job->setCommunicationMode(KProcess::MergedChannels); *job << "git" << "push"; if(!localOrRepoLocationDst.localUrl().isEmpty()) *job << localOrRepoLocationDst.localUrl().url(); return job; } VcsJob* GitPlugin::resolve(const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { return add(localLocations, recursion); } VcsJob* GitPlugin::update(const QList& localLocations, const KDevelop::VcsRevision& rev, IBasicVersionControl::RecursionMode recursion) { if(rev.revisionType()==VcsRevision::Special && rev.revisionValue().value()==VcsRevision::Head) { return pull(VcsLocation(), localLocations.first()); } else { DVcsJob* job = new DVcsJob(urlDir(localLocations.first()), this); { //Probably we should check first if origin is the proper remote we have to use but as a first attempt it works *job << "git" << "checkout" << rev.revisionValue().toString() << "--"; *job << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } } } void GitPlugin::setupCommitMessageEditor(const QUrl& localLocation, KTextEdit* editor) const { new GitMessageHighlighter(editor); QFile mergeMsgFile(dotGitDirectory(localLocation).filePath(QStringLiteral(".git/MERGE_MSG"))); // Some limit on the file size should be set since whole content is going to be read into // the memory. 1Mb seems to be good value since it's rather strange to have so huge commit // message. static const qint64 maxMergeMsgFileSize = 1024*1024; if (mergeMsgFile.size() > maxMergeMsgFileSize || !mergeMsgFile.open(QIODevice::ReadOnly)) return; QString mergeMsg = QString::fromLocal8Bit(mergeMsgFile.read(maxMergeMsgFileSize)); editor->setPlainText(mergeMsg); } class GitVcsLocationWidget : public KDevelop::StandardVcsLocationWidget { Q_OBJECT public: - explicit GitVcsLocationWidget(QWidget* parent = nullptr, Qt::WindowFlags f = nullptr) - : StandardVcsLocationWidget(parent, f) {} + explicit GitVcsLocationWidget(QWidget* parent = nullptr) + : StandardVcsLocationWidget(parent) + {} bool isCorrect() const override { return !url().isEmpty(); } }; KDevelop::VcsLocationWidget* GitPlugin::vcsLocation(QWidget* parent) const { return new GitVcsLocationWidget(parent); } void GitPlugin::registerRepositoryForCurrentBranchChanges(const QUrl& repository) { QDir dir = dotGitDirectory(repository); QString headFile = dir.absoluteFilePath(QStringLiteral(".git/HEAD")); m_watcher->addFile(headFile); } void GitPlugin::fileChanged(const QString& file) { Q_ASSERT(file.endsWith(QStringLiteral("HEAD"))); //SMTH/.git/HEAD -> SMTH/ const QUrl fileUrl = Path(file).parent().parent().toUrl(); //We need to delay the emitted signal, otherwise the branch hasn't change yet //and the repository is not functional m_branchesChange.append(fileUrl); QTimer::singleShot(1000, this, SLOT(delayedBranchChanged())); } void GitPlugin::delayedBranchChanged() { emit repositoryBranchChanged(m_branchesChange.takeFirst()); } CheckInRepositoryJob* GitPlugin::isInRepository(KTextEditor::Document* document) { CheckInRepositoryJob* job = new GitPluginCheckInRepositoryJob(document, repositoryRoot(document->url()).path()); job->start(); return job; } DVcsJob* GitPlugin::setConfigOption(const QUrl& repository, const QString& key, const QString& value, bool global) { auto job = new DVcsJob(urlDir(repository), this); QStringList args; args << "git" << "config"; if(global) args << "--global"; args << key << value; *job << args; return job; } QString GitPlugin::readConfigOption(const QUrl& repository, const QString& key) { QProcess exec; exec.setWorkingDirectory(urlDir(repository).absolutePath()); exec.start("git", QStringList() << "config" << "--get" << key); exec.waitForFinished(); return exec.readAllStandardOutput().trimmed(); } #include "gitplugin.moc" diff --git a/plugins/subversion/svnlocationwidget.cpp b/plugins/subversion/svnlocationwidget.cpp index 89f4c74a3..643f76f8b 100644 --- a/plugins/subversion/svnlocationwidget.cpp +++ b/plugins/subversion/svnlocationwidget.cpp @@ -1,27 +1,27 @@ /*************************************************************************** * Copyright 2010 Aleix Pol Gonzalez * * * * 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. * * * ***************************************************************************/ #include "svnlocationwidget.h" #include -SvnLocationWidget::SvnLocationWidget(QWidget* parent, Qt::WindowFlags f) - : StandardVcsLocationWidget(parent, f) +SvnLocationWidget::SvnLocationWidget(QWidget* parent) + : StandardVcsLocationWidget(parent) {} KDevelop::VcsLocation SvnLocationWidget::location() const { QUrl loc =url(); return KDevelop::VcsLocation(loc.toDisplayString()); } bool SvnLocationWidget::isCorrect() const { return !url().isRelative(); } diff --git a/plugins/subversion/svnlocationwidget.h b/plugins/subversion/svnlocationwidget.h index a1559456c..837649855 100644 --- a/plugins/subversion/svnlocationwidget.h +++ b/plugins/subversion/svnlocationwidget.h @@ -1,26 +1,26 @@ /*************************************************************************** * Copyright 2010 Aleix Pol Gonzalez * * * * 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. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNLOCATIONWIDGET_H #define KDEVPLATFORM_PLUGIN_SVNLOCATIONWIDGET_H #include class SvnLocationWidget : public KDevelop::StandardVcsLocationWidget { Q_OBJECT public: - explicit SvnLocationWidget(QWidget* parent = nullptr, Qt::WindowFlags f = nullptr); + explicit SvnLocationWidget(QWidget* parent = nullptr); KDevelop::VcsLocation location() const override; bool isCorrect() const override; }; #endif // KDEVPLATFORM_PLUGIN_SVNLOCATIONWIDGET_H diff --git a/shell/configdialog.cpp b/shell/configdialog.cpp index 156c51c5f..d01daf919 100644 --- a/shell/configdialog.cpp +++ b/shell/configdialog.cpp @@ -1,215 +1,216 @@ /* * This file is part of KDevelop * Copyright 2014 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "configdialog.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; //FIXME: unit test this code! -ConfigDialog::ConfigDialog(QWidget* parent, Qt::WindowFlags flags) - : KPageDialog(parent, flags), m_currentPageHasChanges(false) +ConfigDialog::ConfigDialog(QWidget* parent) + : KPageDialog(parent) + , m_currentPageHasChanges(false) { setWindowTitle(i18n("Configure")); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); button(QDialogButtonBox::Apply)->setEnabled(false); setObjectName(QStringLiteral("configdialog")); auto onApplyClicked = [this] { auto page = qobject_cast(currentPage()->widget()); Q_ASSERT(page); applyChanges(page); }; connect(button(QDialogButtonBox::Apply), &QPushButton::clicked, onApplyClicked); connect(button(QDialogButtonBox::Ok), &QPushButton::clicked, onApplyClicked); connect(button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, [this]() { auto page = qobject_cast(currentPage()->widget()); Q_ASSERT(page); page->defaults(); }); connect(this, &KPageDialog::currentPageChanged, this, &ConfigDialog::checkForUnsavedChanges); // make sure we don't keep any entries for unloaded plugins connect(ICore::self()->pluginController(), &IPluginController::unloadingPlugin, this, &ConfigDialog::removePagesForPlugin); } KPageWidgetItem* ConfigDialog::itemForPage(ConfigPage* page) const { for (auto& item : m_pages) { if (item->widget() == page) { return item; } } return nullptr; } int ConfigDialog::checkForUnsavedChanges(KPageWidgetItem* current, KPageWidgetItem* before) { Q_UNUSED(current); if (!m_currentPageHasChanges) { return KMessageBox::Yes; } // before must be non-null, because if we change from nothing to a new page m_currentPageHasChanges must also be false! Q_ASSERT(before); auto oldPage = qobject_cast(before->widget()); Q_ASSERT(oldPage); auto dialogResult = KMessageBox::warningYesNoCancel(this, i18n("The settings of the current module have changed.\n" "Do you want to apply the changes or discard them?"), i18n("Apply Settings"), KStandardGuiItem::apply(), KStandardGuiItem::discard(), KStandardGuiItem::cancel()); if (dialogResult == KMessageBox::No) { oldPage->reset(); m_currentPageHasChanges = false; button(QDialogButtonBox::Apply)->setEnabled(false); } else if (dialogResult == KMessageBox::Yes) { applyChanges(oldPage); } else if (dialogResult == KMessageBox::Cancel) { // restore old state QSignalBlocker block(this); // prevent recursion setCurrentPage(before); } return dialogResult; } void ConfigDialog::closeEvent(QCloseEvent* event) { if (checkForUnsavedChanges(currentPage(), currentPage()) == KMessageBox::Cancel) { // if the user clicked cancel he wants to continue editing the current page -> don't close event->ignore(); } else { event->accept(); } } void ConfigDialog::removeConfigPage(ConfigPage* page) { auto item = itemForPage(page); Q_ASSERT(item); removePage(item); m_pages.removeAll(QPointer(item)); // also remove all items that were deleted because a parent KPageWidgetItem was removed m_pages.removeAll(QPointer()); } void ConfigDialog::removePagesForPlugin(IPlugin* plugin) { Q_ASSERT(plugin); foreach (auto&& item, m_pages) { if (!item) { continue; } auto page = qobject_cast(item->widget()); if (page && page->plugin() == plugin) { removePage(item); // this also deletes the config page -> QPointer is set to null } }; // also remove all items that were deleted because a parent KPageWidgetItem was removed m_pages.removeAll(QPointer()); } void ConfigDialog::addConfigPage(ConfigPage* page, ConfigPage* previous) { if (previous) { auto previousItem = itemForPage(previous); Q_ASSERT(previousItem); addConfigPageInternal(insertPage(previousItem, page, page->name()), page); } else { addConfigPageInternal(addPage(page, page->name()), page); } } void ConfigDialog::addSubConfigPage(ConfigPage* parentPage, ConfigPage* page) { auto item = itemForPage(parentPage); Q_ASSERT(item); addConfigPageInternal(addSubPage(item, page, page->name()), page); } void ConfigDialog::addConfigPageInternal(KPageWidgetItem* item, ConfigPage* page) { item->setHeader(page->fullName()); item->setIcon(page->icon()); page->initConfigManager(); page->reset(); // make sure all widgets are in the correct state // make sure that we only connect to changed after calling reset() connect(page, &ConfigPage::changed, this, &ConfigDialog::onPageChanged); m_pages.append(item); for (int i = 0; i < page->childPages(); ++i) { auto child = page->childPage(i); addSubConfigPage(page, child); } } void ConfigDialog::onPageChanged() { QObject* from = sender(); if (from && from != currentPage()->widget()) { qCWarning(SHELL) << "Settings in config page" << from << "changed, while" << currentPage()->widget() << "is currently selected. This case is not implemented yet."; return; // TODO: add a QHash as a member to make sure the apply button is always correct // TODO: when pressing okay show confirm dialog if other pages have changed or just silently apply every page? "Items on other pages have changed, do you wish to review those changes? + list with changed pages." } if (!m_currentlyApplyingChanges) { // e.g. PluginPreferences emits changed() from its apply method, better fix this here than having to // ensure that no plugin emits changed() from apply() // together with KPageDialog emitting currentPageChanged("Plugins", nullptr) this could cause a crash // when we dereference before m_currentPageHasChanges = true; button(QDialogButtonBox::Apply)->setEnabled(true); } } void ConfigDialog::applyChanges(ConfigPage* page) { // must set this to false before calling apply, otherwise we get the confirmation dialog // whenever we enable/disable plugins. // This is because KPageWidget then emits currentPageChanged("Plugins", nullptr), which seems like a bug to me, // it should rather emit currentPageChanged("Plugins", "Plugins") or even better nothing at all, since the current // page did not actually change! // TODO: fix KPageWidget m_currentPageHasChanges = false; m_currentlyApplyingChanges = true; page->apply(); m_currentlyApplyingChanges = false; Q_ASSERT(!m_currentPageHasChanges); button(QDialogButtonBox::Apply)->setEnabled(false); emit configSaved(page); } diff --git a/shell/configdialog.h b/shell/configdialog.h index 64252eb54..69cafdb14 100644 --- a/shell/configdialog.h +++ b/shell/configdialog.h @@ -1,88 +1,88 @@ /* * This file is part of KDevelop * Copyright 2014 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef KDEVPLATFORM_CONFIGDIALOG_H #define KDEVPLATFORM_CONFIGDIALOG_H #include namespace KDevelop { class ConfigPage; class IPlugin; /** * This class is meant to be used to show the global config dialog and the per-project config dialog. * * This used to be handled by KSettings::Dialog, but since we are no longer using KCMs for config widgets, * we use this class instead. * * TODO: check if we can share this with Kate */ class ConfigDialog : public KPageDialog { Q_OBJECT public: - explicit ConfigDialog(QWidget* parent = nullptr, Qt::WindowFlags flags = nullptr); + explicit ConfigDialog(QWidget* parent = nullptr); public Q_SLOTS: /** * Remove a config page */ void removeConfigPage(ConfigPage* page); /** * Add a new config page. * @param page the new page to add * @param next if this parameter is passed the new page will be before it, otherwise * it will be inserted as the last config page. */ void addConfigPage(ConfigPage* page, ConfigPage* next = nullptr); /** * Add a new sub config page * @param parentPage the parent page * @param page the page to add */ void addSubConfigPage(ConfigPage* parentPage, ConfigPage* page); Q_SIGNALS: void configSaved(ConfigPage* page); protected: void closeEvent(QCloseEvent* event) override; private: KPageWidgetItem* itemForPage(ConfigPage* page) const; int checkForUnsavedChanges(KPageWidgetItem* current, KPageWidgetItem* before); void applyChanges(ConfigPage* page); void removePagesForPlugin(IPlugin* plugin); void addConfigPageInternal(KPageWidgetItem* item, ConfigPage* page); void onPageChanged(); private: // we have to use QPointer since KPageDialog::removePage() also removes all child pages QList> m_pages; // TODO: use a QVector once we depend on Qt 5.4 bool m_currentPageHasChanges = false; bool m_currentlyApplyingChanges = false; }; } #endif // KDEVPLATFORM_CONFIGDIALOG_H