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: