/* ==================================================================== * Copyright (c) 2003-2006, Martin Hauner * http://subcommander.tigris.org * * Subcommander is licensed as described in the file doc/COPYING, which * you should have received as part of this distribution. * ==================================================================== */ //sc #include "config.h" #include "MainWindow.h" #include "Diff3Widget.h" #include "Diff.h" #include "Diff3.h" #include "DiffInfoModel.h" #include "FileSelectDialog.h" #include "ConfigManager.h" #include "settings/FontSettingsWidget.h" #include "settings/FontSettingsInfo.h" #include "sublib/SettingsDialog.h" #include "sublib/AboutDialog.h" #include "sublib/NullTextModel.h" #include "sublib/Line.h" #include "sublib/Utility.h" #include "sublib/MessageBox.h" #include "sublib/DebugSettingsInfo.h" #include "sublib/DebugSettingsWidget.h" #include "sublib/settings/ColorSettingsInfo.h" #include "sublib/settings/ColorSettingsWidget.h" #include "svn/Error.h" #include "util/Error.h" #include "util/utf.h" #include "util/FileData.h" #include "util/iconvstream.h" // qt #include #include #include #include #include #include #include #include #include #include #include #include // sys #include #include enum MenuIds { pmFile, pmiFileSave, pmiFileSaveAs, pmTools }; MainWindow::MainWindow( ConfigManager* cfg, QWidget *parent, const char *name ) : super(parent, name, Qt::WStyle_Customize | Qt::WType_TopLevel | Qt::WStyle_NormalBorder | Qt::WStyle_Title | Qt::WStyle_SysMenu | Qt::WStyle_MinMax | Qt::WResizeNoErase ), _file(0), _config(cfg) { setCaption( _q("subcommander [submerge]") ); QMenuBar* mb = new QMenuBar( this ); { QPopupMenu* m = new QPopupMenu( mb ); mb->insertItem( _q("&File"), m, pmFile ); _fileMenu = m; { m->insertItem( _q("&Open file(s)"), this, SLOT(open()), Qt::CTRL+Qt::Key_O ); m->insertSeparator(); m->insertItem( _q("&Save merge"), this, SLOT(save()), Qt::CTRL+Qt::Key_S, pmiFileSave ); m->insertItem( _q("Save merge &As"), this, SLOT(saveAs()), Qt::CTRL+Qt::Key_A, pmiFileSaveAs ); m->setItemEnabled( pmiFileSave, false ); m->setItemEnabled( pmiFileSaveAs, false ); m->insertSeparator(); m->insertItem( _q("E&xit"), qApp, SLOT(quit()), Qt::CTRL+Qt::Key_Q ); } QPopupMenu* m1b = new QPopupMenu( mb ); mb->insertItem( _q("&Tools"), m1b, pmTools ); { m1b->insertItem( _q("S&ettings"), this, SLOT(settings()), Qt::CTRL+Qt::Key_E ); } QPopupMenu* m2 = new QPopupMenu( mb ); mb->insertItem( _q("&Help"), m2 ); { m2->insertItem( _q("A&bout"), this, SLOT(about()), Qt::CTRL+Qt::Key_B ); } } QIconSet::setIconSize( QIconSet::Small, QSize(20,20) ); QIconSet::setIconSize( QIconSet::Large, QSize(32,32) ); setToolBarsMovable(false); setDockMenuEnabled(true); QToolBar* tb = new QToolBar( _q("submerge diff tool bar"), this ); addToolBar(tb); { #ifdef _MACOSX QFont f = font(); f.setPixelSizeFloat(10.0); tb->setFont(f); // todo write to ini setUsesBigPixmaps(true); setUsesTextLabel(true); #else // todo write to ini setUsesBigPixmaps(false); setUsesTextLabel(false); #endif // _MACOSX QIconSet isetNextDiff; isetNextDiff.setPixmap( getIconDir() + "NextDiff-Normal.png", QIconSet::Automatic, QIconSet::Normal ); isetNextDiff.setPixmap( getIconDir() + "NextDiff-Active.png", QIconSet::Automatic, QIconSet::Active ); isetNextDiff.setPixmap( getIconDir() + "NextDiff-Disabled.png", QIconSet::Automatic, QIconSet::Disabled ); _next = new QAction( _q("next diff"), _q("Ctrl+N"), this ); _next->setStatusTip( _q("scroll to next difference") ); _next->setIconSet( isetNextDiff ); _next->setEnabled(false); _next->addTo(tb); connect( _next, SIGNAL(activated()), SLOT(nextDiff()) ); QIconSet isetPrevDiff; isetPrevDiff.setPixmap( getIconDir() + "PrevDiff-Normal.png", QIconSet::Automatic, QIconSet::Normal ); isetPrevDiff.setPixmap( getIconDir() + "PrevDiff-Active.png", QIconSet::Automatic, QIconSet::Active ); isetPrevDiff.setPixmap( getIconDir() + "PrevDiff-Disabled.png", QIconSet::Automatic, QIconSet::Disabled ); _prev = new QAction( _q("prev diff"), _q("Ctrl+P"), this ); _prev->setStatusTip( _q("scroll to previous difference") ); _prev->setIconSet( isetPrevDiff ); _prev->setEnabled(false); _prev->addTo(tb); connect( _prev, SIGNAL(activated()), SLOT(prevDiff()) ); QIconSet isetMerge; isetMerge.setPixmap( getIconDir() + "Merge-Normal.png", QIconSet::Automatic, QIconSet::Normal ); isetMerge.setPixmap( getIconDir() + "Merge-Active.png", QIconSet::Automatic, QIconSet::Active ); isetMerge.setPixmap( getIconDir() + "Merge-Disabled.png", QIconSet::Automatic, QIconSet::Disabled ); _merge = new QAction( _q("merge"), _q("Ctrl+M"), this ); _merge->setStatusTip( _q("merge differences") ); _merge->setIconSet( isetMerge ); _merge->setEnabled(false); _merge->addTo(tb); connect( _merge, SIGNAL(activated()), SLOT(merge()) ); QIconSet isetWhitespace; isetWhitespace.setPixmap( getIconDir() + "Whitespace-NormalOn.png", QIconSet::Automatic, QIconSet::Normal, QIconSet::On ); isetWhitespace.setPixmap( getIconDir() + "Whitespace-NormalOff.png", QIconSet::Automatic, QIconSet::Normal, QIconSet::Off ); _wspace = new QAction( _q("whitespace"), _q("Ctrl+W"), this ); _wspace->setStatusTip( _q("show whitespace differences, requires reload after toggling it") ); _wspace->setIconSet( isetWhitespace ); _wspace->setToggleAction(true); _wspace->setEnabled(true); _wspace->addTo(tb); connect( _wspace, SIGNAL(toggled(bool)), SLOT(whitespace(bool)) ); QIconSet isetReload; isetReload.setPixmap( getIconDir() + "Reload-Normal.png", QIconSet::Automatic, QIconSet::Normal ); isetReload.setPixmap( getIconDir() + "Reload-Active.png", QIconSet::Automatic, QIconSet::Active ); isetReload.setPixmap( getIconDir() + "Reload-Disabled.png", QIconSet::Automatic, QIconSet::Disabled ); _reload = new QAction( _q("reload"), _q("Ctrl+R"), this ); _reload->setStatusTip( _q("reload the files") ); _reload->setIconSet( isetReload ); _reload->setEnabled(false); _reload->addTo(tb); connect( _reload, SIGNAL(activated()), SLOT(refresh()) ); } QWidget* mw = new QWidget(this); QGridLayout* mwl = new QGridLayout( mw, 1, 1, 0, 2 ); mwl->setSpacing(1); { _dw = new Diff3Widget(_config,mw); mwl->addWidget(_dw,0,0); connect( _dw, SIGNAL(diffChanged(int)), SLOT(diffChanged(int)) ); _dw->connectOriginalDrop( this, SLOT(oDropped(const QString&)) ); _dw->connectModifiedDrop( this, SLOT(mDropped(const QString&)) ); _dw->connectLatestDrop ( this, SLOT(lDropped(const QString&)) ); // status bars statusBar()->setSizeGripEnabled(true); { _sbarDiffCnt = new QLabel(this); _sbarWhitespace = new QLabel(this); _sbarEncoding = new QLabel(this); //_sbar4 = new QLabel(this); //statusBar()->addWidget( _sbar4, 4, false ); statusBar()->addWidget( _sbarEncoding, 0, true ); statusBar()->addWidget( _sbarWhitespace, 0, true ); statusBar()->addWidget( _sbarDiffCnt, 0, true ); } } // set default size setMinimumWidth(500); setMinimumHeight(500); super::setCentralWidget(mw); } MainWindow::~MainWindow() { } bool MainWindow::showDockMenu( const QPoint& globalPos ) { // strange, we can't add anything to the default dock menu //QPopupMenu* dockMenu = createDockWindowMenu(); QPopupMenu* menu = new QPopupMenu(this,_q("submerge")); menu->setCheckable(true); //menu->insertItem( "tool bars", dockMenu ); menu->insertItem( _q("show label"), 10 ); menu->setItemChecked(10,usesTextLabel()); menu->insertItem( _q("large icons"), 20 ); menu->setItemChecked(20,usesBigPixmaps()); int result = menu->exec(globalPos); switch( result ) { case 10: { setUsesTextLabel( ! menu->isItemChecked(10) ); break; } case 20: { setUsesBigPixmaps( ! menu->isItemChecked(20) ); break; } } return true; } Diff3Widget* MainWindow::getDiffWidget() const { return _dw; } void MainWindow::showError( const sc::Error* error ) { if( error == sc::Success ) { return; } QString msg = QString( "" "
" "" "" "" "" ).arg( (const char*)error->getMessage() ); const sc::Error* nested = error->getNested(); while( nested != sc::Success ) { QString nmsg = QString( "" "" "" ).arg( (const char*)nested->getMessage() ); msg += nmsg; nested = nested->getNested(); } msg += "
" "%1" "
" "%1" "
" "" ""; msgCritical( _q("submerge:error"), msg, _q("&Ok") ); } void MainWindow::settings() { SettingsDialog* sd = new SettingsDialog( _q("submerge:settings"), this ); // font settings sd->addSettingsWidget( _q("Font Settings"), new FontSettingsWidget() ); sd->addSettingsInfo( new FontSettingsInfo( _q("Fonts"), _q("Font Settings"), _config, 1 ) ); // color settings sd->addSettingsWidget( _q("Color Settings"), new ColorSettingsWidget() ); sd->addSettingsInfo( new ColorSettingsInfo( _q("Colors"), _q("Color Settings"), _config, 2 ) ); // debug settings sd->addSettingsWidget( "Debug Settings", new DebugSettingsWidget(DebugSettingsWidget::L10n) ); sd->addSettingsInfo( new DebugSettingsInfo( "Debug", "Debug Settings", _config, 3 ) ); sd->exec(); removeChild(sd); delete sd; } const sc::Error* MainWindow::diff( const DiffParamPtr param ) { // remember parameters _lastParam = param; const sc::Error* error; // prepare original file error = param->_original->read(); showError(error); SC_ERR(error); error = param->_original->xlate(); if( error != sc::Success ) { error = param->_original->xlateBinary(); } showError(error); SC_ERR(error); // prepare modified file error = param->_modified->read(); showError(error); SC_ERR(error); error = param->_modified->xlate(); if( error != sc::Success ) { error = param->_modified->xlateBinary(); } showError(error); SC_ERR(error); // run the diff.. Diff diff( param->_original, param->_modified ); error = diff.diff( !param->_whitespace ); showError(error); SC_ERR(error); // ... and show it. _diffInfo = DiffInfoModelPtr(diff.getDiffInfo()); _dw->setModel( _diffInfo.get() ); _dw->setModel( new NullTextModel(), _diffInfo->getModel(DiffInfoModel::dmOriginal), _diffInfo->getModel(DiffInfoModel::dmModified) ); _dw->setMergeModel( _diffInfo->getModel(DiffInfoModel::dmMerged) ); _dw->setCenterLabel( param->_originalLabel ); _dw->setRightLabel( param->_modifiedLabel ); // hide split handles.. _dw->enableOriginal(false,false); _dw->enableMerged(false,false); _merge->setEnabled(true); _reload->setEnabled(true); _wspace->setOn( param->_whitespace ); showOptions( param->_whitespace ); setDiffCnt( _diffInfo->getDiffCnt() ); showEncoding( param->_original->getEncoding() ); return sc::Success; } const sc::Error* MainWindow::diff3( const DiffParamPtr param ) { // remember parameters _lastParam = param; const sc::Error* error; // prepare original file error = param->_original->read(); showError(error); SC_ERR(error); error = param->_original->xlate(); if( error != sc::Success ) { error = param->_original->xlateBinary(); } showError(error); SC_ERR(error); // prepare modified file error = param->_modified->read(); showError(error); SC_ERR(error); error = param->_modified->xlate(); if( error != sc::Success ) { error = param->_modified->xlateBinary(); } showError(error); SC_ERR(error); // prepare latest file error = param->_latest->read(); showError(error); SC_ERR(error); error = param->_latest->xlate(); if( error != sc::Success ) { error = param->_latest->xlateBinary(); } showError(error); SC_ERR(error); // run the diff... Diff3 diff( param->_original, param->_modified, param->_latest, param->_merged ); error = diff.diff3( !param->_whitespace ); showError(error); SC_ERR(error); // ... and show it. _diffInfo = DiffInfoModelPtr(diff.getDiffInfo()); _dw->setModel( _diffInfo.get() ); _dw->setModel( _diffInfo->getModel( DiffInfoModel::dmOriginal ), _diffInfo->getModel( DiffInfoModel::dmModified ), _diffInfo->getModel( DiffInfoModel::dmLatest ) ); _dw->setMergeModel( _diffInfo->getModel( DiffInfoModel::dmMerged ) ); _dw->setLeftLabel( param->_originalLabel ); _dw->setCenterLabel( param->_modifiedLabel ); _dw->setRightLabel( param->_latestLabel ); // show split handles _dw->enableOriginal(true,false); _dw->enableMerged(false,false); _merge->setEnabled(true); _reload->setEnabled(true); _wspace->setOn( param->_whitespace ); showOptions( param->_whitespace ); setDiffCnt( _diffInfo->getDiffCnt() ); showEncoding( param->_original->getEncoding() ); return sc::Success; } const sc::Error* MainWindow::save( const char* file ) { TextModel* merged = _diffInfo->getModel( DiffInfoModel::dmMerged ); size_t lines = merged->getLineCnt(); size_t columns = merged->getColumnCnt(); apr::Pool pool; size_t size = lines*columns*2; char* tmpBuf = (char*)apr_palloc( pool, size ); char* tmpDst = tmpBuf; sc::Size tmpLen = 0; FileDataPtr ptrModFile = _lastParam->_modified; for( size_t i = 0; i < lines; i++ ) { const Line& l = merged->getLine(i); if( l.isEmpty() ) { continue; } memcpy( tmpDst, l.getStr(), l.getBytes() ); tmpDst += l.getBytes(); tmpLen += l.getBytes(); } { apr_status_t status; apr_xlate_t* xlate; const char* dstEncoding = ptrModFile->getEncoding(); if( *dstEncoding == '*' ) dstEncoding = APR_LOCALE_CHARSET; status = apr_xlate_open( &xlate, dstEncoding, "utf-8", pool ); APR_ERR(status); apr_size_t size = tmpLen * 2; while(true) { apr::Pool pool; const char* xSrcBuf = (const char*)tmpBuf; apr_size_t xSrcLen = tmpLen; apr_size_t xDstLen = size; char* xDstBuf = (char*)apr_palloc( pool, xDstLen ); status = apr_xlate_conv_buffer( xlate, xSrcBuf, &xSrcLen, xDstBuf, &xDstLen ); // buffer to small? if( status == APR_SUCCESS && xSrcLen > 0 ) { size *= 2; continue; } // everything translated? else if( status == APR_SUCCESS && xSrcLen == 0 ) { // then write to disk.. apr_file_t* aprFile = NULL; status = apr_file_open( &aprFile, file, APR_WRITE|APR_CREATE|APR_TRUNCATE|APR_BINARY, APR_OS_DEFAULT, pool ); APR_ERR(status); apr_size_t xWriteSize = size-xDstLen; status = apr_file_write( aprFile, xDstBuf, &xWriteSize ); APR_ERR(status); status = apr_file_close(aprFile); APR_ERR(status); // we are done break; } else { APR_ERR(status); } } status = apr_xlate_close(xlate); APR_ERR(status); } return sc::Success; } void MainWindow::open() { if( ! _file ) { // prepare file dialog, we always want to use the same dialog // so we don't loose previous file selections. _file = new FileSelectDialog(this); } FileSelectDialog::Result result = (FileSelectDialog::Result)_file->exec(); switch( result ) { case FileSelectDialog::rDiff2: { DiffParamPtr p( new DiffParam() ); p->_original = FileDataPtr( new FileData( sc::String(_file->getOriginal().utf8()), sc::String(_file->getEncoding().utf8()) ) ); p->_modified = FileDataPtr( new FileData( sc::String(_file->getModified().utf8()), sc::String(_file->getEncoding().utf8()) ) ); p->_whitespace = _wspace->isOn(); diff( p ); break; } case FileSelectDialog::rDiff3: { DiffParamPtr p( new DiffParam() ); p->_original = FileDataPtr( new FileData( sc::String(_file->getOriginal().utf8()), sc::String(_file->getEncoding().utf8()) ) ); p->_modified = FileDataPtr( new FileData( sc::String(_file->getModified().utf8()), sc::String(_file->getEncoding().utf8()) ) ); p->_latest = FileDataPtr( new FileData( sc::String(_file->getLatest().utf8()), sc::String(_file->getEncoding().utf8()) ) ); p->_whitespace = _wspace->isOn(); diff3( p ); break; } } } void MainWindow::save() { QString s( _diffInfo->getModel(DiffInfoModel::dmMerged)->getSourceName().getStr() ); //s += ".merged"; save( s ); } void MainWindow::saveAs() { QString s( _diffInfo->getModel(DiffInfoModel::dmMerged)->getSourceName().getStr() ); QString sel = QFileDialog::getSaveFileName( s, "", this, "", _q("save as...") ); if( ! sel.isNull() ) { save( sel ); } } void MainWindow::about() { AboutDialog* ab = new AboutDialog( this ); ab->exec(); this->removeChild(ab); delete ab; } void MainWindow::showOptions( bool whitespaces ) { if( whitespaces ) { _sbarWhitespace->setText( "w+" ); } else { _sbarWhitespace->setText( "w-" ); } _config->setOptWhitespace(whitespaces); _config->save(); } void MainWindow::setDiffCnt( int cnt ) { _sbarDiffCnt->setText( _q("differences: %1").arg( _diffInfo->getDiffCnt() ) ); if( _diffInfo->getDiffCnt() ) { _next->setEnabled(true); _prev->setEnabled(true/*false*/); } } void MainWindow::showEncoding( const sc::String& encoding ) { if( encoding == sc::String("*") ) { QString encdg = QString("* (%1)").arg((const char*)apr::getLocaleEncoding()); _sbarEncoding->setText(encdg); } else { QString encdg(encoding); _sbarEncoding->setText(encdg); } } void MainWindow::nextDiff() { int next = _diffInfo->nextDiff(); int act = _diffInfo->getActiveDiff(); _dw->jumpToBlock( next ); _dw->setActiveDiff( act ); diffChanged( act ); } void MainWindow::prevDiff() { int prev = _diffInfo->prevDiff(); int act = _diffInfo->getActiveDiff(); _dw->jumpToBlock( prev ); _dw->setActiveDiff( act ); diffChanged( act ); } void MainWindow::nextConflict() { } void MainWindow::prevConflict() { } void MainWindow::merge() { _dw->enableMerged( true, true ); _merge->setEnabled(false); _fileMenu->setItemEnabled( pmiFileSave, true ); _fileMenu->setItemEnabled( pmiFileSaveAs, true ); } void MainWindow::whitespace( bool b ) { showOptions(b); } void MainWindow::refresh() { switch( _lastParam->_type ) { case DiffParam::Diff: { _lastParam->_whitespace = _wspace->isOn(); diff( _lastParam ); break; } case DiffParam::Diff3: { _lastParam->_whitespace = _wspace->isOn(); diff3( _lastParam ); break; } } _dw->repaint(); } void MainWindow::diffChanged(int diff) { _next->setEnabled(true/*_diffInfo->hasNextDiff()*/); _prev->setEnabled(true/*_diffInfo->hasPrevDiff()*/); } void MainWindow::oDropped( const QString& f ) { if( ! _lastParam ) { _lastParam = DiffParamPtr( new DiffParam() ); _lastParam->_modified = getEmptyFile(); _lastParam->_latest = getEmptyFile(); _lastParam->_modifiedLabel = _s("empty"); _lastParam->_latestLabel = _s("empty"); } _lastParam->_type = DiffParam::Diff3; _lastParam->_original = FileDataPtr( new FileData( sc::String(f.utf8()), sc::String("*") ) ); _lastParam->_originalLabel = ""; refresh(); } void MainWindow::mDropped( const QString& f ) { if( ! _lastParam ) { // assume diff.. _lastParam = DiffParamPtr( new DiffParam() ); _lastParam->_modified = getEmptyFile(); _lastParam->_latest = getEmptyFile(); _lastParam->_modifiedLabel = _s("empty"); _lastParam->_latestLabel = _s("empty"); _lastParam->_type = DiffParam::Diff; } // diff if( _lastParam->_type == DiffParam::Diff ) { _lastParam->_original = FileDataPtr( new FileData( sc::String(f.utf8()), sc::String("*") ) ); _lastParam->_originalLabel = ""; } // diff3 else { _lastParam->_modified = FileDataPtr( new FileData( sc::String(f.utf8()), sc::String("*") ) ); _lastParam->_modifiedLabel = ""; } refresh(); } void MainWindow::lDropped( const QString& f ) { if( ! _lastParam ) { _lastParam = DiffParamPtr( new DiffParam() ); _lastParam->_original = getEmptyFile(); _lastParam->_modified = getEmptyFile(); _lastParam->_originalLabel = _s("empty"); _lastParam->_modifiedLabel = _s("empty"); _lastParam->_type = DiffParam::Diff; } // diff if( _lastParam->_type == DiffParam::Diff ) { _lastParam->_modified = FileDataPtr( new FileData( sc::String(f.utf8()), sc::String("*") ) ); _lastParam->_modifiedLabel = ""; } // diff3 else { _lastParam->_latest = FileDataPtr( new FileData( sc::String(f.utf8()), sc::String("*") ) ); _lastParam->_latestLabel = ""; } refresh(); } FileDataPtr MainWindow::getEmptyFile() { apr::Pool pool; apr_status_t status; apr_file_t* empty; const char* tempdir = NULL; status = apr_temp_dir_get( &tempdir, pool ); char* tempout = apr_pstrcat( pool, tempdir, "/submerge.empty", NULL ); status = apr_file_open( &empty, tempout, APR_CREATE|APR_WRITE|APR_TRUNCATE, APR_OS_DEFAULT, pool ); status = apr_file_close( empty ); //char errbuf[200]; //apr_strerror( status, errbuf, 200 ); return FileDataPtr( new FileData( sc::String(tempout), sc::String("*") ) ); }