diff --git a/plugins/patchreview/patchreviewtoolview.cpp b/plugins/patchreview/patchreviewtoolview.cpp index baac4feb5c..2b17d10772 100644 --- a/plugins/patchreview/patchreviewtoolview.cpp +++ b/plugins/patchreview/patchreviewtoolview.cpp @@ -1,601 +1,601 @@ /*************************************************************************** Copyright 2006-2009 David Nolden ***************************************************************************/ /*************************************************************************** * * * 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 "patchreviewtoolview.h" #include "localpatchsource.h" #include "patchreview.h" #include "standardpatchexport.h" #include "libdiff2/diffmodellist.h" #include "libdiff2/komparemodellist.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; class PatchFilesModel : public VcsFileChangesModel { public: PatchFilesModel( QObject *parent, bool allowSelection ) : VcsFileChangesModel( parent, allowSelection ) { }; enum ItemRoles { HunksNumberRole = VcsStatusInfoRole+1 }; public slots: void updateState( const KDevelop::VcsStatusInfo &status, unsigned hunksNum ) { int row = VcsFileChangesModel::updateState( invisibleRootItem(), status ); if ( row == -1 ) return; QStandardItem *item = invisibleRootItem()->child( row, 0 ); setFileInfo( item, hunksNum ); item->setData( QVariant( hunksNum ), HunksNumberRole ); } void updateState( const KDevelop::VcsStatusInfo &status ) { int row = VcsFileChangesModel::updateState( invisibleRootItem(), status ); if ( row == -1 ) return; QStandardItem *item = invisibleRootItem()->child( row, 0 ); setFileInfo( invisibleRootItem()->child( row, 0 ), item->data( HunksNumberRole ).toUInt() ); } private: void setFileInfo( QStandardItem *item, unsigned int hunksNum ) { QString newText = i18ncp( "%1: number of changed hunks, %2: file name", "%2 (1 hunk)", "%2 (%1 hunks)", hunksNum, item->data(VcsFileChangesModel::VcsStatusInfoRole).value().url().pathOrUrl() ); item->setText( newText ); } }; PatchReviewToolView::PatchReviewToolView( QWidget* parent, PatchReviewPlugin* plugin ) : QWidget( parent ), m_resetCheckedUrls( true ), m_plugin( plugin ) { connect( plugin, SIGNAL( patchChanged() ), SLOT( patchChanged() ) ); connect( plugin, SIGNAL( startingNewReview() ), SLOT( startingNewReview() ) ); connect( ICore::self()->documentController(), SIGNAL( documentActivated( KDevelop::IDocument* ) ), this, SLOT( documentActivated( KDevelop::IDocument* ) ) ); showEditDialog(); patchChanged(); } void PatchReviewToolView::startingNewReview() { m_resetCheckedUrls = true; } void PatchReviewToolView::patchChanged() { fillEditFromPatch(); kompareModelChanged(); } PatchReviewToolView::~PatchReviewToolView() { } LocalPatchSource* PatchReviewToolView::GetLocalPatchSource() { IPatchSource::Ptr ips = m_plugin->patch(); if ( !ips ) return 0; return dynamic_cast( ips.data() ); } void PatchReviewToolView::updatePatchFromEdit() { LocalPatchSource* lpatch = GetLocalPatchSource(); if( !lpatch ) return; lpatch->setCommand(m_editPatch.command->text()); lpatch->setFilename(m_editPatch.filename->url()); lpatch->setBaseDir(m_editPatch.baseDir->url()); lpatch->setAlreadyApplied( m_editPatch.applied->checkState() == Qt::Checked ); m_plugin->notifyPatchChanged(); } void PatchReviewToolView::fillEditFromPatch() { IPatchSource::Ptr ipatch = m_plugin->patch(); if ( !ipatch ) return; disconnect( m_editPatch.patchSelection, SIGNAL( currentIndexChanged( int ) ), this, SLOT( patchSelectionChanged( int ) ) ); m_editPatch.patchSelection->clear(); foreach( IPatchSource::Ptr patch, m_plugin->knownPatches() ) { if( !patch ) continue; m_editPatch.patchSelection->addItem( patch->icon(), patch->name() ); if( patch == ipatch ) m_editPatch.patchSelection->setCurrentIndex( m_editPatch.patchSelection->count()-1 ); } connect( m_editPatch.patchSelection, SIGNAL( currentIndexChanged( int ) ), this, SLOT( patchSelectionChanged( int ) ) ); m_editPatch.cancelReview->setVisible( ipatch->canCancel() ); QString finishText = i18n( "Finish Review" ); if( !ipatch->finishReviewCustomText().isEmpty() ) finishText = ipatch->finishReviewCustomText(); kDebug() << "finish-text: " << finishText; m_editPatch.finishReview->setText( finishText ); m_fileModel->setIsCheckbable( m_plugin->patch()->canSelectFiles() ); if( m_customWidget ) { kDebug() << "removing custom widget"; m_customWidget->hide(); m_editPatch.customWidgetsLayout->removeWidget( m_customWidget ); } m_customWidget = ipatch->customWidget(); if( m_customWidget ) { m_editPatch.customWidgetsLayout->insertWidget( 0, m_customWidget ); m_customWidget->show(); kDebug() << "got custom widget"; } bool showTests = false; IProject* project = 0; QMap files = ipatch->additionalSelectableFiles(); QMap::const_iterator it = files.constBegin(); for (; it != files.constEnd(); ++it) { project = ICore::self()->projectController()->findProjectForUrl(it.key()); if (project && !ICore::self()->testController()->testSuitesForProject(project).isEmpty()) { showTests = true; break; } } m_editPatch.testsButton->setVisible(showTests); m_editPatch.testProgressBar->hide(); LocalPatchSource* lpatch = dynamic_cast( ipatch.data() ); m_editPatch.localPatchOptions->setVisible( lpatch ); if( !lpatch ) return; m_editPatch.command->setText( lpatch->command()); m_editPatch.filename->setUrl( lpatch->file() ); m_editPatch.baseDir->setUrl( lpatch->baseDir() ); m_editPatch.applied->setCheckState( lpatch->isAlreadyApplied() ? Qt::Checked : Qt::Unchecked ); if ( lpatch->command().isEmpty() ) m_editPatch.tabWidget->setCurrentIndex( m_editPatch.tabWidget->indexOf( m_editPatch.fileTab ) ); else m_editPatch.tabWidget->setCurrentIndex( m_editPatch.tabWidget->indexOf( m_editPatch.commandTab ) ); } void PatchReviewToolView::patchSelectionChanged( int selection ) { m_fileModel->removeRows( 0, m_fileModel->rowCount() ); if( selection >= 0 && selection < m_plugin->knownPatches().size() ) { m_plugin->setPatch( m_plugin->knownPatches()[selection] ); } } void PatchReviewToolView::slotAppliedChanged( int newState ) { if ( LocalPatchSource* lpatch = GetLocalPatchSource() ) { lpatch->setAlreadyApplied( newState == Qt::Checked ); m_plugin->notifyPatchChanged(); } } void PatchReviewToolView::showEditDialog() { m_editPatch.setupUi( this ); m_fileModel = new PatchFilesModel( this, m_plugin->patch()->canSelectFiles() ); m_editPatch.filesList->setModel( m_fileModel ); m_editPatch.filesList->header()->hide(); m_editPatch.filesList->setRootIsDecorated( false ); m_editPatch.filesList->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_editPatch.filesList, SIGNAL(customContextMenuRequested(QPoint)), SLOT(customContextMenuRequested(QPoint))); connect(m_fileModel, SIGNAL(itemChanged(QStandardItem*)), SLOT(fileItemChanged(QStandardItem*))); m_editPatch.previousFile->setIcon( KIcon( "arrow-left" ) ); m_editPatch.previousHunk->setIcon( KIcon( "arrow-up" ) ); m_editPatch.nextHunk->setIcon( KIcon( "arrow-down" ) ); m_editPatch.nextFile->setIcon( KIcon( "arrow-right" ) ); m_editPatch.cancelReview->setIcon( KIcon( "dialog-cancel" ) ); m_editPatch.finishReview->setIcon( KIcon( "dialog-ok" ) ); m_editPatch.updateButton->setIcon( KIcon( "view-refresh" ) ); m_editPatch.testsButton->setIcon( KIcon( "preflight-verifier" ) ); QMenu* exportMenu = new QMenu( m_editPatch.exportReview ); StandardPatchExport* stdactions = new StandardPatchExport( m_plugin, this ); stdactions->addActions( exportMenu ); connect( exportMenu, SIGNAL( triggered( QAction* ) ), m_plugin, SLOT( exporterSelected( QAction* ) ) ); IPluginController* pluginManager = ICore::self()->pluginController(); foreach( IPlugin* p, pluginManager->allPluginsForExtension( "org.kdevelop.IPatchExporter" ) ) { KPluginInfo info = pluginManager->pluginInfo( p ); QAction* action = exportMenu->addAction( KIcon( info.icon() ), info.name() ); action->setData( qVariantFromValue( p ) ); } m_editPatch.exportReview->setMenu( exportMenu ); connect( m_editPatch.previousHunk, SIGNAL( clicked( bool ) ), this, SLOT( prevHunk() ) ); connect( m_editPatch.nextHunk, SIGNAL( clicked( bool ) ), this, SLOT( nextHunk() ) ); connect( m_editPatch.previousFile, SIGNAL( clicked( bool ) ), this, SLOT( prevFile() ) ); connect( m_editPatch.nextFile, SIGNAL( clicked( bool ) ), this, SLOT( nextFile() ) ); connect( m_editPatch.filesList, SIGNAL( activated ( QModelIndex ) ), this, SLOT( fileDoubleClicked( QModelIndex ) ) ); connect( m_editPatch.cancelReview, SIGNAL( clicked( bool ) ), m_plugin, SLOT( cancelReview() ) ); connect( m_editPatch.finishReview, SIGNAL( clicked( bool ) ), this, SLOT( finishReview() ) ); //connect( m_editPatch.cancelButton, SIGNAL(pressed()), this, SLOT(slotEditCancel()) ); //connect( this, SIGNAL(finished(int)), this, SLOT(slotEditDialogFinished(int)) ); connect( m_editPatch.applied, SIGNAL( stateChanged( int ) ), SLOT( updatePatchFromEdit() ) ); connect( m_editPatch.filename, SIGNAL( textChanged( QString ) ), SLOT( updatePatchFromEdit() ) ); m_editPatch.baseDir->setMode( KFile::Directory ); connect( m_editPatch.command, SIGNAL( textChanged( QString ) ), this, SLOT( updatePatchFromEdit() ) ); // connect( m_editPatch.commandToFile, SIGNAL(clicked(bool)), this, SLOT(slotToFile()) ); connect( m_editPatch.filename->lineEdit(), SIGNAL( returnPressed() ), this, SLOT( updatePatchFromEdit() ) ); connect( m_editPatch.filename->lineEdit(), SIGNAL( editingFinished() ), this, SLOT( updatePatchFromEdit() ) ); connect( m_editPatch.filename, SIGNAL( urlSelected( KUrl ) ), this, SLOT( updatePatchFromEdit() ) ); connect( m_editPatch.command, SIGNAL( textChanged( QString ) ), this, SLOT( updatePatchFromEdit() ) ); // connect( m_editPatch.commandToFile, SIGNAL(clicked(bool)), m_plugin, SLOT(commandToFile()) ); connect( m_editPatch.patchSelection, SIGNAL( currentIndexChanged( int ) ), this, SLOT( patchSelectionChanged( int ) ) ); connect( m_editPatch.updateButton, SIGNAL( clicked( bool ) ), m_plugin, SLOT( forceUpdate() ) ); connect( m_editPatch.showButton, SIGNAL( clicked( bool ) ), m_plugin, SLOT( updateReview()) ); connect( m_editPatch.testsButton, SIGNAL( clicked( bool ) ), this, SLOT( runTests() ) ); m_selectAllAction = new QAction( KIcon("ok"), i18n("Select All"), this ); connect( m_selectAllAction, SIGNAL(triggered(bool)), SLOT(selectAll()) ); m_deselectAllAction = new QAction( KIcon("cancel"), i18n("Deselect All"), this ); connect( m_deselectAllAction, SIGNAL(triggered(bool)), SLOT(deselectAll()) ); } void PatchReviewToolView::customContextMenuRequested(const QPoint& ) { KUrl::List urls; QModelIndexList selectionIdxs = m_editPatch.filesList->selectionModel()->selectedIndexes(); if(selectionIdxs.isEmpty()) return; foreach(const QModelIndex& idx, selectionIdxs) { if(idx.column()==0) { urls += idx.data(KDevelop::VcsFileChangesModel::VcsStatusInfoRole).value().url(); } } QPointer menu = new QMenu(m_editPatch.filesList); QList extensions; if(!urls.isEmpty()) { KDevelop::FileContext context(urls); extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &context ); } QList vcsActions; foreach( const ContextMenuExtension& ext, extensions ) { vcsActions += ext.actions(ContextMenuExtension::VcsGroup); } menu->addAction(m_selectAllAction); menu->addAction(m_deselectAllAction); menu->addActions(vcsActions); if ( !menu->isEmpty() ) { menu->exec(QCursor::pos()); } delete menu; } void PatchReviewToolView::nextHunk() { IDocument* current = ICore::self()->documentController()->activeDocument(); if(current && current->textDocument()) m_plugin->seekHunk( true, current->textDocument()->url() ); } void PatchReviewToolView::prevHunk() { IDocument* current = ICore::self()->documentController()->activeDocument(); if(current && current->textDocument()) m_plugin->seekHunk( false, current->textDocument()->url() ); } void PatchReviewToolView::seekFile(bool forwards) { if(!m_plugin->patch()) return; QList checkedUrls = m_fileModel->checkedUrls(); QList allUrls = m_fileModel->urls(); IDocument* current = ICore::self()->documentController()->activeDocument(); if(!current || checkedUrls.empty()) return; kDebug() << "seeking direction" << forwards; int currentIndex = allUrls.indexOf(current->url()); KUrl newUrl; if((forwards && current->url() == checkedUrls.back()) || (!forwards && current->url() == checkedUrls[0])) { newUrl = m_plugin->patch()->file(); kDebug() << "jumping to patch"; } else if(current->url() == m_plugin->patch()->file() || currentIndex == -1) { if(forwards) newUrl = checkedUrls[0]; else newUrl = checkedUrls.back(); kDebug() << "jumping from patch"; } else { QSet checkedUrlsSet( checkedUrls.toSet() ); for(int offset = 1; offset < allUrls.size(); ++offset) { int pos; if(forwards) { pos = (currentIndex + offset) % allUrls.size(); }else{ pos = currentIndex - offset; if(pos < 0) pos += allUrls.size(); } if(checkedUrlsSet.contains(allUrls[pos])) { newUrl = allUrls[pos]; break; } } } if(newUrl.isValid()) { activate( newUrl, forwards ? current : 0 ); }else{ kDebug() << "found no valid target url"; } } void PatchReviewToolView::activate( KUrl url, IDocument* buddy ) const { kDebug() << "activating url" << url; // If the document is already open in this area, just re-activate it if(KDevelop::IDocument* doc = ICore::self()->documentController()->documentForUrl(url)) { foreach(Sublime::View* view, ICore::self()->uiController()->activeArea()->views()) { if(view->document() == dynamic_cast(doc)) { ICore::self()->documentController()->activateDocument(doc); return; } } } // If the document is not open yet, open it in the correct order IDocument* newDoc = ICore::self()->documentController()->openDocument(url, KTextEditor::Range(), IDocumentController::DefaultMode, "", buddy); if(newDoc && newDoc->textDocument()->activeView() && newDoc->textDocument()->activeView()->cursorPosition().line() == 0) m_plugin->seekHunk( true, url ); } void PatchReviewToolView::fileItemChanged( QStandardItem* item ) { KUrl url = m_fileModel->statusInfo(item).url(); - if(item->checkState() == Qt::Unchecked) + if(!m_fileModel->checkedUrls().contains(url)) { // Eventually close the document if(KDevelop::IDocument* doc = ICore::self()->documentController()->documentForUrl(url)) { if(doc->state() == IDocument::Clean) { foreach(Sublime::View* view, ICore::self()->uiController()->activeArea()->views()) { if(view->document() == dynamic_cast(doc)) { kDebug() << "closing view of" << url << "because the item was unchecked"; ICore::self()->uiController()->activeArea()->closeView(view); return; } } } } } } void PatchReviewToolView::nextFile() { seekFile(true); } void PatchReviewToolView::prevFile() { seekFile(false); } void PatchReviewToolView::deselectAll() { m_fileModel->setAllChecked(false); } void PatchReviewToolView::selectAll() { m_fileModel->setAllChecked(true); } void PatchReviewToolView::finishReview() { QList selectedUrls = m_fileModel->checkedUrls(); kDebug() << "finishing review with" << selectedUrls; m_plugin->finishReview( selectedUrls ); } void PatchReviewToolView::fileDoubleClicked( const QModelIndex& idx ) { QModelIndex i = idx.sibling(idx.row(), 0); KUrl file = m_fileModel->statusInfo( i ).url(); activate( file ); } KUrl PatchReviewPlugin::urlForFileModel( const Diff2::DiffModel* model ) { KUrl file = m_patch->baseDir(); file.addPath( model->destinationPath() ); file.addPath( model->destinationFile() ); return file; } void PatchReviewToolView::kompareModelChanged() { QList oldCheckedUrls = m_fileModel->checkedUrls(); m_fileModel->clear(); if ( !m_plugin->modelList() ) return; QMap additionalUrls = m_plugin->patch()->additionalSelectableFiles(); const Diff2::DiffModelList* models = m_plugin->modelList()->models(); if( models ) { Diff2::DiffModelList::const_iterator it = models->constBegin(); for(; it != models->constEnd(); ++it ) { Diff2::DifferenceList * diffs = ( *it )->differences(); int cnt = 0; if ( diffs ) cnt = diffs->count(); KUrl file = m_plugin->urlForFileModel( *it ); if( !QFileInfo( file.toLocalFile() ).isReadable() ) continue; VcsStatusInfo status; status.setUrl( file ); status.setState( VcsStatusInfo::ItemModified ); m_fileModel->updateState( status, cnt ); } } for( QMap::const_iterator it = additionalUrls.constBegin(); it != additionalUrls.constEnd(); it++ ) { VcsStatusInfo status; status.setUrl( it.key() ); status.setState( it.value() ); m_fileModel->updateState( status ); } if(!m_resetCheckedUrls) m_fileModel->setCheckedUrls(oldCheckedUrls); else m_resetCheckedUrls = false; m_editPatch.filesList->resizeColumnToContents( 0 ); // Eventually select the active document documentActivated( ICore::self()->documentController()->activeDocument() ); } void PatchReviewToolView::documentActivated( IDocument* doc ) { if( !doc ) return; if ( !m_plugin->modelList() ) return; QStandardItem *fileItem = m_fileModel->fileItemForUrl( doc->url() ); if ( fileItem ) { m_editPatch.filesList->setCurrentIndex( fileItem->index() ); } else { m_editPatch.filesList->setCurrentIndex( QModelIndex() ); } } void PatchReviewToolView::runTests() { IPatchSource::Ptr ipatch = m_plugin->patch(); if ( !ipatch ) { return; } IProject* project = 0; QMap files = ipatch->additionalSelectableFiles(); QMap::const_iterator it = files.constBegin(); for (; it != files.constEnd(); ++it) { project = ICore::self()->projectController()->findProjectForUrl(it.key()); if (project) { break; } } if (!project) { return; } m_editPatch.testProgressBar->setFormat(i18n("Running tests: %p%")); m_editPatch.testProgressBar->setValue(0); m_editPatch.testProgressBar->show(); ProjectTestJob* job = new ProjectTestJob(project, this); connect (job, SIGNAL(finished(KJob*)), SLOT(testJobResult(KJob*))); connect (job, SIGNAL(percent(KJob*,ulong)), SLOT(testJobPercent(KJob*,ulong))); ICore::self()->runController()->registerJob(job); } void PatchReviewToolView::testJobPercent(KJob* job, ulong percent) { Q_UNUSED(job); m_editPatch.testProgressBar->setValue(percent); } void PatchReviewToolView::testJobResult(KJob* job) { ProjectTestJob* testJob = qobject_cast(job); if (!testJob) { return; } ProjectTestResult result = testJob->testResult(); QString format; if (result.passed > 0 && result.failed == 0 && result.error == 0) { format = i18np("Test passed", "All %1 tests passed", result.passed); } else { format = i18n("Test results: %1 passed, %2 failed, %3 errors", result.passed, result.failed, result.error); } m_editPatch.testProgressBar->setFormat(format); // Needed because some test jobs may raise their own output views ICore::self()->uiController()->raiseToolView(this); }