1 /*
2     SPDX-FileCopyrightText: 2001-2009 Otto Bruggeman <bruggie@gmail.com>
3     SPDX-FileCopyrightText: 2001-2003 John Firebaugh <jfirebaugh@kde.org>
4     SPDX-FileCopyrightText: 2004 Jeff Snyder <jeff@caffeinated.me.uk>
5     SPDX-FileCopyrightText: 2007-2012 Kevin Kofler <kevin.kofler@chello.at>
6 
7     SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 
10 #include "komparelistview.h"
11 
12 #include <QStyle>
13 #include <QPainter>
14 #include <QRegExp>
15 #include <QTimer>
16 #include <QResizeEvent>
17 #include <QMouseEvent>
18 #include <QWheelEvent>
19 #include <QScrollBar>
20 
21 #include <KSharedConfig>
22 
23 #include <libkomparediff2/diffmodel.h>
24 #include <libkomparediff2/diffhunk.h>
25 #include <libkomparediff2/difference.h>
26 #include <libkomparediff2/komparemodellist.h>
27 
28 #include <komparepartdebug.h>
29 #include "viewsettings.h"
30 #include "komparesplitter.h"
31 
32 #define COL_LINE_NO      0
33 #define COL_MAIN         1
34 
35 #define BLANK_LINE_HEIGHT 3
36 #define HUNK_LINE_HEIGHT  5
37 
38 #define ITEM_MARGIN 3
39 
40 using namespace Diff2;
41 
KompareListViewFrame(bool isSource,ViewSettings * settings,KompareSplitter * parent,const char * name)42 KompareListViewFrame::KompareListViewFrame(bool isSource,
43                                            ViewSettings* settings,
44                                            KompareSplitter* parent,
45                                            const char* name):
46     QFrame(parent),
47     m_view(isSource, settings, this, name),
48     m_label(isSource ? QStringLiteral("Source") : QStringLiteral("Dest"), this),
49     m_layout(this)
50 {
51     setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
52     m_label.setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed));
53     QFrame* bottomLine = new QFrame(this);
54     bottomLine->setFrameShape(QFrame::HLine);
55     bottomLine->setFrameShadow(QFrame::Plain);
56     bottomLine->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed));
57     bottomLine->setFixedHeight(1);
58     m_label.setMargin(3);
59     m_layout.setSpacing(0);
60     m_layout.setContentsMargins(0, 0, 0, 0);
61     m_layout.addWidget(&m_label);
62     m_layout.addWidget(bottomLine);
63     m_layout.addWidget(&m_view);
64 
65     connect(&m_view, &KompareListView::differenceClicked,
66             parent, &KompareSplitter::slotDifferenceClicked);
67 
68     connect(parent, &KompareSplitter::scrollViewsToId, &m_view, &KompareListView::scrollToId);
69     connect(parent, &KompareSplitter::setXOffset, &m_view, &KompareListView::setXOffset);
70     connect(&m_view, &KompareListView::resized, parent, &KompareSplitter::slotUpdateScrollBars);
71 }
72 
slotSetModel(const DiffModel * model)73 void KompareListViewFrame::slotSetModel(const DiffModel* model)
74 {
75     if (model)
76     {
77         if (view()->isSource()) {
78             if (!model->sourceRevision().isEmpty())
79                 m_label.setText(model->sourceFile() + QLatin1String(" (") + model->sourceRevision() + QLatin1Char(')'));
80             else
81                 m_label.setText(model->sourceFile());
82         } else {
83             if (!model->destinationRevision().isEmpty())
84                 m_label.setText(model->destinationFile() + QLatin1String(" (") + model->destinationRevision() + QLatin1Char(')'));
85             else
86                 m_label.setText(model->destinationFile());
87         }
88     } else {
89         m_label.setText(QString());
90     }
91 }
92 
KompareListView(bool isSource,ViewSettings * settings,QWidget * parent,const char * name)93 KompareListView::KompareListView(bool isSource,
94                                  ViewSettings* settings,
95                                  QWidget* parent, const char* name) :
96     QTreeWidget(parent),
97     m_isSource(isSource),
98     m_settings(settings),
99     m_scrollId(-1),
100     m_selectedModel(nullptr),
101     m_selectedDifference(nullptr)
102 {
103     setObjectName(QLatin1String(name));
104     setItemDelegate(new KompareListViewItemDelegate(this));
105     setHeaderHidden(true);
106     setColumnCount(3);   // Line Number, Main, Blank
107     setAllColumnsShowFocus(true);
108     setRootIsDecorated(false);
109     setIndentation(0);
110     setFrameStyle(QFrame::NoFrame);
111     setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
112     setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
113     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
114     setFocusPolicy(Qt::NoFocus);
115     setFont(m_settings->m_font);
116     setFocusProxy(parent->parentWidget());
117 }
118 
~KompareListView()119 KompareListView::~KompareListView()
120 {
121     m_settings = nullptr;
122     m_selectedModel = nullptr;
123     m_selectedDifference = nullptr;
124 }
125 
itemAtIndex(int i)126 KompareListViewItem* KompareListView::itemAtIndex(int i)
127 {
128     return m_items[ i ];
129 }
130 
firstVisibleDifference()131 int KompareListView::firstVisibleDifference()
132 {
133     QTreeWidgetItem* item = itemAt(QPoint(0, 0));
134 
135     if (item == nullptr)
136     {
137         qCDebug(KOMPAREPART) << "no item at viewport coordinates (0,0)" ;
138     }
139 
140     while (item) {
141         KompareListViewLineItem* lineItem = dynamic_cast<KompareListViewLineItem*>(item);
142         if (lineItem && lineItem->diffItemParent()->difference()->type() != Difference::Unchanged)
143             break;
144         item = itemBelow(item);
145     }
146 
147     if (item)
148         return m_items.indexOf(((KompareListViewLineItem*)item)->diffItemParent());
149 
150     return -1;
151 }
152 
lastVisibleDifference()153 int KompareListView::lastVisibleDifference()
154 {
155     QTreeWidgetItem* item = itemAt(QPoint(0, visibleHeight() - 1));
156 
157     if (item == nullptr)
158     {
159         qCDebug(KOMPAREPART) << "no item at viewport coordinates (0," << visibleHeight() - 1 << ")" ;
160         // find last item
161         item = itemAt(QPoint(0, 0));
162         if (item) {
163             QTreeWidgetItem* nextItem = item;
164             do {
165                 item = nextItem;
166                 nextItem = itemBelow(item);
167             } while (nextItem);
168         }
169     }
170 
171     while (item) {
172         KompareListViewLineItem* lineItem = dynamic_cast<KompareListViewLineItem*>(item);
173         if (lineItem && lineItem->diffItemParent()->difference()->type() != Difference::Unchanged)
174             break;
175         item = itemAbove(item);
176     }
177 
178     if (item)
179         return m_items.indexOf(((KompareListViewLineItem*)item)->diffItemParent());
180 
181     return -1;
182 }
183 
totalVisualItemRect(QTreeWidgetItem * item)184 QRect KompareListView::totalVisualItemRect(QTreeWidgetItem* item)
185 {
186     QRect total = visualItemRect(item);
187     int n = item->childCount();
188     for (int i = 0; i < n; ++i) {
189         QTreeWidgetItem* child = item->child(i);
190         if (!child->isHidden())
191             total = total.united(totalVisualItemRect(child));
192     }
193     return total;
194 }
195 
itemRect(int i)196 QRect KompareListView::itemRect(int i)
197 {
198     QTreeWidgetItem* item = itemAtIndex(i);
199     return totalVisualItemRect(item);
200 }
201 
minScrollId()202 int KompareListView::minScrollId()
203 {
204     return visibleHeight() / 2;
205 }
206 
maxScrollId()207 int KompareListView::maxScrollId()
208 {
209     int n = topLevelItemCount();
210     if (!n) return 0;
211     KompareListViewItem* item = (KompareListViewItem*)topLevelItem(n - 1);
212     int maxId = item->scrollId() + item->maxHeight() - minScrollId();
213     qCDebug(KOMPAREPART) << "Max ID = " << maxId ;
214     return maxId;
215 }
216 
contentsHeight()217 int KompareListView::contentsHeight()
218 {
219     return verticalScrollBar()->maximum() + viewport()->height() - style()->pixelMetric(QStyle::PM_ScrollBarExtent);
220 }
221 
contentsWidth()222 int KompareListView::contentsWidth()
223 {
224     return (columnWidth(COL_LINE_NO) + columnWidth(COL_MAIN));
225 }
226 
visibleHeight()227 int KompareListView::visibleHeight()
228 {
229     return viewport()->height();
230 }
231 
visibleWidth()232 int KompareListView::visibleWidth()
233 {
234     return viewport()->width();
235 }
236 
contentsX()237 int KompareListView::contentsX()
238 {
239     return horizontalOffset();
240 }
241 
contentsY()242 int KompareListView::contentsY()
243 {
244     return verticalOffset();
245 }
246 
nextPaintOffset() const247 int KompareListView::nextPaintOffset() const
248 {
249     return m_nextPaintOffset;
250 }
251 
setNextPaintOffset(int offset)252 void KompareListView::setNextPaintOffset(int offset)
253 {
254     m_nextPaintOffset = offset;
255 }
256 
setXOffset(int x)257 void KompareListView::setXOffset(int x)
258 {
259     qCDebug(KOMPAREPART) << "SetXOffset : Scroll to x position: " << x ;
260     horizontalScrollBar()->setValue(x);
261 }
262 
scrollToId(int id)263 void KompareListView::scrollToId(int id)
264 {
265 //     qCDebug(KOMPAREPART) << "ScrollToID : Scroll to id : " << id ;
266     int n = topLevelItemCount();
267     KompareListViewItem* item = nullptr;
268     if (n) {
269         int i = 1;
270         for (; i < n; ++i) {
271             if (((KompareListViewItem*)topLevelItem(i))->scrollId() > id)
272                 break;
273         }
274         item = (KompareListViewItem*)topLevelItem(i - 1);
275     }
276 
277     if (item) {
278         QRect rect = totalVisualItemRect(item);
279         int pos = rect.top() + verticalOffset();
280         int itemId = item->scrollId();
281         int height = rect.height();
282         double r = (double)(id - itemId) / (double)item->maxHeight();
283         int y = pos + (int)(r * (double)height) - minScrollId();
284 //         qCDebug(KOMPAREPART) << "scrollToID: " ;
285 //         qCDebug(KOMPAREPART) << "            id = " << id ;
286 //         qCDebug(KOMPAREPART) << "           pos = " << pos ;
287 //         qCDebug(KOMPAREPART) << "        itemId = " << itemId ;
288 //         qCDebug(KOMPAREPART) << "             r = " << r ;
289 //         qCDebug(KOMPAREPART) << "        height = " << height ;
290 //         qCDebug(KOMPAREPART) << "         minID = " << minScrollId() ;
291 //         qCDebug(KOMPAREPART) << "             y = " << y ;
292 //         qCDebug(KOMPAREPART) << "contentsHeight = " << contentsHeight() ;
293 //         qCDebug(KOMPAREPART) << "         c - y = " << contentsHeight() - y ;
294         verticalScrollBar()->setValue(y);
295     }
296 
297     m_scrollId = id;
298 }
299 
scrollId()300 int KompareListView::scrollId()
301 {
302     if (m_scrollId < 0)
303         m_scrollId = minScrollId();
304     return m_scrollId;
305 }
306 
setSelectedDifference(const Difference * diff,bool scroll)307 void KompareListView::setSelectedDifference(const Difference* diff, bool scroll)
308 {
309     qCDebug(KOMPAREPART) << "KompareListView::setSelectedDifference(" << diff << ", " << scroll << ")" ;
310 
311     // When something other than a click causes this function to be called,
312     // it'll only get called once, and all is simple.
313     //
314     // When the user clicks on a diff, this function will get called once when
315     // komparesplitter::slotDifferenceClicked runs, and again when the
316     // setSelection signal from the modelcontroller arrives.
317     //
318     // the first call (which will always be from the splitter) will have
319     // scroll==false, and the second call will bail out here.
320     // Which is why clicking on a difference does not cause the listviews to
321     // scroll.
322     if (m_selectedDifference == diff)
323         return;
324 
325     m_selectedDifference = diff;
326 
327     KompareListViewItem* item = m_itemDict[ diff ];
328     if (!item) {
329         qCDebug(KOMPAREPART) << "KompareListView::slotSetSelection(): couldn't find our selection!" ;
330         return;
331     }
332 
333     // why does this not happen when the user clicks on a diff? see the comment above.
334     if (scroll)
335         scrollToId(item->scrollId());
336     setUpdatesEnabled(false);
337     int x = horizontalScrollBar()->value();
338     int y = verticalScrollBar()->value();
339     setCurrentItem(item);
340     horizontalScrollBar()->setValue(x);
341     verticalScrollBar()->setValue(y);
342     setUpdatesEnabled(true);
343 }
344 
slotSetSelection(const Difference * diff)345 void KompareListView::slotSetSelection(const Difference* diff)
346 {
347     qCDebug(KOMPAREPART) << "KompareListView::slotSetSelection( const Difference* diff )" ;
348 
349     setSelectedDifference(diff, true);
350 }
351 
slotSetSelection(const DiffModel * model,const Difference * diff)352 void KompareListView::slotSetSelection(const DiffModel* model, const Difference* diff)
353 {
354     qCDebug(KOMPAREPART) << "KompareListView::slotSetSelection( const DiffModel* model, const Difference* diff )" ;
355 
356     if (m_selectedModel && m_selectedModel == model) {
357         slotSetSelection(diff);
358         return;
359     }
360 
361     clear();
362     m_items.clear();
363     m_itemDict.clear();
364     m_selectedModel = model;
365 
366     DiffHunkListConstIterator hunkIt = model->hunks()->begin();
367     DiffHunkListConstIterator hEnd   = model->hunks()->end();
368 
369     KompareListViewItem* item = nullptr;
370     m_nextPaintOffset = 0;
371 
372     for (; hunkIt != hEnd; ++hunkIt)
373     {
374         if (item)
375             item = new KompareListViewHunkItem(this, item, *hunkIt, model->isBlended());
376         else
377             item = new KompareListViewHunkItem(this, *hunkIt, model->isBlended());
378 
379         DifferenceListConstIterator diffIt = (*hunkIt)->differences().begin();
380         DifferenceListConstIterator dEnd   = (*hunkIt)->differences().end();
381 
382         for (; diffIt != dEnd; ++diffIt)
383         {
384             item = new KompareListViewDiffItem(this, item, *diffIt);
385 
386             int type = (*diffIt)->type();
387 
388             if (type != Difference::Unchanged)
389             {
390                 m_items.append((KompareListViewDiffItem*)item);
391                 m_itemDict.insert(*diffIt, (KompareListViewDiffItem*)item);
392             }
393         }
394     }
395 
396     resizeColumnToContents(COL_LINE_NO);
397     resizeColumnToContents(COL_MAIN);
398 
399     slotSetSelection(diff);
400 }
401 
diffItemAt(const QPoint & pos)402 KompareListViewDiffItem* KompareListView::diffItemAt(const QPoint& pos)
403 {
404     KompareListViewItem* item = static_cast<KompareListViewItem*>(itemAt(pos));
405     if (!item)
406         return nullptr;
407     switch (item->type()) {
408         case KompareListViewItem::Hunk:
409             if (item->paintHeight()) return nullptr;  // no diff item here
410             // zero height (fake 1 pixel height), so a diff item shines through
411             return static_cast<KompareListViewDiffItem*>(itemBelow(item));
412         case KompareListViewItem::Line:
413         case KompareListViewItem::Blank:
414             return static_cast<KompareListViewLineItem*>(item)->diffItemParent();
415         case KompareListViewItem::Container:
416             return static_cast<KompareListViewLineContainerItem*>(item)->diffItemParent();
417         case KompareListViewItem::Diff:
418             return static_cast<KompareListViewDiffItem*>(item);
419         default:
420             return nullptr;
421     }
422 }
423 
mousePressEvent(QMouseEvent * e)424 void KompareListView::mousePressEvent(QMouseEvent* e)
425 {
426     QPoint vp = e->pos();
427     KompareListViewDiffItem* diffItem = diffItemAt(vp);
428     if (diffItem && diffItem->difference()->type() != Difference::Unchanged) {
429         Q_EMIT differenceClicked(diffItem->difference());
430     }
431 }
432 
mouseDoubleClickEvent(QMouseEvent * e)433 void KompareListView::mouseDoubleClickEvent(QMouseEvent* e)
434 {
435     QPoint vp = e->pos();
436     KompareListViewDiffItem* diffItem = diffItemAt(vp);
437     if (diffItem && diffItem->difference()->type() != Difference::Unchanged) {
438         // FIXME: make a new signal that does both
439         Q_EMIT differenceClicked(diffItem->difference());
440         Q_EMIT applyDifference(!diffItem->difference()->applied());
441     }
442 }
443 
renumberLines()444 void KompareListView::renumberLines()
445 {
446 //     qCDebug(KOMPAREPART) << "Begin" ;
447     unsigned int newLineNo = 1;
448     if (!topLevelItemCount()) return;
449     KompareListViewItem* item = (KompareListViewItem*)topLevelItem(0);
450     while (item) {
451 //         qCDebug(KOMPAREPART) << "type: " << item->type() ;
452         if (item->type() != KompareListViewItem::Container
453                 && item->type() != KompareListViewItem::Blank
454                 && item->type() != KompareListViewItem::Hunk)
455         {
456 //             qCDebug(KOMPAREPART) << QString::number( newLineNo ) ;
457             item->setText(COL_LINE_NO, QString::number(newLineNo++));
458         }
459         item = (KompareListViewItem*)itemBelow(item);
460     }
461 }
462 
slotApplyDifference(bool apply)463 void KompareListView::slotApplyDifference(bool apply)
464 {
465     m_itemDict[ m_selectedDifference ]->applyDifference(apply);
466     // now renumber the line column if this is the destination
467     if (!m_isSource)
468         renumberLines();
469 }
470 
slotApplyAllDifferences(bool apply)471 void KompareListView::slotApplyAllDifferences(bool apply)
472 {
473     QHash<const Diff2::Difference*, KompareListViewDiffItem*>::ConstIterator it = m_itemDict.constBegin();
474     QHash<const Diff2::Difference*, KompareListViewDiffItem*>::ConstIterator end = m_itemDict.constEnd();
475     for (; it != end; ++it)
476         it.value()->applyDifference(apply);
477 
478     // now renumber the line column if this is the destination
479     if (!m_isSource)
480         renumberLines();
481     update();
482 }
483 
slotApplyDifference(const Difference * diff,bool apply)484 void KompareListView::slotApplyDifference(const Difference* diff, bool apply)
485 {
486     m_itemDict[ diff ]->applyDifference(apply);
487     // now renumber the line column if this is the destination
488     if (!m_isSource)
489         renumberLines();
490 }
491 
wheelEvent(QWheelEvent * e)492 void KompareListView::wheelEvent(QWheelEvent* e)
493 {
494     e->ignore(); // we want the parent to catch wheel events
495 }
496 
resizeEvent(QResizeEvent * e)497 void KompareListView::resizeEvent(QResizeEvent* e)
498 {
499     QTreeWidget::resizeEvent(e);
500     Q_EMIT resized();
501 }
502 
KompareListViewItemDelegate(QObject * parent)503 KompareListViewItemDelegate::KompareListViewItemDelegate(QObject* parent)
504     : QStyledItemDelegate(parent)
505 {
506 }
507 
~KompareListViewItemDelegate()508 KompareListViewItemDelegate::~KompareListViewItemDelegate()
509 {
510 }
511 
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const512 void KompareListViewItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
513 {
514     int column = index.column();
515     QStyleOptionViewItem changedOption = option;
516     if (column == COL_LINE_NO)
517         changedOption.displayAlignment = Qt::AlignRight;
518     KompareListViewItem* item = static_cast<KompareListViewItem*>(static_cast<KompareListView*>(parent())->itemFromIndex(index));
519     item->paintCell(painter, changedOption, column);
520 }
521 
sizeHint(const QStyleOptionViewItem & option,const QModelIndex & index) const522 QSize KompareListViewItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
523 {
524     KompareListViewItem* item = static_cast<KompareListViewItem*>(static_cast<KompareListView*>(parent())->itemFromIndex(index));
525     QSize hint = QStyledItemDelegate::sizeHint(option, index);
526     return QSize(hint.width() + ITEM_MARGIN, item->height());
527 }
528 
KompareListViewItem(KompareListView * parent,int type)529 KompareListViewItem::KompareListViewItem(KompareListView* parent, int type)
530     : QTreeWidgetItem(parent, type),
531       m_scrollId(0),
532       m_height(0),
533       m_paintHeight(0),
534       m_paintOffset(parent->nextPaintOffset())
535 {
536 //     qCDebug(KOMPAREPART) << "Created KompareListViewItem with scroll id " << m_scrollId ;
537 }
538 
KompareListViewItem(KompareListView * parent,KompareListViewItem * after,int type)539 KompareListViewItem::KompareListViewItem(KompareListView* parent, KompareListViewItem* after, int type)
540     : QTreeWidgetItem(parent, after, type),
541       m_scrollId(after->scrollId() + after->maxHeight()),
542       m_height(0),
543       m_paintHeight(0),
544       m_paintOffset(parent->nextPaintOffset())
545 {
546 //     qCDebug(KOMPAREPART) << "Created KompareListViewItem with scroll id " << m_scrollId ;
547 }
548 
KompareListViewItem(KompareListViewItem * parent,int type)549 KompareListViewItem::KompareListViewItem(KompareListViewItem* parent, int type)
550     : QTreeWidgetItem(parent, type),
551       m_scrollId(0),
552       m_height(0),
553       m_paintHeight(0),
554       m_paintOffset(parent->kompareListView()->nextPaintOffset())
555 {
556 }
557 
KompareListViewItem(KompareListViewItem * parent,KompareListViewItem *,int type)558 KompareListViewItem::KompareListViewItem(KompareListViewItem* parent, KompareListViewItem* /*after*/, int type)
559     : QTreeWidgetItem(parent, type),
560       m_scrollId(0),
561       m_height(0),
562       m_paintHeight(0),
563       m_paintOffset(parent->kompareListView()->nextPaintOffset())
564 {
565 }
566 
height() const567 int KompareListViewItem::height() const
568 {
569     return m_height;
570 }
571 
setHeight(int h)572 void KompareListViewItem::setHeight(int h)
573 {
574     m_height = m_paintHeight = h;
575     // QTreeWidget doesn't like zero height, fudge around it.
576     m_height -= m_paintOffset;
577     if (m_height <= 0) {
578         kompareListView()->setNextPaintOffset(1 - m_height);
579         m_height = 1;
580     } else kompareListView()->setNextPaintOffset(0);
581 }
582 
paintHeight() const583 int KompareListViewItem::paintHeight() const
584 {
585     return m_paintHeight;
586 }
587 
paintOffset() const588 int KompareListViewItem::paintOffset() const
589 {
590     return m_paintOffset;
591 }
592 
isCurrent() const593 bool KompareListViewItem::isCurrent() const
594 {
595     return treeWidget()->currentItem() == this;
596 }
597 
kompareListView() const598 KompareListView* KompareListViewItem::kompareListView() const
599 {
600     return (KompareListView*)treeWidget();
601 }
602 
paintCell(QPainter * p,const QStyleOptionViewItem & option,int column)603 void KompareListViewItem::paintCell(QPainter* p, const QStyleOptionViewItem& option, int column)
604 {
605     // Default implementation for zero-height items.
606     // We have to paint the item which shines through or we'll end up with glitches.
607     KompareListViewItem* nextItem = (KompareListViewItem*)kompareListView()->itemBelow(this);
608     if (nextItem) {
609         QStyleOptionViewItem changedOption = option;
610         changedOption.rect.translate(0, height());
611         nextItem->paintCell(p, changedOption, column);
612     }
613 }
614 
KompareListViewDiffItem(KompareListView * parent,Difference * difference)615 KompareListViewDiffItem::KompareListViewDiffItem(KompareListView* parent, Difference* difference)
616     : KompareListViewItem(parent, Diff),
617       m_difference(difference),
618       m_sourceItem(nullptr),
619       m_destItem(nullptr)
620 {
621     init();
622 }
623 
KompareListViewDiffItem(KompareListView * parent,KompareListViewItem * after,Difference * difference)624 KompareListViewDiffItem::KompareListViewDiffItem(KompareListView* parent, KompareListViewItem* after, Difference* difference)
625     : KompareListViewItem(parent, after, Diff),
626       m_difference(difference),
627       m_sourceItem(nullptr),
628       m_destItem(nullptr)
629 {
630     init();
631 }
632 
~KompareListViewDiffItem()633 KompareListViewDiffItem::~KompareListViewDiffItem()
634 {
635     m_difference = nullptr;
636 }
637 
init()638 void KompareListViewDiffItem::init()
639 {
640     setHeight(0);
641     setExpanded(true);
642     int nextPaintOffset = kompareListView()->nextPaintOffset();
643     m_destItem = new KompareListViewLineContainerItem(this, false);
644     kompareListView()->setNextPaintOffset(nextPaintOffset);
645     m_sourceItem = new KompareListViewLineContainerItem(this, true);
646     setVisibility();
647 }
648 
setVisibility()649 void KompareListViewDiffItem::setVisibility()
650 {
651     m_sourceItem->setHidden(!(kompareListView()->isSource() || m_difference->applied()));
652     m_destItem->setHidden(!m_sourceItem->isHidden());
653 }
654 
applyDifference(bool apply)655 void KompareListViewDiffItem::applyDifference(bool apply)
656 {
657     qCDebug(KOMPAREPART) << "KompareListViewDiffItem::applyDifference( " << apply << " )" ;
658     setVisibility();
659 }
660 
maxHeight()661 int KompareListViewDiffItem::maxHeight()
662 {
663     int lines = qMax(m_difference->sourceLineCount(), m_difference->destinationLineCount());
664     if (lines == 0)
665         return BLANK_LINE_HEIGHT;
666     else
667         return lines * treeWidget()->fontMetrics().height();
668 }
669 
KompareListViewLineContainerItem(KompareListViewDiffItem * parent,bool isSource)670 KompareListViewLineContainerItem::KompareListViewLineContainerItem(KompareListViewDiffItem* parent, bool isSource)
671     : KompareListViewItem(parent, Container),
672       m_blankLineItem(nullptr),
673       m_isSource(isSource)
674 {
675 //     qCDebug(KOMPAREPART) << "isSource ? " << (isSource ? " Yes!" : " No!") ;
676     setHeight(0);
677     setExpanded(true);
678 
679     int lines = lineCount();
680     int line = lineNumber();
681 //     qCDebug(KOMPAREPART) << "LineNumber : " << lineNumber() ;
682     if (lines == 0) {
683         m_blankLineItem = new KompareListViewBlankLineItem(this);
684         return;
685     }
686 
687     for (int i = 0; i < lines; ++i, ++line) {
688         new KompareListViewLineItem(this, line, lineAt(i));
689     }
690 }
691 
~KompareListViewLineContainerItem()692 KompareListViewLineContainerItem::~KompareListViewLineContainerItem()
693 {
694 }
695 
diffItemParent() const696 KompareListViewDiffItem* KompareListViewLineContainerItem::diffItemParent() const
697 {
698     return (KompareListViewDiffItem*)parent();
699 }
700 
lineCount() const701 int KompareListViewLineContainerItem::lineCount() const
702 {
703     return m_isSource ? diffItemParent()->difference()->sourceLineCount() :
704            diffItemParent()->difference()->destinationLineCount();
705 }
706 
lineNumber() const707 int KompareListViewLineContainerItem::lineNumber() const
708 {
709     return m_isSource ? diffItemParent()->difference()->sourceLineNumber() :
710            diffItemParent()->difference()->destinationLineNumber();
711 }
712 
lineAt(int i) const713 DifferenceString* KompareListViewLineContainerItem::lineAt(int i) const
714 {
715     return m_isSource ? diffItemParent()->difference()->sourceLineAt(i) :
716            diffItemParent()->difference()->destinationLineAt(i);
717 }
718 
KompareListViewLineItem(KompareListViewLineContainerItem * parent,int line,DifferenceString * text)719 KompareListViewLineItem::KompareListViewLineItem(KompareListViewLineContainerItem* parent, int line, DifferenceString* text)
720     : KompareListViewItem(parent, Line)
721 {
722     init(line, text);
723 }
724 
KompareListViewLineItem(KompareListViewLineContainerItem * parent,int line,DifferenceString * text,int type)725 KompareListViewLineItem::KompareListViewLineItem(KompareListViewLineContainerItem* parent, int line, DifferenceString* text, int type)
726     : KompareListViewItem(parent, type)
727 {
728     init(line, text);
729 }
730 
~KompareListViewLineItem()731 KompareListViewLineItem::~KompareListViewLineItem()
732 {
733     m_text = nullptr;
734 }
735 
init(int line,DifferenceString * text)736 void KompareListViewLineItem::init(int line, DifferenceString* text)
737 {
738     setHeight(treeWidget()->fontMetrics().height());
739     setText(COL_LINE_NO, QString::number(line));
740     setText(COL_MAIN, text->string());
741     m_text = text;
742 }
743 
paintCell(QPainter * p,const QStyleOptionViewItem & option,int column)744 void KompareListViewLineItem::paintCell(QPainter* p, const QStyleOptionViewItem& option, int column)
745 {
746     int width = option.rect.width();
747     Qt::Alignment align = option.displayAlignment;
748 
749     p->setRenderHint(QPainter::Antialiasing);
750     p->translate(option.rect.topLeft());
751     p->translate(0, -paintOffset());
752 
753     QColor bg(Qt::white);   // Always make the background white when it is not a real difference
754     if (diffItemParent()->difference()->type() == Difference::Unchanged)
755     {
756         if (column == COL_LINE_NO)
757         {
758             bg = QColor(Qt::lightGray);
759         }
760     }
761     else
762     {
763         bg = kompareListView()->settings()->colorForDifferenceType(
764                  diffItemParent()->difference()->type(),
765                  diffItemParent()->isCurrent(),
766                  diffItemParent()->difference()->applied());
767     }
768 
769     // Paint background
770     p->fillRect(0, 0, width, paintHeight(), bg);
771 
772     // Paint foreground
773     if (diffItemParent()->difference()->type() == Difference::Unchanged)
774         p->setPen(QColor(Qt::darkGray));     // always make normal text gray
775     else
776         p->setPen(QColor(Qt::black));     // make text with changes black
777 
778     paintText(p, bg, column, width, align);
779 
780     // Paint darker lines around selected item
781     if (diffItemParent()->isCurrent())
782     {
783         p->translate(0.5, 0.5);
784         p->setPen(bg.darker(135));
785         QTreeWidgetItem* parentItem = parent();
786         if (this == parentItem->child(0))
787             p->drawLine(0, 0, width, 0);
788         if (this == parentItem->child(parentItem->childCount() - 1))
789             p->drawLine(0, paintHeight() - 1, width, paintHeight() - 1);
790     }
791 
792     p->resetTransform();
793 }
794 
paintText(QPainter * p,const QColor & bg,int column,int width,int align)795 void KompareListViewLineItem::paintText(QPainter* p, const QColor& bg, int column, int width, int align)
796 {
797     if (column == COL_MAIN)
798     {
799         QString textChunk;
800         int offset = ITEM_MARGIN;
801         int prevValue = 0;
802         int charsDrawn = 0;
803         int chunkWidth;
804         QBrush changeBrush(bg, Qt::Dense3Pattern);
805         QBrush normalBrush(bg, Qt::SolidPattern);
806         QBrush brush;
807 
808         if (m_text->string().isEmpty())
809         {
810             p->fillRect(0, 0, width, paintHeight(), normalBrush);
811             return;
812         }
813 
814         p->fillRect(0, 0, offset, paintHeight(), normalBrush);
815 
816         if (!m_text->markerList().isEmpty())
817         {
818             MarkerListConstIterator markerIt = m_text->markerList().begin();
819             MarkerListConstIterator mEnd     = m_text->markerList().end();
820             Marker* m = *markerIt;
821 
822             for (; markerIt != mEnd; ++markerIt)
823             {
824                 m  = *markerIt;
825                 textChunk = m_text->string().mid(prevValue, m->offset() - prevValue);
826 //                 qCDebug(KOMPAREPART) << "TextChunk   = \"" << textChunk << "\"" ;
827 //                 qCDebug(KOMPAREPART) << "c->offset() = " << c->offset() ;
828 //                 qCDebug(KOMPAREPART) << "prevValue   = " << prevValue ;
829                 expandTabs(textChunk, kompareListView()->settings()->m_tabToNumberOfSpaces, charsDrawn);
830                 charsDrawn += textChunk.length();
831                 prevValue = m->offset();
832                 if (m->type() == Marker::End)
833                 {
834                     QFont font(p->font());
835                     font.setBold(true);
836                     p->setFont(font);
837 //                     p->setPen( Qt::blue );
838                     brush = changeBrush;
839                 }
840                 else
841                 {
842                     QFont font(p->font());
843                     font.setBold(false);
844                     p->setFont(font);
845 //                     p->setPen( Qt::black );
846                     brush = normalBrush;
847                 }
848                 chunkWidth = p->fontMetrics().horizontalAdvance(textChunk);
849                 p->fillRect(offset, 0, chunkWidth, paintHeight(), brush);
850                 p->drawText(offset, 0,
851                             chunkWidth, paintHeight(),
852                             align, textChunk);
853                 offset += chunkWidth;
854             }
855         }
856         if (prevValue < m_text->string().length())
857         {
858             // Still have to draw some string without changes
859             textChunk = m_text->string().mid(prevValue, qMax(1, m_text->string().length() - prevValue));
860             expandTabs(textChunk, kompareListView()->settings()->m_tabToNumberOfSpaces, charsDrawn);
861 //             qCDebug(KOMPAREPART) << "TextChunk   = \"" << textChunk << "\"" ;
862             QFont font(p->font());
863             font.setBold(false);
864             p->setFont(font);
865             chunkWidth = p->fontMetrics().horizontalAdvance(textChunk);
866             p->fillRect(offset, 0, chunkWidth, paintHeight(), normalBrush);
867             p->drawText(offset, 0,
868                         chunkWidth, paintHeight(),
869                         align, textChunk);
870             offset += chunkWidth;
871         }
872         p->fillRect(offset, 0, width - offset, paintHeight(), normalBrush);
873     }
874     else
875     {
876         p->fillRect(0, 0, width, paintHeight(), bg);
877         p->drawText(ITEM_MARGIN, 0,
878                     width - ITEM_MARGIN, paintHeight(),
879                     align, text(column));
880     }
881 }
882 
expandTabs(QString & text,int tabstop,int startPos) const883 void KompareListViewLineItem::expandTabs(QString& text, int tabstop, int startPos) const
884 {
885     int index;
886     while ((index = text.indexOf(QChar(9))) != -1)
887         text.replace(index, 1, QString(tabstop - ((startPos + index) % tabstop), QLatin1Char(' ')));
888 }
889 
diffItemParent() const890 KompareListViewDiffItem* KompareListViewLineItem::diffItemParent() const
891 {
892     KompareListViewLineContainerItem* p = (KompareListViewLineContainerItem*)parent();
893     return p->diffItemParent();
894 }
895 
KompareListViewBlankLineItem(KompareListViewLineContainerItem * parent)896 KompareListViewBlankLineItem::KompareListViewBlankLineItem(KompareListViewLineContainerItem* parent)
897     : KompareListViewLineItem(parent, 0, new DifferenceString(), Blank)
898 {
899     setHeight(BLANK_LINE_HEIGHT);
900 }
901 
paintText(QPainter * p,const QColor & bg,int column,int width,int)902 void KompareListViewBlankLineItem::paintText(QPainter* p, const QColor& bg, int column, int width, int /* align */)
903 {
904     if (column == COL_MAIN)
905     {
906         QBrush normalBrush(bg, Qt::SolidPattern);
907         p->fillRect(0, 0, width, paintHeight(), normalBrush);
908     }
909 }
910 
KompareListViewHunkItem(KompareListView * parent,DiffHunk * hunk,bool zeroHeight)911 KompareListViewHunkItem::KompareListViewHunkItem(KompareListView* parent, DiffHunk* hunk, bool zeroHeight)
912     : KompareListViewItem(parent, Hunk),
913       m_zeroHeight(zeroHeight),
914       m_hunk(hunk)
915 {
916     setHeight(maxHeight());
917     setFlags(flags() & ~Qt::ItemIsSelectable);
918 }
919 
KompareListViewHunkItem(KompareListView * parent,KompareListViewItem * after,DiffHunk * hunk,bool zeroHeight)920 KompareListViewHunkItem::KompareListViewHunkItem(KompareListView* parent, KompareListViewItem* after, DiffHunk* hunk,  bool zeroHeight)
921     : KompareListViewItem(parent, after, Hunk),
922       m_zeroHeight(zeroHeight),
923       m_hunk(hunk)
924 {
925     setHeight(maxHeight());
926     setFlags(flags() & ~Qt::ItemIsSelectable);
927 }
928 
~KompareListViewHunkItem()929 KompareListViewHunkItem::~KompareListViewHunkItem()
930 {
931     m_hunk = nullptr;
932 }
933 
maxHeight()934 int KompareListViewHunkItem::maxHeight()
935 {
936     if (m_zeroHeight) {
937         return 0;
938     } else if (m_hunk->function().isEmpty()) {
939         return HUNK_LINE_HEIGHT;
940     } else {
941         return treeWidget()->fontMetrics().height();
942     }
943 }
944 
paintCell(QPainter * p,const QStyleOptionViewItem & option,int column)945 void KompareListViewHunkItem::paintCell(QPainter* p, const QStyleOptionViewItem& option, int column)
946 {
947     if (m_zeroHeight) {
948         KompareListViewItem::paintCell(p, option, column);
949     } else {
950         int x = option.rect.left();
951         int y = option.rect.top() - paintOffset();
952         int width = option.rect.width();
953         Qt::Alignment align = option.displayAlignment;
954 
955         p->fillRect(x, y, width, paintHeight(), QColor(Qt::lightGray));     // Hunk headers should be lightgray
956         p->setPen(QColor(Qt::black));     // Text color in hunk should be black
957         if (column == COL_MAIN) {
958             p->drawText(x + ITEM_MARGIN, y, width - ITEM_MARGIN, paintHeight(),
959                         align, m_hunk->function());
960         }
961     }
962 }
963