KWidgetsAddons

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

KDE's Doxygen guidelines are available online.