Warning, file /sdk/cervisia/diffdialog.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-2007 Christian Loose <christian.loose@kdemail.net> 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 "diffdialog.h" 0022 0023 #include <KComboBox> 0024 #include <qcheckbox.h> 0025 #include <qfileinfo.h> 0026 #include <qlabel.h> 0027 #include <qlayout.h> 0028 #include <qnamespace.h> 0029 #include <qpushbutton.h> 0030 #include <qregexp.h> 0031 0032 #include <QBoxLayout> 0033 #include <QGridLayout> 0034 #include <QHBoxLayout> 0035 #include <QKeyEvent> 0036 #include <QTextStream> 0037 #include <QVBoxLayout> 0038 0039 #include <KLocalizedString> 0040 #include <kconfig.h> 0041 #include <kmessagebox.h> 0042 0043 #include "cvsserviceinterface.h" 0044 #include "diffview.h" 0045 #include "misc.h" 0046 #include "progressdialog.h" 0047 #include <KConfigGroup> 0048 #include <KGuiItem> 0049 #include <KHelpClient> 0050 #include <QDialogButtonBox> 0051 #include <QFileDialog> 0052 #include <QPushButton> 0053 #include <kconfiggroup.h> 0054 #include <kprocess.h> 0055 #include <kshell.h> 0056 0057 DiffDialog::DiffDialog(KConfig &cfg, QWidget *parent, bool modal) 0058 : QDialog(parent) 0059 , partConfig(cfg) 0060 { 0061 markeditem = -1; 0062 setModal(modal); 0063 0064 auto mainLayout = new QVBoxLayout; 0065 setLayout(mainLayout); 0066 0067 auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Close); 0068 connect(buttonBox, &QDialogButtonBox::helpRequested, this, &DiffDialog::slotHelp); 0069 0070 auto user1Button = new QPushButton; 0071 buttonBox->addButton(user1Button, QDialogButtonBox::ActionRole); 0072 connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); 0073 KGuiItem::assign(user1Button, KStandardGuiItem::saveAs()); 0074 0075 auto pairlayout = new QGridLayout(); 0076 mainLayout->addLayout(pairlayout); 0077 pairlayout->setRowStretch(0, 0); 0078 pairlayout->setRowStretch(1, 1); 0079 pairlayout->setColumnStretch(1, 0); 0080 pairlayout->addItem(new QSpacerItem(16, 0), 0, 1); 0081 pairlayout->setColumnStretch(0, 10); 0082 pairlayout->setColumnStretch(2, 10); 0083 0084 revlabel1 = new QLabel; 0085 pairlayout->addWidget(revlabel1, 0, 0); 0086 0087 revlabel2 = new QLabel; 0088 pairlayout->addWidget(revlabel2, 0, 2); 0089 0090 diff1 = new DiffView(cfg, true, false, this); 0091 diff2 = new DiffView(cfg, true, true, this); 0092 auto zoom = new DiffZoomWidget(this); 0093 zoom->setDiffView(diff2); 0094 0095 pairlayout->addWidget(diff1, 1, 0); 0096 pairlayout->addWidget(zoom, 1, 1); 0097 pairlayout->addWidget(diff2, 1, 2); 0098 0099 diff1->setPartner(diff2); 0100 diff2->setPartner(diff1); 0101 0102 syncbox = new QCheckBox(i18n("Synchronize scroll bars")); 0103 syncbox->setChecked(true); 0104 connect(syncbox, SIGNAL(toggled(bool)), this, SLOT(toggleSynchronize(bool))); 0105 0106 itemscombo = new KComboBox; 0107 itemscombo->addItem(QString()); 0108 connect(itemscombo, SIGNAL(activated(int)), this, SLOT(comboActivated(int))); 0109 0110 nofnlabel = new QLabel; 0111 // avoids auto resize when the text is changed 0112 nofnlabel->setMinimumWidth(fontMetrics().width(i18np("%1 difference", "%1 differences", 10000))); 0113 0114 backbutton = new QPushButton(QLatin1String("&<<")); 0115 connect(backbutton, SIGNAL(clicked()), SLOT(backClicked())); 0116 0117 forwbutton = new QPushButton(QLatin1String("&>>")); 0118 connect(forwbutton, SIGNAL(clicked()), SLOT(forwClicked())); 0119 0120 connect(user1Button, SIGNAL(clicked()), SLOT(saveAsClicked())); 0121 0122 QBoxLayout *buttonlayout = new QHBoxLayout(); 0123 mainLayout->addLayout(buttonlayout); 0124 buttonlayout->addWidget(syncbox, 0); 0125 buttonlayout->addStretch(4); 0126 buttonlayout->addWidget(itemscombo); 0127 buttonlayout->addStretch(1); 0128 buttonlayout->addWidget(nofnlabel); 0129 buttonlayout->addStretch(1); 0130 buttonlayout->addWidget(backbutton); 0131 buttonlayout->addWidget(forwbutton); 0132 0133 mainLayout->addWidget(buttonBox); 0134 buttonBox->button(QDialogButtonBox::Close)->setDefault(true); 0135 0136 setAttribute(Qt::WA_DeleteOnClose, true); 0137 0138 KConfigGroup cg(&partConfig, "DiffDialog"); 0139 syncbox->setChecked(cg.readEntry("Sync", false)); 0140 restoreGeometry(cg.readEntry<QByteArray>("geometry", QByteArray())); 0141 } 0142 0143 DiffDialog::~DiffDialog() 0144 { 0145 KConfigGroup cg(&partConfig, "DiffDialog"); 0146 cg.writeEntry("Sync", syncbox->isChecked()); 0147 cg.writeEntry("geometry", saveGeometry()); 0148 0149 qDeleteAll(items); 0150 } 0151 0152 void DiffDialog::slotHelp() 0153 { 0154 KHelpClient::invokeHelp(QLatin1String("diff")); 0155 } 0156 0157 void DiffDialog::keyPressEvent(QKeyEvent *e) 0158 { 0159 switch (e->key()) { 0160 case Qt::Key_Up: 0161 diff1->up(); 0162 diff2->up(); 0163 break; 0164 case Qt::Key_Down: 0165 diff1->down(); 0166 diff2->down(); 0167 break; 0168 case Qt::Key_PageDown: 0169 diff1->next(); 0170 diff2->next(); 0171 break; 0172 case Qt::Key_PageUp: 0173 diff1->prior(); 0174 diff2->prior(); 0175 break; 0176 default: 0177 QDialog::keyPressEvent(e); 0178 } 0179 } 0180 0181 void DiffDialog::toggleSynchronize(bool b) 0182 { 0183 diff1->setPartner(b ? diff2 : 0); 0184 diff2->setPartner(b ? diff1 : 0); 0185 } 0186 0187 void DiffDialog::comboActivated(int index) 0188 { 0189 updateHighlight(index - 1); 0190 } 0191 0192 static void interpretRegion(QString line, int *linenoA, int *linenoB) 0193 { 0194 QRegExp region("^@@ -([0-9]+),([0-9]+) \\+([0-9]+),([0-9]+) @@.*$"); 0195 0196 if (!region.exactMatch(line)) 0197 return; 0198 0199 *linenoA = region.cap(1).toInt() - 1; 0200 *linenoB = region.cap(3).toInt() - 1; 0201 } 0202 0203 static QString regionAsString(int linenoA, int linecountA, int linenoB, int linecountB) 0204 { 0205 int lineendA = linenoA + linecountA - 1; 0206 int lineendB = linenoB + linecountB - 1; 0207 QString res; 0208 if (linecountB == 0) 0209 res = QString("%1,%2d%3").arg(linenoA).arg(lineendA).arg(linenoB - 1); 0210 else if (linecountA == 0) 0211 res = QString("%1a%2,%3").arg(linenoA - 1).arg(linenoB).arg(lineendB); 0212 else if (linenoA == lineendA) 0213 if (linenoB == lineendB) 0214 res = QString("%1c%2").arg(linenoA).arg(linenoB); 0215 else 0216 res = QString("%1c%2,%3").arg(linenoA).arg(linenoB).arg(lineendB); 0217 else if (linenoB == lineendB) 0218 res = QString("%1,%2c%3").arg(linenoA).arg(lineendA).arg(linenoB); 0219 else 0220 res = QString("%1,%2c%3,%4").arg(linenoA).arg(lineendA).arg(linenoB).arg(lineendB); 0221 0222 return res; 0223 } 0224 0225 class DiffItem 0226 { 0227 public: 0228 DiffView::DiffType type; 0229 int linenoA, linecountA; 0230 int linenoB, linecountB; 0231 }; 0232 0233 bool DiffDialog::parseCvsDiff(OrgKdeCervisia5CvsserviceCvsserviceInterface *service, const QString &fileName, const QString &revA, const QString &revB) 0234 { 0235 QStringList linesA, linesB; 0236 int linenoA, linenoB; 0237 0238 setWindowTitle(i18n("CVS Diff: %1", fileName)); 0239 revlabel1->setText(revA.isEmpty() ? i18n("Repository:") : i18n("Revision ") + revA + ':'); 0240 revlabel2->setText(revB.isEmpty() ? i18n("Working dir:") : i18n("Revision ") + revB + ':'); 0241 0242 KConfigGroup cs(&partConfig, "General"); 0243 0244 // Ok, this is a hack: When the user wants an external diff 0245 // front end, it is executed from here. Of course, in that 0246 // case this dialog wouldn't have to be created in the first 0247 // place, but this design at least makes the handling trans- 0248 // parent for the calling routines 0249 0250 QString extdiff = cs.readPathEntry("ExternalDiff", QString()); 0251 if (!extdiff.isEmpty()) { 0252 callExternalDiff(extdiff, service, fileName, revA, revB); 0253 return false; 0254 } 0255 0256 const QString diffOptions = cs.readEntry("DiffOptions"); 0257 const unsigned contextLines = cs.readEntry("ContextLines", 65535); 0258 0259 QDBusReply<QDBusObjectPath> job = service->diff(fileName, revA, revB, diffOptions, contextLines); 0260 if (!job.isValid()) 0261 return false; 0262 0263 ProgressDialog dlg(this, "Diff", service->service(), job, "diff", i18n("CVS Diff")); 0264 if (!dlg.execute()) 0265 return false; 0266 0267 // remember diff output for "save as" action 0268 m_diffOutput = dlg.getOutput(); 0269 0270 QString line; 0271 while (dlg.getLine(line) && !line.startsWith("+++")) 0272 ; 0273 0274 linenoA = linenoB = 0; 0275 while (dlg.getLine(line)) { 0276 // line contains diff region? 0277 if (line.startsWith(QLatin1String("@@"))) { 0278 interpretRegion(line, &linenoA, &linenoB); 0279 diff1->addLine(line, DiffView::Separator); 0280 diff2->addLine(line, DiffView::Separator); 0281 continue; 0282 } 0283 0284 if (line.length() < 1) 0285 continue; 0286 0287 QChar marker = line[0]; 0288 line.remove(0, 1); 0289 0290 if (marker == '-') 0291 linesA.append(line); 0292 else if (marker == '+') 0293 linesB.append(line); 0294 else { 0295 if (!linesA.isEmpty() || !linesB.isEmpty()) { 0296 newDiffHunk(linenoA, linenoB, linesA, linesB); 0297 0298 linesA.clear(); 0299 linesB.clear(); 0300 } 0301 diff1->addLine(line, DiffView::Unchanged, ++linenoA); 0302 diff2->addLine(line, DiffView::Unchanged, ++linenoB); 0303 } 0304 } 0305 0306 if (!linesA.isEmpty() || !linesB.isEmpty()) 0307 newDiffHunk(linenoA, linenoB, linesA, linesB); 0308 0309 // sets the right size as there is no more auto resize in QComboBox 0310 itemscombo->adjustSize(); 0311 0312 updateNofN(); 0313 0314 return true; 0315 } 0316 0317 void DiffDialog::newDiffHunk(int &linenoA, int &linenoB, const QStringList &linesA, const QStringList &linesB) 0318 { 0319 auto item = new DiffItem; 0320 item->linenoA = linenoA + 1; 0321 item->linenoB = linenoB + 1; 0322 item->linecountA = linesA.count(); 0323 item->linecountB = linesB.count(); 0324 items.append(item); 0325 0326 const QString region = regionAsString(linenoA + 1, linesA.count(), linenoB + 1, linesB.count()); 0327 itemscombo->addItem(region); 0328 0329 QStringList::ConstIterator itA = linesA.begin(); 0330 QStringList::ConstIterator itB = linesB.begin(); 0331 while (itA != linesA.end() || itB != linesB.end()) { 0332 if (itA != linesA.end()) { 0333 diff1->addLine(*itA, DiffView::Neutral, ++linenoA); 0334 if (itB != linesB.end()) 0335 diff2->addLine(*itB, DiffView::Change, ++linenoB); 0336 else 0337 diff2->addLine("", DiffView::Delete); 0338 } else { 0339 diff1->addLine("", DiffView::Neutral); 0340 diff2->addLine(*itB, DiffView::Insert, ++linenoB); 0341 } 0342 0343 if (itA != linesA.end()) 0344 ++itA; 0345 if (itB != linesB.end()) 0346 ++itB; 0347 } 0348 } 0349 0350 void DiffDialog::callExternalDiff(const QString &extdiff, 0351 OrgKdeCervisia5CvsserviceCvsserviceInterface *service, 0352 const QString &fileName, 0353 const QString &revA, 0354 const QString &revB) 0355 { 0356 QString extcmdline = extdiff; 0357 extcmdline += ' '; 0358 0359 // create suffix for temporary file (used QFileInfo to remove path from file name) 0360 const QString suffix = '-' + QFileInfo(fileName).fileName(); 0361 0362 QDBusReply<QDBusObjectPath> job; 0363 if (!revA.isEmpty() && !revB.isEmpty()) { 0364 // We're comparing two revisions 0365 QString revAFilename = tempFileName(suffix + QString("-") + revA); 0366 QString revBFilename = tempFileName(suffix + QString("-") + revB); 0367 0368 // download the files for revision A and B 0369 job = service->downloadRevision(fileName, revA, revAFilename, revB, revBFilename); 0370 if (!job.isValid()) 0371 return; 0372 0373 extcmdline += KShell::quoteArg(revAFilename); 0374 extcmdline += ' '; 0375 extcmdline += KShell::quoteArg(revBFilename); 0376 } else { 0377 // We're comparing to a file, and perhaps one revision 0378 QString revAFilename = tempFileName(suffix + QString("-") + revA); 0379 job = service->downloadRevision(fileName, revA, revAFilename); 0380 if (!job.isValid()) 0381 return; 0382 0383 extcmdline += KShell::quoteArg(revAFilename); 0384 extcmdline += ' '; 0385 extcmdline += KShell::quoteArg(QFileInfo(fileName).absoluteFilePath()); 0386 } 0387 0388 ProgressDialog dlg(this, "Diff", service->service(), job, "diff"); 0389 if (dlg.execute()) { 0390 // call external diff application 0391 KProcess proc; 0392 proc.setShellCommand(extcmdline); 0393 proc.startDetached(); 0394 } 0395 } 0396 0397 void DiffDialog::updateNofN() 0398 { 0399 QString str; 0400 if (markeditem >= 0) 0401 str = i18n("%1 of %2", markeditem + 1, items.count()); 0402 else 0403 str = i18np("%1 difference", "%1 differences", items.count()); 0404 nofnlabel->setText(str); 0405 0406 itemscombo->setCurrentIndex(markeditem == -2 ? 0 : markeditem + 1); 0407 0408 backbutton->setEnabled(markeditem != -1); 0409 forwbutton->setEnabled(markeditem != -2 && items.count()); 0410 } 0411 0412 void DiffDialog::updateHighlight(int newitem) 0413 { 0414 if (markeditem >= 0) { 0415 DiffItem *item = items.at(markeditem); 0416 for (int i = item->linenoA; i < item->linenoA + item->linecountA; ++i) 0417 diff1->setInverted(i, false); 0418 for (int i = item->linenoB; i < item->linenoB + item->linecountB; ++i) 0419 diff2->setInverted(i, false); 0420 } 0421 0422 markeditem = newitem; 0423 0424 if (markeditem >= 0) { 0425 DiffItem *item = items.at(markeditem); 0426 for (int i = item->linenoA; i < item->linenoA + item->linecountA; ++i) 0427 diff1->setInverted(i, true); 0428 for (int i = item->linenoB; i < item->linenoB + item->linecountB; ++i) 0429 diff2->setInverted(i, true); 0430 diff1->setCenterLine(item->linenoA); 0431 diff2->setCenterLine(item->linenoB); 0432 } 0433 diff1->repaint(); 0434 diff2->repaint(); 0435 updateNofN(); 0436 } 0437 0438 void DiffDialog::backClicked() 0439 { 0440 int newitem; 0441 if (markeditem == -1) 0442 return; // internal error (button not disabled) 0443 else if (markeditem == -2) // past end 0444 newitem = items.count() - 1; 0445 else 0446 newitem = markeditem - 1; 0447 updateHighlight(newitem); 0448 } 0449 0450 void DiffDialog::forwClicked() 0451 { 0452 int newitem; 0453 if (markeditem == -2 || (markeditem == -1 && !items.count())) 0454 return; // internal error (button not disabled) 0455 else if (markeditem + 1 == items.count()) // past end 0456 newitem = -2; 0457 else 0458 newitem = markeditem + 1; 0459 updateHighlight(newitem); 0460 } 0461 0462 void DiffDialog::saveAsClicked() 0463 { 0464 QString fileName = QFileDialog::getSaveFileName(this, QString(), QString(), QString()); 0465 if (fileName.isEmpty()) 0466 return; 0467 0468 if (!Cervisia::CheckOverwrite(fileName, this)) 0469 return; 0470 0471 QFile f(fileName); 0472 if (!f.open(QIODevice::WriteOnly)) { 0473 KMessageBox::error(this, i18n("Could not open file for writing."), "Cervisia"); 0474 return; 0475 } 0476 0477 QTextStream ts(&f); 0478 QStringList::const_iterator it = m_diffOutput.constBegin(); 0479 for (; it != m_diffOutput.constEnd(); ++it) 0480 ts << *it << "\n"; 0481 0482 f.close(); 0483 } 0484 0485 // Local Variables: 0486 // c-basic-offset: 4 0487 // End: