Warning, file /sdk/cervisia/logdialog.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) 2002-2004 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 "logdialog.h"
0022 
0023 #include <KComboBox>
0024 #include <KTextEdit>
0025 #include <QTabWidget>
0026 #include <QUrl>
0027 #include <qfile.h>
0028 #include <qfileinfo.h>
0029 #include <qlabel.h>
0030 #include <qlayout.h>
0031 #include <qpushbutton.h>
0032 #include <qsplitter.h>
0033 #include <qtextstream.h>
0034 
0035 #include <KConfigGroup>
0036 #include <KHelpClient>
0037 #include <KLocalizedString>
0038 #include <kconfig.h>
0039 #include <kconfiggroup.h>
0040 #include <kfinddialog.h>
0041 #include <kmessagebox.h>
0042 #include <krun.h>
0043 #include <ktreewidgetsearchline.h>
0044 
0045 #include <KGuiItem>
0046 #include <QDialogButtonBox>
0047 #include <QFileDialog>
0048 #include <QPushButton>
0049 #include <QVBoxLayout>
0050 
0051 #include "annotatecontroller.h"
0052 #include "annotatedialog.h"
0053 #include "cvsserviceinterface.h"
0054 #include "debug.h"
0055 #include "diffdialog.h"
0056 #include "loglist.h"
0057 #include "logplainview.h"
0058 #include "logtree.h"
0059 #include "misc.h"
0060 #include "patchoptiondialog.h"
0061 #include "progressdialog.h"
0062 
0063 LogDialog::LogDialog(KConfig &cfg, QWidget *parent)
0064     : QDialog(parent)
0065     , cvsService(0)
0066     , partConfig(cfg)
0067 {
0068     auto mainLayout = new QVBoxLayout;
0069     setLayout(mainLayout);
0070 
0071     splitter = new QSplitter(Qt::Vertical, this);
0072     mainLayout->addWidget(splitter);
0073 
0074     tree = new LogTreeView(this);
0075     connect(tree, SIGNAL(revisionClicked(QString, bool)), this, SLOT(revisionSelected(QString, bool)));
0076 
0077     auto listWidget = new QWidget(this);
0078     auto listLayout = new QVBoxLayout(listWidget);
0079     auto searchLayout = new QHBoxLayout();
0080     listLayout->addLayout(searchLayout);
0081 
0082     list = new LogListView(partConfig, listWidget);
0083     listLayout->addWidget(list, 1);
0084 
0085     auto searchLine = new KTreeWidgetSearchLine(listWidget, list);
0086     auto searchLabel = new QLabel(i18n("Search:"), listWidget);
0087     searchLabel->setBuddy(searchLine);
0088     searchLayout->addWidget(searchLabel);
0089     searchLayout->addWidget(searchLine, 1);
0090 
0091     connect(list, SIGNAL(revisionClicked(QString, bool)), this, SLOT(revisionSelected(QString, bool)));
0092 
0093     plain = new LogPlainView(this);
0094     connect(plain, SIGNAL(revisionClicked(QString, bool)), this, SLOT(revisionSelected(QString, bool)));
0095 
0096     tabWidget = new QTabWidget;
0097     tabWidget->addTab(tree, i18n("&Tree"));
0098     tabWidget->addTab(listWidget, i18n("&List"));
0099     tabWidget->addTab(plain, i18n("CVS &Output"));
0100     splitter->addWidget(tabWidget);
0101     splitter->setStretchFactor(0, 1);
0102 
0103     connect(tabWidget, &QTabWidget::currentChanged, this, &LogDialog::tabChanged);
0104 
0105     tree->setWhatsThis(
0106         i18n("Choose revision A by clicking with the left "
0107              "mouse button,\nrevision B by clicking with "
0108              "the middle mouse button."));
0109 
0110     auto mainWidget = new QWidget;
0111     splitter->addWidget(mainWidget);
0112     QBoxLayout *layout = new QVBoxLayout(mainWidget);
0113     layout->setContentsMargins(0, 0, 0, 0);
0114 
0115     for (int i = 0; i < 2; ++i) {
0116         if (i == 1) {
0117             auto frame = new QFrame(mainWidget);
0118             frame->setFrameStyle(QFrame::HLine | QFrame::Sunken);
0119             layout->addWidget(frame);
0120         }
0121 
0122         auto grid = new QGridLayout();
0123         layout->addLayout(grid);
0124         grid->setRowStretch(0, 0);
0125         grid->setRowStretch(1, 0);
0126         grid->setRowStretch(2, 1);
0127         grid->setColumnStretch(0, 0);
0128         grid->setColumnStretch(1, 1);
0129         grid->setColumnStretch(2, 0);
0130         grid->setColumnStretch(3, 1);
0131         grid->setColumnStretch(4, 2);
0132 
0133         QString versionident = (i == 0) ? i18n("Revision A:") : i18n("Revision B:");
0134         auto versionlabel = new QLabel(versionident, mainWidget);
0135         grid->addWidget(versionlabel, 0, 0);
0136 
0137         revbox[i] = new QLabel(mainWidget);
0138         revbox[i]->setFrameStyle(QFrame::Panel | QFrame::Sunken);
0139         revbox[i]->setTextInteractionFlags(Qt::TextSelectableByMouse);
0140         grid->addWidget(revbox[i], 0, 1, Qt::AlignVCenter);
0141 
0142         auto selectlabel = new QLabel(i18n("Select by tag:"), mainWidget);
0143         grid->addWidget(selectlabel, 0, 2);
0144 
0145         tagcombo[i] = new KComboBox(mainWidget);
0146         QFontMetrics fm(tagcombo[i]->fontMetrics());
0147         tagcombo[i]->setMinimumWidth(fm.width("X") * 20);
0148         grid->addWidget(tagcombo[i], 0, 3);
0149 
0150         auto authorlabel = new QLabel(i18n("Author:"), mainWidget);
0151         grid->addWidget(authorlabel, 1, 0);
0152 
0153         authorbox[i] = new QLabel(mainWidget);
0154         authorbox[i]->setFrameStyle(QFrame::Panel | QFrame::Sunken);
0155         authorbox[i]->setTextInteractionFlags(Qt::TextSelectableByMouse);
0156         grid->addWidget(authorbox[i], 1, 1);
0157 
0158         auto datelabel = new QLabel(i18n("Date:"), mainWidget);
0159         grid->addWidget(datelabel, 1, 2);
0160 
0161         datebox[i] = new QLabel(mainWidget);
0162         datebox[i]->setFrameStyle(QFrame::Panel | QFrame::Sunken);
0163         datebox[i]->setTextInteractionFlags(Qt::TextSelectableByMouse);
0164         grid->addWidget(datebox[i], 1, 3);
0165 
0166         auto commentlabel = new QLabel(i18n("Comment/Tags:"), mainWidget);
0167         grid->addWidget(commentlabel, 2, 0);
0168 
0169         commentbox[i] = new KTextEdit(mainWidget);
0170         commentbox[i]->setReadOnly(true);
0171         fm = commentbox[i]->fontMetrics();
0172         commentbox[i]->setMinimumHeight(2 * fm.lineSpacing() + 10);
0173         grid->addWidget(commentbox[i], 2, 1, 1, 3);
0174 
0175         tagsbox[i] = new KTextEdit(mainWidget);
0176         tagsbox[i]->setReadOnly(true);
0177         tagsbox[i]->setMinimumHeight(2 * fm.lineSpacing() + 10);
0178         grid->addWidget(tagsbox[i], 0, 4, 3, 1);
0179     }
0180 
0181     buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Help | QDialogButtonBox::Close | QDialogButtonBox::Apply);
0182 
0183     okButton = buttonBox->button(QDialogButtonBox::Ok);
0184     okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
0185     connect(okButton, &QPushButton::clicked, this, &LogDialog::slotOk);
0186 
0187     user1Button = new QPushButton;
0188     buttonBox->addButton(user1Button, QDialogButtonBox::ActionRole);
0189 
0190     user2Button = new QPushButton;
0191     buttonBox->addButton(user2Button, QDialogButtonBox::ActionRole);
0192 
0193     user3Button = new QPushButton;
0194     buttonBox->addButton(user3Button, QDialogButtonBox::ActionRole);
0195 
0196     connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
0197 
0198     KGuiItem::assign(user1Button, KGuiItem(i18n("&Annotate A")));
0199     KGuiItem::assign(user2Button, KGuiItem(i18n("&Diff")));
0200     KGuiItem::assign(user3Button, KGuiItem(i18n("&Find")));
0201     user3Button->setVisible(false);
0202 
0203     // initially make the version info widget as small as possible
0204     splitter->setSizes(QList<int>() << height() << 10);
0205 
0206     revbox[0]->setWhatsThis(
0207         i18n("This revision is used when you click "
0208              "Annotate.\nIt is also used as the first "
0209              "item of a Diff operation."));
0210 
0211     revbox[1]->setWhatsThis(
0212         i18n("This revision is used as the second "
0213              "item of a Diff operation."));
0214 
0215     connect(tagcombo[0], SIGNAL(activated(int)), this, SLOT(tagASelected(int)));
0216     connect(tagcombo[1], SIGNAL(activated(int)), this, SLOT(tagBSelected(int)));
0217 
0218     connect(user1Button, SIGNAL(clicked()), this, SLOT(annotateClicked()));
0219     connect(user2Button, SIGNAL(clicked()), this, SLOT(diffClicked()));
0220     connect(user3Button, SIGNAL(clicked()), this, SLOT(findClicked()));
0221 
0222     connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotPatch()));
0223     connect(buttonBox, &QDialogButtonBox::helpRequested, this, &LogDialog::slotHelp);
0224 
0225     KGuiItem::assign(okButton, KGuiItem(i18n("&View A")));
0226     KGuiItem::assign(buttonBox->button(QDialogButtonBox::Apply), KGuiItem(i18n("Create Patch...")));
0227 
0228     mainLayout->addWidget(buttonBox);
0229     buttonBox->button(QDialogButtonBox::Close)->setDefault(true);
0230 
0231     setAttribute(Qt::WA_DeleteOnClose, true);
0232 
0233     KConfigGroup cg(&partConfig, "LogDialog");
0234     tabWidget->setCurrentIndex(cg.readEntry("ShowTab", 0));
0235     restoreGeometry(cg.readEntry<QByteArray>("geometry", QByteArray()));
0236 
0237     splitter->restoreState(cg.readEntry<QByteArray>("Splitter", QByteArray()));
0238 
0239     updateButtons();
0240 }
0241 
0242 //--------------------------------------------------------------------------------
0243 
0244 LogDialog::~LogDialog()
0245 {
0246     qDeleteAll(items);
0247     qDeleteAll(tags);
0248 
0249     KConfigGroup cg(&partConfig, "LogDialog");
0250     cg.writeEntry("ShowTab", tabWidget->currentIndex());
0251     cg.writeEntry("geometry", saveGeometry());
0252     cg.writeEntry("Splitter", splitter->saveState());
0253 }
0254 
0255 //--------------------------------------------------------------------------------
0256 
0257 bool LogDialog::parseCvsLog(OrgKdeCervisia5CvsserviceCvsserviceInterface *service, const QString &fileName)
0258 {
0259     QString rev;
0260 
0261     Cervisia::LogInfo logInfo;
0262 
0263     enum { Begin, Tags, Admin, Revision, Author, Branches, Comment, Finished } state;
0264 
0265     // remember DBUS reference and file name for diff or annotate
0266     cvsService = service;
0267     filename = fileName;
0268 
0269     setWindowTitle(i18n("CVS Log: %1", filename));
0270 
0271     QDBusReply<QDBusObjectPath> job = cvsService->log(filename);
0272     if (!job.isValid())
0273         return false;
0274 
0275     ProgressDialog dlg(this, "Logging", cvsService->service(), job, "log", i18n("CVS Log"));
0276     if (!dlg.execute())
0277         return false;
0278 
0279     // process cvs log output
0280     state = Begin;
0281     QString line;
0282     while (dlg.getLine(line)) {
0283         switch (state) {
0284         case Begin:
0285             if (line == "symbolic names:")
0286                 state = Tags;
0287             break;
0288         case Tags:
0289             if (line[0] == '\t') {
0290                 const QStringList strlist(splitLine(line, ':'));
0291                 rev = strlist[1].simplified();
0292                 const QString tag(strlist[0].simplified());
0293                 QString branchpoint;
0294                 int pos1, pos2;
0295                 if ((pos2 = rev.lastIndexOf('.')) > 0 && (pos1 = rev.lastIndexOf('.', pos2 - 1)) > 0 && rev.mid(pos1 + 1, pos2 - pos1 - 1) == "0") {
0296                     // For a branch tag 2.10.0.6, we want:
0297                     // branchpoint = "2.10"
0298                     // rev = "2.10.6"
0299                     branchpoint = rev.left(pos1);
0300                     rev.remove(pos1 + 1, pos2 - pos1);
0301                 }
0302                 if (rev != "1.1.1") {
0303                     auto taginfo = new LogDialogTagInfo;
0304                     taginfo->rev = rev;
0305                     taginfo->tag = tag;
0306                     taginfo->branchpoint = branchpoint;
0307                     tags.append(taginfo);
0308                 }
0309             } else {
0310                 state = Admin;
0311             }
0312             break;
0313         case Admin:
0314             if (line == "----------------------------") {
0315                 state = Revision;
0316             }
0317             break;
0318         case Revision:
0319             if (line.startsWith(QLatin1String("revision "))) {
0320                 logInfo.m_revision = rev = line.section(' ', 1, 1);
0321                 state = Author;
0322             }
0323             break;
0324         case Author: {
0325             if (line.startsWith(QLatin1String("date: "))) {
0326                 QStringList strList = line.split(';');
0327 
0328                 // convert date into ISO format (YYYY-MM-DDTHH:MM:SS)
0329                 int len = strList[0].length();
0330                 QString dateTimeStr = strList[0].right(len - 6); // remove 'date: '
0331                 dateTimeStr.replace('/', '-');
0332 
0333                 QString date = dateTimeStr.section(' ', 0, 0);
0334                 QString time = dateTimeStr.section(' ', 1, 1);
0335                 logInfo.m_dateTime.setTime_t(QDateTime::fromString(date + 'T' + time, Qt::ISODate).toTime_t());
0336 
0337                 logInfo.m_author = strList[1].section(':', 1, 1).trimmed();
0338 
0339                 state = Branches;
0340             }
0341         } break;
0342         case Branches:
0343             if (!line.startsWith(QLatin1String("branches:"))) {
0344                 logInfo.m_comment = line;
0345                 state = Comment;
0346             }
0347             break;
0348         case Comment:
0349             if (line == "----------------------------") {
0350                 QStringList lines = dlg.getOutput();
0351                 if ((lines.count() >= 2) && // at least revision and date line must follow
0352                     lines[0].startsWith(QLatin1String("revision ")) && lines[1].startsWith(QLatin1String("date: "))) {
0353                     state = Revision;
0354                 }
0355             } else if (line == "=============================================================================") {
0356                 state = Finished;
0357             }
0358             if (state == Comment) // still in message
0359                 logInfo.m_comment += '\n' + line;
0360             else {
0361                 // Create tagcomment
0362                 QString branchrev;
0363                 int pos1, pos2;
0364                 // 1.60.x.y => revision belongs to branch 1.60.0.x
0365                 if ((pos2 = rev.lastIndexOf('.')) > 0 && (pos1 = rev.lastIndexOf('.', pos2 - 1)) > 0)
0366                     branchrev = rev.left(pos2);
0367 
0368                 // Build Cervisia::TagInfo for logInfo
0369                 foreach (LogDialogTagInfo *tagInfo, tags) {
0370                     if (rev == tagInfo->rev) {
0371                         // This never matches branch tags...
0372                         logInfo.m_tags.push_back(Cervisia::TagInfo(tagInfo->tag, Cervisia::TagInfo::Tag));
0373                     }
0374                     if (rev == tagInfo->branchpoint) {
0375                         logInfo.m_tags.push_back(Cervisia::TagInfo(tagInfo->tag, Cervisia::TagInfo::Branch));
0376                     }
0377                     if (branchrev == tagInfo->rev) {
0378                         // ... and this never matches ordinary tags :-)
0379                         logInfo.m_tags.push_back(Cervisia::TagInfo(tagInfo->tag, Cervisia::TagInfo::OnBranch));
0380                     }
0381                 }
0382 
0383                 plain->addRevision(logInfo);
0384                 tree->addRevision(logInfo);
0385                 list->addRevision(logInfo);
0386 
0387                 items.append(new Cervisia::LogInfo(logInfo));
0388 
0389                 // reset for next entry
0390                 logInfo = Cervisia::LogInfo();
0391             }
0392             break;
0393         case Finished:;
0394         }
0395     }
0396 
0397     tagcombo[0]->addItem(QString());
0398     tagcombo[1]->addItem(QString());
0399     foreach (LogDialogTagInfo *tagInfo, tags) {
0400         QString str = tagInfo->tag;
0401         if (!tagInfo->branchpoint.isEmpty())
0402             str += i18n(" (Branchpoint)");
0403         tagcombo[0]->addItem(str);
0404         tagcombo[1]->addItem(str);
0405     }
0406 
0407     plain->scrollToTop();
0408 
0409     tree->collectConnections();
0410     tree->recomputeCellSizes();
0411 
0412     return true; // successful
0413 }
0414 
0415 //--------------------------------------------------------------------------------
0416 
0417 void LogDialog::slotOk()
0418 {
0419     // make sure that the user selected a revision
0420     if (selectionA.isEmpty() && selectionB.isEmpty()) {
0421         KMessageBox::information(this, i18n("Please select revision A or B first."), "Cervisia");
0422         return;
0423     }
0424 
0425     // retrieve the selected revision
0426     QString revision;
0427     if (!selectionA.isEmpty())
0428         revision = selectionA;
0429     else
0430         revision = selectionB;
0431 
0432     // create a temporary file
0433     const QString suffix('-' + revision + '-' + QFileInfo(filename).fileName());
0434     const QString tempFileName(::tempFileName(suffix));
0435 
0436     // retrieve the file with the selected revision from cvs
0437     // and save the content into the temporary file
0438     QDBusReply<QDBusObjectPath> job = cvsService->downloadRevision(filename, revision, tempFileName);
0439     if (!job.isValid())
0440         return;
0441 
0442     ProgressDialog dlg(this, "View", cvsService->service(), job, "view", i18n("View File"));
0443     if (dlg.execute()) {
0444         // make file read-only
0445         QFile::setPermissions(tempFileName, QFileDevice::ReadOwner);
0446 
0447         // open file in preferred editor
0448         (void)new KRun(QUrl::fromLocalFile(tempFileName), 0, true);
0449     }
0450 }
0451 
0452 //--------------------------------------------------------------------------------
0453 
0454 void LogDialog::slotPatch()
0455 {
0456     if (selectionA.isEmpty()) {
0457         KMessageBox::information(this, i18n("Please select revision A or revisions A and B first."), "Cervisia");
0458         return;
0459     }
0460 
0461     Cervisia::PatchOptionDialog optionDlg;
0462     if (optionDlg.exec() == QDialog::Rejected)
0463         return;
0464 
0465     QString format = optionDlg.formatOption();
0466     QString diffOptions = optionDlg.diffOptions();
0467 
0468     QDBusReply<QDBusObjectPath> job = cvsService->diff(filename, selectionA, selectionB, diffOptions, format);
0469     if (!job.isValid())
0470         return;
0471 
0472     ProgressDialog dlg(this, "Diff", cvsService->service(), job, "", i18n("CVS Diff"));
0473     if (!dlg.execute())
0474         return;
0475 
0476     QString fileName = QFileDialog::getSaveFileName();
0477     if (fileName.isEmpty())
0478         return;
0479 
0480     if (!Cervisia::CheckOverwrite(fileName))
0481         return;
0482 
0483     QFile f(fileName);
0484     if (!f.open(QIODevice::WriteOnly)) {
0485         KMessageBox::error(this, i18n("Could not open file for writing."), "Cervisia");
0486         return;
0487     }
0488 
0489     QTextStream t(&f);
0490     QString line;
0491     while (dlg.getLine(line))
0492         t << line << '\n';
0493 
0494     f.close();
0495 }
0496 
0497 //--------------------------------------------------------------------------------
0498 
0499 void LogDialog::slotHelp()
0500 {
0501     KHelpClient::invokeHelp(QLatin1String("browsinglogs"));
0502 }
0503 
0504 //--------------------------------------------------------------------------------
0505 
0506 void LogDialog::findClicked()
0507 {
0508     KFindDialog dlg(this);
0509     if (dlg.exec() == QDialog::Accepted)
0510         plain->searchText(dlg.options(), dlg.pattern());
0511 }
0512 
0513 void LogDialog::diffClicked()
0514 {
0515     if (selectionA.isEmpty()) {
0516         KMessageBox::information(this, i18n("Please select revision A or revisions A and B first."), "Cervisia");
0517         return;
0518     }
0519 
0520     // Non-modal dialog
0521     auto l = new DiffDialog(partConfig);
0522     if (l->parseCvsDiff(cvsService, filename, selectionA, selectionB))
0523         l->show();
0524     else
0525         delete l;
0526 }
0527 
0528 void LogDialog::annotateClicked()
0529 {
0530     auto l = new AnnotateDialog(partConfig);
0531     AnnotateController ctl(l, cvsService);
0532     ctl.showDialog(filename, selectionA);
0533 }
0534 
0535 void LogDialog::revisionSelected(QString rev, bool rmb)
0536 {
0537     foreach (Cervisia::LogInfo *logInfo, items)
0538         if (logInfo->m_revision == rev) {
0539             if (rmb)
0540                 selectionB = rev;
0541             else
0542                 selectionA = rev;
0543 
0544             revbox[rmb ? 1 : 0]->setText(rev);
0545             authorbox[rmb ? 1 : 0]->setText(logInfo->m_author);
0546             datebox[rmb ? 1 : 0]->setText(logInfo->dateTimeToString());
0547             commentbox[rmb ? 1 : 0]->setPlainText(logInfo->m_comment);
0548             tagsbox[rmb ? 1 : 0]->setPlainText(logInfo->tagsToString());
0549 
0550             tree->setSelectedPair(selectionA, selectionB);
0551             list->setSelectedPair(selectionA, selectionB);
0552 
0553             updateButtons();
0554             return;
0555         }
0556     qCDebug(log_cervisia) << "Internal error: Revision not found " << rev << ".";
0557 }
0558 
0559 void LogDialog::tagSelected(LogDialogTagInfo *tag, bool rmb)
0560 {
0561     if (tag->branchpoint.isEmpty())
0562         revisionSelected(tag->rev, rmb);
0563     else
0564         revisionSelected(tag->branchpoint, rmb);
0565 }
0566 
0567 void LogDialog::updateButtons()
0568 {
0569     // no versions selected?
0570     if (selectionA.isEmpty() && selectionB.isEmpty()) {
0571         user1Button->setEnabled(true);
0572         user2Button->setEnabled(false);
0573         okButton->setEnabled(false); // view
0574         buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); // create patch
0575     }
0576     // both versions selected?
0577     else if (!selectionA.isEmpty() && !selectionB.isEmpty()) {
0578         user1Button->setEnabled(true);
0579         user2Button->setEnabled(true);
0580         okButton->setEnabled(true); // view A
0581         buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true); // create patch
0582     }
0583     // only single version selected?
0584     else {
0585         user1Button->setEnabled(true);
0586         user2Button->setEnabled(true);
0587         okButton->setEnabled(true); // view
0588         buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true); // create patch
0589     }
0590 }
0591 
0592 void LogDialog::tagASelected(int n)
0593 {
0594     if (n)
0595         tagSelected(tags.at(n - 1), false);
0596 }
0597 
0598 void LogDialog::tagBSelected(int n)
0599 {
0600     if (n)
0601         tagSelected(tags.at(n - 1), true);
0602 }
0603 
0604 void LogDialog::tabChanged(int index)
0605 {
0606     bool isPlainView = (tabWidget->widget(index) == plain);
0607     user3Button->setVisible(isPlainView);
0608 }
0609 
0610 // Local Variables:
0611 // c-basic-offset: 4
0612 // End: