00001
00002
00003
00004
00005
00006
00007 #include <qtextedit.h>
00008 #include <qsyntaxhighlighter.h>
00009 #include <qlistview.h>
00010 #include <qmessagebox.h>
00011 #include <qstatusbar.h>
00012 #include <qapplication.h>
00013 #include <qeventloop.h>
00014 #include <qcursor.h>
00015 #include <qregexp.h>
00016 #include <qclipboard.h>
00017 #include <qtoolbutton.h>
00018 #include "domain.h"
00019 #include "mainimpl.h"
00020 #include "git.h"
00021 #include "annotate.h"
00022 #include "filecontent.h"
00023
00024 #define MAX_LINE_NUM 5
00025
00026 const QString FileContent::HTML_HEAD = "<font color=\"#C0C0C0\">";
00027 const QString FileContent::HTML_TAIL = "</font>";
00028 const QString FileContent::HTML_FILE_START = "<pre><tt>";
00029 const QString FileContent::HTML_FILE_END = "</tt></pre>";
00030
00031 class FileHighlighter : public QSyntaxHighlighter {
00032 public:
00033 FileHighlighter(QTextEdit* te, FileContent* fc) : QSyntaxHighlighter(te), f(fc) {}
00034 virtual int highlightParagraph(const QString& p, int) {
00035
00036 if (f->isHtmlSource)
00037 return 0;
00038
00039 if (!f->isRangeFilterActive)
00040 setFormat(0, p.length(), textEdit()->font());
00041
00042 int headLen = f->isAnnotationAppended ? f->annoLen + MAX_LINE_NUM : MAX_LINE_NUM;
00043 setFormat(0, headLen, Qt::lightGray);
00044
00045 if (f->isRangeFilterActive && f->rangeInfo->start != 0)
00046 if ( f->rangeInfo->start - 1 <= currentParagraph()
00047 && f->rangeInfo->end - 1 >= currentParagraph()) {
00048
00049 QFont fn(textEdit()->font());
00050 fn.setBold(true);
00051 setFormat(0, p.length(), fn, Qt::blue);
00052 }
00053 return 0;
00054 }
00055 private:
00056 FileContent* f;
00057 };
00058
00059 FileContent::FileContent(Domain* dm, Git* g, QTextEdit* f) : d(dm), git(g), ft(f) {
00060
00061 st = &(d->st);
00062
00063
00064 isRangeFilterActive = isHtmlSource = isAnnotationAppended = false;
00065 isShowAnnotate = true;
00066
00067 rangeInfo = new RangeInfo();
00068 fileHighlighter = new FileHighlighter(ft, this);
00069
00070 ft->setFont(QGit::TYPE_WRITER_FONT);
00071
00072 clearAnnotate();
00073 clearText(false);
00074
00075 connect(ft, SIGNAL(doubleClicked(int,int)), this, SLOT(on_doubleClicked(int,int)));
00076
00077 connect(git, SIGNAL(annotateReady(Annotate*, const QString&, bool, const QString&)),
00078 this, SLOT(on_annotateReady(Annotate*,const QString&,bool, const QString&)));
00079 }
00080
00081 FileContent::~FileContent() {
00082
00083 clear();
00084 delete fileHighlighter;
00085 delete rangeInfo;
00086 }
00087
00088 void FileContent::clearAnnotate() {
00089
00090 git->cancelAnnotate(annotateObj);
00091 annotateObj = NULL;
00092 curAnn = NULL;
00093 annoLen = 0;
00094 isAnnotationAvailable = false;
00095 emit annotationAvailable(false);
00096 }
00097
00098 void FileContent::clearText(bool emitSignal) {
00099
00100 git->cancelProcess(proc);
00101 ft->clear();
00102 fileProcessedData = halfLine = "";
00103 fileRowData.resize(0);
00104 isFileAvailable = isAnnotationAppended = false;
00105 curLine = 1;
00106 if (curAnn)
00107 curAnnIt = curAnn->lines.constBegin();
00108
00109 if (emitSignal)
00110 emit fileAvailable(false);
00111 }
00112
00113 void FileContent::clear() {
00114
00115 clearAnnotate();
00116 clearText();
00117 }
00118
00119 void FileContent::setShowAnnotate(bool b) {
00120
00121
00122
00123
00124 isShowAnnotate = b;
00125
00126 if ( !isFileAvailable
00127 || (curAnn == NULL && isShowAnnotate)
00128 || (isAnnotationAppended == isShowAnnotate))
00129 return;
00130
00131
00132 saveScreenState();
00133 const QByteArray tmp(fileRowData);
00134 fileRowData.detach();
00135 clearText(false);
00136 fileRowData = tmp;
00137 processData(fileRowData);
00138 on_eof(false);
00139 }
00140
00141 void FileContent::setHighlightSource(bool b) {
00142
00143 if (b && !git->isTextHighlighter()) {
00144 dbs("ASSERT in setHighlightSource: no highlighter found");
00145 return;
00146 }
00147 ft->setTextFormat(b ? Qt::RichText : Qt::PlainText);
00148 isHtmlSource = b;
00149 update(true);
00150 }
00151
00152 void FileContent::update(bool force) {
00153
00154 bool shaChanged = (st->sha(true) != st->sha(false));
00155 bool fileNameChanged = (st->fileName(true) != st->fileName(false));
00156
00157 if (!fileNameChanged && !shaChanged && !force)
00158 return;
00159
00160 saveScreenState();
00161
00162 if (fileNameChanged)
00163 clear();
00164 else
00165 clearText();
00166
00167 lookupAnnotation();
00168
00169 if (isHtmlSource)
00170 proc = git->getHighlightedFile(st->fileName(), st->sha(), this);
00171 else
00172 proc = git->getFile(st->fileName(), st->sha(), this);
00173
00174 ss.isValid = false;
00175 if (isRangeFilterActive)
00176 getRange(st->sha(), rangeInfo);
00177 else if (curAnn) {
00178
00179 int& from = ss.hasSelectedText ? ss.paraFrom : ss.topPara;
00180 int& to = ss.hasSelectedText ? ss.paraTo : ss.topPara;
00181 ss.isValid = annotateObj->seekPosition(&from, &to, st->sha(false), st->sha(true));
00182 }
00183 }
00184
00185 void FileContent::startAnnotate(FileHistory* fh) {
00186
00187 annotateObj = git->startAnnotate(fh, d);
00188 }
00189
00190 uint FileContent::annotateLength(const FileAnnotation* annFile) {
00191
00192 uint maxLen = 0;
00193 FOREACH_SL (it, annFile->lines)
00194 if ((*it).length() > maxLen)
00195 maxLen = (*it).length();
00196
00197 return maxLen;
00198 }
00199
00200 bool FileContent::getRange(SCRef sha, RangeInfo* r) {
00201
00202 if (annotateObj)
00203 return annotateObj->getRange(sha, r);
00204
00205 return false;
00206 }
00207
00208 void FileContent::goToAnnotation(int revId) {
00209
00210 if ( !isAnnotationAppended
00211 || !curAnn
00212 || (revId == 0)
00213 || (ft->textFormat() == Qt::RichText))
00214 return;
00215
00216 const QString firstLine(QString::number(revId) + ".");
00217 int idx = 0;
00218 FOREACH_SL (it, curAnn->lines) {
00219 if ((*it).stripWhiteSpace().startsWith(firstLine)) {
00220 ft->setSelection(idx, 0, idx, (*it).length());
00221 return;
00222 }
00223 ++idx;
00224 }
00225 }
00226
00227 bool FileContent::goToRangeStart() {
00228
00229 if ( !isRangeFilterActive
00230 || !curAnn
00231 || (rangeInfo->start == 0))
00232 return false;
00233
00234
00235 ft->setCursorPosition(rangeInfo->start - 2, 0);
00236 int t = ft->paragraphRect(rangeInfo->start - 2).top();
00237 ft->setContentsPos(0, t);
00238 return true;
00239 }
00240
00241 void FileContent::copySelection() {
00242
00243 if (!ft->hasSelectedText())
00244 return;
00245
00246 int headLen = isAnnotationAppended ? annoLen + MAX_LINE_NUM : MAX_LINE_NUM;
00247 headLen++;
00248
00249 TextFormat tf = ft->textFormat();
00250 ft->setTextFormat(Qt::PlainText);
00251 QString sel(ft->selectedText());
00252 ft->setTextFormat(tf);
00253
00254 int indexFrom, dummy;
00255 ft->getSelection(&dummy, &indexFrom, &dummy, &dummy);
00256 QClipboard* cb = QApplication::clipboard();
00257
00258 if (indexFrom < headLen && (tf != Qt::RichText)) {
00259 sel.remove(0, headLen - indexFrom);
00260 if (sel.isEmpty()) {
00261 cb->setText(ft->selectedText(), QClipboard::Clipboard);
00262 return;
00263 }
00264 }
00265 QRegExp re("\n.{0," + QString::number(headLen) + "}");
00266 sel.replace(re, "\n");
00267 cb->setText(sel, QClipboard::Clipboard);
00268 }
00269
00270 bool FileContent::rangeFilter(bool b) {
00271
00272 isRangeFilterActive = false;
00273
00274 if (b) {
00275 if (!annotateObj) {
00276 dbs("ASSERT in rangeFilter: annotateObj not available");
00277 return false;
00278 }
00279 int indexFrom, paraFrom, paraTo, dummy;
00280 ft->getSelection(¶From, &indexFrom, ¶To, &dummy);
00281
00282 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
00283 EM_PROCESS_EVENTS_NO_INPUT;
00284 QString anc;
00285
00286 try {
00287 d->setThrowOnDelete(true);
00288
00289 anc = annotateObj->computeRanges(st->sha(), ++paraFrom, ++paraTo);
00290 d->setThrowOnDelete(false);
00291
00292 } catch (int i) {
00293 d->setThrowOnDelete(false);
00294 QApplication::restoreOverrideCursor();
00295 if (d->isThrowOnDeleteRaised(i, "range filtering")) {
00296 EM_THROW_PENDING;
00297 return false;
00298 }
00299 const QString info("Exception \'" + EM_DESC(i) + "\' "
00300 "not handled in lookupAnnotation...re-throw");
00301 dbs(info);
00302 throw;
00303 }
00304 QApplication::restoreOverrideCursor();
00305
00306 if (!anc.isEmpty() && getRange(anc, rangeInfo)) {
00307
00308 isRangeFilterActive = true;
00309 fileHighlighter->rehighlight();
00310 int st = rangeInfo->start - 1;
00311 ft->setSelection(st, 0, st, 0);
00312 goToRangeStart();
00313 return true;
00314 }
00315 } else {
00316 ft->setBold(false);
00317 fileHighlighter->rehighlight();
00318 ft->setSelection(rangeInfo->start - 1, 0, rangeInfo->end, 0);
00319 rangeInfo->clear();
00320 }
00321 return false;
00322 }
00323
00324 bool FileContent::lookupAnnotation() {
00325
00326 if ( st->sha().isEmpty()
00327 || st->fileName().isEmpty()
00328 || !isAnnotationAvailable
00329 || !annotateObj)
00330 return false;
00331
00332 try {
00333 d->setThrowOnDelete(true);
00334
00335
00336 curAnn = git->lookupAnnotation(annotateObj, st->fileName(), st->sha());
00337
00338 if (curAnn) {
00339 if (!curAnn->lines.empty()) {
00340 annoLen = annotateLength(curAnn);
00341 curAnnIt = curAnn->lines.constBegin();
00342 } else
00343 curAnn = NULL;
00344 } else {
00345 dbp("ASSERT in lookupAnnotation: no annotation for %1", st->fileName());
00346 clearAnnotate();
00347 }
00348 d->setThrowOnDelete(false);
00349
00350 } catch (int i) {
00351
00352 d->setThrowOnDelete(false);
00353
00354 if (d->isThrowOnDeleteRaised(i, "looking up annotation")) {
00355 EM_THROW_PENDING;
00356 return false;
00357 }
00358 const QString info("Exception \'" + EM_DESC(i) + "\' "
00359 "not handled in lookupAnnotation...re-throw");
00360 dbs(info);
00361 throw;
00362 }
00363 return (curAnn != NULL);
00364 }
00365
00366 void FileContent::saveScreenState() {
00367
00368
00369 ss.isValid = (ft->textFormat() != Qt::RichText);
00370 if (!ss.isValid)
00371 return;
00372
00373 ss.topPara = ft->paragraphAt(QPoint(ft->contentsX(), ft->contentsY()));
00374 ss.hasSelectedText = ft->hasSelectedText();
00375 if (ss.hasSelectedText) {
00376 ft->getSelection(&ss.paraFrom, &ss.indexFrom, &ss.paraTo, &ss.indexTo);
00377 ss.annoLen = annoLen;
00378 ss.isAnnotationAppended = isAnnotationAppended;
00379 }
00380 }
00381
00382 void FileContent::restoreScreenState() {
00383
00384 if (!ss.isValid)
00385 return;
00386
00387 if (ss.hasSelectedText) {
00388
00389 ss.indexFrom -= (ss.isAnnotationAppended ? ss.annoLen : 0);
00390 ss.indexTo -= (ss.isAnnotationAppended ? ss.annoLen : 0);
00391
00392
00393 ss.indexFrom += (isAnnotationAppended ? annoLen : 0);
00394 ss.indexTo += (isAnnotationAppended ? annoLen : 0);
00395 ss.indexFrom = QMAX(ss.indexFrom, 0);
00396 ss.indexTo = QMAX(ss.indexTo, 0);
00397 ft->setSelection(ss.paraFrom, ss.indexFrom, ss.paraTo, ss.indexTo);
00398
00399 } else if (ss.topPara != 0) {
00400 int t = ft->paragraphRect(ss.topPara).bottom();
00401 ft->setContentsPos(0, t);
00402 }
00403
00404 ss.isAnnotationAppended = isAnnotationAppended;
00405 ss.annoLen = annoLen;
00406 }
00407
00408
00409
00410 void FileContent::on_doubleClicked(int para, int) {
00411
00412 QString id(ft->text(para));
00413 id = id.section('.', 0, 0, QString::SectionSkipEmpty);
00414 emit revIdSelected(id.toInt());
00415 }
00416
00417 void FileContent::on_annotateReady(Annotate* readyAnn, const QString& fileName,
00418 bool ok, const QString& msg) {
00419
00420 if (readyAnn != annotateObj)
00421 return;
00422
00423 if (!ok) {
00424 d->m()->statusBar()->message("Sorry, annotation not available for this file.");
00425 return;
00426 }
00427 if (st->fileName() != fileName) {
00428 dbp("ASSERT arrived annotation of wrong file <%1>", fileName);
00429 return;
00430 }
00431 d->m()->statusBar()->message(msg, 7000);
00432
00433 isAnnotationAvailable = true;
00434 if (lookupAnnotation())
00435 emit annotationAvailable(true);
00436 }
00437
00438 void FileContent::on_procDataReady(const QByteArray& fileChunk) {
00439
00440 QGit::baAppend(fileRowData, fileChunk);
00441 processData(fileChunk);
00442 }
00443
00444 void FileContent::on_eof(bool emitSignal) {
00445
00446 if ( !fileRowData.isEmpty()
00447 && fileRowData.at(fileRowData.size() - 1) != '\n')
00448 processData('\n');
00449
00450 ft->setText(fileProcessedData);
00451 isFileAvailable = true;
00452 if (ss.isValid)
00453 restoreScreenState();
00454
00455 if (emitSignal)
00456 emit fileAvailable(true);
00457 }
00458
00459 uint FileContent::processData(const QByteArray& fileChunk) {
00460
00461 QString newLines;
00462 if (!QGit::stripPartialParaghraps(fileChunk, &newLines, &halfLine))
00463 return 0;
00464
00465 const QStringList sl(QStringList::split('\n', newLines, true));
00466
00467 if (fileProcessedData.isEmpty() && isShowAnnotate) {
00468
00469
00470
00471
00472 isAnnotationAppended = (curAnn != NULL);
00473 }
00474 bool isHtmlHeader = (isHtmlSource && curLine == 1);
00475 bool isHtmlFirstContentLine = false;
00476
00477 FOREACH_SL (it, sl) {
00478
00479 if (isHtmlHeader) {
00480 if ((*it).startsWith(HTML_FILE_START)) {
00481 isHtmlHeader = false;
00482 isHtmlFirstContentLine = true;
00483 } else {
00484 fileProcessedData.append(*it).append('\n');
00485 continue;
00486 }
00487 }
00488
00489 if (isHtmlFirstContentLine)
00490 fileProcessedData.append(HTML_FILE_START);
00491
00492
00493 if (isHtmlSource)
00494 fileProcessedData.append(HTML_HEAD);
00495
00496
00497 if (isAnnotationAppended && curAnn) {
00498
00499 if (curAnnIt == curAnn->lines.constEnd()) {
00500
00501 if (isHtmlSource && (*it) == HTML_FILE_END) {
00502 fileProcessedData.append(HTML_TAIL).append(*it).append('\n');
00503 continue;
00504 } else {
00505 dbs("ASSERT in FileContent::processData: bad annotate");
00506 clearAnnotate();
00507 return 0;
00508 }
00509 }
00510 fileProcessedData.append((*curAnnIt).leftJustify(annoLen));
00511 ++curAnnIt;
00512 }
00513
00514 fileProcessedData.append(QString("%1 ").arg(curLine++, MAX_LINE_NUM));
00515
00516
00517 if (isHtmlSource)
00518 fileProcessedData.append(HTML_TAIL);
00519
00520
00521 if (isHtmlFirstContentLine) {
00522 isHtmlFirstContentLine = false;
00523 fileProcessedData.append((*it).section(HTML_FILE_START, 1));
00524 } else
00525 fileProcessedData.append(*it);
00526
00527 fileProcessedData.append('\n');
00528 }
00529 return sl.count();
00530 }