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

KDE's Doxygen guidelines are available online.