/*************************************************************************** kompare_part.cpp - description ------------------- begin : Sun Mar 4 2001 copyright : (C) 2001-2004 Otto Bruggeman (C) 2001-2003 John Firebaugh (C) 2004 Jeff Snyder email : otto.bruggeman@home.nl jfirebaugh@kde.org jeff@caffeinated.me.uk ****************************************************************************/ /*************************************************************************** ** ** 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 "kompare_qsplitter.h" // make sure we get there first #include #include #include #include #include #include #include #include #include #include #include #include #include //#include #include #include "diffmodel.h" #include "komparelistview.h" #include "kompareconnectwidget.h" #include "diffsettings.h" #include "viewsettings.h" #include "kompareprefdlg.h" #include "komparesaveoptionswidget.h" #include "komparesplitter.h" #include "kompare_part.h" typedef KParts::GenericFactory KomparePartFactory; K_EXPORT_COMPONENT_FACTORY( libkomparepart, KomparePartFactory ) ViewSettings* KomparePart::m_viewSettings = 0L; DiffSettings* KomparePart::m_diffSettings = 0L; KomparePart::KomparePart( TQWidget *parentWidget, const char *widgetName, TQObject *parent, const char *name, const TQStringList & /*args*/ ) : KParts::ReadWritePart(parent, name), m_tempDiff( 0 ), m_info() { // we need an instance setInstance( KomparePartFactory::instance() ); if( !m_viewSettings ) { m_viewSettings = new ViewSettings( 0 ); } if( !m_diffSettings ) { m_diffSettings = new DiffSettings( 0 ); } readProperties( kapp->config() ); // This creates the "Model creator" and connects the signals and slots m_modelList = new Diff2::KompareModelList( m_diffSettings, m_info, this, "komparemodellist" ); connect( m_modelList, TQT_SIGNAL(status( Kompare::Status )), this, TQT_SLOT(slotSetStatus( Kompare::Status )) ); connect( m_modelList, TQT_SIGNAL(setStatusBarModelInfo( int, int, int, int, int )), this, TQT_SIGNAL(setStatusBarModelInfo( int, int, int, int, int )) ); connect( m_modelList, TQT_SIGNAL(error( TQString )), this, TQT_SLOT(slotShowError( TQString )) ); connect( m_modelList, TQT_SIGNAL(applyAllDifferences( bool )), this, TQT_SLOT(updateActions()) ); connect( m_modelList, TQT_SIGNAL(applyDifference( bool )), this, TQT_SLOT(updateActions()) ); connect( m_modelList, TQT_SIGNAL(applyAllDifferences( bool )), this, TQT_SIGNAL(appliedChanged()) ); connect( m_modelList, TQT_SIGNAL(applyDifference( bool )), this, TQT_SIGNAL(appliedChanged()) ); connect( m_modelList, TQT_SIGNAL( setModified( bool ) ), this, TQT_SLOT( slotSetModified( bool ) ) ); // This is the stuff to connect the "interface" of the kompare part to the model inside connect( m_modelList, TQT_SIGNAL(modelsChanged(const Diff2::DiffModelList*)), this, TQT_SIGNAL(modelsChanged(const Diff2::DiffModelList*)) ); connect( m_modelList, TQT_SIGNAL(setSelection(const Diff2::DiffModel*, const Diff2::Difference*)), this, TQT_SIGNAL(setSelection(const Diff2::DiffModel*, const Diff2::Difference*)) ); connect( this, TQT_SIGNAL(selectionChanged(const Diff2::DiffModel*, const Diff2::Difference*)), m_modelList, TQT_SLOT(slotSelectionChanged(const Diff2::DiffModel*, const Diff2::Difference*)) ); connect( m_modelList, TQT_SIGNAL(setSelection(const Diff2::Difference*)), this, TQT_SIGNAL(setSelection(const Diff2::Difference*)) ); connect( this, TQT_SIGNAL(selectionChanged(const Diff2::Difference*)), m_modelList, TQT_SLOT(slotSelectionChanged(const Diff2::Difference*)) ); connect( m_modelList, TQT_SIGNAL(applyDifference(bool)), this, TQT_SIGNAL(applyDifference(bool)) ); connect( m_modelList, TQT_SIGNAL(applyAllDifferences(bool)), this, TQT_SIGNAL(applyAllDifferences(bool)) ); connect( m_modelList, TQT_SIGNAL(applyDifference(const Diff2::Difference*, bool)), this, TQT_SIGNAL(applyDifference(const Diff2::Difference*, bool)) ); // This creates the splitterwidget and connects the signals and slots m_splitter = new KompareSplitter ( m_viewSettings, parentWidget, widgetName ); connect( m_modelList, TQT_SIGNAL(setSelection(const Diff2::DiffModel*, const Diff2::Difference*)), m_splitter, TQT_SLOT(slotSetSelection(const Diff2::DiffModel*, const Diff2::Difference*)) ); // connect( m_splitter, TQT_SIGNAL(selectionChanged(const Diff2::Difference*, const Diff2::Difference*)), // m_modelList, TQT_SLOT(slotSelectionChanged(const Diff2::Difference*, const Diff2::Difference*)) ); connect( m_modelList, TQT_SIGNAL(setSelection(const Diff2::Difference*)), m_splitter, TQT_SLOT(slotSetSelection(const Diff2::Difference*)) ); connect( m_splitter, TQT_SIGNAL(selectionChanged(const Diff2::Difference*)), m_modelList, TQT_SLOT(slotSelectionChanged(const Diff2::Difference*)) ); connect( m_modelList, TQT_SIGNAL(applyDifference(bool)), m_splitter, TQT_SLOT(slotApplyDifference(bool)) ); connect( m_modelList, TQT_SIGNAL(applyAllDifferences(bool)), m_splitter, TQT_SLOT(slotApplyAllDifferences(bool)) ); connect( m_modelList, TQT_SIGNAL(applyDifference(const Diff2::Difference*, bool)), m_splitter, TQT_SLOT(slotApplyDifference(const Diff2::Difference*, bool)) ); connect( this, TQT_SIGNAL(configChanged()), m_splitter, TQT_SIGNAL(configChanged()) ); // notify the part that this is our internal widget setWidget( m_splitter->parentWidget() ); setupActions(); // set our XML-UI resource file setXMLFile( "komparepartui.rc" ); // we are read-write by default -> uhm what if we are opened by lets say konq in RO mode ? // Then we should not be doing this... setReadWrite( true ); // we are not modified since we haven't done anything yet setModified( false ); } KomparePart::~KomparePart() { // This is the only place allowed to call cleanUpTemporaryFiles // because before there might still be a use for them (when swapping) cleanUpTemporaryFiles(); } void KomparePart::setupActions() { // create our actions m_saveAll = new KAction( i18n("Save &All"), "save_all", 0, this, TQT_SLOT(saveAll()), actionCollection(), "file_save_all" ); m_saveDiff = new KAction( i18n("Save .&diff..."), 0, this, TQT_SLOT(saveDiff()), actionCollection(), "file_save_diff" ); m_swap = new KAction( i18n( "Swap Source with Destination" ), 0, this, TQT_SLOT(slotSwap()), actionCollection(), "file_swap" ); m_diffStats = new KAction( i18n( "Show Statistics" ), 0, this, TQT_SLOT(slotShowDiffstats()), actionCollection(), "file_diffstats" ); KStdAction::preferences(this, TQT_SLOT(optionsPreferences()), actionCollection()); } void KomparePart::updateActions() { m_saveAll->setEnabled ( m_modelList->isModified() ); m_saveDiff->setEnabled ( m_modelList->mode() == Kompare::ComparingFiles || m_modelList->mode() == Kompare::ComparingDirs ); m_swap->setEnabled ( m_modelList->mode() == Kompare::ComparingFiles || m_modelList->mode() == Kompare::ComparingDirs ); m_diffStats->setEnabled( m_modelList->modelCount() > 0 ); } void KomparePart::setEncoding( const TQString& encoding ) { kdDebug() << "Encoding: " << encoding << endl; m_modelList->setEncoding( encoding ); } bool KomparePart::openDiff( const KURL& url ) { kdDebug(8103) << "Url = " << url.url() << endl; emit kompareInfo( &m_info ); m_info.mode = Kompare::ShowingDiff; m_info.source = url; bool result = false; m_info.localSource = fetchURL( url ); if ( !m_info.localSource.isEmpty() ) { kdDebug(8103) << "Download succeeded " << endl; result = m_modelList->openDiff( m_info.localSource ); updateActions(); updateCaption(); updateStatus(); } else { kdDebug(8103) << "Download failed !" << endl; } return result; } bool KomparePart::openDiff( const TQString& diffOutput ) { bool value = false; emit kompareInfo( &m_info ); m_info.mode = Kompare::ShowingDiff; if ( m_modelList->parseDiffOutput( diffOutput ) == 0 ) { value = true; m_modelList->show(); updateActions(); updateCaption(); updateStatus(); } return value; } bool KomparePart::openDiff3( const KURL& diff3Url ) { // FIXME: Implement this !!! kdDebug(8103) << "Not implemented yet. Filename is: " << diff3Url.url() << endl; return false; } bool KomparePart::openDiff3( const TQString& diff3Output ) { // FIXME: Implement this !!! kdDebug(8103) << "Not implemented yet. diff3 output is: " << endl; kdDebug(8103) << diff3Output << endl; return false; } bool KomparePart::exists( const TQString& url ) { TQFileInfo fi( url ); return fi.exists(); } const TQString KomparePart::fetchURL( const KURL& url ) { TQString tempFileName( "" ); if ( !url.isLocalFile() ) { if ( ! TDEIO::NetAccess::download( url, tempFileName, widget() ) ) { slotShowError( i18n( "The URL %1 cannot be downloaded." ).arg( url.prettyURL() ) ); tempFileName = ""; } return tempFileName; } else { // is Local already, check if exists if ( exists( url.path() ) ) return url.path(); else { slotShowError( i18n( "The URL %1 does not exist on your system." ).arg( url.prettyURL() ) ); return tempFileName; } } } void KomparePart::cleanUpTemporaryFiles() { // i hope a local file will not be removed if it was not downloaded... if ( !m_info.localSource.isEmpty() ) TDEIO::NetAccess::removeTempFile( m_info.localSource ); if ( !m_info.localDestination.isEmpty() ) TDEIO::NetAccess::removeTempFile( m_info.localDestination ); } void KomparePart::compare( const KURL& source, const KURL& destination ) { m_info.source = source; m_info.destination = destination; m_info.localSource = fetchURL( source ); m_info.localDestination = fetchURL( destination ); emit kompareInfo( &m_info ); if ( !m_info.localSource.isEmpty() && !m_info.localDestination.isEmpty() ) { m_modelList->compare( m_info.localSource, m_info.localDestination ); updateActions(); updateCaption(); updateStatus(); } } void KomparePart::compareFiles( const KURL& sourceFile, const KURL& destinationFile ) { emit kompareInfo( &m_info ); m_info.mode = Kompare::ComparingFiles; m_info.source = sourceFile; m_info.destination = destinationFile; m_info.localSource = fetchURL( sourceFile ); m_info.localDestination = fetchURL( destinationFile ); if ( !m_info.localSource.isEmpty() && !m_info.localDestination.isEmpty() ) { m_modelList->compareFiles( m_info.localSource, m_info.localDestination ); updateActions(); updateCaption(); updateStatus(); } } void KomparePart::compareDirs( const KURL& sourceDirectory, const KURL& destinationDirectory ) { emit kompareInfo( &m_info ); m_info.mode = Kompare::ComparingDirs; m_info.source = sourceDirectory; m_info.destination = destinationDirectory; m_info.localSource = fetchURL( sourceDirectory ); m_info.localDestination = fetchURL( destinationDirectory ); if ( !m_info.localSource.isEmpty() && !m_info.localDestination.isEmpty() ) { m_modelList->compareDirs( m_info.localSource, m_info.localDestination ); updateActions(); updateCaption(); updateStatus(); } } void KomparePart::compare3Files( const KURL& /*originalFile*/, const KURL& /*changedFile1*/, const KURL& /*changedFile2*/ ) { // FIXME: actually implement this some day :) updateActions(); updateCaption(); updateStatus(); } void KomparePart::openFileAndDiff( const KURL& file, const KURL& diffFile ) { emit kompareInfo( &m_info ); m_info.source = file; m_info.destination = diffFile; m_info.localSource = fetchURL( file ); m_info.localDestination = fetchURL( diffFile ); m_info.mode = Kompare::BlendingFile; if ( !m_info.localSource.isEmpty() && !m_info.localDestination.isEmpty() ) { m_modelList->openFileAndDiff( m_info.localSource, m_info.localDestination ); updateActions(); updateCaption(); updateStatus(); } } void KomparePart::openDirAndDiff ( const KURL& dir, const KURL& diffFile ) { emit kompareInfo( &m_info ); m_info.source = dir; m_info.destination = diffFile; m_info.localSource = fetchURL( dir ); m_info.localDestination = fetchURL( diffFile ); m_info.mode = Kompare::BlendingDir; if ( !m_info.localSource.isEmpty() && !m_info.localDestination.isEmpty() ) { m_modelList->openDirAndDiff( m_info.localSource, m_info.localDestination ); updateActions(); updateCaption(); updateStatus(); } } bool KomparePart::openFile() { // This is called from openURL // This is a little inefficient but i will do it anyway openDiff( m_url ); return true; } bool KomparePart::saveAll() { bool result = m_modelList->saveAll(); updateActions(); updateCaption(); updateStatus(); return result; } void KomparePart::saveDiff() { KDialogBase* dlg = new KDialogBase( widget(), "save_options", true /* modal */, i18n("Diff Options"), KDialogBase::Ok|KDialogBase::Cancel ); KompareSaveOptionsWidget* w = new KompareSaveOptionsWidget( m_info.localSource, m_info.localDestination, m_diffSettings, dlg ); dlg->setMainWidget( w ); dlg->setButtonOK( KStdGuiItem::save() ); if( dlg->exec() ) { w->saveOptions(); TDEConfig* config = instance()->config(); saveProperties( config ); config->sync(); while ( 1 ) { KURL url = KFileDialog::getSaveURL( m_info.destination.url(), i18n("*.diff *.dif *.patch|Patch Files"), widget(), i18n( "Save .diff" ) ); if ( TDEIO::NetAccess::exists( url, false, widget() ) ) { int result = KMessageBox::warningYesNoCancel( widget(), i18n("The file exists or is write-protected; do you want to overwrite it?"), i18n("File Exists"), i18n("Overwrite"), i18n("Do Not Overwrite") ); if ( result == KMessageBox::Cancel ) { break; } else if ( result == KMessageBox::No ) { continue; } else { kdDebug(8103) << "URL = " << url.prettyURL() << endl; kdDebug(8103) << "Directory = " << w->directory() << endl; kdDebug(8103) << "DiffSettings = " << m_diffSettings << endl; m_modelList->saveDiff( url.url(), w->directory(), m_diffSettings ); break; } } else { kdDebug(8103) << "URL = " << url.prettyURL() << endl; kdDebug(8103) << "Directory = " << w->directory() << endl; kdDebug(8103) << "DiffSettings = " << m_diffSettings << endl; m_modelList->saveDiff( url.url(), w->directory(), m_diffSettings ); break; } } } delete dlg; } TDEAboutData *KomparePart::createAboutData() { TDEAboutData *about = new TDEAboutData("kompare", I18N_NOOP("KomparePart"), "3.2"); about->addAuthor("John Firebaugh", "Author", "jfirebaugh@kde.org"); about->addAuthor("Otto Bruggeman", "Author", "otto.bruggeman@home.nl" ); return about; } void KomparePart::slotSetStatus( enum Kompare::Status status ) { updateActions(); switch( status ) { case Kompare::RunningDiff: emit setStatusBarText( i18n( "Running diff..." ) ); break; case Kompare::Parsing: emit setStatusBarText( i18n( "Parsing diff output..." ) ); break; case Kompare::FinishedParsing: updateStatus(); break; case Kompare::FinishedWritingDiff: updateStatus(); emit diffURLChanged(); break; default: break; } } void KomparePart::updateCaption() { TQString source = m_info.source.prettyURL(); TQString destination = m_info.destination.prettyURL(); TQString text; switch ( m_info.mode ) { case Kompare::ComparingFiles : case Kompare::ComparingDirs : case Kompare::BlendingFile : case Kompare::BlendingDir : text = source + ":" + destination; break; case Kompare::ShowingDiff : text = source; break; default: break; } emit setWindowCaption( text ); } void KomparePart::updateStatus() { TQString source = m_info.source.prettyURL(); TQString destination = m_info.destination.prettyURL(); TQString text; switch ( m_info.mode ) { case Kompare::ComparingFiles : text = i18n( "Comparing file %1 with file %2" ) .arg( source ) .arg( destination ); break; case Kompare::ComparingDirs : text = i18n( "Comparing files in %1 with files in %2" ) .arg( source ) .arg( destination ); break; case Kompare::ShowingDiff : text = i18n( "Viewing diff output from %1" ).arg( source ); break; case Kompare::BlendingFile : text = i18n( "Blending diff output from %1 into file %2" ) .arg( source ) .arg( destination ); break; case Kompare::BlendingDir : text = i18n( "Blending diff output from %1 into folder %2" ) .arg( m_info.source.prettyURL() ) .arg( m_info.destination.prettyURL() ); break; default: break; } emit setStatusBarText( text ); } void KomparePart::slotShowError( TQString error ) { KMessageBox::error( widget(), error ); } void KomparePart::slotSwap() { if ( isModified() ) { int query = KMessageBox::warningYesNoCancel ( widget(), i18n( "You have made changes to the destination file(s).\n" "Would you like to save them?" ), i18n( "Save Changes?" ), KStdGuiItem::save(), KStdGuiItem::discard() ); if ( query == KMessageBox::Yes ) m_modelList->saveAll(); if ( query == KMessageBox::Cancel ) return; // Abort prematurely so no swapping } // Swap the info in the Kompare::Info struct KURL url = m_info.source; m_info.source = m_info.destination; m_info.destination = url; TQString string = m_info.localSource; m_info.localSource = m_info.localDestination; m_info.localDestination = string; // Update window caption and statusbar text updateCaption(); updateStatus(); m_modelList->swap(); } void KomparePart::slotShowDiffstats( void ) { // Fetch all the args needed for komparestatsmessagebox // oldfile, newfile, diffformat, noofhunks, noofdiffs TQString oldFile; TQString newFile; TQString diffFormat; int filesInDiff; int noOfHunks; int noOfDiffs; oldFile = m_modelList->selectedModel() ? m_modelList->selectedModel()->sourceFile() : TQString( "" ); newFile = m_modelList->selectedModel() ? m_modelList->selectedModel()->destinationFile() : TQString( "" ); if ( m_modelList->selectedModel() ) { switch( m_info.format ) { case Kompare::Unified : diffFormat = i18n( "Unified" ); break; case Kompare::Context : diffFormat = i18n( "Context" ); break; case Kompare::RCS : diffFormat = i18n( "RCS" ); break; case Kompare::Ed : diffFormat = i18n( "Ed" ); break; case Kompare::Normal : diffFormat = i18n( "Normal" ); break; case Kompare::UnknownFormat : default: diffFormat = i18n( "Unknown" ); break; } } else { diffFormat = ""; } filesInDiff = m_modelList->modelCount(); noOfHunks = m_modelList->selectedModel() ? m_modelList->selectedModel()->hunkCount() : 0; noOfDiffs = m_modelList->selectedModel() ? m_modelList->selectedModel()->differenceCount() : 0; if ( m_modelList->modelCount() == 0 ) { // no diff loaded yet KMessageBox::information( 0L, i18n( "No diff file, or no 2 files have been diffed. " "Therefore no stats are available."), i18n("Diff Statistics"), TQString(), false ); } else if ( m_modelList->modelCount() == 1 ) { // 1 file in diff, or 2 files compared KMessageBox::information( 0L, i18n( "Statistics:\n" "\n" "Old file: %1\n" "New file: %2\n" "\n" "Format: %3\n" "Number of hunks: %4\n" "Number of differences: %5") .arg(oldFile).arg(newFile).arg(diffFormat) .arg(noOfHunks).arg(noOfDiffs), i18n("Diff Statistics"), TQString(), false ); } else { // more than 1 file in diff, or 2 directories compared KMessageBox::information( 0L, i18n( "Statistics:\n" "\n" "Number of files in diff file: %1\n" "Format: %2\n" "\n" "Current old file: %3\n" "Current new file: %4\n" "\n" "Number of hunks: %5\n" "Number of differences: %6") .arg(filesInDiff).arg(diffFormat).arg(oldFile) .arg(newFile).arg(noOfHunks).arg(noOfDiffs), i18n("Diff Statistics"), TQString(), false ); } } bool KomparePart::queryClose() { if( !isModified() ) return true; int query = KMessageBox::warningYesNoCancel ( widget(), i18n("You have made changes to the destination file(s).\n" "Would you like to save them?" ), i18n( "Save Changes?" ), KStdGuiItem::save(), KStdGuiItem::discard() ); if( query == KMessageBox::Cancel ) return false; if( query == KMessageBox::Yes ) return m_modelList->saveAll(); return true; } int KomparePart::readProperties( TDEConfig *config ) { m_viewSettings->loadSettings( config ); m_diffSettings->loadSettings( config ); emit configChanged(); return 0; } int KomparePart::saveProperties( TDEConfig *config ) { m_viewSettings->saveSettings( config ); m_diffSettings->saveSettings( config ); return 0; } void KomparePart::optionsPreferences() { // show preferences KomparePrefDlg* pref = new KomparePrefDlg( m_viewSettings, m_diffSettings ); connect( pref, TQT_SIGNAL(applyClicked()), this, TQT_SIGNAL(configChanged()) ); if ( pref->exec() ) emit configChanged(); } void KomparePart::slotSetModified( bool modified ) { kdDebug() << "KomparePart::slotSetModified( " << modified << " );" << endl; setModified( modified ); updateActions(); updateCaption(); } #include "kompare_part.moc"