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: