diff --git a/kdevplatform/project/projectchangesmodel.cpp b/kdevplatform/project/projectchangesmodel.cpp index b47df2a8b6..ffe3e9fcbe 100644 --- a/kdevplatform/project/projectchangesmodel.cpp +++ b/kdevplatform/project/projectchangesmodel.cpp @@ -1,295 +1,295 @@ /* This file is part of KDevelop Copyright 2010 Aleix Pol Gonzalez 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 "projectchangesmodel.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KDevelop::IProject*) using namespace KDevelop; ProjectChangesModel::ProjectChangesModel(QObject* parent) : VcsFileChangesModel(parent) { const auto projects = ICore::self()->projectController()->projects(); for (IProject* p : projects) { addProject(p); } connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &ProjectChangesModel::addProject); connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, &ProjectChangesModel::removeProject); connect(ICore::self()->documentController(), &IDocumentController::documentSaved, this, &ProjectChangesModel::documentSaved); connect(ICore::self()->projectController()->projectModel(), &ProjectModel::rowsInserted, this, &ProjectChangesModel::itemsAdded); connect(ICore::self()->runController(), &IRunController::jobUnregistered, this, &ProjectChangesModel::jobUnregistered); } ProjectChangesModel::~ProjectChangesModel() {} void ProjectChangesModel::addProject(IProject* p) { QStandardItem* it = new QStandardItem(p->name()); it->setData(p->name(), ProjectChangesModel::ProjectNameRole); IPlugin* plugin = p->versionControlPlugin(); if(plugin) { auto* vcs = plugin->extension(); auto info = ICore::self()->pluginController()->pluginInfo(plugin); it->setIcon(QIcon::fromTheme(info.iconName())); it->setToolTip(vcs->name()); auto* branchingExtension = plugin->extension(); if(branchingExtension) { const auto pathUrl = p->path().toUrl(); branchingExtension->registerRepositoryForCurrentBranchChanges(pathUrl); // can't use new signal slot syntax here, IBranchingVersionControl is not a QObject connect(plugin, SIGNAL(repositoryBranchChanged(QUrl)), this, SLOT(repositoryBranchChanged(QUrl))); repositoryBranchChanged(pathUrl); } else reload(QList() << p); } else { it->setEnabled(false); } appendRow(it); } void ProjectChangesModel::removeProject(IProject* p) { QStandardItem* it=projectItem(p); if (!it) { // when the project is closed before it was fully populated, we won't ever see a // projectOpened signal - handle this gracefully by just ignoring the remove request return; } removeRow(it->row()); } QStandardItem* findItemChild(QStandardItem* parent, const QVariant& value, int role = Qt::DisplayRole) { for(int i=0; irowCount(); i++) { QStandardItem* curr=parent->child(i); if(curr->data(role) == value) return curr; } return nullptr; } QStandardItem* ProjectChangesModel::projectItem(IProject* p) const { return findItemChild(invisibleRootItem(), p->name(), ProjectChangesModel::ProjectNameRole); } void ProjectChangesModel::updateState(IProject* p, const KDevelop::VcsStatusInfo& status) { QStandardItem* pItem = projectItem(p); Q_ASSERT(pItem); VcsFileChangesModel::updateState(pItem, status); } void ProjectChangesModel::changes(IProject* project, const QList& urls, IBasicVersionControl::RecursionMode mode) { IPlugin* vcsplugin=project->versionControlPlugin(); IBasicVersionControl* vcs = vcsplugin ? vcsplugin->extension() : nullptr; if(vcs && vcs->isVersionControlled(urls.first())) { //TODO: filter? VcsJob* job=vcs->status(urls, mode); - job->setProperty("urls", qVariantFromValue>(urls)); - job->setProperty("mode", qVariantFromValue(mode)); - job->setProperty("project", qVariantFromValue(project)); + job->setProperty("urls", QVariant::fromValue>(urls)); + job->setProperty("mode", QVariant::fromValue(mode)); + job->setProperty("project", QVariant::fromValue(project)); connect(job, &VcsJob::finished, this, &ProjectChangesModel::statusReady); ICore::self()->runController()->registerJob(job); } } void ProjectChangesModel::statusReady(KJob* job) { auto* status=static_cast(job); const QList states = status->fetchResults().toList(); auto* project = job->property("project").value(); if(!project) return; QSet foundUrls; foundUrls.reserve(states.size()); for (const QVariant& state : states) { const VcsStatusInfo st = state.value(); foundUrls += st.url(); updateState(project, st); } QStandardItem* itProject = projectItem(project); if (!itProject) { qCDebug(PROJECT) << "Project no longer listed in model:" << project->name() << "- skipping update"; return; } IBasicVersionControl::RecursionMode mode = IBasicVersionControl::RecursionMode(job->property("mode").toInt()); const QSet uncertainUrls = urls(itProject).toSet().subtract(foundUrls); const QList sourceUrls = job->property("urls").value>(); for (const QUrl& url : sourceUrls) { if(url.isLocalFile() && QDir(url.toLocalFile()).exists()) { for (const QUrl& currentUrl : uncertainUrls) { if((mode == IBasicVersionControl::NonRecursive && currentUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash) == url.adjusted(QUrl::StripTrailingSlash)) || (mode == IBasicVersionControl::Recursive && url.isParentOf(currentUrl)) ) { removeUrl(currentUrl); } } } } } void ProjectChangesModel::documentSaved(KDevelop::IDocument* document) { reload({document->url()}); } void ProjectChangesModel::itemsAdded(const QModelIndex& parent, int start, int end) { ProjectModel* model=ICore::self()->projectController()->projectModel(); ProjectBaseItem* item=model->itemFromIndex(parent); if(!item) return; IProject* project=item->project(); if(!project) return; QList urls; for(int i=start; iindex(i, 0, parent); item=model->itemFromIndex(idx); if(item->type()==ProjectBaseItem::File || item->type()==ProjectBaseItem::Folder || item->type()==ProjectBaseItem::BuildFolder) urls += item->path().toUrl(); } if(!urls.isEmpty()) changes(project, urls, KDevelop::IBasicVersionControl::NonRecursive); } void ProjectChangesModel::reload(const QList& projects) { for (IProject* project : projects) { changes(project, {project->path().toUrl()}, KDevelop::IBasicVersionControl::Recursive); } } void ProjectChangesModel::reload(const QList& urls) { for (const QUrl& url : urls) { IProject* project=ICore::self()->projectController()->findProjectForUrl(url); if (project) { // FIXME: merge multiple urls of the same project changes(project, {url}, KDevelop::IBasicVersionControl::NonRecursive); } } } void ProjectChangesModel::reloadAll() { QList< IProject* > projects = ICore::self()->projectController()->projects(); reload(projects); } void ProjectChangesModel::jobUnregistered(KJob* job) { static const std::array readOnly = { KDevelop::VcsJob::Add, KDevelop::VcsJob::Remove, KDevelop::VcsJob::Pull, KDevelop::VcsJob::Commit, KDevelop::VcsJob::Move, KDevelop::VcsJob::Copy, KDevelop::VcsJob::Revert, }; auto* vcsjob = qobject_cast(job); if (vcsjob && std::find(readOnly.begin(), readOnly.end(), vcsjob->type()) != readOnly.end()) { reloadAll(); } } void ProjectChangesModel::repositoryBranchChanged(const QUrl& url) { IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if(project) { IPlugin* v = project->versionControlPlugin(); Q_ASSERT(v); auto* branching = v->extension(); Q_ASSERT(branching); VcsJob* job = branching->currentBranch(url); connect(job, &VcsJob::resultsReady, this, &ProjectChangesModel::branchNameReady); job->setProperty("project", QVariant::fromValue(project)); ICore::self()->runController()->registerJob(job); } } void ProjectChangesModel::branchNameReady(VcsJob* job) { auto* project = qobject_cast(job->property("project").value()); if(job->status()==VcsJob::JobSucceeded) { QString name = job->fetchResults().toString(); QString branchName = name.isEmpty() ? i18n("no branch") : name; projectItem(project)->setText(i18nc("project name (branch name)", "%1 (%2)", project->name(), branchName)); } else { projectItem(project)->setText(project->name()); } reload(QList() << project); } diff --git a/kdevplatform/shell/launchconfigurationdialog.cpp b/kdevplatform/shell/launchconfigurationdialog.cpp index 0fb112a18f..ed9a54ad68 100644 --- a/kdevplatform/shell/launchconfigurationdialog.cpp +++ b/kdevplatform/shell/launchconfigurationdialog.cpp @@ -1,1018 +1,1018 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat 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 "launchconfigurationdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "runcontroller.h" #include "launchconfiguration.h" #include "debug.h" #include #include #include namespace KDevelop { bool launchConfigGreaterThan(KDevelop::LaunchConfigurationType* a, KDevelop::LaunchConfigurationType* b) { return a->name()>b->name(); } //TODO: Maybe use KPageDialog instead, might make the model stuff easier and the default-size stuff as well LaunchConfigurationDialog::LaunchConfigurationDialog(QWidget* parent) : QDialog(parent) { setWindowTitle( i18n( "Launch Configurations" ) ); QWidget *mainWidget = new QWidget(this); auto *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(mainWidget); setupUi(mainWidget); splitter->setSizes(QList{260, 620}); splitter->setCollapsible(0, false); addConfig->setToolTip(i18nc("@info:tooltip", "Add a new launch configuration.")); deleteConfig->setEnabled( false ); deleteConfig->setToolTip(i18nc("@info:tooltip", "Delete selected launch configuration.")); model = new LaunchConfigurationsModel( tree ); tree->setModel( model ); tree->setExpandsOnDoubleClick( true ); tree->setSelectionBehavior( QAbstractItemView::SelectRows ); tree->setSelectionMode( QAbstractItemView::SingleSelection ); tree->setUniformRowHeights( true ); tree->setItemDelegate( new LaunchConfigurationModelDelegate(this) ); tree->setColumnHidden(1, true); for(int row=0; rowrowCount(); row++) { tree->setExpanded(model->index(row, 0), true); } tree->setContextMenuPolicy(Qt::CustomContextMenu); connect( tree, &QTreeView::customContextMenuRequested, this, &LaunchConfigurationDialog::doTreeContextMenu ); connect( deleteConfig, &QPushButton::clicked, this, &LaunchConfigurationDialog::deleteConfiguration); connect( model, &LaunchConfigurationsModel::dataChanged, this, &LaunchConfigurationDialog::modelChanged ); connect( tree->selectionModel(), &QItemSelectionModel::selectionChanged, this, &LaunchConfigurationDialog::selectionChanged); QModelIndex idx = model->indexForConfig( Core::self()->runControllerInternal()->defaultLaunch() ); qCDebug(SHELL) << "selecting index:" << idx; if( !idx.isValid() ) { for( int i = 0; i < model->rowCount(); i++ ) { if( model->rowCount( model->index( i, 0, QModelIndex() ) ) > 0 ) { idx = model->index( 1, 0, model->index( i, 0, QModelIndex() ) ); break; } } if( !idx.isValid() ) { idx = model->index( 0, 0, QModelIndex() ); } } tree->selectionModel()->select( QItemSelection( idx, idx ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->selectionModel()->setCurrentIndex( idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); // Unfortunately tree->resizeColumnToContents() only looks at the top-level // items, instead of all open ones. Hence we're calculating it ourselves like // this: // Take the selected index, check if it has childs, if so take the first child // Then count the level by going up, then let the tree calculate the width // for the selected or its first child index and add indentation*level // // If Qt Software ever fixes resizeColumnToContents, the following line // can be enabled and the rest be removed // tree->resizeColumnToContents( 0 ); int level = 0; QModelIndex widthidx = idx; if( model->rowCount( idx ) > 0 ) { widthidx = model->index( 0, 0, idx ); } QModelIndex parentidx = widthidx.parent(); while( parentidx.isValid() ) { level++; parentidx = parentidx.parent(); } // make sure the base column width is honored, e.g. when no launch configs exist tree->resizeColumnToContents(0); int width = tree->columnWidth( 0 ); while ( widthidx.isValid() ) { width = qMax( width, level*tree->indentation() + tree->indentation() + tree->sizeHintForIndex( widthidx ).width() ); widthidx = widthidx.parent(); } tree->setColumnWidth( 0, width ); auto* m = new QMenu(this); QList types = Core::self()->runController()->launchConfigurationTypes(); std::sort(types.begin(), types.end(), launchConfigGreaterThan); //we want it in reverse order for (LaunchConfigurationType* type : qAsConst(types)) { connect(type, &LaunchConfigurationType::signalAddLaunchConfiguration, this, &LaunchConfigurationDialog::addConfiguration); QMenu* suggestionsMenu = type->launcherSuggestions(); if(suggestionsMenu) { // take ownership suggestionsMenu->setParent(m, suggestionsMenu->windowFlags()); m->addMenu(suggestionsMenu); } } // Simplify menu structure to get rid of 1-entry levels while (m->actions().count() == 1) { QMenu* subMenu = m->actions().at(0)->menu(); if (subMenu && subMenu->isEnabled() && subMenu->actions().count()<5) { m = subMenu; } else { break; } } if(!m->isEmpty()) { auto* separator = new QAction(m); separator->setSeparator(true); m->insertAction(m->actions().at(0), separator); } for (LaunchConfigurationType* type : qAsConst(types)) { QAction* action = new QAction(type->icon(), type->name(), m); - action->setProperty("configtype", qVariantFromValue(type)); + action->setProperty("configtype", QVariant::fromValue(type)); connect(action, &QAction::triggered, this, &LaunchConfigurationDialog::createEmptyLauncher); if(!m->actions().isEmpty()) m->insertAction(m->actions().at(0), action); else m->addAction(action); } addConfig->setMenu(m); addConfig->setEnabled( !m->isEmpty() ); messageWidget->setCloseButtonVisible( false ); messageWidget->setMessageType( KMessageWidget::Warning ); messageWidget->setText( i18n("No launch configurations available. (Is any of the Execute plugins loaded?)") ); messageWidget->setVisible( m->isEmpty() ); connect(debugger, QOverload::of(&QComboBox::currentIndexChanged), this, &LaunchConfigurationDialog::launchModeChanged); connect(buttonBox, &QDialogButtonBox::accepted, this, &LaunchConfigurationDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &LaunchConfigurationDialog::reject); connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, QOverload<>::of(&LaunchConfigurationDialog::saveConfig)); connect(buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, QOverload<>::of(&LaunchConfigurationDialog::saveConfig)); mainLayout->addWidget(buttonBox); resize( QSize(qMax(1200, sizeHint().width()), qMax(500, sizeHint().height())) ); } void LaunchConfigurationDialog::doTreeContextMenu(const QPoint& point) { if ( ! tree->selectionModel()->selectedRows().isEmpty() ) { QModelIndex selected = tree->selectionModel()->selectedRows().first(); if ( selected.parent().isValid() && ! selected.parent().parent().isValid() ) { // only display the menu if a launch config is clicked QMenu menu(tree); QAction* rename = new QAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Rename configuration"), &menu); QAction* delete_ = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete configuration"), &menu); connect(rename, &QAction::triggered, this, &LaunchConfigurationDialog::renameSelected); connect(delete_, &QAction::triggered, this, &LaunchConfigurationDialog::deleteConfiguration); menu.addAction(rename); menu.addAction(delete_); menu.exec(tree->viewport()->mapToGlobal(point)); } } } void LaunchConfigurationDialog::renameSelected() { if( !tree->selectionModel()->selectedRows().isEmpty() ) { QModelIndex parent = tree->selectionModel()->selectedRows().first(); if( parent.parent().isValid() ) { parent = parent.parent(); } QModelIndex index = model->index(tree->selectionModel()->selectedRows().first().row(), 0, parent); tree->edit( index ); } } QSize LaunchConfigurationDialog::sizeHint() const { QSize s = QDialog::sizeHint(); return s.expandedTo(QSize(880, 520)); } void LaunchConfigurationDialog::createEmptyLauncher() { auto* action = qobject_cast(sender()); Q_ASSERT(action); auto* type = qobject_cast(action->property("configtype").value()); Q_ASSERT(type); IProject* p = model->projectForIndex(tree->currentIndex()); QPair< QString, QString > launcher( type->launchers().at( 0 )->supportedModes().at(0), type->launchers().at( 0 )->id() ); ILaunchConfiguration* l = ICore::self()->runController()->createLaunchConfiguration(type, launcher, p); addConfiguration(l); } void LaunchConfigurationDialog::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { if( !deselected.indexes().isEmpty() ) { LaunchConfiguration* l = model->configForIndex( deselected.indexes().first() ); if( l ) { disconnect(l, &LaunchConfiguration::nameChanged, this, &LaunchConfigurationDialog::updateNameLabel); if( currentPageChanged ) { if( KMessageBox::questionYesNo( this, i18n("Selected Launch Configuration has unsaved changes. Do you want to save it?"), i18n("Unsaved Changes") ) == KMessageBox::Yes ) { saveConfig( deselected.indexes().first() ); } else { auto* tab = qobject_cast( stack->currentWidget() ); tab->setLaunchConfiguration( l ); buttonBox->button(QDialogButtonBox::Apply)->setEnabled( false ); currentPageChanged = false; } } } } updateNameLabel(nullptr); for( int i = 1; i < stack->count(); i++ ) { QWidget* w = stack->widget(i); stack->removeWidget(w); delete w; } if( !selected.indexes().isEmpty() ) { QModelIndex idx = selected.indexes().first(); LaunchConfiguration* l = model->configForIndex( idx ); ILaunchMode* lm = model->modeForIndex( idx ); if( l ) { updateNameLabel( l ); tree->expand( model->indexForConfig( l ) ); connect( l, &LaunchConfiguration::nameChanged, this, &LaunchConfigurationDialog::updateNameLabel ); if( lm ) { QVariant currentLaunchMode = idx.sibling(idx.row(), 1).data(Qt::EditRole); { QSignalBlocker blocker(debugger); const QList launchers = l->type()->launchers(); debugger->clear(); for (ILauncher* launcher : launchers) { if (launcher->supportedModes().contains(lm->id())) { debugger->addItem(launcher->name(), launcher->id()); } } debugger->setCurrentIndex(debugger->findData(currentLaunchMode)); } debugger->setVisible(debugger->count()>0); debugLabel->setVisible(debugger->count()>0); ILauncher* launcher = l->type()->launcherForId( currentLaunchMode.toString() ); if( launcher ) { LaunchConfigPagesContainer* tab = launcherWidgets.value( launcher ); if(!tab) { QList pages = launcher->configPages(); if(!pages.isEmpty()) { tab = new LaunchConfigPagesContainer( launcher->configPages(), stack ); connect( tab, &LaunchConfigPagesContainer::changed, this, &LaunchConfigurationDialog::pageChanged ); stack->addWidget( tab ); } } if(tab) { tab->setLaunchConfiguration( l ); stack->setCurrentWidget( tab ); } else { QLabel* label = new QLabel(i18nc("%1 is a launcher name", "No configuration is needed for '%1'", launcher->name()), stack); label->setAlignment(Qt::AlignCenter); QFont font = label->font(); font.setItalic(true); label->setFont(font); stack->addWidget(label); stack->setCurrentWidget(label); } updateNameLabel( l ); addConfig->setEnabled( false ); deleteConfig->setEnabled( false ); } else { addConfig->setEnabled( false ); deleteConfig->setEnabled( false ); stack->setCurrentIndex( 0 ); } } else { //TODO: enable removal button LaunchConfigurationType* type = l->type(); LaunchConfigPagesContainer* tab = typeWidgets.value( type ); if( !tab ) { tab = new LaunchConfigPagesContainer( type->configPages(), stack ); connect( tab, &LaunchConfigPagesContainer::changed, this, &LaunchConfigurationDialog::pageChanged ); stack->addWidget( tab ); } qCDebug(SHELL) << "created pages, setting config up"; tab->setLaunchConfiguration( l ); stack->setCurrentWidget( tab ); addConfig->setEnabled( addConfig->menu() && !addConfig->menu()->isEmpty() ); deleteConfig->setEnabled( true ); debugger->setVisible( false ); debugLabel->setVisible( false ); } } else { addConfig->setEnabled( addConfig->menu() && !addConfig->menu()->isEmpty() ); deleteConfig->setEnabled( false ); stack->setCurrentIndex( 0 ); QLabel* l = new QLabel(i18n("Select a configuration to edit from the left,
" "or click the \"Add\" button to add a new one.
"), stack); l->setAlignment(Qt::AlignCenter); stack->addWidget(l); stack->setCurrentWidget(l); debugger->setVisible( false ); debugLabel->setVisible( false ); } } else { debugger->setVisible( false ); debugLabel->setVisible( false ); addConfig->setEnabled( false ); deleteConfig->setEnabled( false ); stack->setCurrentIndex( 0 ); } } void LaunchConfigurationDialog::saveConfig( const QModelIndex& idx ) { Q_UNUSED( idx ); auto* tab = qobject_cast( stack->currentWidget() ); if( tab ) { tab->save(); buttonBox->button(QDialogButtonBox::Apply)->setEnabled( false ); currentPageChanged = false; } } void LaunchConfigurationDialog::saveConfig() { if( !tree->selectionModel()->selectedRows().isEmpty() ) { saveConfig( tree->selectionModel()->selectedRows().first() ); } } void LaunchConfigurationDialog::pageChanged() { currentPageChanged = true; buttonBox->button(QDialogButtonBox::Apply)->setEnabled( true ); } void LaunchConfigurationDialog::modelChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (tree->selectionModel()) { QModelIndex index = tree->selectionModel()->selectedRows().first(); if (index.row() >= topLeft.row() && index.row() <= bottomRight.row() && bottomRight.column() == 1) selectionChanged(tree->selectionModel()->selection(), tree->selectionModel()->selection()); } } void LaunchConfigurationDialog::deleteConfiguration() { if( !tree->selectionModel()->selectedRows().isEmpty() ) { model->deleteConfiguration( tree->selectionModel()->selectedRows().first() ); tree->resizeColumnToContents( 0 ); } } void LaunchConfigurationDialog::updateNameLabel( LaunchConfiguration* l ) { if( l ) { configName->setText( i18n("Editing %2: %1", l->name(), l->type()->name() ) ); } else { configName->clear(); } } void LaunchConfigurationDialog::createConfiguration() { if( !tree->selectionModel()->selectedRows().isEmpty() ) { QModelIndex idx = tree->selectionModel()->selectedRows().first(); if( idx.parent().isValid() ) { idx = idx.parent(); } model->createConfiguration( idx ); QModelIndex newindex = model->index( model->rowCount( idx ) - 1, 0, idx ); tree->selectionModel()->select( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->edit( newindex ); tree->resizeColumnToContents( 0 ); } } void LaunchConfigurationDialog::addConfiguration(ILaunchConfiguration* _launch) { auto* launch = dynamic_cast(_launch); Q_ASSERT(launch); int row = launch->project() ? model->findItemForProject(launch->project())->row : 0; QModelIndex idx = model->index(row, 0); model->addConfiguration(launch, idx); QModelIndex newindex = model->index( model->rowCount( idx ) - 1, 0, idx ); tree->selectionModel()->select( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->edit( newindex ); tree->resizeColumnToContents( 0 ); } LaunchConfigurationsModel::LaunchConfigurationsModel(QObject* parent): QAbstractItemModel(parent) { auto* global = new GenericPageItem; global->text = i18n("Global"); global->row = 0; const auto projects = Core::self()->projectController()->projects(); topItems.reserve(1 + projects.size()); topItems << global; for (IProject* p : projects) { auto* t = new ProjectItem; t->project = p; t->row = topItems.count(); topItems << t; } const auto launchConfigurations = Core::self()->runControllerInternal()->launchConfigurationsInternal(); for (LaunchConfiguration* l : launchConfigurations) { addItemForLaunchConfig( l ); } } void LaunchConfigurationsModel::addItemForLaunchConfig( LaunchConfiguration* l ) { auto* t = new LaunchItem; t->launch = l; TreeItem* parent; if( l->project() ) { parent = findItemForProject( l->project() ); } else { parent = topItems.at(0); } t->parent = parent; t->row = parent->children.count(); parent->children.append( t ); addLaunchModeItemsForLaunchConfig ( t ); } void LaunchConfigurationsModel::addLaunchModeItemsForLaunchConfig ( LaunchItem* t ) { QList items; QSet modes; const auto launchers = t->launch->type()->launchers(); for (ILauncher* launcher : launchers) { const auto supportedModes = launcher->supportedModes(); for (const QString& mode : supportedModes) { if( !modes.contains( mode ) && launcher->configPages().count() > 0 ) { modes.insert( mode ); auto* lmi = new LaunchModeItem; lmi->mode = Core::self()->runController()->launchModeForId( mode ); lmi->parent = t; lmi->row = t->children.count(); items.append( lmi ); } } } if( !items.isEmpty() ) { QModelIndex p = indexForConfig( t->launch ); beginInsertRows( p, t->children.count(), t->children.count() + items.count() - 1 ); t->children.append( items ); endInsertRows(); } } LaunchConfigurationsModel::ProjectItem* LaunchConfigurationsModel::findItemForProject(IProject* p) const { for (TreeItem* t : topItems) { auto* pi = dynamic_cast( t ); if( pi && pi->project == p ) { return pi; } } Q_ASSERT(false); return nullptr; } int LaunchConfigurationsModel::columnCount(const QModelIndex& parent) const { Q_UNUSED( parent ); return 2; } QVariant LaunchConfigurationsModel::data(const QModelIndex& index, int role) const { if( index.isValid() && index.column() >= 0 && index.column() < 2 ) { auto* t = static_cast( index.internalPointer() ); switch( role ) { case Qt::DisplayRole: { auto* li = dynamic_cast( t ); if( li ) { if( index.column() == 0 ) { return li->launch->name(); } else if( index.column() == 1 ) { return li->launch->type()->name(); } } auto* pi = dynamic_cast( t ); if( pi && index.column() == 0 ) { return pi->project->name(); } auto* gpi = dynamic_cast( t ); if( gpi && index.column() == 0 ) { return gpi->text; } auto* lmi = dynamic_cast( t ); if( lmi ) { if( index.column() == 0 ) { return lmi->mode->name(); } else if( index.column() == 1 ) { LaunchConfiguration* l = configForIndex( index ); return l->type()->launcherForId( l->launcherForMode( lmi->mode->id() ) )->name(); } } break; } case Qt::DecorationRole: { auto* li = dynamic_cast( t ); if( index.column() == 0 && li ) { return li->launch->type()->icon(); } auto* lmi = dynamic_cast( t ); if( lmi && index.column() == 0 ) { return lmi->mode->icon(); } if ( index.column() == 0 && !index.parent().isValid() ) { if (index.row() == 0) { // global item return QIcon::fromTheme(QStringLiteral("folder")); } else { // project item return QIcon::fromTheme(QStringLiteral("folder-development")); } } break; } case Qt::EditRole: { auto* li = dynamic_cast( t ); if( li ) { if( index.column() == 0 ) { return li->launch->name(); } else if ( index.column() == 1 ) { return li->launch->type()->id(); } } auto* lmi = dynamic_cast( t ); if( lmi && index.column() == 1 ) { return configForIndex( index )->launcherForMode( lmi->mode->id() ); } break; } default: break; } } return QVariant(); } QModelIndex LaunchConfigurationsModel::index(int row, int column, const QModelIndex& parent) const { if( !hasIndex( row, column, parent ) ) return QModelIndex(); TreeItem* tree; if( !parent.isValid() ) { tree = topItems.at( row ); } else { auto* t = static_cast( parent.internalPointer() ); tree = t->children.at( row ); } if( tree ) { return createIndex( row, column, tree ); } return QModelIndex(); } QModelIndex LaunchConfigurationsModel::parent(const QModelIndex& child) const { if( child.isValid() ) { auto* t = static_cast( child.internalPointer() ); if( t->parent ) { return createIndex( t->parent->row, 0, t->parent ); } } return QModelIndex(); } int LaunchConfigurationsModel::rowCount(const QModelIndex& parent) const { if( parent.column() > 0 ) return 0; if( parent.isValid() ) { auto* t = static_cast( parent.internalPointer() ); return t->children.count(); } else { return topItems.count(); } return 0; } QVariant LaunchConfigurationsModel::headerData(int section, Qt::Orientation orientation, int role) const { if( orientation == Qt::Horizontal && role == Qt::DisplayRole ) { if( section == 0 ) { return i18nc("Name of the Launch Configurations", "Name"); } else if( section == 1 ) { return i18nc("The type of the Launch Configurations (i.e. Python Application, C++ Application)", "Type"); } } return QVariant(); } Qt::ItemFlags LaunchConfigurationsModel::flags(const QModelIndex& index) const { if( index.isValid() && index.column() >= 0 && index.column() < columnCount( QModelIndex() ) ) { auto* t = static_cast( index.internalPointer() ); if( t && ( dynamic_cast( t ) || ( dynamic_cast( t ) && index.column() == 1 ) ) ) { return Qt::ItemFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable ); } else if( t ) { return Qt::ItemFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable ); } } return Qt::NoItemFlags; } bool LaunchConfigurationsModel::setData(const QModelIndex& index, const QVariant& value, int role) { if( index.isValid() && index.parent().isValid() && role == Qt::EditRole ) { if( index.row() >= 0 && index.row() < rowCount( index.parent() ) ) { auto* t = dynamic_cast( static_cast( index.internalPointer() ) ); if( t ) { if( index.column() == 0 ) { t->launch->setName( value.toString() ); } else if( index.column() == 1 ) { if (t->launch->type()->id() != value.toString()) { t->launch->setType( value.toString() ); QModelIndex p = indexForConfig(t->launch); qCDebug(SHELL) << data(p); beginRemoveRows( p, 0, t->children.count() ); qDeleteAll( t->children ); t->children.clear(); endRemoveRows(); addLaunchModeItemsForLaunchConfig( t ); } } emit dataChanged(index, index); return true; } auto* lmi = dynamic_cast( static_cast( index.internalPointer() ) ); if( lmi ) { if( index.column() == 1 && index.data(Qt::EditRole)!=value) { LaunchConfiguration* l = configForIndex( index ); l->setLauncherForMode( lmi->mode->id(), value.toString() ); emit dataChanged(index, index); return true; } } } } return false; } ILaunchMode* LaunchConfigurationsModel::modeForIndex( const QModelIndex& idx ) const { if( idx.isValid() ) { auto* item = dynamic_cast( static_cast( idx.internalPointer() ) ); if( item ) { return item->mode; } } return nullptr; } LaunchConfiguration* LaunchConfigurationsModel::configForIndex(const QModelIndex& idx ) const { if( idx.isValid() ) { auto* item = dynamic_cast( static_cast( idx.internalPointer() ) ); if( item ) { return item->launch; } auto* lmitem = dynamic_cast( static_cast( idx.internalPointer() ) ); if( lmitem ) { return dynamic_cast( lmitem->parent )->launch; } } return nullptr; } QModelIndex LaunchConfigurationsModel::indexForConfig( LaunchConfiguration* l ) const { if( l ) { TreeItem* tparent = topItems.at( 0 ); if( l->project() ) { for (TreeItem* t : topItems) { auto* pi = dynamic_cast( t ); if( pi && pi->project == l->project() ) { tparent = t; break; } } } if( tparent ) { for (TreeItem* c : qAsConst(tparent->children)) { auto* li = dynamic_cast( c ); if( li->launch && li->launch == l ) { return index( c->row, 0, index( tparent->row, 0, QModelIndex() ) ); } } } } return QModelIndex(); } void LaunchConfigurationsModel::deleteConfiguration( const QModelIndex& index ) { auto* t = dynamic_cast( static_cast( index.internalPointer() ) ); if( !t ) return; beginRemoveRows( parent( index ), index.row(), index.row() ); t->parent->children.removeAll( t ); Core::self()->runControllerInternal()->removeLaunchConfiguration( t->launch ); endRemoveRows(); } void LaunchConfigurationsModel::createConfiguration(const QModelIndex& parent ) { if(!Core::self()->runController()->launchConfigurationTypes().isEmpty()) { auto* t = static_cast( parent.internalPointer() ); auto* ti = dynamic_cast( t ); LaunchConfigurationType* type = Core::self()->runController()->launchConfigurationTypes().at(0); QPair launcher = qMakePair( type->launchers().at( 0 )->supportedModes().at(0), type->launchers().at( 0 )->id() ); IProject* p = ( ti ? ti->project : nullptr ); ILaunchConfiguration* l = Core::self()->runController()->createLaunchConfiguration( type, launcher, p ); addConfiguration(l, parent); } } void LaunchConfigurationsModel::addConfiguration(ILaunchConfiguration* l, const QModelIndex& parent) { if( parent.isValid() ) { beginInsertRows( parent, rowCount( parent ), rowCount( parent ) ); addItemForLaunchConfig( dynamic_cast( l ) ); endInsertRows(); } else { delete l; Q_ASSERT(false && "could not add the configuration"); } } IProject* LaunchConfigurationsModel::projectForIndex(const QModelIndex& idx) { if(idx.parent().isValid()) { return projectForIndex(idx.parent()); } else { const auto* item = dynamic_cast(topItems[idx.row()]); return item ? item->project : nullptr; } } LaunchConfigPagesContainer::LaunchConfigPagesContainer( const QList& factories, QWidget* parent ) : QWidget(parent) { setLayout( new QVBoxLayout( this ) ); layout()->setContentsMargins( 0, 0, 0, 0 ); QWidget* parentwidget = this; QTabWidget* tab = nullptr; if( factories.count() > 1 ) { tab = new QTabWidget( this ); parentwidget = tab; layout()->addWidget( tab ); } for (LaunchConfigurationPageFactory* fac : factories) { LaunchConfigurationPage* page = fac->createWidget( parentwidget ); if ( page->layout() ) { // remove margins for single page, reset margins for tabbed display const int pageMargin = tab ? -1 : 0; page->layout()->setContentsMargins(pageMargin, pageMargin, pageMargin, pageMargin); } pages.append( page ); connect( page, &LaunchConfigurationPage::changed, this, &LaunchConfigPagesContainer::changed ); if( tab ) { tab->addTab( page, page->icon(), page->title() ); } else { layout()->addWidget( page ); } } } void LaunchConfigPagesContainer::setLaunchConfiguration( KDevelop::LaunchConfiguration* l ) { config = l; for (LaunchConfigurationPage* p : qAsConst(pages)) { p->loadFromConfiguration( config->config(), config->project() ); } } void LaunchConfigPagesContainer::save() { for (LaunchConfigurationPage* p : qAsConst(pages)) { p->saveToConfiguration( config->config() ); } config->config().sync(); } QWidget* LaunchConfigurationModelDelegate::createEditor ( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const { const auto* model = static_cast(index.model()); ILaunchMode* mode = model->modeForIndex( index ); LaunchConfiguration* config = model->configForIndex( index ); if( index.column() == 1 && mode && config ) { auto* box = new KComboBox( parent ); const QList launchers = config->type()->launchers(); for (auto* launcher : launchers) { if (launcher->supportedModes().contains(mode->id())) { box->addItem(launcher->name(), launcher->id()); } } return box; } else if( !mode && config && index.column() == 1 ) { auto* box = new KComboBox( parent ); const QList types = Core::self()->runController()->launchConfigurationTypes(); for (auto* type : types) { box->addItem(type->name(), type->id()); } return box; } return QStyledItemDelegate::createEditor ( parent, option, index ); } void LaunchConfigurationModelDelegate::setEditorData ( QWidget* editor, const QModelIndex& index ) const { const auto* model = static_cast(index.model()); LaunchConfiguration* config = model->configForIndex( index ); if( index.column() == 1 && config ) { auto* box = qobject_cast( editor ); box->setCurrentIndex( box->findData( index.data( Qt::EditRole ) ) ); } else { QStyledItemDelegate::setEditorData ( editor, index ); } } void LaunchConfigurationModelDelegate::setModelData ( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const { auto* lmodel = static_cast(model); LaunchConfiguration* config = lmodel->configForIndex( index ); if( index.column() == 1 && config ) { auto* box = qobject_cast( editor ); lmodel->setData( index, box->itemData( box->currentIndex() ) ); } else { QStyledItemDelegate::setModelData ( editor, model, index ); } } void LaunchConfigurationDialog::launchModeChanged(int item) { QModelIndex index = tree->currentIndex(); if(debugger->isVisible() && item>=0) tree->model()->setData(index.sibling(index.row(), 1), debugger->itemData(item), Qt::EditRole); } } diff --git a/kdevplatform/shell/runcontroller.cpp b/kdevplatform/shell/runcontroller.cpp index 6b80bf5c31..e6c592dffc 100644 --- a/kdevplatform/shell/runcontroller.cpp +++ b/kdevplatform/shell/runcontroller.cpp @@ -1,1093 +1,1093 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda Copyright 2008 Aleix Pol 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 "runcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "uicontroller.h" #include "projectcontroller.h" #include "mainwindow.h" #include "launchconfiguration.h" #include "launchconfigurationdialog.h" #include "unitylauncher.h" #include "debug.h" #include #include #include #include using namespace KDevelop; namespace { namespace Strings { QString LaunchConfigurationsGroup() { return QStringLiteral("Launch"); } QString LaunchConfigurationsListEntry() { return QStringLiteral("Launch Configurations"); } QString CurrentLaunchConfigProjectEntry() { return QStringLiteral("Current Launch Config Project"); } QString CurrentLaunchConfigNameEntry() { return QStringLiteral("Current Launch Config GroupName"); } QString ConfiguredFromProjectItemEntry() { return QStringLiteral("Configured from ProjectItem"); } } } using Target = QPair; Q_DECLARE_METATYPE(Target) //TODO: Doesn't handle add/remove of launch configs in the dialog or renaming of configs //TODO: Doesn't auto-select launch configs opened from projects class DebugMode : public ILaunchMode { public: DebugMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("debug-run")); } QString id() const override { return QStringLiteral("debug"); } QString name() const override { return i18n("Debug"); } }; class ProfileMode : public ILaunchMode { public: ProfileMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("office-chart-area")); } QString id() const override { return QStringLiteral("profile"); } QString name() const override { return i18n("Profile"); } }; class ExecuteMode : public ILaunchMode { public: ExecuteMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("system-run")); } QString id() const override { return QStringLiteral("execute"); } QString name() const override { return i18n("Execute"); } }; class KDevelop::RunControllerPrivate { public: QItemDelegate* delegate; IRunController::State state; RunController* q; QHash jobs; QAction* stopAction; KActionMenu* stopJobsMenu; QAction* runAction; QAction* dbgAction; KSelectAction* currentTargetAction; QMap launchConfigurationTypes; QList launchConfigurations; QMap launchModes; QMap > launchAsInfo; KDevelop::ProjectBaseItem* contextItem; DebugMode* debugMode; ExecuteMode* executeMode; ProfileMode* profileMode; UnityLauncher* unityLauncher; bool hasLaunchConfigType( const QString& typeId ) { return launchConfigurationTypes.contains( typeId ); } void saveCurrentLaunchAction() { if (!currentTargetAction) return; if( currentTargetAction->currentAction() ) { KConfigGroup grp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); LaunchConfiguration* l = static_cast( currentTargetAction->currentAction()->data().value() ); grp.writeEntry( Strings::CurrentLaunchConfigProjectEntry(), l->project() ? l->project()->name() : QString() ); grp.writeEntry( Strings::CurrentLaunchConfigNameEntry(), l->configGroupName() ); grp.sync(); } } QString launchActionText( LaunchConfiguration* l ) { QString label; if( l->project() ) { label = QStringLiteral("%1 : %2").arg( l->project()->name(), l->name()); } else { label = l->name(); } return label; } void launchAs( int id ) { //qCDebug(SHELL) << "Launching id:" << id; QPair info = launchAsInfo[id]; //qCDebug(SHELL) << "fetching type and mode:" << info.first << info.second; LaunchConfigurationType* type = launchConfigurationTypeForId( info.first ); ILaunchMode* mode = q->launchModeForId( info.second ); //qCDebug(SHELL) << "got mode and type:" << type << type->id() << mode << mode->id(); if( type && mode ) { const auto launchers = type->launchers(); auto it = std::find_if(launchers.begin(), launchers.end(), [&](ILauncher* l) { //qCDebug(SHELL) << "available launcher" << l << l->id() << l->supportedModes(); return (l->supportedModes().contains(mode->id())); }); if (it != launchers.end()) { ILauncher* launcher = *it; QStringList itemPath = Core::self()->projectController()->projectModel()->pathFromIndex(contextItem->index()); auto it = std::find_if(launchConfigurations.constBegin(), launchConfigurations.constEnd(), [&] (LaunchConfiguration* l) { QStringList path = l->config().readEntry(Strings::ConfiguredFromProjectItemEntry(), QStringList()); if (l->type() == type && path == itemPath) { qCDebug(SHELL) << "already generated ilaunch" << path; return true; } return false; }); ILaunchConfiguration* ilaunch = (it != launchConfigurations.constEnd()) ? *it : nullptr; if (!ilaunch) { ilaunch = q->createLaunchConfiguration( type, qMakePair( mode->id(), launcher->id() ), contextItem->project(), contextItem->text() ); auto* launch = static_cast(ilaunch); type->configureLaunchFromItem( launch->config(), contextItem ); launch->config().writeEntry(Strings::ConfiguredFromProjectItemEntry(), itemPath); //qCDebug(SHELL) << "created config, launching"; } else { //qCDebug(SHELL) << "reusing generated config, launching"; } q->setDefaultLaunch(ilaunch); q->execute( mode->id(), ilaunch ); } } } void updateCurrentLaunchAction() { if (!currentTargetAction) return; KConfigGroup launchGrp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); QString currentLaunchProject = launchGrp.readEntry( Strings::CurrentLaunchConfigProjectEntry(), "" ); QString currentLaunchName = launchGrp.readEntry( Strings::CurrentLaunchConfigNameEntry(), "" ); LaunchConfiguration* l = nullptr; if( currentTargetAction->currentAction() ) { l = static_cast( currentTargetAction->currentAction()->data().value() ); } else if( !launchConfigurations.isEmpty() ) { l = launchConfigurations.at( 0 ); } if( l && ( ( !currentLaunchProject.isEmpty() && ( !l->project() || l->project()->name() != currentLaunchProject ) ) || l->configGroupName() != currentLaunchName ) ) { const auto actions = currentTargetAction->actions(); for (QAction* a : actions) { LaunchConfiguration* l = static_cast( qvariant_cast( a->data() ) ); if( currentLaunchName == l->configGroupName() && ( ( currentLaunchProject.isEmpty() && !l->project() ) || ( l->project() && l->project()->name() == currentLaunchProject ) ) ) { a->setChecked( true ); break; } } } if( !currentTargetAction->currentAction() ) { qCDebug(SHELL) << "oops no current action, using first if list is non-empty"; if( !currentTargetAction->actions().isEmpty() ) { currentTargetAction->actions().at(0)->setChecked( true ); } } } void addLaunchAction( LaunchConfiguration* l ) { if (!currentTargetAction) return; QAction* action = currentTargetAction->addAction(launchActionText( l )); - action->setData(qVariantFromValue(l)); + action->setData(QVariant::fromValue(l)); } void readLaunchConfigs( const KSharedConfigPtr& cfg, IProject* prj ) { KConfigGroup group(cfg, Strings::LaunchConfigurationsGroup()); const QStringList configs = group.readEntry(Strings::LaunchConfigurationsListEntry(), QStringList()); for (const QString& cfg : configs) { KConfigGroup grp = group.group( cfg ); if( launchConfigurationTypeForId( grp.readEntry( LaunchConfiguration::LaunchConfigurationTypeEntry(), "" ) ) ) { q->addLaunchConfiguration( new LaunchConfiguration( grp, prj ) ); } } } LaunchConfigurationType* launchConfigurationTypeForId( const QString& id ) { QMap::iterator it = launchConfigurationTypes.find( id ); if( it != launchConfigurationTypes.end() ) { return it.value(); } else { qCWarning(SHELL) << "couldn't find type for id:" << id << ". Known types:" << launchConfigurationTypes.keys(); } return nullptr; } }; RunController::RunController(QObject *parent) : IRunController(parent) , d_ptr(new RunControllerPrivate) { Q_D(RunController); setObjectName(QStringLiteral("RunController")); QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kdevelop/RunController"), this, QDBusConnection::ExportScriptableSlots); // TODO: need to implement compile only if needed before execute // TODO: need to implement abort all running programs when project closed d->currentTargetAction = nullptr; d->state = Idle; d->q = this; d->delegate = new RunDelegate(this); d->contextItem = nullptr; d->executeMode = nullptr; d->debugMode = nullptr; d->profileMode = nullptr; d->unityLauncher = new UnityLauncher(this); d->unityLauncher->setLauncherId(KAboutData::applicationData().desktopFileName()); if(!(Core::self()->setupFlags() & Core::NoUi)) { // Note that things like registerJob() do not work without the actions, it'll simply crash. setupActions(); } } RunController::~RunController() = default; void KDevelop::RunController::launchChanged( LaunchConfiguration* l ) { Q_D(RunController); const auto actions = d->currentTargetAction->actions(); for (QAction* a : actions) { if( static_cast( a->data().value() ) == l ) { a->setText( d->launchActionText( l ) ); break; } } } void RunController::cleanup() { Q_D(RunController); delete d->executeMode; d->executeMode = nullptr; delete d->profileMode; d->profileMode = nullptr; delete d->debugMode; d->debugMode = nullptr; stopAllProcesses(); d->saveCurrentLaunchAction(); } void RunController::initialize() { Q_D(RunController); d->executeMode = new ExecuteMode(); addLaunchMode( d->executeMode ); d->profileMode = new ProfileMode(); addLaunchMode( d->profileMode ); d->debugMode = new DebugMode; addLaunchMode( d->debugMode ); d->readLaunchConfigs( Core::self()->activeSession()->config(), nullptr ); const auto projects = Core::self()->projectController()->projects(); for (IProject* project : projects) { slotProjectOpened(project); } connect(Core::self()->projectController(), &IProjectController::projectOpened, this, &RunController::slotProjectOpened); connect(Core::self()->projectController(), &IProjectController::projectClosing, this, &RunController::slotProjectClosing); connect(Core::self()->projectController(), &IProjectController::projectConfigurationChanged, this, &RunController::slotRefreshProject); if( (Core::self()->setupFlags() & Core::NoUi) == 0 ) { // Only do this in GUI mode d->updateCurrentLaunchAction(); } } KJob* RunController::execute(const QString& runMode, ILaunchConfiguration* launch) { if( !launch ) { qCDebug(SHELL) << "execute called without launch config!"; return nullptr; } auto* run = static_cast(launch); //TODO: Port to launch framework, probably needs to be part of the launcher //if(!run.dependencies().isEmpty()) // ICore::self()->documentController()->saveAllDocuments(IDocument::Silent); //foreach(KJob* job, run.dependencies()) //{ // jobs.append(job); //} qCDebug(SHELL) << "mode:" << runMode; QString launcherId = run->launcherForMode( runMode ); qCDebug(SHELL) << "launcher id:" << launcherId; ILauncher* launcher = run->type()->launcherForId( launcherId ); if( !launcher ) { const QString messageText = i18n("The current launch configuration does not support the '%1' mode.", runMode); auto* message = new Sublime::Message(messageText, Sublime::Message::Error); ICore::self()->uiController()->postMessage(message); return nullptr; } KJob* launchJob = launcher->start(runMode, run); registerJob(launchJob); return launchJob; } void RunController::setupActions() { Q_D(RunController); QAction* action; // TODO not multi-window friendly, FIXME KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); action = new QAction(i18n("Configure Launches..."), this); ac->addAction(QStringLiteral("configure_launches"), action); action->setMenuRole(QAction::NoRole); // OSX: Be explicit about role, prevent hiding due to conflict with "Preferences..." menu item action->setStatusTip(i18n("Open Launch Configuration Dialog")); action->setToolTip(i18nc("@info:tooltip", "Open Launch Configuration Dialog")); action->setWhatsThis(i18nc("@info:whatsthis", "Opens a dialog to setup new launch configurations, or to change the existing ones.")); connect(action, &QAction::triggered, this, &RunController::showConfigurationDialog); d->runAction = new QAction( QIcon::fromTheme(QStringLiteral("system-run")), i18n("Execute Launch"), this); d->runAction->setIconText( i18nc("Short text for 'Execute launch' used in the toolbar", "Execute") ); ac->setDefaultShortcut( d->runAction, Qt::SHIFT + Qt::Key_F9); d->runAction->setToolTip(i18nc("@info:tooltip", "Execute current launch")); d->runAction->setStatusTip(i18n("Execute current launch")); d->runAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration.")); ac->addAction(QStringLiteral("run_execute"), d->runAction); connect(d->runAction, &QAction::triggered, this, &RunController::slotExecute); d->dbgAction = new QAction( QIcon::fromTheme(QStringLiteral("debug-run")), i18n("Debug Launch"), this); ac->setDefaultShortcut( d->dbgAction, Qt::ALT + Qt::Key_F9); d->dbgAction->setIconText( i18nc("Short text for 'Debug launch' used in the toolbar", "Debug") ); d->dbgAction->setToolTip(i18nc("@info:tooltip", "Debug current launch")); d->dbgAction->setStatusTip(i18n("Debug current launch")); d->dbgAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Debugger.")); ac->addAction(QStringLiteral("run_debug"), d->dbgAction); connect(d->dbgAction, &QAction::triggered, this, &RunController::slotDebug); Core::self()->uiControllerInternal()->area(0, QStringLiteral("code"))->addAction(d->dbgAction); // TODO: at least get a profile target, it's sad to have the menu entry without a profiler // QAction* profileAction = new QAction( QIcon::fromTheme(""), i18n("Profile Launch"), this); // profileAction->setToolTip(i18nc("@info:tooltip", "Profile current launch")); // profileAction->setStatusTip(i18n("Profile current launch")); // profileAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Profiler.")); // ac->addAction("run_profile", profileAction); // connect(profileAction, SIGNAL(triggered(bool)), this, SLOT(slotProfile())); action = d->stopAction = new QAction( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop All Jobs"), this); action->setIconText(i18nc("Short text for 'Stop All Jobs' used in the toolbar", "Stop All")); // Ctrl+Escape would be nicer, but that is taken by the ksysguard desktop shortcut ac->setDefaultShortcut( action, QKeySequence(QStringLiteral("Ctrl+Shift+Escape"))); action->setToolTip(i18nc("@info:tooltip", "Stop all currently running jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "Requests that all running jobs are stopped.")); action->setEnabled(false); ac->addAction(QStringLiteral("run_stop_all"), action); connect(action, &QAction::triggered, this, &RunController::stopAllProcesses); Core::self()->uiControllerInternal()->area(0, QStringLiteral("debug"))->addAction(action); action = d->stopJobsMenu = new KActionMenu( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop"), this); action->setIconText(i18nc("Short text for 'Stop' used in the toolbar", "Stop")); action->setToolTip(i18nc("@info:tooltip", "Menu allowing to stop individual jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "List of jobs that can be stopped individually.")); action->setEnabled(false); ac->addAction(QStringLiteral("run_stop_menu"), action); d->currentTargetAction = new KSelectAction( i18n("Current Launch Configuration"), this); d->currentTargetAction->setToolTip(i18nc("@info:tooltip", "Current launch configuration")); d->currentTargetAction->setStatusTip(i18n("Current launch Configuration")); d->currentTargetAction->setWhatsThis(i18nc("@info:whatsthis", "Select which launch configuration to run when run is invoked.")); ac->addAction(QStringLiteral("run_default_target"), d->currentTargetAction); } LaunchConfigurationType* RunController::launchConfigurationTypeForId( const QString& id ) { Q_D(RunController); return d->launchConfigurationTypeForId( id ); } void KDevelop::RunController::slotProjectOpened(KDevelop::IProject * project) { Q_D(RunController); d->readLaunchConfigs( project->projectConfiguration(), project ); d->updateCurrentLaunchAction(); } void KDevelop::RunController::slotProjectClosing(KDevelop::IProject * project) { Q_D(RunController); if (!d->currentTargetAction) return; const auto actions = d->currentTargetAction->actions(); for (QAction* action : actions) { LaunchConfiguration* l = static_cast(qvariant_cast(action->data())); if ( project == l->project() ) { l->save(); d->launchConfigurations.removeAll(l); delete l; bool wasSelected = action->isChecked(); delete action; if (wasSelected && !d->currentTargetAction->actions().isEmpty()) d->currentTargetAction->actions().at(0)->setChecked(true); } } } void KDevelop::RunController::slotRefreshProject(KDevelop::IProject* project) { slotProjectClosing(project); slotProjectOpened(project); } void RunController::slotDebug() { Q_D(RunController); if (d->launchConfigurations.isEmpty()) { showConfigurationDialog(); } if (!d->launchConfigurations.isEmpty()) { executeDefaultLaunch( QStringLiteral("debug") ); } } void RunController::slotProfile() { Q_D(RunController); if (d->launchConfigurations.isEmpty()) { showConfigurationDialog(); } if (!d->launchConfigurations.isEmpty()) { executeDefaultLaunch( QStringLiteral("profile") ); } } void RunController::slotExecute() { Q_D(RunController); if (d->launchConfigurations.isEmpty()) { showConfigurationDialog(); } if (!d->launchConfigurations.isEmpty()) { executeDefaultLaunch( QStringLiteral("execute") ); } } void KDevelop::RunController::showConfigurationDialog() const { LaunchConfigurationDialog dlg; dlg.exec(); } LaunchConfiguration* KDevelop::RunController::defaultLaunch() const { Q_D(const RunController); QAction* projectAction = d->currentTargetAction->currentAction(); if( projectAction ) return static_cast(qvariant_cast(projectAction->data())); return nullptr; } void KDevelop::RunController::registerJob(KJob * job) { Q_D(RunController); if (!job) return; if (!(job->capabilities() & KJob::Killable)) { // see e.g. https://bugs.kde.org/show_bug.cgi?id=314187 qCWarning(SHELL) << "non-killable job" << job << "registered - this might lead to crashes on shutdown."; } if (!d->jobs.contains(job)) { QAction* stopJobAction = nullptr; if (Core::self()->setupFlags() != Core::NoUi) { stopJobAction = new QAction(job->objectName().isEmpty() ? i18n("<%1> Unnamed job", QString::fromUtf8(job->staticMetaObject.className())) : job->objectName(), this); stopJobAction->setData(QVariant::fromValue(static_cast(job))); d->stopJobsMenu->addAction(stopJobAction); connect (stopJobAction, &QAction::triggered, this, &RunController::slotKillJob); job->setUiDelegate( new KDialogJobUiDelegate() ); } d->jobs.insert(job, stopJobAction); connect( job, &KJob::finished, this, &RunController::finished ); connect( job, &KJob::destroyed, this, &RunController::jobDestroyed ); // FIXME percent is a private signal and thus we cannot use new connect syntax connect(job, SIGNAL(percent(KJob*,ulong)), this, SLOT(jobPercentChanged())); IRunController::registerJob(job); emit jobRegistered(job); } job->start(); checkState(); } void KDevelop::RunController::unregisterJob(KJob * job) { Q_D(RunController); IRunController::unregisterJob(job); Q_ASSERT(d->jobs.contains(job)); // Delete the stop job action QAction *action = d->jobs.take(job); if (action) action->deleteLater(); checkState(); emit jobUnregistered(job); } void KDevelop::RunController::checkState() { Q_D(RunController); bool running = false; int jobCount = 0; int totalProgress = 0; for (auto it = d->jobs.constBegin(), end = d->jobs.constEnd(); it != end; ++it) { KJob *job = it.key(); if (!job->isSuspended()) { running = true; ++jobCount; totalProgress += job->percent(); } } d->unityLauncher->setProgressVisible(running); if (jobCount > 0) { d->unityLauncher->setProgress((totalProgress + 1) / jobCount); } else { d->unityLauncher->setProgress(0); } if ( ( d->state != Running ? false : true ) == running ) { d->state = running ? Running : Idle; emit runStateChanged(d->state); } if (Core::self()->setupFlags() != Core::NoUi) { d->stopAction->setEnabled(running); d->stopJobsMenu->setEnabled(running); } } void KDevelop::RunController::stopAllProcesses() { Q_D(RunController); // composite jobs might remove child jobs, see also: // https://bugs.kde.org/show_bug.cgi?id=258904 const auto jobs = d->jobs.keys(); for (KJob* job : jobs) { // now we check the real list whether it was deleted if (!d->jobs.contains(job)) continue; if (job->capabilities() & KJob::Killable) { job->kill(KJob::EmitResult); } else { qCWarning(SHELL) << "cannot stop non-killable job: " << job; } } } void KDevelop::RunController::slotKillJob() { auto* action = qobject_cast(sender()); Q_ASSERT(action); KJob* job = static_cast(qvariant_cast(action->data())); if (job->capabilities() & KJob::Killable) job->kill(); } void KDevelop::RunController::finished(KJob * job) { unregisterJob(job); switch (job->error()) { case KJob::NoError: case KJob::KilledJobError: case OutputJob::FailedShownError: break; default: { auto* message = new Sublime::Message(job->errorString(), Sublime::Message::Error); Core::self()->uiController()->postMessage(message); } } } void RunController::jobDestroyed(QObject* job) { Q_D(RunController); KJob* kjob = static_cast(job); if (d->jobs.contains(kjob)) { qCWarning(SHELL) << "job destroyed without emitting finished signal!"; unregisterJob(kjob); } } void RunController::jobPercentChanged() { checkState(); } void KDevelop::RunController::suspended(KJob * job) { Q_UNUSED(job); checkState(); } void KDevelop::RunController::resumed(KJob * job) { Q_UNUSED(job); checkState(); } QList< KJob * > KDevelop::RunController::currentJobs() const { Q_D(const RunController); return d->jobs.keys(); } QList RunController::launchConfigurations() const { QList configs; const auto configsInternal = launchConfigurationsInternal(); configs.reserve(configsInternal.size()); for (LaunchConfiguration* config : configsInternal) { configs << config; } return configs; } QList RunController::launchConfigurationsInternal() const { Q_D(const RunController); return d->launchConfigurations; } QList RunController::launchConfigurationTypes() const { Q_D(const RunController); return d->launchConfigurationTypes.values(); } void RunController::addConfigurationType( LaunchConfigurationType* type ) { Q_D(RunController); if( !d->launchConfigurationTypes.contains( type->id() ) ) { d->launchConfigurationTypes.insert( type->id(), type ); } } void RunController::removeConfigurationType( LaunchConfigurationType* type ) { Q_D(RunController); const auto oldLaunchConfigurations = d->launchConfigurations; for (LaunchConfiguration* l : oldLaunchConfigurations) { if( l->type() == type ) { removeLaunchConfigurationInternal( l ); } } d->launchConfigurationTypes.remove( type->id() ); } void KDevelop::RunController::addLaunchMode(KDevelop::ILaunchMode* mode) { Q_D(RunController); if( !d->launchModes.contains( mode->id() ) ) { d->launchModes.insert( mode->id(), mode ); } } QList< KDevelop::ILaunchMode* > KDevelop::RunController::launchModes() const { Q_D(const RunController); return d->launchModes.values(); } void KDevelop::RunController::removeLaunchMode(KDevelop::ILaunchMode* mode) { Q_D(RunController); d->launchModes.remove( mode->id() ); } KDevelop::ILaunchMode* KDevelop::RunController::launchModeForId(const QString& id) const { Q_D(const RunController); auto it = d->launchModes.find( id ); if( it != d->launchModes.end() ) { return it.value(); } return nullptr; } void KDevelop::RunController::addLaunchConfiguration(KDevelop::LaunchConfiguration* l) { Q_D(RunController); if( !d->launchConfigurations.contains( l ) ) { d->addLaunchAction( l ); d->launchConfigurations << l; if( !d->currentTargetAction->currentAction() ) { if( !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().at(0)->setChecked( true ); } } connect( l, &LaunchConfiguration::nameChanged, this, &RunController::launchChanged ); } } void KDevelop::RunController::removeLaunchConfiguration(KDevelop::LaunchConfiguration* l) { KConfigGroup launcherGroup; if( l->project() ) { launcherGroup = l->project()->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() ); } else { launcherGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); } QStringList configs = launcherGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); configs.removeAll( l->configGroupName() ); launcherGroup.deleteGroup( l->configGroupName() ); launcherGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs ); launcherGroup.sync(); removeLaunchConfigurationInternal( l ); } void RunController::removeLaunchConfigurationInternal(LaunchConfiguration *l) { Q_D(RunController); const auto actions = d->currentTargetAction->actions(); for (QAction* a : actions) { if( static_cast( a->data().value() ) == l ) { bool wasSelected = a->isChecked(); d->currentTargetAction->removeAction( a ); if( wasSelected && !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().at(0)->setChecked( true ); } break; } } d->launchConfigurations.removeAll( l ); delete l; } void KDevelop::RunController::executeDefaultLaunch(const QString& runMode) { if (auto dl = defaultLaunch()) { execute(runMode, dl); } else { qCWarning(SHELL) << "no default launch!"; } } void RunController::setDefaultLaunch(ILaunchConfiguration* l) { Q_D(RunController); const auto actions = d->currentTargetAction->actions(); for (QAction* a : actions) { if( static_cast( a->data().value() ) == l ) { a->setChecked(true); break; } } } bool launcherNameExists(const QString& name) { const auto configs = Core::self()->runControllerInternal()->launchConfigurations(); return std::any_of(configs.begin(), configs.end(), [&](ILaunchConfiguration* config) { return (config->name() == name); }); } QString makeUnique(const QString& name) { if(launcherNameExists(name)) { for(int i=2; ; i++) { QString proposed = QStringLiteral("%1 (%2)").arg(name).arg(i); if(!launcherNameExists(proposed)) { return proposed; } } } return name; } ILaunchConfiguration* RunController::createLaunchConfiguration ( LaunchConfigurationType* type, const QPair& launcher, IProject* project, const QString& name ) { KConfigGroup launchGroup; if( project ) { launchGroup = project->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() ); } else { launchGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); } QStringList configs = launchGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); uint num = 0; QString baseName = QStringLiteral("Launch Configuration"); while( configs.contains( QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ) ) ) { num++; } QString groupName = QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ); KConfigGroup launchConfigGroup = launchGroup.group( groupName ); QString cfgName = name; if( name.isEmpty() ) { cfgName = i18n("New %1 Launcher", type->name() ); cfgName = makeUnique(cfgName); } launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationNameEntry(), cfgName ); launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationTypeEntry(), type->id() ); launchConfigGroup.sync(); configs << groupName; launchGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs ); launchGroup.sync(); auto* l = new LaunchConfiguration( launchConfigGroup, project ); l->setLauncherForMode( launcher.first, launcher.second ); Core::self()->runControllerInternal()->addLaunchConfiguration( l ); return l; } QItemDelegate * KDevelop::RunController::delegate() const { Q_D(const RunController); return d->delegate; } ContextMenuExtension RunController::contextMenuExtension(Context* ctx, QWidget* parent) { Q_D(RunController); d->launchAsInfo.clear(); d->contextItem = nullptr; ContextMenuExtension ext; if( ctx->type() == Context::ProjectItemContext ) { auto* prjctx = static_cast(ctx); if( prjctx->items().count() == 1 ) { ProjectBaseItem* itm = prjctx->items().at( 0 ); int i = 0; for (ILaunchMode* mode : qAsConst(d->launchModes)) { KActionMenu* menu = new KActionMenu(i18n("%1 As...", mode->name() ), parent); const auto types = launchConfigurationTypes(); for (LaunchConfigurationType* type : types) { bool hasLauncher = false; const auto launchers = type->launchers(); for (ILauncher* launcher : launchers) { if( launcher->supportedModes().contains( mode->id() ) ) { hasLauncher = true; } } if( hasLauncher && type->canLaunch(itm) ) { d->launchAsInfo[i] = qMakePair( type->id(), mode->id() ); auto* act = new QAction(menu); act->setText( type->name() ); qCDebug(SHELL) << "Connect " << i << "for action" << act->text() << "in mode" << mode->name(); connect(act, &QAction::triggered, this, [this, i] () { Q_D(RunController); d->launchAs(i); } ); menu->addAction(act); i++; } } if( menu->menu()->actions().count() > 0 ) { ext.addAction( ContextMenuExtension::RunGroup, menu); } else { delete menu; } } if( ext.actions( ContextMenuExtension::RunGroup ).count() > 0 ) { d->contextItem = itm; } } } return ext; } RunDelegate::RunDelegate( QObject* parent ) : QItemDelegate(parent), runProviderBrush( KColorScheme::View, KColorScheme::PositiveText ), errorBrush( KColorScheme::View, KColorScheme::NegativeText ) { } void RunDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { QStyleOptionViewItem opt = option; // if( status.isValid() && status.canConvert() ) // { // IRunProvider::OutputTypes type = status.value(); // if( type == IRunProvider::RunProvider ) // { // opt.palette.setBrush( QPalette::Text, runProviderBrush.brush( option.palette ) ); // } else if( type == IRunProvider::StandardError ) // { // opt.palette.setBrush( QPalette::Text, errorBrush.brush( option.palette ) ); // } // } QItemDelegate::paint(painter, opt, index); } #include "moc_runcontroller.cpp" diff --git a/kdevplatform/vcs/models/vcsitemeventmodel.cpp b/kdevplatform/vcs/models/vcsitemeventmodel.cpp index 91f80f4917..0d4963cd3c 100644 --- a/kdevplatform/vcs/models/vcsitemeventmodel.cpp +++ b/kdevplatform/vcs/models/vcsitemeventmodel.cpp @@ -1,115 +1,115 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "vcsitemeventmodel.h" #include #include #include #include #include #include #include #include #include "../vcsrevision.h" #include "../vcsevent.h" namespace KDevelop { VcsItemEventModel::VcsItemEventModel( QObject* parent ) : QStandardItemModel( parent ) { setColumnCount(2); } VcsItemEventModel::~VcsItemEventModel() {} void VcsItemEventModel::addItemEvents( const QList& list ) { if(rowCount()==0) setColumnCount(2); bool copySource = false; QMimeDatabase mimeDataBase; for (const KDevelop::VcsItemEvent& ev : list) { KDevelop::VcsItemEvent::Actions act = ev.actions(); QStringList actionStrings; if( act & KDevelop::VcsItemEvent::Added ) actionStrings << i18n("Added"); else if( act & KDevelop::VcsItemEvent::Deleted ) actionStrings << i18n("Deleted"); else if( act & KDevelop::VcsItemEvent::Modified ) actionStrings << i18n("Modified"); else if( act & KDevelop::VcsItemEvent::Copied ) actionStrings << i18n("Copied"); else if( act & KDevelop::VcsItemEvent::Replaced ) actionStrings << i18n("Replaced"); QUrl repoUrl = QUrl::fromLocalFile(ev.repositoryLocation()); QMimeType mime = repoUrl.isLocalFile() ? mimeDataBase.mimeTypeForFile(repoUrl.toLocalFile(), QMimeDatabase::MatchExtension) : mimeDataBase.mimeTypeForUrl(repoUrl); QList rowItems{ new QStandardItem(QIcon::fromTheme(mime.iconName()), ev.repositoryLocation()), new QStandardItem(actionStrings.join(i18nc("separates an action list", ", "))), }; QString loc = ev.repositoryCopySourceLocation(); if(!loc.isEmpty()) { //according to the documentation, those are optional. don't force them on the UI rowItems << new QStandardItem(ev.repositoryCopySourceLocation()); VcsRevision rev = ev.repositoryCopySourceRevision(); if(rev.revisionType()!=VcsRevision::Invalid) { rowItems << new QStandardItem(ev.repositoryCopySourceRevision().revisionValue().toString()); } copySource = true; } - rowItems.first()->setData(qVariantFromValue(ev)); + rowItems.first()->setData(QVariant::fromValue(ev)); appendRow(rowItems); } if(copySource) setColumnCount(4); } QVariant VcsItemEventModel::headerData(int section, Qt::Orientation orientation, int role) const { if(orientation == Qt::Horizontal && role==Qt::DisplayRole) { switch(section) { case 0: return i18n("Location"); case 1: return i18n("Actions"); case 2: return i18n("Source Location"); case 3: return i18n("Source Revision"); } } return QStandardItemModel::headerData(section, orientation, role); } KDevelop::VcsItemEvent VcsItemEventModel::itemEventForIndex( const QModelIndex& idx ) const { return itemFromIndex(idx)->data().value(); } } diff --git a/kdevplatform/vcs/vcsrevision.h b/kdevplatform/vcs/vcsrevision.h index 27a667b502..8b77e14213 100644 --- a/kdevplatform/vcs/vcsrevision.h +++ b/kdevplatform/vcs/vcsrevision.h @@ -1,176 +1,176 @@ /* This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * Copyright 2007 Matthew Woehlke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef KDEVPLATFORM_VCSREVISION_H #define KDEVPLATFORM_VCSREVISION_H #include "vcsexport.h" #include #include class QStringList; class QString; namespace KDevelop { /** * Encapsulates a vcs revision number, date or range of revisions. * * The type of the QVariant value depends on the type of the revision, * the following table lists the standard types and the according datatype * in the QVariant: * * * * * * * *
Revision typeQVariant type
GlobalNumberqlonglong/QString
FileNumberqlonglong/QString
DateQDateTime
SpecialKDevelop::VcsRevision::RevisionSpecialType or int, see explanation below
* * The vcs plugins need to register the Revision and RevisionSpecialType with * qRegisterMetaType. * * Also Users of this class should set RevisionSpecialType QVariant values via * @code - * setRevisionValue( qVariantFromValue( val ), KDevelop::VcsRevision::Special); + * setRevisionValue(QVariant::fromValue(val), KDevelop::VcsRevision::Special); * @endcode * instead of * @code - * setRevisionValue( qVariantFromValue( val ), KDevelop::VcsRevision::Special); + * setRevisionValue(QVariant::fromValue(val), KDevelop::VcsRevision::Special); * @endcode * * If the latter method is used the QVariant will be an Integer, which might not * be handled by the vcs plugin and is possibly ambiguous with the qlonglong * parameters. * */ class KDEVPLATFORMVCS_EXPORT VcsRevision { public: /** * @note Not all VCS's support both FileNumber and GlobalNumber. For those * that don't, asking for one may give you the other, therefore you should * check which is returned. For example, CVS does not support GlobalNumber, * and Subversion does not support FileNumber, while Perforce supports both. */ enum RevisionType { Special = 0 /**< One of the special versions in RevisionSpecialType. */, GlobalNumber = 1 /**< Global repository version when item was last changed. */, FileNumber = 2 /**< Item's independent version number. */, Date = 3, /**< The date of the revision to check out */ Invalid = 4 /**< The type is not set, this is an invalid revision. */, UserType = 1000 /**< This should be used by subclasses as base for their own types. */ }; enum RevisionSpecialType { Head = 0 /**< Latest revision in the repository. */, Working = 1 /**< The local copy (including any changes made). */, Base = 2 /**< The repository source of the local copy. */, Previous = 3 /**< The version prior the other one (only valid in functions that take two revisions). */, Start = 4, /**< The first commit in a repository. */ UserSpecialType = 1000 /**< This should be used by subclasses as base for their own special types. */ }; /** * Creates an invalid revision. */ VcsRevision(); virtual ~VcsRevision(); VcsRevision( const VcsRevision& ); VcsRevision& operator=( const VcsRevision& ); /** * Set the value of this revision */ void setRevisionValue( const QVariant& rev, RevisionType type ); /** * returns the type of the revision */ RevisionType revisionType() const; RevisionSpecialType specialType() const; /** * Return the value of this revision. * * See the class documentation for the different QVariant types */ QVariant revisionValue() const; /** * This returns the value of the revision, suitable for displaying to the * user. For numbers it just returns the number converted to a string, for * the special types it returns the literal value of the special type and * for a datetime value it returns a localized string of the datetime value. */ QString prettyValue() const; bool operator==( const KDevelop::VcsRevision&) const; /** * Helper function to create a vcs revision for one of the special types */ static VcsRevision createSpecialRevision( KDevelop::VcsRevision::RevisionSpecialType type ); protected: /** * Get the keys that make up the internal data of this revision instance */ QStringList keys() const; /** * get the value for a given key, this retrieves internal data and is * meant to be used by subclasses */ QVariant value(const QString& key) const; /** * change the value of the given internal data */ void setValue( const QString& key, const QVariant& value ); /** * write methods for subclasses to easily set the type and value */ void setType( RevisionType t); void setSpecialType( RevisionSpecialType t); void setValue( const QVariant& ); private: QSharedDataPointer d; }; KDEVPLATFORMVCS_EXPORT uint qHash( const KDevelop::VcsRevision& rev); } Q_DECLARE_METATYPE(KDevelop::VcsRevision) Q_DECLARE_TYPEINFO(KDevelop::VcsRevision, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(KDevelop::VcsRevision::RevisionSpecialType) #endif diff --git a/plugins/executeplasmoid/plasmoidexecutionconfig.cpp b/plugins/executeplasmoid/plasmoidexecutionconfig.cpp index e337819872..a1a8b352ca 100644 --- a/plugins/executeplasmoid/plasmoidexecutionconfig.cpp +++ b/plugins/executeplasmoid/plasmoidexecutionconfig.cpp @@ -1,331 +1,331 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2009 Niko Sams 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 "plasmoidexecutionconfig.h" #include "plasmoidexecutionjob.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class la; Q_DECLARE_METATYPE(KDevelop::IProject*) QIcon PlasmoidExecutionConfig::icon() const { return QIcon::fromTheme(QStringLiteral("system-run")); } QStringList readProcess(QProcess* p) { QStringList ret; while(!p->atEnd()) { QByteArray line = p->readLine(); int nameEnd=line.indexOf(' '); if(nameEnd>0) { ret += QString::fromUtf8(line.left(nameEnd)); } } return ret; } PlasmoidExecutionConfig::PlasmoidExecutionConfig( QWidget* parent ) : LaunchConfigurationPage( parent ) { setupUi(this); connect( identifier->lineEdit(), &QLineEdit::textEdited, this, &PlasmoidExecutionConfig::changed ); QProcess pPlasmoids; pPlasmoids.start(QStringLiteral("plasmoidviewer"), QStringList(QStringLiteral("--list")), QIODevice::ReadOnly); QProcess pThemes; pThemes.start(QStringLiteral("plasmoidviewer"), QStringList(QStringLiteral("--list-themes")), QIODevice::ReadOnly); pThemes.waitForFinished(); pPlasmoids.waitForFinished(); const auto plasmoidListing = readProcess(&pPlasmoids); for (const QString& plasmoid : plasmoidListing) { identifier->addItem(plasmoid); } themes->addItem(QString()); const auto themeListing = readProcess(&pThemes); for (const QString& theme : themeListing) { themes->addItem(theme); } connect( dependencies, &KDevelop::DependenciesWidget::changed, this, &PlasmoidExecutionConfig::changed ); } void PlasmoidExecutionConfig::saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* project ) const { Q_UNUSED( project ); cfg.writeEntry("PlasmoidIdentifier", identifier->lineEdit()->text()); QStringList args{ QStringLiteral("--formfactor"), formFactor->currentText(), }; if(!themes->currentText().isEmpty()) { args += QStringLiteral("--theme"); args += themes->currentText(); } cfg.writeEntry("Arguments", args); QVariantList deps = dependencies->dependencies(); cfg.writeEntry( "Dependencies", KDevelop::qvariantToString( QVariant( deps ) ) ); } void PlasmoidExecutionConfig::loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject* ) { bool b = blockSignals( true ); identifier->lineEdit()->setText(cfg.readEntry("PlasmoidIdentifier", "")); blockSignals( b ); QStringList arguments = cfg.readEntry("Arguments", QStringList()); int idxFormFactor = arguments.indexOf(QStringLiteral("--formfactor"))+1; if(idxFormFactor>0) formFactor->setCurrentIndex(formFactor->findText(arguments[idxFormFactor])); int idxTheme = arguments.indexOf(QStringLiteral("--theme"))+1; if(idxTheme>0) themes->setCurrentIndex(themes->findText(arguments[idxTheme])); dependencies->setDependencies( KDevelop::stringToQVariant( cfg.readEntry( "Dependencies", QString() ) ).toList()); } QString PlasmoidExecutionConfig::title() const { return i18n("Configure Plasmoid Execution"); } QList< KDevelop::LaunchConfigurationPageFactory* > PlasmoidLauncher::configPages() const { return QList(); } QString PlasmoidLauncher::description() const { return i18n("Display a plasmoid"); } QString PlasmoidLauncher::id() { return QStringLiteral("PlasmoidLauncher"); } QString PlasmoidLauncher::name() const { return i18n("Plasmoid Launcher"); } PlasmoidLauncher::PlasmoidLauncher(ExecutePlasmoidPlugin* plugin) : m_plugin(plugin) { } KJob* PlasmoidLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) { Q_ASSERT(cfg); if( !cfg ) { return nullptr; } if( launchMode == QLatin1String("execute") ) { KJob* depsJob = dependencies(cfg); QList jobs; if(depsJob) jobs << depsJob; jobs << new PlasmoidExecutionJob(m_plugin, cfg); return new KDevelop::ExecuteCompositeJob( KDevelop::ICore::self()->runController(), jobs ); } qCWarning(EXECUTEPLASMOID) << "Unknown launch mode " << launchMode << "for config:" << cfg->name(); return nullptr; } KJob* PlasmoidLauncher::calculateDependencies(KDevelop::ILaunchConfiguration* cfg) { const QVariantList deps = KDevelop::stringToQVariant(cfg->config().readEntry("Dependencies", QString())).toList(); if( !deps.isEmpty() ) { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QList items; for (const QVariant& dep : deps) { KDevelop::ProjectBaseItem* item = model->itemFromIndex( model->pathToIndex( dep.toStringList() ) ); if( item ) { items << item; } else { KMessageBox::error(KDevelop::ICore::self()->uiController()->activeMainWindow(), i18n("Could not resolve the dependency: %1", dep.toString())); } } auto* job = new KDevelop::BuilderJob; job->addItems( KDevelop::BuilderJob::Install, items ); job->updateJobName(); return job; } return nullptr; } KJob* PlasmoidLauncher::dependencies(KDevelop::ILaunchConfiguration* cfg) { return calculateDependencies(cfg); } QStringList PlasmoidLauncher::supportedModes() const { return QStringList() << QStringLiteral("execute"); } KDevelop::LaunchConfigurationPage* PlasmoidPageFactory::createWidget(QWidget* parent) { return new PlasmoidExecutionConfig( parent ); } PlasmoidPageFactory::PlasmoidPageFactory() {} PlasmoidExecutionConfigType::PlasmoidExecutionConfigType() { factoryList.append( new PlasmoidPageFactory ); } PlasmoidExecutionConfigType::~PlasmoidExecutionConfigType() { qDeleteAll(factoryList); factoryList.clear(); } QString PlasmoidExecutionConfigType::name() const { return i18n("Plasmoid Launcher"); } QList PlasmoidExecutionConfigType::configPages() const { return factoryList; } QString PlasmoidExecutionConfigType::typeId() { return QStringLiteral("PlasmoidLauncherType"); } QIcon PlasmoidExecutionConfigType::icon() const { return QIcon::fromTheme(QStringLiteral("plasma")); } static bool canLaunchMetadataFile(const KDevelop::Path &path) { KConfig cfg(path.toLocalFile(), KConfig::SimpleConfig); KConfigGroup group(&cfg, "Desktop Entry"); QStringList services = group.readEntry("ServiceTypes", group.readEntry("X-KDE-ServiceTypes", QStringList())); return services.contains(QStringLiteral("Plasma/Applet")); } //don't bother, nobody uses this interface bool PlasmoidExecutionConfigType::canLaunch(const QUrl& ) const { return false; } bool PlasmoidExecutionConfigType::canLaunch(KDevelop::ProjectBaseItem* item) const { KDevelop::ProjectFolderItem* folder = item->folder(); if(folder && folder->hasFileOrFolder(QStringLiteral("metadata.desktop"))) { return canLaunchMetadataFile(KDevelop::Path(folder->path(), QStringLiteral("metadata.desktop"))); } return false; } void PlasmoidExecutionConfigType::configureLaunchFromItem(KConfigGroup config, KDevelop::ProjectBaseItem* item) const { config.writeEntry("PlasmoidIdentifier", item->path().toUrl().toLocalFile()); } void PlasmoidExecutionConfigType::configureLaunchFromCmdLineArguments(KConfigGroup /*config*/, const QStringList &/*args*/) const {} QMenu* PlasmoidExecutionConfigType::launcherSuggestions() { QList found; const QList projects = KDevelop::ICore::self()->projectController()->projects(); for (KDevelop::IProject* p : projects) { const QSet files = p->fileSet(); for (const KDevelop::IndexedString& file : files) { KDevelop::Path path(file.str()); if (path.lastPathSegment() == QLatin1String("metadata.desktop") && canLaunchMetadataFile(path)) { path = path.parent(); QString relUrl = p->path().relativePath(path); auto* action = new QAction(relUrl, this); action->setProperty("url", relUrl); - action->setProperty("project", qVariantFromValue(p)); + action->setProperty("project", QVariant::fromValue(p)); connect(action, &QAction::triggered, this, &PlasmoidExecutionConfigType::suggestionTriggered); found.append(action); } } } QMenu *m = nullptr; if(!found.isEmpty()) { m = new QMenu(i18n("Plasmoids")); m->addActions(found); } return m; } void PlasmoidExecutionConfigType::suggestionTriggered() { auto* action = qobject_cast(sender()); auto* p = action->property("project").value(); QString relUrl = action->property("url").toString(); KDevelop::ILauncher* launcherInstance = launchers().at( 0 ); QPair launcher = qMakePair( launcherInstance->supportedModes().at(0), launcherInstance->id() ); QString name = relUrl.mid(relUrl.lastIndexOf(QLatin1Char('/'))+1); KDevelop::ILaunchConfiguration* config = KDevelop::ICore::self()->runController()->createLaunchConfiguration(this, launcher, p, name); KConfigGroup cfg = config->config(); cfg.writeEntry("PlasmoidIdentifier", relUrl); emit signalAddLaunchConfiguration(config); } diff --git a/plugins/git/gitplugin.cpp b/plugins/git/gitplugin.cpp index b578343ede..faf240bd8b 100644 --- a/plugins/git/gitplugin.cpp +++ b/plugins/git/gitplugin.cpp @@ -1,1577 +1,1577 @@ /*************************************************************************** * 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 #include "gitclonejob.h" #include #include "rebasedialog.h" #include "stashmanagerdialog.h" #include #include #include #include #include #include #include "gitjob.h" #include "gitmessagehighlighter.h" #include "gitplugincheckinrepositoryjob.h" #include "gitnameemaildialog.h" #include "debug.h" #include 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(); 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; for (const QUrl& url : urls) { QDir d(url.toLocalFile()); if(d.exists()) { const QStringList entries = d.entryList(QDir::Files | QDir::NoDotAndDotDot); ret.reserve(ret.size() + entries.size()); for (const QString& entry : entries) { QUrl entryUrl = QUrl::fromLocalFile(d.absoluteFilePath(entry)); ret += entryUrl; } } else ret += url; } return ret; } QString toRevisionName(const KDevelop::VcsRevision& rev, const 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 + QLatin1String("^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::UserType: 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 + QLatin1String("..") + 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->addAction(i18n("Rebase"), this, SLOT(ctxRebase())); 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::ctxRebase() { RebaseDialog *dialog = new RebaseDialog(this, m_urls.first(), nullptr); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->open(); } 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(QStringLiteral(".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(QLatin1Char(' ')); if (items.size() == 2 && items.at(0) == QLatin1String("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") || scheme == QLatin1String("git+ssh")) { 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, IBasicVersionControl::RecursionMode recursion) { 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) + QLatin1String("
")); } } if (!modified.isEmpty()) { auto res = KMessageBox::questionYesNo(nullptr, i18n("The following files have uncommitted changes, " "which will be lost. Continue?") + QLatin1String("

") + 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")); } auto* 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) { const QStringList otherStr = getLsFiles(dir, QStringList() << QStringLiteral("--others"), KDevelop::OutputJob::Silent); QList toadd, otherFiles; otherFiles.reserve(otherStr.size()); for (const QString& file : otherStr) { QUrl v = QUrl::fromLocalFile(dir.absoluteFilePath(file)); otherFiles += v; } //We add the files that are not versioned for (const QUrl& file : files) { if(otherFiles.contains(file) && QFileInfo(file.toLocalFile()).isFile()) toadd += file; } if(!toadd.isEmpty()) { VcsJob* job = add(toadd); job->exec(); // krazy:exclude=crashy } } bool isEmptyDirStructure(const QDir &dir) { const auto infos = dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot); for (const QFileInfo& i : infos) { 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()); const QStringList otherStr = getLsFiles(dotGitDir, QStringList{QStringLiteral("--others"), QStringLiteral("--"), file.toLocalFile()}, KDevelop::OutputJob::Silent); if(!otherStr.isEmpty()) { //remove files not under version control QList otherFiles; otherFiles.reserve(otherStr.size()); for (const QString& f : otherStr) { otherFiles << QUrl::fromLocalFile(dotGitDir.path() + QLatin1Char('/') + 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(QLatin1Char('\n')); bool skipNext=false; QMap definedRevisions; for (auto& line : lines) { if(skipNext) { skipNext=false; - results += qVariantFromValue(*annotation); + results += QVariant::fromValue(*annotation); continue; } if (line.isEmpty()) continue; QStringRef name = line.left(line.indexOf(QLatin1Char(' '))); QStringRef value = line.mid(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(QLatin1String("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(QLatin1Char(' ')); 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) { auto* 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) { auto* 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(); } auto* 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::rebase(const QUrl& repository, const QString& branchName) { DVcsJob* job = new DVcsJob(urlDir(repository), this); *job << "git" << "rebase" << 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(QLatin1Char('\n'), QString::SkipEmptyParts); QStringList branchList; for (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(QLatin1String("->"))) continue; // Skip entries such as '(no branch)' if (branch.contains(QLatin1String("(no branch)"))) continue; QStringRef name = branch; if (name.startsWith(QLatin1Char('*'))) name = branch.mid(2); branchList << name.trimmed().toString(); } job->setResults(branchList); } /* Few words about how this hardcore works: 1. get all commits (with --parents) 2. select master (root) branch and get all unique 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 iteration 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 separate 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 merge 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 */ QVector GitPlugin::allCommits(const QString& repo) { initBranchHash(repo); const QStringList 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(QLatin1Char('\n'), QString::SkipEmptyParts); static QRegExp rx_com(QStringLiteral("commit \\w{40,40}")); QVector commitList; 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(QLatin1Char(' '), 1, 1).trimmed()); // qCDebug(PLUGIN_GIT) << "commit is: " << commits[i].section(' ', 1); QStringList parents; QString parent = commits[i].section(QLatin1Char(' '), 2); int section = 2; while (!parent.isEmpty()) { /* qCDebug(PLUGIN_GIT) << "Parent is: " << parent;*/ parents.append(parent.trimmed()); section++; parent = commits[i].section(QLatin1Char(' '), section); } item.setParents(parents); //Avoid Merge string while (!commits[i].contains(QLatin1String("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; mask.reserve(branchesShas.count()); //set mask (properties for each graph column in row) for(int i = 0; i < branchesShas.count(); ++i) { qCDebug(PLUGIN_GIT)<<"commit: " << item.commit(); if (branchesShas[i].contains(item.commit())) { mask.append(item.type()); //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 further (from top to bottom) revisions //FIXME: we should set CROSS between parent and child (and do it when find merge point) additionalFlags[i] = false; const auto parentShas = item.parents(); for (const QString& sha : parentShas) { 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 (auto iter = commitList.begin(); iter != commitList.end(); ++iter) { QStringList parents = iter->parents(); //we need only only child branches if (parents.count() != 1) break; QString parent = parents[0]; const QString commit = iter->commit(); 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 (auto f_iter = iter; f_iter != commitList.end(); ++f_iter) { if (parent == f_iter->commit()) { for(int j = 0; j < i; ++j) { if(branchesShas[j].contains(parent)) f_iter->setProperty(j, DVcsEvent::MERGE); else f_iter->setProperty(j, DVcsEvent::HCROSS); } f_iter->setType(DVcsEvent::MERGE); f_iter->setProperty(i, DVcsEvent::MERGE_RIGHT); qCDebug(PLUGIN_GIT) << parent << " is parent of " << commit; qCDebug(PLUGIN_GIT) << f_iter->commit() << " is merge"; parent_checked = true; break; } else f_iter->setProperty(i, DVcsEvent::CROSS); } } } //mark HEADs if (!branchesShas[i].empty() && commit == branchesShas[i][0]) { iter->setType(DVcsEvent::HEAD); iter->setProperty(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); const 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); const QStringList commits = job->output().split(QLatin1Char('\n'), QString::SkipEmptyParts); // qCDebug(PLUGIN_GIT) << "\n\n\n commits" << commits << "\n\n\n"; branchesShas.append(commits); for (const QString& branch : gitBranches) { if (branch == root) continue; QStringList args(branch); for (const QString& branch_arg : gitBranches) { if (branch_arg != branch) //man gitRevList for '^' args << QLatin1Char('^') + 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); const QStringList commits = job->output().split(QLatin1Char('\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, QVector& commits) const { // static QRegExp rx_sep( "[-=]+" ); // static QRegExp rx_date( "date:\\s+([^;]*);\\s+author:\\s+([^;]*).*" ); static QRegularExpression rx_com( QStringLiteral("commit \\w{1,40}") ); const auto output = job->output(); const auto lines = output.splitRef(QLatin1Char('\n'), QString::SkipEmptyParts); DVcsEvent item; QString commitLog; for (int i=0; i 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(QLatin1Char(' '))[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.midRef(4) + QLatin1Char('\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)); + job->setResults(QVariant::fromValue(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) { const QStringList outputLines = job->output().split(QLatin1Char('\n'), QString::SkipEmptyParts); QDir dir = job->directory(); QMap allStatus; for (const QString& line : outputLines) { VcsStatusInfo::State status = lsfilesToState(line[0].toLatin1()); QUrl url = QUrl::fromLocalFile(dir.absoluteFilePath(line.mid(2))); allStatus[url] = status; } QVariantList statuses; statuses.reserve(allStatus.size()); 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)); + statuses.append(QVariant::fromValue(status)); } job->setResults(statuses); } void GitPlugin::parseGitStatusOutput(DVcsJob* job) { const auto output = job->output(); const auto outputLines = output.splitRef(QLatin1Char('\n'), QString::SkipEmptyParts); QDir workingDir = job->directory(); QDir dotGit = dotGitDirectory(QUrl::fromLocalFile(workingDir.absolutePath())); QVariantList statuses; QList processedFiles; for (const QStringRef& line : outputLines) { //every line is 2 chars for the status, 1 space then the file desc QStringRef curr=line.mid(3); QStringRef state = line.left(2); int arrow = curr.indexOf(QLatin1String(" -> ")); if(arrow>=0) { VcsStatusInfo status; status.setUrl(QUrl::fromLocalFile(dotGit.absoluteFilePath(curr.toString().left(arrow)))); status.setState(VcsStatusInfo::ItemDeleted); - statuses.append(qVariantFromValue(status)); + statuses.append(QVariant::fromValue(status)); processedFiles += status.url(); curr = curr.mid(arrow+4); } if (curr.startsWith(QLatin1Char('\"')) && curr.endsWith(QLatin1Char('\"'))) { //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)); + statuses.append(QVariant::fromValue(status)); } QStringList paths; QStringList oldcmd=job->dvcsCommand(); QStringList::const_iterator it=oldcmd.constBegin()+oldcmd.indexOf(QStringLiteral("--"))+1, itEnd=oldcmd.constEnd(); paths.reserve(oldcmd.size()); for(; it!=itEnd; ++it) paths += *it; //here we add the already up to date files const QStringList files = getLsFiles(job->directory(), QStringList{QStringLiteral("-c"), QStringLiteral("--")} << paths, OutputJob::Silent); for (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)); + statuses.append(QVariant::fromValue(status)); } } job->setResults(statuses); } void GitPlugin::parseGitVersionOutput(DVcsJob* job) { const auto output = job->output().trimmed(); auto versionString = output.midRef(output.lastIndexOf(QLatin1Char(' '))).split(QLatin1Char('.')); static const std::array minimumVersion = {1, 7}; qCDebug(PLUGIN_GIT) << "checking git version" << versionString << "against" << minimumVersion[0] << minimumVersion[1]; m_oldVersion = false; if (static_cast(versionString.size()) < minimumVersion.size()) { m_oldVersion = true; qCWarning(PLUGIN_GIT) << "invalid git version string:" << job->output().trimmed(); return; } for (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(QLatin1Char('\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(QLatin1Char('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) == QLatin1Char('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); } } const QStringList otherStr = getLsFiles(dir, QStringList{QStringLiteral("--others"), QStringLiteral("--"), source.toLocalFile()}, KDevelop::OutputJob::Silent); if(otherStr.isEmpty()) { auto* 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) : 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(QLatin1String("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, &GitPlugin::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 << QStringLiteral("git") << QStringLiteral("config"); if(global) args << QStringLiteral("--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(QStringLiteral("git"), QStringList{QStringLiteral("config"), QStringLiteral("--get"), key}); exec.waitForFinished(); return QString::fromUtf8(exec.readAllStandardOutput().trimmed()); } #include "gitplugin.moc" diff --git a/plugins/grepview/grepoutputview.cpp b/plugins/grepview/grepoutputview.cpp index 59879b8026..a62b7428e5 100644 --- a/plugins/grepview/grepoutputview.cpp +++ b/plugins/grepview/grepoutputview.cpp @@ -1,490 +1,490 @@ /************************************************************************** * Copyright 2010 Silvère Lestang * * Copyright 2010 Julien Desgats * * * * 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 "grepoutputview.h" #include "grepoutputmodel.h" #include "grepoutputdelegate.h" #include "ui_grepoutputview.h" #include "grepviewplugin.h" #include "grepdialog.h" #include "greputil.h" #include "grepjob.h" #include "debug.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; GrepOutputViewFactory::GrepOutputViewFactory(GrepViewPlugin* plugin) : m_plugin(plugin) {} QWidget* GrepOutputViewFactory::create(QWidget* parent) { return new GrepOutputView(parent, m_plugin); } Qt::DockWidgetArea GrepOutputViewFactory::defaultPosition() const { return Qt::BottomDockWidgetArea; } QString GrepOutputViewFactory::id() const { return QStringLiteral("org.kdevelop.GrepOutputView"); } const int GrepOutputView::HISTORY_SIZE = 5; namespace { enum { GrepSettingsStorageItemCount = 10 }; } GrepOutputView::GrepOutputView(QWidget* parent, GrepViewPlugin* plugin) : QWidget(parent) , m_next(nullptr) , m_prev(nullptr) , m_collapseAll(nullptr) , m_expandAll(nullptr) , m_refresh(nullptr) , m_clearSearchHistory(nullptr) , m_statusLabel(nullptr) , m_plugin(plugin) { Ui::GrepOutputView::setupUi(this); setWindowTitle(i18nc("@title:window", "Find/Replace Output View")); setWindowIcon(QIcon::fromTheme(QStringLiteral("edit-find"), windowIcon())); m_prev = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18n("&Previous Item"), this); m_next = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), i18n("&Next Item"), this); /* Expand-all and collapse-all icons were added to breeze with version 5.57. We use a fallback * icon here because we support older frameworks versions and oxygen doesn't have such an icon */ m_collapseAll = new QAction(QIcon::fromTheme(QStringLiteral("collapse-all"), QIcon::fromTheme(QStringLiteral("arrow-left-double"))), i18n("C&ollapse All"), this); m_expandAll = new QAction(QIcon::fromTheme(QStringLiteral("expand-all"), QIcon::fromTheme(QStringLiteral("arrow-right-double"))), i18n("&Expand All"), this); updateButtonState(false); auto *separator = new QAction(this); separator->setSeparator(true); QAction *newSearchAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("New &Search"), this); m_refresh = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Refresh"), this); m_refresh->setEnabled(false); m_clearSearchHistory = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-list")), i18n("Clear Search History"), this); m_clearSearchHistory->setEnabled(false); addAction(m_prev); addAction(m_next); addAction(m_collapseAll); addAction(m_expandAll); addAction(separator); addAction(newSearchAction); addAction(m_refresh); addAction(m_clearSearchHistory); separator = new QAction(this); separator->setSeparator(true); addAction(separator); auto *statusWidget = new QWidgetAction(this); m_statusLabel = new QLabel(this); statusWidget->setDefaultWidget(m_statusLabel); addAction(statusWidget); modelSelector->setEditable(false); modelSelector->setContextMenuPolicy(Qt::CustomContextMenu); connect(modelSelector, &KComboBox::customContextMenuRequested, this, &GrepOutputView::modelSelectorContextMenu); connect(modelSelector, QOverload::of(&KComboBox::currentIndexChanged), this, &GrepOutputView::changeModel); resultsTreeView->setItemDelegate(GrepOutputDelegate::self()); resultsTreeView->setRootIsDecorated(false); resultsTreeView->setHeaderHidden(true); resultsTreeView->setUniformRowHeights(false); resultsTreeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); connect(m_prev, &QAction::triggered, this, &GrepOutputView::selectPreviousItem); connect(m_next, &QAction::triggered, this, &GrepOutputView::selectNextItem); connect(m_collapseAll, &QAction::triggered, this, &GrepOutputView::collapseAllItems); connect(m_expandAll, &QAction::triggered, this, &GrepOutputView::expandAllItems); connect(applyButton, &QPushButton::clicked, this, &GrepOutputView::onApply); connect(m_refresh, &QAction::triggered, this, &GrepOutputView::refresh); connect(m_clearSearchHistory, &QAction::triggered, this, &GrepOutputView::clearSearchHistory); KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); replacementCombo->addItems( cg.readEntry("LastReplacementItems", QStringList()) ); replacementCombo->setInsertPolicy(QComboBox::InsertAtTop); applyButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); connect(replacementCombo, &KComboBox::editTextChanged, this, &GrepOutputView::replacementTextChanged); connect(replacementCombo, QOverload<>::of(&KComboBox::returnPressed), this, &GrepOutputView::onApply); connect(newSearchAction, &QAction::triggered, this, &GrepOutputView::showDialog); resultsTreeView->header()->setStretchLastSection(true); resultsTreeView->header()->setStretchLastSection(true); // read Find/Replace settings history const QStringList s = cg.readEntry("LastSettings", QStringList()); if (s.size() % GrepSettingsStorageItemCount != 0) { qCWarning(PLUGIN_GREPVIEW) << "Stored settings history has unexpected size:" << s; } else { m_settingsHistory.reserve(s.size() / GrepSettingsStorageItemCount); auto it = s.begin(); while (it != s.end()) { GrepJobSettings settings; settings.projectFilesOnly = ((it++)->toUInt() != 0); settings.caseSensitive = ((it++)->toUInt() != 0); settings.regexp = ((it++)->toUInt() != 0); settings.depth = (it++)->toInt(); settings.pattern = *(it++); settings.searchTemplate = *(it++); settings.replacementTemplate = *(it++); settings.files = *(it++); settings.exclude = *(it++); settings.searchPaths = *(it++); settings.fromHistory = true; m_settingsHistory << settings; } } // rerun the grep jobs with settings from the history auto* dlg = new GrepDialog(m_plugin, this, false); dlg->historySearch(m_settingsHistory); updateCheckable(); } void GrepOutputView::replacementTextChanged() { updateCheckable(); if (model()) { // see https://bugs.kde.org/show_bug.cgi?id=274902 - renewModel can trigger a call here without an active model updateApplyState(model()->index(0, 0), model()->index(0, 0)); } } GrepOutputView::~GrepOutputView() { KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); cg.writeEntry("LastReplacementItems", qCombo2StringList(replacementCombo, true)); QStringList settingsStrings; settingsStrings.reserve(m_settingsHistory.size() * GrepSettingsStorageItemCount); for (const GrepJobSettings& s : qAsConst(m_settingsHistory)) { settingsStrings << QString::number(s.projectFilesOnly ? 1 : 0) << QString::number(s.caseSensitive ? 1 : 0) << QString::number(s.regexp ? 1 : 0) << QString::number(s.depth) << s.pattern << s.searchTemplate << s.replacementTemplate << s.files << s.exclude << s.searchPaths; } cg.writeEntry("LastSettings", settingsStrings); emit outputViewIsClosed(); } GrepOutputModel* GrepOutputView::renewModel(const GrepJobSettings& settings, const QString& description) { // clear oldest model while(modelSelector->count() >= GrepOutputView::HISTORY_SIZE) { QVariant var = modelSelector->itemData(GrepOutputView::HISTORY_SIZE - 1); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(GrepOutputView::HISTORY_SIZE - 1); } while(m_settingsHistory.count() >= GrepOutputView::HISTORY_SIZE) { m_settingsHistory.removeFirst(); } replacementCombo->clearEditText(); auto* newModel = new GrepOutputModel(resultsTreeView); applyButton->setEnabled(false); // text may be already present newModel->setReplacement(replacementCombo->currentText()); connect(newModel, &GrepOutputModel::rowsRemoved, this, &GrepOutputView::rowsRemoved); connect(resultsTreeView, &QTreeView::activated, newModel, &GrepOutputModel::activate); connect(replacementCombo, &KComboBox::editTextChanged, newModel, &GrepOutputModel::setReplacement); connect(newModel, &GrepOutputModel::rowsInserted, this, &GrepOutputView::expandElements); connect(newModel, &GrepOutputModel::showErrorMessage, this, &GrepOutputView::showErrorMessage); connect(m_plugin, &GrepViewPlugin::grepJobFinished, this, &GrepOutputView::updateScrollArea); // appends new model to history - modelSelector->insertItem(0, description, qVariantFromValue(newModel)); + modelSelector->insertItem(0, description, QVariant::fromValue(newModel)); modelSelector->setCurrentIndex(0); m_settingsHistory.append(settings); updateCheckable(); return newModel; } GrepOutputModel* GrepOutputView::model() { return static_cast(resultsTreeView->model()); } void GrepOutputView::changeModel(int index) { if (model()) { disconnect(model(), &GrepOutputModel::showMessage, this, &GrepOutputView::showMessage); disconnect(model(), &GrepOutputModel::dataChanged, this, &GrepOutputView::updateApplyState); } replacementCombo->clearEditText(); //after deleting the whole search history, index is -1 if(index >= 0) { QVariant var = modelSelector->itemData(index); auto *resultModel = static_cast(qvariant_cast(var)); resultsTreeView->setModel(resultModel); resultsTreeView->expandAll(); connect(model(), &GrepOutputModel::showMessage, this, &GrepOutputView::showMessage); connect(model(), &GrepOutputModel::dataChanged, this, &GrepOutputView::updateApplyState); model()->showMessageEmit(); applyButton->setEnabled(model()->hasResults() && model()->getRootItem() && model()->getRootItem()->checkState() != Qt::Unchecked && !replacementCombo->currentText().isEmpty()); if(model()->hasResults()) expandElements(QModelIndex()); else { updateButtonState(false); } } updateCheckable(); updateApplyState(model()->index(0, 0), model()->index(0, 0)); m_refresh->setEnabled(true); m_clearSearchHistory->setEnabled(true); } void GrepOutputView::setMessage(const QString& msg, MessageType type) { if (type == Error) { QPalette palette = m_statusLabel->palette(); KColorScheme::adjustForeground(palette, KColorScheme::NegativeText, QPalette::WindowText); m_statusLabel->setPalette(palette); } else { m_statusLabel->setPalette(QPalette()); } m_statusLabel->setText(msg); } void GrepOutputView::showErrorMessage( const QString& errorMessage ) { setMessage(errorMessage, Error); } void GrepOutputView::showMessage( KDevelop::IStatus* , const QString& message ) { setMessage(message, Information); } void GrepOutputView::onApply() { if(model()) { Q_ASSERT(model()->rowCount()); // ask a confirmation before an empty string replacement if(replacementCombo->currentText().length() == 0 && KMessageBox::questionYesNo(this, i18n("Do you want to replace with an empty string?"), i18n("Start replacement")) == KMessageBox::No) { return; } setEnabled(false); model()->doReplacements(); setEnabled(true); } } void GrepOutputView::showDialog() { m_plugin->showDialog(true); } void GrepOutputView::refresh() { int index = modelSelector->currentIndex(); if (index >= 0) { QVariant var = modelSelector->currentData(); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(index); QVector refresh_history({ m_settingsHistory.takeAt(m_settingsHistory.count() - 1 - index) }); refresh_history.first().fromHistory = false; auto* dlg = new GrepDialog(m_plugin, this, false); dlg->historySearch(refresh_history); } } void GrepOutputView::expandElements(const QModelIndex& index) { updateButtonState(true); resultsTreeView->expand(index); } void GrepOutputView::updateButtonState(bool enable) { m_prev->setEnabled(enable); m_next->setEnabled(enable); m_collapseAll->setEnabled(enable); m_expandAll->setEnabled(enable); } void GrepOutputView::selectPreviousItem() { if (!model()) { return; } QModelIndex prev_idx = model()->previousItemIndex(resultsTreeView->currentIndex()); if (prev_idx.isValid()) { resultsTreeView->setCurrentIndex(prev_idx); model()->activate(prev_idx); } } void GrepOutputView::selectNextItem() { if (!model()) { return; } QModelIndex next_idx = model()->nextItemIndex(resultsTreeView->currentIndex()); if (next_idx.isValid()) { resultsTreeView->setCurrentIndex(next_idx); model()->activate(next_idx); } } void GrepOutputView::collapseAllItems() { // Collapse everything resultsTreeView->collapseAll(); if (resultsTreeView->model()) { // Now reopen the first children, which correspond to the files. resultsTreeView->expand(resultsTreeView->model()->index(0, 0)); } } void GrepOutputView::expandAllItems() { resultsTreeView->expandAll(); } void GrepOutputView::rowsRemoved() { Q_ASSERT(model()); updateButtonState(model()->rowCount() > 0); } void GrepOutputView::updateApplyState(const QModelIndex& topLeft, const QModelIndex& bottomRight) { Q_UNUSED(bottomRight); if (!model() || !model()->hasResults()) { applyButton->setEnabled(false); return; } // we only care about the root item if(!topLeft.parent().isValid()) { applyButton->setEnabled(topLeft.data(Qt::CheckStateRole) != Qt::Unchecked && model()->itemsCheckable()); } } void GrepOutputView::updateCheckable() { if(model()) model()->makeItemsCheckable(!replacementCombo->currentText().isEmpty() || model()->itemsCheckable()); } void GrepOutputView::clearSearchHistory() { GrepJob *runningJob = m_plugin->grepJob(); if(runningJob) { connect(runningJob, &GrepJob::finished, this, [=]() {updateButtonState(false);}); runningJob->kill(); } while(modelSelector->count() > 0) { QVariant var = modelSelector->itemData(0); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(0); } m_settingsHistory.clear(); applyButton->setEnabled(false); updateButtonState(false); m_refresh->setEnabled(false); m_clearSearchHistory->setEnabled(false); m_statusLabel->setText(QString()); } void GrepOutputView::modelSelectorContextMenu(const QPoint& pos) { QPoint globalPos = modelSelector->mapToGlobal(pos); QMenu myMenu(this); myMenu.addAction(m_clearSearchHistory); myMenu.exec(globalPos); } void GrepOutputView::updateScrollArea() { if (!model()) { return; } for (int col = 0; col < model()->columnCount(); ++col) resultsTreeView->resizeColumnToContents(col); } diff --git a/plugins/perforce/perforceplugin.cpp b/plugins/perforce/perforceplugin.cpp index ac2fd49e15..418a2d1027 100644 --- a/plugins/perforce/perforceplugin.cpp +++ b/plugins/perforce/perforceplugin.cpp @@ -1,685 +1,685 @@ /*************************************************************************** * Copyright 2010 Morten Danielsen Volden * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "perforceplugin.h" #include "ui/perforceimportmetadatawidget.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QString toRevisionName(const KDevelop::VcsRevision& rev, const QString& currentRevision=QString()) { bool ok; int previous = currentRevision.toInt(&ok); previous--; QString tmp; switch(rev.revisionType()) { case VcsRevision::Special: switch(rev.revisionValue().value()) { case VcsRevision::Head: return QStringLiteral("#head"); case VcsRevision::Base: return QStringLiteral("#have"); case VcsRevision::Working: return QStringLiteral("#have"); case VcsRevision::Previous: Q_ASSERT(!currentRevision.isEmpty()); tmp.setNum(previous); tmp.prepend(QLatin1Char('#')); return tmp; 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: tmp.append(QLatin1Char('#') + rev.revisionValue().toString()); return tmp; case VcsRevision::Date: case VcsRevision::FileNumber: case VcsRevision::Invalid: case VcsRevision::UserType: Q_ASSERT(false); } return QString(); } VcsItemEvent::Actions actionsFromString(QString const& changeDescription) { if(changeDescription == QLatin1String("add")) return VcsItemEvent::Added; if(changeDescription == QLatin1String("delete")) return VcsItemEvent::Deleted; return VcsItemEvent::Modified; } QDir urlDir(const QUrl& url) { QFileInfo f(url.toLocalFile()); if(f.isDir()) return QDir(url.toLocalFile()); else return f.absoluteDir(); } } PerforcePlugin::PerforcePlugin(QObject* parent, const QVariantList&): KDevelop::IPlugin(QStringLiteral("kdevperforce"), parent) , m_common(new KDevelop::VcsPluginHelper(this, this)) , m_perforceConfigName(QStringLiteral("p4config.txt")) , m_perforceExecutable(QStringLiteral("p4")) , m_edit_action(nullptr) { QProcessEnvironment currentEviron(QProcessEnvironment::systemEnvironment()); QString tmp(currentEviron.value(QStringLiteral("P4CONFIG"))); if (tmp.isEmpty()) { // We require the P4CONFIG variable to be set because the perforce command line client will need it setErrorDescription(i18n("The variable P4CONFIG is not set. Is perforce installed on the system?")); return; } else { m_perforceConfigName = tmp; } qCDebug(PLUGIN_PERFORCE) << "The value of P4CONFIG is : " << tmp; } PerforcePlugin::~PerforcePlugin() { } QString PerforcePlugin::name() const { return i18n("Perforce"); } KDevelop::VcsImportMetadataWidget* PerforcePlugin::createImportMetadataWidget(QWidget* parent) { return new PerforceImportMetadataWidget(parent); } bool PerforcePlugin::isValidRemoteRepositoryUrl(const QUrl& remoteLocation) { Q_UNUSED(remoteLocation); // TODO return false; } bool PerforcePlugin::isValidDirectory(const QUrl & dirPath) { const QFileInfo finfo(dirPath.toLocalFile()); QDir dir = finfo.isDir() ? QDir(dirPath.toLocalFile()) : finfo.absoluteDir(); do { if (dir.exists(m_perforceConfigName)) { return true; } } while (dir.cdUp()); return false; } bool PerforcePlugin::isVersionControlled(const QUrl& localLocation) { QFileInfo fsObject(localLocation.toLocalFile()); if (fsObject.isDir()) { return isValidDirectory(localLocation); } return parseP4fstat(fsObject, KDevelop::OutputJob::Silent); } DVcsJob* PerforcePlugin::p4fstatJob(const QFileInfo& curFile, OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(curFile.absolutePath(), this, verbosity); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "fstat" << curFile.fileName(); return job; } bool PerforcePlugin::parseP4fstat(const QFileInfo& curFile, OutputJob::OutputJobVerbosity verbosity) { QScopedPointer job(p4fstatJob(curFile, verbosity)); if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) { qCDebug(PLUGIN_PERFORCE) << "Perforce returned: " << job->output(); if (!job->output().isEmpty()) return true; } return false; } QString PerforcePlugin::getRepositoryName(const QFileInfo& curFile) { const QString DEPOT_FILE_STR(QStringLiteral("... depotFile ")); QString ret; QScopedPointer job(p4fstatJob(curFile, KDevelop::OutputJob::Silent)); if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) { if (!job->output().isEmpty()) { const QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); for (const QString& line : outputLines) { int idx(line.indexOf(DEPOT_FILE_STR)); if (idx != -1) { ret = line.mid(DEPOT_FILE_STR.size()); return ret; } } } } return ret; } KDevelop::VcsJob* PerforcePlugin::repositoryLocation(const QUrl& /*localLocation*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::add(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "add" << localLocations; return job; } KDevelop::VcsJob* PerforcePlugin::remove(const QList& /*localLocations*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::copy(const QUrl& /*localLocationSrc*/, const QUrl& /*localLocationDstn*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::move(const QUrl& /*localLocationSrc*/, const QUrl& /*localLocationDst*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::status(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { if (localLocations.count() != 1) { KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return nullptr; } QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "fstat" << curFile.fileName(); connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4StatusOutput); return job; } KDevelop::VcsJob* PerforcePlugin::revert(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { if (localLocations.count() != 1) { KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return nullptr; } QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "revert" << curFile.fileName(); return job; } KDevelop::VcsJob* PerforcePlugin::update(const QList& localLocations, const KDevelop::VcsRevision& /*rev*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); //*job << m_perforceExecutable << "-p" << "127.0.0.1:1666" << "info"; - Let's keep this for now it's very handy for debugging QString fileOrDirectory; if (curFile.isDir()) fileOrDirectory = curFile.absolutePath() + "/..."; else fileOrDirectory = curFile.fileName(); *job << m_perforceExecutable << "sync" << fileOrDirectory; return job; } KDevelop::VcsJob* PerforcePlugin::commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { if (localLocations.empty() || message.isEmpty()) return errorsFound(i18n("No files or message specified")); QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "submit" << "-d" << message << localLocations; return job; } KDevelop::VcsJob* PerforcePlugin::diff(const QUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { QFileInfo curFile(fileOrDirectory.toLocalFile()); QString depotSrcFileName = getRepositoryName(curFile); QString depotDstFileName = depotSrcFileName; depotSrcFileName.append(toRevisionName(srcRevision, dstRevision.prettyValue())); // dstRevision actually contains the number that we want to take previous of DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); switch (dstRevision.revisionType()) { case VcsRevision::FileNumber: case VcsRevision::GlobalNumber: depotDstFileName.append(QLatin1Char('#') + dstRevision.prettyValue()); *job << m_perforceExecutable << "diff2" << "-u" << depotSrcFileName << depotDstFileName; break; case VcsRevision::Special: switch (dstRevision.revisionValue().value()) { case VcsRevision::Working: *job << m_perforceExecutable << "diff" << "-du" << depotSrcFileName; break; case VcsRevision::Start: case VcsRevision::UserSpecialType: default: break; } break; default: break; } connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4DiffOutput); return job; } KDevelop::VcsJob* PerforcePlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, long unsigned int limit) { static QString lastSeenChangeList; QFileInfo curFile(localLocation.toLocalFile()); QString localLocationAndRevStr = localLocation.toLocalFile(); DVcsJob* job = new DVcsJob(urlDir(localLocation), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "filelog" << "-lit"; if(limit > 0) *job << QStringLiteral("-m %1").arg(limit); if (curFile.isDir()) { localLocationAndRevStr.append("/..."); } QString revStr = toRevisionName(rev, QString()); if(!revStr.isEmpty()) { // This is not too nice, but perforce argument for restricting output from filelog does not Work :-( // So putting this in so we do not end up in infinite loop calling log, if(revStr == lastSeenChangeList) { localLocationAndRevStr.append("#none"); lastSeenChangeList.clear(); } else { localLocationAndRevStr.append(revStr); lastSeenChangeList = revStr; } } *job << localLocationAndRevStr; qCDebug(PLUGIN_PERFORCE) << "Issuing the following command to p4: " << job->dvcsCommand(); connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4LogOutput); return job; } KDevelop::VcsJob* PerforcePlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& /*rev*/, const KDevelop::VcsRevision& /*limit*/) { QFileInfo curFile(localLocation.toLocalFile()); if (curFile.isDir()) { KMessageBox::error(nullptr, i18n("Please select a file for this operation")); return errorsFound(i18n("Directory not supported for this operation")); } DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "filelog" << "-lit" << localLocation; connect(job, &DVcsJob::readyForParsing, this , &PerforcePlugin::parseP4LogOutput); return job; } KDevelop::VcsJob* PerforcePlugin::annotate(const QUrl& localLocation, const KDevelop::VcsRevision& /*rev*/) { QFileInfo curFile(localLocation.toLocalFile()); if (curFile.isDir()) { KMessageBox::error(nullptr, i18n("Please select a file for this operation")); return errorsFound(i18n("Directory not supported for this operation")); } DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "annotate" << "-qi" << localLocation; connect(job, &DVcsJob::readyForParsing, this , &PerforcePlugin::parseP4AnnotateOutput); return job; } KDevelop::VcsJob* PerforcePlugin::resolve(const QList& /*localLocations*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::createWorkingCopy(const KDevelop::VcsLocation& /*sourceRepository*/, const QUrl& /*destinationDirectory*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { return nullptr; } KDevelop::VcsLocationWidget* PerforcePlugin::vcsLocation(QWidget* parent) const { return new StandardVcsLocationWidget(parent); } KDevelop::VcsJob* PerforcePlugin::edit(const QList& localLocations) { QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "edit" << localLocations; return job; } KDevelop::VcsJob* PerforcePlugin::edit(const QUrl& /*localLocation*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::unedit(const QUrl& /*localLocation*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::localRevision(const QUrl& /*localLocation*/, KDevelop::VcsRevision::RevisionType) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::import(const QString& /*commitMessage*/, const QUrl& /*sourceDirectory*/, const KDevelop::VcsLocation& /*destinationRepository*/) { return nullptr; } KDevelop::ContextMenuExtension PerforcePlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent) { m_common->setupFromContext(context); const QList & ctxUrlList = m_common->contextUrlList(); bool hasVersionControlledEntries = false; for( const QUrl& url : ctxUrlList) { if (isValidDirectory(url)) { hasVersionControlledEntries = true; break; } } if (!hasVersionControlledEntries) return IPlugin::contextMenuExtension(context, parent); QMenu * perforceMenu = m_common->commonActions(parent); perforceMenu->addSeparator(); perforceMenu->addSeparator(); if (!m_edit_action) { m_edit_action = new QAction(i18n("Edit"), this); connect(m_edit_action, &QAction::triggered, this, & PerforcePlugin::ctxEdit); } perforceMenu->addAction(m_edit_action); ContextMenuExtension menuExt; menuExt.addAction(ContextMenuExtension::VcsGroup, perforceMenu->menuAction()); return menuExt; } void PerforcePlugin::ctxEdit() { QList const & ctxUrlList = m_common->contextUrlList(); KDevelop::ICore::self()->runController()->registerJob(edit(ctxUrlList)); } void PerforcePlugin::setEnvironmentForJob(DVcsJob* job, const QFileInfo& curFile) { KProcess* jobproc = job->process(); jobproc->setEnv(QStringLiteral("P4CONFIG"), m_perforceConfigName); if (curFile.isDir()) { jobproc->setEnv(QStringLiteral("PWD"), curFile.filePath()); } else { jobproc->setEnv(QStringLiteral("PWD"), curFile.absolutePath()); } } QList PerforcePlugin::getQvariantFromLogOutput(QStringList const& outputLines) { const QString LOGENTRY_START(QStringLiteral("... #")); const QString DEPOTMESSAGE_START(QStringLiteral("... .")); QMap changes; QList commits; QString currentFileName; QString changeNumberStr, author,changeDescription, commitMessage; VcsEvent currentVcsEvent; VcsItemEvent currentRepoFile; VcsRevision rev; int indexofAt; int changeNumber = 0; for (const QString& line : outputLines) { if (!line.startsWith(LOGENTRY_START) && !line.startsWith(DEPOTMESSAGE_START) && !line.startsWith('\t')) { currentFileName = line; } if(line.indexOf(LOGENTRY_START) != -1) { // expecting the Logentry line to be of the form: //... #5 change 10 edit on 2010/12/06 12:07:31 by mvo@testbed (text) changeNumberStr = line.section(' ', 3, 3 ); // We use global change number changeNumber = changeNumberStr.toInt(); author = line.section(' ', 9, 9); changeDescription = line.section(' ' , 4, 4 ); indexofAt = author.indexOf('@'); author.remove(indexofAt, author.size()); // Only keep the username itself rev.setRevisionValue(changeNumberStr, KDevelop::VcsRevision::GlobalNumber); changes[changeNumber].setRevision(rev); changes[changeNumber].setAuthor(author); changes[changeNumber].setDate(QDateTime::fromString(line.section(' ', 6, 7), QStringLiteral("yyyy/MM/dd hh:mm:ss"))); currentRepoFile.setRepositoryLocation(currentFileName); currentRepoFile.setActions( actionsFromString(changeDescription) ); changes[changeNumber].addItem(currentRepoFile); commitMessage.clear(); // We have a new entry, clear message } if (line.startsWith('\t') || line.startsWith(DEPOTMESSAGE_START)) { commitMessage += line.trimmed() + '\n'; changes[changeNumber].setMessage(commitMessage); } } for(const auto& item : qAsConst(changes)) { commits.prepend(QVariant::fromValue(item)); } return commits; } void PerforcePlugin::parseP4StatusOutput(DVcsJob* job) { const QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); QVariantList statuses; const QString ACTION_STR(QStringLiteral("... action ")); const QString CLIENT_FILE_STR(QStringLiteral("... clientFile ")); VcsStatusInfo status; status.setState(VcsStatusInfo::ItemUserState); for (const QString& line : outputLines) { int idx(line.indexOf(ACTION_STR)); if (idx != -1) { QString curr = line.mid(ACTION_STR.size()); if (curr == QLatin1String("edit")) { status.setState(VcsStatusInfo::ItemModified); } else if (curr == QLatin1String("add")) { status.setState(VcsStatusInfo::ItemAdded); } else { status.setState(VcsStatusInfo::ItemUserState); } continue; } idx = line.indexOf(CLIENT_FILE_STR); if (idx != -1) { QUrl fileUrl = QUrl::fromLocalFile(line.mid(CLIENT_FILE_STR.size())); status.setUrl(fileUrl); } } - statuses.append(qVariantFromValue(status)); + statuses.append(QVariant::fromValue(status)); job->setResults(statuses); } void PerforcePlugin::parseP4LogOutput(KDevelop::DVcsJob* job) { QList commits(getQvariantFromLogOutput(job->output().split('\n', QString::SkipEmptyParts))); job->setResults(commits); } void PerforcePlugin::parseP4DiffOutput(DVcsJob* job) { VcsDiff diff; diff.setDiff(job->output()); QDir dir(job->directory()); do { if (dir.exists(m_perforceConfigName)) { break; } } while (dir.cdUp()); diff.setBaseDiff(QUrl::fromLocalFile(dir.absolutePath())); - job->setResults(qVariantFromValue(diff)); + job->setResults(QVariant::fromValue(diff)); } void PerforcePlugin::parseP4AnnotateOutput(DVcsJob *job) { QVariantList results; /// First get the changelists for this file QStringList strList(job->dvcsCommand()); QString localLocation(strList.last()); /// ASSUMPTION WARNING - localLocation is the last in the annotate command KDevelop::VcsRevision dummyRev; QScopedPointer logJob(new DVcsJob(job->directory(), this, OutputJob::Silent)); QFileInfo curFile(localLocation); setEnvironmentForJob(logJob.data(), curFile); *logJob << m_perforceExecutable << "filelog" << "-lit" << localLocation; QList commits; if (logJob->exec() && logJob->status() == KDevelop::VcsJob::JobSucceeded) { if (!job->output().isEmpty()) { commits = getQvariantFromLogOutput(logJob->output().split('\n', QString::SkipEmptyParts)); } } VcsEvent item; QMap globalCommits; /// Move the VcsEvents to a more suitable data structure for (auto& commit : qAsConst(commits)) { if (commit.canConvert()) { item = commit.value(); } globalCommits.insert(item.revision().revisionValue().toLongLong(), item); } const QStringList lines = job->output().split('\n'); int lineNumber = 0; QMap::iterator currentEvent; bool convertToIntOk(false); int globalRevisionInt(0); QString globalRevision; for (auto& line : lines) { if (line.isEmpty()) { continue; } globalRevision = line.left(line.indexOf(':')); VcsAnnotationLine annotation; annotation.setLineNumber(lineNumber); VcsRevision rev; rev.setRevisionValue(globalRevision, KDevelop::VcsRevision::GlobalNumber); annotation.setRevision(rev); // Find the other info in the commits list globalRevisionInt = globalRevision.toLongLong(&convertToIntOk); if(convertToIntOk) { currentEvent = globalCommits.find(globalRevisionInt); annotation.setAuthor(currentEvent->author()); annotation.setCommitMessage(currentEvent->message()); annotation.setDate(currentEvent->date()); } - results += qVariantFromValue(annotation); + results += QVariant::fromValue(annotation); ++lineNumber; } job->setResults(results); } KDevelop::VcsJob* PerforcePlugin::errorsFound(const QString& error, KDevelop::OutputJob::OutputJobVerbosity verbosity) { DVcsJob* j = new DVcsJob(QDir::temp(), this, verbosity); *j << "echo" << i18n("error: %1", error) << "-n"; return j; } diff --git a/plugins/subversion/svnblamejob.cpp b/plugins/subversion/svnblamejob.cpp index 149e357be7..f3002e6b59 100644 --- a/plugins/subversion/svnblamejob.cpp +++ b/plugins/subversion/svnblamejob.cpp @@ -1,187 +1,187 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "svnblamejob.h" #include "svnblamejob_p.h" #include #include #include "svnclient.h" #include SvnInternalBlameJob::SvnInternalBlameJob( SvnJobBase* parent ) : SvnInternalJobBase( parent ) { - m_startRevision.setRevisionValue( qVariantFromValue( KDevelop::VcsRevision::Start ), + m_startRevision.setRevisionValue(QVariant::fromValue(KDevelop::VcsRevision::Start), KDevelop::VcsRevision::Special ); - m_endRevision.setRevisionValue( qVariantFromValue( KDevelop::VcsRevision::Head ), + m_endRevision.setRevisionValue(QVariant::fromValue(KDevelop::VcsRevision::Head), KDevelop::VcsRevision::Special ); } void SvnInternalBlameJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread* /*thread*/) { initBeforeRun(); QByteArray ba = location().toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ).toUtf8(); svn::Client cli(m_ctxt); svn::AnnotatedFile* file; try { file = cli.annotate( ba.data(), createSvnCppRevisionFromVcsRevision( startRevision() ), createSvnCppRevisionFromVcsRevision( endRevision() ) ); }catch( const svn::ClientException& ce ) { qCDebug(PLUGIN_SVN) << "Exception while blaming file: " << location() << QString::fromUtf8( ce.message() ); setErrorMessage( QString::fromUtf8( ce.message() ) ); m_success = false; return; } svn_revnum_t minrev = -1, maxrev = -1; for (const auto& line : *file) { const svn_revnum_t lineRevision = line.revision(); if (lineRevision < minrev || minrev == -1) { minrev = lineRevision; } if (lineRevision > maxrev || maxrev == -1 ) { maxrev = lineRevision; } } QHash commitMessages; try { const svn::LogEntries* entries = cli.log( ba.data(), svn::Revision(minrev), svn::Revision(maxrev), false, false ); for (const auto& entry : *entries) { commitMessages[entry.revision] = QString::fromUtf8(entry.message.c_str() ); } }catch( const svn::ClientException& ce ) { qCDebug(PLUGIN_SVN) << "Exception while fetching log messages for blame: " << location() << QString::fromUtf8( ce.message() ); setErrorMessage( QString::fromUtf8( ce.message() ) ); m_success = false; } for (const auto& svnLine : *file) { KDevelop::VcsAnnotationLine line; line.setAuthor(QString::fromUtf8(svnLine.author().c_str())); line.setDate(QDateTime::fromString(QString::fromUtf8(svnLine.date().c_str()), Qt::ISODate)); line.setText(QString::fromUtf8(svnLine.line().c_str())); KDevelop::VcsRevision rev; rev.setRevisionValue(QVariant(qlonglong(svnLine.revision())), KDevelop::VcsRevision::GlobalNumber); line.setRevision( rev ); line.setLineNumber(svnLine.lineNumber()); line.setCommitMessage(commitMessages[svnLine.revision()]); emit blameLine( line ); } } void SvnInternalBlameJob::setLocation( const QUrl &url ) { QMutexLocker l( &m_mutex ); m_location = url; } QUrl SvnInternalBlameJob::location() const { QMutexLocker l( &m_mutex ); return m_location; } KDevelop::VcsRevision SvnInternalBlameJob::startRevision() const { QMutexLocker l( &m_mutex ); return m_startRevision; } KDevelop::VcsRevision SvnInternalBlameJob::endRevision() const { QMutexLocker l( &m_mutex ); return m_endRevision; } void SvnInternalBlameJob::setStartRevision( const KDevelop::VcsRevision& rev ) { QMutexLocker l( &m_mutex ); m_startRevision = rev; } void SvnInternalBlameJob::setEndRevision( const KDevelop::VcsRevision& rev ) { QMutexLocker l( &m_mutex ); m_endRevision = rev; } SvnBlameJob::SvnBlameJob( KDevSvnPlugin* parent ) : SvnJobBaseImpl( parent, KDevelop::OutputJob::Silent ) { setType( KDevelop::VcsJob::Annotate ); connect(m_job.data(), &SvnInternalBlameJob::blameLine, this, &SvnBlameJob::blameLineReceived); setObjectName(i18n("Subversion Annotate")); } QVariant SvnBlameJob::fetchResults() { QList results = m_annotations; m_annotations.clear(); return results; } void SvnBlameJob::start() { if ( !m_job->location().isValid() ) { internalJobFailed(); setErrorText( i18n( "Not enough information to blame location" ) ); } else { qCDebug(PLUGIN_SVN) << "blaming url:" << m_job->location(); startInternalJob(); } } void SvnBlameJob::setLocation( const QUrl &url ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setLocation( url ); } void SvnBlameJob::setStartRevision( const KDevelop::VcsRevision& rev ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setStartRevision( rev ); } void SvnBlameJob::setEndRevision( const KDevelop::VcsRevision& rev ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setEndRevision( rev ); } void SvnBlameJob::blameLineReceived( const KDevelop::VcsAnnotationLine& line ) { - m_annotations.append( qVariantFromValue( line ) ); + m_annotations.append(QVariant::fromValue(line)); emit resultsReady( this ); } diff --git a/plugins/subversion/svndiffjob.cpp b/plugins/subversion/svndiffjob.cpp index 6a95dbcab4..f1482b5a99 100644 --- a/plugins/subversion/svndiffjob.cpp +++ b/plugins/subversion/svndiffjob.cpp @@ -1,342 +1,342 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "svndiffjob.h" #include "svndiffjob_p.h" #include #include #include #include #include #include #include "kdevsvncpp/path.hpp" #include "kdevsvncpp/revision.hpp" #include "icore.h" #include "iruncontroller.h" #include "svnclient.h" ///@todo The subversion library returns borked diffs, where the headers are at the end. This function /// takes those headers, and moves them into the correct place to create a valid working diff. /// Find the source of this problem. QString repairDiff(const QString& diff) { qCDebug(PLUGIN_SVN) << "diff before repair:" << diff; QStringList lines = diff.split(QLatin1Char('\n')); QMap headers; for(int a = 0; a < lines.size()-1; ++a) { if(lines[a].startsWith(QLatin1String("Index: ")) && lines[a+1].startsWith(QLatin1String("====="))) { const QString fileName = lines[a].midRef(strlen("Index: ")).trimmed().toString(); headers[fileName] = lines[a]; qCDebug(PLUGIN_SVN) << "found header for" << fileName; lines[a] = QString(); if(lines[a+1].startsWith(QLatin1String("======"))) { headers[fileName] += QLatin1Char('\n') + lines[a+1]; lines[a+1] = QString(); } } } QRegExp spaceRegExp(QStringLiteral("\\s")); for(int a = 0; a < lines.size()-1; ++a) { if(lines[a].startsWith(QLatin1String("--- "))) { QString tail = lines[a].mid(strlen("--- ")); if(tail.indexOf(spaceRegExp) != -1) { QString file = tail.left(tail.indexOf(spaceRegExp)); qCDebug(PLUGIN_SVN) << "checking for" << file; const auto headerIt = headers.constFind(file); if (headerIt != headers.constEnd()) { qCDebug(PLUGIN_SVN) << "adding header for" << file << ":" << *headerIt; lines[a] = *headerIt + QLatin1Char('\n') + lines[a]; } } } } QString ret = lines.join(QLatin1Char('\n')); qCDebug(PLUGIN_SVN) << "repaired diff:" << ret; return ret; } //@TODO: Handle raw diffs by using SvnCatJob to fetch both files/revisions SvnInternalDiffJob::SvnInternalDiffJob( SvnJobBase* parent ) : SvnInternalJobBase( parent ) { m_pegRevision.setRevisionValue( KDevelop::VcsRevision::Head, KDevelop::VcsRevision::Special ); } void SvnInternalDiffJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread* /*thread*/) { initBeforeRun(); SvnClient cli(m_ctxt); try { QString diff; if( destination().isValid() ) { QByteArray srcba; if( source().type() == KDevelop::VcsLocation::LocalLocation ) { srcba = source().localUrl().toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ).toUtf8(); }else { srcba = source().repositoryServer().toUtf8(); } QByteArray dstba; if( destination().type() == KDevelop::VcsLocation::LocalLocation ) { dstba = destination().localUrl().toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ).toUtf8(); }else { dstba = destination().repositoryServer().toUtf8(); } svn::Revision srcRev = createSvnCppRevisionFromVcsRevision( srcRevision() ); svn::Revision dstRev = createSvnCppRevisionFromVcsRevision( dstRevision() ); if( srcba.isEmpty() || ( dstba.isEmpty() && srcRev.kind() == svn_opt_revision_unspecified && dstRev.kind() == svn_opt_revision_unspecified ) ) { throw svn::ClientException( "Not enough information for a diff"); } diff = cli.diff( svn::Path( srcba.data() ), srcRev, svn::Path( dstba.data() ), dstRev, recursive(), ignoreAncestry(), noDiffOnDelete(), ignoreContentType() ); }else { QByteArray srcba; if( source().type() == KDevelop::VcsLocation::LocalLocation ) { srcba = source().localUrl().toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ).toUtf8(); }else { srcba = source().repositoryServer().toUtf8(); } svn::Revision pegRev = createSvnCppRevisionFromVcsRevision( pegRevision() ); svn::Revision srcRev = createSvnCppRevisionFromVcsRevision( srcRevision() ); svn::Revision dstRev = createSvnCppRevisionFromVcsRevision( dstRevision() ); if( srcba.isEmpty() || pegRev.kind() == svn_opt_revision_unspecified || dstRev.kind() == svn_opt_revision_unspecified || srcRev.kind() == svn_opt_revision_unspecified) { throw svn::ClientException( "Not enough information for a diff"); } diff = cli.diff( svn::Path( srcba.data() ), pegRev, srcRev, dstRev, recursive(), ignoreAncestry(), noDiffOnDelete(), ignoreContentType() ); } diff = repairDiff(diff); emit gotDiff( diff ); }catch( const svn::ClientException& ce ) { qCDebug(PLUGIN_SVN) << "Exception while doing a diff: " << m_source.localUrl() << m_source.repositoryServer() << m_srcRevision.prettyValue() << m_destination.localUrl() << m_destination.repositoryServer() << m_dstRevision.prettyValue() << QString::fromUtf8( ce.message() ); setErrorMessage( QString::fromUtf8( ce.message() ) ); m_success = false; } } void SvnInternalDiffJob::setSource( const KDevelop::VcsLocation& src ) { QMutexLocker l( &m_mutex ); m_source = src; } void SvnInternalDiffJob::setDestination( const KDevelop::VcsLocation& dst ) { QMutexLocker l( &m_mutex ); m_destination = dst; } void SvnInternalDiffJob::setSrcRevision( const KDevelop::VcsRevision& srcRev ) { QMutexLocker l( &m_mutex ); m_srcRevision = srcRev; } void SvnInternalDiffJob::setDstRevision( const KDevelop::VcsRevision& dstRev ) { QMutexLocker l( &m_mutex ); m_dstRevision = dstRev; } void SvnInternalDiffJob::setPegRevision( const KDevelop::VcsRevision& pegRev ) { QMutexLocker l( &m_mutex ); m_pegRevision = pegRev; } void SvnInternalDiffJob::setRecursive( bool recursive ) { QMutexLocker l( &m_mutex ); m_recursive = recursive; } void SvnInternalDiffJob::setIgnoreAncestry( bool ignoreAncestry ) { QMutexLocker l( &m_mutex ); m_ignoreAncestry = ignoreAncestry; } void SvnInternalDiffJob::setIgnoreContentType( bool ignoreContentType ) { QMutexLocker l( &m_mutex ); m_ignoreContentType = ignoreContentType; } void SvnInternalDiffJob::setNoDiffOnDelete( bool noDiffOnDelete ) { QMutexLocker l( &m_mutex ); m_noDiffOnDelete = noDiffOnDelete; } bool SvnInternalDiffJob::recursive() const { QMutexLocker l( &m_mutex ); return m_recursive; } bool SvnInternalDiffJob::ignoreAncestry() const { QMutexLocker l( &m_mutex ); return m_ignoreAncestry; } bool SvnInternalDiffJob::ignoreContentType() const { QMutexLocker l( &m_mutex ); return m_ignoreContentType; } bool SvnInternalDiffJob::noDiffOnDelete() const { QMutexLocker l( &m_mutex ); return m_noDiffOnDelete; } KDevelop::VcsLocation SvnInternalDiffJob::source() const { QMutexLocker l( &m_mutex ); return m_source; } KDevelop::VcsLocation SvnInternalDiffJob::destination() const { QMutexLocker l( &m_mutex ); return m_destination; } KDevelop::VcsRevision SvnInternalDiffJob::srcRevision() const { QMutexLocker l( &m_mutex ); return m_srcRevision; } KDevelop::VcsRevision SvnInternalDiffJob::dstRevision() const { QMutexLocker l( &m_mutex ); return m_dstRevision; } KDevelop::VcsRevision SvnInternalDiffJob::pegRevision() const { QMutexLocker l( &m_mutex ); return m_pegRevision; } SvnDiffJob::SvnDiffJob( KDevSvnPlugin* parent ) : SvnJobBaseImpl( parent, KDevelop::OutputJob::Silent ) { setType( KDevelop::VcsJob::Add ); connect( m_job.data(), &SvnInternalDiffJob::gotDiff, this, &SvnDiffJob::setDiff, Qt::QueuedConnection ); setObjectName(i18n("Subversion Diff")); } QVariant SvnDiffJob::fetchResults() { - return qVariantFromValue( m_diff ); + return QVariant::fromValue(m_diff); } void SvnDiffJob::start() { if( !m_job->source().isValid() || ( !m_job->destination().isValid() && ( m_job->srcRevision().revisionType() == KDevelop::VcsRevision::Invalid || m_job->dstRevision().revisionType() == KDevelop::VcsRevision::Invalid ) ) ) { internalJobFailed(); setErrorText( i18n( "Not enough information given to execute diff" ) ); } else { startInternalJob(); } } void SvnDiffJob::setSource( const KDevelop::VcsLocation& source ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setSource( source ); } void SvnDiffJob::setDestination( const KDevelop::VcsLocation& destination ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setDestination( destination ); } void SvnDiffJob::setPegRevision( const KDevelop::VcsRevision& pegRevision ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setPegRevision( pegRevision ); } void SvnDiffJob::setSrcRevision( const KDevelop::VcsRevision& srcRevision ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setSrcRevision( srcRevision ); } void SvnDiffJob::setDstRevision( const KDevelop::VcsRevision& dstRevision ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setDstRevision( dstRevision ); } void SvnDiffJob::setRecursive( bool recursive ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setRecursive( recursive ); } void SvnDiffJob::setIgnoreAncestry( bool ignoreAncestry ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setIgnoreAncestry( ignoreAncestry ); } void SvnDiffJob::setIgnoreContentType( bool ignoreContentType ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setIgnoreContentType( ignoreContentType ); } void SvnDiffJob::setNoDiffOnDelete( bool noDiffOnDelete ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setNoDiffOnDelete( noDiffOnDelete ); } void SvnDiffJob::setDiff( const QString& diff ) { m_diff = KDevelop::VcsDiff(); m_diff.setBaseDiff(QUrl::fromLocalFile(QStringLiteral("/"))); m_diff.setDiff( diff ); emit resultsReady( this ); } diff --git a/plugins/subversion/svninfojob.cpp b/plugins/subversion/svninfojob.cpp index 6ff6d63ae4..7823f9561d 100644 --- a/plugins/subversion/svninfojob.cpp +++ b/plugins/subversion/svninfojob.cpp @@ -1,153 +1,153 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "svninfojob.h" #include "svninfojob_p.h" #include #include #include "kdevsvncpp/client.hpp" #include "kdevsvncpp/info.hpp" SvnInternalInfoJob::SvnInternalInfoJob( SvnJobBase* parent ) : SvnInternalJobBase( parent ) { } void SvnInternalInfoJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread* /*thread*/) { initBeforeRun(); svn::Client cli(m_ctxt); try { QByteArray ba = location().toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ).toUtf8(); svn::InfoVector v = cli.info( ba.data() ); svn::Info i = v.at(0); SvnInfoHolder h; h.name = QString::fromUtf8( i.path().path().c_str() ); h.url = QUrl::fromUserInput( QString::fromUtf8( i.url() ) ); h.rev = qlonglong( i.revision() ); h.kind = i.kind(); h.repoUrl = QUrl::fromUserInput( QString::fromUtf8( i.repos() ) ); h.repouuid = QString::fromUtf8( i.uuid() ); h.lastChangedRev = qlonglong( i.lastChangedRevision() ); h.lastChangedDate = QDateTime::fromTime_t( i.lastChangedDate() ); h.lastChangedAuthor = QString::fromUtf8( i.lastChangedAuthor() ); h.scheduled = i.schedule(); h.copyFromUrl = QUrl::fromUserInput( QString::fromUtf8( i.copyFromUrl() ) ); h.copyFromRevision = qlonglong( i.copyFromRevision() ); h.textTime = QDateTime::fromTime_t( i.textTime() ); h.propertyTime = QDateTime::fromTime_t( i.propertyTime() ); h.oldFileConflict = QString::fromUtf8( i.oldConflictFile() ); h.newFileConflict = QString::fromUtf8( i.newConflictFile() ); h.workingCopyFileConflict = QString::fromUtf8( i.workingConflictFile() ); h.propertyRejectFile = QString::fromUtf8( i.propertyRejectFile() ); emit gotInfo( h ); }catch( const svn::ClientException& ce ) { qCDebug(PLUGIN_SVN) << "Exception while getting info for file: " << m_location << QString::fromUtf8( ce.message() ); setErrorMessage( QString::fromUtf8( ce.message() ) ); m_success = false; } } void SvnInternalInfoJob::setLocation( const QUrl &url ) { QMutexLocker l( &m_mutex ); m_location = url; } QUrl SvnInternalInfoJob::location() const { QMutexLocker l( &m_mutex ); return m_location; } SvnInfoJob::SvnInfoJob( KDevSvnPlugin* parent ) : SvnJobBaseImpl( parent, KDevelop::OutputJob::Silent ), m_provideInfo( SvnInfoJob::AllInfo ) { setType( KDevelop::VcsJob::Add ); connect( m_job.data(), &SvnInternalInfoJob::gotInfo, this, &SvnInfoJob::setInfo, Qt::QueuedConnection ); setObjectName(i18n("Subversion Info")); } QVariant SvnInfoJob::fetchResults() { if( m_provideInfo == RepoUrlOnly ) { return QVariant(m_info.url); }else if( m_provideInfo == RevisionOnly ) { KDevelop::VcsRevision rev; svn::Revision svnRev( m_info.rev ); switch( m_provideRevisionType ) { case KDevelop::VcsRevision::Date: rev.setRevisionValue( QVariant( QDateTime::fromTime_t( svnRev.date() ) ), KDevelop::VcsRevision::Date ); break; default: rev.setRevisionValue( QVariant( qlonglong( svnRev.revnum() ) ), KDevelop::VcsRevision::GlobalNumber ); break; } - return qVariantFromValue( rev ); + return QVariant::fromValue(rev); } - return qVariantFromValue( m_info ); + return QVariant::fromValue(m_info); } void SvnInfoJob::start() { if (!m_job->location().isValid()) { internalJobFailed(); setErrorText( i18n( "Not enough information to execute info job" ) ); } else { startInternalJob(); } } void SvnInfoJob::setLocation( const QUrl &url ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setLocation( url ); } void SvnInfoJob::setProvideInformation( ProvideInformationType type ) { m_provideInfo = type; } void SvnInfoJob::setProvideRevisionType( KDevelop::VcsRevision::RevisionType t ) { m_provideRevisionType = t; } void SvnInfoJob::setInfo( const SvnInfoHolder& info ) { m_info = info; emit resultsReady( this ); } diff --git a/plugins/subversion/svnlogjob.cpp b/plugins/subversion/svnlogjob.cpp index 57eab7019a..17bf726bcb 100644 --- a/plugins/subversion/svnlogjob.cpp +++ b/plugins/subversion/svnlogjob.cpp @@ -1,170 +1,170 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "svnlogjob.h" #include "svnlogjob_p.h" #include #include #include "svnclient.h" SvnInternalLogJob::SvnInternalLogJob( SvnJobBase* parent ) : SvnInternalJobBase( parent ) { - m_endRevision.setRevisionValue( qVariantFromValue( KDevelop::VcsRevision::Start ), + m_endRevision.setRevisionValue(QVariant::fromValue(KDevelop::VcsRevision::Start), KDevelop::VcsRevision::Special ); - m_startRevision.setRevisionValue( qVariantFromValue( KDevelop::VcsRevision::Head ), + m_startRevision.setRevisionValue(QVariant::fromValue(KDevelop::VcsRevision::Head), KDevelop::VcsRevision::Special ); m_limit = 0; } void SvnInternalLogJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread* /*thread*/) { initBeforeRun(); SvnClient cli(m_ctxt); connect( &cli, &SvnClient::logEventReceived, this, &SvnInternalLogJob::logEvent ); try { QByteArray ba = location().toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ).toUtf8(); cli.log( ba.data(), createSvnCppRevisionFromVcsRevision( startRevision() ), createSvnCppRevisionFromVcsRevision( endRevision() ), limit() ); }catch( const svn::ClientException& ce ) { qCDebug(PLUGIN_SVN) << "Exception while logging file: " << location() << QString::fromUtf8( ce.message() ); setErrorMessage( QString::fromUtf8( ce.message() ) ); m_success = false; } } void SvnInternalLogJob::setLocation( const QUrl &url ) { QMutexLocker l( &m_mutex ); m_location = url; } QUrl SvnInternalLogJob::location() const { QMutexLocker l( &m_mutex ); return m_location; } KDevelop::VcsRevision SvnInternalLogJob::startRevision() const { QMutexLocker l( &m_mutex ); return m_startRevision; } KDevelop::VcsRevision SvnInternalLogJob::endRevision() const { QMutexLocker l( &m_mutex ); return m_endRevision; } int SvnInternalLogJob::limit() const { QMutexLocker l( &m_mutex ); return m_limit; } void SvnInternalLogJob::setStartRevision( const KDevelop::VcsRevision& rev ) { QMutexLocker l( &m_mutex ); m_startRevision = rev; } void SvnInternalLogJob::setEndRevision( const KDevelop::VcsRevision& rev ) { QMutexLocker l( &m_mutex ); m_endRevision = rev; } void SvnInternalLogJob::setLimit( int limit ) { QMutexLocker l( &m_mutex ); m_limit = limit; } SvnLogJob::SvnLogJob( KDevSvnPlugin* parent ) : SvnJobBaseImpl( parent, KDevelop::OutputJob::Silent ) { setType( KDevelop::VcsJob::Log ); connect( m_job.data(), &SvnInternalLogJob::logEvent, this, &SvnLogJob::logEventReceived ); setObjectName(i18n("Subversion Log")); } QVariant SvnLogJob::fetchResults() { QList list = m_eventList; m_eventList.clear(); return list; } void SvnLogJob::start() { if( !m_job->location().isValid() ) { internalJobFailed(); setErrorText( i18n( "Not enough information to log location" ) ); }else { qCDebug(PLUGIN_SVN) << "logging url:" << m_job->location(); startInternalJob(); } } void SvnLogJob::setLocation( const QUrl &url ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setLocation( url ); } void SvnLogJob::setStartRevision( const KDevelop::VcsRevision& rev ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setStartRevision( rev ); } void SvnLogJob::setEndRevision( const KDevelop::VcsRevision& rev ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setEndRevision( rev ); } void SvnLogJob::setLimit( int limit ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setLimit( limit ); } void SvnLogJob::logEventReceived( const KDevelop::VcsEvent& ev ) { - m_eventList << qVariantFromValue( ev ); + m_eventList << QVariant::fromValue(ev); emit resultsReady( this ); } diff --git a/plugins/subversion/svnstatusjob.cpp b/plugins/subversion/svnstatusjob.cpp index c5cc4e619a..391d12516e 100644 --- a/plugins/subversion/svnstatusjob.cpp +++ b/plugins/subversion/svnstatusjob.cpp @@ -1,175 +1,174 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "svnstatusjob.h" #include "svnstatusjob_p.h" #include #include #include extern "C" { #include } #include #include "kdevsvncpp/client.hpp" #include "kdevsvncpp/status.hpp" KDevelop::VcsStatusInfo::State getState( const svn::Status& st ) { KDevelop::VcsStatusInfo::State state; if( st.isVersioned() ) { if( st.textStatus() == svn_wc_status_added ) { state = KDevelop::VcsStatusInfo::ItemAdded; }else if( st.textStatus() == svn_wc_status_modified || st.propStatus() == svn_wc_status_modified ) { state = KDevelop::VcsStatusInfo::ItemModified; }else if( st.textStatus() == svn_wc_status_deleted ) { state = KDevelop::VcsStatusInfo::ItemDeleted; }else if( st.textStatus() == svn_wc_status_conflicted || st.propStatus() == svn_wc_status_conflicted ) { state = KDevelop::VcsStatusInfo::ItemHasConflicts; }else { state = KDevelop::VcsStatusInfo::ItemUpToDate; } }else { state = KDevelop::VcsStatusInfo::ItemUnknown; } return state; } SvnInternalStatusJob::SvnInternalStatusJob( SvnJobBase* parent ) : SvnInternalJobBase( parent ) { } void SvnInternalStatusJob::setRecursive( bool recursive ) { QMutexLocker l( &m_mutex ); m_recursive = recursive; } void SvnInternalStatusJob::setLocations( const QList& urls ) { QMutexLocker l( &m_mutex ); m_locations = urls; } QList SvnInternalStatusJob::locations() const { QMutexLocker l( &m_mutex ); return m_locations; } bool SvnInternalStatusJob::recursive() const { QMutexLocker l( &m_mutex ); return m_recursive; } void SvnInternalStatusJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread* /*thread*/) { qCDebug(PLUGIN_SVN) << "Running internal status job with urls:" << m_locations; initBeforeRun(); svn::Client cli(m_ctxt); const QList l = locations(); for (const QUrl& url : l) { //qCDebug(PLUGIN_SVN) << "Fetching status info for:" << url; try { QByteArray ba = url.toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ).toUtf8(); const svn::StatusEntries se = cli.status(ba.data(), recursive()); for (auto& statusEntry : se) { KDevelop::VcsStatusInfo info; info.setUrl(QUrl::fromLocalFile(QString::fromUtf8(statusEntry.path()))); info.setState(getState(statusEntry)); emit gotNewStatus( info ); } }catch( const svn::ClientException& ce ) { qCDebug(PLUGIN_SVN) << "Couldn't get status: " << url << QString::fromUtf8( ce.message() ); setErrorMessage( QString::fromUtf8( ce.message() ) ); m_success = false; } } } SvnStatusJob::SvnStatusJob( KDevSvnPlugin* parent ) : SvnJobBaseImpl( parent, KDevelop::OutputJob::Silent ) { setType( KDevelop::VcsJob::Status ); connect(m_job.data(), &SvnInternalStatusJob::gotNewStatus, this, &SvnStatusJob::addToStats, Qt::QueuedConnection); setObjectName(i18n("Subversion Status")); } QVariant SvnStatusJob::fetchResults() { QList temp = m_stats; m_stats.clear(); return QVariant(temp); } void SvnStatusJob::start() { if( m_job->locations().isEmpty() ) { internalJobFailed(); setErrorText( i18n( "Not enough information to execute status job" ) ); } else { qCDebug(PLUGIN_SVN) << "Starting status job"; startInternalJob(); } } void SvnStatusJob::setLocations( const QList& urls ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setLocations( urls ); } void SvnStatusJob::setRecursive( bool recursive ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setRecursive( recursive ); } void SvnStatusJob::addToStats( const KDevelop::VcsStatusInfo& info ) { //qCDebug(PLUGIN_SVN) << "new status info:" << info.url() << info.state(); - if( !m_stats.contains( qVariantFromValue( info ) ) ) - { - m_stats << qVariantFromValue( info ); + if (!m_stats.contains(QVariant::fromValue(info))) { + m_stats << QVariant::fromValue(info); emit resultsReady( this ); }else { qCDebug(PLUGIN_SVN) << "Already have this info:"; } }