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