diff options
Diffstat (limited to 'cervisia/resolvedlg.cpp')
-rw-r--r-- | cervisia/resolvedlg.cpp | 634 |
1 files changed, 634 insertions, 0 deletions
diff --git a/cervisia/resolvedlg.cpp b/cervisia/resolvedlg.cpp new file mode 100644 index 00000000..2cf0ffd6 --- /dev/null +++ b/cervisia/resolvedlg.cpp @@ -0,0 +1,634 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2003-2004 Christian Loose <christian.loose@hamburg.de> + * + * 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. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "resolvedlg.h" + +#include <qfile.h> +#include <qkeycode.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpushbutton.h> +#include <qtextcodec.h> +#include <qtextstream.h> +#include <kdebug.h> +#include <kfiledialog.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <qregexp.h> +#include "misc.h" +#include "resolvedlg_p.h" +using Cervisia::ResolveEditorDialog; + + +// *UGLY HACK* +// The following conditions are a rough hack +static QTextCodec *DetectCodec(const QString &fileName) +{ + if (fileName.endsWith(".ui") || fileName.endsWith(".docbook") + || fileName.endsWith(".xml")) + return QTextCodec::codecForName("utf8"); + + return QTextCodec::codecForLocale(); +} + + +namespace +{ + +class LineSeparator +{ +public: + LineSeparator(const QString& text) + : m_text(text) + , m_startPos(0) + , m_endPos(0) + { + } + + QString nextLine() const + { + // already reach end of text on previous call + if( m_endPos < 0 ) + { + m_currentLine = QString::null; + return m_currentLine; + } + + m_endPos = m_text.find('\n', m_startPos); + + int length = m_endPos - m_startPos + 1; + m_currentLine = m_text.mid(m_startPos, length); + m_startPos = m_endPos + 1; + + return m_currentLine; + } + + bool atEnd() const + { + return (m_endPos < 0 && m_currentLine.isEmpty()); + } + +private: + const QString m_text; + mutable QString m_currentLine; + mutable int m_startPos, m_endPos; +}; + +} + + +ResolveDialog::ResolveDialog(KConfig& cfg, QWidget *parent, const char *name) + : KDialogBase(parent, name, false, QString::null, + Close | Help | User1 | User2, Close, true, + KStdGuiItem::saveAs(), KStdGuiItem::save()) + , markeditem(-1) + , partConfig(cfg) +{ + items.setAutoDelete(true); + + QFrame* mainWidget = makeMainWidget(); + + QBoxLayout *layout = new QVBoxLayout(mainWidget, 0, spacingHint()); + + QSplitter *vertSplitter = new QSplitter(QSplitter::Vertical, mainWidget); + + QSplitter *splitter = new QSplitter(QSplitter::Horizontal, vertSplitter); + + QWidget *versionALayoutWidget = new QWidget(splitter); + QBoxLayout *versionAlayout = new QVBoxLayout(versionALayoutWidget, 5); + + QLabel *revlabel1 = new QLabel(i18n("Your version (A):"), versionALayoutWidget); + versionAlayout->addWidget(revlabel1); + diff1 = new DiffView(cfg, true, false, versionALayoutWidget); + versionAlayout->addWidget(diff1, 10); + + QWidget* versionBLayoutWidget = new QWidget(splitter); + QBoxLayout *versionBlayout = new QVBoxLayout(versionBLayoutWidget, 5); + + QLabel *revlabel2 = new QLabel(i18n("Other version (B):"), versionBLayoutWidget); + versionBlayout->addWidget(revlabel2); + diff2 = new DiffView(cfg, true, false, versionBLayoutWidget); + versionBlayout->addWidget(diff2, 10); + + diff1->setPartner(diff2); + diff2->setPartner(diff1); + + QWidget* mergeLayoutWidget = new QWidget(vertSplitter); + QBoxLayout *mergeLayout = new QVBoxLayout(mergeLayoutWidget, 5); + + QLabel *mergelabel = new QLabel(i18n("Merged version:"), mergeLayoutWidget); + mergeLayout->addWidget(mergelabel); + + merge = new DiffView(cfg, false, false, mergeLayoutWidget); + mergeLayout->addWidget(merge, 10); + + layout->addWidget(vertSplitter); + + abutton = new QPushButton("&A", mainWidget); + connect( abutton, SIGNAL(clicked()), SLOT(aClicked()) ); + + bbutton = new QPushButton("&B", mainWidget); + connect( bbutton, SIGNAL(clicked()), SLOT(bClicked()) ); + + abbutton = new QPushButton("A+B", mainWidget); + connect( abbutton, SIGNAL(clicked()), SLOT(abClicked()) ); + + babutton = new QPushButton("B+A", mainWidget); + connect( babutton, SIGNAL(clicked()), SLOT(baClicked()) ); + + editbutton = new QPushButton(i18n("&Edit"), mainWidget); + connect( editbutton, SIGNAL(clicked()), SLOT(editClicked()) ); + + nofnlabel = new QLabel(mainWidget); + nofnlabel->setAlignment(AlignCenter); + + backbutton = new QPushButton("&<<", mainWidget); + connect( backbutton, SIGNAL(clicked()), SLOT(backClicked()) ); + + forwbutton = new QPushButton("&>>", mainWidget); + connect( forwbutton, SIGNAL(clicked()), SLOT(forwClicked()) ); + + QBoxLayout *buttonlayout = new QHBoxLayout(layout); + buttonlayout->addWidget(abutton, 1); + buttonlayout->addWidget(bbutton, 1); + buttonlayout->addWidget(abbutton, 1); + buttonlayout->addWidget(babutton, 1); + buttonlayout->addWidget(editbutton, 1); + buttonlayout->addStretch(1); + buttonlayout->addWidget(nofnlabel, 2); + buttonlayout->addStretch(1); + buttonlayout->addWidget(backbutton, 1); + buttonlayout->addWidget(forwbutton, 1); + + connect( this, SIGNAL(user2Clicked()), SLOT(saveClicked()) ); + connect( this, SIGNAL(user1Clicked()), SLOT(saveAsClicked()) ); + + QFontMetrics const fm(fontMetrics()); + setMinimumSize(fm.width('0') * 120, + fm.lineSpacing() * 40); + + setHelp("resolvingconflicts"); + + setWFlags(Qt::WDestructiveClose | getWFlags()); + + QSize size = configDialogSize(partConfig, "ResolveDialog"); + resize(size); +} + + +ResolveDialog::~ResolveDialog() +{ + saveDialogSize(partConfig, "ResolveDialog"); +} + + +// One resolve item has a line number range of linenoA:linenoA+linecountA-1 +// in A and linenoB:linenoB+linecountB-1 in B. If the user has chosen version A +// for the merged file (indicated by chosenA==true), then the line number +// range in the merged file is offsetM:offsetM+linecountA-1 (accordingly for +// the other case). +class ResolveItem +{ +public: + int linenoA, linecountA; + int linenoB, linecountB; + int linecountTotal; + int offsetM; + ResolveDialog::ChooseType chosen; +}; + + +bool ResolveDialog::parseFile(const QString &name) +{ + int lineno1, lineno2; + int advanced1, advanced2; + enum { Normal, VersionA, VersionB } state; + + setCaption(i18n("CVS Resolve: %1").arg(name)); + + fname = name; + + QString fileContent = readFile(); + if( fileContent.isNull() ) + return false; + + LineSeparator separator(fileContent); + + state = Normal; + lineno1 = lineno2 = 0; + advanced1 = advanced2 = 0; + do + { + QString line = separator.nextLine(); + + // reached end of file? + if( separator.atEnd() ) + break; + + switch( state ) + { + case Normal: + { + // check for start of conflict block + // Set to look for <<<<<<< at begining of line with exaclty one + // space after then anything after that. + QRegExp rx( "^<{7}\\s.*$" ); + int separatorPos = rx.search(line); + if( separatorPos >= 0 ) + { + state = VersionA; + advanced1 = 0; + } + else + { + addToMergeAndVersionA(line, DiffView::Unchanged, lineno1); + addToVersionB(line, DiffView::Unchanged, lineno2); + } + } + break; + case VersionA: + { + // Set to look for ======= at begining of line which may have one + // or more spaces after then nothing else. + QRegExp rx( "^={7}\\s*$" ); + int separatorPos = rx.search(line); + if( separatorPos < 0 ) // still in version A + { + advanced1++; + addToMergeAndVersionA(line, DiffView::Change, lineno1); + } + else + { + state = VersionB; + advanced2 = 0; + } + } + break; + case VersionB: + { + // Set to look for >>>>>>> at begining of line with exaclty one + // space after then anything after that. + QRegExp rx( "^>{7}\\s.*$" ); + int separatorPos = rx.search(line); + if( separatorPos < 0 ) // still in version B + { + advanced2++; + addToVersionB(line, DiffView::Change, lineno2); + } + else + { + // create an resolve item + ResolveItem *item = new ResolveItem; + item->linenoA = lineno1-advanced1+1; + item->linecountA = advanced1; + item->linenoB = lineno2-advanced2+1; + item->linecountB = advanced2; + item->offsetM = item->linenoA-1; + item->chosen = ChA; + item->linecountTotal = item->linecountA; + items.append(item); + + for (; advanced1 < advanced2; advanced1++) + diff1->addLine("", DiffView::Neutral); + for (; advanced2 < advanced1; advanced2++) + diff2->addLine("", DiffView::Neutral); + + state = Normal; + } + } + break; + } + } + while( !separator.atEnd() ); + + updateNofN(); + + return true; // succesful +} + + +void ResolveDialog::addToMergeAndVersionA(const QString& line, + DiffView::DiffType type, int& lineNo) +{ + lineNo++; + diff1->addLine(line, type, lineNo); + merge->addLine(line, type, lineNo); +} + + +void ResolveDialog::addToVersionB(const QString& line, DiffView::DiffType type, + int& lineNo) +{ + lineNo++; + diff2->addLine(line, type, lineNo); +} + + +void ResolveDialog::saveFile(const QString &name) +{ + QFile f(name); + if (!f.open(IO_WriteOnly)) + { + KMessageBox::sorry(this, + i18n("Could not open file for writing."), + "Cervisia"); + return; + } + QTextStream stream(&f); + QTextCodec *fcodec = DetectCodec(name); + stream.setCodec(fcodec); + + QString output; + for( int i = 0; i < merge->count(); i++ ) + output +=merge->stringAtOffset(i); + stream << output; + + f.close(); +} + + +QString ResolveDialog::readFile() +{ + QFile f(fname); + if( !f.open(IO_ReadOnly) ) + return QString::null; + + QTextStream stream(&f); + QTextCodec* codec = DetectCodec(fname); + stream.setCodec(codec); + + return stream.read(); +} + + +void ResolveDialog::updateNofN() +{ + QString str; + if (markeditem >= 0) + str = i18n("%1 of %2").arg(markeditem+1).arg(items.count()); + else + str = i18n("%1 conflicts").arg(items.count()); + nofnlabel->setText(str); + + backbutton->setEnabled(markeditem != -1); + forwbutton->setEnabled(markeditem != -2 && items.count()); + + bool marked = markeditem >= 0; + abutton->setEnabled(marked); + bbutton->setEnabled(marked); + abbutton->setEnabled(marked); + babutton->setEnabled(marked); + editbutton->setEnabled(marked); +} + + +void ResolveDialog::updateHighlight(int newitem) +{ + if (markeditem >= 0) + { + ResolveItem *item = items.at(markeditem); + for (int i = item->linenoA; i < item->linenoA+item->linecountA; ++i) + diff1->setInverted(i, false); + for (int i = item->linenoB; i < item->linenoB+item->linecountB; ++i) + diff2->setInverted(i, false); + } + + markeditem = newitem; + + if (markeditem >= 0) + { + ResolveItem *item = items.at(markeditem); + for (int i = item->linenoA; i < item->linenoA+item->linecountA; ++i) + diff1->setInverted(i, true); + for (int i = item->linenoB; i < item->linenoB+item->linecountB; ++i) + diff2->setInverted(i, true); + diff1->setCenterLine(item->linenoA); + diff2->setCenterLine(item->linenoB); + merge->setCenterOffset(item->offsetM); + } + diff1->repaint(); + diff2->repaint(); + merge->repaint(); + updateNofN(); +} + + +void ResolveDialog::updateMergedVersion(ResolveItem* item, + ResolveDialog::ChooseType chosen) +{ + // Remove old variant + for (int i = 0; i < item->linecountTotal; ++i) + merge->removeAtOffset(item->offsetM); + + // Insert new + int total = 0; + LineSeparator separator(m_contentMergedVersion); + QString line = separator.nextLine(); + while( !separator.atEnd() ) + { + merge->insertAtOffset(line, DiffView::Change, item->offsetM+total); + line = separator.nextLine(); + ++total; + } + + // Adjust other items + int difference = total - item->linecountTotal; + item->chosen = chosen; + item->linecountTotal = total; + while ( (item = items.next()) != 0 ) + item->offsetM += difference; + + merge->repaint(); +} + + +void ResolveDialog::backClicked() +{ + int newitem; + if (markeditem == -1) + return; // internal error (button not disabled) + else if (markeditem == -2) // past end + newitem = items.count()-1; + else + newitem = markeditem-1; + updateHighlight(newitem); +} + + +void ResolveDialog::forwClicked() +{ + int newitem; + if (markeditem == -2 || (markeditem == -1 && !items.count())) + return; // internal error (button not disabled) + else if (markeditem+1 == (int)items.count()) // past end + newitem = -2; + else + newitem = markeditem+1; + updateHighlight(newitem); +} + + +void ResolveDialog::choose(ChooseType ch) +{ + if (markeditem < 0) + return; + + ResolveItem *item = items.at(markeditem); + + switch (ch) + { + case ChA: + m_contentMergedVersion = contentVersionA(item); + break; + case ChB: + m_contentMergedVersion = contentVersionB(item); + break; + case ChAB: + m_contentMergedVersion = contentVersionA(item) + contentVersionB(item); + break; + case ChBA: + m_contentMergedVersion = contentVersionB(item) + contentVersionA(item); + break; + default: + kdDebug(8050) << "Internal error at switch" << endl; + } + + updateMergedVersion(item, ch); +} + + +void ResolveDialog::aClicked() +{ + choose(ChA); +} + + +void ResolveDialog::bClicked() +{ + choose(ChB); +} + + +void ResolveDialog::abClicked() +{ + choose(ChAB); +} + + +void ResolveDialog::baClicked() +{ + choose(ChBA); +} + + +void ResolveDialog::editClicked() +{ + if (markeditem < 0) + return; + + ResolveItem *item = items.at(markeditem); + + QString mergedPart; + int total = item->linecountTotal; + int offset = item->offsetM; + for( int i = 0; i < total; ++i ) + mergedPart += merge->stringAtOffset(offset+i); + + ResolveEditorDialog *dlg = new ResolveEditorDialog(partConfig, this, "edit"); + dlg->setContent(mergedPart); + + if (dlg->exec()) + { + m_contentMergedVersion = dlg->content(); + updateMergedVersion(item, ChEdit); + } + + delete dlg; + diff1->repaint(); + diff2->repaint(); + merge->repaint(); +} + + +void ResolveDialog::saveClicked() +{ + saveFile(fname); +} + + +void ResolveDialog::saveAsClicked() +{ + QString filename = + KFileDialog::getSaveFileName(0, 0, this, 0); + + if( !filename.isEmpty() && Cervisia::CheckOverwrite(filename) ) + saveFile(filename); +} + + +void ResolveDialog::keyPressEvent(QKeyEvent *e) +{ + switch (e->key()) + { + case Key_A: aClicked(); break; + case Key_B: bClicked(); break; + case Key_Left: backClicked(); break; + case Key_Right:forwClicked(); break; + case Key_Up: diff1->up(); break; + case Key_Down: diff1->down(); break; + default: + KDialogBase::keyPressEvent(e); + } +} + + + +/* This will return the A side of the diff in a QString. */ +QString ResolveDialog::contentVersionA(const ResolveItem *item) +{ + QString result; + for( int i = item->linenoA; i < item->linenoA+item->linecountA; ++i ) + { + result += diff1->stringAtLine(i); + } + + return result; +} + + +/* This will return the B side of the diff item in a QString. */ +QString ResolveDialog::contentVersionB(const ResolveItem *item) +{ + QString result; + for( int i = item->linenoB; i < item->linenoB+item->linecountB; ++i ) + { + result += diff2->stringAtLine(i); + } + + return result; +} + +#include "resolvedlg.moc" + + +// Local Variables: +// c-basic-offset: 4 +// End: |