/* This file is part of the KDE Libraries SPDX-FileCopyrightText: 2006 Tobias Koenig SPDX-FileCopyrightText: 2007 Rafael Fernández López SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kpageview_p.h" #include #include #include #include #include #include #include "kpagemodel.h" #include "loggingcategory.h" constexpr const auto viewWidth = 300; using namespace KDEPrivate; // KPagePlainView KPagePlainView::KPagePlainView(QWidget *parent) : QAbstractItemView(parent) { hide(); } QModelIndex KPagePlainView::indexAt(const QPoint &) const { return QModelIndex(); } void KPagePlainView::scrollTo(const QModelIndex &, ScrollHint) { } QRect KPagePlainView::visualRect(const QModelIndex &) const { return QRect(); } QModelIndex KPagePlainView::moveCursor(QAbstractItemView::CursorAction, Qt::KeyboardModifiers) { return QModelIndex(); } int KPagePlainView::horizontalOffset() const { return 0; } int KPagePlainView::verticalOffset() const { return 0; } bool KPagePlainView::isIndexHidden(const QModelIndex &) const { return false; } void KPagePlainView::setSelection(const QRect &, QFlags) { } QRegion KPagePlainView::visualRegionForSelection(const QItemSelection &) const { return QRegion(); } // KPageListView KPageListView::KPageListView(QWidget *parent) : QListView(parent) { if (layoutDirection() == Qt::RightToLeft) { setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::LeftEdge})); } else { setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::RightEdge})); } setViewMode(QListView::ListMode); setMovement(QListView::Static); setVerticalScrollMode(QListView::ScrollPerPixel); QFont boldFont(font()); boldFont.setBold(true); setFont(boldFont); } KPageListView::~KPageListView() { } void KPageListView::setModel(QAbstractItemModel *model) { connect(model, &QAbstractItemModel::layoutChanged, this, &KPageListView::updateWidth); QListView::setModel(model); // Set our own selection model, which won't allow our current selection to be cleared setSelectionModel(new KDEPrivate::SelectionModel(model, this)); updateWidth(); } void KPageListView::changeEvent(QEvent *event) { QListView::changeEvent(event); if (event->type() == QEvent::FontChange) { updateWidth(); } } void KPageListView::setFlexibleWidth(bool flexibleWidth) { m_flexibleWidth = flexibleWidth; } void KPageListView::updateWidth() { if (!model()) { return; } if (m_flexibleWidth) { setFixedWidth(sizeHintForColumn(0) + verticalScrollBar()->sizeHint().width() + 5); } else { setFixedWidth(viewWidth); } } // KPageTreeView KPageTreeView::KPageTreeView(QWidget *parent) : QTreeView(parent) { if (layoutDirection() == Qt::RightToLeft) { setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::LeftEdge})); } else { setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::RightEdge})); } header()->hide(); } void KPageTreeView::setModel(QAbstractItemModel *model) { connect(model, &QAbstractItemModel::layoutChanged, this, &KPageTreeView::updateWidth); QTreeView::setModel(model); // Set our own selection model, which won't allow our current selection to be cleared setSelectionModel(new KDEPrivate::SelectionModel(model, this)); updateWidth(); } void KPageTreeView::updateWidth() { if (!model()) { return; } int columns = model()->columnCount(); expandItems(); int width = 0; for (int i = 0; i < columns; ++i) { resizeColumnToContents(i); width = qMax(width, sizeHintForColumn(i)); } setFixedWidth(qMax(viewWidth, width + 25)); } void KPageTreeView::expandItems(const QModelIndex &index) { setExpanded(index, true); const int count = model()->rowCount(index); for (int i = 0; i < count; ++i) { expandItems(model()->index(i, 0, index)); } } // KPageTabbedView KPageTabbedView::KPageTabbedView(QWidget *parent) : QAbstractItemView(parent) { // hide the viewport of the QAbstractScrollArea const QList list = findChildren(); for (int i = 0; i < list.count(); ++i) { list[i]->hide(); } setFrameShape(NoFrame); QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); mTabWidget = new QTabWidget(this); mTabWidget->setDocumentMode(true); mTabWidget->tabBar()->setExpanding(true); connect(mTabWidget, &QTabWidget::currentChanged, this, &KPageTabbedView::currentPageChanged); layout->addWidget(mTabWidget); } KPageTabbedView::~KPageTabbedView() { if (model()) { for (int i = 0; i < mTabWidget->count(); ++i) { QWidget *page = qvariant_cast(model()->data(model()->index(i, 0), KPageModel::WidgetRole)); if (page) { page->setVisible(false); page->setParent(nullptr); // reparent our children before they are deleted } } } } void KPageTabbedView::setModel(QAbstractItemModel *model) { QAbstractItemView::setModel(model); connect(model, &QAbstractItemModel::layoutChanged, this, &KPageTabbedView::layoutChanged); layoutChanged(); } QModelIndex KPageTabbedView::indexAt(const QPoint &) const { if (model()) { return model()->index(0, 0); } else { return QModelIndex(); } } void KPageTabbedView::scrollTo(const QModelIndex &index, ScrollHint) { if (!index.isValid()) { return; } mTabWidget->setCurrentIndex(index.row()); } QRect KPageTabbedView::visualRect(const QModelIndex &) const { return QRect(); } QSize KPageTabbedView::minimumSizeHint() const { return mTabWidget->minimumSizeHint(); } QModelIndex KPageTabbedView::moveCursor(QAbstractItemView::CursorAction, Qt::KeyboardModifiers) { return QModelIndex(); } int KPageTabbedView::horizontalOffset() const { return 0; } int KPageTabbedView::verticalOffset() const { return 0; } bool KPageTabbedView::isIndexHidden(const QModelIndex &index) const { return (mTabWidget->currentIndex() != index.row()); } void KPageTabbedView::setSelection(const QRect &, QFlags) { } QRegion KPageTabbedView::visualRegionForSelection(const QItemSelection &) const { return QRegion(); } void KPageTabbedView::currentPageChanged(int index) { if (!model()) { return; } QModelIndex modelIndex = model()->index(index, 0); selectionModel()->setCurrentIndex(modelIndex, QItemSelectionModel::ClearAndSelect); } void KPageTabbedView::layoutChanged() { // save old position int pos = mTabWidget->currentIndex(); // clear tab bar int count = mTabWidget->count(); for (int i = 0; i < count; ++i) { mTabWidget->removeTab(0); } if (!model()) { return; } // add new tabs for (int i = 0; i < model()->rowCount(); ++i) { const QString title = model()->data(model()->index(i, 0)).toString(); const QIcon icon = model()->data(model()->index(i, 0), Qt::DecorationRole).value(); QWidget *page = qvariant_cast(model()->data(model()->index(i, 0), KPageModel::WidgetRole)); if (page) { QWidget *widget = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(widget); layout->setContentsMargins({}); layout->addWidget(page); page->setVisible(true); mTabWidget->addTab(widget, icon, title); } } mTabWidget->setCurrentIndex(pos); } void KPageTabbedView::dataChanged(const QModelIndex &index, const QModelIndex &, const QList &roles) { if (!index.isValid()) { return; } if (index.row() < 0 || index.row() >= mTabWidget->count()) { return; } if (roles.isEmpty() || roles.contains(Qt::DisplayRole) || roles.contains(Qt::DecorationRole)) { const QString title = model()->data(index).toString(); const QIcon icon = model()->data(index, Qt::DecorationRole).value(); mTabWidget->setTabText(index.row(), title); mTabWidget->setTabIcon(index.row(), icon); } } // KPageListViewDelegate KPageListViewDelegate::KPageListViewDelegate(QObject *parent) : QAbstractItemDelegate(parent) { } static int layoutText(QTextLayout *layout, int maxWidth) { qreal height = 0; int textWidth = 0; layout->beginLayout(); while (true) { QTextLine line = layout->createLine(); if (!line.isValid()) { break; } line.setLineWidth(maxWidth); line.setPosition(QPointF(0, height)); height += line.height(); textWidth = qMax(textWidth, qRound(line.naturalTextWidth() + 0.5)); } layout->endLayout(); return textWidth; } void KPageListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!index.isValid()) { return; } QStyleOptionViewItem opt(option); opt.showDecorationSelected = true; QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); const QIcon::Mode iconMode = (option.state & QStyle::State_Selected) && (option.state & QStyle::State_Active) ? QIcon::Selected : QIcon::Normal; int iconSize = style->pixelMetric(QStyle::PM_IconViewIconSize); const QString text = index.model()->data(index, Qt::DisplayRole).toString(); const QIcon icon = index.model()->data(index, Qt::DecorationRole).value(); const QPixmap pixmap = icon.pixmap(iconSize, iconSize, iconMode); QFontMetrics fm = painter->fontMetrics(); int wp = pixmap.width() / pixmap.devicePixelRatio(); int hp = pixmap.height() / pixmap.devicePixelRatio(); QTextLayout iconTextLayout(text, option.font); QTextOption textOption(Qt::AlignHCenter); iconTextLayout.setTextOption(textOption); int maxWidth = qMax(3 * wp, 8 * fm.height()); layoutText(&iconTextLayout, maxWidth); QPen pen = painter->pen(); QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) { cg = QPalette::Inactive; } style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); if (option.state & QStyle::State_Selected) { painter->setPen(option.palette.color(cg, QPalette::HighlightedText)); } else { painter->setPen(option.palette.color(cg, QPalette::Text)); } painter->drawPixmap(option.rect.x() + (option.rect.width() / 2) - (wp / 2), option.rect.y() + 5, pixmap); if (!text.isEmpty()) { iconTextLayout.draw(painter, QPoint(option.rect.x() + (option.rect.width() / 2) - (maxWidth / 2), option.rect.y() + hp + 7)); } painter->setPen(pen); drawFocus(painter, option, option.rect); } QSize KPageListViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!index.isValid()) { return QSize(0, 0); } QStyleOptionViewItem opt(option); opt.showDecorationSelected = true; QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); int iconSize = style->pixelMetric(QStyle::PM_IconViewIconSize); const QString text = index.model()->data(index, Qt::DisplayRole).toString(); const QIcon icon = index.model()->data(index, Qt::DecorationRole).value(); const QPixmap pixmap = icon.pixmap(iconSize, iconSize); QFontMetrics fm = option.fontMetrics; int gap = fm.height(); int wp = pixmap.width() / pixmap.devicePixelRatio(); int hp = pixmap.height() / pixmap.devicePixelRatio(); if (hp == 0) { // No pixmap loaded yet, we'll use the default icon size in this case. hp = iconSize; wp = iconSize; } QTextLayout iconTextLayout(text, option.font); int wt = layoutText(&iconTextLayout, qMax(3 * wp, 8 * fm.height())); int ht = iconTextLayout.boundingRect().height(); int width; int height; if (text.isEmpty()) { height = hp; } else { height = hp + ht + 10; } width = qMax(wt, wp) + gap; return QSize(width, height); } void KPageListViewDelegate::drawFocus(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) const { if (option.state & QStyle::State_HasFocus) { QStyleOptionFocusRect o; o.QStyleOption::operator=(option); o.rect = rect; o.state |= QStyle::State_KeyboardFocusChange; QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled; o.backgroundColor = option.palette.color(cg, (option.state & QStyle::State_Selected) ? QPalette::Highlight : QPalette::Window); QStyle *style = option.widget ? option.widget->style() : QApplication::style(); style->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, option.widget); } } // KPageListViewProxy KPageListViewProxy::KPageListViewProxy(QObject *parent) : QAbstractProxyModel(parent) { } KPageListViewProxy::~KPageListViewProxy() { } int KPageListViewProxy::rowCount(const QModelIndex &) const { return mList.count(); } int KPageListViewProxy::columnCount(const QModelIndex &) const { return 1; } QModelIndex KPageListViewProxy::index(int row, int column, const QModelIndex &) const { if (column > 1 || row >= mList.count()) { return QModelIndex(); } else { return createIndex(row, column, mList[row].internalPointer()); } } QModelIndex KPageListViewProxy::parent(const QModelIndex &) const { return QModelIndex(); } QVariant KPageListViewProxy::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.row() >= mList.count()) { return QVariant(); } return sourceModel()->data(mList[index.row()], role); } QModelIndex KPageListViewProxy::mapFromSource(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } for (int i = 0; i < mList.count(); ++i) { if (mList[i] == index) { return createIndex(i, 0, index.internalPointer()); } } return QModelIndex(); } QModelIndex KPageListViewProxy::mapToSource(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } return mList[index.row()]; } void KPageListViewProxy::rebuildMap() { mList.clear(); const QAbstractItemModel *model = sourceModel(); if (!model) { return; } for (int i = 0; i < model->rowCount(); ++i) { addMapEntry(model->index(i, 0)); } for (int i = 0; i < mList.count(); ++i) { qCDebug(KWidgetsAddonsLog, "%d:0 -> %d:%d", i, mList[i].row(), mList[i].column()); } Q_EMIT layoutChanged(); } void KPageListViewProxy::addMapEntry(const QModelIndex &index) { if (sourceModel()->rowCount(index) == 0) { mList.append(index); } else { const int count = sourceModel()->rowCount(index); for (int i = 0; i < count; ++i) { addMapEntry(sourceModel()->index(i, 0, index)); } } } SelectionModel::SelectionModel(QAbstractItemModel *model, QObject *parent) : QItemSelectionModel(model, parent) { } void SelectionModel::clear() { // Don't allow the current selection to be cleared } void SelectionModel::select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) { // Don't allow the current selection to be cleared if (!index.isValid() && (command & QItemSelectionModel::Clear)) { return; } QItemSelectionModel::select(index, command); } void SelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) { // Don't allow the current selection to be cleared if (!selection.count() && (command & QItemSelectionModel::Clear)) { return; } QItemSelectionModel::select(selection, command); } #include "moc_kpageview_p.cpp"