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