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

KDE's Doxygen guidelines are available online.