File indexing completed on 2024-05-05 10:16:51
0001 /* 0002 This file is part of the Okteta Gui library, made within the KDE community. 0003 0004 SPDX-FileCopyrightText: 2003, 2007, 2008 Friedrich W. H. Kossebau <kossebau@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0007 */ 0008 0009 #include "columnsview.hpp" 0010 #include "columnsview_p.hpp" 0011 0012 #include <logging.hpp> 0013 // Qt 0014 #include <QPaintEvent> 0015 #include <QPainter> 0016 #include <QStyle> 0017 #include <QScrollBar> 0018 // Std 0019 #include <utility> 0020 0021 namespace Okteta { 0022 0023 ColumnsView::ColumnsView(ColumnsViewPrivate* dd, QWidget* parent) 0024 : QAbstractScrollArea(parent) 0025 , d_ptr(dd) 0026 { 0027 Q_D(ColumnsView); 0028 0029 d->init(); 0030 } 0031 0032 ColumnsView::ColumnsView(/*bool R,*/ QWidget* parent) 0033 : QAbstractScrollArea(parent) 0034 , d_ptr(new ColumnsViewPrivate(this)) 0035 { 0036 Q_D(ColumnsView); 0037 0038 d->init(); 0039 } 0040 0041 ColumnsView::~ColumnsView() = default; 0042 0043 LineSize ColumnsView::noOfLines() const 0044 { 0045 Q_D(const ColumnsView); 0046 0047 return d->NoOfLines; 0048 } 0049 0050 PixelY ColumnsView::lineHeight() const 0051 { 0052 Q_D(const ColumnsView); 0053 0054 return d->LineHeight; 0055 } 0056 0057 Line ColumnsView::lineAt(PixelY y) const 0058 { 0059 Q_D(const ColumnsView); 0060 0061 return (d->LineHeight != 0) ? y / d->LineHeight : 0; 0062 } 0063 0064 LineRange ColumnsView::visibleLines() const 0065 { 0066 const PixelYRange ySpan = PixelYRange::fromWidth(yOffset(), visibleHeight()); 0067 return LineRange(lineAt(ySpan.start()), lineAt(ySpan.end())); 0068 } 0069 LineRange ColumnsView::visibleLines(const PixelYRange& yPixels) const 0070 { 0071 return LineRange(lineAt(yPixels.start()), lineAt(yPixels.end())); 0072 } 0073 0074 PixelX ColumnsView::visibleWidth() const 0075 { 0076 return viewport()->width(); 0077 } 0078 0079 PixelY ColumnsView::visibleHeight() const 0080 { 0081 return viewport()->height(); 0082 } 0083 0084 PixelY ColumnsView::columnsHeight() const 0085 { 0086 Q_D(const ColumnsView); 0087 0088 return d->NoOfLines * d->LineHeight; 0089 } 0090 0091 PixelX ColumnsView::columnsWidth() const 0092 { 0093 Q_D(const ColumnsView); 0094 0095 return d->ColumnsWidth; 0096 } 0097 0098 QPoint ColumnsView::viewportToColumns(QPoint point) const 0099 { 0100 return QPoint(xOffset(), yOffset()) + point; 0101 } 0102 0103 PixelX ColumnsView::xOffset() const 0104 { 0105 return horizontalScrollBar()->value(); 0106 } 0107 0108 PixelY ColumnsView::yOffset() const 0109 { 0110 return verticalScrollBar()->value(); 0111 } 0112 0113 PixelY ColumnsView::yOffsetOfLine(Line lineIndex) const 0114 { 0115 Q_D(const ColumnsView); 0116 0117 return lineIndex * d->LineHeight - yOffset(); 0118 } 0119 0120 void ColumnsView::setColumnsPos(PixelX x, PixelY y) 0121 { 0122 horizontalScrollBar()->setValue(x); 0123 verticalScrollBar()->setValue(y); 0124 } 0125 0126 void ColumnsView::setNoOfLines(LineSize newNoOfLines) 0127 { 0128 Q_D(ColumnsView); 0129 0130 if (d->NoOfLines == newNoOfLines) { 0131 return; 0132 } 0133 0134 d->NoOfLines = newNoOfLines; 0135 0136 updateScrollBars(); 0137 } 0138 0139 void ColumnsView::setLineHeight(PixelY newLineHeight) 0140 { 0141 Q_D(ColumnsView); 0142 0143 if (newLineHeight == d->LineHeight) { 0144 return; 0145 } 0146 0147 d->LineHeight = newLineHeight; 0148 0149 for (auto* column : std::as_const(d->columns)) { 0150 column->setLineHeight(d->LineHeight); 0151 } 0152 0153 verticalScrollBar()->setSingleStep(d->LineHeight); 0154 updateScrollBars(); 0155 } 0156 0157 void ColumnsView::updateWidths() 0158 { 0159 Q_D(ColumnsView); 0160 0161 d->updateWidths(); 0162 0163 updateScrollBars(); 0164 } 0165 0166 void ColumnsView::updateScrollBars() 0167 { 0168 QSize viewSize = maximumViewportSize(); 0169 0170 const int scrollBarWidth = style()->pixelMetric(QStyle::PM_ScrollBarExtent); 0171 const PixelY usedHeight = columnsHeight(); 0172 const PixelX usedWidth = columnsWidth(); 0173 0174 const bool needsVerticalBarDefinitely = (usedHeight > viewSize.height()); 0175 const bool needsHorizontalBarDefinitely = (usedWidth > viewSize.width()); 0176 0177 if (needsVerticalBarDefinitely) { 0178 viewSize.rwidth() -= scrollBarWidth; 0179 } 0180 if (needsHorizontalBarDefinitely) { 0181 viewSize.rheight() -= scrollBarWidth; 0182 } 0183 // check again if bars are not needed now 0184 if (!needsVerticalBarDefinitely && usedHeight > viewSize.height()) { 0185 viewSize.rwidth() -= scrollBarWidth; 0186 } 0187 if (!needsHorizontalBarDefinitely && usedWidth > viewSize.width()) { 0188 viewSize.rheight() -= scrollBarWidth; 0189 } 0190 0191 verticalScrollBar()->setRange(0, usedHeight - viewSize.height()); 0192 verticalScrollBar()->setPageStep(viewSize.height()); 0193 horizontalScrollBar()->setRange(0, usedWidth - viewSize.width()); 0194 horizontalScrollBar()->setPageStep(viewSize.width()); 0195 } 0196 0197 void ColumnsView::updateColumn(AbstractColumnRenderer& columnRenderer) 0198 { 0199 if (columnRenderer.isVisible()) { 0200 viewport()->update(columnRenderer.x() - xOffset(), 0, columnRenderer.width(), visibleHeight()); 0201 } 0202 } 0203 0204 void ColumnsView::updateColumn(AbstractColumnRenderer& columnRenderer, const LineRange& lines) 0205 { 0206 Q_D(ColumnsView); 0207 0208 if (columnRenderer.isVisible()) { // TODO: catch hidden range && columnRenderer.overlaps(Xs) ) 0209 LineRange linesToUpdate = visibleLines(); 0210 linesToUpdate.restrictTo(lines); 0211 if (linesToUpdate.isValid()) { 0212 const PixelX x = columnRenderer.x() - xOffset(); 0213 const PixelY y = yOffsetOfLine(linesToUpdate.start()); 0214 const int width = columnRenderer.width(); 0215 const int height = d->LineHeight * linesToUpdate.width(); 0216 viewport()->update(x, y, width, height); 0217 } 0218 } 0219 } 0220 0221 LineSize ColumnsView::noOfLinesPerPage() const 0222 { 0223 Q_D(const ColumnsView); 0224 0225 if (d->LineHeight < 1) { 0226 return 1; 0227 } 0228 0229 LineSize result = (visibleHeight() - 1) / d->LineHeight; // -1 ensures to get always the last visible line 0230 0231 if (result < 1) { 0232 // ensure to move down at least one line 0233 result = 1; 0234 } 0235 0236 return result; 0237 } 0238 0239 void ColumnsView::addColumn(AbstractColumnRenderer* columnRenderer) 0240 { 0241 Q_D(ColumnsView); 0242 0243 // if( Reversed ) 0244 // Columns.prepend( C ); 0245 // else 0246 d->columns.append(columnRenderer); 0247 0248 updateWidths(); 0249 } 0250 0251 void ColumnsView::removeColumn(AbstractColumnRenderer* columnRenderer) 0252 { 0253 Q_D(ColumnsView); 0254 0255 const int columnRendererIndex = d->columns.indexOf(columnRenderer); 0256 if (columnRendererIndex != -1) { 0257 d->columns.removeAt(columnRendererIndex); 0258 } 0259 0260 delete columnRenderer; 0261 0262 updateWidths(); 0263 } 0264 0265 void ColumnsView::scrollContentsBy(int dx, int dy) 0266 { 0267 viewport()->scroll(dx, dy); 0268 } 0269 0270 bool ColumnsView::event(QEvent* event) 0271 { 0272 if (event->type() == QEvent::StyleChange || event->type() == QEvent::LayoutRequest) { 0273 updateScrollBars(); 0274 } 0275 0276 return QAbstractScrollArea::event(event); 0277 } 0278 0279 void ColumnsView::resizeEvent(QResizeEvent* event) 0280 { 0281 updateScrollBars(); 0282 0283 QAbstractScrollArea::resizeEvent(event); 0284 } 0285 0286 void ColumnsView::paintEvent(QPaintEvent* paintEvent) 0287 { 0288 QAbstractScrollArea::paintEvent(paintEvent); 0289 0290 const PixelX x = xOffset(); 0291 const PixelY y = yOffset(); 0292 0293 QRect dirtyRect = paintEvent->rect(); 0294 dirtyRect.translate(x, y); 0295 0296 QPainter painter(viewport()); 0297 painter.translate(-x, -y); 0298 0299 renderColumns(&painter, dirtyRect.x(), dirtyRect.y(), dirtyRect.width(), dirtyRect.height()); 0300 } 0301 0302 void ColumnsView::renderColumns(QPainter* painter, int cx, int cy, int cw, int ch) 0303 { 0304 Q_D(ColumnsView); 0305 0306 PixelXRange dirtyXs = PixelXRange::fromWidth(cx, cw); 0307 0308 // content to be shown? 0309 if (dirtyXs.startsBefore(d->ColumnsWidth)) { 0310 PixelYRange dirtyYs = PixelYRange::fromWidth(cy, ch); 0311 0312 // collect affected columns 0313 QVector<AbstractColumnRenderer*> dirtyColumns; 0314 dirtyColumns.reserve(d->columns.size()); 0315 for (auto* column : std::as_const(d->columns)) { 0316 if (column->isVisible() && column->overlaps(dirtyXs)) { 0317 dirtyColumns.append(column); 0318 } 0319 } 0320 0321 // any lines of any columns to be drawn? 0322 if (d->NoOfLines > 0) { 0323 // calculate affected lines 0324 LineRange dirtyLines = visibleLines(dirtyYs); 0325 dirtyLines.restrictEndTo(d->NoOfLines - 1); 0326 0327 if (dirtyLines.isValid()) { 0328 // paint full columns 0329 for (auto* column : std::as_const(d->columns)) { 0330 column->renderColumn(painter, dirtyXs, dirtyYs); 0331 } 0332 0333 PixelY cy = dirtyLines.start() * d->LineHeight; 0334 // qCDebug(LOG_OKTETA_GUI)<<"Dirty lines: "<<dirtyLines.start()<<"-"<<dirtyLines.end(); 0335 // starting painting with the first line 0336 Line line = dirtyLines.start(); 0337 auto it = dirtyColumns.constBegin(); 0338 AbstractColumnRenderer* column = *it; 0339 painter->translate(column->x(), cy); 0340 0341 while (true) { 0342 column->renderFirstLine(painter, dirtyXs, line); 0343 ++it; 0344 if (it == dirtyColumns.constEnd()) { 0345 break; 0346 } 0347 painter->translate(column->width(), 0); 0348 column = *it; 0349 } 0350 painter->translate(-column->x(), 0); 0351 0352 // Go through the other lines 0353 while (true) { 0354 ++line; 0355 0356 if (line > dirtyLines.end()) { 0357 break; 0358 } 0359 0360 it = dirtyColumns.constBegin(); 0361 column = *it; 0362 painter->translate(column->x(), d->LineHeight); 0363 0364 while (true) { 0365 column->renderNextLine(painter); 0366 ++it; 0367 if (it == dirtyColumns.constEnd()) { 0368 break; 0369 } 0370 painter->translate(column->width(), 0); 0371 column = *it; 0372 } 0373 painter->translate(-column->x(), 0); 0374 } 0375 cy = dirtyLines.end() * d->LineHeight; 0376 0377 painter->translate(0, -cy); 0378 } 0379 } 0380 0381 // draw empty columns? 0382 dirtyYs.setStart(columnsHeight()); 0383 if (dirtyYs.isValid()) { 0384 for (auto* column : std::as_const(dirtyColumns)) { 0385 column->renderEmptyColumn(painter, dirtyXs, dirtyYs); 0386 } 0387 } 0388 } 0389 0390 // painter empty rects 0391 dirtyXs.setStart(d->ColumnsWidth); 0392 if (dirtyXs.isValid()) { 0393 renderEmptyArea(painter, dirtyXs.start(), cy, dirtyXs.width(), ch); 0394 } 0395 } 0396 0397 void ColumnsView::renderEmptyArea(QPainter* painter, int cx, int cy, int cw, int ch) 0398 { 0399 painter->fillRect(cx, cy, cw, ch, viewport()->palette().brush(QPalette::Base)); // TODO: use stylist here, too 0400 } 0401 0402 } 0403 0404 #include "moc_columnsview.cpp"