Logo Search packages:      
Sourcecode: qgit version File versions  Download package

patchview.cpp

/*
      Description: patch viewer window

      Author: Marco Costalba (C) 2005-2006

      Copyright: See COPYING file that comes with this distribution

*/
#include <qtextedit.h>
#include <qlineedit.h>
#include <qapplication.h>
#include <qsyntaxhighlighter.h>
#include <qradiobutton.h>
#include <qbuttongroup.h>
#include <qtabwidget.h>
#include "common.h"
#include "git.h"
#include "domain.h"
#include "mainimpl.h"
#include "revdesc.h"
#include "filelist.h"
#include "patchbase.h"
#include "patchview.h"

// diff types options, corresponds to buttons id
#define DIFF_TO_PARENT 0
#define DIFF_TO_HEAD   1
#define DIFF_TO_SHA    2

class DiffHighlighter : public QSyntaxHighlighter {
public:
      DiffHighlighter(PatchView* p, QTextEdit* te) :
                      QSyntaxHighlighter(te), pv(p), combinedLenght(0) {}

      void setCombinedLength(uint cl) { combinedLenght = cl; }
      virtual int highlightParagraph (const QString& text, int) {

            QColor myColor;
            const char firstChar = text[0].latin1();
            switch (firstChar) {
            case '@':
                  myColor = Qt::darkMagenta;
                  break;
            case '+':
                  myColor = Qt::darkGreen;
                  break;
            case '-':
                  myColor = Qt::red;
                  break;
            case 'c':
            case 'd':
            case 'i':
            case 'n':
            case 'o':
            case 'r':
            case 's':
                  if (   text.startsWith("diff --git a/")
                      || text.startsWith("copy ")
                      || text.startsWith("index ")
                      || text.startsWith("new ")
                      || text.startsWith("old ")
                      || text.startsWith("rename ")
                      || text.startsWith("similarity "))
                        myColor = Qt::darkBlue;
                  else if (combinedLenght > 0 && text.startsWith("diff --combined"))
                        myColor = Qt::darkBlue;
                  break;
            case ' ':
                  if (combinedLenght > 0) {
                        if (text.left(combinedLenght).contains('+'))
                              myColor = Qt::darkGreen;
                        else if (text.left(combinedLenght).contains('-'))
                              myColor = Qt::red;
                  }
                  break;
            }
            if (myColor.isValid())
                  setFormat(0, text.length(), myColor);

            if (pv->matches.count() > 0) {
                  int indexFrom, indexTo;
                  if (pv->getMatch(currentParagraph(), &indexFrom, &indexTo)) {

                        QFont f = textEdit()->currentFont();
                        f.setUnderline(true);
                        f.setBold(true);
                        if (indexTo == 0)
                              indexTo = text.length();

                        setFormat(indexFrom, indexTo - indexFrom, f, Qt::blue);
                  }
            }
            return 0;
      }
private:
      PatchView* pv;
      uint combinedLenght;
};

PatchView::PatchView(MainImpl* mi, Git* g) : Domain(mi, g) {

      patchTab = new TabPatch(m());
      patchTab->textBrowserDesc->setDomain(this);
      m()->tabWdg->addTab(patchTab, "&Patch");
      tabPosition = m()->tabWdg->count() - 1;

      listBoxFiles = new ListBoxFiles(this, git, patchTab->listBoxFiles);
      diffHighlighter = new DiffHighlighter(this, patchTab->textEditDiff);
      seekTarget = diffLoaded = false;
      pickAxeRE.setMinimal(true);
      pickAxeRE.setCaseSensitive(false);

      connect(patchTab->lineEditDiff, SIGNAL(returnPressed()),
              this, SLOT(on_lineEditDiff_returnPressed()));

      connect(patchTab->buttonGroupDiff, SIGNAL(clicked(int)),
              this, SLOT(on_buttonGroupDiff_clicked(int)));

      connect(listBoxFiles, SIGNAL(contextMenu(const QString&, int)),
              this, SLOT(on_contextMenu(const QString&, int)));
}

PatchView::~PatchView() {

      if (!parent())
            return;

      git->cancelProcess(proc);
      delete diffHighlighter;
      delete listBoxFiles;
      delete patchTab;
}

void PatchView::clear(bool complete) {

      if (complete) {
            st.clear();
            patchTab->textBrowserDesc->clear();
            listBoxFiles->clear();
      }
      patchTab->textEditDiff->clear();
      matches.clear();
      diffLoaded = false;
      seekTarget = !target.isEmpty();
      partialParagraphs = "";
}

void PatchView::centerOnFileHeader(const QString& fileName) {

      target = fileName;
      bool combined = (st.isMerge() && !st.allMergeFiles());
      git->formatPatchFileHeader(&target, st.sha(), st.diffToSha(), combined, st.allMergeFiles());
      seekTarget = !target.isEmpty();
      if (seekTarget)
            centerTarget();
}

void PatchView::on_contextMenu(const QString& data, int type) {

      if (isLinked()) // skip if not linked to main view
            Domain::on_contextMenu(data, type);
}

void PatchView::centerTarget() {

      patchTab->textEditDiff->setCursorPosition(0, 0);
      if (!patchTab->textEditDiff->find(target, true, true)) // updates cursor position
            return;

      // target found
      seekTarget = false;
      int para, index;
      patchTab->textEditDiff->getCursorPosition(&para, &index);
      QPoint p = patchTab->textEditDiff->paragraphRect(para).topLeft();
      patchTab->textEditDiff->setContentsPos(p.x(), p.y());
      patchTab->textEditDiff->removeSelection();
}

void PatchView::centerMatch(uint id) {

      if (matches.count() <= id)
            return;

      patchTab->textEditDiff->setSelection(matches[id].paraFrom, matches[id].indexFrom,
                                           matches[id].paraTo, matches[id].indexTo);
}

void PatchView::on_procDataReady(const QString& data) {

      int X = patchTab->textEditDiff->contentsX();
      int Y = patchTab->textEditDiff->contentsY();

      bool targetInNewChunk = false;
      if (seekTarget)
            targetInNewChunk = (data.find(target) != -1);

      // QTextEdit::append() adds a new paragraph, i.e. inserts a LF
      // if not already present. For performance reasons we cannot use
      // QTextEdit::text() + QString::append() + QTextEdit::setText()
      // so we append only \n terminating text
      //
      // NOTE: last char of diff data MUST always be '\n' for this to work
      int pos = data.findRev('\n') + 1;
      if (pos == 0) {
            partialParagraphs.append(data);
            return;
      }
      patchTab->textEditDiff->append(partialParagraphs + data.left(pos));
      partialParagraphs = data.mid(pos);
      if (targetInNewChunk)
            centerTarget();
      else {
            patchTab->textEditDiff->setContentsPos(X, Y);
            patchTab->textEditDiff->sync();
      }
}

void PatchView::on_eof() {

      diffLoaded = true;
      computeMatches();
      diffHighlighter->rehighlight();
      centerMatch();
}

int PatchView::doSearch(SCRef txt, int pos) {

      if (isRegExp)
            return pickAxeRE.search(txt, pos);

      return txt.find(pickAxeRE.pattern(), pos, true);
}

void PatchView::computeMatches() {

      matches.clear();
      if (pickAxeRE.isEmpty())
            return;

      SCRef txt = patchTab->textEditDiff->text();
      int pos, lastPos = 0, lastPara = 0;

      // must be at the end to catch patterns across more the one chunk
      while ((pos = doSearch(txt, lastPos)) != -1) {

            matches.append(MatchSelection());
            MatchSelection& s = matches.last();

            s.paraFrom = txt.mid(lastPos, pos - lastPos).contains('\n');
            s.paraFrom += lastPara;
            s.indexFrom = pos - txt.findRev('\n', pos) - 1; // index starts from 0

            lastPos = pos;
            pos += (isRegExp) ? pickAxeRE.matchedLength() : pickAxeRE.pattern().length();
            pos--;

            s.paraTo = s.paraFrom + txt.mid(lastPos, pos - lastPos).contains('\n');
            s.indexTo = pos - txt.findRev('\n', pos) - 1;
            s.indexTo++; // in QTextEdit::setSelection() indexTo is not included

            lastPos = pos;
            lastPara = s.paraTo;
      }
}

bool PatchView::getMatch(int para, int* indexFrom, int* indexTo) {

      for (uint i = 0; i < matches.count(); i++)
            if (matches[i].paraFrom <= para && matches[i].paraTo >= para) {

                  *indexFrom = (para == matches[i].paraFrom) ? matches[i].indexFrom : 0;
                  *indexTo = (para == matches[i].paraTo) ? matches[i].indexTo : 0;
                  return true;
            }
      return false;
}

void PatchView::on_highlightPatch(const QString& exp, bool re) {

      pickAxeRE.setPattern(exp);
      isRegExp = re;
      if (diffLoaded)
            on_eof();
}

void PatchView::on_lineEditDiff_returnPressed() {

      if (patchTab->lineEditDiff->text().isEmpty())
            return;

      patchTab->radioButtonSha->setChecked(true); // could be called by code
      on_buttonGroupDiff_clicked(DIFF_TO_SHA);
}

void PatchView::on_buttonGroupDiff_clicked(int diffType) {

      QString sha;
      switch(diffType) {
      case DIFF_TO_PARENT:
            break;
      case DIFF_TO_HEAD:
            sha = "HEAD";
            break;
      case DIFF_TO_SHA:
            sha = patchTab->lineEditDiff->text();
            break;
      }
      if (sha == QGit::ZERO_SHA)
            return;

      // check for a ref name or an abbreviated form
      normalizedSha = (sha.length() != 40 && !sha.isEmpty()) ? git->getRefSha(sha) : sha;

      if (normalizedSha != st.diffToSha()) { // avoid looping
            st.setDiffToSha(normalizedSha); // could be empty
            UPDATE();
      }
}

void PatchView::updatePatch() {

      git->cancelProcess(proc);
      clear(false); // only patch content

      bool combined = (st.isMerge() && !st.allMergeFiles());
      if (combined) {
            const Rev* r = git->revLookup(st.sha());
            diffHighlighter->setCombinedLength(r->parentsCount());
      } else
            diffHighlighter->setCombinedLength(0);

      if (normalizedSha != st.diffToSha()) { // note <(null> != <(empty)>

            if (!st.diffToSha().isEmpty()) {
                  patchTab->lineEditDiff->setText(st.diffToSha());
                  on_lineEditDiff_returnPressed();

            } else if (!normalizedSha.isEmpty()) {
                  normalizedSha = "";
                  // we cannot uncheck radioButtonSha directly
                  // because "Parent" button will stay off
                  patchTab->radioButtonSha->group()->find(0)->toggle();
            }
      }
      proc = git->getDiff(st.sha(), this, st.diffToSha(), combined); // non blocking
}

bool PatchView::doUpdate() {

      bool dataCleared = m()->lineEditSHA->text().isEmpty();
      bool isFileChanged = (st.fileName(true) != st.fileName(false));
      bool isChanged = (   (st.sha(true) != st.sha(false))
                        || (st.diffToSha(true) != st.diffToSha(false))
                        || (st.isMerge(true) != st.isMerge(false))
                        || (st.allMergeFiles(true) != st.allMergeFiles(false)));
      try {
            EM_REGISTER(m()->exSetRepositoryCalled);
            EM_REGISTER(m()->exExiting);

            const RevFile* files = NULL;
            if (isChanged || dataCleared) {

                  if (!isLinked()) {
                        uint maxLen = 30;
                        QString caption(git->getShortLog(st.sha()));
                        if (caption.length() > maxLen)
                              caption = caption.left(maxLen - 3).
                                        stripWhiteSpace().append("...");

                        m()->tabWdg->changeTab(patchTab, caption);
                  }
                  SCRef d(git->getDesc(st.sha(), m()->shortLogRE, m()->longLogRE));
                  patchTab->textBrowserDesc->setText(d);
                  patchTab->textBrowserDesc->setCursorPosition(0, 0);

                  updatePatch();

                  files = git->getFiles(st.sha(), st.diffToSha(), st.allMergeFiles());
            }
            if (isFileChanged || isChanged || dataCleared) {

                  listBoxFiles->update(files);
                  if (!st.fileName().isEmpty())
                        centerOnFileHeader(st.fileName());
            }
            EM_REMOVE(m()->exExiting);
            EM_REMOVE(m()->exSetRepositoryCalled);

            return true;

      } catch(int i) {

            EM_REMOVE(m()->exExiting);
            EM_REMOVE(m()->exSetRepositoryCalled);

            if (EM_MATCH(i, m()->exSetRepositoryCalled, "update patch")) {
                  EM_CHECK_PENDING;
                  return false;
            }
            if (EM_MATCH(i, m()->exExiting, "update patch")) {
                  EM_CHECK_PENDING;
                  return false;
            }
            const QString info("Exception \'" + EM_DESC(i) + "\' "
                               "not handled in update patch...re-throw");
            dbp("%1", info);
            throw i;
      }
}

Generated by  Doxygen 1.6.0   Back to index