/* ====================================================================
* 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 <qapplication.h>
#include <qmenubar.h>
#include <qtoolbar.h>
#include <qtoolbutton.h>
#include <qlabel.h>
#include <qvbox.h>
#include <qlayout.h>
#include <qpushbutton.h>
#include <qstatusbar.h>
#include <qfiledialog.h>
#include <qpopupmenu.h>
#include <qaction.h>
// sys
#include <string>
#include <fstream>
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(
"<qt>"
"<center>"
"<table width=\"500\">"
"<tr>"
"<td>" "%1" "</td>"
"</tr>"
).arg( (const char*)error->getMessage() );
const sc::Error* nested = error->getNested();
while( nested != sc::Success )
{
QString nmsg = QString(
"<tr>"
"<td>" "%1" "</td>"
"</tr>"
).arg( (const char*)nested->getMessage() );
msg += nmsg;
nested = nested->getNested();
}
msg +=
"<table>"
"</center>"
"</qt>";
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("*") ) );
}
syntax highlighted by Code2HTML, v. 0.9.1