diff --git a/vcs/models/vcseventmodel.cpp b/vcs/models/vcseventmodel.cpp index 56d5f36aa0..d4623ad7f6 100644 --- a/vcs/models/vcseventmodel.cpp +++ b/vcs/models/vcseventmodel.cpp @@ -1,183 +1,191 @@ /*************************************************************************** * 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 "vcseventmodel.h" #include #include #include #include #include #include #include "../vcsevent.h" #include "../vcsrevision.h" #include #include #include #include namespace KDevelop { -class VcsEventModelPrivate +class VcsBasicEventModelPrivate { public: QList m_events; - KDevelop::IBasicVersionControl* m_iface; - VcsRevision m_rev; - QUrl m_url; - bool done; - bool fetching; }; -VcsEventModel::VcsEventModel( KDevelop::IBasicVersionControl* iface, const VcsRevision& rev, const QUrl& url, QObject* parent ) - : QAbstractTableModel( parent ), d(new VcsEventModelPrivate) +VcsBasicEventModel::VcsBasicEventModel(QObject* parent) + : QAbstractTableModel(parent), d(new VcsBasicEventModelPrivate) { - d->m_iface = iface; - d->m_rev = rev; - d->m_url = url; - d->done = false; - d->fetching = false; } -VcsEventModel::~VcsEventModel() -{ - delete d; -} +VcsBasicEventModel::~VcsBasicEventModel() = default; -int VcsEventModel::rowCount( const QModelIndex& parent) const +int VcsBasicEventModel::rowCount(const QModelIndex& parent) const { return parent.isValid() ? 0 : d->m_events.count(); } -int VcsEventModel::columnCount( const QModelIndex& parent) const +int VcsBasicEventModel::columnCount(const QModelIndex& parent) const { return parent.isValid() ? 0 : ColumnCount; } -QVariant VcsEventModel::data( const QModelIndex& idx, int role ) const +QVariant VcsBasicEventModel::data(const QModelIndex& idx, int role) const { if( !idx.isValid() || role != Qt::DisplayRole ) return QVariant(); if( idx.row() < 0 || idx.row() >= rowCount() || idx.column() < 0 || idx.column() >= columnCount() ) return QVariant(); KDevelop::VcsEvent ev = d->m_events.at( idx.row() ); switch( idx.column() ) { case RevisionColumn: return QVariant( ev.revision().revisionValue() ); case SummaryColumn: // show the first line only return QVariant( ev.message().section('\n', 0, 0) ); case AuthorColumn: return QVariant( ev.author() ); case DateColumn: return QVariant( QLocale().toString( ev.date() ) ); default: break; } return QVariant(); } -QVariant VcsEventModel::headerData( int section, Qt::Orientation orientation, int role ) const +QVariant VcsBasicEventModel::headerData(int section, Qt::Orientation orientation, int role) const { if( section < 0 || section >= columnCount() || orientation != Qt::Horizontal || role != Qt::DisplayRole ) return QVariant(); switch( section ) { case RevisionColumn: return QVariant( i18n("Revision") ); case SummaryColumn: return QVariant( i18n("Message") ); case AuthorColumn: return QVariant( i18n("Author") ); case DateColumn: return QVariant( i18n("Date") ); default: break; } return QVariant(); } -void VcsEventModel::addEvents( const QList& list ) +void VcsBasicEventModel::addEvents(const QList& list) { if( list.isEmpty() ) return; beginInsertRows( QModelIndex(), rowCount(), rowCount()+list.count()-1 ); d->m_events += list; endInsertRows(); } -KDevelop::VcsEvent VcsEventModel::eventForIndex( const QModelIndex& idx ) const +KDevelop::VcsEvent VcsBasicEventModel::eventForIndex(const QModelIndex& idx) const { if( !idx.isValid() || idx.row() < 0 || idx.row() >= rowCount() ) { return KDevelop::VcsEvent(); } return d->m_events.at( idx.row() ); } -bool VcsEventModel::canFetchMore(const QModelIndex& parent) const +class VcsEventLogModelPrivate +{ +public: + KDevelop::IBasicVersionControl* m_iface; + VcsRevision m_rev; + QUrl m_url; + bool done; + bool fetching; +}; + +VcsEventLogModel::VcsEventLogModel(KDevelop::IBasicVersionControl* iface, const VcsRevision& rev, const QUrl& url, QObject* parent) + : KDevelop::VcsBasicEventModel(parent), d(new VcsEventLogModelPrivate) +{ + d->m_iface = iface; + d->m_rev = rev; + d->m_url = url; + d->done = false; + d->fetching = false; +} + +VcsEventLogModel::~VcsEventLogModel() = default; + +bool VcsEventLogModel::canFetchMore(const QModelIndex& parent) const { return !d->done && !d->fetching && !parent.isValid(); } -void VcsEventModel::fetchMore(const QModelIndex& parent) +void VcsEventLogModel::fetchMore(const QModelIndex& parent) { d->fetching = true; Q_ASSERT(!parent.isValid()); Q_UNUSED(parent); VcsJob* job = d->m_iface->log(d->m_url, d->m_rev, qMax(rowCount(), 100)); - connect(this, &VcsEventModel::destroyed, job, [job] { job->kill(); }); - connect(job, &VcsJob::finished, this, &VcsEventModel::jobReceivedResults); + connect(this, &VcsEventLogModel::destroyed, job, [job] { job->kill(); }); + connect(job, &VcsJob::finished, this, &VcsEventLogModel::jobReceivedResults); ICore::self()->runController()->registerJob( job ); } -void VcsEventModel::jobReceivedResults(KJob* job) +void VcsEventLogModel::jobReceivedResults(KJob* job) { QList l = qobject_cast(job)->fetchResults().toList(); if(l.isEmpty() || job->error()!=0) { d->done = true; return; } QList newevents; foreach( const QVariant &v, l ) { if( v.canConvert() ) { newevents << v.value(); } } d->m_rev = newevents.last().revision(); - if(!d->m_events.isEmpty()) { + if (rowCount()) { newevents.removeFirst(); } d->done = newevents.isEmpty(); addEvents( newevents ); d->fetching = false; } } - diff --git a/vcs/models/vcseventmodel.h b/vcs/models/vcseventmodel.h index 035eb08cb4..dc2dc5f6c5 100644 --- a/vcs/models/vcseventmodel.h +++ b/vcs/models/vcseventmodel.h @@ -1,70 +1,96 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_VCSEVENTMODEL_H #define KDEVPLATFORM_VCSEVENTMODEL_H #include +#include #include class QUrl; class KJob; namespace KDevelop { class VcsRevision; class IBasicVersionControl; class VcsEvent; -class KDEVPLATFORMVCS_EXPORT VcsEventModel : public QAbstractTableModel +/** + * This is a generic model to store a list of VcsEvents. + * + * To add events use @c addEvents + */ +class KDEVPLATFORMVCS_EXPORT VcsBasicEventModel : public QAbstractTableModel { Q_OBJECT public: enum Column { RevisionColumn, SummaryColumn, AuthorColumn, DateColumn, ColumnCount, }; - VcsEventModel( KDevelop::IBasicVersionControl* iface, const KDevelop::VcsRevision& rev, const QUrl& url, QObject* parent ); - ~VcsEventModel() override; - int rowCount( const QModelIndex& = QModelIndex() ) const override; - int columnCount( const QModelIndex& parent = QModelIndex() ) const override; - QVariant data( const QModelIndex&, int role = Qt::DisplayRole ) const override; - QVariant headerData( int, Qt::Orientation, int role = Qt::DisplayRole ) const override; - KDevelop::VcsEvent eventForIndex( const QModelIndex& ) const; + VcsBasicEventModel(QObject* parent); + ~VcsBasicEventModel() override; + int rowCount(const QModelIndex& = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex&, int role = Qt::DisplayRole) const override; + QVariant headerData(int, Qt::Orientation, int role = Qt::DisplayRole) const override; + KDevelop::VcsEvent eventForIndex(const QModelIndex&) const; +protected: + void addEvents(const QList&); + +private: + QScopedPointer d; +}; + +/** + * This model stores a list of VcsEvents corresponding to the log obtained + * via IBasicVersionControl::log for a given revision. The model is populated + * lazily via @c fetchMore. + */ +class KDEVPLATFORMVCS_EXPORT VcsEventLogModel : public VcsBasicEventModel +{ +Q_OBJECT +public: + + VcsEventLogModel(KDevelop::IBasicVersionControl* iface, const KDevelop::VcsRevision& rev, const QUrl& url, QObject* parent); + ~VcsEventLogModel() override; + + /// Adds events to the model via @sa IBasicVersionControl::log void fetchMore(const QModelIndex& parent) override; bool canFetchMore(const QModelIndex& parent) const override; private slots: void jobReceivedResults( KJob* job ); private: - void addEvents( const QList& ); - class VcsEventModelPrivate* const d; + QScopedPointer d; }; } #endif diff --git a/vcs/widgets/vcseventwidget.cpp b/vcs/widgets/vcseventwidget.cpp index 60199aa119..5199e6c729 100644 --- a/vcs/widgets/vcseventwidget.cpp +++ b/vcs/widgets/vcseventwidget.cpp @@ -1,235 +1,235 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Dukju Ahn * * 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 "vcseventwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_vcseventwidget.h" #include "vcsdiffwidget.h" #include "../interfaces/ibasicversioncontrol.h" #include "../models/vcseventmodel.h" #include "../models/vcsitemeventmodel.h" #include "../debug.h" #include "../vcsevent.h" #include "../vcsjob.h" #include "../vcslocation.h" #include "../vcsrevision.h" namespace KDevelop { class VcsEventWidgetPrivate { public: explicit VcsEventWidgetPrivate( VcsEventWidget* w ) : q( w ) { m_copyAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy revision number"), q); m_copyAction->setShortcut(Qt::ControlModifier+Qt::Key_C); QObject::connect(m_copyAction, &QAction::triggered, q, [&] { copyRevision(); }); } Ui::VcsEventWidget* m_ui; VcsItemEventModel* m_detailModel; - VcsEventModel *m_logModel; + VcsEventLogModel *m_logModel; QUrl m_url; QModelIndex m_contextIndex; VcsEventWidget* q; QAction* m_copyAction; IBasicVersionControl* m_iface; void eventViewCustomContextMenuRequested( const QPoint &point ); void eventViewClicked( const QModelIndex &index ); void jobReceivedResults( KDevelop::VcsJob* job ); void copyRevision(); void diffToPrevious(); void diffRevisions(); void currentRowChanged(const QModelIndex& start, const QModelIndex& end); }; void VcsEventWidgetPrivate::eventViewCustomContextMenuRequested( const QPoint &point ) { m_contextIndex = m_ui->eventView->indexAt( point ); if( !m_contextIndex.isValid() ){ qCDebug(VCS) << "contextMenu is not in TreeView"; return; } QMenu menu( m_ui->eventView ); menu.addAction(m_copyAction); menu.addAction(i18n("Diff to previous revision"), q, SLOT(diffToPrevious())); QAction* action = menu.addAction(i18n("Diff between revisions"), q, SLOT(diffRevisions())); action->setEnabled(m_ui->eventView->selectionModel()->selectedRows().size()>=2); menu.exec( m_ui->eventView->viewport()->mapToGlobal(point) ); } void VcsEventWidgetPrivate::currentRowChanged(const QModelIndex& start, const QModelIndex& end) { Q_UNUSED(end); if(start.isValid()) eventViewClicked(start); } void VcsEventWidgetPrivate::eventViewClicked( const QModelIndex &index ) { KDevelop::VcsEvent ev = m_logModel->eventForIndex( index ); m_detailModel->removeRows(0, m_detailModel->rowCount()); if( ev.revision().revisionType() != KDevelop::VcsRevision::Invalid ) { m_ui->itemEventView->setEnabled(true); m_ui->message->setEnabled(true); m_ui->message->setPlainText( ev.message() ); m_detailModel->addItemEvents( ev.items() ); }else { m_ui->itemEventView->setEnabled(false); m_ui->message->setEnabled(false); m_ui->message->clear(); } QHeaderView* header = m_ui->itemEventView->header(); header->setSectionResizeMode(QHeaderView::ResizeToContents); header->setStretchLastSection(true); } void VcsEventWidgetPrivate::copyRevision() { qApp->clipboard()->setText(m_contextIndex.sibling(m_contextIndex.row(), 0).data().toString()); } void VcsEventWidgetPrivate::diffToPrevious() { KDevelop::VcsEvent ev = m_logModel->eventForIndex( m_contextIndex ); KDevelop::VcsRevision prev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Previous); KDevelop::VcsJob* job = m_iface->diff( m_url, prev, ev.revision() ); VcsDiffWidget* widget = new VcsDiffWidget( job ); widget->setRevisions( prev, ev.revision() ); QDialog* dlg = new QDialog( q ); widget->connect(widget, &VcsDiffWidget::destroyed, dlg, &QDialog::deleteLater); dlg->setWindowTitle( i18n("Difference To Previous") ); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); auto mainWidget = new QWidget; QVBoxLayout *mainLayout = new QVBoxLayout; dlg->setLayout(mainLayout); mainLayout->addWidget(mainWidget); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dlg->connect(buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept); dlg->connect(buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject); mainLayout->addWidget(widget); mainLayout->addWidget(buttonBox); dlg->show(); } void VcsEventWidgetPrivate::diffRevisions() { QModelIndexList l = m_ui->eventView->selectionModel()->selectedRows(); KDevelop::VcsEvent ev1 = m_logModel->eventForIndex( l.first() ); KDevelop::VcsEvent ev2 = m_logModel->eventForIndex( l.last() ); KDevelop::VcsJob* job = m_iface->diff( m_url, ev1.revision(), ev2.revision() ); VcsDiffWidget* widget = new VcsDiffWidget( job ); widget->setRevisions( ev1.revision(), ev2.revision() ); auto dlg = new QDialog( q ); dlg->setWindowTitle( i18n("Difference between Revisions") ); widget->connect(widget, &VcsDiffWidget::destroyed, dlg, &QDialog::deleteLater); auto mainLayout = new QVBoxLayout(dlg); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dlg->connect(buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept); dlg->connect(buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject); mainLayout->addWidget(buttonBox); mainLayout->addWidget(widget); dlg->show(); } VcsEventWidget::VcsEventWidget( const QUrl& url, const VcsRevision& rev, KDevelop::IBasicVersionControl* iface, QWidget* parent ) : QWidget(parent), d(new VcsEventWidgetPrivate(this) ) { d->m_iface = iface; d->m_url = url; d->m_ui = new Ui::VcsEventWidget(); d->m_ui->setupUi(this); - d->m_logModel = new VcsEventModel(iface, rev, url, this); + d->m_logModel = new VcsEventLogModel(iface, rev, url, this); d->m_ui->eventView->setModel( d->m_logModel ); d->m_ui->eventView->sortByColumn(0, Qt::DescendingOrder); d->m_ui->eventView->setContextMenuPolicy( Qt::CustomContextMenu ); QHeaderView* header = d->m_ui->eventView->header(); header->setSectionResizeMode( 0, QHeaderView::ResizeToContents ); header->setSectionResizeMode( 1, QHeaderView::Stretch ); header->setSectionResizeMode( 2, QHeaderView::ResizeToContents ); header->setSectionResizeMode( 3, QHeaderView::ResizeToContents ); // Select first row as soon as the model got populated connect(d->m_logModel, &QAbstractItemModel::rowsInserted, this, [this]() { auto view = d->m_ui->eventView; view->setCurrentIndex(view->model()->index(0, 0)); }); d->m_detailModel = new VcsItemEventModel(this); d->m_ui->itemEventView->setModel( d->m_detailModel ); connect( d->m_ui->eventView, &QTreeView::clicked, this, [&] (const QModelIndex& index) { d->eventViewClicked(index); } ); connect( d->m_ui->eventView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, [&] (const QModelIndex& start, const QModelIndex& end) { d->currentRowChanged(start, end); }); connect( d->m_ui->eventView, &QTreeView::customContextMenuRequested, this, [&] (const QPoint& point) { d->eventViewCustomContextMenuRequested(point); } ); } VcsEventWidget::~VcsEventWidget() { delete d->m_ui; delete d; } } #include "moc_vcseventwidget.cpp"