| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the examples of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:BSD$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** BSD License Usage |
| 18 | ** Alternatively, you may use this file under the terms of the BSD license |
| 19 | ** as follows: |
| 20 | ** |
| 21 | ** "Redistribution and use in source and binary forms, with or without |
| 22 | ** modification, are permitted provided that the following conditions are |
| 23 | ** met: |
| 24 | ** * Redistributions of source code must retain the above copyright |
| 25 | ** notice, this list of conditions and the following disclaimer. |
| 26 | ** * Redistributions in binary form must reproduce the above copyright |
| 27 | ** notice, this list of conditions and the following disclaimer in |
| 28 | ** the documentation and/or other materials provided with the |
| 29 | ** distribution. |
| 30 | ** * Neither the name of The Qt Company Ltd nor the names of its |
| 31 | ** contributors may be used to endorse or promote products derived |
| 32 | ** from this software without specific prior written permission. |
| 33 | ** |
| 34 | ** |
| 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." |
| 46 | ** |
| 47 | ** $QT_END_LICENSE$ |
| 48 | ** |
| 49 | ****************************************************************************/ |
| 50 | |
| 51 | #include "pieview.h" |
| 52 | |
| 53 | #include <QtWidgets> |
| 54 | |
| 55 | PieView::PieView(QWidget *parent) |
| 56 | : QAbstractItemView(parent) |
| 57 | { |
| 58 | horizontalScrollBar()->setRange(min: 0, max: 0); |
| 59 | verticalScrollBar()->setRange(min: 0, max: 0); |
| 60 | } |
| 61 | |
| 62 | void PieView::dataChanged(const QModelIndex &topLeft, |
| 63 | const QModelIndex &bottomRight, |
| 64 | const QVector<int> &roles) |
| 65 | { |
| 66 | QAbstractItemView::dataChanged(topLeft, bottomRight, roles); |
| 67 | |
| 68 | if (!roles.contains(t: Qt::DisplayRole)) |
| 69 | return; |
| 70 | |
| 71 | validItems = 0; |
| 72 | totalValue = 0.0; |
| 73 | |
| 74 | for (int row = 0; row < model()->rowCount(parent: rootIndex()); ++row) { |
| 75 | |
| 76 | QModelIndex index = model()->index(row, column: 1, parent: rootIndex()); |
| 77 | double value = model()->data(index, role: Qt::DisplayRole).toDouble(); |
| 78 | |
| 79 | if (value > 0.0) { |
| 80 | totalValue += value; |
| 81 | validItems++; |
| 82 | } |
| 83 | } |
| 84 | viewport()->update(); |
| 85 | } |
| 86 | |
| 87 | bool PieView::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) |
| 88 | { |
| 89 | if (index.column() == 0) |
| 90 | return QAbstractItemView::edit(index, trigger, event); |
| 91 | else |
| 92 | return false; |
| 93 | } |
| 94 | |
| 95 | /* |
| 96 | Returns the item that covers the coordinate given in the view. |
| 97 | */ |
| 98 | |
| 99 | QModelIndex PieView::indexAt(const QPoint &point) const |
| 100 | { |
| 101 | if (validItems == 0) |
| 102 | return QModelIndex(); |
| 103 | |
| 104 | // Transform the view coordinates into contents widget coordinates. |
| 105 | int wx = point.x() + horizontalScrollBar()->value(); |
| 106 | int wy = point.y() + verticalScrollBar()->value(); |
| 107 | |
| 108 | if (wx < totalSize) { |
| 109 | double cx = wx - totalSize / 2; |
| 110 | double cy = totalSize / 2 - wy; // positive cy for items above the center |
| 111 | |
| 112 | // Determine the distance from the center point of the pie chart. |
| 113 | double d = std::sqrt(x: std::pow(x: cx, y: 2) + std::pow(x: cy, y: 2)); |
| 114 | |
| 115 | if (d == 0 || d > pieSize / 2) |
| 116 | return QModelIndex(); |
| 117 | |
| 118 | // Determine the angle of the point. |
| 119 | double angle = qRadiansToDegrees(radians: std::atan2(y: cy, x: cx)); |
| 120 | if (angle < 0) |
| 121 | angle = 360 + angle; |
| 122 | |
| 123 | // Find the relevant slice of the pie. |
| 124 | double startAngle = 0.0; |
| 125 | |
| 126 | for (int row = 0; row < model()->rowCount(parent: rootIndex()); ++row) { |
| 127 | |
| 128 | QModelIndex index = model()->index(row, column: 1, parent: rootIndex()); |
| 129 | double value = model()->data(index).toDouble(); |
| 130 | |
| 131 | if (value > 0.0) { |
| 132 | double sliceAngle = 360 * value / totalValue; |
| 133 | |
| 134 | if (angle >= startAngle && angle < (startAngle + sliceAngle)) |
| 135 | return model()->index(row, column: 1, parent: rootIndex()); |
| 136 | |
| 137 | startAngle += sliceAngle; |
| 138 | } |
| 139 | } |
| 140 | } else { |
| 141 | double itemHeight = QFontMetrics(viewOptions().font).height(); |
| 142 | int listItem = int((wy - margin) / itemHeight); |
| 143 | int validRow = 0; |
| 144 | |
| 145 | for (int row = 0; row < model()->rowCount(parent: rootIndex()); ++row) { |
| 146 | |
| 147 | QModelIndex index = model()->index(row, column: 1, parent: rootIndex()); |
| 148 | if (model()->data(index).toDouble() > 0.0) { |
| 149 | |
| 150 | if (listItem == validRow) |
| 151 | return model()->index(row, column: 0, parent: rootIndex()); |
| 152 | |
| 153 | // Update the list index that corresponds to the next valid row. |
| 154 | ++validRow; |
| 155 | } |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | return QModelIndex(); |
| 160 | } |
| 161 | |
| 162 | bool PieView::isIndexHidden(const QModelIndex & /*index*/) const |
| 163 | { |
| 164 | return false; |
| 165 | } |
| 166 | |
| 167 | /* |
| 168 | Returns the rectangle of the item at position \a index in the |
| 169 | model. The rectangle is in contents coordinates. |
| 170 | */ |
| 171 | |
| 172 | QRect PieView::itemRect(const QModelIndex &index) const |
| 173 | { |
| 174 | if (!index.isValid()) |
| 175 | return QRect(); |
| 176 | |
| 177 | // Check whether the index's row is in the list of rows represented |
| 178 | // by slices. |
| 179 | QModelIndex valueIndex; |
| 180 | |
| 181 | if (index.column() != 1) |
| 182 | valueIndex = model()->index(row: index.row(), column: 1, parent: rootIndex()); |
| 183 | else |
| 184 | valueIndex = index; |
| 185 | |
| 186 | if (model()->data(index: valueIndex).toDouble() <= 0.0) |
| 187 | return QRect(); |
| 188 | |
| 189 | int listItem = 0; |
| 190 | for (int row = index.row()-1; row >= 0; --row) { |
| 191 | if (model()->data(index: model()->index(row, column: 1, parent: rootIndex())).toDouble() > 0.0) |
| 192 | listItem++; |
| 193 | } |
| 194 | |
| 195 | switch (index.column()) { |
| 196 | case 0: { |
| 197 | const qreal itemHeight = QFontMetricsF(viewOptions().font).height(); |
| 198 | |
| 199 | return QRect(totalSize, |
| 200 | qRound(d: margin + listItem * itemHeight), |
| 201 | totalSize - margin, qRound(d: itemHeight)); |
| 202 | } |
| 203 | case 1: |
| 204 | return viewport()->rect(); |
| 205 | } |
| 206 | return QRect(); |
| 207 | } |
| 208 | |
| 209 | QRegion PieView::itemRegion(const QModelIndex &index) const |
| 210 | { |
| 211 | if (!index.isValid()) |
| 212 | return QRegion(); |
| 213 | |
| 214 | if (index.column() != 1) |
| 215 | return itemRect(index); |
| 216 | |
| 217 | if (model()->data(index).toDouble() <= 0.0) |
| 218 | return QRegion(); |
| 219 | |
| 220 | double startAngle = 0.0; |
| 221 | for (int row = 0; row < model()->rowCount(parent: rootIndex()); ++row) { |
| 222 | |
| 223 | QModelIndex sliceIndex = model()->index(row, column: 1, parent: rootIndex()); |
| 224 | double value = model()->data(index: sliceIndex).toDouble(); |
| 225 | |
| 226 | if (value > 0.0) { |
| 227 | double angle = 360 * value / totalValue; |
| 228 | |
| 229 | if (sliceIndex == index) { |
| 230 | QPainterPath slicePath; |
| 231 | slicePath.moveTo(x: totalSize / 2, y: totalSize / 2); |
| 232 | slicePath.arcTo(x: margin, y: margin, w: margin + pieSize, h: margin + pieSize, |
| 233 | startAngle, arcLength: angle); |
| 234 | slicePath.closeSubpath(); |
| 235 | |
| 236 | return QRegion(slicePath.toFillPolygon().toPolygon()); |
| 237 | } |
| 238 | |
| 239 | startAngle += angle; |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | return QRegion(); |
| 244 | } |
| 245 | |
| 246 | int PieView::horizontalOffset() const |
| 247 | { |
| 248 | return horizontalScrollBar()->value(); |
| 249 | } |
| 250 | |
| 251 | void PieView::mousePressEvent(QMouseEvent *event) |
| 252 | { |
| 253 | QAbstractItemView::mousePressEvent(event); |
| 254 | origin = event->pos(); |
| 255 | if (!rubberBand) |
| 256 | rubberBand = new QRubberBand(QRubberBand::Rectangle, viewport()); |
| 257 | rubberBand->setGeometry(QRect(origin, QSize())); |
| 258 | rubberBand->show(); |
| 259 | } |
| 260 | |
| 261 | void PieView::mouseMoveEvent(QMouseEvent *event) |
| 262 | { |
| 263 | if (rubberBand) |
| 264 | rubberBand->setGeometry(QRect(origin, event->pos()).normalized()); |
| 265 | QAbstractItemView::mouseMoveEvent(event); |
| 266 | } |
| 267 | |
| 268 | void PieView::mouseReleaseEvent(QMouseEvent *event) |
| 269 | { |
| 270 | QAbstractItemView::mouseReleaseEvent(event); |
| 271 | if (rubberBand) |
| 272 | rubberBand->hide(); |
| 273 | viewport()->update(); |
| 274 | } |
| 275 | |
| 276 | QModelIndex PieView::moveCursor(QAbstractItemView::CursorAction cursorAction, |
| 277 | Qt::KeyboardModifiers /*modifiers*/) |
| 278 | { |
| 279 | QModelIndex current = currentIndex(); |
| 280 | |
| 281 | switch (cursorAction) { |
| 282 | case MoveLeft: |
| 283 | case MoveUp: |
| 284 | if (current.row() > 0) |
| 285 | current = model()->index(row: current.row() - 1, column: current.column(), |
| 286 | parent: rootIndex()); |
| 287 | else |
| 288 | current = model()->index(row: 0, column: current.column(), parent: rootIndex()); |
| 289 | break; |
| 290 | case MoveRight: |
| 291 | case MoveDown: |
| 292 | if (current.row() < rows(index: current) - 1) |
| 293 | current = model()->index(row: current.row() + 1, column: current.column(), |
| 294 | parent: rootIndex()); |
| 295 | else |
| 296 | current = model()->index(row: rows(index: current) - 1, column: current.column(), |
| 297 | parent: rootIndex()); |
| 298 | break; |
| 299 | default: |
| 300 | break; |
| 301 | } |
| 302 | |
| 303 | viewport()->update(); |
| 304 | return current; |
| 305 | } |
| 306 | |
| 307 | void PieView::paintEvent(QPaintEvent *event) |
| 308 | { |
| 309 | QItemSelectionModel *selections = selectionModel(); |
| 310 | QStyleOptionViewItem option = viewOptions(); |
| 311 | |
| 312 | QBrush background = option.palette.base(); |
| 313 | QPen foreground(option.palette.color(cr: QPalette::WindowText)); |
| 314 | |
| 315 | QPainter painter(viewport()); |
| 316 | painter.setRenderHint(hint: QPainter::Antialiasing); |
| 317 | |
| 318 | painter.fillRect(event->rect(), background); |
| 319 | painter.setPen(foreground); |
| 320 | |
| 321 | // Viewport rectangles |
| 322 | QRect pieRect = QRect(margin, margin, pieSize, pieSize); |
| 323 | |
| 324 | if (validItems <= 0) |
| 325 | return; |
| 326 | |
| 327 | painter.save(); |
| 328 | painter.translate(dx: pieRect.x() - horizontalScrollBar()->value(), |
| 329 | dy: pieRect.y() - verticalScrollBar()->value()); |
| 330 | painter.drawEllipse(x: 0, y: 0, w: pieSize, h: pieSize); |
| 331 | double startAngle = 0.0; |
| 332 | int row; |
| 333 | |
| 334 | for (row = 0; row < model()->rowCount(parent: rootIndex()); ++row) { |
| 335 | QModelIndex index = model()->index(row, column: 1, parent: rootIndex()); |
| 336 | double value = model()->data(index).toDouble(); |
| 337 | |
| 338 | if (value > 0.0) { |
| 339 | double angle = 360 * value / totalValue; |
| 340 | |
| 341 | QModelIndex colorIndex = model()->index(row, column: 0, parent: rootIndex()); |
| 342 | QColor color = QColor(model()->data(index: colorIndex, role: Qt::DecorationRole).toString()); |
| 343 | |
| 344 | if (currentIndex() == index) |
| 345 | painter.setBrush(QBrush(color, Qt::Dense4Pattern)); |
| 346 | else if (selections->isSelected(index)) |
| 347 | painter.setBrush(QBrush(color, Qt::Dense3Pattern)); |
| 348 | else |
| 349 | painter.setBrush(QBrush(color)); |
| 350 | |
| 351 | painter.drawPie(x: 0, y: 0, w: pieSize, h: pieSize, a: int(startAngle*16), alen: int(angle*16)); |
| 352 | |
| 353 | startAngle += angle; |
| 354 | } |
| 355 | } |
| 356 | painter.restore(); |
| 357 | |
| 358 | int keyNumber = 0; |
| 359 | |
| 360 | for (row = 0; row < model()->rowCount(parent: rootIndex()); ++row) { |
| 361 | QModelIndex index = model()->index(row, column: 1, parent: rootIndex()); |
| 362 | double value = model()->data(index).toDouble(); |
| 363 | |
| 364 | if (value > 0.0) { |
| 365 | QModelIndex labelIndex = model()->index(row, column: 0, parent: rootIndex()); |
| 366 | |
| 367 | QStyleOptionViewItem option = viewOptions(); |
| 368 | option.rect = visualRect(index: labelIndex); |
| 369 | if (selections->isSelected(index: labelIndex)) |
| 370 | option.state |= QStyle::State_Selected; |
| 371 | if (currentIndex() == labelIndex) |
| 372 | option.state |= QStyle::State_HasFocus; |
| 373 | itemDelegate()->paint(painter: &painter, option, index: labelIndex); |
| 374 | |
| 375 | ++keyNumber; |
| 376 | } |
| 377 | } |
| 378 | } |
| 379 | |
| 380 | void PieView::resizeEvent(QResizeEvent * /* event */) |
| 381 | { |
| 382 | updateGeometries(); |
| 383 | } |
| 384 | |
| 385 | int PieView::rows(const QModelIndex &index) const |
| 386 | { |
| 387 | return model()->rowCount(parent: model()->parent(child: index)); |
| 388 | } |
| 389 | |
| 390 | void PieView::rowsInserted(const QModelIndex &parent, int start, int end) |
| 391 | { |
| 392 | for (int row = start; row <= end; ++row) { |
| 393 | QModelIndex index = model()->index(row, column: 1, parent: rootIndex()); |
| 394 | double value = model()->data(index).toDouble(); |
| 395 | |
| 396 | if (value > 0.0) { |
| 397 | totalValue += value; |
| 398 | ++validItems; |
| 399 | } |
| 400 | } |
| 401 | |
| 402 | QAbstractItemView::rowsInserted(parent, start, end); |
| 403 | } |
| 404 | |
| 405 | void PieView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) |
| 406 | { |
| 407 | for (int row = start; row <= end; ++row) { |
| 408 | QModelIndex index = model()->index(row, column: 1, parent: rootIndex()); |
| 409 | double value = model()->data(index).toDouble(); |
| 410 | if (value > 0.0) { |
| 411 | totalValue -= value; |
| 412 | --validItems; |
| 413 | } |
| 414 | } |
| 415 | |
| 416 | QAbstractItemView::rowsAboutToBeRemoved(parent, start, end); |
| 417 | } |
| 418 | |
| 419 | void PieView::scrollContentsBy(int dx, int dy) |
| 420 | { |
| 421 | viewport()->scroll(dx, dy); |
| 422 | } |
| 423 | |
| 424 | void PieView::scrollTo(const QModelIndex &index, ScrollHint) |
| 425 | { |
| 426 | QRect area = viewport()->rect(); |
| 427 | QRect rect = visualRect(index); |
| 428 | |
| 429 | if (rect.left() < area.left()) { |
| 430 | horizontalScrollBar()->setValue( |
| 431 | horizontalScrollBar()->value() + rect.left() - area.left()); |
| 432 | } else if (rect.right() > area.right()) { |
| 433 | horizontalScrollBar()->setValue( |
| 434 | horizontalScrollBar()->value() + qMin( |
| 435 | a: rect.right() - area.right(), b: rect.left() - area.left())); |
| 436 | } |
| 437 | |
| 438 | if (rect.top() < area.top()) { |
| 439 | verticalScrollBar()->setValue( |
| 440 | verticalScrollBar()->value() + rect.top() - area.top()); |
| 441 | } else if (rect.bottom() > area.bottom()) { |
| 442 | verticalScrollBar()->setValue( |
| 443 | verticalScrollBar()->value() + qMin( |
| 444 | a: rect.bottom() - area.bottom(), b: rect.top() - area.top())); |
| 445 | } |
| 446 | |
| 447 | update(); |
| 448 | } |
| 449 | |
| 450 | /* |
| 451 | Find the indices corresponding to the extent of the selection. |
| 452 | */ |
| 453 | |
| 454 | void PieView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) |
| 455 | { |
| 456 | // Use content widget coordinates because we will use the itemRegion() |
| 457 | // function to check for intersections. |
| 458 | |
| 459 | QRect contentsRect = rect.translated( |
| 460 | dx: horizontalScrollBar()->value(), |
| 461 | dy: verticalScrollBar()->value()).normalized(); |
| 462 | |
| 463 | int rows = model()->rowCount(parent: rootIndex()); |
| 464 | int columns = model()->columnCount(parent: rootIndex()); |
| 465 | QModelIndexList indexes; |
| 466 | |
| 467 | for (int row = 0; row < rows; ++row) { |
| 468 | for (int column = 0; column < columns; ++column) { |
| 469 | QModelIndex index = model()->index(row, column, parent: rootIndex()); |
| 470 | QRegion region = itemRegion(index); |
| 471 | if (region.intersects(r: contentsRect)) |
| 472 | indexes.append(t: index); |
| 473 | } |
| 474 | } |
| 475 | |
| 476 | if (indexes.size() > 0) { |
| 477 | int firstRow = indexes.at(i: 0).row(); |
| 478 | int lastRow = firstRow; |
| 479 | int firstColumn = indexes.at(i: 0).column(); |
| 480 | int lastColumn = firstColumn; |
| 481 | |
| 482 | for (int i = 1; i < indexes.size(); ++i) { |
| 483 | firstRow = qMin(a: firstRow, b: indexes.at(i).row()); |
| 484 | lastRow = qMax(a: lastRow, b: indexes.at(i).row()); |
| 485 | firstColumn = qMin(a: firstColumn, b: indexes.at(i).column()); |
| 486 | lastColumn = qMax(a: lastColumn, b: indexes.at(i).column()); |
| 487 | } |
| 488 | |
| 489 | QItemSelection selection( |
| 490 | model()->index(row: firstRow, column: firstColumn, parent: rootIndex()), |
| 491 | model()->index(row: lastRow, column: lastColumn, parent: rootIndex())); |
| 492 | selectionModel()->select(selection, command); |
| 493 | } else { |
| 494 | QModelIndex noIndex; |
| 495 | QItemSelection selection(noIndex, noIndex); |
| 496 | selectionModel()->select(selection, command); |
| 497 | } |
| 498 | |
| 499 | update(); |
| 500 | } |
| 501 | |
| 502 | void PieView::updateGeometries() |
| 503 | { |
| 504 | horizontalScrollBar()->setPageStep(viewport()->width()); |
| 505 | horizontalScrollBar()->setRange(min: 0, max: qMax(a: 0, b: 2 * totalSize - viewport()->width())); |
| 506 | verticalScrollBar()->setPageStep(viewport()->height()); |
| 507 | verticalScrollBar()->setRange(min: 0, max: qMax(a: 0, b: totalSize - viewport()->height())); |
| 508 | } |
| 509 | |
| 510 | int PieView::verticalOffset() const |
| 511 | { |
| 512 | return verticalScrollBar()->value(); |
| 513 | } |
| 514 | |
| 515 | /* |
| 516 | Returns the position of the item in viewport coordinates. |
| 517 | */ |
| 518 | |
| 519 | QRect PieView::visualRect(const QModelIndex &index) const |
| 520 | { |
| 521 | QRect rect = itemRect(index); |
| 522 | if (!rect.isValid()) |
| 523 | return rect; |
| 524 | |
| 525 | return QRect(rect.left() - horizontalScrollBar()->value(), |
| 526 | rect.top() - verticalScrollBar()->value(), |
| 527 | rect.width(), rect.height()); |
| 528 | } |
| 529 | |
| 530 | /* |
| 531 | Returns a region corresponding to the selection in viewport coordinates. |
| 532 | */ |
| 533 | |
| 534 | QRegion PieView::visualRegionForSelection(const QItemSelection &selection) const |
| 535 | { |
| 536 | int ranges = selection.count(); |
| 537 | |
| 538 | if (ranges == 0) |
| 539 | return QRect(); |
| 540 | |
| 541 | QRegion region; |
| 542 | for (int i = 0; i < ranges; ++i) { |
| 543 | const QItemSelectionRange &range = selection.at(i); |
| 544 | for (int row = range.top(); row <= range.bottom(); ++row) { |
| 545 | for (int col = range.left(); col <= range.right(); ++col) { |
| 546 | QModelIndex index = model()->index(row, column: col, parent: rootIndex()); |
| 547 | region += visualRect(index); |
| 548 | } |
| 549 | } |
| 550 | } |
| 551 | return region; |
| 552 | } |
| 553 | |