1 /*
2 This file is part of the Okteta Gui library, made within the KDE community.
3
4 SPDX-FileCopyrightText: 2003, 2007, 2008 Friedrich W. H. Kossebau <kossebau@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7 */
8
9 #include "columnsview.hpp"
10 #include "columnsview_p.hpp"
11
12 // Qt
13 #include <QPaintEvent>
14 #include <QPainter>
15 #include <QStyle>
16 #include <QScrollBar>
17
18 namespace Okteta {
19
ColumnsView(ColumnsViewPrivate * dd,QWidget * parent)20 ColumnsView::ColumnsView(ColumnsViewPrivate* dd, QWidget* parent)
21 : QAbstractScrollArea(parent)
22 , d_ptr(dd)
23 {
24 Q_D(ColumnsView);
25
26 d->init();
27 }
28
ColumnsView(QWidget * parent)29 ColumnsView::ColumnsView(/*bool R,*/ QWidget* parent)
30 : QAbstractScrollArea(parent)
31 , d_ptr(new ColumnsViewPrivate(this))
32 {
33 Q_D(ColumnsView);
34
35 d->init();
36 }
37
38 ColumnsView::~ColumnsView() = default;
39
noOfLines() const40 LineSize ColumnsView::noOfLines() const
41 {
42 Q_D(const ColumnsView);
43
44 return d->NoOfLines;
45 }
46
lineHeight() const47 PixelY ColumnsView::lineHeight() const
48 {
49 Q_D(const ColumnsView);
50
51 return d->LineHeight;
52 }
53
lineAt(PixelY y) const54 Line ColumnsView::lineAt(PixelY y) const
55 {
56 Q_D(const ColumnsView);
57
58 return (d->LineHeight != 0) ? y / d->LineHeight : 0;
59 }
60
visibleLines() const61 LineRange ColumnsView::visibleLines() const
62 {
63 const PixelYRange ySpan = PixelYRange::fromWidth(yOffset(), visibleHeight());
64 return LineRange(lineAt(ySpan.start()), lineAt(ySpan.end()));
65 }
visibleLines(const PixelYRange & yPixels) const66 LineRange ColumnsView::visibleLines(const PixelYRange& yPixels) const
67 {
68 return LineRange(lineAt(yPixels.start()), lineAt(yPixels.end()));
69 }
70
visibleWidth() const71 PixelX ColumnsView::visibleWidth() const
72 {
73 return viewport()->width();
74 }
75
visibleHeight() const76 PixelY ColumnsView::visibleHeight() const
77 {
78 return viewport()->height();
79 }
80
columnsHeight() const81 PixelY ColumnsView::columnsHeight() const
82 {
83 Q_D(const ColumnsView);
84
85 return d->NoOfLines * d->LineHeight;
86 }
87
columnsWidth() const88 PixelX ColumnsView::columnsWidth() const
89 {
90 Q_D(const ColumnsView);
91
92 return d->ColumnsWidth;
93 }
94
viewportToColumns(const QPoint & point) const95 QPoint ColumnsView::viewportToColumns(const QPoint& point) const
96 {
97 return QPoint(xOffset(), yOffset()) + point;
98 }
99
xOffset() const100 PixelX ColumnsView::xOffset() const
101 {
102 return horizontalScrollBar()->value();
103 }
104
yOffset() const105 PixelY ColumnsView::yOffset() const
106 {
107 return verticalScrollBar()->value();
108 }
109
yOffsetOfLine(Line lineIndex) const110 PixelY ColumnsView::yOffsetOfLine(Line lineIndex) const
111 {
112 Q_D(const ColumnsView);
113
114 return lineIndex * d->LineHeight - yOffset();
115 }
116
setColumnsPos(PixelX x,PixelY y)117 void ColumnsView::setColumnsPos(PixelX x, PixelY y)
118 {
119 horizontalScrollBar()->setValue(x);
120 verticalScrollBar()->setValue(y);
121 }
122
setNoOfLines(LineSize newNoOfLines)123 void ColumnsView::setNoOfLines(LineSize newNoOfLines)
124 {
125 Q_D(ColumnsView);
126
127 if (d->NoOfLines == newNoOfLines) {
128 return;
129 }
130
131 d->NoOfLines = newNoOfLines;
132
133 updateScrollBars();
134 }
135
setLineHeight(PixelY newLineHeight)136 void ColumnsView::setLineHeight(PixelY newLineHeight)
137 {
138 Q_D(ColumnsView);
139
140 if (newLineHeight == d->LineHeight) {
141 return;
142 }
143
144 d->LineHeight = newLineHeight;
145
146 for (auto column : qAsConst(d->columns)) {
147 column->setLineHeight(d->LineHeight);
148 }
149
150 verticalScrollBar()->setSingleStep(d->LineHeight);
151 updateScrollBars();
152 }
153
updateWidths()154 void ColumnsView::updateWidths()
155 {
156 Q_D(ColumnsView);
157
158 d->updateWidths();
159
160 updateScrollBars();
161 }
162
updateScrollBars()163 void ColumnsView::updateScrollBars()
164 {
165 QSize viewSize = maximumViewportSize();
166
167 const int scrollBarWidth = style()->pixelMetric(QStyle::PM_ScrollBarExtent);
168 const PixelY usedHeight = columnsHeight();
169 const PixelX usedWidth = columnsWidth();
170
171 const bool needsVerticalBarDefinitely = (usedHeight > viewSize.height());
172 const bool needsHorizontalBarDefinitely = (usedWidth > viewSize.width());
173
174 if (needsVerticalBarDefinitely) {
175 viewSize.rwidth() -= scrollBarWidth;
176 }
177 if (needsHorizontalBarDefinitely) {
178 viewSize.rheight() -= scrollBarWidth;
179 }
180 // check again if bars are not needed now
181 if (!needsVerticalBarDefinitely && usedHeight > viewSize.height()) {
182 viewSize.rwidth() -= scrollBarWidth;
183 }
184 if (!needsHorizontalBarDefinitely && usedWidth > viewSize.width()) {
185 viewSize.rheight() -= scrollBarWidth;
186 }
187
188 verticalScrollBar()->setRange(0, usedHeight - viewSize.height());
189 verticalScrollBar()->setPageStep(viewSize.height());
190 horizontalScrollBar()->setRange(0, usedWidth - viewSize.width());
191 horizontalScrollBar()->setPageStep(viewSize.width());
192 }
193
updateColumn(AbstractColumnRenderer & columnRenderer)194 void ColumnsView::updateColumn(AbstractColumnRenderer& columnRenderer)
195 {
196 if (columnRenderer.isVisible()) {
197 viewport()->update(columnRenderer.x() - xOffset(), 0, columnRenderer.width(), visibleHeight());
198 }
199 }
200
updateColumn(AbstractColumnRenderer & columnRenderer,const LineRange & lines)201 void ColumnsView::updateColumn(AbstractColumnRenderer& columnRenderer, const LineRange& lines)
202 {
203 Q_D(ColumnsView);
204
205 if (columnRenderer.isVisible()) { // TODO: catch hidden range && columnRenderer.overlaps(Xs) )
206 LineRange linesToUpdate = visibleLines();
207 linesToUpdate.restrictTo(lines);
208 if (linesToUpdate.isValid()) {
209 const PixelX x = columnRenderer.x() - xOffset();
210 const PixelY y = yOffsetOfLine(linesToUpdate.start());
211 const int width = columnRenderer.width();
212 const int height = d->LineHeight * linesToUpdate.width();
213 viewport()->update(x, y, width, height);
214 }
215 }
216 }
217
noOfLinesPerPage() const218 LineSize ColumnsView::noOfLinesPerPage() const
219 {
220 Q_D(const ColumnsView);
221
222 if (d->LineHeight < 1) {
223 return 1;
224 }
225
226 LineSize result = (visibleHeight() - 1) / d->LineHeight; // -1 ensures to get always the last visible line
227
228 if (result < 1) {
229 // ensure to move down at least one line
230 result = 1;
231 }
232
233 return result;
234 }
235
addColumn(AbstractColumnRenderer * columnRenderer)236 void ColumnsView::addColumn(AbstractColumnRenderer* columnRenderer)
237 {
238 Q_D(ColumnsView);
239
240 // if( Reversed )
241 // Columns.prepend( C );
242 // else
243 d->columns.append(columnRenderer);
244
245 updateWidths();
246 }
247
removeColumn(AbstractColumnRenderer * columnRenderer)248 void ColumnsView::removeColumn(AbstractColumnRenderer* columnRenderer)
249 {
250 Q_D(ColumnsView);
251
252 const int columnRendererIndex = d->columns.indexOf(columnRenderer);
253 if (columnRendererIndex != -1) {
254 d->columns.removeAt(columnRendererIndex);
255 }
256
257 delete columnRenderer;
258
259 updateWidths();
260 }
261
scrollContentsBy(int dx,int dy)262 void ColumnsView::scrollContentsBy(int dx, int dy)
263 {
264 viewport()->scroll(dx, dy);
265 }
266
event(QEvent * event)267 bool ColumnsView::event(QEvent* event)
268 {
269 if (event->type() == QEvent::StyleChange || event->type() == QEvent::LayoutRequest) {
270 updateScrollBars();
271 }
272
273 return QAbstractScrollArea::event(event);
274 }
275
resizeEvent(QResizeEvent * event)276 void ColumnsView::resizeEvent(QResizeEvent* event)
277 {
278 updateScrollBars();
279
280 QAbstractScrollArea::resizeEvent(event);
281 }
282
paintEvent(QPaintEvent * paintEvent)283 void ColumnsView::paintEvent(QPaintEvent* paintEvent)
284 {
285 QAbstractScrollArea::paintEvent(paintEvent);
286
287 const PixelX x = xOffset();
288 const PixelY y = yOffset();
289
290 QRect dirtyRect = paintEvent->rect();
291 dirtyRect.translate(x, y);
292
293 QPainter painter(viewport());
294 painter.translate(-x, -y);
295
296 renderColumns(&painter, dirtyRect.x(), dirtyRect.y(), dirtyRect.width(), dirtyRect.height());
297 }
298
renderColumns(QPainter * painter,int cx,int cy,int cw,int ch)299 void ColumnsView::renderColumns(QPainter* painter, int cx, int cy, int cw, int ch)
300 {
301 Q_D(ColumnsView);
302
303 PixelXRange dirtyXs = PixelXRange::fromWidth(cx, cw);
304
305 // content to be shown?
306 if (dirtyXs.startsBefore(d->ColumnsWidth)) {
307 PixelYRange dirtyYs = PixelYRange::fromWidth(cy, ch);
308
309 // collect affected columns
310 QVector<AbstractColumnRenderer*> dirtyColumns;
311 dirtyColumns.reserve(d->columns.size());
312 for (auto column : qAsConst(d->columns)) {
313 if (column->isVisible() && column->overlaps(dirtyXs)) {
314 dirtyColumns.append(column);
315 }
316 }
317
318 // any lines of any columns to be drawn?
319 if (d->NoOfLines > 0) {
320 // calculate affected lines
321 LineRange dirtyLines = visibleLines(dirtyYs);
322 dirtyLines.restrictEndTo(d->NoOfLines - 1);
323
324 if (dirtyLines.isValid()) {
325 // paint full columns
326 for (auto column : qAsConst(d->columns)) {
327 column->renderColumn(painter, dirtyXs, dirtyYs);
328 }
329
330 PixelY cy = dirtyLines.start() * d->LineHeight;
331 // qCDebug(LOG_OKTETA_GUI)<<"Dirty lines: "<<dirtyLines.start()<<"-"<<dirtyLines.end();
332 // starting painting with the first line
333 Line line = dirtyLines.start();
334 auto it = dirtyColumns.constBegin();
335 AbstractColumnRenderer* column = *it;
336 painter->translate(column->x(), cy);
337
338 while (true) {
339 column->renderFirstLine(painter, dirtyXs, line);
340 ++it;
341 if (it == dirtyColumns.constEnd()) {
342 break;
343 }
344 painter->translate(column->width(), 0);
345 column = *it;
346 }
347 painter->translate(-column->x(), 0);
348
349 // Go through the other lines
350 while (true) {
351 ++line;
352
353 if (line > dirtyLines.end()) {
354 break;
355 }
356
357 it = dirtyColumns.constBegin();
358 column = *it;
359 painter->translate(column->x(), d->LineHeight);
360
361 while (true) {
362 column->renderNextLine(painter);
363 ++it;
364 if (it == dirtyColumns.constEnd()) {
365 break;
366 }
367 painter->translate(column->width(), 0);
368 column = *it;
369 }
370 painter->translate(-column->x(), 0);
371 }
372 cy = dirtyLines.end() * d->LineHeight;
373
374 painter->translate(0, -cy);
375 }
376 }
377
378 // draw empty columns?
379 dirtyYs.setStart(columnsHeight());
380 if (dirtyYs.isValid()) {
381 for (auto column : qAsConst(dirtyColumns)) {
382 column->renderEmptyColumn(painter, dirtyXs, dirtyYs);
383 }
384 }
385 }
386
387 // painter empty rects
388 dirtyXs.setStart(d->ColumnsWidth);
389 if (dirtyXs.isValid()) {
390 renderEmptyArea(painter, dirtyXs.start(), cy, dirtyXs.width(), ch);
391 }
392 }
393
renderEmptyArea(QPainter * painter,int cx,int cy,int cw,int ch)394 void ColumnsView::renderEmptyArea(QPainter* painter, int cx, int cy, int cw, int ch)
395 {
396 painter->fillRect(cx, cy, cw, ch, viewport()->palette().brush(QPalette::Base)); // TODO: use stylist here, too
397 }
398
399 }
400