00001
00002
00003
00004
00005
00006
00007
00008
00009 #include <qtextedit.h>
00010 #include <qlineedit.h>
00011 #include <qapplication.h>
00012 #include <qsyntaxhighlighter.h>
00013 #include <qradiobutton.h>
00014 #include <qbuttongroup.h>
00015 #include <qtoolbutton.h>
00016 #include <qtabwidget.h>
00017 #include <qaction.h>
00018 #include "common.h"
00019 #include "git.h"
00020 #include "domain.h"
00021 #include "mainimpl.h"
00022 #include "revdesc.h"
00023 #include "filelist.h"
00024 #include "patchbase.h"
00025 #include "patchview.h"
00026
00027 class DiffHighlighter : public QSyntaxHighlighter {
00028 public:
00029 DiffHighlighter(PatchView* p, QTextEdit* te) :
00030 QSyntaxHighlighter(te), pv(p), combinedLenght(0) {}
00031
00032 void setCombinedLength(uint cl) { combinedLenght = cl; }
00033 virtual int highlightParagraph (const QString& text, int) {
00034
00035 QColor myColor;
00036 const char firstChar = text[0].latin1();
00037 switch (firstChar) {
00038 case '@':
00039 myColor = Qt::darkMagenta;
00040 break;
00041 case '+':
00042 myColor = Qt::darkGreen;
00043 break;
00044 case '-':
00045 myColor = Qt::red;
00046 break;
00047 case 'c':
00048 case 'd':
00049 case 'i':
00050 case 'n':
00051 case 'o':
00052 case 'r':
00053 case 's':
00054 if ( text.startsWith("diff --git a/")
00055 || text.startsWith("copy ")
00056 || text.startsWith("index ")
00057 || text.startsWith("new ")
00058 || text.startsWith("old ")
00059 || text.startsWith("rename ")
00060 || text.startsWith("similarity "))
00061 myColor = Qt::darkBlue;
00062 else if (combinedLenght > 0 && text.startsWith("diff --combined"))
00063 myColor = Qt::darkBlue;
00064 break;
00065 case ' ':
00066 if (combinedLenght > 0) {
00067 if (text.left(combinedLenght).contains('+'))
00068 myColor = Qt::darkGreen;
00069 else if (text.left(combinedLenght).contains('-'))
00070 myColor = Qt::red;
00071 }
00072 break;
00073 }
00074 if (myColor.isValid())
00075 setFormat(0, text.length(), myColor);
00076
00077 if (pv->matches.count() > 0) {
00078 int indexFrom, indexTo;
00079 if (pv->getMatch(currentParagraph(), &indexFrom, &indexTo)) {
00080
00081 QFont f = textEdit()->currentFont();
00082 f.setUnderline(true);
00083 f.setBold(true);
00084 if (indexTo == 0)
00085 indexTo = text.length();
00086
00087 setFormat(indexFrom, indexTo - indexFrom, f, Qt::blue);
00088 }
00089 }
00090 return 0;
00091 }
00092 private:
00093 PatchView* pv;
00094 uint combinedLenght;
00095 };
00096
00097 PatchView::PatchView(MainImpl* mi, Git* g) : Domain(mi, g) {
00098
00099 seekTarget = diffLoaded = false;
00100 pickAxeRE.setMinimal(true);
00101 pickAxeRE.setCaseSensitive(false);
00102
00103 patchTab = new TabPatch(m());
00104 patchTab->toolButton_all->hide();
00105 patchTab->toolButton_added->hide();
00106 patchTab->toolButton_removed->hide();
00107 patchTab->textEditDiff->setFont(QGit::TYPE_WRITER_FONT);
00108 patchTab->textBrowserDesc->setDomain(this);
00109 patchTab->buttonFilterPatch->setIconSet(patchTab->toolButton_all->iconSet());
00110 curFilter = prevFilter = VIEW_ALL;
00111
00112 listBoxFiles = new ListBoxFiles(this, git, patchTab->listBoxFiles);
00113 diffHighlighter = new DiffHighlighter(this, patchTab->textEditDiff);
00114
00115 m()->tabWdg->addTab(patchTab, "&Patch");
00116 tabPosition = m()->tabWdg->count() - 1;
00117
00118 connect(patchTab->lineEditDiff, SIGNAL(returnPressed()),
00119 this, SLOT(on_lineEditDiff_returnPressed()));
00120
00121 connect(patchTab->buttonGroupDiff, SIGNAL(clicked(int)),
00122 this, SLOT(on_buttonGroupDiff_clicked(int)));
00123
00124 connect(patchTab->buttonFilterPatch, SIGNAL(clicked()),
00125 this, SLOT(on_buttonFilterPatch_clicked()));
00126
00127 connect(listBoxFiles, SIGNAL(contextMenu(const QString&, int)),
00128 this, SLOT(on_contextMenu(const QString&, int)));
00129 }
00130
00131 PatchView::~PatchView() {
00132
00133 if (!parent())
00134 return;
00135
00136 git->cancelProcess(proc);
00137 delete diffHighlighter;
00138 delete listBoxFiles;
00139
00140
00141 m()->tabWdg->removePage(patchTab);
00142 delete patchTab;
00143 }
00144
00145 void PatchView::clear(bool complete) {
00146
00147 if (complete) {
00148 st.clear();
00149 patchTab->textBrowserDesc->clear();
00150 listBoxFiles->clear();
00151 }
00152 patchTab->textEditDiff->clear();
00153 patchRowData.resize(0);
00154 partialParagraphs = "";
00155 matches.clear();
00156 diffLoaded = false;
00157 seekTarget = !target.isEmpty();
00158 }
00159
00160 void PatchView::on_buttonFilterPatch_clicked() {
00161
00162 QIconSet ic;
00163 prevFilter = curFilter;
00164 if (curFilter == VIEW_ALL) {
00165 curFilter = VIEW_ADDED;
00166 ic = patchTab->toolButton_added->iconSet();
00167
00168 } else if (curFilter == VIEW_ADDED) {
00169 curFilter = VIEW_REMOVED;
00170 ic = patchTab->toolButton_removed->iconSet();
00171
00172 } else if (curFilter == VIEW_REMOVED) {
00173 curFilter = VIEW_ALL;
00174 ic = patchTab->toolButton_all->iconSet();
00175 }
00176 patchTab->buttonFilterPatch->setIconSet(ic);
00177 QTextEdit* te = patchTab->textEditDiff;
00178 int topPara = te->paragraphAt(QPoint(te->contentsX(), te->contentsY()));
00179 partialParagraphs = "";
00180 patchTab->textEditDiff->setUpdatesEnabled(false);
00181 patchTab->textEditDiff->setText(processData(patchRowData, &topPara));
00182 int t = te->paragraphRect(topPara).bottom();
00183 te->setContentsPos(0, t);
00184 patchTab->textEditDiff->setUpdatesEnabled(true);
00185 }
00186
00187 void PatchView::centerOnFileHeader(const QString& fileName) {
00188
00189 if (st.fileName().isEmpty())
00190 return;
00191
00192 target = fileName;
00193 bool combined = (st.isMerge() && !st.allMergeFiles());
00194 git->formatPatchFileHeader(&target, st.sha(), st.diffToSha(), combined, st.allMergeFiles());
00195 seekTarget = !target.isEmpty();
00196 if (seekTarget)
00197 centerTarget();
00198 }
00199
00200 void PatchView::on_contextMenu(const QString& data, int type) {
00201
00202 if (isLinked())
00203 Domain::on_contextMenu(data, type);
00204 }
00205
00206 void PatchView::centerTarget() {
00207
00208 patchTab->textEditDiff->setCursorPosition(0, 0);
00209 if (!patchTab->textEditDiff->find(target, true, true))
00210 return;
00211
00212
00213 seekTarget = false;
00214 int para, index;
00215 patchTab->textEditDiff->getCursorPosition(¶, &index);
00216 QPoint p = patchTab->textEditDiff->paragraphRect(para).topLeft();
00217 patchTab->textEditDiff->setContentsPos(p.x(), p.y());
00218 patchTab->textEditDiff->removeSelection();
00219 }
00220
00221 void PatchView::centerMatch(uint id) {
00222
00223 if (matches.count() <= id)
00224 return;
00225
00226 patchTab->textEditDiff->setSelection(matches[id].paraFrom, matches[id].indexFrom,
00227 matches[id].paraTo, matches[id].indexTo);
00228 }
00229
00230 void PatchView::on_procDataReady(const QByteArray& data) {
00231
00232 int X = patchTab->textEditDiff->contentsX();
00233 int Y = patchTab->textEditDiff->contentsY();
00234 bool targetInNewChunk = false;
00235
00236 QGit::baAppend(patchRowData, data);
00237
00238
00239
00240
00241
00242 SCRef newLines = processData(data);
00243 patchTab->textEditDiff->append(newLines);
00244
00245 if (seekTarget)
00246 targetInNewChunk = (newLines.find(target) != -1);
00247
00248 if (targetInNewChunk)
00249 centerTarget();
00250 else {
00251 patchTab->textEditDiff->setContentsPos(X, Y);
00252 patchTab->textEditDiff->sync();
00253 }
00254 }
00255
00256 const QString PatchView::processData(const QByteArray& fileChunk, int* prevLineNum) {
00257
00258 QString newLines;
00259 if (!QGit::stripPartialParaghraps(fileChunk, &newLines, &partialParagraphs))
00260 return newLines;
00261
00262 if (!prevLineNum && curFilter == VIEW_ALL)
00263 goto skip_filter;
00264
00265 {
00266
00267 QString filteredLines;
00268 int notNegCnt = 0, notPosCnt = 0;
00269 QValueVector<int> toAdded(1, 0), toRemoved(1, 0);
00270
00271
00272
00273
00274 if (prevLineNum && prevFilter == VIEW_ALL)
00275 *prevLineNum = -(*prevLineNum);
00276
00277 const QStringList sl(QStringList::split('\n', newLines, true));
00278 FOREACH_SL (it, sl) {
00279
00280
00281 bool n = (*it).startsWith("-") && !(*it).startsWith("---");
00282 bool p = (*it).startsWith("+") && !(*it).startsWith("+++");
00283
00284 if (!p)
00285 notPosCnt++;
00286 if (!n)
00287 notNegCnt++;
00288
00289 toAdded.append(notNegCnt);
00290 toRemoved.append(notPosCnt);
00291
00292 int curLineNum = toAdded.count() - 1;
00293
00294 bool toRemove = (n && curFilter == VIEW_ADDED) || (p && curFilter == VIEW_REMOVED);
00295 if (!toRemove)
00296 filteredLines.append(*it).append('\n');
00297
00298 if (prevLineNum && *prevLineNum == notNegCnt && prevFilter == VIEW_ADDED)
00299 *prevLineNum = -curLineNum;
00300
00301 if (prevLineNum && *prevLineNum == notPosCnt && prevFilter == VIEW_REMOVED)
00302 *prevLineNum = -curLineNum;
00303 }
00304 if (prevLineNum && *prevLineNum <= 0) {
00305 if (curFilter == VIEW_ALL)
00306 *prevLineNum = -(*prevLineNum);
00307
00308 else if (curFilter == VIEW_ADDED)
00309 *prevLineNum = toAdded.at(-(*prevLineNum));
00310
00311 else if (curFilter == VIEW_REMOVED)
00312 *prevLineNum = toRemoved.at(-(*prevLineNum));
00313
00314 if (*prevLineNum < 0)
00315 *prevLineNum = 0;
00316 }
00317 newLines = filteredLines;
00318
00319 }
00320
00321 skip_filter:
00322
00323 return newLines;
00324 }
00325
00326 void PatchView::on_eof() {
00327
00328 if ( !patchRowData.isEmpty()
00329 && patchRowData.at(patchRowData.size() - 1) != '\n')
00330 patchTab->textEditDiff->append(processData('\n'));
00331
00332 diffLoaded = true;
00333 computeMatches();
00334 diffHighlighter->rehighlight();
00335 centerMatch();
00336 }
00337
00338 int PatchView::doSearch(SCRef txt, int pos) {
00339
00340 if (isRegExp)
00341 return pickAxeRE.search(txt, pos);
00342
00343 return txt.find(pickAxeRE.pattern(), pos, true);
00344 }
00345
00346 void PatchView::computeMatches() {
00347
00348 matches.clear();
00349 if (pickAxeRE.isEmpty())
00350 return;
00351
00352 SCRef txt = patchTab->textEditDiff->text();
00353 int pos, lastPos = 0, lastPara = 0;
00354
00355
00356 while ((pos = doSearch(txt, lastPos)) != -1) {
00357
00358 matches.append(MatchSelection());
00359 MatchSelection& s = matches.last();
00360
00361 s.paraFrom = txt.mid(lastPos, pos - lastPos).contains('\n');
00362 s.paraFrom += lastPara;
00363 s.indexFrom = pos - txt.findRev('\n', pos) - 1;
00364
00365 lastPos = pos;
00366 pos += (isRegExp) ? pickAxeRE.matchedLength() : pickAxeRE.pattern().length();
00367 pos--;
00368
00369 s.paraTo = s.paraFrom + txt.mid(lastPos, pos - lastPos).contains('\n');
00370 s.indexTo = pos - txt.findRev('\n', pos) - 1;
00371 s.indexTo++;
00372
00373 lastPos = pos;
00374 lastPara = s.paraTo;
00375 }
00376 }
00377
00378 bool PatchView::getMatch(int para, int* indexFrom, int* indexTo) {
00379
00380 for (uint i = 0; i < matches.count(); i++)
00381 if (matches[i].paraFrom <= para && matches[i].paraTo >= para) {
00382
00383 *indexFrom = (para == matches[i].paraFrom) ? matches[i].indexFrom : 0;
00384 *indexTo = (para == matches[i].paraTo) ? matches[i].indexTo : 0;
00385 return true;
00386 }
00387 return false;
00388 }
00389
00390 void PatchView::on_highlightPatch(const QString& exp, bool re) {
00391
00392 pickAxeRE.setPattern(exp);
00393 isRegExp = re;
00394 if (diffLoaded)
00395 on_eof();
00396 }
00397
00398 void PatchView::on_lineEditDiff_returnPressed() {
00399
00400 if (patchTab->lineEditDiff->text().isEmpty())
00401 return;
00402
00403 patchTab->radioButtonSha->setChecked(true);
00404 on_buttonGroupDiff_clicked(DIFF_TO_SHA);
00405 }
00406
00407 void PatchView::on_buttonGroupDiff_clicked(int diffType) {
00408
00409 QString sha;
00410 switch (diffType) {
00411 case DIFF_TO_PARENT:
00412 break;
00413 case DIFF_TO_HEAD:
00414 sha = "HEAD";
00415 break;
00416 case DIFF_TO_SHA:
00417 sha = patchTab->lineEditDiff->text();
00418 break;
00419 }
00420 if (sha == QGit::ZERO_SHA)
00421 return;
00422
00423
00424 normalizedSha = (sha.length() != 40 && !sha.isEmpty()) ? git->getRefSha(sha) : sha;
00425
00426 if (normalizedSha != st.diffToSha()) {
00427 st.setDiffToSha(normalizedSha);
00428 UPDATE();
00429 }
00430 }
00431
00432 void PatchView::on_updateRevDesc() {
00433
00434 bool showHeader = m()->ActShowDescHeader->isOn();
00435 SCRef d(git->getDesc(st.sha(), m()->shortLogRE, m()->longLogRE, showHeader));
00436 patchTab->textBrowserDesc->setText(d);
00437 patchTab->textBrowserDesc->setCursorPosition(0, 0);
00438 }
00439
00440 void PatchView::updatePatch() {
00441
00442 git->cancelProcess(proc);
00443 clear(false);
00444
00445 bool combined = (st.isMerge() && !st.allMergeFiles());
00446 if (combined) {
00447 const Rev* r = git->revLookup(st.sha());
00448 if (r)
00449 diffHighlighter->setCombinedLength(r->parentsCount());
00450 } else
00451 diffHighlighter->setCombinedLength(0);
00452
00453 if (normalizedSha != st.diffToSha()) {
00454
00455 if (!st.diffToSha().isEmpty()) {
00456 patchTab->lineEditDiff->setText(st.diffToSha());
00457 on_lineEditDiff_returnPressed();
00458
00459 } else if (!normalizedSha.isEmpty()) {
00460 normalizedSha = "";
00461
00462
00463 patchTab->radioButtonSha->group()->find(0)->toggle();
00464 }
00465 }
00466 proc = git->getDiff(st.sha(), this, st.diffToSha(), combined);
00467 }
00468
00469 bool PatchView::doUpdate(bool force) {
00470
00471 const RevFile* files = NULL;
00472 bool newFiles = false;
00473
00474 if (st.isChanged(StateInfo::SHA) || force) {
00475
00476 if (!isLinked()) {
00477 QString caption(git->getShortLog(st.sha()));
00478 if (caption.length() > 30)
00479 caption = caption.left(30 - 3).stripWhiteSpace().append("...");
00480
00481 m()->tabWdg->changeTab(patchTab, caption);
00482 }
00483 on_updateRevDesc();
00484 }
00485
00486 if (st.isChanged(StateInfo::ANY & ~StateInfo::FILE_NAME) || force) {
00487
00488 updatePatch();
00489 listBoxFiles->clear();
00490 files = git->getFiles(st.sha(), st.diffToSha(), st.allMergeFiles());
00491 newFiles = true;
00492 }
00493
00494 listBoxFiles->update(files, newFiles);
00495
00496 if (st.isChanged() || force)
00497 centerOnFileHeader(st.fileName());
00498
00499 return true;
00500 }