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