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"