Version 2.6
[qgit4/redivivus.git] / src / listview.cpp
blob6ed8e5cfc0f0d37eafe982739b9f037cc9310df5
1 /*
2 Description: qgit revision list view
4 Author: Marco Costalba (C) 2005-2007
6 Copyright: See COPYING file that comes with this distribution
8 */
9 #include <QApplication>
10 #include <QHeaderView>
11 #include <QMimeData>
12 #include <QMouseEvent>
13 #include <QPainter>
14 #include <QPixmap>
15 #include <QShortcut>
16 #include "FileHistory.h"
17 #include "domain.h"
18 #include "git.h"
19 #include "listview.h"
21 using namespace QGit;
23 ListView::ListView(QWidget* parent) : QTreeView(parent), d(NULL), git(NULL), fh(NULL), lp(NULL) {}
25 void ListView::setup(Domain* dm, Git* g) {
27 d = dm;
28 git = g;
29 fh = d->model();
30 st = &(d->st);
31 filterNextContextMenuRequest = false;
33 setFont(QGit::STD_FONT);
35 // create ListViewProxy unplugged, will be plug
36 // to the model only when filtering is needed
37 lp = new ListViewProxy(this, d, git);
38 setModel(fh);
40 ListViewDelegate* lvd = new ListViewDelegate(git, lp, this);
41 lvd->setLaneHeight(fontMetrics().height() + 2);
42 setItemDelegate(lvd);
44 setupGeometry(); // after setting delegate
46 // shortcuts are activated only if widget is visible, this is good
47 new QShortcut(Qt::Key_Up, this, SLOT(on_keyUp()));
48 new QShortcut(Qt::Key_Down, this, SLOT(on_keyDown()));
50 connect(lvd, SIGNAL(updateView()), viewport(), SLOT(update()));
52 connect(this, SIGNAL(diffTargetChanged(int)), lvd, SLOT(diffTargetChanged(int)));
54 connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),
55 this, SLOT(on_customContextMenuRequested(const QPoint&)));
58 ListView::~ListView() {
60 git->cancelDataLoading(fh); // non blocking
63 const QString ListView::sha(int row) const {
65 if (!lp->sourceModel()) // unplugged
66 return fh->sha(row);
68 QModelIndex idx = lp->mapToSource(lp->index(row, 0));
69 return fh->sha(idx.row());
72 int ListView::row(SCRef sha) const {
74 if (!lp->sourceModel()) // unplugged
75 return fh->row(sha);
77 int row = fh->row(sha);
78 QModelIndex idx = fh->index(row, 0);
79 return lp->mapFromSource(idx).row();
82 void ListView::setupGeometry() {
84 QPalette pl = palette();
85 pl.setColor(QPalette::Base, ODD_LINE_COL);
86 pl.setColor(QPalette::AlternateBase, EVEN_LINE_COL);
87 setPalette(pl); // does not seem to inherit application paletteAnnotate
89 QHeaderView* hv = header();
90 hv->setStretchLastSection(true);
91 hv->setSectionResizeMode(LOG_COL, QHeaderView::Interactive);
92 hv->setSectionResizeMode(TIME_COL, QHeaderView::Interactive);
93 hv->setSectionResizeMode(ANN_ID_COL, QHeaderView::ResizeToContents);
94 hv->resizeSection(GRAPH_COL, DEF_GRAPH_COL_WIDTH);
95 hv->resizeSection(LOG_COL, DEF_LOG_COL_WIDTH);
96 hv->resizeSection(AUTH_COL, DEF_AUTH_COL_WIDTH);
97 hv->resizeSection(TIME_COL, DEF_TIME_COL_WIDTH);
99 if (git->isMainHistory(fh))
100 hideColumn(ANN_ID_COL);
103 void ListView::scrollToNextHighlighted(int direction) {
105 // Depending on the value of direction, scroll to:
106 // -1 = the next highlighted item above the current one (i.e. newer in history)
107 // 1 = the next highlighted item below the current one (i.e. older in history)
108 // 0 = the first highlighted item from the top of the list
110 QModelIndex idx = currentIndex();
112 if (!direction) {
113 idx = idx.sibling(0,0);
114 if (lp->isHighlighted(idx.row())) {
115 setCurrentIndex(idx);
116 return;
120 do {
121 idx = (direction >= 0 ? indexBelow(idx) : indexAbove(idx));
122 if (!idx.isValid())
123 return;
125 } while (!lp->isHighlighted(idx.row()));
127 setCurrentIndex(idx);
130 void ListView::scrollToCurrent(ScrollHint hint) {
132 if (currentIndex().isValid())
133 scrollTo(currentIndex(), hint);
136 void ListView::on_keyUp() {
138 QModelIndex idx = indexAbove(currentIndex());
139 if (idx.isValid())
140 setCurrentIndex(idx);
143 void ListView::on_keyDown() {
145 QModelIndex idx = indexBelow(currentIndex());
146 if (idx.isValid())
147 setCurrentIndex(idx);
150 void ListView::on_changeFont(const QFont& f) {
152 setFont(f);
153 ListViewDelegate* lvd = static_cast<ListViewDelegate*>(itemDelegate());
154 lvd->setLaneHeight(fontMetrics().height());
155 scrollToCurrent();
158 const QString ListView::currentText(int column) {
160 QModelIndex idx = model()->index(currentIndex().row(), column);
161 return (idx.isValid() ? idx.data().toString() : "");
164 int ListView::getLaneType(SCRef sha, int pos) const {
166 const Rev* r = git->revLookup(sha, fh);
167 return (r && pos < r->lanes.count() && pos >= 0 ? r->lanes.at(pos) : -1);
170 void ListView::showIdValues() {
172 fh->setAnnIdValid();
173 viewport()->update();
176 void ListView::getSelectedItems(QStringList& selectedItems) {
178 selectedItems.clear();
179 QModelIndexList ml = selectionModel()->selectedRows();
180 FOREACH (QModelIndexList, it, ml)
181 selectedItems.append(sha((*it).row()));
183 // selectedRows() returns the items in an unspecified order,
184 // so be sure rows are ordered from newest to oldest.
185 selectedItems = git->sortShaListByIndex(selectedItems);
188 const QString ListView::shaFromAnnId(uint id) {
190 if (git->isMainHistory(fh))
191 return "";
193 return sha(model()->rowCount() - id);
196 int ListView::filterRows(bool isOn, bool highlight, SCRef filter, int colNum, ShaSet* set) {
198 setUpdatesEnabled(false);
199 int matchedNum = lp->setFilter(isOn, highlight, filter, colNum, set);
200 viewport()->update();
201 setUpdatesEnabled(true);
202 UPDATE_DOMAIN(d);
203 return matchedNum;
206 bool ListView::update() {
208 int stRow = row(st->sha());
209 if (stRow == -1)
210 return false; // main/tree view asked us a sha not in history
212 QModelIndex index = currentIndex();
213 QItemSelectionModel* sel = selectionModel();
215 if (index.isValid() && (index.row() == stRow)) {
217 if (sel->isSelected(index) != st->selectItem())
218 sel->select(index, QItemSelectionModel::Toggle);
220 scrollTo(index);
221 } else {
222 // setCurrentIndex() does not clear previous
223 // selections in a multi selection QListView
224 clearSelection();
226 QModelIndex newIndex = model()->index(stRow, 0);
227 if (newIndex.isValid()) {
229 // emits QItemSelectionModel::currentChanged()
230 setCurrentIndex(newIndex);
231 scrollTo(newIndex);
232 if (!st->selectItem())
233 sel->select(newIndex, QItemSelectionModel::Deselect);
236 if (git->isMainHistory(fh))
237 emit diffTargetChanged(row(st->diffToSha()));
239 return currentIndex().isValid();
242 void ListView::currentChanged(const QModelIndex& index, const QModelIndex&) {
244 SCRef selRev = sha(index.row());
245 if (st->sha() != selRev) { // to avoid looping
246 st->setSha(selRev);
247 st->setSelectItem(true);
248 UPDATE_DOMAIN(d);
252 bool ListView::filterRightButtonPressed(QMouseEvent* e) {
254 QModelIndex index = indexAt(e->pos());
255 SCRef selSha = sha(index.row());
256 if (selSha.isEmpty())
257 return false;
259 if (e->modifiers() == Qt::ControlModifier) { // check for 'diff to' function
261 if (selSha != ZERO_SHA && st->sha() != ZERO_SHA) {
263 if (selSha != st->diffToSha())
264 st->setDiffToSha(selSha);
265 else
266 st->setDiffToSha(""); // restore std view
268 filterNextContextMenuRequest = true;
269 UPDATE_DOMAIN(d);
270 return true; // filter event out
273 // check for 'children & parents' function, i.e. if mouse is on the graph
274 if (index.column() == GRAPH_COL) {
276 filterNextContextMenuRequest = true;
277 QStringList parents, children;
278 if (getLaneParentsChildren(selSha, e->pos().x(), parents, children))
279 emit lanesContextMenuRequested(parents, children);
281 return true; // filter event out
283 return false;
286 void ListView::mousePressEvent(QMouseEvent* e) {
288 if (currentIndex().isValid() && e->button() == Qt::LeftButton)
289 d->setReadyToDrag(true);
291 if (e->button() == Qt::RightButton && filterRightButtonPressed(e))
292 return; // filtered out
294 QTreeView::mousePressEvent(e);
297 void ListView::mouseReleaseEvent(QMouseEvent* e) {
299 d->setReadyToDrag(false); // in case of just click without moving
300 QTreeView::mouseReleaseEvent(e);
303 void ListView::mouseMoveEvent(QMouseEvent* e) {
305 if (d->isReadyToDrag()) {
307 if (indexAt(e->pos()).row() == currentIndex().row())
308 return; // move at least by one line to activate drag
310 if (!d->setDragging(true))
311 return;
313 QStringList selRevs;
314 getSelectedItems(selRevs);
315 selRevs.removeAll(ZERO_SHA);
316 if (!selRevs.empty())
317 emit revisionsDragged(selRevs); // blocking until drop event
319 d->setDragging(false);
321 QTreeView::mouseMoveEvent(e);
324 void ListView::dragEnterEvent(QDragEnterEvent* e) {
326 if (e->mimeData()->hasFormat("text/plain"))
327 e->accept();
330 void ListView::dragMoveEvent(QDragMoveEvent* e) {
332 // already checked by dragEnterEvent()
333 e->accept();
336 void ListView::dropEvent(QDropEvent *e) {
338 SCList remoteRevs(e->mimeData()->text().split('\n', QString::SkipEmptyParts));
339 if (!remoteRevs.isEmpty()) {
340 // some sanity check on dropped data
341 SCRef sha(remoteRevs.first().section('@', 0, 0));
342 SCRef remoteRepo(remoteRevs.first().section('@', 1));
343 if (sha.length() == 40 && !remoteRepo.isEmpty())
344 emit revisionsDropped(remoteRevs);
348 void ListView::on_customContextMenuRequested(const QPoint& pos) {
350 QModelIndex index = indexAt(pos);
351 if (!index.isValid())
352 return;
354 if (filterNextContextMenuRequest) {
355 // event filter does not work on them
356 filterNextContextMenuRequest = false;
357 return;
359 emit contextMenu(sha(index.row()), POPUP_LIST_EV);
362 bool ListView::getLaneParentsChildren(SCRef sha, int x, SList p, SList c) {
364 ListViewDelegate* lvd = static_cast<ListViewDelegate*>(itemDelegate());
365 uint lane = x / lvd->laneWidth();
366 int t = getLaneType(sha, lane);
367 if (t == EMPTY || t == -1)
368 return false;
370 // first find the parents
371 p.clear();
372 QString root;
373 if (!isFreeLane(t)) {
374 p = git->revLookup(sha, fh)->parents(); // pointer cannot be NULL
375 root = sha;
376 } else {
377 SCRef par(git->getLaneParent(sha, lane));
378 if (par.isEmpty()) {
379 dbs("ASSERT getLaneParentsChildren: parent not found");
380 return false;
382 p.append(par);
383 root = p.first();
385 // then find children
386 c = git->getChildren(root);
387 return true;
390 // *****************************************************************************
392 ListViewDelegate::ListViewDelegate(Git* g, ListViewProxy* px, QObject* p) : QItemDelegate(p) {
394 git = g;
395 lp = px;
396 laneHeight = 0;
397 diffTargetRow = -1;
400 QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const {
402 return QSize(laneWidth(), laneHeight);
405 void ListViewDelegate::diffTargetChanged(int row) {
407 if (diffTargetRow != row) {
408 diffTargetRow = row;
409 emit updateView();
413 const Rev* ListViewDelegate::revLookup(int row, FileHistory** fhPtr) const {
415 ListView* lv = static_cast<ListView*>(parent());
416 FileHistory* fh = static_cast<FileHistory*>(lv->model());
418 if (lp->sourceModel())
419 fh = static_cast<FileHistory*>(lp->sourceModel());
421 if (fhPtr)
422 *fhPtr = fh;
424 return git->revLookup(lv->sha(row), fh);
427 static QColor blend(const QColor& col1, const QColor& col2, int amount = 128) {
429 // Returns ((256 - amount)*col1 + amount*col2) / 256;
430 return QColor(((256 - amount)*col1.red() + amount*col2.red() ) / 256,
431 ((256 - amount)*col1.green() + amount*col2.green()) / 256,
432 ((256 - amount)*col1.blue() + amount*col2.blue() ) / 256);
435 void ListViewDelegate::paintGraphLane(QPainter* p, int type, int x1, int x2,
436 const QColor& col, const QColor& activeCol, const QBrush& back) const {
438 const int padding = 2;
439 x1 += padding;
440 x2 += padding;
442 int h = laneHeight / 2;
443 int m = (x1 + x2) / 2;
444 int r = (x2 - x1) * 1 / 3;
445 int d = 2 * r;
447 #define P_CENTER m , h
448 #define P_0 x2, h // >
449 #define P_90 m , 0 // ^
450 #define P_180 x1, h // <
451 #define P_270 m , 2 * h // v
452 #define DELTA_UR 2*(x1 - m), 2*h , 0*16, 90*16 // -,
453 #define DELTA_DR 2*(x1 - m), 2*-h, 270*16, 90*16 // -'
454 #define DELTA_UL 2*(x2 - m), 2*h , 90*16, 90*16 // ,-
455 #define DELTA_DL 2*(x2 - m), 2*-h, 180*16, 90*16 // '-
456 #define CENTER_UR x1, 2*h, 225
457 #define CENTER_DR x1, 0 , 135
458 #define CENTER_UL x2, 2*h, 315
459 #define CENTER_DL x2, 0 , 45
460 #define R_CENTER m - r, h - r, d, d
462 static QColor const & lanePenColor = QPalette().color(QPalette::WindowText);
463 static QPen lanePen(lanePenColor, 2); // fast path here
465 // arc
466 switch (type) {
467 case JOIN:
468 case JOIN_R:
469 case HEAD:
470 case HEAD_R: {
471 QConicalGradient gradient(CENTER_UR);
472 gradient.setColorAt(0.375, col);
473 gradient.setColorAt(0.625, activeCol);
474 lanePen.setBrush(gradient);
475 p->setPen(lanePen);
476 p->drawArc(P_CENTER, DELTA_UR);
477 break;
479 case JOIN_L: {
480 QConicalGradient gradient(CENTER_UL);
481 gradient.setColorAt(0.375, activeCol);
482 gradient.setColorAt(0.625, col);
483 lanePen.setBrush(gradient);
484 p->setPen(lanePen);
485 p->drawArc(P_CENTER, DELTA_UL);
486 break;
488 case TAIL:
489 case TAIL_R: {
490 QConicalGradient gradient(CENTER_DR);
491 gradient.setColorAt(0.375, activeCol);
492 gradient.setColorAt(0.625, col);
493 lanePen.setBrush(gradient);
494 p->setPen(lanePen);
495 p->drawArc(P_CENTER, DELTA_DR);
496 break;
498 default:
499 break;
502 lanePen.setColor(col);
503 p->setPen(lanePen);
505 // vertical line
506 switch (type) {
507 case ACTIVE:
508 case NOT_ACTIVE:
509 case MERGE_FORK:
510 case MERGE_FORK_R:
511 case MERGE_FORK_L:
512 case JOIN:
513 case JOIN_R:
514 case JOIN_L:
515 case CROSS:
516 p->drawLine(P_90, P_270);
517 break;
518 case HEAD_L:
519 case BRANCH:
520 p->drawLine(P_CENTER, P_270);
521 break;
522 case TAIL_L:
523 case INITIAL:
524 case BOUNDARY:
525 case BOUNDARY_C:
526 case BOUNDARY_R:
527 case BOUNDARY_L:
528 p->drawLine(P_90, P_CENTER);
529 break;
530 default:
531 break;
534 lanePen.setColor(activeCol);
535 p->setPen(lanePen);
537 // horizontal line
538 switch (type) {
539 case MERGE_FORK:
540 case JOIN:
541 case HEAD:
542 case TAIL:
543 case CROSS:
544 case CROSS_EMPTY:
545 case BOUNDARY_C:
546 p->drawLine(P_180, P_0);
547 break;
548 case MERGE_FORK_R:
549 case BOUNDARY_R:
550 p->drawLine(P_180, P_CENTER);
551 break;
552 case MERGE_FORK_L:
553 case HEAD_L:
554 case TAIL_L:
555 case BOUNDARY_L:
556 p->drawLine(P_CENTER, P_0);
557 break;
558 default:
559 break;
562 // center symbol, e.g. rect or ellipse
563 switch (type) {
564 case ACTIVE:
565 case INITIAL:
566 case BRANCH:
567 p->setPen(Qt::black);
568 p->setBrush(col);
569 p->drawEllipse(R_CENTER);
570 break;
571 case MERGE_FORK:
572 case MERGE_FORK_R:
573 case MERGE_FORK_L:
574 p->setPen(Qt::black);
575 p->setBrush(col);
576 p->drawRect(R_CENTER);
577 break;
578 case UNAPPLIED:
579 // Red minus sign
580 p->setPen(Qt::NoPen);
581 p->setBrush(Qt::red);
582 p->drawRect(m - r, h - 1, d, 2);
583 break;
584 case APPLIED:
585 // Green plus sign
586 p->setPen(Qt::NoPen);
587 p->setBrush(DARK_GREEN);
588 p->drawRect(m - r, h - 1, d, 2);
589 p->drawRect(m - 1, h - r, 2, d);
590 break;
591 case BOUNDARY:
592 p->setPen(Qt::black);
593 p->setBrush(back);
594 p->drawEllipse(R_CENTER);
595 break;
596 case BOUNDARY_C:
597 case BOUNDARY_R:
598 case BOUNDARY_L:
599 p->setPen(Qt::black);
600 p->setBrush(back);
601 p->drawRect(R_CENTER);
602 break;
603 default:
604 break;
606 #undef P_CENTER
607 #undef P_0
608 #undef P_90
609 #undef P_180
610 #undef P_270
611 #undef DELTA_UR
612 #undef DELTA_DR
613 #undef DELTA_UL
614 #undef DELTA_DL
615 #undef CENTER_UR
616 #undef CENTER_DR
617 #undef CENTER_UL
618 #undef CENTER_DL
619 #undef R_CENTER
622 void ListViewDelegate::paintGraph(QPainter* p, const QStyleOptionViewItem& opt,
623 const QModelIndex& i) const {
624 static const QColor & baseColor = QPalette().color(QPalette::WindowText);
625 static const QColor colors[COLORS_NUM] = { baseColor, Qt::red, DARK_GREEN,
626 Qt::blue, Qt::darkGray, BROWN,
627 Qt::magenta, ORANGE };
628 if (opt.state & QStyle::State_Selected)
629 p->fillRect(opt.rect, opt.palette.highlight());
630 else if (i.row() & 1)
631 p->fillRect(opt.rect, opt.palette.alternateBase());
632 else
633 p->fillRect(opt.rect, opt.palette.base());
635 FileHistory* fh;
636 const Rev* r = revLookup(i.row(), &fh);
637 if (!r)
638 return;
640 p->save();
641 p->setClipRect(opt.rect, Qt::IntersectClip);
642 p->translate(opt.rect.topLeft());
644 // calculate lanes
645 if (r->lanes.count() == 0)
646 git->setLane(r->sha(), fh);
648 QBrush back = opt.palette.base();
649 const QVector<int>& lanes(r->lanes);
650 uint laneNum = lanes.count();
651 uint activeLane = 0;
652 for (uint i = 0; i < laneNum; i++)
653 if (isActive(lanes[i])) {
654 activeLane = i;
655 break;
658 int x1 = 0, x2 = 0;
659 int maxWidth = opt.rect.width();
660 int lw = laneWidth();
661 QColor activeColor = colors[activeLane % COLORS_NUM];
662 if (opt.state & QStyle::State_Selected)
663 activeColor = blend(activeColor, opt.palette.highlightedText().color(), 208);
664 for (uint i = 0; i < laneNum && x2 < maxWidth; i++) {
666 x1 = x2;
667 x2 += lw;
669 int ln = lanes[i];
670 if (ln == EMPTY)
671 continue;
673 QColor color = i == activeLane ? activeColor : colors[i % COLORS_NUM];
674 paintGraphLane(p, ln, x1, x2, color, activeColor, back);
676 p->restore();
679 void ListViewDelegate::paintLog(QPainter* p, const QStyleOptionViewItem& opt,
680 const QModelIndex& index) const {
682 int row = index.row();
683 const Rev* r = revLookup(row);
684 if (!r)
685 return;
687 if (r->isDiffCache)
688 p->fillRect(opt.rect, changedFiles(ZERO_SHA) ? ORANGE : DARK_ORANGE);
690 if (diffTargetRow == row)
691 p->fillRect(opt.rect, LIGHT_BLUE);
693 bool isHighlighted = lp->isHighlighted(row);
694 QPixmap* pm = getTagMarks(r->sha(), opt);
696 if (!pm && !isHighlighted) { // fast path in common case
697 QItemDelegate::paint(p, opt, index);
698 return;
700 QStyleOptionViewItem newOpt(opt); // we need a copy
701 if (pm) {
702 p->drawPixmap(newOpt.rect.x(), newOpt.rect.y() + 1, *pm); // +1 means leave a pixel spacing above the pixmap
703 newOpt.rect.adjust(pm->width(), 0, 0, 0);
704 delete pm;
706 if (isHighlighted)
707 newOpt.font.setBold(true);
709 QItemDelegate::paint(p, newOpt, index);
712 void ListViewDelegate::paint(QPainter* p, const QStyleOptionViewItem& opt,
713 const QModelIndex& index) const {
715 p->setRenderHints(QPainter::Antialiasing);
717 if (index.column() == GRAPH_COL)
718 return paintGraph(p, opt, index);
720 if (index.column() == LOG_COL)
721 return paintLog(p, opt, index);
723 return QItemDelegate::paint(p, opt, index);
726 bool ListViewDelegate::changedFiles(SCRef sha) const {
728 const RevFile* f = git->getFiles(sha);
729 if (f)
730 for (int i = 0; i < f->count(); i++)
731 if (!f->statusCmp(i, RevFile::UNKNOWN))
732 return true;
733 return false;
736 QPixmap* ListViewDelegate::getTagMarks(SCRef sha, const QStyleOptionViewItem& opt) const {
738 uint rt = git->checkRef(sha);
739 if (rt == 0)
740 return NULL; // common case
742 QPixmap* pm = new QPixmap(); // must be deleted by caller
744 if (rt & Git::BRANCH)
745 addRefPixmap(&pm, sha, Git::BRANCH, opt);
747 if (rt & Git::RMT_BRANCH)
748 addRefPixmap(&pm, sha, Git::RMT_BRANCH, opt);
750 if (rt & Git::TAG)
751 addRefPixmap(&pm, sha, Git::TAG, opt);
753 if (rt & Git::REF)
754 addRefPixmap(&pm, sha, Git::REF, opt);
756 return pm;
759 void ListViewDelegate::addRefPixmap(QPixmap** pp, SCRef sha, int type, QStyleOptionViewItem opt) const {
761 QString curBranch;
762 SCList refs = git->getRefName(sha, (Git::RefType)type, &curBranch);
763 FOREACH_SL (it, refs) {
765 bool isCur = (curBranch == *it);
766 opt.font.setBold(isCur);
768 QColor clr;
769 if (type == Git::BRANCH)
770 clr = (isCur ? Qt::green : DARK_GREEN);
772 else if (type == Git::RMT_BRANCH)
773 clr = LIGHT_ORANGE;
775 else if (type == Git::TAG)
776 clr = Qt::yellow;
778 else if (type == Git::REF)
779 clr = PURPLE;
781 opt.palette.setColor(QPalette::Window, clr);
782 opt.palette.setColor(QPalette::WindowText, QColor(Qt::black));
783 addTextPixmap(pp, *it, opt);
787 void ListViewDelegate::addTextPixmap(QPixmap** pp, SCRef txt, const QStyleOptionViewItem& opt) const {
789 QPixmap* pm = *pp;
790 int ofs = pm->isNull() ? 0 : pm->width() + 2;
791 int spacing = 4;
792 QFontMetrics fm(opt.font);
793 int pw = fm.boundingRect(txt).width() + 2 * spacing;
794 int ph = fm.height();
796 QPixmap* newPm = new QPixmap(ofs + pw, ph);
797 QPainter p;
798 p.begin(newPm);
799 if (!pm->isNull()) {
800 newPm->fill(opt.palette.base().color());
801 p.drawPixmap(0, 0, *pm);
803 p.setPen(opt.palette.color(QPalette::WindowText));
804 p.setBrush(opt.palette.color(QPalette::Window));
805 p.setFont(opt.font);
806 p.drawRect(ofs, 0, pw - 1, ph - 1);
807 p.drawText(ofs + spacing, fm.ascent(), txt);
808 p.end();
810 delete pm;
811 *pp = newPm;
814 // *****************************************************************************
816 ListViewProxy::ListViewProxy(QObject* p, Domain * dm, Git * g) : QSortFilterProxyModel(p) {
818 d = dm;
819 git = g;
820 colNum = 0;
821 isHighLight = false;
822 setDynamicSortFilter(false);
825 bool ListViewProxy::isMatch(SCRef sha) const {
827 if (colNum == SHA_MAP_COL)
828 // in this case shaMap contains all good sha to search for
829 return shaSet.contains(sha);
831 const Rev* r = git->revLookup(sha);
832 if (!r) {
833 dbp("ASSERT in ListViewFilter::isMatch, sha <%1> not found", sha);
834 return false;
836 QString target;
837 if (colNum == LOG_COL)
838 target = r->shortLog();
839 else if (colNum == AUTH_COL)
840 target = r->author();
841 else if (colNum == LOG_MSG_COL)
842 target = r->longLog();
843 else if (colNum == COMMIT_COL)
844 target = sha;
846 // wildcard search, case insensitive
847 return (target.contains(filter));
850 bool ListViewProxy::isMatch(int source_row) const {
852 FileHistory* fh = d->model();
853 if (fh->rowCount() <= source_row) // FIXME required to avoid an ASSERT in d->isMatch()
854 return false;
856 bool extFilter = (colNum == -1);
857 return ((!extFilter && isMatch(fh->sha(source_row)))
858 ||( extFilter && d->isMatch(fh->sha(source_row))));
861 bool ListViewProxy::isHighlighted(int row) const {
863 // FIXME row == source_row only because when
864 // higlights the rows are not hidden
865 return (isHighLight && isMatch(row));
868 bool ListViewProxy::filterAcceptsRow(int source_row, const QModelIndex&) const {
870 return (isHighLight || isMatch(source_row));
873 int ListViewProxy::setFilter(bool isOn, bool h, SCRef fl, int cn, ShaSet* s) {
875 filter = QRegExp(fl, Qt::CaseInsensitive, QRegExp::Wildcard);
876 colNum = cn;
877 if (s)
878 shaSet = *s;
880 // isHighlighted() is called also when filter is off,
881 // so reset 'isHighLight' flag in that case
882 isHighLight = h && isOn;
884 ListView* lv = static_cast<ListView*>(parent());
885 FileHistory* fh = d->model();
887 if (!isOn && sourceModel()){
888 lv->setModel(fh);
889 setSourceModel(NULL);
891 } else if (isOn && !isHighLight) {
892 setSourceModel(fh); // trigger a rows scanning
893 lv->setModel(this);
895 return (sourceModel() ? rowCount() : 0);