filecontent.cpp

Go to the documentation of this file.
00001 /*
00002         Author: Marco Costalba (C) 2005-2006
00003 
00004         Copyright: See COPYING file that comes with this distribution
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\">"; // light gray
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         // init native types
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 // add an annotation if is available and still not appended, this
00121 // can happen if annotation became available while loading the file.
00122 // If isShowAnnotate is unset try to remove any annotation.
00123 
00124         isShowAnnotate = b;
00125 
00126         if (   !isFileAvailable
00127             || (curAnn == NULL && isShowAnnotate)
00128             || (isAnnotationAppended == isShowAnnotate))
00129                 return;
00130 
00131         // re-feed with file content processData()
00132         saveScreenState();
00133         const QByteArray tmp(fileRowData);
00134         fileRowData.detach(); // QByteArray uses explicit sharing
00135         clearText(false); // emitSignal = false
00136         fileRowData = tmp;
00137         processData(fileRowData);
00138         on_eof(false); // print and call restoreScreenState()
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(); // before file loading
00168 
00169         if (isHtmlSource) // both calls bound on_eof() and on_procDataReady() slots
00170                 proc = git->getHighlightedFile(st->fileName(), st->sha(), this);
00171         else
00172                 proc = git->getFile(st->fileName(), st->sha(), this); // non blocking
00173 
00174         ss.isValid = false;
00175         if (isRangeFilterActive)
00176                 getRange(st->sha(), rangeInfo);
00177         else if (curAnn) {
00178                 // call seekPosition() while loading the file so to shadow the compute time
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); // non blocking
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)) // setSelection() fails in this case
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         // scroll the viewport so that range is at top
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++; // to count the space after line number
00248 
00249         TextFormat tf = ft->textFormat();
00250         ft->setTextFormat(Qt::PlainText); // we want text without formatting tags, this
00251         QString sel(ft->selectedText());  // trick does not work with getSelection()
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()) { // an header part, like the author name, was selected
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(&paraFrom, &indexFrom, &paraTo, &dummy); // does not work with RichText
00281 
00282                 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
00283                 EM_PROCESS_EVENTS_NO_INPUT;
00284                 QString anc;
00285 
00286                 try {
00287                         d->setThrowOnDelete(true);
00288                         // could call qApp->processEvents()
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); // clear selection
00312                         goToRangeStart();
00313                         return true;
00314                 }
00315         } else {
00316                 ft->setBold(false); // bold if the first paragraph was highlighted
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                 // could call qApp->processEvents()
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         // getSelection() does not work with RichText
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                 // index without previous annotation
00389                 ss.indexFrom -= (ss.isAnnotationAppended ? ss.annoLen : 0);
00390                 ss.indexTo -= (ss.isAnnotationAppended ? ss.annoLen : 0);
00391 
00392                 // index with current annotation
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); // slow
00398 
00399         } else if (ss.topPara != 0) {
00400                 int t = ft->paragraphRect(ss.topPara).bottom(); // slow for big files
00401                 ft->setContentsPos(0, t);
00402         }
00403         // leave ss in a consistent state with current screen settings
00404         ss.isAnnotationAppended = isAnnotationAppended;
00405         ss.annoLen = annoLen;
00406 }
00407 
00408 // ************************************ SLOTS ********************************
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) // Git::annotateReady() is sent to all receivers
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'); // flush pending half lines
00449 
00450         ft->setText(fileProcessedData); // much faster then ft->append()
00451         isFileAvailable = true;
00452         if (ss.isValid)
00453                 restoreScreenState(); // could be slow for big files
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)); // allowEmptyEntries
00466 
00467         if (fileProcessedData.isEmpty() && isShowAnnotate) { // one shot at the beginning
00468 
00469                 // check if it is possible to add annotation while appending data
00470                 // if annotation is not available we will defer this in a separated
00471                 // step, calling setShowAnnotate() at proper time
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                 // add HTML page header
00489                 if (isHtmlFirstContentLine)
00490                         fileProcessedData.append(HTML_FILE_START);
00491 
00492                 // add color tag head
00493                 if (isHtmlSource)
00494                         fileProcessedData.append(HTML_HEAD);
00495 
00496                 // add annotation
00497                 if (isAnnotationAppended && curAnn) { // curAnn can change while loading
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                 // add line number
00514                 fileProcessedData.append(QString("%1 ").arg(curLine++, MAX_LINE_NUM));
00515 
00516                 // add color tag tail
00517                 if (isHtmlSource)
00518                         fileProcessedData.append(HTML_TAIL);
00519 
00520                 // finally add content
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'); // removed by stripPartialParaghraps()
00528         }
00529         return sl.count();
00530 }

Generated on Fri Dec 7 21:57:37 2007 for QGit by  doxygen 1.5.3