/*************************************************************************** KompareNavTreePart.cpp ---------------------- begin : Sun Mar 4 2001 Copyright 2001-2005,2009 Otto Bruggeman Copyright 2001-2003 John Firebaugh Copyright 2007-2011 Kevin Kofler ****************************************************************************/ /*************************************************************************** ** ** 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 "komparenavtreepart.h" #include #include #include #include #include #include #include #include #include "difference.h" #include "diffmodel.h" #include "diffmodellist.h" #include "komparemodellist.h" #define COL_SOURCE 0 #define COL_DESTINATION 1 #define COL_DIFFERENCE 2 using namespace Diff2; KompareNavTreePart::KompareNavTreePart( QWidget* parentWidget, QObject* parent, const QVariantList& ) : KParts::ReadOnlyPart( parent ), m_splitter( 0 ), m_modelList( 0 ), m_srcDirTree( 0 ), m_destDirTree( 0 ), m_fileList( 0 ), m_changesList( 0 ), m_srcRootItem( 0 ), m_destRootItem( 0 ), m_selectedModel( 0 ), m_selectedDifference( 0 ), m_source( "" ), m_destination( "" ), m_info( 0 ) { m_splitter = new QSplitter( Qt::Horizontal, parentWidget ); setWidget( m_splitter ); m_srcDirTree = new QTreeWidget( m_splitter ); m_srcDirTree->setHeaderLabel( i18n("Source Folder") ); m_srcDirTree->setRootIsDecorated( false ); m_srcDirTree->setSortingEnabled( true ); m_srcDirTree->sortByColumn( 0, Qt::AscendingOrder ); m_destDirTree = new QTreeWidget( m_splitter ); m_destDirTree->setHeaderLabel( i18n("Destination Folder") ); m_destDirTree->setRootIsDecorated( false ); m_destDirTree->setSortingEnabled( true ); m_destDirTree->sortByColumn( 0, Qt::AscendingOrder ); m_fileList = new QTreeWidget( m_splitter ); m_fileList->setHeaderLabels( QStringList() << i18n("Source File") << i18n("Destination File") ); m_fileList->setAllColumnsShowFocus( true ); m_fileList->setRootIsDecorated( false ); m_fileList->setSortingEnabled( true ); m_fileList->sortByColumn( 0, Qt::AscendingOrder ); m_changesList = new QTreeWidget( m_splitter ); m_changesList->setHeaderLabels( QStringList() << i18n("Source Line") << i18n("Destination Line") << i18n("Difference") ); m_changesList->setAllColumnsShowFocus( true ); m_changesList->setRootIsDecorated( false ); m_changesList->setSortingEnabled( true ); m_changesList->sortByColumn( 0, Qt::AscendingOrder ); connect( m_srcDirTree, SIGNAL(currentItemChanged( QTreeWidgetItem*, QTreeWidgetItem* )), this, SLOT(slotSrcDirTreeSelectionChanged( QTreeWidgetItem* )) ); connect( m_destDirTree, SIGNAL(currentItemChanged( QTreeWidgetItem*, QTreeWidgetItem* )), this, SLOT(slotDestDirTreeSelectionChanged( QTreeWidgetItem* )) ); connect( m_fileList, SIGNAL(currentItemChanged( QTreeWidgetItem*, QTreeWidgetItem* )), this, SLOT(slotFileListSelectionChanged( QTreeWidgetItem* )) ); connect( m_changesList, SIGNAL(currentItemChanged( QTreeWidgetItem*, QTreeWidgetItem* )), this, SLOT(slotChangesListSelectionChanged( QTreeWidgetItem* )) ); } KompareNavTreePart::~KompareNavTreePart() { m_modelList = 0; m_selectedModel = 0; m_selectedDifference = 0; } void KompareNavTreePart::slotKompareInfo( struct Kompare::Info* info ) { m_info = info; } void KompareNavTreePart::slotModelsChanged( const DiffModelList* modelList ) { kDebug(8105) << "Models (" << modelList << ") have changed... scanning the models... " << endl; if ( modelList ) { m_modelList = modelList; m_srcDirTree->clear(); m_destDirTree->clear(); m_fileList->clear(); m_changesList->clear(); buildTreeInMemory(); } else { m_modelList = modelList; m_srcDirTree->clear(); m_destDirTree->clear(); m_fileList->clear(); m_changesList->clear(); } } void KompareNavTreePart::buildTreeInMemory() { kDebug(8105) << "BuildTreeInMemory called" << endl; if ( m_modelList->count() == 0 ) { kDebug(8105) << "No models... weird shit..." << endl; return; // avoids a crash on clear() } if ( m_info == 0 ) { kDebug(8105) << "No Info... weird shit..." << endl; return; } QString srcBase; QString destBase; DiffModel* model; model = m_modelList->first(); m_selectedModel = 0L; switch ( m_info->mode ) { case Kompare::ShowingDiff: // BUG: 107489 No common root because it is a multi directory relative path diff // We need to detect this and create a different rootitem / or so or should we always add this? // Trouble we run into is that the directories do not start with a / // so we have an unknown top root dir // Thinking some more about it i guess it is best to use "" as base and simply show some string // like Unknown filesystem path as root text but only in the case of dirs starting without a / srcBase = model->sourcePath(); destBase = model->destinationPath(); // FIXME: these tests will not work on windows, we need something else if ( srcBase[0] != '/' ) srcBase = ""; if ( destBase[0] != '/' ) destBase = ""; break; case Kompare::ComparingFiles: srcBase = model->sourcePath(); destBase = model->destinationPath(); break; case Kompare::ComparingDirs: srcBase = m_info->localSource; if ( !srcBase.endsWith( '/' ) ) srcBase += '/'; destBase = m_info->localDestination; if ( !destBase.endsWith( '/' ) ) destBase += '/'; break; case Kompare::BlendingFile: case Kompare::BlendingDir: default: kDebug(8105) << "Oops needs to implement this..." << endl; } // kDebug(8105) << "srcBase = " << srcBase << endl; // kDebug(8105) << "destBase = " << destBase << endl; m_srcRootItem = new KDirLVI( m_srcDirTree, srcBase ); m_destRootItem = new KDirLVI( m_destDirTree, destBase ); QString srcPath; QString destPath; // Create the tree from the models DiffModelListConstIterator modelIt = m_modelList->begin(); DiffModelListConstIterator mEnd = m_modelList->end(); for ( ; modelIt != mEnd; ++modelIt ) { model = *modelIt; srcPath = model->sourcePath(); destPath = model->destinationPath(); kDebug(8105) << "srcPath = " << srcPath << endl; kDebug(8105) << "destPath = " << destPath << endl; m_srcRootItem->addModel( srcPath, model, &m_modelToSrcDirItemDict ); m_destRootItem->addModel( destPath, model, &m_modelToDestDirItemDict ); } // m_srcDirTree->setSelected( m_srcDirTree->firstChild(), true ); } void KompareNavTreePart::buildDirectoryTree() { // FIXME: afaict this can be deleted // kDebug(8105) << "BuildDirTree called" << endl; } QString KompareNavTreePart::compareFromEndAndReturnSame( const QString& string1, const QString& string2 ) { QString result; int srcLen = string1.length(); int destLen = string2.length(); while ( srcLen != 0 && destLen != 0 ) { if ( string1[--srcLen] == string2[--destLen] ) result.prepend( string1[srcLen] ); else break; } if ( srcLen != 0 && destLen != 0 && result.startsWith( '/' ) ) result = result.remove( 0, 1 ); // strip leading /, we need it later return result; } void KompareNavTreePart::slotSetSelection( const DiffModel* model, const Difference* diff ) { kDebug(8105) << "KompareNavTreePart::slotSetSelection model = " << model << ", diff = " << diff << endl; if ( model == m_selectedModel ) { // model is the same, so no need to update that... if ( diff != m_selectedDifference ) { m_selectedDifference = diff; setSelectedDifference( diff ); } return; } // model is different so we need to find the right dirs, file and changeitems to select // if m_selectedModel == NULL then everything needs to be done as well if ( !m_selectedModel || model->sourcePath() != m_selectedModel->sourcePath() ) { // dirs are different, so we need to update the dirviews as well m_selectedModel = model; m_selectedDifference = diff; setSelectedDir( model ); setSelectedFile( model ); setSelectedDifference( diff ); return; } if ( !m_selectedModel || model->sourceFile() != m_selectedModel->sourceFile() ) { m_selectedModel = model; setSelectedFile( model ); m_selectedDifference = diff; setSelectedDifference( diff ); } } void KompareNavTreePart::setSelectedDir( const DiffModel* model ) { KDirLVI* currentDir; currentDir = m_modelToSrcDirItemDict[ model ]; kDebug(8105) << "Manually setting selection in srcdirtree with currentDir = " << currentDir << endl; m_srcDirTree->blockSignals( true ); m_srcDirTree->setCurrentItem( currentDir ); m_srcDirTree->scrollToItem( currentDir ); m_srcDirTree->blockSignals( false ); currentDir = m_modelToDestDirItemDict[ model ]; kDebug(8105) << "Manually setting selection in destdirtree with currentDir = " << currentDir << endl; m_destDirTree->blockSignals( true ); m_destDirTree->setCurrentItem( currentDir ); m_destDirTree->scrollToItem( currentDir ); m_destDirTree->blockSignals( false ); m_fileList->blockSignals( true ); currentDir->fillFileList( m_fileList, &m_modelToFileItemDict ); m_fileList->blockSignals( false ); } void KompareNavTreePart::setSelectedFile( const DiffModel* model ) { KFileLVI* currentFile; currentFile = m_modelToFileItemDict[ model ]; kDebug(8105) << "Manually setting selection in filelist" << endl; m_fileList->blockSignals( true ); m_fileList->setCurrentItem( currentFile ); m_fileList->scrollToItem( currentFile ); m_fileList->blockSignals( false ); m_changesList->blockSignals( true ); currentFile->fillChangesList( m_changesList, &m_diffToChangeItemDict ); m_changesList->blockSignals( false ); } void KompareNavTreePart::setSelectedDifference( const Difference* diff ) { KChangeLVI* currentDiff; currentDiff = m_diffToChangeItemDict[ diff ]; kDebug(8105) << "Manually setting selection in changeslist to " << currentDiff << endl; m_changesList->blockSignals( true ); m_changesList->setCurrentItem( currentDiff ); m_changesList->scrollToItem( currentDiff ); m_changesList->blockSignals( false ); } void KompareNavTreePart::slotSetSelection( const Difference* diff ) { // kDebug(8105) << "Scotty i need more power !!" << endl; if ( m_selectedDifference != diff ) { // kDebug(8105) << "But sir, i am giving you all she's got" << endl; m_selectedDifference = diff; setSelectedDifference( diff ); } } void KompareNavTreePart::slotSrcDirTreeSelectionChanged( QTreeWidgetItem* item ) { if (!item) return; kDebug(8105) << "Sent by the sourceDirectoryTree with item = " << item << endl; m_srcDirTree->scrollToItem( item ); KDirLVI* dir = static_cast(item); // order the dest tree view to set its selected item to the same as here QString path; // We start with an empty path and after the call path contains the full path path = dir->fullPath( path ); KDirLVI* selItem = m_destRootItem->setSelected( path ); m_destDirTree->blockSignals( true ); m_destDirTree->setCurrentItem( selItem ); m_destDirTree->scrollToItem( selItem ); m_destDirTree->blockSignals( false ); // fill the changes list dir->fillFileList( m_fileList, &m_modelToFileItemDict ); } void KompareNavTreePart::slotDestDirTreeSelectionChanged( QTreeWidgetItem* item ) { if (!item) return; kDebug(8105) << "Sent by the destinationDirectoryTree with item = " << item << endl; m_destDirTree->scrollToItem( item ); KDirLVI* dir = static_cast(item); // order the src tree view to set its selected item to the same as here QString path; // We start with an empty path and after the call path contains the full path path = dir->fullPath( path ); KDirLVI* selItem = m_srcRootItem->setSelected( path ); m_srcDirTree->blockSignals( true ); m_srcDirTree->setCurrentItem( selItem ); m_srcDirTree->scrollToItem( selItem ); m_srcDirTree->blockSignals( false ); // fill the changes list dir->fillFileList( m_fileList, &m_modelToFileItemDict ); } void KompareNavTreePart::slotFileListSelectionChanged( QTreeWidgetItem* item ) { if (!item) return; kDebug(8105) << "Sent by the fileList with item = " << item << endl; KFileLVI* file = static_cast(item); m_selectedModel = file->model(); m_changesList->blockSignals( true ); file->fillChangesList( m_changesList, &m_diffToChangeItemDict ); m_changesList->blockSignals( false ); if ( m_changesList->currentItem() ) { // FIXME: This is ugly... m_selectedDifference = (static_cast(m_changesList->currentItem()))->difference(); } emit selectionChanged( m_selectedModel, m_selectedDifference ); } void KompareNavTreePart::slotChangesListSelectionChanged( QTreeWidgetItem* item ) { if (!item) return; kDebug(8105) << "Sent by the changesList" << endl; KChangeLVI* change = static_cast(item); m_selectedDifference = change->difference(); emit selectionChanged( m_selectedDifference ); } void KompareNavTreePart::slotApplyDifference( bool /*apply*/ ) { KChangeLVI* clvi = m_diffToChangeItemDict[m_selectedDifference]; if ( clvi ) clvi->setDifferenceText(); } void KompareNavTreePart::slotApplyAllDifferences( bool /*apply*/ ) { QHash::ConstIterator it = m_diffToChangeItemDict.constBegin(); QHash::ConstIterator end = m_diffToChangeItemDict.constEnd(); kDebug(8105) << "m_diffToChangeItemDict.count() = " << m_diffToChangeItemDict.count() << endl; for ( ; it != end ; ++it ) { it.value()->setDifferenceText(); } } void KompareNavTreePart::slotApplyDifference( const Difference* diff, bool /*apply*/ ) { // this applies to the currently selected difference KChangeLVI* clvi = m_diffToChangeItemDict[diff]; if ( clvi ) clvi->setDifferenceText(); } void KChangeLVI::setDifferenceText() { QString text; switch( m_difference->type() ) { case Difference::Change: // Shouldn't this simply be diff->sourceLineCount() ? // because you change the _number of lines_ lines in source, not in dest if( m_difference->applied() ) text = i18np( "Applied: Changes made to %1 line undone", "Applied: Changes made to %1 lines undone", m_difference->sourceLineCount() ); else text = i18np( "Changed %1 line", "Changed %1 lines", m_difference->sourceLineCount() ); break; case Difference::Insert: if( m_difference->applied() ) text = i18np( "Applied: Insertion of %1 line undone", "Applied: Insertion of %1 lines undone", m_difference->destinationLineCount() ); else text = i18np( "Inserted %1 line", "Inserted %1 lines", m_difference->destinationLineCount() ); break; case Difference::Delete: if( m_difference->applied() ) text = i18np( "Applied: Deletion of %1 line undone", "Applied: Deletion of %1 lines undone", m_difference->sourceLineCount() ); else text = i18np( "Deleted %1 line", "Deleted %1 lines", m_difference->sourceLineCount() ); break; default: kDebug(8105) << "Unknown or Unchanged enum value when checking for diff->type() in KChangeLVI's constructor" << endl; text = ""; } setText( 2, text ); } KChangeLVI::KChangeLVI( QTreeWidget* parent, Difference* diff ) : QTreeWidgetItem( parent ) { m_difference = diff; setText( 0, QString::number( diff->sourceLineNumber() ) ); setText( 1, QString::number( diff->destinationLineNumber() ) ); setDifferenceText(); } bool KChangeLVI::operator<( const QTreeWidgetItem& item ) const { int column = treeWidget()->sortColumn(); QString text1 = text(column); QString text2 = item.text(column); // Compare the numbers. if (column < 2 && text1.length() != text2.length()) return text1.length() < text2.length(); return text1 < text2; } KChangeLVI::~KChangeLVI() { } KFileLVI::KFileLVI( QTreeWidget* parent, DiffModel* model ) : QTreeWidgetItem( parent ) { m_model = model; QString src = model->sourceFile(); QString dst = model->destinationFile(); setText( 0, src ); setText( 1, dst ); setIcon( 0, SmallIcon( getIcon( src ) ) ); setIcon( 1, SmallIcon( getIcon( dst ) ) ); } bool KFileLVI::hasExtension(const QString& extensions, const QString& fileName) { QStringList extList = extensions.split(' '); foreach (const QString &ext, extList) { if ( fileName.endsWith(ext, Qt::CaseInsensitive) ) { return true; } } return false; } const QString KFileLVI::getIcon(const QString& fileName) { // C++, C if ( hasExtension( ".h .hpp", fileName ) ) { return "text-x-c++hdr"; } if ( hasExtension( ".cpp", fileName ) ) { return "text-x-c++src"; } if ( hasExtension( ".c", fileName ) ) { return "text-x-csrc"; } // Python if ( hasExtension( ".py .pyw", fileName ) ) { return "text-x-python"; } // C# if ( hasExtension( ".cs", fileName ) ) { return "text-x-csharp"; } // Objective-C if ( hasExtension( ".m", fileName ) ) { return "text-x-objcsrc"; } // Java if ( hasExtension( ".java", fileName ) ) { return "text-x-java"; } // Script if ( hasExtension( ".sh", fileName ) ) { return "text-x-script"; } // Makefile if ( hasExtension( ".cmake Makefile", fileName ) ) { return "text-x-makefile"; } // Ada if ( hasExtension( ".ada .ads .adb", fileName ) ) { return "text-x-adasrc"; } // Pascal if ( hasExtension( ".pas", fileName ) ) { return "text-x-pascal"; } // Patch if ( hasExtension( ".diff", fileName ) ) { return "text-x-patch"; } // Tcl if ( hasExtension( ".tcl", fileName ) ) { return "text-x-tcl"; } // Text if ( hasExtension( ".txt", fileName ) ) { return "text-plain"; } // Xml if ( hasExtension( ".xml", fileName ) ) { return "text-xml"; } // unknown or no file extension return "text-plain"; } void KFileLVI::fillChangesList( QTreeWidget* changesList, QHash* diffToChangeItemDict ) { changesList->clear(); diffToChangeItemDict->clear(); DifferenceListConstIterator diffIt = m_model->differences()->constBegin(); DifferenceListConstIterator dEnd = m_model->differences()->constEnd(); for ( ; diffIt != dEnd; ++diffIt ) { KChangeLVI* change = new KChangeLVI( changesList, *diffIt ); diffToChangeItemDict->insert( *diffIt, change ); } changesList->setCurrentItem( changesList->topLevelItem( 0 ) ); } KFileLVI::~KFileLVI() { } KDirLVI::KDirLVI( QTreeWidget* parent, QString& dir ) : QTreeWidgetItem( parent ) { // kDebug(8105) << "KDirLVI (QTreeWidget) constructor called with dir = " << dir << endl; m_rootItem = true; m_dirName = dir; setIcon( 0, SmallIcon( "folder" ) ); setExpanded( true ); if ( m_dirName.isEmpty() ) setText( 0, i18n( "Unknown" ) ); else setText( 0, m_dirName ); } KDirLVI::KDirLVI( KDirLVI* parent, QString& dir ) : QTreeWidgetItem( parent ) { // kDebug(8105) << "KDirLVI (KDirLVI) constructor called with dir = " << dir << endl; m_rootItem = false; m_dirName = dir; setIcon( 0, SmallIcon( "folder" ) ); setExpanded( true ); setText( 0, m_dirName ); } // addModel always removes it own path from the beginning void KDirLVI::addModel( QString& path, DiffModel* model, QHash* modelToDirItemDict ) { // kDebug(8105) << "KDirLVI::addModel called with path = " << path << " from KDirLVI with m_dirName = " << m_dirName << endl; if ( !m_dirName.isEmpty() ) { if ( path.indexOf( m_dirName ) > -1 ) path = path.remove( path.indexOf( m_dirName ), m_dirName.length() ); } // kDebug(8105) << "Path after removal of own dir (\"" << m_dirName << "\") = " << path << endl; if ( path.isEmpty() ) { m_modelList.append( model ); modelToDirItemDict->insert( model, this ); return; } KDirLVI* child; QString dir = path.mid( 0, path.indexOf( "/", 0 ) + 1 ); child = findChild( dir ); if ( !child ) { // does not exist yet so make it // kDebug(8105) << "KDirLVI::addModel creating new KDirLVI because not found" << endl; child = new KDirLVI( this, dir ); } child->addModel( path, model, modelToDirItemDict ); } KDirLVI* KDirLVI::findChild( QString dir ) { // kDebug(8105) << "KDirLVI::findChild called with dir = " << dir << endl; KDirLVI* child; if ( ( child = static_cast(this->child(0)) ) != 0L ) { // has children, check if dir already exists, if so addModel QTreeWidgetItemIterator it(child); while (*it) { child = static_cast(*it); if ( dir == child->dirName() ) return child; ++it; } } return 0L; } void KDirLVI::fillFileList( QTreeWidget* fileList, QHash* modelToFileItemDict ) { fileList->clear(); DiffModelListIterator modelIt = m_modelList.begin(); DiffModelListIterator mEnd = m_modelList.end(); for ( ;modelIt != mEnd; ++modelIt ) { KFileLVI* file = new KFileLVI( fileList, *modelIt ); modelToFileItemDict->insert( *modelIt, file ); } fileList->setCurrentItem( fileList->topLevelItem( 0 ) ); } QString KDirLVI::fullPath( QString& path ) { // if ( !path.isEmpty() ) // kDebug(8105) << "KDirLVI::fullPath called with path = " << path << endl; // else // kDebug(8105) << "KDirLVI::fullPath called with empty path..." << endl; if ( m_rootItem ) // don't bother adding rootItem's dir... return path; path = path.prepend( m_dirName ); KDirLVI* lviParent = dynamic_cast( parent() ); if ( lviParent ) { path = lviParent->fullPath( path ); } return path; } KDirLVI* KDirLVI::setSelected( QString dir ) { // kDebug(8105) << "KDirLVI::setSelected called with dir = " << dir << endl; // root item's dirName is never taken into account... remember that if ( !m_rootItem ) { dir = dir.remove( 0, m_dirName.length() ); } if ( dir.isEmpty() ) { return this; } KDirLVI* child = static_cast(this->child(0)); if ( !child ) return 0L; QTreeWidgetItemIterator it(child); while (*it) { child = static_cast(*it); if ( dir.startsWith( child->dirName() ) ) return child->setSelected( dir ); ++it; } return 0L; } KDirLVI::~KDirLVI() { m_modelList.clear(); } static KAboutData aboutData() { KAboutData about("komparenavtreepart", 0, ki18n("KompareNavTreePart"), "1.2"); about.addAuthor(ki18n("John Firebaugh"), ki18n("Author"), "jfirebaugh@kde.org"); about.addAuthor(ki18n("Otto Bruggeman"), ki18n("Author"), "bruggie@gmail.com" ); return about; } K_PLUGIN_FACTORY(KompareNavTreePartFactory, registerPlugin(); ) K_EXPORT_PLUGIN( KompareNavTreePartFactory(aboutData()) ) #include "komparenavtreepart.moc"