/* * Copyright (C) 1999-2002 Bernd Gehrmann * bernd@mail.berlios.de * Copyright (c) 2002-2004 Christian Loose * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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 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 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: