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

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.h"
0010 #include "kpageview_p.h"
0011 
0012 #include "kpagemodel.h"
0013 #include "loggingcategory.h"
0014 
0015 #include <ktitlewidget.h>
0016 
0017 #include <QAbstractItemView>
0018 #include <QGridLayout>
0019 #include <QSize>
0020 #include <QTimer>
0021 
0022 void KPageViewPrivate::rebuildGui()
0023 {
0024     // clean up old view
0025     Q_Q(KPageView);
0026 
0027     QModelIndex currentLastIndex;
0028     if (view && view->selectionModel()) {
0029         QObject::disconnect(m_selectionChangedConnection);
0030         currentLastIndex = view->selectionModel()->currentIndex();
0031     }
0032 
0033     delete view;
0034     view = q->createView();
0035 
0036     Q_ASSERT(view);
0037 
0038     view->setSelectionBehavior(QAbstractItemView::SelectItems);
0039     view->setSelectionMode(QAbstractItemView::SingleSelection);
0040 
0041     if (model) {
0042         view->setModel(model);
0043     }
0044 
0045     // setup new view
0046     if (view->selectionModel()) {
0047         m_selectionChangedConnection = QObject::connect(view->selectionModel(),
0048                                                         &QItemSelectionModel::selectionChanged,
0049                                                         q,
0050                                                         [this](const QItemSelection &selected, const QItemSelection &deselected) {
0051                                                             pageSelected(selected, deselected);
0052                                                         });
0053 
0054         if (currentLastIndex.isValid()) {
0055             view->selectionModel()->setCurrentIndex(currentLastIndex, QItemSelectionModel::Select);
0056         } else if (model) {
0057             view->selectionModel()->setCurrentIndex(model->index(0, 0), QItemSelectionModel::Select);
0058         }
0059     }
0060 
0061     if (faceType == KPageView::Tabbed) {
0062         stack->setVisible(false);
0063         layout->removeWidget(stack);
0064     } else {
0065         layout->addWidget(stack, 2, 1);
0066         stack->setVisible(true);
0067     }
0068 
0069     layout->removeWidget(titleWidget);
0070 
0071     if (pageHeader) {
0072         layout->removeWidget(pageHeader);
0073         pageHeader->setVisible(q->showPageHeader());
0074         titleWidget->setVisible(false);
0075 
0076         if (faceType == KPageView::Tabbed) {
0077             layout->addWidget(pageHeader, 1, 1);
0078         } else {
0079             layout->addWidget(pageHeader, 1, 1, 1, 2);
0080         }
0081     } else {
0082         titleWidget->setVisible(q->showPageHeader());
0083         if (faceType == KPageView::Tabbed) {
0084             layout->addWidget(titleWidget, 1, 1);
0085         } else {
0086             layout->addWidget(titleWidget, 1, 1, 1, 2);
0087         }
0088     }
0089 
0090     Qt::Alignment alignment = q->viewPosition();
0091     if (alignment & Qt::AlignTop) {
0092         layout->addWidget(view, 2, 1);
0093     } else if (alignment & Qt::AlignRight) {
0094         layout->addWidget(view, 1, 2, 4, 1);
0095     } else if (alignment & Qt::AlignBottom) {
0096         layout->addWidget(view, 4, 1);
0097     } else if (alignment & Qt::AlignLeft) {
0098         layout->addWidget(view, 1, 0, 4, 1);
0099     }
0100 }
0101 
0102 void KPageViewPrivate::updateSelection()
0103 {
0104     // Select the first item in the view if not done yet.
0105 
0106     if (!model) {
0107         return;
0108     }
0109 
0110     if (!view || !view->selectionModel()) {
0111         return;
0112     }
0113 
0114     const QModelIndex index = view->selectionModel()->currentIndex();
0115     if (!index.isValid()) {
0116         view->selectionModel()->setCurrentIndex(model->index(0, 0), QItemSelectionModel::Select);
0117     }
0118 }
0119 
0120 void KPageViewPrivate::cleanupPages()
0121 {
0122     // Remove all orphan pages from the stacked widget.
0123 
0124     const QList<QWidget *> widgets = collectPages();
0125 
0126     for (int i = 0; i < stack->count(); ++i) {
0127         QWidget *page = stack->widget(i);
0128 
0129         bool found = false;
0130         for (int j = 0; j < widgets.count(); ++j) {
0131             if (widgets[j] == page) {
0132                 found = true;
0133             }
0134         }
0135 
0136         if (!found) {
0137             stack->removeWidget(page);
0138         }
0139     }
0140 }
0141 
0142 QList<QWidget *> KPageViewPrivate::collectPages(const QModelIndex &parentIndex)
0143 {
0144     // Traverse through the model recursive and collect all widgets in
0145     // a list.
0146     QList<QWidget *> retval;
0147 
0148     int rows = model->rowCount(parentIndex);
0149     for (int j = 0; j < rows; ++j) {
0150         const QModelIndex index = model->index(j, 0, parentIndex);
0151         retval.append(qvariant_cast<QWidget *>(model->data(index, KPageModel::WidgetRole)));
0152 
0153         if (model->rowCount(index) > 0) {
0154             retval += collectPages(index);
0155         }
0156     }
0157 
0158     return retval;
0159 }
0160 
0161 KPageView::FaceType KPageViewPrivate::effectiveFaceType() const
0162 {
0163     if (faceType == KPageView::Auto) {
0164         return detectAutoFace();
0165     }
0166 
0167     return faceType;
0168 }
0169 
0170 KPageView::FaceType KPageViewPrivate::detectAutoFace() const
0171 {
0172     if (!model) {
0173         return KPageView::Plain;
0174     }
0175 
0176     // Check whether the model has sub pages.
0177     bool hasSubPages = false;
0178     const int count = model->rowCount();
0179     for (int i = 0; i < count; ++i) {
0180         if (model->rowCount(model->index(i, 0)) > 0) {
0181             hasSubPages = true;
0182             break;
0183         }
0184     }
0185 
0186     if (hasSubPages) {
0187         return KPageView::Tree;
0188     }
0189 
0190     if (model->rowCount() > 1) {
0191         return KPageView::List;
0192     }
0193 
0194     return KPageView::Plain;
0195 }
0196 
0197 void KPageViewPrivate::modelChanged()
0198 {
0199     if (!model) {
0200         return;
0201     }
0202 
0203     // If the face type is Auto, we rebuild the GUI whenever the layout
0204     // of the model changes.
0205     if (faceType == KPageView::Auto) {
0206         rebuildGui();
0207         // If you discover some crashes use the line below instead...
0208         // QTimer::singleShot(0, q, SLOT(rebuildGui()));
0209     }
0210 
0211     // Set the stack to the minimum size of the largest widget.
0212     QSize size = stack->size();
0213     const QList<QWidget *> widgets = collectPages();
0214     for (int i = 0; i < widgets.count(); ++i) {
0215         const QWidget *widget = widgets[i];
0216         if (widget) {
0217             size = size.expandedTo(widget->minimumSizeHint());
0218         }
0219     }
0220     stack->setMinimumSize(size);
0221 
0222     updateSelection();
0223 }
0224 
0225 void KPageViewPrivate::pageSelected(const QItemSelection &index, const QItemSelection &previous)
0226 {
0227     if (!model) {
0228         return;
0229     }
0230 
0231     // Return if the current Index is not valid
0232     if (index.indexes().size() != 1) {
0233         return;
0234     }
0235     QModelIndex currentIndex = index.indexes().first();
0236 
0237     QModelIndex previousIndex;
0238     // The previous index can be invalid
0239     if (previous.indexes().size() == 1) {
0240         previousIndex = previous.indexes().first();
0241     }
0242 
0243     if (faceType != KPageView::Tabbed) {
0244         QWidget *widget = qvariant_cast<QWidget *>(model->data(currentIndex, KPageModel::WidgetRole));
0245 
0246         if (widget) {
0247             if (stack->indexOf(widget) == -1) { // not included yet
0248                 stack->addWidget(widget);
0249             }
0250 
0251             stack->setCurrentWidget(widget);
0252         } else {
0253             stack->setCurrentWidget(defaultWidget);
0254         }
0255 
0256         updateTitleWidget(currentIndex);
0257     }
0258 
0259     Q_Q(KPageView);
0260     Q_EMIT q->currentPageChanged(currentIndex, previousIndex);
0261 }
0262 
0263 void KPageViewPrivate::updateTitleWidget(const QModelIndex &index)
0264 {
0265     Q_Q(KPageView);
0266 
0267     const bool headerVisible = model->data(index, KPageModel::HeaderVisibleRole).toBool();
0268     if (!headerVisible) {
0269         titleWidget->setVisible(false);
0270         return;
0271     }
0272     QString header = model->data(index, KPageModel::HeaderRole).toString();
0273     if (header.isNull()) { // TODO KF6 remove that ugly logic, see also doxy-comments in KPageWidgetItem::setHeader()
0274         header = model->data(index, Qt::DisplayRole).toString();
0275     }
0276 
0277     titleWidget->setText(header);
0278 
0279     titleWidget->setVisible(q->showPageHeader());
0280 }
0281 
0282 void KPageViewPrivate::dataChanged(const QModelIndex &, const QModelIndex &)
0283 {
0284     // When data has changed we update the header and icon for the currently selected
0285     // page.
0286     if (!view) {
0287         return;
0288     }
0289 
0290     QModelIndex index = view->selectionModel()->currentIndex();
0291     if (!index.isValid()) {
0292         return;
0293     }
0294 
0295     updateTitleWidget(index);
0296 }
0297 
0298 KPageViewPrivate::KPageViewPrivate(KPageView *_parent)
0299     : q_ptr(_parent)
0300     , model(nullptr)
0301     , faceType(KPageView::Auto)
0302     , layout(nullptr)
0303     , stack(nullptr)
0304     , titleWidget(nullptr)
0305     , view(nullptr)
0306 {
0307 }
0308 
0309 void KPageViewPrivate::init()
0310 {
0311     Q_Q(KPageView);
0312     layout = new QGridLayout(q);
0313     stack = new KPageStackedWidget(q);
0314     titleWidget = new KTitleWidget(q);
0315     layout->addWidget(titleWidget, 1, 1, 1, 2);
0316     layout->addWidget(stack, 2, 1);
0317 
0318     defaultWidget = new QWidget(q);
0319     stack->addWidget(defaultWidget);
0320 
0321     // stack should use most space
0322     layout->setColumnStretch(1, 1);
0323     layout->setRowStretch(2, 1);
0324 }
0325 
0326 // KPageView Implementation
0327 KPageView::KPageView(QWidget *parent)
0328     : KPageView(*new KPageViewPrivate(this), parent)
0329 {
0330 }
0331 
0332 KPageView::KPageView(KPageViewPrivate &dd, QWidget *parent)
0333     : QWidget(parent)
0334     , d_ptr(&dd)
0335 {
0336     d_ptr->init();
0337 }
0338 
0339 KPageView::~KPageView() = default;
0340 
0341 void KPageView::setModel(QAbstractItemModel *model)
0342 {
0343     Q_D(KPageView);
0344     // clean up old model
0345     if (d->model) {
0346         disconnect(d->m_layoutChangedConnection);
0347         disconnect(d->m_dataChangedConnection);
0348     }
0349 
0350     d->model = model;
0351 
0352     if (d->model) {
0353         d->m_layoutChangedConnection = connect(d->model, &QAbstractItemModel::layoutChanged, this, [d]() {
0354             d->modelChanged();
0355         });
0356         d->m_dataChangedConnection = connect(d->model, &QAbstractItemModel::dataChanged, this, [d](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
0357             d->dataChanged(topLeft, bottomRight);
0358         });
0359 
0360         // set new model in navigation view
0361         if (d->view) {
0362             d->view->setModel(model);
0363         }
0364     }
0365 
0366     d->rebuildGui();
0367 }
0368 
0369 QAbstractItemModel *KPageView::model() const
0370 {
0371     Q_D(const KPageView);
0372     return d->model;
0373 }
0374 
0375 void KPageView::setFaceType(FaceType faceType)
0376 {
0377     Q_D(KPageView);
0378     d->faceType = faceType;
0379 
0380     d->rebuildGui();
0381 }
0382 
0383 KPageView::FaceType KPageView::faceType() const
0384 {
0385     Q_D(const KPageView);
0386     return d->faceType;
0387 }
0388 
0389 void KPageView::setCurrentPage(const QModelIndex &index)
0390 {
0391     Q_D(KPageView);
0392     if (!d->view || !d->view->selectionModel()) {
0393         return;
0394     }
0395 
0396     d->view->selectionModel()->setCurrentIndex(index, QItemSelectionModel::SelectCurrent);
0397 }
0398 
0399 QModelIndex KPageView::currentPage() const
0400 {
0401     Q_D(const KPageView);
0402     if (!d->view || !d->view->selectionModel()) {
0403         return QModelIndex();
0404     }
0405 
0406     return d->view->selectionModel()->currentIndex();
0407 }
0408 
0409 void KPageView::setItemDelegate(QAbstractItemDelegate *delegate)
0410 {
0411     Q_D(KPageView);
0412     if (d->view) {
0413         d->view->setItemDelegate(delegate);
0414     }
0415 }
0416 
0417 QAbstractItemDelegate *KPageView::itemDelegate() const
0418 {
0419     Q_D(const KPageView);
0420     if (d->view) {
0421         return d->view->itemDelegate();
0422     } else {
0423         return nullptr;
0424     }
0425 }
0426 
0427 void KPageView::setDefaultWidget(QWidget *widget)
0428 {
0429     Q_D(KPageView);
0430 
0431     Q_ASSERT(widget);
0432 
0433     bool isCurrent = (d->stack->currentIndex() == d->stack->indexOf(d->defaultWidget));
0434 
0435     // remove old default widget
0436     d->stack->removeWidget(d->defaultWidget);
0437     delete d->defaultWidget;
0438 
0439     // add new default widget
0440     d->defaultWidget = widget;
0441     d->stack->addWidget(d->defaultWidget);
0442 
0443     if (isCurrent) {
0444         d->stack->setCurrentWidget(d->defaultWidget);
0445     }
0446 }
0447 
0448 void KPageView::setPageHeader(QWidget *header)
0449 {
0450     Q_D(KPageView);
0451     if (d->pageHeader == header) {
0452         return;
0453     }
0454 
0455     if (d->pageHeader) {
0456         d->layout->removeWidget(d->pageHeader);
0457     }
0458     d->layout->removeWidget(d->titleWidget);
0459 
0460     d->pageHeader = header;
0461 
0462     // Give it a colSpan of 2 to add a margin to the right
0463     if (d->pageHeader) {
0464         d->layout->addWidget(d->pageHeader, 1, 1, 1, 2);
0465         d->pageHeader->setVisible(showPageHeader());
0466     } else {
0467         d->layout->addWidget(d->titleWidget, 1, 1, 1, 2);
0468         d->titleWidget->setVisible(showPageHeader());
0469     }
0470 }
0471 
0472 QWidget *KPageView::pageHeader() const
0473 {
0474     Q_D(const KPageView);
0475     if (!d->pageHeader) {
0476         return d->titleWidget;
0477     }
0478     return d->pageHeader;
0479 }
0480 
0481 void KPageView::setPageFooter(QWidget *footer)
0482 {
0483     Q_D(KPageView);
0484     if (d->pageFooter == footer) {
0485         return;
0486     }
0487 
0488     if (d->pageFooter) {
0489         d->layout->removeWidget(d->pageFooter);
0490     }
0491 
0492     d->pageFooter = footer;
0493 
0494     if (footer) {
0495         d->layout->addWidget(d->pageFooter, 3, 1);
0496     }
0497 }
0498 
0499 QWidget *KPageView::pageFooter() const
0500 {
0501     Q_D(const KPageView);
0502     return d->pageFooter;
0503 }
0504 
0505 QAbstractItemView *KPageView::createView()
0506 {
0507     Q_D(KPageView);
0508     const FaceType faceType = d->effectiveFaceType();
0509 
0510     if (faceType == Plain) {
0511         return new KDEPrivate::KPagePlainView(this);
0512     }
0513     if (faceType == FlatList) {
0514         return new KDEPrivate::KPageListView(this);
0515     }
0516     if (faceType == List) {
0517         auto view = new KDEPrivate::KPageListView(this);
0518         view->setItemDelegate(new KDEPrivate::KPageListViewDelegate(this));
0519         return view;
0520     }
0521     if (faceType == Tree) {
0522         return new KDEPrivate::KPageTreeView(this);
0523     }
0524     if (faceType == Tabbed) {
0525         return new KDEPrivate::KPageTabbedView(this);
0526     }
0527 
0528     return nullptr;
0529 }
0530 
0531 bool KPageView::showPageHeader() const
0532 {
0533     Q_D(const KPageView);
0534     const FaceType faceType = d->effectiveFaceType();
0535 
0536     if (faceType == Tabbed) {
0537         return false;
0538     } else {
0539         return d->pageHeader || !d->titleWidget->text().isEmpty();
0540     }
0541 }
0542 
0543 Qt::Alignment KPageView::viewPosition() const
0544 {
0545     Q_D(const KPageView);
0546     const FaceType faceType = d->effectiveFaceType();
0547 
0548     if (faceType == Plain || faceType == Tabbed) {
0549         return Qt::AlignTop;
0550     } else {
0551         return Qt::AlignLeft;
0552     }
0553 }
0554 
0555 #include "moc_kpageview.cpp"