summaryrefslogtreecommitdiffstats
path: root/cervisia/logdlg.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'cervisia/logdlg.cpp')
-rw-r--r--cervisia/logdlg.cpp620
1 files changed, 620 insertions, 0 deletions
diff --git a/cervisia/logdlg.cpp b/cervisia/logdlg.cpp
new file mode 100644
index 00000000..41a0751c
--- /dev/null
+++ b/cervisia/logdlg.cpp
@@ -0,0 +1,620 @@
+/*
+ * Copyright (C) 1999-2002 Bernd Gehrmann
+ * bernd@mail.berlios.de
+ * Copyright (c) 2002-2004 Christian Loose <christian.loose@kdemail.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#include "logdlg.h"
+
+#include <qcombobox.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qpushbutton.h>
+#include <qtabwidget.h>
+#include <qtextedit.h>
+#include <qwhatsthis.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kfiledialog.h>
+#include <kfinddialog.h>
+#include <kglobalsettings.h>
+#include <kiconloader.h>
+#include <klistviewsearchline.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kprocess.h>
+#include <krfcdate.h>
+#include <krun.h>
+#include <kurl.h>
+
+#include "cvsservice_stub.h"
+#include "annotatedlg.h"
+#include "annotatectl.h"
+#include "diffdlg.h"
+#include "loginfo.h"
+#include "loglist.h"
+#include "logplainview.h"
+#include "logtree.h"
+#include "misc.h"
+#include "progressdlg.h"
+#include "patchoptiondlg.h"
+
+
+LogDialog::LogDialog(KConfig& cfg, QWidget *parent, const char *name)
+ : KDialogBase(parent, name, false, QString::null,
+ Ok | Apply | Close | Help | User1 | User2 | User3, Close, true,
+ KGuiItem(i18n("&Annotate")),
+ KGuiItem(i18n("&Diff"), "vcs_diff"),
+ KGuiItem(i18n("&Find..."), "find"))
+ , cvsService(0)
+ , partConfig(cfg)
+{
+ QSplitter *splitter = new QSplitter(Qt::Vertical, this);
+ setMainWidget(splitter);
+
+ tree = new LogTreeView(this);
+ connect( tree, SIGNAL(revisionClicked(QString,bool)),
+ this, SLOT(revisionSelected(QString,bool)) );
+
+ QWidget* listWidget = new QWidget(this);
+ QVBoxLayout* listLayout = new QVBoxLayout(listWidget);
+ QHBoxLayout* searchLayout = new QHBoxLayout(listLayout);
+ searchLayout->setMargin(KDialog::spacingHint());
+ searchLayout->setSpacing(KDialog::spacingHint());
+
+ list = new LogListView(partConfig, listWidget);
+ listLayout->addWidget(list, 1);
+
+ KListViewSearchLine* searchLine = new KListViewSearchLine(listWidget, list);
+ QLabel* searchLabel = new QLabel(searchLine, i18n("S&earch:"), listWidget);
+ searchLayout->addWidget(searchLabel);
+ searchLayout->addWidget(searchLine, 1);
+
+ connect( list, SIGNAL(revisionClicked(QString,bool)),
+ this, SLOT(revisionSelected(QString,bool)) );
+
+ plain = new LogPlainView(this);
+ connect( plain, SIGNAL(revisionClicked(QString,bool)),
+ this, SLOT(revisionSelected(QString,bool)) );
+
+ tabWidget = new QTabWidget(splitter);
+ tabWidget->addTab(tree, i18n("&Tree"));
+ tabWidget->addTab(listWidget, i18n("&List"));
+ tabWidget->addTab(plain, i18n("CVS &Output"));
+
+ connect(tabWidget, SIGNAL(currentChanged(QWidget*)),
+ this, SLOT(tabChanged(QWidget*)));
+
+ QWhatsThis::add(tree, i18n("Choose revision A by clicking with the left "
+ "mouse button,\nrevision B by clicking with "
+ "the middle mouse button."));
+
+ items.setAutoDelete(true);
+ tags.setAutoDelete(true);
+
+ QWidget *mainWidget = new QWidget(splitter);
+ QBoxLayout *layout = new QVBoxLayout(mainWidget, 0, spacingHint());
+
+ for (int i = 0; i < 2; ++i)
+ {
+ if ( i == 1 )
+ {
+ QFrame *frame = new QFrame(mainWidget);
+ frame->setFrameStyle(QFrame::HLine | QFrame::Sunken);
+ layout->addWidget(frame);
+ }
+
+ QGridLayout *grid = new QGridLayout(layout);
+ grid->setRowStretch(0, 0);
+ grid->setRowStretch(1, 0);
+ grid->setRowStretch(2, 1);
+ grid->setColStretch(0, 0);
+ grid->setColStretch(1, 1);
+ grid->setColStretch(2, 0);
+ grid->setColStretch(3, 1);
+ grid->setColStretch(4, 2);
+
+ QString versionident = (i==0)? i18n("Revision A:") : i18n("Revision B:");
+ QLabel *versionlabel = new QLabel(versionident, mainWidget);
+ grid->addWidget(versionlabel, 0, 0);
+
+ revbox[i] = new QLabel(mainWidget);
+ revbox[i]->setFrameStyle(QFrame::Panel | QFrame::Sunken);
+ grid->addWidget(revbox[i], 0, 1, Qt::AlignVCenter);
+
+ QLabel *selectlabel = new QLabel(i18n("Select by tag:"), mainWidget);
+ grid->addWidget(selectlabel, 0, 2);
+
+ tagcombo[i] = new QComboBox(mainWidget);
+ QFontMetrics fm(tagcombo[i]->fontMetrics());
+ tagcombo[i]->setMinimumWidth(fm.width("X")*20);
+ grid->addWidget(tagcombo[i], 0, 3);
+
+ QLabel *authorlabel = new QLabel(i18n("Author:"), mainWidget);
+ grid->addWidget(authorlabel, 1, 0);
+
+ authorbox[i] = new QLabel(mainWidget);
+ authorbox[i]->setFrameStyle(QFrame::Panel | QFrame::Sunken);
+ grid->addWidget(authorbox[i], 1, 1);
+
+ QLabel *datelabel = new QLabel(i18n("Date:"), mainWidget);
+ grid->addWidget(datelabel, 1, 2);
+
+ datebox[i] = new QLabel(mainWidget);
+ datebox[i]->setFrameStyle(QFrame::Panel | QFrame::Sunken);
+ grid->addWidget(datebox[i], 1, 3);
+
+ QLabel *commentlabel = new QLabel(i18n("Comment/Tags:"), mainWidget);
+ grid->addWidget(commentlabel, 2, 0);
+
+ commentbox[i] = new QTextEdit(mainWidget);
+ commentbox[i]->setReadOnly(true);
+ commentbox[i]->setTextFormat(Qt::PlainText);
+ fm = commentbox[i]->fontMetrics();
+ commentbox[i]->setMinimumHeight(2*fm.lineSpacing()+10);
+ grid->addMultiCellWidget(commentbox[i], 2, 2, 1, 3);
+
+ tagsbox[i] = new QTextEdit(mainWidget);
+ tagsbox[i]->setReadOnly(true);
+ tagsbox[i]->setMinimumHeight(2*fm.lineSpacing()+10);
+ grid->addWidget(tagsbox[i], 2, 4);
+ }
+
+ QWhatsThis::add(revbox[0], i18n("This revision is used when you click "
+ "Annotate.\nIt is also used as the first "
+ "item of a Diff operation."));
+ QWhatsThis::add(revbox[1], i18n("This revision is used as the second "
+ "item of a Diff operation."));
+
+ connect( tagcombo[0], SIGNAL(activated(int)),
+ this, SLOT(tagASelected(int)) );
+ connect( tagcombo[1], SIGNAL(activated(int)),
+ this, SLOT(tagBSelected(int)) );
+
+ connect( this, SIGNAL(user1Clicked()),
+ this, SLOT(annotateClicked()) );
+ connect( this, SIGNAL(user2Clicked()),
+ this, SLOT(diffClicked()) );
+ connect( this, SIGNAL(user3Clicked()),
+ this, SLOT(findClicked()) );
+
+ setButtonGuiItem(Ok, KGuiItem(i18n("to view something", "&View"),"fileopen"));
+ setButtonGuiItem(Apply, KGuiItem(i18n("Create Patch...")));
+ setHelp("browsinglogs");
+
+ setWFlags(Qt::WDestructiveClose | getWFlags());
+
+ QSize size = configDialogSize(partConfig, "LogDialog");
+ resize(size);
+
+ KConfigGroupSaver cs(&partConfig, "LogDialog");
+ tabWidget->setCurrentPage(partConfig.readNumEntry("ShowTab", 0));
+
+ updateButtons();
+}
+
+
+LogDialog::~LogDialog()
+{
+ saveDialogSize(partConfig, "LogDialog");
+
+ KConfigGroupSaver cs(&partConfig, "LogDialog");
+ partConfig.writeEntry("ShowTab", tabWidget->currentPageIndex());
+}
+
+
+bool LogDialog::parseCvsLog(CvsService_stub* service, const QString& fileName)
+{
+ QString rev;
+
+ Cervisia::LogInfo logInfo;
+
+ enum { Begin, Tags, Admin, Revision,
+ Author, Branches, Comment, Finished } state;
+
+ // remember DCOP reference and file name for diff or annotate
+ cvsService = service;
+ filename = fileName;
+
+ setCaption(i18n("CVS Log: %1").arg(filename));
+
+ DCOPRef job = cvsService->log(filename);
+ if( !cvsService->ok() )
+ return false;
+
+ ProgressDialog dlg(this, "Logging", job, "log", i18n("CVS Log"));
+ if( !dlg.execute() )
+ return false;
+
+ // process cvs log output
+ state = Begin;
+ QString line;
+ while( dlg.getLine(line) )
+ {
+ switch( state )
+ {
+ case Begin:
+ if( line == "symbolic names:" )
+ state = Tags;
+ break;
+ case Tags:
+ if( line[0] == '\t' )
+ {
+ const QStringList strlist(splitLine(line, ':'));
+ rev = strlist[1].simplifyWhiteSpace();
+ const QString tag(strlist[0].simplifyWhiteSpace());
+ QString branchpoint;
+ int pos1, pos2;
+ if( (pos2 = rev.findRev('.')) > 0 &&
+ (pos1 = rev.findRev('.', pos2-1)) > 0 &&
+ rev.mid(pos1+1, pos2-pos1-1) == "0" )
+ {
+ // For a branch tag 2.10.0.6, we want:
+ // branchpoint = "2.10"
+ // rev = "2.10.6"
+ branchpoint = rev.left(pos1);
+ rev.remove(pos1+1, pos2-pos1);
+ }
+ if( rev != "1.1.1" )
+ {
+ LogDialogTagInfo *taginfo = new LogDialogTagInfo;
+ taginfo->rev = rev;
+ taginfo->tag = tag;
+ taginfo->branchpoint = branchpoint;
+ tags.append(taginfo);
+ }
+ }
+ else
+ {
+ state = Admin;
+ }
+ break;
+ case Admin:
+ if( line == "----------------------------" )
+ {
+ state = Revision;
+ }
+ break;
+ case Revision:
+ logInfo.m_revision = rev = line.section(' ', 1, 1);
+ state = Author;
+ break;
+ case Author:
+ {
+ QStringList strList = QStringList::split(";", line);
+
+ // convert date into ISO format (YYYY-MM-DDTHH:MM:SS)
+ int len = strList[0].length();
+ QString dateTimeStr = strList[0].right(len-6); // remove 'date: '
+ dateTimeStr.replace('/', '-');
+
+ QString date = dateTimeStr.section(' ', 0, 0);
+ QString time = dateTimeStr.section(' ', 1, 1);
+ logInfo.m_dateTime.setTime_t(KRFCDate::parseDateISO8601(date + 'T' + time));
+
+ logInfo.m_author = strList[1].section(':', 1, 1).stripWhiteSpace();
+
+ state = Branches;
+ }
+ break;
+ case Branches:
+ if( !line.startsWith("branches:") )
+ {
+ logInfo.m_comment = line;
+ state = Comment;
+ }
+ break;
+ case Comment:
+ if( line == "----------------------------" )
+ {
+ state = Revision;
+ }
+ else if( line == "=============================================================================" )
+ {
+ state = Finished;
+ }
+ if( state == Comment ) // still in message
+ logInfo.m_comment += '\n' + line;
+ else
+ {
+ // Create tagcomment
+ QString branchrev;
+ int pos1, pos2;
+ // 1.60.x.y => revision belongs to branch 1.60.0.x
+ if( (pos2 = rev.findRev('.')) > 0 &&
+ (pos1 = rev.findRev('.', pos2-1)) > 0 )
+ branchrev = rev.left(pos2);
+
+ // Build Cervisia::TagInfo for logInfo
+ QPtrListIterator<LogDialogTagInfo> it(tags);
+ for( ; it.current(); ++it )
+ {
+ if( rev == it.current()->rev )
+ {
+ // This never matches branch tags...
+ logInfo.m_tags.push_back(Cervisia::TagInfo(it.current()->tag,
+ Cervisia::TagInfo::Tag));
+ }
+ if( rev == it.current()->branchpoint )
+ {
+ logInfo.m_tags.push_back(Cervisia::TagInfo(it.current()->tag,
+ Cervisia::TagInfo::Branch));
+ }
+ if( branchrev == it.current()->rev )
+ {
+ // ... and this never matches ordinary tags :-)
+ logInfo.m_tags.push_back(Cervisia::TagInfo(it.current()->tag,
+ Cervisia::TagInfo::OnBranch));
+ }
+ }
+
+ plain->addRevision(logInfo);
+ tree->addRevision(logInfo);
+ list->addRevision(logInfo);
+
+ items.append(new Cervisia::LogInfo(logInfo));
+
+ // reset for next entry
+ logInfo = Cervisia::LogInfo();
+ }
+ break;
+ case Finished:
+ ;
+ }
+ }
+
+ tagcombo[0]->insertItem(QString::null);
+ tagcombo[1]->insertItem(QString::null);
+ QPtrListIterator<LogDialogTagInfo> it(tags);
+ for( ; it.current(); ++it )
+ {
+ QString str = it.current()->tag;
+ if( !it.current()->branchpoint.isEmpty() )
+ str += i18n(" (Branchpoint)");
+ tagcombo[0]->insertItem(str);
+ tagcombo[1]->insertItem(str);
+ }
+
+ plain->scrollToTop();
+
+ tree->collectConnections();
+ tree->recomputeCellSizes();
+
+ return true; // successful
+}
+
+
+void LogDialog::slotOk()
+{
+ // make sure that the user selected a revision
+ if( selectionA.isEmpty() && selectionB.isEmpty() )
+ {
+ KMessageBox::information(this,
+ i18n("Please select revision A or B first."), "Cervisia");
+ return;
+ }
+
+ // retrieve the selected revision
+ QString revision;
+ if( !selectionA.isEmpty() )
+ revision = selectionA;
+ else
+ revision = selectionB;
+
+ // create a temporary file
+ const QString suffix("-" + revision + "-" + QFileInfo(filename).fileName());
+ const QString tempFileName(::tempFileName(suffix));
+
+ // retrieve the file with the selected revision from cvs
+ // and save the content into the temporary file
+ DCOPRef job = cvsService->downloadRevision(filename, revision, tempFileName);
+ if( !cvsService->ok() )
+ return;
+
+ ProgressDialog dlg(this, "View", job, "view", i18n("View File"));
+ if( dlg.execute() )
+ {
+ // make file read-only
+ chmod(QFile::encodeName(tempFileName), 0400);
+
+ // open file in preferred editor
+ KURL url;
+ url.setPath(tempFileName);
+ (void) new KRun(url, 0, true, false);
+ }
+}
+
+
+void LogDialog::slotApply()
+{
+ if( selectionA.isEmpty() )
+ {
+ KMessageBox::information(this,
+ i18n("Please select revision A or revisions A and B first."),
+ "Cervisia");
+ return;
+ }
+
+ Cervisia::PatchOptionDialog optionDlg;
+ if( optionDlg.exec() == KDialogBase::Rejected )
+ return;
+
+ QString format = optionDlg.formatOption();
+ QString diffOptions = optionDlg.diffOptions();
+
+ DCOPRef job = cvsService->diff(filename, selectionA, selectionB, diffOptions,
+ format);
+ if( !cvsService->ok() )
+ return;
+
+ ProgressDialog dlg(this, "Diff", job, "", i18n("CVS Diff"));
+ if( !dlg.execute() )
+ return;
+
+ QString fileName = KFileDialog::getSaveFileName();
+ if( fileName.isEmpty() )
+ return;
+
+ if( !Cervisia::CheckOverwrite(fileName) )
+ return;
+
+ QFile f(fileName);
+ if( !f.open(IO_WriteOnly) )
+ {
+ KMessageBox::sorry(this,
+ i18n("Could not open file for writing."),
+ "Cervisia");
+ return;
+ }
+
+ QTextStream t(&f);
+ QString line;
+ while( dlg.getLine(line) )
+ t << line << '\n';
+
+ f.close();
+}
+
+
+void LogDialog::findClicked()
+{
+ KFindDialog dlg(this);
+ if( dlg.exec() == KDialogBase::Accepted )
+ plain->searchText(dlg.options(), dlg.pattern());
+}
+
+
+void LogDialog::diffClicked()
+{
+ if (selectionA.isEmpty())
+ {
+ KMessageBox::information(this,
+ i18n("Please select revision A or revisions A and B first."),
+ "Cervisia");
+ return;
+ }
+
+ // Non-modal dialog
+ DiffDialog *l = new DiffDialog(partConfig);
+ if (l->parseCvsDiff(cvsService, filename, selectionA, selectionB))
+ l->show();
+ else
+ delete l;
+}
+
+
+void LogDialog::annotateClicked()
+{
+ AnnotateDialog *l = new AnnotateDialog(partConfig);
+ AnnotateController ctl(l, cvsService);
+ ctl.showDialog(filename, selectionA);
+}
+
+
+void LogDialog::revisionSelected(QString rev, bool rmb)
+{
+ QPtrListIterator<Cervisia::LogInfo> it(items);
+ for (; it.current(); ++it)
+ if (it.current()->m_revision == rev)
+ {
+ if (rmb)
+ selectionB = rev;
+ else
+ selectionA = rev;
+
+ revbox[rmb?1:0]->setText(rev);
+ authorbox[rmb?1:0]->setText(it.current()->m_author);
+ datebox[rmb?1:0]->setText(it.current()->dateTimeToString());
+ commentbox[rmb?1:0]->setText(it.current()->m_comment);
+ tagsbox[rmb?1:0]->setText(it.current()->tagsToString());
+
+ tree->setSelectedPair(selectionA, selectionB);
+ list->setSelectedPair(selectionA, selectionB);
+
+ updateButtons();
+ return;
+ }
+ kdDebug(8050) << "Internal error: Revision not found " << rev << "." << endl;
+}
+
+
+void LogDialog::tagSelected(LogDialogTagInfo* tag, bool rmb)
+{
+ if (tag->branchpoint.isEmpty())
+ revisionSelected(tag->rev, rmb);
+ else
+ revisionSelected(tag->branchpoint, rmb);
+}
+
+
+void LogDialog::updateButtons()
+{
+ // no versions selected?
+ if( selectionA.isEmpty() && selectionB.isEmpty() )
+ {
+ enableButton(User1, true); // annotate
+ enableButton(User2, false); // diff
+ enableButtonOK(false); // view
+ enableButtonApply(false); // create patch
+ }
+ // both versions selected?
+ else if( !selectionA.isEmpty() && !selectionB.isEmpty() )
+ {
+ enableButton(User1, false); // annotate
+ enableButton(User2, true); // diff
+ enableButtonOK(false); // view
+ enableButtonApply(true); // create patch
+ }
+ // only single version selected?
+ else
+ {
+ enableButton(User1, true); // annotate
+ enableButton(User2, true); // diff
+ enableButtonOK(true); // view
+ enableButtonApply(true); // create patch
+ }
+}
+
+
+void LogDialog::tagASelected(int n)
+{
+ if (n)
+ tagSelected(tags.at(n-1), false);
+}
+
+
+void LogDialog::tagBSelected(int n)
+{
+ if (n)
+ tagSelected(tags.at(n-1), true);
+}
+
+
+void LogDialog::tabChanged(QWidget* w)
+{
+ bool isPlainView = (w == plain);
+ showButton(User3, isPlainView);
+}
+
+#include "logdlg.moc"
+
+// Local Variables:
+// c-basic-offset: 4
+// End: