1 /*
2     SPDX-FileCopyrightText: 2004-2005 Jeff Snyder <jeff-webcvsspam@caffeinated.me.uk>
3     SPDX-FileCopyrightText: 2007-2011 Kevin Kofler <kevin.kofler@chello.at>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 // associated header
9 #include "komparesplitter.h"
10 
11 // qt
12 #include <QStyle>
13 #include <QString>
14 #include <QTimer>
15 #include <QScrollBar>
16 #include <QMap>
17 #include <QSplitter>
18 #include <QApplication>
19 #include <QPainter>
20 #include <QPixmap>
21 #include <QKeyEvent>
22 #include <QGridLayout>
23 #include <QResizeEvent>
24 #include <QChildEvent>
25 #include <QEvent>
26 #include <QWheelEvent>
27 
28 // kde
29 
30 // kompare
31 #include "komparelistview.h"
32 #include "viewsettings.h"
33 #include "kompareconnectwidget.h"
34 #include <libkomparediff2/diffmodel.h>
35 #include <libkomparediff2/difference.h>
36 
37 using namespace Diff2;
38 
KompareSplitter(ViewSettings * settings,QWidget * parent)39 KompareSplitter::KompareSplitter(ViewSettings* settings, QWidget* parent) :
40     QSplitter(Qt::Horizontal, parent),
41     m_settings(settings)
42 {
43     QFrame* scrollFrame = static_cast<QFrame*>(parent);
44 
45     // Set up the scrollFrame
46     scrollFrame->setFrameStyle(QFrame::NoFrame | QFrame::Plain);
47     scrollFrame->setLineWidth(scrollFrame->style()->pixelMetric(QStyle::PM_DefaultFrameWidth));
48     QGridLayout* pairlayout = new QGridLayout(scrollFrame);
49     pairlayout->setSpacing(0);
50     pairlayout->setContentsMargins(0, 0, 0, 0);
51     m_vScroll = new QScrollBar(Qt::Vertical, scrollFrame);
52     pairlayout->addWidget(m_vScroll, 0, 1);
53     m_hScroll = new QScrollBar(Qt::Horizontal, scrollFrame);
54     pairlayout->addWidget(m_hScroll, 1, 0);
55 
56     new KompareListViewFrame(true, m_settings, this, "source");
57     new KompareListViewFrame(false, m_settings, this, "destination");
58     pairlayout->addWidget(this, 0, 0);
59 
60     // set up our looks
61     setLineWidth(style()->pixelMetric(QStyle::PM_DefaultFrameWidth));
62 
63     setHandleWidth(50);
64     setChildrenCollapsible(false);
65     setFrameStyle(QFrame::NoFrame);
66     setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
67     setOpaqueResize(true);
68     setFocusPolicy(Qt::WheelFocus);
69 
70     connect(this, &KompareSplitter::configChanged, this, &KompareSplitter::slotConfigChanged);
71     connect(this, &KompareSplitter::configChanged, this, &KompareSplitter::slotDelayedRepaintHandles);
72     connect(this, &KompareSplitter::configChanged, this, &KompareSplitter::slotDelayedUpdateScrollBars);
73 
74     // scrolling
75     connect(m_vScroll, &QScrollBar::valueChanged, this, &KompareSplitter::slotScrollToId);
76     connect(m_vScroll, &QScrollBar::sliderMoved,  this, &KompareSplitter::slotScrollToId);
77     connect(m_hScroll, &QScrollBar::valueChanged, this, &KompareSplitter::setXOffset);
78     connect(m_hScroll, &QScrollBar::sliderMoved,  this, &KompareSplitter::setXOffset);
79 
80     m_scrollTimer = new QTimer(this);
81     m_restartTimer = false;
82     connect(m_scrollTimer, &QTimer::timeout, this, &KompareSplitter::timerTimeout);
83 
84     // we need to receive childEvents now so that d->list is ready for when
85     // slotSetSelection(...) arrives
86     qApp->sendPostedEvents(this, QEvent::ChildAdded);
87 
88     // init stuff
89     slotUpdateScrollBars();
90 }
91 
~KompareSplitter()92 KompareSplitter::~KompareSplitter()
93 {
94 }
95 
createHandle()96 QSplitterHandle* KompareSplitter::createHandle()
97 {
98     return new KompareConnectWidgetFrame(m_settings, this);
99 }
100 
slotDelayedRepaintHandles()101 void KompareSplitter::slotDelayedRepaintHandles()
102 {
103     QTimer::singleShot(0, this, &KompareSplitter::slotRepaintHandles);
104 }
105 
slotRepaintHandles()106 void KompareSplitter::slotRepaintHandles()
107 {
108     const int end = count();
109     for (int i = 1; i < end; ++i)
110         handle(i)->update();
111 }
112 
timerTimeout()113 void KompareSplitter::timerTimeout()
114 {
115     if (m_restartTimer)
116         m_restartTimer = false;
117     else
118         m_scrollTimer->stop();
119 
120     slotDelayedRepaintHandles();
121 
122     Q_EMIT scrollViewsToId(m_scrollTo);
123     slotRepaintHandles();
124     m_vScroll->setValue(m_scrollTo);
125 }
126 
slotScrollToId(int id)127 void KompareSplitter::slotScrollToId(int id)
128 {
129     m_scrollTo = id;
130 
131     if (m_restartTimer)
132         return;
133 
134     if (m_scrollTimer->isActive())
135     {
136         m_restartTimer = true;
137     }
138     else
139     {
140         Q_EMIT scrollViewsToId(id);
141         slotRepaintHandles();
142         m_vScroll->setValue(id);
143         m_scrollTimer->start(30);
144     }
145 }
146 
slotDelayedUpdateScrollBars()147 void KompareSplitter::slotDelayedUpdateScrollBars()
148 {
149     QTimer::singleShot(0, this, &KompareSplitter::slotUpdateScrollBars);
150 }
151 
slotUpdateScrollBars()152 void KompareSplitter::slotUpdateScrollBars()
153 {
154     const int end = count();
155     for (int i = 0; i < end; ++i) {
156         KompareListView* lv = listView(i);
157         int minHScroll = minHScrollId();
158         if (lv->contentsX() < minHScroll) {
159             lv->setXOffset(minHScroll);
160         }
161     }
162 
163     int m_scrollDistance = m_settings->m_scrollNoOfLines * lineHeight();
164     int m_pageSize = pageSize();
165 
166     if (needVScrollBar())
167     {
168         m_vScroll->show();
169 
170         m_vScroll->blockSignals(true);
171         m_vScroll->setRange(minVScrollId(),
172                             maxVScrollId());
173         m_vScroll->setValue(scrollId());
174         m_vScroll->setSingleStep(m_scrollDistance);
175         m_vScroll->setPageStep(m_pageSize);
176         m_vScroll->blockSignals(false);
177     }
178     else
179     {
180         m_vScroll->hide();
181     }
182 
183     if (needHScrollBar())
184     {
185         m_hScroll->show();
186         m_hScroll->blockSignals(true);
187         m_hScroll->setRange(minHScrollId(), maxHScrollId());
188         m_hScroll->setValue(maxContentsX());
189         m_hScroll->setSingleStep(10);
190         m_hScroll->setPageStep(minVisibleWidth() - 10);
191         m_hScroll->blockSignals(false);
192     }
193     else
194     {
195         m_hScroll->hide();
196     }
197 }
198 
slotDelayedUpdateVScrollValue()199 void KompareSplitter::slotDelayedUpdateVScrollValue()
200 {
201     QTimer::singleShot(0, this, &KompareSplitter::slotUpdateVScrollValue);
202 }
203 
slotUpdateVScrollValue()204 void KompareSplitter::slotUpdateVScrollValue()
205 {
206     m_vScroll->setValue(scrollId());
207 }
208 
keyPressEvent(QKeyEvent * e)209 void KompareSplitter::keyPressEvent(QKeyEvent* e)
210 {
211     //keyboard scrolling
212     switch (e->key()) {
213     case Qt::Key_Right:
214     case Qt::Key_L:
215         m_hScroll->triggerAction(QAbstractSlider::SliderSingleStepAdd);
216         break;
217     case Qt::Key_Left:
218     case Qt::Key_H:
219         m_hScroll->triggerAction(QAbstractSlider::SliderSingleStepSub);
220         break;
221     case Qt::Key_Up:
222     case Qt::Key_K:
223         m_vScroll->triggerAction(QAbstractSlider::SliderSingleStepSub);
224         break;
225     case Qt::Key_Down:
226     case Qt::Key_J:
227         m_vScroll->triggerAction(QAbstractSlider::SliderSingleStepAdd);
228         break;
229     case Qt::Key_PageDown:
230         m_vScroll->triggerAction(QAbstractSlider::SliderPageStepAdd);
231         break;
232     case Qt::Key_PageUp:
233         m_vScroll->triggerAction(QAbstractSlider::SliderPageStepSub);
234         break;
235     }
236     e->accept();
237     slotRepaintHandles();
238 }
239 
wheelEvent(QWheelEvent * e)240 void KompareSplitter::wheelEvent(QWheelEvent* e)
241 {
242     if (e->angleDelta().y() != 0)
243     {
244         if (e->modifiers() & Qt::ControlModifier) {
245             if (e->angleDelta().y() < 0)   // scroll down one page
246                 m_vScroll->triggerAction(QAbstractSlider::SliderPageStepAdd);
247             else // scroll up one page
248                 m_vScroll->triggerAction(QAbstractSlider::SliderPageStepSub);
249         } else {
250             if (e->angleDelta().y() < 0)   // scroll down
251                 m_vScroll->triggerAction(QAbstractSlider::SliderSingleStepAdd);
252             else // scroll up
253                 m_vScroll->triggerAction(QAbstractSlider::SliderSingleStepSub);
254         }
255     }
256     else
257     {
258         if (e->modifiers() & Qt::ControlModifier) {
259             if (e->angleDelta().y() < 0)   // scroll right one page
260                 m_hScroll->triggerAction(QAbstractSlider::SliderPageStepAdd);
261             else // scroll left one page
262                 m_hScroll->triggerAction(QAbstractSlider::SliderPageStepSub);
263         } else {
264             if (e->angleDelta().y() < 0)   // scroll to the right
265                 m_hScroll->triggerAction(QAbstractSlider::SliderSingleStepAdd);
266             else // scroll to the left
267                 m_hScroll->triggerAction(QAbstractSlider::SliderSingleStepSub);
268         }
269     }
270     e->accept();
271     slotDelayedRepaintHandles();
272 }
273 
274 /* FIXME: this should return/the scrollId() from the listview containing the
275  * /base/ of the diff. but there's bigger issues with that atm.
276  */
277 
scrollId()278 int KompareSplitter::scrollId()
279 {
280     if (widget(0))
281         return listView(0)->scrollId();
282     return minVScrollId();
283 }
284 
lineHeight()285 int KompareSplitter::lineHeight()
286 {
287     if (widget(0))
288         return listView(0)->fontMetrics().height();
289     return 1;
290 }
291 
pageSize()292 int KompareSplitter::pageSize()
293 {
294     if (widget(0)) {
295         KompareListView* view = listView(0);
296         return view->visibleHeight() - view->style()->pixelMetric(QStyle::PM_ScrollBarExtent);
297     }
298     return 1;
299 }
300 
needVScrollBar()301 bool KompareSplitter::needVScrollBar()
302 {
303     int pagesize = pageSize();
304     const int end = count();
305     for (int i = 0; i < end; ++i) {
306         KompareListView* view = listView(i);
307         if (view ->contentsHeight() > pagesize)
308             return true;
309     }
310     return false;
311 }
312 
minVScrollId()313 int KompareSplitter::minVScrollId()
314 {
315 
316     int min = -1;
317     int mSId;
318     const int end = count();
319     for (int i = 0; i < end; ++i) {
320         mSId = listView(i)->minScrollId();
321         if (mSId < min || min == -1)
322             min = mSId;
323     }
324     return (min == -1) ? 0 : min;
325 }
326 
maxVScrollId()327 int KompareSplitter::maxVScrollId()
328 {
329     int max = 0;
330     int mSId;
331     const int end = count();
332     for (int i = 0; i < end; ++i) {
333         mSId = listView(i)->maxScrollId();
334         if (mSId > max)
335             max = mSId;
336     }
337     return max;
338 }
339 
needHScrollBar()340 bool KompareSplitter::needHScrollBar()
341 {
342     const int end = count();
343     for (int i = 0; i < end; ++i) {
344         KompareListView* view = listView(i);
345         if (view->contentsWidth() > view->visibleWidth())
346             return true;
347     }
348     return false;
349 }
350 
minHScrollId()351 int KompareSplitter::minHScrollId()
352 {
353     // hardcode an offset to hide the tree controls
354     return 6;
355 }
356 
maxHScrollId()357 int KompareSplitter::maxHScrollId()
358 {
359     int max = 0;
360     int mHSId;
361     const int end = count();
362     for (int i = 0; i < end; ++i) {
363         KompareListView* view = listView(i);
364         mHSId = view->contentsWidth() - view->visibleWidth();
365         if (mHSId > max)
366             max = mHSId;
367     }
368     return max;
369 }
370 
maxContentsX()371 int KompareSplitter::maxContentsX()
372 {
373     int max = 0;
374     int mCX;
375     const int end = count();
376     for (int i = 0; i < end; ++i) {
377         mCX = listView(i)->contentsX();
378         if (mCX > max)
379             max = mCX;
380     }
381     return max;
382 }
383 
minVisibleWidth()384 int KompareSplitter::minVisibleWidth()
385 {
386     // Why the hell do we want to know this?
387     // ah yes, it is because we use it to set the "page size" for horiz. scrolling.
388     // despite the fact that *none* has a pgright and pgleft key :P
389     // But we do have mousewheels with horizontal scrolling functionality,
390     // pressing shift and scrolling then goes left and right one page at the time
391     int min = -1;
392     int vW;
393     const int end = count();
394     for (int i = 0; i < end; ++i) {
395         vW = listView(i)->visibleWidth();
396         if (vW < min || min == -1)
397             min = vW;
398     }
399     return (min == -1) ? 0 : min;
400 }
401 
listView(int index)402 KompareListView* KompareSplitter::listView(int index)
403 {
404     return static_cast<KompareListViewFrame*>(widget(index))->view();
405 }
406 
connectWidget(int index)407 KompareConnectWidget* KompareSplitter::connectWidget(int index)
408 {
409     return static_cast<KompareConnectWidgetFrame*>(handle(index))->wid();
410 }
411 
slotSetSelection(const DiffModel * model,const Difference * diff)412 void KompareSplitter::slotSetSelection(const DiffModel* model, const Difference* diff)
413 {
414     const int end = count();
415     for (int i = 0; i < end; ++i) {
416         connectWidget(i)->slotSetSelection(model, diff);
417         listView(i)->slotSetSelection(model, diff);
418         static_cast<KompareListViewFrame*>(widget(i))->slotSetModel(model);
419     }
420 
421     slotDelayedRepaintHandles();
422     slotDelayedUpdateScrollBars();
423 }
424 
slotSetSelection(const Difference * diff)425 void KompareSplitter::slotSetSelection(const Difference* diff)
426 {
427     const int end = count();
428     for (int i = 0; i < end; ++i) {
429         connectWidget(i)->slotSetSelection(diff);
430         listView(i)->slotSetSelection(diff);
431     }
432 
433     slotDelayedRepaintHandles();
434     slotDelayedUpdateScrollBars();
435 }
436 
slotApplyDifference(bool apply)437 void KompareSplitter::slotApplyDifference(bool apply)
438 {
439     const int end = count();
440     for (int i = 0; i < end; ++i)
441         listView(i)->slotApplyDifference(apply);
442     slotDelayedRepaintHandles();
443 }
444 
slotApplyAllDifferences(bool apply)445 void KompareSplitter::slotApplyAllDifferences(bool apply)
446 {
447     const int end = count();
448     for (int i = 0; i < end; ++i)
449         listView(i)->slotApplyAllDifferences(apply);
450     slotDelayedRepaintHandles();
451     slotScrollToId(m_scrollTo);   // FIXME!
452 }
453 
slotApplyDifference(const Difference * diff,bool apply)454 void KompareSplitter::slotApplyDifference(const Difference* diff, bool apply)
455 {
456     const int end = count();
457     for (int i = 0; i < end; ++i)
458         listView(i)->slotApplyDifference(diff, apply);
459     slotDelayedRepaintHandles();
460 }
461 
slotDifferenceClicked(const Difference * diff)462 void KompareSplitter::slotDifferenceClicked(const Difference* diff)
463 {
464     const int end = count();
465     for (int i = 0; i < end; ++i)
466         listView(i)->setSelectedDifference(diff, false);
467     Q_EMIT selectionChanged(diff);
468 }
469 
slotConfigChanged()470 void KompareSplitter::slotConfigChanged()
471 {
472     const int end = count();
473     for (int i = 0; i < end; ++i) {
474         KompareListView* view = listView(i);
475         view->setFont(m_settings->m_font);
476         view->update();
477     }
478 }
479