diff --git a/plugins/patchreview/CMakeLists.txt b/plugins/patchreview/CMakeLists.txt index 222e28f7c9..ddd01b77d8 100644 --- a/plugins/patchreview/CMakeLists.txt +++ b/plugins/patchreview/CMakeLists.txt @@ -1,46 +1,46 @@ #add_subdirectory(libdiff2) ########### next target ############### add_definitions( ${KDE4_ENABLE_EXCEPTIONS}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/settings) set(patchreview_PART_SRCS patchreview.cpp localpatchsource.cpp standardpatchexport.cpp settings/diffsettings.cpp settings/settingsbase.cpp libdiff2/kompareprocess.cpp libdiff2/komparemodellist.cpp libdiff2/diffmodellist.cpp libdiff2/diffmodel.cpp libdiff2/difference.cpp libdiff2/diffhunk.cpp libdiff2/parser.cpp libdiff2/parserbase.cpp libdiff2/cvsdiffparser.cpp libdiff2/diffparser.cpp libdiff2/perforceparser.cpp libdiff2/stringlistpair.cpp ) set (patchreview_UI patchreview.ui) kde4_add_ui_files(patchreview_PART_SRCS ${patchreview_UI} ) kde4_add_plugin(kdevpatchreview ${patchreview_PART_SRCS}) -target_link_libraries(kdevpatchreview ${KDE4_KDEUI_LIBS} ${KDE4_KTEXTEDITOR_LIBS} ${KDE4_KPARTS_LIBS} kdevplatforminterfaces kdevplatformutil kdevplatformlanguage ${KDE4_KDE3SUPPORT_LIBS} sublime) +target_link_libraries(kdevpatchreview ${KDE4_KDEUI_LIBS} ${KDE4_KTEXTEDITOR_LIBS} ${KDE4_KPARTS_LIBS} kdevplatforminterfaces kdevplatformutil kdevplatformlanguage ${KDEVPLATFORM_VCS_LIBRARIES} ${KDE4_KDE3SUPPORT_LIBS} sublime) install(TARGETS kdevpatchreview DESTINATION ${PLUGIN_INSTALL_DIR}) ########### install files ############### install(FILES kdevpatchreview.desktop DESTINATION ${SERVICES_INSTALL_DIR}) install(FILES kdevpatchreview.rc DESTINATION ${DATA_INSTALL_DIR}/kdevpatchreview) add_subdirectory(libdiff2) diff --git a/plugins/patchreview/patchreview.cpp b/plugins/patchreview/patchreview.cpp index 28118698da..e80cdb187c 100644 --- a/plugins/patchreview/patchreview.cpp +++ b/plugins/patchreview/patchreview.cpp @@ -1,1777 +1,1660 @@ /*************************************************************************** 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 "patchreview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libdiff2/komparemodellist.h" #include "libdiff2/kompare.h" #include #include #include #include #include #include #include #include #include #include #include #include ///Whether arbitrary exceptions that occurred while diff-parsing within the library should be caught #define CATCHLIBDIFF /* Exclude this file from doublequote_chars check as krazy doesn't understand std::string*/ //krazy:excludeall=doublequote_chars #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "diffsettings.h" #include #include #include "standardpatchexport.h" #include +#include +#include using namespace KDevelop; namespace { // Maximum number of files to open directly within a tab when the review is started const int maximumFilesToOpenDirectly = 15; } Q_DECLARE_METATYPE( const Diff2::DiffModel* ) + +class PatchFilesModel : public VcsFileChangesModel +{ +public: + PatchFilesModel(QObject *parent, bool allowSelection = false) : 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->text()); + item->setText(newText); + } +}; + + + PatchReviewToolView::PatchReviewToolView( QWidget* parent, PatchReviewPlugin* plugin ) : QWidget( parent ), m_reversed( false ), m_plugin( plugin ) { connect( plugin, SIGNAL(patchChanged()), SLOT(patchChanged()) ); connect( ICore::self()->documentController(), SIGNAL(documentActivated(KDevelop::IDocument*)), this, SLOT(documentActivated(KDevelop::IDocument*)) ); showEditDialog(); patchChanged(); } 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->m_command = m_editPatch.command->text(); lpatch->m_filename = m_editPatch.filename->url(); lpatch->m_baseDir = m_editPatch.baseDir->url(); lpatch->m_depth = m_editPatch.depth->value(); 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); if(m_customWidget) { kDebug() << "removing custom widget"; m_customWidget->hide(); m_editPatch.verticalLayout->removeWidget(m_customWidget); } m_customWidget = ipatch->customWidget(); if(m_customWidget) { m_editPatch.verticalLayout->insertWidget(0, m_customWidget); m_customWidget->show(); kDebug() << "got custom widget"; } LocalPatchSource* lpatch = dynamic_cast(ipatch.data()); if(!lpatch) { m_editPatch.tabWidget->hide(); m_editPatch.baseDir->hide(); m_editPatch.label->hide(); m_editPatch.depth->hide(); m_editPatch.depthLabel->hide(); m_editPatch.applied->hide(); return; }else{ m_editPatch.tabWidget->show(); m_editPatch.baseDir->show(); m_editPatch.label->show(); m_editPatch.depth->show(); m_editPatch.depthLabel->show(); m_editPatch.applied->show(); } m_editPatch.command->setText( lpatch->m_command ); m_editPatch.filename->setUrl( lpatch->m_filename ); m_editPatch.baseDir->setUrl( lpatch->m_baseDir ); m_editPatch.depth->setValue( lpatch->m_depth ); if (lpatch->isAlreadyApplied()) { m_editPatch.applied->setCheckState(Qt::Checked); } else { m_editPatch.applied->setCheckState(Qt::Unchecked); } if ( lpatch->m_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_editPatch.filesList->clear(); + m_fileModel->removeRows(0, m_fileModel->rowCount()); if(selection >= 0 && selection < m_plugin->knownPatches().size()) { m_plugin->setPatch(m_plugin->knownPatches()[selection]); } } void PatchReviewToolView::slotDepthChanged(int newDepth) { if (LocalPatchSource* lpatch = GetLocalPatchSource()) { lpatch->m_depth = newDepth; m_plugin->notifyPatchChanged(); } } void PatchReviewToolView::slotAppliedChanged(int newState) { if (LocalPatchSource* lpatch = GetLocalPatchSource()) { lpatch->setAlreadyApplied(newState == Qt::Checked); m_plugin->notifyPatchChanged(); } } void PatchReviewToolView::slotEditCommandChanged() { // m_editPatch.filename->lineEdit()->setText( "" ); updatePatchFromEdit(); } void PatchReviewToolView::slotEditFileNameChanged() { // m_editPatch.command->setText( "" ); updatePatchFromEdit(); } void PatchReviewToolView::showEditDialog() { m_editPatch.setupUi( this ); + m_fileModel = new PatchFilesModel(this, true); + m_editPatch.filesList->setModel(m_fileModel); m_editPatch.filesList->header()->hide(); m_editPatch.filesList->setRootIsDecorated(false); m_editPatch.previousHunk->setIcon(KIcon("arrow-up")); m_editPatch.nextHunk->setIcon(KIcon("arrow-down")); m_editPatch.cancelReview->setIcon(KIcon("dialog-cancel")); m_editPatch.finishReview->setIcon(KIcon("dialog-ok")); 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.filesList, SIGNAL( doubleClicked( const QModelIndex& ) ), this, SLOT( fileDoubleClicked( const 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.depth, SIGNAL(valueChanged(int)), SLOT(slotDepthChanged(int)) ); connect( m_editPatch.applied, SIGNAL(stateChanged(int)), SLOT(slotAppliedChanged(int)) ); connect( m_editPatch.filename, SIGNAL( textChanged( const QString& ) ), SLOT(slotEditFileNameChanged()) ); connect( m_editPatch.baseDir, SIGNAL(textChanged(QString)), SLOT(updatePatchFromEdit()) ); m_editPatch.baseDir->setMode(KFile::Directory); connect( m_editPatch.command, SIGNAL( textChanged( const QString& ) ), this, SLOT(slotEditCommandChanged()) ); // connect( m_editPatch.commandToFile, SIGNAL( clicked( bool ) ), this, SLOT( slotToFile() ) ); connect( m_editPatch.filename->lineEdit(), SIGNAL( returnPressed() ), this, SLOT(slotEditFileNameChanged()) ); connect( m_editPatch.filename->lineEdit(), SIGNAL( editingFinished() ), this, SLOT(slotEditFileNameChanged()) ); connect( m_editPatch.filename, SIGNAL( urlSelected( const KUrl& ) ), this, SLOT(slotEditFileNameChanged()) ); connect( m_editPatch.command, SIGNAL(textChanged(QString)), this, SLOT(slotEditCommandChanged()) ); // 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(showPatch()) ); } void PatchReviewToolView::nextHunk() { // updateKompareModel(); m_plugin->seekHunk( true ); } void PatchReviewToolView::prevHunk() { // updateKompareModel(); m_plugin->seekHunk( false ); } KUrl PatchReviewPlugin::diffFile() { return m_patch->file(); } void PatchReviewPlugin::seekHunk( bool forwards, const KUrl& fileName ) { try { if ( !m_modelList.get() ) throw "no model"; for (int a = 0; a < m_modelList->modelCount(); ++a) { const Diff2::DiffModel* model = m_modelList->modelAt(a); if ( !model || !model->differences() ) continue; KUrl file = urlForFileModel( model ); if ( !fileName.isEmpty() && fileName != file ) continue; IDocument* doc = ICore::self()->documentController()->documentForUrl( file ); if ( doc && doc == ICore::self()->documentController()->activeDocument() && m_highlighters.contains(doc->url()) && m_highlighters[doc->url()] ) { ICore::self()->documentController()->activateDocument( doc ); if ( doc->textDocument() ) { KTextEditor::MovingInterface* moving = dynamic_cast( doc->textDocument() ); Q_ASSERT(moving); const QList< KTextEditor::MovingRange* > ranges = m_highlighters[doc->url()]->ranges(); KTextEditor::View * v = doc->textDocument() ->activeView(); int bestLine = -1; if ( v ) { KTextEditor::Cursor c = v->cursorPosition(); for ( QList< KTextEditor::MovingRange* >::const_iterator it = ranges.begin(); it != ranges.end(); ++it ) { int line; line = (*it)->start().line(); if ( forwards ) { if ( line > c.line() && ( bestLine == -1 || line < bestLine ) ) bestLine = line; } else { if ( line < c.line() && ( bestLine == -1 || line > bestLine ) ) bestLine = line; } } if ( bestLine != -1 ) { v->setCursorPosition( KTextEditor::Cursor( bestLine, 0 ) ); return ; } } } } } } catch ( const QString & str ) { kDebug() << "seekHunk():" << str; } catch ( const char * str ) { kDebug() << "seekHunk():" << str; } kDebug() << "no matching hunk found"; } void PatchReviewPlugin::addHighlighting(const KUrl& highlightFile, IDocument* document) { try { if ( !modelList() ) throw "no model"; for (int a = 0; a < modelList()->modelCount(); ++a) { Diff2::DiffModel* model = modelList()->modelAt(a); if ( !model ) continue; KUrl file = urlForFileModel( model ); if (file != highlightFile) continue; kDebug() << "highlighting" << file.prettyUrl(); IDocument* doc = document; if(!doc) doc = ICore::self()->documentController()->documentForUrl( file ); kDebug() << "highlighting file" << file << "with doc" << doc; if ( !doc || !doc->textDocument() ) continue; removeHighlighting( file ); m_highlighters[ file ] = new PatchHighlighter( model, doc, this ); } } catch ( const QString & str ) { kDebug() << "highlightFile():" << str; } catch ( const char * str ) { kDebug() << "highlightFile():" << str; } } void PatchReviewPlugin::highlightPatch() { try { if ( !modelList() ) throw "no model"; for (int a = 0; a < modelList()->modelCount(); ++a) { const Diff2::DiffModel* model = modelList()->modelAt(a); if ( !model ) continue; KUrl file = urlForFileModel( model ); addHighlighting(file); } } catch ( const QString & str ) { kDebug() << "highlightFile():" << str; } catch ( const char * str ) { kDebug() << "highlightFile():" << str; } } void PatchReviewToolView::finishReview() { - QList selectedUrls; - for(int a = 0; a< m_editPatch.filesList->topLevelItemCount(); ++a) { - QTreeWidgetItem* item = m_editPatch.filesList->topLevelItem(a); - if(item && item->checkState(0) == Qt::Checked) { - QVariant v = item->data(0, Qt::UserRole); - - if( v.canConvert() ) { - selectedUrls << v.value(); - }else if ( v.canConvert() ) { - const Diff2::DiffModel* model = v.value(); - - KUrl file = m_plugin->urlForFileModel( model ); - - selectedUrls << file; - } - } - } + QList selectedUrls = m_fileModel->checkedUrls(); kDebug() << "finishing review with" << selectedUrls; m_plugin->finishReview(selectedUrls); } void PatchReviewToolView::fileDoubleClicked( const QModelIndex& i ) { - try { - if ( !m_plugin->modelList() ) - throw "no model"; - - QVariant v = i.data( Qt::UserRole ); - - if( v.canConvert() ) { - KUrl u = v.value(); - ICore::self()->documentController()->openDocument( u, KTextEditor::Cursor() ); - return; - } - - if ( !v.canConvert() ) - throw "cannot convert"; - const Diff2::DiffModel* model = v.value(); - if ( !model ) - throw "bad model-value"; + KUrl file = m_fileModel->statusInfo( i ).url(); - KUrl file = m_plugin->urlForFileModel( model ); + kDebug() << "opening" << file.toLocalFile(); - kDebug() << "opening" << file.toLocalFile(); + ICore::self()->documentController()->openDocument( file, KTextEditor::Cursor() ); - ICore::self()->documentController()->openDocument( file, KTextEditor::Cursor() ); - - m_plugin->seekHunk( true, file ); - } catch ( const QString & str ) { - kDebug() << "fileDoubleClicked():" << str; - } catch ( const char * str ) { - kDebug() << "fileDoubleClicked():" << str; - } + m_plugin->seekHunk( true, file ); } KUrl PatchReviewPlugin::urlForFileModel(const Diff2::DiffModel* model) { KUrl file = m_patch->baseDir(); file.addPath( model->destinationPath() ); file.addPath( model->destinationFile() ); return file; } -static QString stateToString(KDevelop::VcsStatusInfo::State state) -{ - switch(state) - { - case KDevelop::VcsStatusInfo::ItemAdded: - return i18nc("VCS file status", "Added"); - case KDevelop::VcsStatusInfo::ItemDeleted: - return i18nc("VCS file status", "Deleted"); - case KDevelop::VcsStatusInfo::ItemHasConflicts: - return i18nc("VCS file status", "Has Conflicts"); - case KDevelop::VcsStatusInfo::ItemModified: - return i18nc("VCS file status", "Modified"); - case KDevelop::VcsStatusInfo::ItemUpToDate: - return i18nc("VCS file status", "Up To Date"); - case KDevelop::VcsStatusInfo::ItemUnknown: - case KDevelop::VcsStatusInfo::ItemUserState: - return i18nc("VCS file status", "Unknown"); - } - return i18nc("Unknown VCS file status, probably a backend error", "?"); -} - -static KIcon stateToIcon(KDevelop::VcsStatusInfo::State state) -{ - switch(state) - { - case KDevelop::VcsStatusInfo::ItemAdded: - return KIcon("vcs-added"); - case KDevelop::VcsStatusInfo::ItemDeleted: - return KIcon("vcs-removed"); - case KDevelop::VcsStatusInfo::ItemHasConflicts: - return KIcon("vcs-conflicting"); - case KDevelop::VcsStatusInfo::ItemModified: - return KIcon("vcs-locally-modified"); - case KDevelop::VcsStatusInfo::ItemUpToDate: - return KIcon("vcs-normal"); - case KDevelop::VcsStatusInfo::ItemUnknown: - case KDevelop::VcsStatusInfo::ItemUserState: - return KIcon("unknown"); - } - return KIcon("dialog-error"); -} - void PatchReviewToolView::kompareModelChanged() { - m_editPatch.filesList->clear(); - m_editPatch.filesList->setColumnCount(1); + m_fileModel->removeRows(0, m_fileModel->rowCount()); if (!m_plugin->modelList()) return; QMap additionalUrls = m_plugin->patch()->additionalSelectableFiles(); - QSet haveUrls; - 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); - haveUrls.insert(file); - if(!QFileInfo(file.toLocalFile()).isReadable()) continue; - - QTreeWidgetItem* item = new QTreeWidgetItem(m_editPatch.filesList); - - m_editPatch.filesList->insertTopLevelItem(0, item); - - const QString filenameArgument = ICore::self()->projectController()->prettyFileName(file, KDevelop::IProjectController::FormatPlain); - - QString text; - QIcon icon; - if(additionalUrls.contains(file)) { - text = i18ncp("%1: number of changed hunks, %2: file name, %3: vcs file state", - "%2 (1 hunk, %3)", "%2 (%1 hunks, %3)", cnt, filenameArgument, stateToString(additionalUrls[file])); - icon = stateToIcon(additionalUrls[file]); - } else { - text = i18ncp("%1: number of changed hunks, %2: file name", - "%2 (1 hunk)", "%2 (%1 hunks)", cnt, filenameArgument); - } - item->setData( 0, Qt::DisplayRole, text ); - item->setIcon( 0, icon ); - item->setData( 0, Qt::UserRole, qVariantFromValue(*it)); - item->setCheckState( 0, Qt::Checked ); - } - } - - // Maps the _really_ useful items (with VCS state) to index 0, - // the items that have at least a project found to 1, - // and the probably really useless items without project found to 2. - // The project-manager filters useless stuff like backups out so they get index 2. - QMap > > newItems; - - for(QMap::const_iterator it = additionalUrls.constBegin(); it != additionalUrls.constEnd(); ++it) - { - KUrl url = it.key(); - - if(!haveUrls.contains(url)) - { - haveUrls.insert(url); - - if(*it != KDevelop::VcsStatusInfo::ItemUnknown) - { - newItems[0] << qMakePair(url, *it); - }else{ - if(((bool)ICore::self()->projectController()->findProjectForUrl(url))) - { - newItems[1] << qMakePair(url, *it); - }else{ - newItems[2] << qMakePair(url, *it); - } - } + VcsStatusInfo status; + status.setUrl(file); + status.setState(VcsStatusInfo::ItemModified); + + m_fileModel->updateState(status, cnt); } } - - for(int a = 0; a < 3; ++a) - { - for(QList< QPair< KUrl, KDevelop::VcsStatusInfo::State > >::iterator itemIt = newItems[a].begin(); itemIt != newItems[a].end(); ++itemIt) - { - KUrl url = itemIt->first; - KDevelop::VcsStatusInfo::State state = itemIt->second; - - QTreeWidgetItem* item = new QTreeWidgetItem(m_editPatch.filesList); - - QString text = ICore::self()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain); - text += " (" + stateToString(state) + ")"; - - item->setData( 0, Qt::DisplayRole, text ); - QVariant v; - v.setValue( url ); - item->setData( 0, Qt::UserRole, v ); - item->setIcon( 0, stateToIcon(state) ); - item->setCheckState( 0, Qt::Unchecked ); - - if(a == 0) - item->setCheckState( 0, Qt::Checked ); - - m_editPatch.filesList->addTopLevelItem(item); - } + + 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); } + + m_editPatch.filesList->resizeColumnToContents(0); } void PatchReviewToolView::documentActivated(IDocument* doc) { QModelIndexList i = m_editPatch.filesList->selectionModel() ->selectedIndexes(); if ( !m_plugin->modelList() ) return ; - for(int a = 0; a < m_editPatch.filesList->topLevelItemCount(); ++a) { - - QTreeWidgetItem* item = m_editPatch.filesList->topLevelItem(a); - - QVariant v = item->data( 0, Qt::UserRole ); - if ( v.canConvert() ) { - const Diff2::DiffModel * model = v.value(); - - KUrl file = m_plugin->urlForFileModel(model); - - if(file == doc->url()) { - m_editPatch.filesList->setCurrentItem(item); - return; - } - } + QStandardItem *fileItem = m_fileModel->fileItemForUrl(doc->url()); + if (fileItem) { + m_editPatch.filesList->setCurrentIndex(fileItem->index()); + } else { + m_editPatch.filesList->setCurrentIndex(QModelIndex()); } - m_editPatch.filesList->setCurrentIndex(QModelIndex()); } void PatchHighlighter::aboutToDeleteMovingInterfaceContent(KTextEditor::Document* ) { kDebug() << "about to delete"; clear(); } QSize sizeHintForHtml(QString html, QSize maxSize) { QTextDocument doc; doc.setHtml(html); QSize ret; if(doc.idealWidth() > maxSize.width()) { doc.setPageSize( QSize(maxSize.width(), 30) ); ret.setWidth(maxSize.width()); }else{ ret.setWidth(doc.idealWidth()); } ret.setHeight(doc.size().height()); if(ret.height() > maxSize.height()) ret.setHeight(maxSize.height()); return ret; } namespace { QPointer currentTooltip; KTextEditor::MovingRange* currentTooltipMark; } void PatchHighlighter::showToolTipForMark(QPoint pos, KTextEditor::MovingRange* markRange, QPair< int, int > highlightMark) { if(currentTooltipMark == markRange && currentTooltip) return; delete currentTooltip; //Got the difference Diff2::Difference* diff = m_differencesForRanges[markRange]; QString html; #if 0 if(diff->hasConflict()) html += i18n("Conflict
"); #endif Diff2::DifferenceStringList lines; if(m_plugin->patch()->isAlreadyApplied() && !diff->applied()) html += i18n("Reverted.
"); else if(!m_plugin->patch()->isAlreadyApplied() && diff->applied()) html += i18n("Applied.
"); if(diff->applied()) { if(isInsertion(diff)) { html += i18n("Insertion
"); }else{ if(isRemoval(diff)) html += i18n("Removal
"); html += i18n("Previous:
"); lines = diff->sourceLines(); } }else{ if(isRemoval(diff)) { html += i18n("Removal
"); }else{ if(isInsertion(diff)) html += i18n("Insertion
"); html += i18n("Alternative:
"); lines = diff->destinationLines(); } } for(int a = 0; a < lines.size(); ++a) { Diff2::DifferenceString* line = lines[a]; uint currentPos = 0; QString string = line->string(); Diff2::MarkerList markers = line->markerList(); for(int b = 0; b < markers.size(); ++b) { QString spanText = Qt::escape(string.mid(currentPos, markers[b]->offset() - currentPos)); if(markers[b]->type() == Diff2::Marker::End && (currentPos != 0 || markers[b]->offset() != string.size())) { if(a == highlightMark.first && b == highlightMark.second) html += "" + spanText + ""; else html += "" + spanText + ""; }else{ html += spanText; } currentPos = markers[b]->offset(); } html += Qt::escape(string.mid(currentPos, string.length()-currentPos)); html += "
"; } KTextBrowser* browser = new KTextBrowser; browser->setPalette( QApplication::palette() ); browser->setHtml(html); int maxHeight = 500; browser->setMinimumSize(sizeHintForHtml(html, QSize((ICore::self()->uiController()->activeMainWindow()->width()*2)/3, maxHeight))); browser->setMaximumSize(browser->minimumSize() + QSize(10, 10)); if(browser->minimumHeight() != maxHeight) browser->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); QVBoxLayout* layout = new QVBoxLayout; layout->setMargin(0); layout->addWidget(browser); KDevelop::ActiveToolTip* tooltip = new KDevelop::ActiveToolTip(ICore::self()->uiController()->activeMainWindow(), pos + QPoint(5, -browser->sizeHint().height() - 30)); tooltip->setLayout(layout); tooltip->resize( tooltip->sizeHint() + QSize(10, 10) ); tooltip->move(pos - QPoint(0, 20 + tooltip->height())); tooltip->addExtendRect(QRect(pos - QPoint(15, 15), pos + QPoint(15, 15))); currentTooltip = tooltip; currentTooltipMark = markRange; ActiveToolTip::showToolTip(tooltip); } void PatchHighlighter::markClicked(KTextEditor::Document* doc, KTextEditor::Mark mark, bool& handled) { m_applying = true; if(handled) return; handled = true; if(doc->activeView()) ///This is a workaround, if the cursor is somewhere else, the editor will always jump there when a mark was clicked doc->activeView()->setCursorPosition(KTextEditor::Cursor(mark.line, 0)); KTextEditor::MovingRange* range = rangeForMark(mark); if(range) { KTextEditor::MovingInterface* moving = dynamic_cast( doc ); Q_ASSERT(moving); QString currentText = doc->text(range->toRange()); Diff2::Difference* diff = m_differencesForRanges[range]; removeLineMarker(range); QString sourceText; QString targetText; for(int a = 0; a < diff->sourceLineCount(); ++a) { sourceText += diff->sourceLineAt(a)->string(); if(!sourceText.endsWith("\n")) sourceText += "\n"; } for(int a = 0; a < diff->destinationLineCount(); ++a) { targetText += diff->destinationLineAt(a)->string(); if(!targetText.endsWith("\n")) targetText += "\n"; } QString replace; QString replaceWith; if(!diff->applied()) { replace = sourceText; replaceWith = targetText; }else { replace = targetText; replaceWith = sourceText; } if(currentText.simplified() != replace.simplified()) { KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), i18n("Could not apply the change: Text should be \"%1\", but is \"%2\".", replace, currentText)); return; } diff->apply(!diff->applied()); KTextEditor::Cursor start = range->start().toCursor(); range->document()->replaceText(range->toRange(), replaceWith); KTextEditor::Range newRange(start, start); uint replaceWithLines = replaceWith.count('\n'); newRange.end().setLine(newRange.end().line() + replaceWithLines); range->setRange(newRange); addLineMarker(range, diff); } { // After applying the change, show the tooltip again, mainly to update an old tooltip delete currentTooltip; bool h = false; markToolTipRequested(doc, mark, QCursor::pos(), h); } m_applying = false; } KTextEditor::MovingRange* PatchHighlighter::rangeForMark(KTextEditor::Mark mark) { for(QMap< KTextEditor::MovingRange*, Diff2::Difference* >::const_iterator it = m_differencesForRanges.constBegin(); it != m_differencesForRanges.constEnd(); ++it) { if(it.key()->start().line() == mark.line) { return it.key(); } } return 0; } void PatchHighlighter::markToolTipRequested(KTextEditor::Document* , KTextEditor::Mark mark, QPoint pos, bool& handled) { if(handled) return; handled = true; int myMarksPattern = KTextEditor::MarkInterface::markType22 | KTextEditor::MarkInterface::markType23 | KTextEditor::MarkInterface::markType24 | KTextEditor::MarkInterface::markType25 | KTextEditor::MarkInterface::markType26 | KTextEditor::MarkInterface::markType27; if(mark.type & myMarksPattern) { //There is a mark in this line. Show the old text. KTextEditor::MovingRange* range = rangeForMark(mark); if(range) showToolTipForMark(pos, range); } } bool PatchHighlighter::isInsertion(Diff2::Difference* diff) { return diff->sourceLineCount() == 0; } bool PatchHighlighter::isRemoval(Diff2::Difference* diff) { return diff->destinationLineCount() == 0; } QStringList PatchHighlighter::splitAndAddNewlines(const QString& text) const { QStringList result = text.split('\n', QString::KeepEmptyParts); for(QStringList::iterator iter = result.begin(); iter != result.end(); ++iter) { iter->append('\n'); } if (!result.isEmpty()) { QString & last = result.last(); last.remove(last.size() - 1, 1); } return result; } void PatchHighlighter::performContentChange(KTextEditor::Document* doc, const QStringList& oldLines, const QStringList& newLines, int editLineNumber) { QPair, QList > diffChange = m_model->linesChanged(oldLines, newLines, editLineNumber); QList inserted = diffChange.first; QList removed = diffChange.second; // Remove all ranges that are in the same line (the line markers) foreach(KTextEditor::MovingRange* r, m_differencesForRanges.keys()) { Diff2::Difference* diff = m_differencesForRanges[r]; if (removed.contains(diff)) { removeLineMarker(r); m_ranges.remove(r); m_differencesForRanges.remove(r); delete r; delete diff; } } KTextEditor::MovingInterface* moving = dynamic_cast( doc ); if ( !moving ) return; foreach(Diff2::Difference* diff, inserted) { int lineStart = diff->destinationLineNumber(); if (lineStart > 0) { --lineStart; } int lineEnd = diff->destinationLineEnd(); if (lineEnd > 0) { --lineEnd; } KTextEditor::Range newRange(lineStart, 0, lineEnd, 0); KTextEditor::MovingRange * r = moving->newMovingRange( newRange ); m_differencesForRanges[r] = diff; m_ranges.insert(r); addLineMarker(r, diff); } } void PatchHighlighter::textRemoved(KTextEditor::Document* doc, const KTextEditor::Range& range, const QString& oldText) { if (m_applying) { // Do not interfere with patch application return; } kDebug() << "removal range" << range; kDebug() << "removed text" << oldText; QStringList removedLines = splitAndAddNewlines(oldText); int startLine = range.start().line(); QString remainingLine = doc->line(startLine); remainingLine += '\n'; QString prefix = remainingLine.mid(0, range.start().column()); QString suffix = remainingLine.mid(range.start().column()); if (!removedLines.empty()) { removedLines.first() = prefix + removedLines.first(); removedLines.last() = removedLines.last() + suffix; } performContentChange(doc, removedLines, QStringList() << remainingLine, startLine + 1); } void PatchHighlighter::textInserted(KTextEditor::Document* doc, KTextEditor::Range range) { if(range == doc->documentRange()) { kDebug() << "re-doing"; //The document was loaded / reloaded if ( !m_model->differences() ) return ; KTextEditor::MovingInterface* moving = dynamic_cast( doc ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( doc ); if( !markIface ) return; clear(); QColor activeIconColor = QApplication::palette().color(QPalette::Active, QPalette::Highlight); QColor inActiveIconColor = QApplication::palette().color(QPalette::Active, QPalette::Base); KColorScheme scheme(QPalette::Active); QImage tintedInsertion = KIcon("insert-text").pixmap(16, 16).toImage(); KIconEffect::colorize(tintedInsertion, scheme.foreground(KColorScheme::NegativeText).color(), 1.0); QImage tintedRemoval = KIcon("edit-delete").pixmap(16, 16).toImage(); KIconEffect::colorize(tintedRemoval, scheme.foreground(KColorScheme::NegativeText).color(), 1.0); QImage tintedChange = KIcon("text-field").pixmap(16, 16).toImage(); KIconEffect::colorize(tintedChange, scheme.foreground(KColorScheme::NegativeText).color(), 1.0); markIface->setMarkDescription(KTextEditor::MarkInterface::markType22, i18n("Insertion")); markIface->setMarkPixmap(KTextEditor::MarkInterface::markType22, QPixmap::fromImage(tintedInsertion)); markIface->setMarkDescription(KTextEditor::MarkInterface::markType23, i18n("Removal")); markIface->setMarkPixmap(KTextEditor::MarkInterface::markType23, QPixmap::fromImage(tintedRemoval)); markIface->setMarkDescription(KTextEditor::MarkInterface::markType24, i18n("Change")); markIface->setMarkPixmap(KTextEditor::MarkInterface::markType24, QPixmap::fromImage(tintedChange)); markIface->setMarkDescription(KTextEditor::MarkInterface::markType25, i18n("Insertion")); markIface->setMarkPixmap(KTextEditor::MarkInterface::markType25, KIcon("insert-text").pixmap(16, 16)); markIface->setMarkDescription(KTextEditor::MarkInterface::markType26, i18n("Removal")); markIface->setMarkPixmap(KTextEditor::MarkInterface::markType26, KIcon("edit-delete").pixmap(16, 16)); markIface->setMarkDescription(KTextEditor::MarkInterface::markType27, i18n("Change")); markIface->setMarkPixmap(KTextEditor::MarkInterface::markType27, KIcon("text-field").pixmap(16, 16)); for ( Diff2::DifferenceList::const_iterator it = m_model->differences() ->constBegin(); it != m_model->differences() ->constEnd(); ++it ) { Diff2::Difference* diff = *it; int line, lineCount; Diff2::DifferenceStringList lines ; if(diff->applied()) { line = diff->destinationLineNumber(); lineCount = diff->destinationLineCount(); lines = diff->destinationLines(); } else { line = diff->sourceLineNumber(); lineCount = diff->sourceLineCount(); lines = diff->sourceLines(); } if ( line > 0 ) line -= 1; KTextEditor::Cursor c( line, 0 ); KTextEditor::Cursor endC( line + lineCount, 0 ); if ( doc->lines() <= c.line() ) c.setLine( doc->lines() - 1 ); if ( doc->lines() <= endC.line() ) endC.setLine( doc->lines() ); if ( endC.isValid() && c.isValid() ) { KTextEditor::MovingRange * r = moving->newMovingRange( KTextEditor::Range(c, endC) ); m_ranges << r; m_differencesForRanges[r] = *it; addLineMarker(r, diff); } } } else { if (m_applying) { // Do not interfere with patch application return; } kDebug() << "insertion range" << range; QString text = doc->text(range); kDebug() << "inserted text" << text; QStringList insertedLines = splitAndAddNewlines(text); int startLine = range.start().line(); int endLine = range.end().line(); QString prefix = doc->line(startLine).mid(0, range.start().column()); QString suffix = doc->line(endLine).mid(range.end().column()); suffix += '\n'; QString removedLine = prefix + suffix; if (!insertedLines.empty()) { insertedLines.first() = prefix + insertedLines.first(); insertedLines.last() = insertedLines.last() + suffix; } performContentChange(doc, QStringList() << removedLine, insertedLines, startLine + 1); } } PatchHighlighter::PatchHighlighter( Diff2::DiffModel* model, IDocument* kdoc, PatchReviewPlugin* plugin ) throw( QString ) : m_doc( kdoc ), m_plugin(plugin), m_model(model), m_applying(false) { // connect( kdoc, SIGNAL( destroyed( QObject* ) ), this, SLOT( documentDestroyed() ) ); connect( kdoc->textDocument(), SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)), this, SLOT(textInserted(KTextEditor::Document*,KTextEditor::Range)) ); connect( kdoc->textDocument(), SIGNAL(textChanged(KTextEditor::Document*, const KTextEditor::Range&, const KTextEditor::Range&)), this, SLOT(textChanged(KTextEditor::Document*, const KTextEditor::Range&, const KTextEditor::Range&)) ); connect( kdoc->textDocument(), SIGNAL(textRemoved(KTextEditor::Document*, const KTextEditor::Range&, const QString&)), this, SLOT(textRemoved(KTextEditor::Document*, const KTextEditor::Range&, const QString&)) ); connect( kdoc->textDocument(), SIGNAL( destroyed( QObject* ) ), this, SLOT( documentDestroyed() ) ); KTextEditor::Document* doc = kdoc->textDocument(); if ( doc->lines() == 0 ) return ; connect(doc, SIGNAL(markToolTipRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&)), this, SLOT(markToolTipRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&))); connect(doc, SIGNAL(markClicked(KTextEditor::Document*,KTextEditor::Mark,bool&)), this, SLOT(markClicked(KTextEditor::Document*,KTextEditor::Mark,bool&))); connect(doc, SIGNAL(aboutToDeleteMovingInterfaceContent (KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*))); textInserted(kdoc->textDocument(), kdoc->textDocument()->documentRange()); } void PatchHighlighter::removeLineMarker(KTextEditor::MovingRange* range) { KTextEditor::MovingInterface* moving = dynamic_cast( range->document() ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( range->document() ); if( !markIface ) return; markIface->removeMark(range->start().line(), KTextEditor::MarkInterface::markType22); markIface->removeMark(range->start().line(), KTextEditor::MarkInterface::markType23); markIface->removeMark(range->start().line(), KTextEditor::MarkInterface::markType24); markIface->removeMark(range->start().line(), KTextEditor::MarkInterface::markType25); markIface->removeMark(range->start().line(), KTextEditor::MarkInterface::markType26); markIface->removeMark(range->start().line(), KTextEditor::MarkInterface::markType27); // Remove all ranges that are in the same line (the line markers) foreach(KTextEditor::MovingRange* r, m_ranges) { if(r != range && range->contains(r->toRange())) { delete r; m_ranges.remove(r); m_differencesForRanges.remove(r); } } } void PatchHighlighter::addLineMarker(KTextEditor::MovingRange* range, Diff2::Difference* diff) { KTextEditor::MovingInterface* moving = dynamic_cast( range->document() ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( range->document() ); if( !markIface ) return; KSharedPtr t( new KTextEditor::Attribute() ); bool isOriginalState = diff->applied() == m_plugin->patch()->isAlreadyApplied(); if(isOriginalState) { t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 0, 255, 255), 20 ) ) ); }else{ t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 255, 0, 255), 20 ) ) ); } range->setAttribute( t ); range->setZDepth( -500 ); KTextEditor::MarkInterface::MarkTypes mark; if(isOriginalState) { mark = KTextEditor::MarkInterface::markType27; if(isInsertion(diff)) mark = KTextEditor::MarkInterface::markType25; if(isRemoval(diff)) mark = KTextEditor::MarkInterface::markType26; }else{ mark = KTextEditor::MarkInterface::markType24; if(isInsertion(diff)) mark = KTextEditor::MarkInterface::markType22; if(isRemoval(diff)) mark = KTextEditor::MarkInterface::markType23; } markIface->addMark(range->start().line(), mark); Diff2::DifferenceStringList lines; if(diff->applied()) lines = diff->destinationLines(); else lines = diff->sourceLines(); for(int a = 0; a < lines.size(); ++a) { Diff2::DifferenceString* line = lines[a]; int currentPos = 0; QString string = line->string(); Diff2::MarkerList markers = line->markerList(); for(int b = 0; b < markers.size(); ++b) { if(markers[b]->type() == Diff2::Marker::End) { if(currentPos != 0 || markers[b]->offset() != string.size()) { KTextEditor::MovingRange * r2 = moving->newMovingRange( KTextEditor::Range( KTextEditor::Cursor(a + range->start().line(), currentPos), KTextEditor::Cursor(a + range->start().line(), markers[b]->offset()) ) ); m_ranges << r2; KSharedPtr t( new KTextEditor::Attribute() ); t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 255, 0, 0), 70 ) ) ); r2->setAttribute( t ); r2->setZDepth( -600 ); } } currentPos = markers[b]->offset(); } } } void PatchHighlighter::clear() { if(m_ranges.empty()) return; KTextEditor::MovingInterface* moving = dynamic_cast( m_doc->textDocument() ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( m_doc->textDocument() ); if( !markIface ) return; QHash< int, KTextEditor::Mark* > marks = markIface->marks(); foreach(int line, marks.keys()) { markIface->removeMark(line, KTextEditor::MarkInterface::markType22); markIface->removeMark(line, KTextEditor::MarkInterface::markType23); markIface->removeMark(line, KTextEditor::MarkInterface::markType24); markIface->removeMark(line, KTextEditor::MarkInterface::markType25); markIface->removeMark(line, KTextEditor::MarkInterface::markType26); markIface->removeMark(line, KTextEditor::MarkInterface::markType27); } qDeleteAll(m_ranges); m_ranges.clear(); m_differencesForRanges.clear(); } PatchHighlighter::~PatchHighlighter() { clear(); } IDocument* PatchHighlighter::doc() { return m_doc; } void PatchHighlighter::documentDestroyed() { kDebug() << "document destroyed"; m_ranges.clear(); m_differencesForRanges.clear(); } void PatchReviewPlugin::removeHighlighting( const KUrl& file ) { if ( file.isEmpty() ) { ///Remove all highlighting qDeleteAll(m_highlighters); m_highlighters.clear(); } else { HighlightMap::iterator it = m_highlighters.find( file ); if ( it != m_highlighters.end() ) { delete * it; m_highlighters.erase( it ); } } } void PatchReviewPlugin::notifyPatchChanged() { kDebug() << "notifying patch change: " << m_patch->file(); m_updateKompareTimer->start(500); } void PatchReviewPlugin::showPatch() { startReview(m_patch, OpenAndRaise); } void PatchReviewPlugin::forceUpdate() { m_patch->update(); notifyPatchChanged(); } void PatchReviewPlugin::updateKompareModel() { if (!m_patch) { ///TODO: this method should be cleaned up, it can be called by the timer and /// e.g. https://bugs.kde.org/show_bug.cgi?id=267187 shows how it could /// lead to asserts before... return; } kDebug() << "updating model"; try { Q_ASSERT(m_patch); removeHighlighting(); m_modelList.reset( 0 ); delete m_diffSettings; emit patchChanged(); { IDocument* patchDoc = ICore::self()->documentController()->documentForUrl(m_patch->file()); if(patchDoc) patchDoc->reload(); } m_diffSettings = new DiffSettings( 0 ); m_kompareInfo.reset( new Kompare::Info() ); m_kompareInfo->localDestination=m_patch->file().toLocalFile(); m_kompareInfo->localSource=m_patch->baseDir().toLocalFile(); m_kompareInfo->depth = m_patch->depth(); m_kompareInfo->applied = m_patch->isAlreadyApplied(); m_modelList.reset(new Diff2::KompareModelList( m_diffSettings.data(), new QWidget, this )); m_modelList->slotKompareInfo(m_kompareInfo.get()); try { if ( !m_modelList->openDirAndDiff() ) { #if 0 // Don't error out on empty files, as those are valid diffs too if(QFileInfo(m_patch->file().toLocalFile()).size() != 0) throw "could not open diff " + m_patch->file().prettyUrl() + " on " + m_patch->baseDir().prettyUrl(); #endif } } catch ( const QString & str ) { throw; } catch ( ... ) { throw QString( "lib/libdiff2 crashed, memory may be corrupted. Please restart kdevelop." ); } emit patchChanged(); for(int i=0; imodelCount(); i++) { const Diff2::DiffModel* model=m_modelList->modelAt(i); for(int j=0; jdifferences()->count(); j++) { model->differences()->at(j)->apply(m_patch->isAlreadyApplied()); } } highlightPatch(); return; } catch ( const QString & str ) { KMessageBox::error(0, str, i18n("Kompare Model Update")); } catch ( const char * str ) { KMessageBox::error(0, str, i18n("Kompare Model Update")); } removeHighlighting(); m_modelList.reset( 0 ); m_kompareInfo.reset( 0 ); delete m_diffSettings; emit patchChanged(); } K_PLUGIN_FACTORY(KDevProblemReporterFactory, registerPlugin(); ) K_EXPORT_PLUGIN(KDevProblemReporterFactory(KAboutData("kdevpatchreview","kdevpatchreview", ki18n("Patch Review"), "0.1", ki18n("Highlights code affected by a patch"), KAboutData::License_GPL))) class PatchReviewToolViewFactory : public KDevelop::IToolViewFactory { public: PatchReviewToolViewFactory(PatchReviewPlugin *plugin): m_plugin(plugin) {} virtual QWidget* create(QWidget *parent = 0) { return m_plugin->createToolView(parent); } virtual Qt::DockWidgetArea defaultPosition() { return Qt::BottomDockWidgetArea; } virtual QString id() const { return "org.kdevelop.PatchReview"; } private: PatchReviewPlugin *m_plugin; }; PatchReviewPlugin::~PatchReviewPlugin() { removeHighlighting(); delete m_patch; } void PatchReviewPlugin::registerPatch(IPatchSource::Ptr patch) { if(!m_knownPatches.contains(patch)) { m_knownPatches << patch; connect(patch, SIGNAL(destroyed(QObject*)), SLOT(clearPatch(QObject*))); } } void PatchReviewPlugin::clearPatch(QObject* _patch) { kDebug() << "clearing patch" << _patch << "current:" << (QObject*)m_patch; IPatchSource::Ptr patch((IPatchSource*)_patch); m_knownPatches.removeAll(patch); m_knownPatches.removeAll(0); if(patch == m_patch) { kDebug() << "is current patch"; if(!m_knownPatches.empty()) setPatch(m_knownPatches.first()); else setPatch(IPatchSource::Ptr(new LocalPatchSource)); } } #if 0 #if HAVE_KOMPARE void showDiff(const KDevelop::VcsDiff& d) { ICore::self()->uiController()->switchToArea("review", KDevelop::IUiController::ThisWindow); foreach(const VcsLocation& l, d.leftTexts().keys()) { KUrl to; if(d.rightTexts().contains(l)) { KTemporaryFile temp2; temp2.setSuffix("2.patch"); //FIXME: don't leak temp2.setAutoRemove(false); temp2.open(); QTextStream t2(&temp2); t2 << d.rightTexts()[l]; temp2.close(); to=temp2.fileName(); } else to=l.localUrl(); KUrl fakeUrl(to); fakeUrl.setScheme("kdevpatch"); IDocumentFactory* docf=ICore::self()->documentController()->factory("text/x-patch"); IDocument* doc=docf->create(fakeUrl, ICore::self()); IPatchDocument* pdoc=dynamic_cast(doc); Q_ASSERT(pdoc); ICore::self()->documentController()->openDocument(doc); pdoc->setDiff(d.leftTexts()[l], to); } } #endif #endif void PatchReviewPlugin::cancelReview() { if(m_patch) { removeHighlighting(); m_modelList.reset( 0 ); m_patch->cancelReview(); emit patchChanged(); delete m_patch; Sublime::MainWindow* w = dynamic_cast(ICore::self()->uiController()->activeMainWindow()); if(w->area()->objectName() == "review") { setUniqueWorkingSet(); // Make the working-set unique, so that we don't affect other areas w->area()->clearViews(); ICore::self()->uiController()->switchToArea("code", KDevelop::IUiController::ThisWindow); } } } void PatchReviewPlugin::finishReview(QList< KUrl > selection) { if(m_patch) { if(!m_patch->finishReview(selection)) return; removeHighlighting(); m_modelList.reset( 0 ); emit patchChanged(); if(!dynamic_cast(m_patch.data())) { delete m_patch; // make sure "show" button still openes the file dialog to open a custom patch file setPatch(new LocalPatchSource); } Sublime::MainWindow* w = dynamic_cast(ICore::self()->uiController()->activeMainWindow()); if(w->area()->objectName() == "review") { w->area()->clearViews(); ICore::self()->uiController()->switchToArea("code", KDevelop::IUiController::ThisWindow); } } } void PatchReviewPlugin::startReview(IPatchSource* patch, IPatchReview::ReviewMode mode) { Q_UNUSED(mode); setPatch(patch); QMetaObject::invokeMethod(this, "updateReview", Qt::QueuedConnection); } void PatchReviewPlugin::switchAreaAndMakeWorkingSetUique() { Sublime::MainWindow* w = dynamic_cast(ICore::self()->uiController()->activeMainWindow()); if (w->area()->objectName() != "review") ICore::self()->uiController()->switchToArea("review", KDevelop::IUiController::ThisWindow); setUniqueWorkingSet(); } bool PatchReviewPlugin::isWorkingSetUnique() const { Sublime::MainWindow* w = dynamic_cast(ICore::self()->uiController()->activeMainWindow()); foreach(Sublime::Area* area, w->areas()) if(area != w->area() && area->workingSet() == w->area()->workingSet()) return false; return true; } void PatchReviewPlugin::setUniqueWorkingSet() { Sublime::MainWindow* w = dynamic_cast(ICore::self()->uiController()->activeMainWindow()); if(!w->area()->workingSet().startsWith("review")) w->area()->setWorkingSet("review"); while(!isWorkingSetUnique()) w->area()->setWorkingSet(QString("review_%1").arg(rand() % 10000)); } void PatchReviewPlugin::updateReview() { if(!m_patch) return; m_updateKompareTimer->stop(); updateKompareModel(); switchAreaAndMakeWorkingSetUique(); if(!m_modelList.get()) return; // list of opened documents to prevent flicker QMap documents; foreach(IDocument* doc, ICore::self()->documentController()->openDocuments()) { documents[doc->url()] = doc; } IDocument* futureActiveDoc = 0; //Open the diff itself #ifdef HAVE_KOMPARE KUrl fakeUrl(m_patch->file()); fakeUrl.setScheme("kdevpatch"); IDocumentFactory* docf=ICore::self()->documentController()->factory("text/x-patch"); IDocument* doc=docf->create(fakeUrl, ICore::self()); IPatchDocument* pdoc=dynamic_cast(doc); Q_ASSERT(pdoc); futureActiveDoc = ICore::self()->documentController()->openDocument(doc); //TODO: close kompare doc if available #else if (!documents.contains(m_patch->file())) { futureActiveDoc = ICore::self()->documentController()->openDocument(m_patch->file()); } else { futureActiveDoc = documents.take(m_patch->file()); } #endif if (!futureActiveDoc || !futureActiveDoc->textDocument()) { // might happen if e.g. openDocument dialog was cancelled by user // or under the theoretic possibility of a non-text document getting opened return; } futureActiveDoc->textDocument()->setReadWrite(false); futureActiveDoc->setPrettyName(i18n("Overview")); if(m_modelList->modelCount() < maximumFilesToOpenDirectly) { //Open all relates files for(int a = 0; a < m_modelList->modelCount(); ++a) { KUrl absoluteUrl = m_patch->baseDir(); KUrl url(m_modelList->modelAt(a)->destination()); if(url.isRelative()) absoluteUrl.addPath(url.path()); else absoluteUrl = url; if(QFileInfo(absoluteUrl.path()).exists() && absoluteUrl.path() != "/dev/null") { ICore::self()->documentController()->openDocument(absoluteUrl); if (documents.contains(absoluteUrl)) { documents.remove(absoluteUrl); } seekHunk(true, absoluteUrl); //Jump to the first changed position }else{ // Maybe the file was deleted kDebug() << "could not open" << absoluteUrl << "because it doesn't exist"; } } } Sublime::MainWindow* w = dynamic_cast(ICore::self()->uiController()->activeMainWindow()); // Close views for documents that were loaded from the working set, but are not in the patch QList documentsList = documents.values(); foreach(Sublime::View* view, w->area()->views()) { IDocument* doc = dynamic_cast(view->document()); if(doc && documentsList.contains(doc)) { w->area()->closeView(view); } } Q_ASSERT(futureActiveDoc); ICore::self()->documentController()->activateDocument(futureActiveDoc); bool b = ICore::self()->uiController()->findToolView(i18n("Patch Review"), m_factory); Q_ASSERT(b); } void PatchReviewPlugin::setPatch(IPatchSource* patch) { if (patch == m_patch) { return; } if(m_patch) { disconnect(m_patch, SIGNAL(patchChanged()), this, SLOT(notifyPatchChanged())); if (qobject_cast(m_patch)) { // make sure we don't leak this // TODO: what about other patch sources? delete m_patch; } } m_patch = patch; if(m_patch) { kDebug() << "setting new patch" << patch->name() << "with file" << patch->file(); registerPatch(patch); connect(m_patch, SIGNAL(patchChanged()), this, SLOT(notifyPatchChanged())); } notifyPatchChanged(); } PatchReviewPlugin::PatchReviewPlugin(QObject *parent, const QVariantList &) : KDevelop::IPlugin(KDevProblemReporterFactory::componentData(), parent), m_patch(0), m_factory(new PatchReviewToolViewFactory(this)) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IPatchReview ) qRegisterMetaType( "const Diff2::DiffModel*" ); core()->uiController()->addToolView(i18n("Patch Review"), m_factory); setXMLFile("kdevpatchreview.rc"); connect(ICore::self()->documentController(), SIGNAL(documentClosed(KDevelop::IDocument*)), this, SLOT(documentClosed(KDevelop::IDocument*))); connect(ICore::self()->documentController(), SIGNAL(textDocumentCreated(KDevelop::IDocument*)), this, SLOT(textDocumentCreated(KDevelop::IDocument*))); m_updateKompareTimer = new QTimer( this ); m_updateKompareTimer->setSingleShot( true ); connect( m_updateKompareTimer, SIGNAL( timeout() ), this, SLOT( updateKompareModel() ) ); setPatch(IPatchSource::Ptr(new LocalPatchSource)); } void PatchReviewPlugin::documentClosed(IDocument* doc) { removeHighlighting(doc->url()); } void PatchReviewPlugin::textDocumentCreated(IDocument* doc) { addHighlighting( doc->url(), doc ); } void PatchReviewPlugin::unload() { core()->uiController()->removeToolView(m_factory); KDevelop::IPlugin::unload(); } QWidget* PatchReviewPlugin::createToolView(QWidget* parent) { return new PatchReviewToolView(parent, this); } void PatchReviewPlugin::exporterSelected(QAction* action) { IPlugin* exporter = qobject_cast(action->data().value()); if(exporter) { qDebug() << "exporting patch" << exporter << action->text(); exporter->extension()->exportPatch(patch()); } } #if 0 void PatchReviewPlugin::determineState() { LocalPatchSourcePointer lpatch = m_patch; if ( !lpatch ) { kDebug() <<"determineState(..) could not lock patch"; } try { if ( lpatch->filename.isEmpty() ) throw "state can only be determined for file-patches"; KUrl fileUrl = lpatch->filename; { K3Process proc; ///Try to apply, if it works, the patch is not applied QString cmd = "patch --dry-run -s -f -i " + fileUrl.toLocalFile(); proc << splitArgs( cmd ); kDebug() << "calling " << cmd; if ( !proc.start( K3Process::Block ) ) throw "could not start process"; if ( !proc.normalExit() ) throw "process did not exit normally"; kDebug() << "exit-status:" << proc.exitStatus(); if ( proc.exitStatus() == 0 ) { // lpatch->state = LocalPatchSource::NotApplied; return; } } { ///Try to revert, of it works, the patch is applied K3Process proc; QString cmd = "patch --dry-run -s -f -i --reverse " + fileUrl.toLocalFile(); proc << splitArgs( cmd ); kDebug() << "calling " << cmd; if ( !proc.start( K3Process::Block ) ) throw "could not start process"; if ( !proc.normalExit() ) throw "process did not exit normally"; kDebug() << "exit-status:" << proc.exitStatus(); if ( proc.exitStatus() == 0 ) { // lpatch->state = LocalPatchSource::Applied; return; } } } catch ( const QString& str ) { kWarning() <<"Error:" << str; } catch ( const char* str ) { kWarning() << "Error:" << str; } // lpatch->state = LocalPatchSource::Unknown; } #endif #include "patchreview.moc" // kate: space-indent on; indent-width 2; tab-width 2; replace-tabs on diff --git a/plugins/patchreview/patchreview.h b/plugins/patchreview/patchreview.h index eb0bc382c9..8e86063697 100644 --- a/plugins/patchreview/patchreview.h +++ b/plugins/patchreview/patchreview.h @@ -1,243 +1,246 @@ /*************************************************************************** Copyright 2006 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. * * * ***************************************************************************/ #ifndef PATCHREVIEW_H #define PATCHREVIEW_H #include #include #include #include #include #include "localpatchsource.h" #include "ui_patchreview.h" #include #include #include "libdiff2/diffmodel.h" class PatchReviewToolViewFactory; class PatchReviewPlugin; namespace KParts { class Part; } class QDialog; namespace Diff2 { class KompareModelList; class DiffModel; class DiffModel; } namespace KTextEditor { class Document; class Range; class MovingRange; class Mark; } namespace Kompare { class Info; } namespace KDevelop { class IDocument; +class VcsFileChangesModel; } ///Delete itself when the document(or textDocument), or Diff-Model is deleted. class PatchHighlighter : public QObject { Q_OBJECT public: PatchHighlighter( Diff2::DiffModel* model, KDevelop::IDocument* doc, PatchReviewPlugin* plugin ) throw( QString ); ~PatchHighlighter(); KDevelop::IDocument* doc(); QList ranges() const { return m_differencesForRanges.keys(); } private slots: void documentDestroyed(); void aboutToDeleteMovingInterfaceContent(KTextEditor::Document*); private: void addLineMarker(KTextEditor::MovingRange* arg1, Diff2::Difference* arg2); void removeLineMarker(KTextEditor::MovingRange* range); QStringList splitAndAddNewlines(const QString& text) const; void performContentChange(KTextEditor::Document* doc, const QStringList& oldLines, const QStringList& newLines, int editLineNumber); KTextEditor::MovingRange* rangeForMark(KTextEditor::Mark mark); void clear(); QSet m_ranges; QMap m_differencesForRanges; KDevelop::IDocument* m_doc; PatchReviewPlugin* m_plugin; Diff2::DiffModel* m_model; bool m_applying; public slots: void markToolTipRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&); void showToolTipForMark(QPoint arg1, KTextEditor::MovingRange* arg2, QPair< int, int > highlightMark = qMakePair(-1, -1)); bool isRemoval(Diff2::Difference*); bool isInsertion(Diff2::Difference*); void markClicked(KTextEditor::Document*,KTextEditor::Mark,bool&); void textInserted(KTextEditor::Document*,KTextEditor::Range); void textRemoved(KTextEditor::Document*, const KTextEditor::Range&, const QString& oldText); }; class DiffSettings; class PatchReviewPlugin; class PatchReviewToolView : public QWidget { Q_OBJECT public: PatchReviewToolView( QWidget* parent, PatchReviewPlugin* plugin ); ~PatchReviewToolView(); signals: void dialogClosed( PatchReviewToolView* ); void stateChanged( PatchReviewToolView* ); private slots: void fileDoubleClicked( const QModelIndex& i ); void nextHunk(); void prevHunk(); void patchChanged(); void updatePatchFromEdit(); void slotEditCommandChanged(); void slotEditFileNameChanged(); void slotDepthChanged(int newDepth); void slotAppliedChanged(int newState); void finishReview(); private: void kompareModelChanged(); void showEditDialog(); ///Fills the editor views from m_editingPatch void fillEditFromPatch(); /// Retrieve the patch from plugin and perform all necessary casts LocalPatchSource* GetLocalPatchSource(); Ui_EditPatch m_editPatch; QTime m_lastDataTime; QString m_lastTerminalData; QPointer m_konsolePart; bool m_reversed; PatchReviewPlugin* m_plugin; QPointer m_customWidget; + + class PatchFilesModel* m_fileModel; public slots: void documentActivated(KDevelop::IDocument*); void patchSelectionChanged(int); }; class PatchReviewPlugin : public KDevelop::IPlugin, public KDevelop::IPatchReview { Q_OBJECT Q_INTERFACES( KDevelop::IPatchReview ) public: PatchReviewPlugin(QObject *parent, const QVariantList & = QVariantList() ); ~PatchReviewPlugin(); virtual void unload(); QWidget* createToolView(QWidget* parent); KDevelop::IPatchSource::Ptr patch() const { return m_patch; } QList knownPatches() const { return m_knownPatches; } Diff2::KompareModelList* modelList() const { return m_modelList.get(); } void seekHunk( bool forwards, const KUrl& file = KUrl() ); KUrl diffFile(); void setPatch(KDevelop::IPatchSource* patch); void registerPatch(KDevelop::IPatchSource::Ptr patch); virtual void startReview(KDevelop::IPatchSource* patch, ReviewMode mode); void finishReview(QList selection); KUrl urlForFileModel(const Diff2::DiffModel* model); Q_SIGNALS: void patchChanged(); public Q_SLOTS: //Does parts of the review-starting that are problematic to do directly in startReview, as they may open dialogs etc. void updateReview(); void cancelReview(); void clearPatch(QObject* patch); void notifyPatchChanged(); void highlightPatch(); void updateKompareModel(); void showPatch(); void forceUpdate(); private Q_SLOTS: void documentClosed(KDevelop::IDocument*); void textDocumentCreated(KDevelop::IDocument*); void exporterSelected(QAction* action); private: // Switches to the review area, // makes sure that the working set active in the current area starts with "review" and // is not active in any other area. Creates new working sets if required. void switchAreaAndMakeWorkingSetUique(); // Returns whether the current working set is active only in this area bool isWorkingSetUnique() const; // Makes sure that this working set is active only in this area, and that its name starts with "review". void setUniqueWorkingSet(); QList m_knownPatches; void addHighlighting( const KUrl& file, KDevelop::IDocument* document = 0 ); void removeHighlighting( const KUrl& file = KUrl() ); KDevelop::IPatchSource::Ptr m_patch; QTimer* m_updateKompareTimer; PatchReviewToolViewFactory* m_factory; #if 0 void determineState(); #endif QPointer m_diffSettings; std::auto_ptr m_kompareInfo; std::auto_ptr m_modelList; typedef QMap > HighlightMap; HighlightMap m_highlighters; }; #endif // kate: space-indent on; indent-width 2; tab-width 2; replace-tabs on diff --git a/plugins/patchreview/patchreview.ui b/plugins/patchreview/patchreview.ui index 8fe9cd17fd..9d52dac374 100644 --- a/plugins/patchreview/patchreview.ui +++ b/plugins/patchreview/patchreview.ui @@ -1,315 +1,310 @@ EditPatch 0 0 908 452 Edit Patch 0 0 ... ... Qt::Horizontal 448 20 Show Qt::Horizontal QSizePolicy::Fixed 40 20 Export Diff... QToolButton::InstantPopup Cancel Review Finish Review 0 0 Patch 0 Custom Patch 0 0 Update Base: 0 File <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-family:Sans Serif; font-size:9pt; font-weight:400; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">File:</span></p></body></html> command-output 9 6 6 0 <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-family:Sans Serif; font-size:9pt; font-weight:400; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Command:</span></p></body></html> 0 0 Depth: Patch depth - see "patch -p" 3 Patch is already applied on local version Applied false Qt::Vertical 88 37 Changes 0 - + QAbstractItemView::NoEditTriggers - - - 1 - - KUrlRequester QFrame
kurlrequester.h
diff --git a/vcs/CMakeLists.txt b/vcs/CMakeLists.txt index f3cd726bdf..5fedee80ea 100644 --- a/vcs/CMakeLists.txt +++ b/vcs/CMakeLists.txt @@ -1,113 +1,116 @@ add_subdirectory(dvcs/tests) +add_subdirectory(models/tests) add_subdirectory(tests) add_definitions(-DKDE_DEFAULT_DEBUG_AREA=9509) set(kdevplatformvcs_UIS widgets/vcscommitdialog.ui widgets/vcseventwidget.ui widgets/vcsdiffwidget.ui dvcs/ui/cvsgenericoutputview.ui dvcs/ui/cvsmainview.ui dvcs/ui/importmetadatawidget.ui dvcs/ui/logview.ui dvcs/ui/branchmanager.ui ) set(kdevplatformvcs_LIB_SRCS vcsjob.cpp vcsrevision.cpp vcsannotation.cpp vcspluginhelper.cpp vcslocation.cpp vcsdiff.cpp vcsevent.cpp vcsstatusinfo.cpp widgets/vcsimportmetadatawidget.cpp widgets/vcseventwidget.cpp widgets/vcsdiffwidget.cpp widgets/vcscommitdialog.cpp widgets/vcsdiffpatchsources.cpp widgets/vcslocationwidget.cpp widgets/standardvcslocationwidget.cpp models/vcsannotationmodel.cpp models/vcseventmodel.cpp + models/vcsfilechangesmodel.cpp models/vcsitemeventmodel.cpp dvcs/dvcsjob.cpp dvcs/dvcsplugin.cpp dvcs/ui/dvcsmainview.cpp dvcs/ui/dvcsgenericoutputview.cpp dvcs/ui/importdialog.cpp dvcs/ui/importmetadatawidget.cpp dvcs/ui/logview.cpp dvcs/ui/branchmanager.cpp dvcs/ui/revhistory/commitView.cpp dvcs/ui/revhistory/commitlogmodel.cpp ) kde4_add_ui_files(kdevplatformvcs_LIB_SRCS ${kdevplatformvcs_UIS}) kde4_add_library(kdevplatformvcs SHARED ${kdevplatformvcs_LIB_SRCS}) target_link_libraries(kdevplatformvcs ${KDE4_KIO_LIBS} ${KDE4_KPARTS_LIBS} kdevplatforminterfaces kdevplatformutil kdevplatformproject kdevplatformlanguage kdevplatformoutputview ) # add kdevplatform* when they're exported targets target_link_libraries(kdevplatformvcs LINK_INTERFACE_LIBRARIES kdevplatformoutputview kdevplatforminterfaces) set_target_properties(kdevplatformvcs PROPERTIES VERSION ${KDEVPLATFORM_LIB_VERSION} SOVERSION ${KDEVPLATFORM_LIB_SOVERSION}) install(TARGETS kdevplatformvcs EXPORT KDevPlatformTargets ${INSTALL_TARGETS_DEFAULT_ARGS} ) install(FILES vcsexport.h vcsjob.h vcsrevision.h vcsannotation.h vcsdiff.h vcspluginhelper.h vcsevent.h vcsstatusinfo.h vcslocation.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/vcs COMPONENT Devel ) install(FILES widgets/vcsimportmetadatawidget.h widgets/vcsannotationwidget.h widgets/vcseventwidget.h widgets/vcsdiffwidget.h widgets/vcscommitdialog.h widgets/vcslocationwidget.h widgets/standardvcslocationwidget.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/vcs/widgets COMPONENT Devel ) install(FILES models/vcsannotationmodel.h models/vcseventmodel.h + models/vcsfilechangesmodel.h models/vcsitemeventmodel.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/vcs/models COMPONENT Devel ) install(FILES interfaces/ibasicversioncontrol.h interfaces/icentralizedversioncontrol.h interfaces/idistributedversioncontrol.h interfaces/ibranchingversioncontrol.h interfaces/ibrowsableversioncontrol.h interfaces/irepositoryversioncontrol.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/vcs/interfaces COMPONENT Devel ) install(FILES dvcs/dvcsjob.h dvcs/dvcsplugin.h dvcs/dvcsevent.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/vcs/dvcs COMPONENT Devel ) diff --git a/vcs/models/tests/CMakeLists.txt b/vcs/models/tests/CMakeLists.txt new file mode 100644 index 0000000000..e6b6942d24 --- /dev/null +++ b/vcs/models/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +set(modelsTest_SRCS modelstest.cpp) +kde4_add_unit_test(modelsTest ${modelsTest_SRCS}) +target_link_libraries(modelsTest + ${QT_QTTEST_LIBRARY} + ${QT_QTGUI_LIBRARY} + ${KDE4_KDECORE_LIBS} + ${KDEVPLATFORM_TESTS_LIBRARIES} + kdevplatformutil + kdevplatformvcs + ) diff --git a/vcs/models/tests/modelstest.cpp b/vcs/models/tests/modelstest.cpp new file mode 100644 index 0000000000..0af6ffd803 --- /dev/null +++ b/vcs/models/tests/modelstest.cpp @@ -0,0 +1,125 @@ +/*************************************************************************** + * Copyright 2011 Andrey Batyiev * + * * + * 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 "modelstest.h" + +#include +#include + +#include +#include +#include + +using namespace KDevelop; + +void ModelsTest::testInit() +{ + AutoTestShell::init(); + Core::initialize(); +} + + +void ModelsTest::testVcsFileChangesModel() +{ + VcsFileChangesModel *model = new VcsFileChangesModel(this); + + // Newly created model should be empty + QVERIFY(model->rowCount() == 0); + + // Pull some files into + QString filenames[] = {"foo", "bar", "pew", "trash"}; + VcsStatusInfo::State states[] = {VcsStatusInfo::ItemAdded, VcsStatusInfo::ItemModified, VcsStatusInfo::ItemDeleted, VcsStatusInfo::ItemUpToDate}; + VcsStatusInfo status; + + for(int i = 0; i < 3; i++) { + status.setUrl(KUrl(filenames[i])); + status.setState(states[i]); + model->updateState(status); + QVERIFY(model->rowCount() == (i+1)); + } + + // Pulling up-to-date file doesn't change anything + { + status.setUrl(KUrl(filenames[3])); + status.setState(states[3]); + model->updateState(status); + QVERIFY(model->rowCount() == 3); + } + + // Check that all OK + for(int i = 0; i < 3; i++) { + QStandardItem* item = model->fileItemForUrl(filenames[i]); + QVERIFY(item); + VcsStatusInfo info = VcsFileChangesModel::statusInfo(item); + QVERIFY(info.url().toLocalFile() == filenames[i]); + QVERIFY(info.state() == states[i]); + } + + // Pull it all again = nothing changed + for(int i = 0; i < 3; i++) { + status.setUrl(KUrl(filenames[i])); + status.setState(states[i]); + model->updateState(status); + QVERIFY(model->rowCount() == 3); + } + + // Check that all OK + for(int i = 0; i < 3; i++) { + QStandardItem* item = model->fileItemForUrl(filenames[i]); + QVERIFY(item); + VcsStatusInfo info = VcsFileChangesModel::statusInfo(item); + QVERIFY(info.url().toLocalFile() == filenames[i]); + QVERIFY(info.state() == states[i]); + } + + // Remove one file + { + states[1] = VcsStatusInfo::ItemUpToDate; + status.setUrl(KUrl(filenames[1])); + status.setState(states[1]); + model->updateState(status); + QVERIFY(model->rowCount() == 2); + } + + // Check them all + for(int i = 0; i < 3; i++) { + if(states[i] != VcsStatusInfo::ItemUpToDate && states[i] != VcsStatusInfo::ItemUnknown) { + QStandardItem* item = model->fileItemForUrl(filenames[i]); + QVERIFY(item); + VcsStatusInfo info = VcsFileChangesModel::statusInfo(item); + QVERIFY(info.url().toLocalFile() == filenames[i]); + QVERIFY(info.state() == states[i]); + } + } + + // Delete them all + model->removeRows(0, model->rowCount()); + QVERIFY(model->rowCount() == 0); + + // Pull it all again + for(int i = 0; i < 3; i++) { + status.setUrl(KUrl(filenames[i])); + status.setState(states[i]); + model->updateState(status); + } + QVERIFY(model->rowCount() == 2); +} + +QTEST_KDEMAIN(ModelsTest, GUI); diff --git a/vcs/models/tests/modelstest.h b/vcs/models/tests/modelstest.h new file mode 100644 index 0000000000..81587d6859 --- /dev/null +++ b/vcs/models/tests/modelstest.h @@ -0,0 +1,35 @@ +/*************************************************************************** + * Copyright 2011 Andrey Batyiev * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License or (at your option) version 3 or any later version * + * accepted by the membership of KDE e.V. (or its successor approved * + * by the membership of KDE e.V.), which shall act as a proxy * + * defined in Section 14 of version 3 of the license. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + ***************************************************************************/ + +#ifndef MODELSTEST_H +#define MODELSTEST_H + +#include + + +class ModelsTest : public QObject +{ + Q_OBJECT +private slots: + void testInit(); + void testVcsFileChangesModel(); +}; + +#endif // MODELSTEST_H diff --git a/vcs/models/vcsfilechangesmodel.cpp b/vcs/models/vcsfilechangesmodel.cpp new file mode 100644 index 0000000000..ff87e8b8bd --- /dev/null +++ b/vcs/models/vcsfilechangesmodel.cpp @@ -0,0 +1,184 @@ +/* This file is part of KDevelop + Copyright 2010 Aleix Pol + + Splitted into separate class + Copyright 2011 Andrey Batyiev + + 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 +#include +#include +#include + +#include +#include + +#include + +#include "vcsfilechangesmodel.h" + +namespace KDevelop +{ + +static QString stateToString(KDevelop::VcsStatusInfo::State state) +{ + switch(state) + { + case KDevelop::VcsStatusInfo::ItemAdded: + return i18nc("file was added to versioncontrolsystem", "Added"); + case KDevelop::VcsStatusInfo::ItemDeleted: + return i18nc("file was deleted from versioncontrolsystem", "Deleted"); + case KDevelop::VcsStatusInfo::ItemHasConflicts: + return i18nc("file is confilicting (versioncontrolsystem)", "Has Conflicts"); + case KDevelop::VcsStatusInfo::ItemModified: + return i18nc("version controlled file was modified", "Modified"); + case KDevelop::VcsStatusInfo::ItemUpToDate: + return i18nc("file is up to date in versioncontrolsystem", "Up To Date"); + case KDevelop::VcsStatusInfo::ItemUnknown: + case KDevelop::VcsStatusInfo::ItemUserState: + return i18nc("file is not known to versioncontrolsystem", "Unknown"); + } + return i18nc("Unknown VCS file status, probably a backend error", "?"); +} + +static KIcon stateToIcon(KDevelop::VcsStatusInfo::State state) +{ + switch(state) + { + case KDevelop::VcsStatusInfo::ItemAdded: + return KIcon("vcs-added"); + case KDevelop::VcsStatusInfo::ItemDeleted: + return KIcon("vcs-removed"); + case KDevelop::VcsStatusInfo::ItemHasConflicts: + return KIcon("vcs-conflicting"); + case KDevelop::VcsStatusInfo::ItemModified: + return KIcon("vcs-locally-modified"); + case KDevelop::VcsStatusInfo::ItemUpToDate: + return KIcon("vcs-normal"); + case KDevelop::VcsStatusInfo::ItemUnknown: + case KDevelop::VcsStatusInfo::ItemUserState: + return KIcon("unknown"); + } + return KIcon("dialog-error"); +} + +class VcsFileChangesModelPrivate +{ +public: + bool allowSelection; +}; + +VcsFileChangesModel::VcsFileChangesModel(QObject *parent, bool allowSelection) + : QStandardItemModel(parent), d(new VcsFileChangesModelPrivate) +{ + setColumnCount(2); + setHeaderData(0, Qt::Horizontal, i18n("Filename")); + setHeaderData(1, Qt::Horizontal, i18n("Status")); + d->allowSelection = allowSelection; +} + +int VcsFileChangesModel::updateState(QStandardItem *parent, const KDevelop::VcsStatusInfo &status) +{ + QStandardItem* it1=0; + QStandardItem* itStatus; + + it1=fileItemForUrl(parent, status.url()); + + if(status.state()==VcsStatusInfo::ItemUnknown || status.state()==VcsStatusInfo::ItemUpToDate) { + if(it1) + parent->removeRow(it1->row()); + return -1; + } else { + if(!it1) { + QString path = ICore::self()->projectController()->prettyFileName(status.url(), KDevelop::IProjectController::FormatPlain); + KIcon icon(KMimeType::findByUrl(status.url(), 0, false, true)->iconName(status.url())); + it1 = new QStandardItem(icon, path); + itStatus = new QStandardItem; + + if(d->allowSelection) { + it1->setCheckable(true); + it1->setCheckState(status.state() == VcsStatusInfo::ItemUnknown ? Qt::Unchecked : Qt::Checked); + } + + parent->appendRow(QList() << it1 << itStatus); + } else { + QStandardItem *parent = it1->parent(); + if(parent == 0) + parent = invisibleRootItem(); + itStatus = parent->child(it1->row(), 1); + } + + QString text = stateToString(status.state()); + if(itStatus->text()!=text) { + itStatus->setText(text); + itStatus->setIcon(stateToIcon(status.state())); + } + it1->setData(qVariantFromValue(status), VcsStatusInfoRole); + return it1->row(); + } +} + +QStandardItem* VcsFileChangesModel::fileItemForUrl(QStandardItem* parent, const QUrl& url) +{ + for(int i=0; irowCount(); i++) { + QStandardItem* curr=parent->child(i); + + if(curr->data(VcsStatusInfoRole).value().url()==url) { + return curr; + } + } + + return 0; +} + +QList VcsFileChangesModel::checkedStatuses(QStandardItem *parent) const +{ + QList ret; + + if(!d->allowSelection) + return ret; + + for(int i = 0; i < parent->rowCount(); i++) { + QStandardItem* item = parent->child(i); + if(item->checkState() == Qt::Checked) { + ret << statusInfo(item); + } + } + + return ret; +} + +QList VcsFileChangesModel::checkedUrls(QStandardItem *parent) const +{ + QList ret; + + if(!d->allowSelection) + return ret; + + for(int i = 0; i < parent->rowCount(); i++) { + QStandardItem* item = parent->child(i); + if(item->checkState() == Qt::Checked) { + ret << statusInfo(item).url(); + } + } + + return ret; +} + + +} diff --git a/vcs/models/vcsfilechangesmodel.h b/vcs/models/vcsfilechangesmodel.h new file mode 100644 index 0000000000..af02e800ac --- /dev/null +++ b/vcs/models/vcsfilechangesmodel.h @@ -0,0 +1,127 @@ +/* This file is part of KDevelop + Copyright 2010 Aleix Pol + + Splitted into separate class + Copyright 2011 Andrey Batyiev + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef FILECHANGESMODEL_H +#define FILECHANGESMODEL_H + +#include + +#include + +#include "../vcsexport.h" + +class KUrl; + +namespace KDevelop +{ +class VcsStatusInfo; + +/** + * This class holds and represents information about changes in files. + * Also it is possible to provide tree like models by inheriting this class, see protected members. + * All stuff should be pulled in by @p updateState. + */ + +class KDEVPLATFORMVCS_EXPORT VcsFileChangesModel : public QStandardItemModel +{ + Q_OBJECT +public: + /** + * Constructor for class. + * @param allowSelection if true, model will show checkboxes on items. + */ + VcsFileChangesModel(QObject *parent, bool allowSelection = false); + enum ItemRoles { VcsStatusInfoRole = Qt::UserRole+1 }; + + /** + * Returns item for particular url. + */ + QStandardItem* fileItemForUrl(const QUrl &url) { + return fileItemForUrl(invisibleRootItem(), url); + } + + /** + * Returns list of currently checked statuses. + */ + QList checkedStatuses() const { + return checkedStatuses(invisibleRootItem()); + } + + /** + * Returns list of currently checked urls. + */ + QList checkedUrls() const { + return checkedUrls(invisibleRootItem()); + } + + + /** + * Simple helper to get VcsStatusInfo. + */ + static VcsStatusInfo statusInfo(const QModelIndex &i) { + return i.data(VcsStatusInfoRole).value(); + } + + /** + * Simple helper to get VcsStatusInfo. + */ + static VcsStatusInfo statusInfo(const QStandardItem *item) { + return item->data(VcsStatusInfoRole).value(); + } + +public slots: + /** + * Used to post update of status of some file. Any status except UpToDate + * and Unknown will update (or add) item representation. + */ + void updateState(const KDevelop::VcsStatusInfo &status) { + updateState(invisibleRootItem(), status); + } + +protected: + /** + * Post update of status of some file. + * @return changed row or -1 if row is deleted + */ + int updateState(QStandardItem *parent, const KDevelop::VcsStatusInfo &status); + + /** + * Returns item for particular url. + */ + static QStandardItem* fileItemForUrl(QStandardItem *parent, const QUrl &url); + + /** + * Returns list of currently checked statuses. + */ + QList checkedStatuses(QStandardItem *parent) const; + + /** + * Returns list of currently checked urls. + */ + QList checkedUrls(QStandardItem *parent) const; + +private: + class VcsFileChangesModelPrivate *const d; +}; +} + +#endif // FILECHANGESMODEL_H diff --git a/vcs/widgets/vcscommitdialog.cpp b/vcs/widgets/vcscommitdialog.cpp index fca9e94245..607d5aa7cb 100644 --- a/vcs/widgets/vcscommitdialog.cpp +++ b/vcs/widgets/vcscommitdialog.cpp @@ -1,181 +1,111 @@ /*************************************************************************** * Copyright 2007 Dukju Ahn * * Copyright 2008 Evgeniy Ivanov * * Copyright 2011 Andrey Batyiev * * 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 "vcscommitdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../vcsjob.h" #include "../interfaces/ibasicversioncontrol.h" #include "../interfaces/idistributedversioncontrol.h" #include "../interfaces/icentralizedversioncontrol.h" #include "../vcsstatusinfo.h" +#include "../models/vcsfilechangesmodel.h" #include "ui_vcscommitdialog.h" #include #include namespace KDevelop { class VcsCommitDialogPrivate { public: - - VcsCommitDialogPrivate(VcsCommitDialog* dialog) - : dlg(dialog) - {} - - void insertRow( const QString& state, const KUrl& url, - const KStatefulBrush &foregroundColor = KStatefulBrush(KColorScheme::View, KColorScheme::NormalText), - Qt::CheckState checkstate = Qt::Checked) - { - QStringList strings; - strings << "" << state << ICore::self()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain); - QTreeWidgetItem *item = new QTreeWidgetItem( ui.files, strings ); - item->setData(0, Qt::UserRole, url); - item->setForeground(2, foregroundColor.brush(dlg)); - item->setCheckState(0, checkstate); - } - - QList< KUrl > selection() { - if(!m_selection.isEmpty()) - return m_selection; - - QList< KUrl > ret; - - QTreeWidgetItemIterator it( ui.files, QTreeWidgetItemIterator::Checked ); - for( ; *it; ++it ){ - QVariant v = (*it)->data(0, Qt::UserRole); - Q_ASSERT(v.canConvert()); - ret << v.value(); - } - - return ret; - } - - VcsCommitDialog* dlg; Ui::VcsCommitDialog ui; - QList< KUrl > m_selection; IPatchSource* m_patchSource; + VcsFileChangesModel* m_model; }; VcsCommitDialog::VcsCommitDialog( IPatchSource *patchSource, QWidget *parent ) - : KDialog( parent ), d(new VcsCommitDialogPrivate(this)) + : KDialog( parent ), d(new VcsCommitDialogPrivate()) { d->ui.setupUi( mainWidget() ); QWidget *customWidget = patchSource->customWidget(); if( customWidget ) { d->ui.gridLayout->addWidget( customWidget, 0, 0, 1, 2 ); } setButtons( KDialog::Ok | KDialog::Cancel ); - d->ui.files->resizeColumnToContents(0); - d->ui.files->resizeColumnToContents(1); d->m_patchSource = patchSource; + d->m_model = new VcsFileChangesModel( this, true ); + d->ui.files->setModel( d->m_model ); connect(this, SIGNAL( okClicked() ), SLOT( ok() ) ); connect(this, SIGNAL( cancelClicked() ), SLOT( cancel() ) ); } VcsCommitDialog::~VcsCommitDialog() { delete d; } void VcsCommitDialog::setRecursive( bool recursive ) { d->ui.recursiveChk->setChecked( recursive ); } void VcsCommitDialog::setCommitCandidates( const QVariant& statuses ) { - KStatefulBrush deletedRed(KColorScheme::View, KColorScheme::NegativeText); - KStatefulBrush newGreen(KColorScheme::View, KColorScheme::ActiveText); - foreach( const QVariant &var, statuses.toList() ) { VcsStatusInfo info = qVariantValue( var ); - - QString state; - KStatefulBrush brush(KColorScheme::View, KColorScheme::NormalText); - Qt::CheckState checked = Qt::Checked; - - switch( info.state() ) - { - case VcsStatusInfo::ItemAdded: - state = i18nc("file was added to versioncontrolsystem", "Added"); - brush = newGreen; - break; - case VcsStatusInfo::ItemDeleted: - state = i18nc("file was deleted from versioncontrolsystem", "Deleted"); - brush = deletedRed; - break; - case VcsStatusInfo::ItemModified: - state = i18nc("version controlled file was modified", "Modified"); - break; - case VcsStatusInfo::ItemUnknown: - state = i18nc("file is not known to versioncontrolsystem", "Unknown"); - brush = newGreen; - checked = Qt::Unchecked; - break; - default: - break; - } - - if(!state.isEmpty()) - { - d->insertRow(state, info.url(), brush, checked); - } - } - if( d->ui.files->topLevelItemCount() == 0 ) - { - reject(); + d->m_model->updateState( info ); } } bool VcsCommitDialog::recursive() const { return d->ui.recursiveChk->isChecked(); } void VcsCommitDialog::ok() { - if( d->m_patchSource->finishReview(d->selection()) ) + if( d->m_patchSource->finishReview( d->m_model->checkedUrls() ) ) { deleteLater(); } } void VcsCommitDialog::cancel() { d->m_patchSource->cancelReview(); } } diff --git a/vcs/widgets/vcscommitdialog.ui b/vcs/widgets/vcscommitdialog.ui index 5f09c95b05..f32f8a2a95 100644 --- a/vcs/widgets/vcscommitdialog.ui +++ b/vcs/widgets/vcscommitdialog.ui @@ -1,72 +1,54 @@ VcsCommitDialog false 0 0 497 432 Select Files to commit Commit Files: Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop Recursive - - - Select Files to commit + + + QAbstractItemView::NoEditTriggers - - 0 + + false - - true - - - - - - - - - Status - - - - - Files to commit - -