File indexing completed on 2024-04-28 03:59:10
0001 /* 0002 This file is part of the KDE Libraries 0003 SPDX-FileCopyrightText: 2006 Tobias Koenig <tokoe@kde.org> 0004 SPDX-FileCopyrightText: 2007 Rafael Fernández López <ereslibre@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "kpageview_p.h" 0010 0011 #include <QApplication> 0012 #include <QHeaderView> 0013 #include <QPainter> 0014 #include <QScrollBar> 0015 #include <QTextLayout> 0016 #include <QVBoxLayout> 0017 0018 #include "kpagemodel.h" 0019 #include "loggingcategory.h" 0020 0021 constexpr const auto viewWidth = 300; 0022 0023 using namespace KDEPrivate; 0024 0025 // KPagePlainView 0026 0027 KPagePlainView::KPagePlainView(QWidget *parent) 0028 : QAbstractItemView(parent) 0029 { 0030 hide(); 0031 } 0032 0033 QModelIndex KPagePlainView::indexAt(const QPoint &) const 0034 { 0035 return QModelIndex(); 0036 } 0037 0038 void KPagePlainView::scrollTo(const QModelIndex &, ScrollHint) 0039 { 0040 } 0041 0042 QRect KPagePlainView::visualRect(const QModelIndex &) const 0043 { 0044 return QRect(); 0045 } 0046 0047 QModelIndex KPagePlainView::moveCursor(QAbstractItemView::CursorAction, Qt::KeyboardModifiers) 0048 { 0049 return QModelIndex(); 0050 } 0051 0052 int KPagePlainView::horizontalOffset() const 0053 { 0054 return 0; 0055 } 0056 0057 int KPagePlainView::verticalOffset() const 0058 { 0059 return 0; 0060 } 0061 0062 bool KPagePlainView::isIndexHidden(const QModelIndex &) const 0063 { 0064 return false; 0065 } 0066 0067 void KPagePlainView::setSelection(const QRect &, QFlags<QItemSelectionModel::SelectionFlag>) 0068 { 0069 } 0070 0071 QRegion KPagePlainView::visualRegionForSelection(const QItemSelection &) const 0072 { 0073 return QRegion(); 0074 } 0075 0076 // KPageListView 0077 0078 KPageListView::KPageListView(QWidget *parent) 0079 : QListView(parent) 0080 { 0081 if (layoutDirection() == Qt::RightToLeft) { 0082 setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::LeftEdge})); 0083 } else { 0084 setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::RightEdge})); 0085 } 0086 setViewMode(QListView::ListMode); 0087 setMovement(QListView::Static); 0088 setVerticalScrollMode(QListView::ScrollPerPixel); 0089 0090 QFont boldFont(font()); 0091 boldFont.setBold(true); 0092 setFont(boldFont); 0093 } 0094 0095 KPageListView::~KPageListView() 0096 { 0097 } 0098 0099 void KPageListView::setModel(QAbstractItemModel *model) 0100 { 0101 connect(model, &QAbstractItemModel::layoutChanged, this, &KPageListView::updateWidth); 0102 0103 QListView::setModel(model); 0104 0105 // Set our own selection model, which won't allow our current selection to be cleared 0106 setSelectionModel(new KDEPrivate::SelectionModel(model, this)); 0107 0108 updateWidth(); 0109 } 0110 0111 void KPageListView::changeEvent(QEvent *event) 0112 { 0113 QListView::changeEvent(event); 0114 0115 if (event->type() == QEvent::FontChange) { 0116 updateWidth(); 0117 } 0118 } 0119 0120 void KPageListView::setFlexibleWidth(bool flexibleWidth) 0121 { 0122 m_flexibleWidth = flexibleWidth; 0123 } 0124 0125 void KPageListView::updateWidth() 0126 { 0127 if (!model()) { 0128 return; 0129 } 0130 if (m_flexibleWidth) { 0131 setFixedWidth(sizeHintForColumn(0) + verticalScrollBar()->sizeHint().width() + 5); 0132 } else { 0133 setFixedWidth(viewWidth); 0134 } 0135 } 0136 0137 // KPageTreeView 0138 0139 KPageTreeView::KPageTreeView(QWidget *parent) 0140 : QTreeView(parent) 0141 { 0142 if (layoutDirection() == Qt::RightToLeft) { 0143 setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::LeftEdge})); 0144 } else { 0145 setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::RightEdge})); 0146 } 0147 header()->hide(); 0148 } 0149 0150 void KPageTreeView::setModel(QAbstractItemModel *model) 0151 { 0152 connect(model, &QAbstractItemModel::layoutChanged, this, &KPageTreeView::updateWidth); 0153 0154 QTreeView::setModel(model); 0155 0156 // Set our own selection model, which won't allow our current selection to be cleared 0157 setSelectionModel(new KDEPrivate::SelectionModel(model, this)); 0158 0159 updateWidth(); 0160 } 0161 0162 void KPageTreeView::updateWidth() 0163 { 0164 if (!model()) { 0165 return; 0166 } 0167 0168 int columns = model()->columnCount(); 0169 0170 expandItems(); 0171 0172 int width = 0; 0173 for (int i = 0; i < columns; ++i) { 0174 resizeColumnToContents(i); 0175 width = qMax(width, sizeHintForColumn(i)); 0176 } 0177 0178 setFixedWidth(qMax(viewWidth, width + 25)); 0179 } 0180 0181 void KPageTreeView::expandItems(const QModelIndex &index) 0182 { 0183 setExpanded(index, true); 0184 0185 const int count = model()->rowCount(index); 0186 for (int i = 0; i < count; ++i) { 0187 expandItems(model()->index(i, 0, index)); 0188 } 0189 } 0190 0191 // KPageTabbedView 0192 0193 KPageTabbedView::KPageTabbedView(QWidget *parent) 0194 : QAbstractItemView(parent) 0195 { 0196 // hide the viewport of the QAbstractScrollArea 0197 const QList<QWidget *> list = findChildren<QWidget *>(); 0198 for (int i = 0; i < list.count(); ++i) { 0199 list[i]->hide(); 0200 } 0201 0202 setFrameShape(NoFrame); 0203 0204 QVBoxLayout *layout = new QVBoxLayout(this); 0205 layout->setContentsMargins(0, 0, 0, 0); 0206 0207 mTabWidget = new QTabWidget(this); 0208 mTabWidget->setDocumentMode(true); 0209 connect(mTabWidget, &QTabWidget::currentChanged, this, &KPageTabbedView::currentPageChanged); 0210 0211 layout->addWidget(mTabWidget); 0212 } 0213 0214 KPageTabbedView::~KPageTabbedView() 0215 { 0216 if (model()) { 0217 for (int i = 0; i < mTabWidget->count(); ++i) { 0218 QWidget *page = qvariant_cast<QWidget *>(model()->data(model()->index(i, 0), KPageModel::WidgetRole)); 0219 0220 if (page) { 0221 page->setVisible(false); 0222 page->setParent(nullptr); // reparent our children before they are deleted 0223 } 0224 } 0225 } 0226 } 0227 0228 void KPageTabbedView::setModel(QAbstractItemModel *model) 0229 { 0230 QAbstractItemView::setModel(model); 0231 0232 connect(model, &QAbstractItemModel::layoutChanged, this, &KPageTabbedView::layoutChanged); 0233 0234 layoutChanged(); 0235 } 0236 0237 QModelIndex KPageTabbedView::indexAt(const QPoint &) const 0238 { 0239 if (model()) { 0240 return model()->index(0, 0); 0241 } else { 0242 return QModelIndex(); 0243 } 0244 } 0245 0246 void KPageTabbedView::scrollTo(const QModelIndex &index, ScrollHint) 0247 { 0248 if (!index.isValid()) { 0249 return; 0250 } 0251 0252 mTabWidget->setCurrentIndex(index.row()); 0253 } 0254 0255 QRect KPageTabbedView::visualRect(const QModelIndex &) const 0256 { 0257 return QRect(); 0258 } 0259 0260 QSize KPageTabbedView::minimumSizeHint() const 0261 { 0262 return mTabWidget->minimumSizeHint(); 0263 } 0264 0265 QModelIndex KPageTabbedView::moveCursor(QAbstractItemView::CursorAction, Qt::KeyboardModifiers) 0266 { 0267 return QModelIndex(); 0268 } 0269 0270 int KPageTabbedView::horizontalOffset() const 0271 { 0272 return 0; 0273 } 0274 0275 int KPageTabbedView::verticalOffset() const 0276 { 0277 return 0; 0278 } 0279 0280 bool KPageTabbedView::isIndexHidden(const QModelIndex &index) const 0281 { 0282 return (mTabWidget->currentIndex() != index.row()); 0283 } 0284 0285 void KPageTabbedView::setSelection(const QRect &, QFlags<QItemSelectionModel::SelectionFlag>) 0286 { 0287 } 0288 0289 QRegion KPageTabbedView::visualRegionForSelection(const QItemSelection &) const 0290 { 0291 return QRegion(); 0292 } 0293 0294 void KPageTabbedView::currentPageChanged(int index) 0295 { 0296 if (!model()) { 0297 return; 0298 } 0299 0300 QModelIndex modelIndex = model()->index(index, 0); 0301 0302 selectionModel()->setCurrentIndex(modelIndex, QItemSelectionModel::ClearAndSelect); 0303 } 0304 0305 void KPageTabbedView::layoutChanged() 0306 { 0307 // save old position 0308 int pos = mTabWidget->currentIndex(); 0309 0310 // clear tab bar 0311 int count = mTabWidget->count(); 0312 for (int i = 0; i < count; ++i) { 0313 mTabWidget->removeTab(0); 0314 } 0315 0316 if (!model()) { 0317 return; 0318 } 0319 0320 // add new tabs 0321 for (int i = 0; i < model()->rowCount(); ++i) { 0322 const QString title = model()->data(model()->index(i, 0)).toString(); 0323 const QIcon icon = model()->data(model()->index(i, 0), Qt::DecorationRole).value<QIcon>(); 0324 QWidget *page = qvariant_cast<QWidget *>(model()->data(model()->index(i, 0), KPageModel::WidgetRole)); 0325 if (page) { 0326 QWidget *widget = new QWidget(this); 0327 QVBoxLayout *layout = new QVBoxLayout(widget); 0328 layout->setContentsMargins({}); 0329 layout->addWidget(page); 0330 page->setVisible(true); 0331 mTabWidget->addTab(widget, icon, title); 0332 } 0333 } 0334 0335 mTabWidget->setCurrentIndex(pos); 0336 } 0337 0338 void KPageTabbedView::dataChanged(const QModelIndex &index, const QModelIndex &, const QList<int> &roles) 0339 { 0340 if (!index.isValid()) { 0341 return; 0342 } 0343 0344 if (index.row() < 0 || index.row() >= mTabWidget->count()) { 0345 return; 0346 } 0347 0348 if (roles.isEmpty() || roles.contains(Qt::DisplayRole) || roles.contains(Qt::DecorationRole)) { 0349 const QString title = model()->data(index).toString(); 0350 const QIcon icon = model()->data(index, Qt::DecorationRole).value<QIcon>(); 0351 0352 mTabWidget->setTabText(index.row(), title); 0353 mTabWidget->setTabIcon(index.row(), icon); 0354 } 0355 } 0356 0357 // KPageListViewDelegate 0358 0359 KPageListViewDelegate::KPageListViewDelegate(QObject *parent) 0360 : QAbstractItemDelegate(parent) 0361 { 0362 } 0363 0364 static int layoutText(QTextLayout *layout, int maxWidth) 0365 { 0366 qreal height = 0; 0367 int textWidth = 0; 0368 layout->beginLayout(); 0369 while (true) { 0370 QTextLine line = layout->createLine(); 0371 if (!line.isValid()) { 0372 break; 0373 } 0374 line.setLineWidth(maxWidth); 0375 line.setPosition(QPointF(0, height)); 0376 height += line.height(); 0377 textWidth = qMax(textWidth, qRound(line.naturalTextWidth() + 0.5)); 0378 } 0379 layout->endLayout(); 0380 return textWidth; 0381 } 0382 0383 void KPageListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 0384 { 0385 if (!index.isValid()) { 0386 return; 0387 } 0388 0389 QStyleOptionViewItem opt(option); 0390 opt.showDecorationSelected = true; 0391 QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); 0392 0393 const QIcon::Mode iconMode = (option.state & QStyle::State_Selected) && (option.state & QStyle::State_Active) ? QIcon::Selected : QIcon::Normal; 0394 int iconSize = style->pixelMetric(QStyle::PM_IconViewIconSize); 0395 const QString text = index.model()->data(index, Qt::DisplayRole).toString(); 0396 const QIcon icon = index.model()->data(index, Qt::DecorationRole).value<QIcon>(); 0397 const QPixmap pixmap = icon.pixmap(iconSize, iconSize, iconMode); 0398 0399 QFontMetrics fm = painter->fontMetrics(); 0400 int wp = pixmap.width() / pixmap.devicePixelRatio(); 0401 int hp = pixmap.height() / pixmap.devicePixelRatio(); 0402 0403 QTextLayout iconTextLayout(text, option.font); 0404 QTextOption textOption(Qt::AlignHCenter); 0405 iconTextLayout.setTextOption(textOption); 0406 int maxWidth = qMax(3 * wp, 8 * fm.height()); 0407 layoutText(&iconTextLayout, maxWidth); 0408 0409 QPen pen = painter->pen(); 0410 QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; 0411 if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) { 0412 cg = QPalette::Inactive; 0413 } 0414 0415 style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); 0416 if (option.state & QStyle::State_Selected) { 0417 painter->setPen(option.palette.color(cg, QPalette::HighlightedText)); 0418 } else { 0419 painter->setPen(option.palette.color(cg, QPalette::Text)); 0420 } 0421 0422 painter->drawPixmap(option.rect.x() + (option.rect.width() / 2) - (wp / 2), option.rect.y() + 5, pixmap); 0423 if (!text.isEmpty()) { 0424 iconTextLayout.draw(painter, QPoint(option.rect.x() + (option.rect.width() / 2) - (maxWidth / 2), option.rect.y() + hp + 7)); 0425 } 0426 0427 painter->setPen(pen); 0428 0429 drawFocus(painter, option, option.rect); 0430 } 0431 0432 QSize KPageListViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const 0433 { 0434 if (!index.isValid()) { 0435 return QSize(0, 0); 0436 } 0437 0438 QStyleOptionViewItem opt(option); 0439 opt.showDecorationSelected = true; 0440 QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); 0441 0442 int iconSize = style->pixelMetric(QStyle::PM_IconViewIconSize); 0443 const QString text = index.model()->data(index, Qt::DisplayRole).toString(); 0444 const QIcon icon = index.model()->data(index, Qt::DecorationRole).value<QIcon>(); 0445 const QPixmap pixmap = icon.pixmap(iconSize, iconSize); 0446 0447 QFontMetrics fm = option.fontMetrics; 0448 int gap = fm.height(); 0449 int wp = pixmap.width() / pixmap.devicePixelRatio(); 0450 int hp = pixmap.height() / pixmap.devicePixelRatio(); 0451 0452 if (hp == 0) { 0453 // No pixmap loaded yet, we'll use the default icon size in this case. 0454 hp = iconSize; 0455 wp = iconSize; 0456 } 0457 0458 QTextLayout iconTextLayout(text, option.font); 0459 int wt = layoutText(&iconTextLayout, qMax(3 * wp, 8 * fm.height())); 0460 int ht = iconTextLayout.boundingRect().height(); 0461 0462 int width; 0463 int height; 0464 if (text.isEmpty()) { 0465 height = hp; 0466 } else { 0467 height = hp + ht + 10; 0468 } 0469 0470 width = qMax(wt, wp) + gap; 0471 0472 return QSize(width, height); 0473 } 0474 0475 void KPageListViewDelegate::drawFocus(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) const 0476 { 0477 if (option.state & QStyle::State_HasFocus) { 0478 QStyleOptionFocusRect o; 0479 o.QStyleOption::operator=(option); 0480 o.rect = rect; 0481 o.state |= QStyle::State_KeyboardFocusChange; 0482 QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled; 0483 o.backgroundColor = option.palette.color(cg, (option.state & QStyle::State_Selected) ? QPalette::Highlight : QPalette::Window); 0484 0485 QStyle *style = option.widget ? option.widget->style() : QApplication::style(); 0486 style->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, option.widget); 0487 } 0488 } 0489 0490 // KPageListViewProxy 0491 0492 KPageListViewProxy::KPageListViewProxy(QObject *parent) 0493 : QAbstractProxyModel(parent) 0494 { 0495 } 0496 0497 KPageListViewProxy::~KPageListViewProxy() 0498 { 0499 } 0500 0501 int KPageListViewProxy::rowCount(const QModelIndex &) const 0502 { 0503 return mList.count(); 0504 } 0505 0506 int KPageListViewProxy::columnCount(const QModelIndex &) const 0507 { 0508 return 1; 0509 } 0510 0511 QModelIndex KPageListViewProxy::index(int row, int column, const QModelIndex &) const 0512 { 0513 if (column > 1 || row >= mList.count()) { 0514 return QModelIndex(); 0515 } else { 0516 return createIndex(row, column, mList[row].internalPointer()); 0517 } 0518 } 0519 0520 QModelIndex KPageListViewProxy::parent(const QModelIndex &) const 0521 { 0522 return QModelIndex(); 0523 } 0524 0525 QVariant KPageListViewProxy::data(const QModelIndex &index, int role) const 0526 { 0527 if (!index.isValid()) { 0528 return QVariant(); 0529 } 0530 0531 if (index.row() >= mList.count()) { 0532 return QVariant(); 0533 } 0534 0535 return sourceModel()->data(mList[index.row()], role); 0536 } 0537 0538 QModelIndex KPageListViewProxy::mapFromSource(const QModelIndex &index) const 0539 { 0540 if (!index.isValid()) { 0541 return QModelIndex(); 0542 } 0543 0544 for (int i = 0; i < mList.count(); ++i) { 0545 if (mList[i] == index) { 0546 return createIndex(i, 0, index.internalPointer()); 0547 } 0548 } 0549 0550 return QModelIndex(); 0551 } 0552 0553 QModelIndex KPageListViewProxy::mapToSource(const QModelIndex &index) const 0554 { 0555 if (!index.isValid()) { 0556 return QModelIndex(); 0557 } 0558 0559 return mList[index.row()]; 0560 } 0561 0562 void KPageListViewProxy::rebuildMap() 0563 { 0564 mList.clear(); 0565 0566 const QAbstractItemModel *model = sourceModel(); 0567 if (!model) { 0568 return; 0569 } 0570 0571 for (int i = 0; i < model->rowCount(); ++i) { 0572 addMapEntry(model->index(i, 0)); 0573 } 0574 0575 for (int i = 0; i < mList.count(); ++i) { 0576 qCDebug(KWidgetsAddonsLog, "%d:0 -> %d:%d", i, mList[i].row(), mList[i].column()); 0577 } 0578 0579 Q_EMIT layoutChanged(); 0580 } 0581 0582 void KPageListViewProxy::addMapEntry(const QModelIndex &index) 0583 { 0584 if (sourceModel()->rowCount(index) == 0) { 0585 mList.append(index); 0586 } else { 0587 const int count = sourceModel()->rowCount(index); 0588 for (int i = 0; i < count; ++i) { 0589 addMapEntry(sourceModel()->index(i, 0, index)); 0590 } 0591 } 0592 } 0593 0594 SelectionModel::SelectionModel(QAbstractItemModel *model, QObject *parent) 0595 : QItemSelectionModel(model, parent) 0596 { 0597 } 0598 0599 void SelectionModel::clear() 0600 { 0601 // Don't allow the current selection to be cleared 0602 } 0603 0604 void SelectionModel::select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) 0605 { 0606 // Don't allow the current selection to be cleared 0607 if (!index.isValid() && (command & QItemSelectionModel::Clear)) { 0608 return; 0609 } 0610 QItemSelectionModel::select(index, command); 0611 } 0612 0613 void SelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) 0614 { 0615 // Don't allow the current selection to be cleared 0616 if (!selection.count() && (command & QItemSelectionModel::Clear)) { 0617 return; 0618 } 0619 QItemSelectionModel::select(selection, command); 0620 } 0621 0622 #include "moc_kpageview_p.cpp"