Warning, file /sdk/cervisia/resolvedialog.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *  Copyright (C) 1999-2002 Bernd Gehrmann
0003  *                          bernd@mail.berlios.de
0004  *  Copyright (c) 2003-2004 Christian Loose <christian.loose@hamburg.de>
0005  *
0006  * This program is free software; you can redistribute it and/or modify
0007  * it under the terms of the GNU General Public License as published by
0008  * the Free Software Foundation; either version 2 of the License, or
0009  * (at your option) any later version.
0010  *
0011  * This program is distributed in the hope that it will be useful,
0012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014  * GNU General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU General Public License
0017  * along with this program; if not, write to the Free Software
0018  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
0019  */
0020 
0021 #include "resolvedialog.h"
0022 #include "resolvedialog_p.h"
0023 
0024 #include <QDialogButtonBox>
0025 #include <QFileDialog>
0026 #include <QHBoxLayout>
0027 #include <QKeyEvent>
0028 #include <QPushButton>
0029 #include <QSplitter>
0030 #include <QVBoxLayout>
0031 #include <qfile.h>
0032 #include <qlabel.h>
0033 #include <qlayout.h>
0034 #include <qnamespace.h>
0035 #include <qregexp.h>
0036 #include <qtextcodec.h>
0037 #include <qtextstream.h>
0038 
0039 #include <KConfig>
0040 #include <KConfigGroup>
0041 #include <KGuiItem>
0042 #include <KHelpClient>
0043 #include <KLocalizedString>
0044 #include <kmessagebox.h>
0045 
0046 #include "debug.h"
0047 #include "misc.h"
0048 
0049 using Cervisia::ResolveEditorDialog;
0050 
0051 // *UGLY HACK*
0052 // The following conditions are a rough hack
0053 static QTextCodec *DetectCodec(const QString &fileName)
0054 {
0055     if (fileName.endsWith(QLatin1String(".ui")) || fileName.endsWith(QLatin1String(".docbook")) || fileName.endsWith(QLatin1String(".xml")))
0056         return QTextCodec::codecForName("utf8");
0057 
0058     return QTextCodec::codecForLocale();
0059 }
0060 
0061 namespace
0062 {
0063 
0064 class LineSeparator
0065 {
0066 public:
0067     LineSeparator(const QString &text)
0068         : m_text(text)
0069         , m_startPos(0)
0070         , m_endPos(0)
0071     {
0072     }
0073 
0074     QString nextLine() const
0075     {
0076         // already reach end of text on previous call
0077         if (m_endPos < 0) {
0078             m_currentLine.clear();
0079             return m_currentLine;
0080         }
0081 
0082         m_endPos = m_text.indexOf('\n', m_startPos);
0083 
0084         int length = m_endPos - m_startPos + 1;
0085         m_currentLine = m_text.mid(m_startPos, length);
0086         m_startPos = m_endPos + 1;
0087 
0088         return m_currentLine;
0089     }
0090 
0091     bool atEnd() const
0092     {
0093         return (m_endPos < 0 && m_currentLine.isEmpty());
0094     }
0095 
0096 private:
0097     const QString m_text;
0098     mutable QString m_currentLine;
0099     mutable int m_startPos, m_endPos;
0100 };
0101 
0102 }
0103 
0104 ResolveDialog::ResolveDialog(KConfig &cfg, QWidget *parent)
0105     : QDialog(parent)
0106     , markeditem(-1)
0107     , partConfig(cfg)
0108 {
0109     auto mainLayout = new QVBoxLayout;
0110     setLayout(mainLayout);
0111 
0112     auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Close);
0113 
0114     auto user1Button = new QPushButton;
0115     buttonBox->addButton(user1Button, QDialogButtonBox::ActionRole);
0116 
0117     auto user2Button = new QPushButton;
0118     buttonBox->addButton(user2Button, QDialogButtonBox::ActionRole);
0119 
0120     connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
0121     connect(buttonBox, &QDialogButtonBox::helpRequested, this, &ResolveDialog::slotHelp);
0122 
0123     KGuiItem::assign(user1Button, KStandardGuiItem::saveAs());
0124     KGuiItem::assign(user2Button, KStandardGuiItem::save());
0125 
0126     auto vertSplitter = new QSplitter(Qt::Vertical, this);
0127     mainLayout->addWidget(vertSplitter);
0128 
0129     auto splitter = new QSplitter(Qt::Horizontal, vertSplitter);
0130 
0131     auto versionALayoutWidget = new QWidget(splitter);
0132     QBoxLayout *versionAlayout = new QVBoxLayout(versionALayoutWidget);
0133     versionAlayout->setSpacing(5);
0134 
0135     auto revlabel1 = new QLabel(i18n("Your version (A):"), versionALayoutWidget);
0136     versionAlayout->addWidget(revlabel1);
0137     diff1 = new DiffView(cfg, true, false, versionALayoutWidget);
0138     versionAlayout->addWidget(diff1, 10);
0139 
0140     auto versionBLayoutWidget = new QWidget(splitter);
0141     QBoxLayout *versionBlayout = new QVBoxLayout(versionBLayoutWidget);
0142     versionBlayout->setSpacing(5);
0143 
0144     auto revlabel2 = new QLabel(i18n("Other version (B):"), versionBLayoutWidget);
0145     versionBlayout->addWidget(revlabel2);
0146     diff2 = new DiffView(cfg, true, false, versionBLayoutWidget);
0147     versionBlayout->addWidget(diff2, 10);
0148 
0149     diff1->setPartner(diff2);
0150     diff2->setPartner(diff1);
0151 
0152     auto mergeLayoutWidget = new QWidget(vertSplitter);
0153     QBoxLayout *mergeLayout = new QVBoxLayout(mergeLayoutWidget);
0154     mergeLayout->setSpacing(5);
0155 
0156     auto mergelabel = new QLabel(i18n("Merged version:"), mergeLayoutWidget);
0157     mergeLayout->addWidget(mergelabel);
0158 
0159     merge = new DiffView(cfg, false, false, mergeLayoutWidget);
0160     mergeLayout->addWidget(merge, 10);
0161 
0162     mainLayout->addWidget(vertSplitter);
0163 
0164     abutton = new QPushButton("&A");
0165     connect(abutton, SIGNAL(clicked()), SLOT(aClicked()));
0166 
0167     bbutton = new QPushButton("&B");
0168     connect(bbutton, SIGNAL(clicked()), SLOT(bClicked()));
0169 
0170     abbutton = new QPushButton("A+B");
0171     connect(abbutton, SIGNAL(clicked()), SLOT(abClicked()));
0172 
0173     babutton = new QPushButton("B+A");
0174     connect(babutton, SIGNAL(clicked()), SLOT(baClicked()));
0175 
0176     editbutton = new QPushButton(i18n("&Edit"));
0177     connect(editbutton, SIGNAL(clicked()), SLOT(editClicked()));
0178 
0179     nofnlabel = new QLabel;
0180     nofnlabel->setAlignment(Qt::AlignCenter);
0181 
0182     backbutton = new QPushButton("&<<");
0183     connect(backbutton, SIGNAL(clicked()), SLOT(backClicked()));
0184 
0185     forwbutton = new QPushButton("&>>");
0186     connect(forwbutton, SIGNAL(clicked()), SLOT(forwClicked()));
0187 
0188     QBoxLayout *buttonlayout = new QHBoxLayout();
0189     mainLayout->addLayout(buttonlayout);
0190     buttonlayout->addWidget(abutton, 1);
0191     buttonlayout->addWidget(bbutton, 1);
0192     buttonlayout->addWidget(abbutton, 1);
0193     buttonlayout->addWidget(babutton, 1);
0194     buttonlayout->addWidget(editbutton, 1);
0195     buttonlayout->addStretch(1);
0196     buttonlayout->addWidget(nofnlabel, 2);
0197     buttonlayout->addStretch(1);
0198     buttonlayout->addWidget(backbutton, 1);
0199     buttonlayout->addWidget(forwbutton, 1);
0200 
0201     connect(user2Button, SIGNAL(clicked()), SLOT(saveClicked()));
0202     connect(user1Button, SIGNAL(clicked()), SLOT(saveAsClicked()));
0203 
0204     mainLayout->addWidget(buttonBox);
0205     buttonBox->button(QDialogButtonBox::Close)->setDefault(true);
0206 
0207     QFontMetrics const fm(fontMetrics());
0208     resize(fm.width('0') * 100, fm.lineSpacing() * 40);
0209 
0210     setAttribute(Qt::WA_DeleteOnClose, true);
0211 
0212     KConfigGroup cg(&partConfig, "ResolveDialog");
0213     restoreGeometry(cg.readEntry<QByteArray>("geometry", QByteArray()));
0214 }
0215 
0216 ResolveDialog::~ResolveDialog()
0217 {
0218     KConfigGroup cg(&partConfig, "ResolveDialog");
0219     cg.writeEntry("geometry", saveGeometry());
0220 
0221     qDeleteAll(items);
0222 }
0223 
0224 void ResolveDialog::slotHelp()
0225 {
0226     KHelpClient::invokeHelp(QLatin1String("resolvingconflicts"));
0227 }
0228 
0229 // One resolve item has a line number range of linenoA:linenoA+linecountA-1
0230 // in A and linenoB:linenoB+linecountB-1 in B. If the user has chosen version A
0231 // for the merged file (indicated by chosenA==true), then the line number
0232 // range in the merged file is offsetM:offsetM+linecountA-1 (accordingly for
0233 // the other case).
0234 class ResolveItem
0235 {
0236 public:
0237     int linenoA, linecountA;
0238     int linenoB, linecountB;
0239     int linecountTotal;
0240     int offsetM;
0241     ResolveDialog::ChooseType chosen;
0242 };
0243 
0244 bool ResolveDialog::parseFile(const QString &name)
0245 {
0246     int lineno1, lineno2;
0247     int advanced1, advanced2;
0248     enum { Normal, VersionA, VersionB } state;
0249 
0250     setWindowTitle(i18n("CVS Resolve: %1", name));
0251 
0252     fname = name;
0253 
0254     QString fileContent = readFile();
0255     if (fileContent.isNull())
0256         return false;
0257 
0258     LineSeparator separator(fileContent);
0259 
0260     state = Normal;
0261     lineno1 = lineno2 = 0;
0262     advanced1 = advanced2 = 0;
0263     do {
0264         QString line = separator.nextLine();
0265 
0266         // reached end of file?
0267         if (separator.atEnd())
0268             break;
0269 
0270         switch (state) {
0271         case Normal: {
0272             // check for start of conflict block
0273             // Set to look for <<<<<<< at beginning of line with exactly one
0274             // space after then anything after that.
0275             QRegExp rx("^<{7}\\s.*$");
0276             if (line.contains(rx)) {
0277                 state = VersionA;
0278                 advanced1 = 0;
0279             } else {
0280                 addToMergeAndVersionA(line, DiffView::Unchanged, lineno1);
0281                 addToVersionB(line, DiffView::Unchanged, lineno2);
0282             }
0283         } break;
0284         case VersionA: {
0285             // Set to look for ======= at beginning of line which may have one
0286             // or more spaces after then nothing else.
0287             QRegExp rx("^={7}\\s*$");
0288             if (!line.contains(rx)) // still in version A
0289             {
0290                 advanced1++;
0291                 addToMergeAndVersionA(line, DiffView::Change, lineno1);
0292             } else {
0293                 state = VersionB;
0294                 advanced2 = 0;
0295             }
0296         } break;
0297         case VersionB: {
0298             // Set to look for >>>>>>> at beginning of line with exactly one
0299             // space after then anything after that.
0300             QRegExp rx("^>{7}\\s.*$");
0301             if (!line.contains(rx)) // still in version B
0302             {
0303                 advanced2++;
0304                 addToVersionB(line, DiffView::Change, lineno2);
0305             } else {
0306                 // create an resolve item
0307                 auto item = new ResolveItem;
0308                 item->linenoA = lineno1 - advanced1 + 1;
0309                 item->linecountA = advanced1;
0310                 item->linenoB = lineno2 - advanced2 + 1;
0311                 item->linecountB = advanced2;
0312                 item->offsetM = item->linenoA - 1;
0313                 item->chosen = ChA;
0314                 item->linecountTotal = item->linecountA;
0315                 items.append(item);
0316 
0317                 for (; advanced1 < advanced2; advanced1++)
0318                     diff1->addLine("", DiffView::Neutral);
0319                 for (; advanced2 < advanced1; advanced2++)
0320                     diff2->addLine("", DiffView::Neutral);
0321 
0322                 state = Normal;
0323             }
0324         } break;
0325         }
0326     } while (!separator.atEnd());
0327 
0328     updateNofN();
0329     forwClicked(); // go to first conflict
0330 
0331     return true; // successful
0332 }
0333 
0334 void ResolveDialog::addToMergeAndVersionA(const QString &line, DiffView::DiffType type, int &lineNo)
0335 {
0336     lineNo++;
0337     diff1->addLine(line, type, lineNo);
0338     merge->addLine(line, type, lineNo);
0339 }
0340 
0341 void ResolveDialog::addToVersionB(const QString &line, DiffView::DiffType type, int &lineNo)
0342 {
0343     lineNo++;
0344     diff2->addLine(line, type, lineNo);
0345 }
0346 
0347 void ResolveDialog::saveFile(const QString &name)
0348 {
0349     QFile f(name);
0350     if (!f.open(QIODevice::WriteOnly)) {
0351         KMessageBox::error(this, i18n("Could not open file for writing."), "Cervisia");
0352         return;
0353     }
0354     QTextStream stream(&f);
0355     QTextCodec *fcodec = DetectCodec(name);
0356     stream.setCodec(fcodec);
0357 
0358     QString output;
0359     for (int i = 0; i < merge->count(); i++)
0360         output += merge->stringAtOffset(i);
0361     stream << output;
0362 
0363     f.close();
0364 }
0365 
0366 QString ResolveDialog::readFile()
0367 {
0368     QFile f(fname);
0369     if (!f.open(QIODevice::ReadOnly))
0370         return {};
0371 
0372     QTextStream stream(&f);
0373     QTextCodec *codec = DetectCodec(fname);
0374     stream.setCodec(codec);
0375 
0376     return stream.readAll();
0377 }
0378 
0379 void ResolveDialog::updateNofN()
0380 {
0381     QString str;
0382     if (markeditem >= 0)
0383         str = i18n("%1 of %2", markeditem + 1, items.count());
0384     else
0385         str = i18n("%1 conflicts", items.count());
0386     nofnlabel->setText(str);
0387 
0388     backbutton->setEnabled(markeditem != -1);
0389     forwbutton->setEnabled(markeditem != -2 && items.count());
0390 
0391     bool marked = markeditem >= 0;
0392     abutton->setEnabled(marked);
0393     bbutton->setEnabled(marked);
0394     abbutton->setEnabled(marked);
0395     babutton->setEnabled(marked);
0396     editbutton->setEnabled(marked);
0397 }
0398 
0399 void ResolveDialog::updateHighlight(int newitem)
0400 {
0401     if (markeditem >= 0) {
0402         ResolveItem *item = items.at(markeditem);
0403         for (int i = item->linenoA; i < item->linenoA + item->linecountA; ++i)
0404             diff1->setInverted(i, false);
0405         for (int i = item->linenoB; i < item->linenoB + item->linecountB; ++i)
0406             diff2->setInverted(i, false);
0407     }
0408 
0409     markeditem = newitem;
0410 
0411     if (markeditem >= 0) {
0412         ResolveItem *item = items.at(markeditem);
0413         for (int i = item->linenoA; i < item->linenoA + item->linecountA; ++i)
0414             diff1->setInverted(i, true);
0415         for (int i = item->linenoB; i < item->linenoB + item->linecountB; ++i)
0416             diff2->setInverted(i, true);
0417         diff1->setCenterLine(item->linenoA);
0418         diff2->setCenterLine(item->linenoB);
0419         merge->setCenterOffset(item->offsetM);
0420     }
0421     diff1->repaint();
0422     diff2->repaint();
0423     merge->repaint();
0424     updateNofN();
0425 }
0426 
0427 void ResolveDialog::updateMergedVersion(ResolveDialog::ChooseType chosen)
0428 {
0429     if (markeditem < 0)
0430         return;
0431 
0432     ResolveItem *item = items.at(markeditem);
0433 
0434     // Remove old variant
0435     for (int i = 0; i < item->linecountTotal; ++i)
0436         merge->removeAtOffset(item->offsetM);
0437 
0438     // Insert new
0439     int total = 0;
0440     LineSeparator separator(m_contentMergedVersion);
0441     QString line = separator.nextLine();
0442     while (!separator.atEnd()) {
0443         merge->insertAtOffset(line, DiffView::Change, item->offsetM + total);
0444         line = separator.nextLine();
0445         ++total;
0446     }
0447 
0448     // Adjust other items
0449     int difference = total - item->linecountTotal;
0450     item->chosen = chosen;
0451     item->linecountTotal = total;
0452 
0453     for (int i = markeditem + 1; i < items.count(); i++)
0454         items[i]->offsetM += difference;
0455 
0456     merge->repaint();
0457 }
0458 
0459 void ResolveDialog::backClicked()
0460 {
0461     int newitem;
0462     if (markeditem == -1)
0463         return; // internal error (button not disabled)
0464     else if (markeditem == -2) // past end
0465         newitem = items.count() - 1;
0466     else
0467         newitem = markeditem - 1;
0468     updateHighlight(newitem);
0469 }
0470 
0471 void ResolveDialog::forwClicked()
0472 {
0473     int newitem;
0474     if (markeditem == -2 || (markeditem == -1 && !items.count()))
0475         return; // internal error (button not disabled)
0476     else if (markeditem + 1 == (int)items.count()) // past end
0477         newitem = -2;
0478     else
0479         newitem = markeditem + 1;
0480     updateHighlight(newitem);
0481 }
0482 
0483 void ResolveDialog::choose(ChooseType ch)
0484 {
0485     if (markeditem < 0)
0486         return;
0487 
0488     ResolveItem *item = items.at(markeditem);
0489 
0490     switch (ch) {
0491     case ChA:
0492         m_contentMergedVersion = contentVersionA(item);
0493         break;
0494     case ChB:
0495         m_contentMergedVersion = contentVersionB(item);
0496         break;
0497     case ChAB:
0498         m_contentMergedVersion = contentVersionA(item) + contentVersionB(item);
0499         break;
0500     case ChBA:
0501         m_contentMergedVersion = contentVersionB(item) + contentVersionA(item);
0502         break;
0503     default:
0504         qCDebug(log_cervisia) << "Internal error at switch";
0505     }
0506 
0507     updateMergedVersion(ch);
0508 }
0509 
0510 void ResolveDialog::aClicked()
0511 {
0512     choose(ChA);
0513 }
0514 
0515 void ResolveDialog::bClicked()
0516 {
0517     choose(ChB);
0518 }
0519 
0520 void ResolveDialog::abClicked()
0521 {
0522     choose(ChAB);
0523 }
0524 
0525 void ResolveDialog::baClicked()
0526 {
0527     choose(ChBA);
0528 }
0529 
0530 void ResolveDialog::editClicked()
0531 {
0532     if (markeditem < 0)
0533         return;
0534 
0535     ResolveItem *item = items.at(markeditem);
0536 
0537     QString mergedPart;
0538     int total = item->linecountTotal;
0539     int offset = item->offsetM;
0540     for (int i = 0; i < total; ++i)
0541         mergedPart += merge->stringAtOffset(offset + i);
0542 
0543     auto dlg = new ResolveEditorDialog(partConfig, this);
0544     dlg->setObjectName("edit");
0545     dlg->setContent(mergedPart);
0546 
0547     if (dlg->exec()) {
0548         m_contentMergedVersion = dlg->content();
0549         updateMergedVersion(ChEdit);
0550     }
0551 
0552     delete dlg;
0553     diff1->repaint();
0554     diff2->repaint();
0555     merge->repaint();
0556 }
0557 
0558 void ResolveDialog::saveClicked()
0559 {
0560     saveFile(fname);
0561 }
0562 
0563 void ResolveDialog::saveAsClicked()
0564 {
0565     QString filename = QFileDialog::getSaveFileName(this);
0566 
0567     if (!filename.isEmpty() && Cervisia::CheckOverwrite(filename))
0568         saveFile(filename);
0569 }
0570 
0571 void ResolveDialog::keyPressEvent(QKeyEvent *e)
0572 {
0573     switch (e->key()) {
0574     case Qt::Key_A:
0575         aClicked();
0576         break;
0577     case Qt::Key_B:
0578         bClicked();
0579         break;
0580     case Qt::Key_Left:
0581         backClicked();
0582         break;
0583     case Qt::Key_Right:
0584         forwClicked();
0585         break;
0586     case Qt::Key_Up:
0587         diff1->up();
0588         break;
0589     case Qt::Key_Down:
0590         diff1->down();
0591         break;
0592     default:
0593         QDialog::keyPressEvent(e);
0594     }
0595 }
0596 
0597 /* This will return the A side of the diff in a QString. */
0598 QString ResolveDialog::contentVersionA(const ResolveItem *item) const
0599 {
0600     QString result;
0601     for (int i = item->linenoA; i < item->linenoA + item->linecountA; ++i) {
0602         result += diff1->stringAtLine(i);
0603     }
0604 
0605     return result;
0606 }
0607 
0608 /* This will return the B side of the diff item in a QString. */
0609 QString ResolveDialog::contentVersionB(const ResolveItem *item) const
0610 {
0611     QString result;
0612     for (int i = item->linenoB; i < item->linenoB + item->linecountB; ++i) {
0613         result += diff2->stringAtLine(i);
0614     }
0615 
0616     return result;
0617 }
0618 
0619 // Local Variables:
0620 // c-basic-offset: 4
0621 // End: