1/*
2 This file is part of the KDE Libraries
3 SPDX-FileCopyrightText: 2006 Tobias Koenig <tokoe@kde.org>
4 SPDX-FileCopyrightText: 2007 Rafael Fernández López <ereslibre@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "kpageview.h"
10#include "kpageview_p.h"
11
12#include "kpagemodel.h"
13#include "kpagewidgetmodel.h"
14#include "loggingcategory.h"
15
16#include <ktitlewidget.h>
17
18#include <QAbstractButton>
19#include <QAbstractItemView>
20#include <QApplication>
21#include <QCheckBox>
22#include <QComboBox>
23#include <QEvent>
24#include <QGridLayout>
25#include <QLabel>
26#include <QPaintEvent>
27#include <QPainter>
28#include <QSize>
29#include <QTimer>
30
31// Helper class that draws a rect over a matched widget
32class SearchMatchOverlay : public QWidget
33{
34public:
35 SearchMatchOverlay(QWidget *parent, int tabIdx = -1)
36 : QWidget(parent)
37 , m_tabIdx(tabIdx)
38 {
39 setAttribute(Qt::WA_TransparentForMouseEvents, on: true);
40 resize_impl();
41 parent->installEventFilter(filterObj: this);
42
43 show();
44 raise();
45 }
46
47private:
48 void doResize()
49 {
50 QMetaObject::invokeMethod(object: this, function: &SearchMatchOverlay::resize_impl, type: Qt::QueuedConnection);
51 }
52
53 void resize_impl()
54 {
55 if (m_tabIdx >= 0) {
56 auto tabBar = qobject_cast<QTabBar *>(object: parentWidget());
57 if (!tabBar) {
58 setVisible(false);
59 return;
60 }
61 const QRect r = tabBar->tabRect(index: m_tabIdx);
62 if (geometry() != r) {
63 setGeometry(r);
64 }
65 return;
66 }
67
68 if (parentWidget() && size() != parentWidget()->size()) {
69 resize(parentWidget()->size());
70 }
71 }
72
73 bool eventFilter(QObject *o, QEvent *e) override
74 {
75 if (parentWidget() && o == parentWidget() && (e->type() == QEvent::Resize || e->type() == QEvent::Show)) {
76 doResize();
77 }
78 return QWidget::eventFilter(watched: o, event: e);
79 }
80
81 void paintEvent(QPaintEvent *e) override
82 {
83 QPainter p(this);
84 p.setClipRegion(e->region());
85 QColor c = palette().brush(cg: QPalette::Active, cr: QPalette::Highlight).color();
86 c.setAlpha(110);
87 p.fillRect(rect(), color: c);
88 }
89
90 int m_tabIdx = -1;
91};
92
93void KPageViewPrivate::rebuildGui()
94{
95 // clean up old view
96 Q_Q(KPageView);
97
98 QModelIndex currentLastIndex;
99 if (view && view->selectionModel()) {
100 QObject::disconnect(m_selectionChangedConnection);
101 currentLastIndex = view->selectionModel()->currentIndex();
102 }
103
104 delete view;
105 view = q->createView();
106
107 Q_ASSERT(view);
108
109 view->setSelectionBehavior(QAbstractItemView::SelectItems);
110 view->setSelectionMode(QAbstractItemView::SingleSelection);
111
112 if (model) {
113 view->setModel(model);
114 }
115
116 // setup new view
117 if (view->selectionModel()) {
118 m_selectionChangedConnection = QObject::connect(sender: view->selectionModel(),
119 signal: &QItemSelectionModel::selectionChanged,
120 context: q,
121 slot: [this](const QItemSelection &selected, const QItemSelection &deselected) {
122 pageSelected(selected, deselected);
123 });
124
125 if (currentLastIndex.isValid()) {
126 view->selectionModel()->setCurrentIndex(index: currentLastIndex, command: QItemSelectionModel::Select);
127 } else if (model) {
128 view->selectionModel()->setCurrentIndex(index: model->index(row: 0, column: 0), command: QItemSelectionModel::Select);
129 }
130 }
131
132 if (faceType == KPageView::Tabbed) {
133 stack->setVisible(false);
134 layout->removeWidget(w: stack);
135 } else {
136 layout->addWidget(stack, row: 3, column: 1);
137 stack->setVisible(true);
138 }
139
140 titleWidget->setPalette(qApp->palette(titleWidget));
141
142 if (!hasSearchableView()) {
143 layout->removeWidget(w: searchLineEditContainer);
144 searchLineEditContainer->setVisible(false);
145 titleWidget->setAutoFillBackground(false);
146 layout->setSpacing(0);
147 separatorLine->setVisible(false);
148 titleWidget->setObjectName("KPageView::TitleWidgetNonSearchable");
149 titleWidget->setContentsMargins(left: q_ptr->style()->pixelMetric(metric: QStyle::PM_LayoutLeftMargin),
150 top: q_ptr->style()->pixelMetric(metric: QStyle::PM_LayoutTopMargin),
151 right: q_ptr->style()->pixelMetric(metric: QStyle::PM_LayoutRightMargin),
152 bottom: q_ptr->style()->pixelMetric(metric: QStyle::PM_LayoutBottomMargin));
153 } else {
154 titleWidget->setObjectName("KPageView::TitleWidget");
155 searchLineEditContainer->setVisible(true);
156 searchLineEditContainer->setSizePolicy(hor: QSizePolicy::Ignored, ver: QSizePolicy::Fixed);
157 separatorLine->setVisible(true);
158
159 // Adjust margins for a better alignment
160 searchLineEditContainer->setContentsMargins(left: 4, top: 3, right: 4, bottom: 3);
161 titleWidget->setContentsMargins(left: 5, top: 4, right: 4, bottom: 2);
162
163 // Adjust the search + title background so that it merges into the titlebar
164 layout->setSpacing(0);
165 layout->setContentsMargins({});
166 }
167
168 layout->removeWidget(w: titleWidget);
169
170 if (pageHeader) {
171 layout->removeWidget(w: pageHeader);
172 pageHeader->setVisible(q->showPageHeader());
173 titleWidget->setVisible(false);
174
175 if (faceType == KPageView::Tabbed) {
176 layout->addWidget(pageHeader, row: 1, column: 1);
177 } else {
178 layout->addWidget(pageHeader, row: 1, column: 1, rowSpan: 1, columnSpan: 2);
179 }
180 } else {
181 titleWidget->setVisible(q->showPageHeader());
182 if (faceType == KPageView::Tabbed) {
183 layout->addWidget(titleWidget, row: 1, column: 1);
184 } else {
185 layout->addWidget(titleWidget, row: 1, column: 1, rowSpan: 1, columnSpan: 2);
186 }
187 }
188
189 Qt::Alignment alignment = q->viewPosition();
190 if (alignment & Qt::AlignTop) {
191 layout->addWidget(view, row: 2, column: 1);
192 } else if (alignment & Qt::AlignRight) {
193 // search line
194 layout->addWidget(searchLineEditContainer, row: 1, column: 2, Qt::AlignVCenter);
195 // item view below the search line
196 layout->addWidget(view, row: 3, column: 2, rowSpan: 3, columnSpan: 1);
197 } else if (alignment & Qt::AlignBottom) {
198 layout->addWidget(view, row: 4, column: 1);
199 } else if (alignment & Qt::AlignLeft) {
200 // search line
201 layout->addWidget(searchLineEditContainer, row: 1, column: 0, Qt::AlignVCenter);
202 // item view below the search line
203 layout->addWidget(view, row: 3, column: 0, rowSpan: 3, columnSpan: 1);
204 }
205}
206
207void KPageViewPrivate::updateSelection()
208{
209 // Select the first item in the view if not done yet.
210
211 if (!model) {
212 return;
213 }
214
215 if (!view || !view->selectionModel()) {
216 return;
217 }
218
219 const QModelIndex index = view->selectionModel()->currentIndex();
220 if (!index.isValid()) {
221 view->selectionModel()->setCurrentIndex(index: model->index(row: 0, column: 0), command: QItemSelectionModel::Select);
222 }
223}
224
225void KPageViewPrivate::cleanupPages()
226{
227 // Remove all orphan pages from the stacked widget.
228
229 const QList<QWidget *> widgets = collectPages();
230
231 for (int i = 0; i < stack->count(); ++i) {
232 QWidget *page = stack->widget(i);
233
234 bool found = false;
235 for (int j = 0; j < widgets.count(); ++j) {
236 if (widgets[j] == page) {
237 found = true;
238 }
239 }
240
241 if (!found) {
242 stack->removeWidget(w: page);
243 }
244 }
245}
246
247QList<QWidget *> KPageViewPrivate::collectPages(const QModelIndex &parentIndex)
248{
249 // Traverse through the model recursive and collect all widgets in
250 // a list.
251 QList<QWidget *> retval;
252
253 int rows = model->rowCount(parent: parentIndex);
254 for (int j = 0; j < rows; ++j) {
255 const QModelIndex index = model->index(row: j, column: 0, parent: parentIndex);
256 retval.append(t: qvariant_cast<QWidget *>(v: model->data(index, role: KPageModel::WidgetRole)));
257
258 if (model->rowCount(parent: index) > 0) {
259 retval += collectPages(parentIndex: index);
260 }
261 }
262
263 return retval;
264}
265
266KPageView::FaceType KPageViewPrivate::effectiveFaceType() const
267{
268 if (faceType == KPageView::Auto) {
269 return detectAutoFace();
270 }
271
272 return faceType;
273}
274
275KPageView::FaceType KPageViewPrivate::detectAutoFace() const
276{
277 if (!model) {
278 return KPageView::Plain;
279 }
280
281 // Check whether the model has sub pages.
282 bool hasSubPages = false;
283 const int count = model->rowCount();
284 for (int i = 0; i < count; ++i) {
285 if (model->rowCount(parent: model->index(row: i, column: 0)) > 0) {
286 hasSubPages = true;
287 break;
288 }
289 }
290
291 if (hasSubPages) {
292 return KPageView::Tree;
293 }
294
295 if (model->rowCount() > 1) {
296 return KPageView::List;
297 }
298
299 return KPageView::Plain;
300}
301
302void KPageViewPrivate::modelChanged()
303{
304 if (!model) {
305 return;
306 }
307
308 // If the face type is Auto, we rebuild the GUI whenever the layout
309 // of the model changes.
310 if (faceType == KPageView::Auto) {
311 rebuildGui();
312 // If you discover some crashes use the line below instead...
313 // QTimer::singleShot(0, q, SLOT(rebuildGui()));
314 }
315
316 // Set the stack to the minimum size of the largest widget.
317 QSize size = stack->size();
318 const QList<QWidget *> widgets = collectPages();
319 for (int i = 0; i < widgets.count(); ++i) {
320 const QWidget *widget = widgets[i];
321 if (widget) {
322 size = size.expandedTo(otherSize: widget->minimumSizeHint());
323 }
324 }
325 stack->setMinimumSize(size);
326
327 updateSelection();
328}
329
330void KPageViewPrivate::pageSelected(const QItemSelection &index, const QItemSelection &previous)
331{
332 if (!model) {
333 return;
334 }
335
336 // Return if the current Index is not valid
337 if (index.indexes().size() != 1) {
338 return;
339 }
340 QModelIndex currentIndex = index.indexes().first();
341
342 QModelIndex previousIndex;
343 // The previous index can be invalid
344 if (previous.indexes().size() == 1) {
345 previousIndex = previous.indexes().first();
346 }
347
348 if (faceType != KPageView::Tabbed) {
349 QWidget *widget = qvariant_cast<QWidget *>(v: model->data(index: currentIndex, role: KPageModel::WidgetRole));
350
351 if (widget) {
352 if (stack->indexOf(widget) == -1) { // not included yet
353 stack->addWidget(w: widget);
354 }
355
356 stack->setCurrentWidget(widget);
357 } else {
358 stack->setCurrentWidget(defaultWidget);
359 }
360
361 updateTitleWidget(index: currentIndex);
362 }
363
364 Q_Q(KPageView);
365 Q_EMIT q->currentPageChanged(current: currentIndex, previous: previousIndex);
366}
367
368void KPageViewPrivate::updateTitleWidget(const QModelIndex &index)
369{
370 Q_Q(KPageView);
371
372 const bool headerVisible = model->data(index, role: KPageModel::HeaderVisibleRole).toBool();
373 if (!headerVisible) {
374 titleWidget->setVisible(false);
375 return;
376 }
377 QString header = model->data(index, role: KPageModel::HeaderRole).toString();
378 if (header.isEmpty()) {
379 header = model->data(index, role: Qt::DisplayRole).toString();
380 }
381
382 titleWidget->setText(text: header);
383
384 titleWidget->setVisible(q->showPageHeader());
385}
386
387void KPageViewPrivate::dataChanged(const QModelIndex &, const QModelIndex &)
388{
389 // When data has changed we update the header and icon for the currently selected
390 // page.
391 if (!view) {
392 return;
393 }
394
395 QModelIndex index = view->selectionModel()->currentIndex();
396 if (!index.isValid()) {
397 return;
398 }
399
400 updateTitleWidget(index);
401}
402
403KPageViewPrivate::KPageViewPrivate(KPageView *_parent)
404 : q_ptr(_parent)
405 , model(nullptr)
406 , faceType(KPageView::Auto)
407 , layout(nullptr)
408 , stack(nullptr)
409 , titleWidget(nullptr)
410 , searchLineEditContainer(new QWidget())
411 , searchLineEdit(new QLineEdit())
412 , view(nullptr)
413{
414}
415
416void KPageViewPrivate::init()
417{
418 Q_Q(KPageView);
419 layout = new QGridLayout(q);
420 stack = new KPageStackedWidget(q);
421 titleWidget = new KTitleWidget(q);
422 titleWidget->setObjectName("KPageView::TitleWidget");
423
424 separatorLine = new QFrame(q);
425 separatorLine->setFrameShape(QFrame::HLine);
426 separatorLine->setFixedHeight(1);
427 separatorLine->setFrameShadow(QFrame::Sunken);
428
429 // list view under it to the left
430 layout->addWidget(titleWidget, row: 1, column: 1, rowSpan: 1, columnSpan: 2);
431 // separator
432 layout->addWidget(separatorLine, row: 2, column: 0, rowSpan: 1, columnSpan: 3);
433 // and then the actual page on the right
434 layout->addWidget(stack, row: 3, column: 1);
435
436 defaultWidget = new QWidget(q);
437 stack->addWidget(w: defaultWidget);
438
439 // stack should use most space
440 layout->setColumnStretch(column: 1, stretch: 1);
441 layout->setRowStretch(row: 3, stretch: 1);
442
443 searchTimer.setInterval(400);
444 searchTimer.setSingleShot(true);
445 searchTimer.callOnTimeout(args: q, args: [this] {
446 onSearchTextChanged();
447 });
448 q->setFocusProxy(searchLineEdit);
449 searchLineEdit->setPlaceholderText(KPageView::tr(s: "Search..."));
450 searchLineEdit->setClearButtonEnabled(true);
451 searchLineEdit->setParent(defaultWidget);
452 auto a = new QAction(q);
453 a->setIcon(QIcon::fromTheme(QStringLiteral("search")));
454 searchLineEdit->addAction(action: a, position: QLineEdit::LeadingPosition);
455 q->connect(sender: searchLineEdit, signal: &QLineEdit::textChanged, context: &searchTimer, slot: QOverload<>::of(ptr: &QTimer::start));
456 auto containerLayout = new QVBoxLayout(searchLineEditContainer);
457 containerLayout->setContentsMargins({});
458 containerLayout->setSpacing(0);
459 containerLayout->addWidget(searchLineEdit);
460 searchLineEditContainer->setObjectName("KPageView::Search");
461}
462
463static QList<KPageWidgetItem *> getAllPages(KPageWidgetModel *model, const QModelIndex &parent)
464{
465 const int rc = model->rowCount(parent);
466 QList<KPageWidgetItem *> ret;
467 for (int i = 0; i < rc; ++i) {
468 auto child = model->index(row: i, column: 0, parent);
469 auto item = model->item(index: child);
470 if (child.isValid() && item) {
471 ret << item;
472 ret << getAllPages(model, parent: child);
473 }
474 }
475 return ret;
476}
477
478template<typename WidgetType>
479static QList<QWidget *> hasMatchingText(const QString &text, QWidget *page)
480{
481 QList<QWidget *> ret;
482 const auto widgets = page->findChildren<WidgetType *>();
483 for (auto label : widgets) {
484 if (label->text().contains(text, Qt::CaseInsensitive)) {
485 ret << label;
486 }
487 }
488 return ret;
489}
490
491template<>
492QList<QWidget *> hasMatchingText<QComboBox>(const QString &text, QWidget *page)
493{
494 QList<QWidget *> ret;
495 const auto comboxBoxes = page->findChildren<QComboBox *>();
496 for (auto cb : comboxBoxes) {
497 if (cb->findText(text, flags: Qt::MatchFlag::MatchContains) != -1) {
498 ret << cb;
499 }
500 }
501 return ret;
502}
503
504template<typename...>
505struct FindChildrenHelper {
506 static QList<QWidget *> hasMatchingTextForTypes(const QString &, QWidget *)
507 {
508 return {};
509 }
510};
511
512template<typename First, typename... Rest>
513struct FindChildrenHelper<First, Rest...> {
514 static QList<QWidget *> hasMatchingTextForTypes(const QString &text, QWidget *page)
515 {
516 return hasMatchingText<First>(text, page) << FindChildrenHelper<Rest...>::hasMatchingTextForTypes(text, page);
517 }
518};
519
520static QModelIndex walkTreeAndHideItems(QTreeView *tree, const QString &searchText, const QSet<QString> &pagesToHide, const QModelIndex &parent)
521{
522 QModelIndex current;
523 auto model = tree->model();
524 const int rows = model->rowCount(parent);
525 for (int i = 0; i < rows; ++i) {
526 const auto index = model->index(row: i, column: 0, parent);
527 const auto itemName = index.data().toString();
528 tree->setRowHidden(row: i, parent, hide: pagesToHide.contains(value: itemName) && !itemName.contains(s: searchText, cs: Qt::CaseInsensitive));
529 if (!searchText.isEmpty() && !tree->isRowHidden(row: i, parent) && !current.isValid()) {
530 current = model->index(row: i, column: 0, parent);
531 }
532 auto curr = walkTreeAndHideItems(tree, searchText, pagesToHide, parent: index);
533 if (!current.isValid()) {
534 current = curr;
535 }
536 }
537 return current;
538}
539
540bool KPageViewPrivate::hasSearchableView() const
541{
542 // We support search for only these two types as they can hide rows
543 return qobject_cast<KDEPrivate::KPageListView *>(object: view) || qobject_cast<KDEPrivate::KPageTreeView *>(object: view);
544}
545
546void KPageViewPrivate::onSearchTextChanged()
547{
548 if (!hasSearchableView()) {
549 return;
550 }
551
552 const QString text = searchLineEdit->text();
553 QSet<QString> pagesToHide;
554 std::vector<QWidget *> matchedWidgets;
555 if (!text.isEmpty()) {
556 const auto pages = getAllPages(model: static_cast<KPageWidgetModel *>(model), parent: {});
557 for (auto item : pages) {
558 const auto matchingWidgets = FindChildrenHelper<QLabel, QAbstractButton, QComboBox>::hasMatchingTextForTypes(text, page: item->widget());
559 if (matchingWidgets.isEmpty()) {
560 pagesToHide << item->name();
561 }
562 matchedWidgets.insert(position: matchedWidgets.end(), first: matchingWidgets.begin(), last: matchingWidgets.end());
563 }
564 }
565
566 if (model) {
567 QModelIndex current;
568 if (auto list = qobject_cast<QListView *>(object: view)) {
569 for (int i = 0; i < model->rowCount(); ++i) {
570 const auto itemName = model->index(row: i, column: 0).data().toString();
571 list->setRowHidden(row: i, hide: pagesToHide.contains(value: itemName) && !itemName.contains(s: text, cs: Qt::CaseInsensitive));
572 if (!text.isEmpty() && !list->isRowHidden(row: i) && !current.isValid()) {
573 current = model->index(row: i, column: 0);
574 }
575 }
576 } else if (auto tree = qobject_cast<QTreeView *>(object: view)) {
577 current = walkTreeAndHideItems(tree, searchText: text, pagesToHide, parent: {});
578 auto parent = current.parent();
579 while (parent.isValid()) {
580 tree->setRowHidden(row: parent.row(), parent: parent.parent(), hide: false);
581 parent = parent.parent();
582 }
583 } else {
584 qWarning() << "Unreacheable, unknown view:" << view;
585 Q_UNREACHABLE();
586 }
587
588 if (current.isValid()) {
589 view->setCurrentIndex(current);
590 }
591 }
592
593 qDeleteAll(c: m_searchMatchOverlays);
594 m_searchMatchOverlays.clear();
595
596 using TabWidgetAndPage = QPair<QTabWidget *, QWidget *>;
597 auto tabWidgetParent = [](QWidget *w) {
598 // Finds if @p w is in a QTabWidget and returns
599 // The QTabWidget + the widget in the stack where
600 // @p w lives
601 auto parent = w->parentWidget();
602 TabWidgetAndPage p = {nullptr, nullptr};
603 if (auto tw = qobject_cast<QTabWidget *>(object: parent)) {
604 p.first = tw;
605 }
606 QVarLengthArray<QWidget *, 8> parentChain;
607 while (parent) {
608 if (!p.first) {
609 if (auto tw = qobject_cast<QTabWidget *>(object: parent)) {
610 if (parentChain.size() >= 3) {
611 // last == QTabWidget
612 // second last == QStackedWidget of QTabWidget
613 // third last => the widget we want
614 p.second = parentChain.value(i: (parentChain.size() - 1) - 2);
615 }
616 p.first = tw;
617 break;
618 }
619 }
620 parent = parent->parentWidget();
621 parentChain << parent;
622 }
623 return p;
624 };
625
626 for (auto w : matchedWidgets) {
627 if (w) {
628 m_searchMatchOverlays << new SearchMatchOverlay(w);
629 if (!w->isVisible()) {
630 const auto [tabWidget, page] = tabWidgetParent(w);
631 if (!tabWidget && !page) {
632 continue;
633 }
634 const int idx = tabWidget->indexOf(widget: page);
635 if (idx < 0) {
636 // qDebug() << page << tabWidget << "not found" << w;
637 continue;
638 }
639 m_searchMatchOverlays << new SearchMatchOverlay(tabWidget->tabBar(), idx);
640 }
641 }
642 }
643}
644
645// KPageView Implementation
646KPageView::KPageView(QWidget *parent)
647 : KPageView(*new KPageViewPrivate(this), parent)
648{
649}
650
651KPageView::KPageView(KPageViewPrivate &dd, QWidget *parent)
652 : QWidget(parent)
653 , d_ptr(&dd)
654{
655 d_ptr->init();
656}
657
658KPageView::~KPageView() = default;
659
660void KPageView::setModel(QAbstractItemModel *model)
661{
662 Q_D(KPageView);
663 // clean up old model
664 if (d->model) {
665 disconnect(d->m_layoutChangedConnection);
666 disconnect(d->m_dataChangedConnection);
667 }
668
669 d->model = model;
670
671 if (d->model) {
672 d->m_layoutChangedConnection = connect(sender: d->model, signal: &QAbstractItemModel::layoutChanged, context: this, slot: [d]() {
673 d->modelChanged();
674 });
675 d->m_dataChangedConnection = connect(sender: d->model, signal: &QAbstractItemModel::dataChanged, context: this, slot: [d](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
676 d->dataChanged(topLeft, bottomRight);
677 });
678
679 // set new model in navigation view
680 if (d->view) {
681 d->view->setModel(model);
682 }
683 }
684
685 d->rebuildGui();
686}
687
688QAbstractItemModel *KPageView::model() const
689{
690 Q_D(const KPageView);
691 return d->model;
692}
693
694void KPageView::setFaceType(FaceType faceType)
695{
696 Q_D(KPageView);
697 d->faceType = faceType;
698
699 d->rebuildGui();
700}
701
702KPageView::FaceType KPageView::faceType() const
703{
704 Q_D(const KPageView);
705 return d->faceType;
706}
707
708void KPageView::setCurrentPage(const QModelIndex &index)
709{
710 Q_D(KPageView);
711 if (!d->view || !d->view->selectionModel()) {
712 return;
713 }
714
715 d->view->selectionModel()->setCurrentIndex(index, command: QItemSelectionModel::SelectCurrent);
716}
717
718QModelIndex KPageView::currentPage() const
719{
720 Q_D(const KPageView);
721 if (!d->view || !d->view->selectionModel()) {
722 return QModelIndex();
723 }
724
725 return d->view->selectionModel()->currentIndex();
726}
727
728void KPageView::setItemDelegate(QAbstractItemDelegate *delegate)
729{
730 Q_D(KPageView);
731 if (d->view) {
732 d->view->setItemDelegate(delegate);
733 }
734}
735
736QAbstractItemDelegate *KPageView::itemDelegate() const
737{
738 Q_D(const KPageView);
739 if (d->view) {
740 return d->view->itemDelegate();
741 } else {
742 return nullptr;
743 }
744}
745
746void KPageView::setDefaultWidget(QWidget *widget)
747{
748 Q_D(KPageView);
749
750 Q_ASSERT(widget);
751
752 bool isCurrent = (d->stack->currentIndex() == d->stack->indexOf(d->defaultWidget));
753
754 // remove old default widget
755 d->stack->removeWidget(w: d->defaultWidget);
756 delete d->defaultWidget;
757
758 // add new default widget
759 d->defaultWidget = widget;
760 d->stack->addWidget(w: d->defaultWidget);
761
762 if (isCurrent) {
763 d->stack->setCurrentWidget(d->defaultWidget);
764 }
765}
766
767void KPageView::setPageHeader(QWidget *header)
768{
769 Q_D(KPageView);
770 if (d->pageHeader == header) {
771 return;
772 }
773
774 if (d->pageHeader) {
775 d->layout->removeWidget(w: d->pageHeader);
776 }
777 d->layout->removeWidget(w: d->titleWidget);
778
779 d->pageHeader = header;
780
781 // Give it a colSpan of 2 to add a margin to the right
782 if (d->pageHeader) {
783 d->layout->addWidget(d->pageHeader, row: 1, column: 1, rowSpan: 1, columnSpan: 2);
784 d->pageHeader->setVisible(showPageHeader());
785 } else {
786 d->layout->addWidget(d->titleWidget, row: 1, column: 1, rowSpan: 1, columnSpan: 2);
787 d->titleWidget->setVisible(showPageHeader());
788 }
789}
790
791QWidget *KPageView::pageHeader() const
792{
793 Q_D(const KPageView);
794 if (!d->pageHeader) {
795 return d->titleWidget;
796 }
797 return d->pageHeader;
798}
799
800void KPageView::setPageFooter(QWidget *footer)
801{
802 Q_D(KPageView);
803 if (d->pageFooter == footer) {
804 return;
805 }
806
807 if (d->pageFooter) {
808 d->layout->removeWidget(w: d->pageFooter);
809 }
810
811 d->pageFooter = footer;
812
813 if (footer) {
814 d->pageFooter->setContentsMargins(left: 4, top: 4, right: 4, bottom: 4);
815 d->layout->addWidget(d->pageFooter, row: 4, column: 1);
816 }
817}
818
819QWidget *KPageView::pageFooter() const
820{
821 Q_D(const KPageView);
822 return d->pageFooter;
823}
824
825QAbstractItemView *KPageView::createView()
826{
827 Q_D(KPageView);
828 const FaceType faceType = d->effectiveFaceType();
829
830 if (faceType == Plain) {
831 return new KDEPrivate::KPagePlainView(this);
832 }
833 if (faceType == FlatList) {
834 return new KDEPrivate::KPageListView(this);
835 }
836 if (faceType == List) {
837 auto view = new KDEPrivate::KPageListView(this);
838 view->setItemDelegate(new KDEPrivate::KPageListViewDelegate(this));
839 view->setFlexibleWidth(true);
840 return view;
841 }
842 if (faceType == Tree) {
843 return new KDEPrivate::KPageTreeView(this);
844 }
845 if (faceType == Tabbed) {
846 return new KDEPrivate::KPageTabbedView(this);
847 }
848
849 return nullptr;
850}
851
852bool KPageView::showPageHeader() const
853{
854 Q_D(const KPageView);
855 const FaceType faceType = d->effectiveFaceType();
856
857 if (faceType == Tabbed) {
858 return false;
859 } else {
860 return d->pageHeader || !d->titleWidget->text().isEmpty();
861 }
862}
863
864Qt::Alignment KPageView::viewPosition() const
865{
866 Q_D(const KPageView);
867 const FaceType faceType = d->effectiveFaceType();
868
869 if (faceType == Plain || faceType == Tabbed) {
870 return Qt::AlignTop;
871 } else {
872 return Qt::AlignLeft;
873 }
874}
875
876#include "moc_kpageview.cpp"
877

source code of kwidgetsaddons/src/kpageview.cpp