/* * Copyright (C) 1999-2002 Bernd Gehrmann * bernd@mail.berlios.de * Copyright (c) 2003-2004 Christian Loose * * 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 #include #include #include #include #include #include #include #include #include #include #include #include "misc.h" #include "resolvedlg_p.h" using Cervisia::ResolveEditorDialog; // *UGLY HACK* // The following conditions are a rough hack static TQTextCodec *DetectCodec(const TQString &fileName) { if (fileName.endsWith(".ui") || fileName.endsWith(".docbook") || fileName.endsWith(".xml")) return TQTextCodec::codecForName("utf8"); return TQTextCodec::codecForLocale(); } namespace { class LineSeparator { public: LineSeparator(const TQString& text) : m_text(text) , m_startPos(0) , m_endPos(0) { } TQString nextLine() const { // already reach end of text on previous call if( m_endPos < 0 ) { m_currentLine = TQString(); 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 TQString m_text; mutable TQString m_currentLine; mutable int m_startPos, m_endPos; }; } ResolveDialog::ResolveDialog(TDEConfig& cfg, TQWidget *parent, const char *name) : KDialogBase(parent, name, false, TQString(), Close | Help | User1 | User2, Close, true, KStdGuiItem::saveAs(), KStdGuiItem::save()) , markeditem(-1) , partConfig(cfg) { items.setAutoDelete(true); TQFrame* mainWidget = makeMainWidget(); TQBoxLayout *layout = new TQVBoxLayout(mainWidget, 0, spacingHint()); TQSplitter *vertSplitter = new TQSplitter(TQt::Vertical, mainWidget); TQSplitter *splitter = new TQSplitter(TQt::Horizontal, vertSplitter); TQWidget *versionALayoutWidget = new TQWidget(splitter); TQBoxLayout *versionAlayout = new TQVBoxLayout(versionALayoutWidget, 5); TQLabel *revlabel1 = new TQLabel(i18n("Your version (A):"), versionALayoutWidget); versionAlayout->addWidget(revlabel1); diff1 = new DiffView(cfg, true, false, versionALayoutWidget); versionAlayout->addWidget(diff1, 10); TQWidget* versionBLayoutWidget = new TQWidget(splitter); TQBoxLayout *versionBlayout = new TQVBoxLayout(versionBLayoutWidget, 5); TQLabel *revlabel2 = new TQLabel(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); TQWidget* mergeLayoutWidget = new TQWidget(vertSplitter); TQBoxLayout *mergeLayout = new TQVBoxLayout(mergeLayoutWidget, 5); TQLabel *mergelabel = new TQLabel(i18n("Merged version:"), mergeLayoutWidget); mergeLayout->addWidget(mergelabel); merge = new DiffView(cfg, false, false, mergeLayoutWidget); mergeLayout->addWidget(merge, 10); layout->addWidget(vertSplitter); abutton = new TQPushButton("&A", mainWidget); connect( abutton, TQ_SIGNAL(clicked()), TQ_SLOT(aClicked()) ); bbutton = new TQPushButton("&B", mainWidget); connect( bbutton, TQ_SIGNAL(clicked()), TQ_SLOT(bClicked()) ); abbutton = new TQPushButton("A+B", mainWidget); connect( abbutton, TQ_SIGNAL(clicked()), TQ_SLOT(abClicked()) ); babutton = new TQPushButton("B+A", mainWidget); connect( babutton, TQ_SIGNAL(clicked()), TQ_SLOT(baClicked()) ); editbutton = new TQPushButton(i18n("&Edit"), mainWidget); connect( editbutton, TQ_SIGNAL(clicked()), TQ_SLOT(editClicked()) ); nofnlabel = new TQLabel(mainWidget); nofnlabel->setAlignment(AlignCenter); backbutton = new TQPushButton("&<<", mainWidget); connect( backbutton, TQ_SIGNAL(clicked()), TQ_SLOT(backClicked()) ); forwbutton = new TQPushButton("&>>", mainWidget); connect( forwbutton, TQ_SIGNAL(clicked()), TQ_SLOT(forwClicked()) ); TQBoxLayout *buttonlayout = new TQHBoxLayout(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, TQ_SIGNAL(user2Clicked()), TQ_SLOT(saveClicked()) ); connect( this, TQ_SIGNAL(user1Clicked()), TQ_SLOT(saveAsClicked()) ); TQFontMetrics const fm(fontMetrics()); setMinimumSize(fm.width('0') * 120, fm.lineSpacing() * 40); setHelp("resolvingconflicts"); setWFlags(TQt::WDestructiveClose | getWFlags()); TQSize 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 TQString &name) { int lineno1, lineno2; int advanced1, advanced2; enum { Normal, VersionA, VersionB } state; setCaption(i18n("CVS Resolve: %1").arg(name)); fname = name; TQString fileContent = readFile(); if( fileContent.isNull() ) return false; LineSeparator separator(fileContent); state = Normal; lineno1 = lineno2 = 0; advanced1 = advanced2 = 0; do { TQString 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. TQRegExp 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. TQRegExp 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. TQRegExp 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 TQString& line, DiffView::DiffType type, int& lineNo) { lineNo++; diff1->addLine(line, type, lineNo); merge->addLine(line, type, lineNo); } void ResolveDialog::addToVersionB(const TQString& line, DiffView::DiffType type, int& lineNo) { lineNo++; diff2->addLine(line, type, lineNo); } void ResolveDialog::saveFile(const TQString &name) { TQFile f(name); if (!f.open(IO_WriteOnly)) { KMessageBox::sorry(this, i18n("Could not open file for writing."), "Cervisia"); return; } TQTextStream stream(&f); TQTextCodec *fcodec = DetectCodec(name); stream.setCodec(fcodec); TQString output; for( int i = 0; i < merge->count(); i++ ) output +=merge->stringAtOffset(i); stream << output; f.close(); } TQString ResolveDialog::readFile() { TQFile f(fname); if( !f.open(IO_ReadOnly) ) return TQString(); TQTextStream stream(&f); TQTextCodec* codec = DetectCodec(fname); stream.setCodec(codec); return stream.read(); } void ResolveDialog::updateNofN() { TQString 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); TQString 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); TQString 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() { TQString filename = KFileDialog::getSaveFileName(0, 0, this, 0); if( !filename.isEmpty() && Cervisia::CheckOverwrite(filename) ) saveFile(filename); } void ResolveDialog::keyPressEvent(TQKeyEvent *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 TQString. */ TQString ResolveDialog::contentVersionA(const ResolveItem *item) { TQString 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 TQString. */ TQString ResolveDialog::contentVersionB(const ResolveItem *item) { TQString result; for( int i = item->linenoB; i < item->linenoB+item->linecountB; ++i ) { result += diff2->stringAtLine(i); } return result; } #include "resolvedlg.moc"