File indexing completed on 2024-04-28 15:32:09

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"