12#include "kpageview_p.h"
14#include "common_helpers_p.h"
15#include "kpagemodel.h"
16#include "kpagewidgetmodel.h"
17#include "loggingcategory.h"
19#include <ktitlewidget.h>
21#include <QAbstractButton>
22#include <QAbstractItemView>
23#include <QApplication>
35#include <QWidgetAction>
38class NoPaddingToolBarProxyStyle :
public QProxyStyle
41 int pixelMetric(
PixelMetric metric,
const QStyleOption *option,
const QWidget *widget)
const override
51class SearchMatchOverlay :
public QWidget
60 parent->installEventFilter(
this);
85 const QRect r = tabBar->tabRect(m_tabIdx);
97 bool eventFilter(
QObject *o, QEvent *e)
override
105 void paintEvent(QPaintEvent *e)
override
108 p.setClipRegion(e->
region());
111 p.fillRect(
rect(), c);
117void KPageViewPrivate::rebuildGui()
123 if (view && view->selectionModel()) {
125 currentLastIndex = view->selectionModel()->currentIndex();
129 view = q->createView();
137 view->setModel(model);
141 if (view->selectionModel()) {
146 pageSelected(selected, deselected);
149 if (currentLastIndex.
isValid()) {
157 stack->setVisible(
false);
158 layout->removeWidget(stack);
160 layout->addWidget(stack, 3, 1, 1, 2);
161 stack->setVisible(
true);
164 titleWidget->setPalette(qApp->palette(titleWidget));
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");
179 titleWidget->setObjectName(
"KPageView::TitleWidget");
180 searchLineEdit->setEnabled(
true);
181 searchLineEditContainer->setVisible(
true);
183 separatorLine->setVisible(
true);
186 searchLineEditContainer->setContentsMargins(4, 3, 4, 3);
187 titleWidget->setContentsMargins(5, 4, 4, 2);
190 layout->setSpacing(0);
191 layout->setContentsMargins({});
194 layout->removeWidget(titleWidget);
195 layout->removeWidget(actionsToolBar);
197 actionsToolBar->setVisible(q->showPageHeader());
199 layout->removeWidget(pageHeader);
200 pageHeader->setVisible(q->showPageHeader());
201 titleWidget->setVisible(
false);
204 layout->addWidget(pageHeader, 1, 1);
206 layout->addWidget(pageHeader, 1, 1);
207 layout->addWidget(actionsToolBar, 1, 2);
210 titleWidget->setVisible(q->showPageHeader());
212 layout->addWidget(titleWidget, 1, 1);
214 layout->addWidget(titleWidget, 1, 1);
215 layout->addWidget(actionsToolBar, 1, 2);
221 layout->addWidget(view, 2, 1);
226 layout->addWidget(view, 3, 2, 3, 1);
228 layout->addWidget(view, 4, 1);
233 layout->addWidget(view, 3, 0, 3, 1);
237void KPageViewPrivate::updateSelection()
245 if (!view || !view->selectionModel()) {
249 const QModelIndex index = view->selectionModel()->currentIndex();
255void KPageViewPrivate::cleanupPages()
261 for (
int i = 0; i < stack->count(); ++i) {
262 QWidget *page = stack->widget(i);
265 for (
int j = 0; j < widgets.
count(); ++j) {
266 if (widgets[j] == page) {
272 stack->removeWidget(page);
283 int rows = model->rowCount(parentIndex);
284 for (
int j = 0; j < rows; ++j) {
285 const QModelIndex index = model->index(j, 0, parentIndex);
288 if (model->rowCount(index) > 0) {
289 retval += collectPages(index);
299 return detectAutoFace();
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) {
325 if (model->rowCount() > 1) {
332void KPageViewPrivate::modelChanged()
347 QSize size = stack->size();
349 for (
int i = 0; i < widgets.
count(); ++i) {
350 const QWidget *widget = widgets[i];
355 stack->setMinimumSize(size);
367 if (index.
indexes().size() != 1) {
374 if (previous.
indexes().size() == 1) {
375 previousIndex = previous.
indexes().first();
382 if (stack->indexOf(widget) == -1) {
383 stack->addWidget(widget);
386 stack->setCurrentWidget(widget);
388 stack->setCurrentWidget(defaultWidget);
391 updateTitleWidget(currentIndex);
392 updateActionsLayout(currentIndex, previousIndex);
396 Q_EMIT q->currentPageChanged(currentIndex, previousIndex);
399void KPageViewPrivate::updateTitleWidget(
const QModelIndex &index)
404 if (!headerVisible) {
405 titleWidget->setVisible(
false);
413 titleWidget->setText(header);
415 titleWidget->setVisible(q->showPageHeader());
424 for (
const auto action : previousActions) {
425 actionsToolBar->removeAction(action);
430 if (actions.isEmpty()) {
431 actionsToolBar->hide();
433 actionsToolBar->show();
434 for (
const auto action : actions) {
435 actionsToolBar->addAction(action);
448 QModelIndex index = view->selectionModel()->currentIndex();
453 updateTitleWidget(index);
456KPageViewPrivate::KPageViewPrivate(
KPageView *_parent)
462 , titleWidget(nullptr)
463 , searchLineEditContainer(nullptr)
464 , searchLineEdit(nullptr)
469void KPageViewPrivate::init()
473 stack =
new KPageStackedWidget(q);
476 titleWidget->setObjectName(
"KPageView::TitleWidget");
479 separatorLine =
new QFrame(q);
481 separatorLine->setFixedHeight(1);
485 actionsToolBar->setObjectName(
QLatin1String(
"KPageView::TitleWidget"));
488 actionsToolBar->setStyle(
new NoPaddingToolBarProxyStyle);
489 actionsToolBar->show();
492 layout->addWidget(titleWidget, 1, 1);
493 layout->addWidget(actionsToolBar, 1, 1);
495 layout->addWidget(separatorLine, 2, 0, 1, 3);
497 layout->addWidget(stack, 3, 1, 1, 2);
499 defaultWidget =
new QWidget(q);
500 stack->addWidget(defaultWidget);
503 layout->setColumnStretch(1, 1);
504 layout->setRowStretch(3, 1);
506 searchLineEdit =
new QLineEdit(defaultWidget);
507 searchTimer.setInterval(400);
508 searchTimer.setSingleShot(
true);
509 searchTimer.callOnTimeout(q, [
this] {
510 onSearchTextChanged();
512 q->setFocusProxy(searchLineEdit);
513 searchLineEdit->setPlaceholderText(
KPageView::tr(
"Search…",
"@info:placeholder"));
514 searchLineEdit->setClearButtonEnabled(
true);
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");
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) {
537 ret << getAllPages(model, child);
543template<
typename W
idgetType>
547 const auto widgets = page->
findChildren<WidgetType *>();
548 for (
auto label : widgets) {
561 for (
auto cb : comboxBoxes) {
562 if (cb->findText(text, Qt::MatchFlag::MatchContains) != -1) {
570struct FindChildrenHelper {
577template<
typename First,
typename... Rest>
578struct FindChildrenHelper<First, Rest...> {
579 static QList<QWidget *> hasMatchingTextForTypes(
const QString &text, QWidget *page)
581 return hasMatchingText<First>(text, page) << FindChildrenHelper<Rest...>::hasMatchingTextForTypes(text, page);
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);
595 current = model->index(i, 0, parent);
597 auto curr = walkTreeAndHideItems(tree, searchText, pagesToHide, index);
605bool KPageViewPrivate::hasSearchableView()
const
608 return qobject_cast<KDEPrivate::KPageListView *>(view) || qobject_cast<KDEPrivate::KPageTreeView *>(view);
611void KPageViewPrivate::onSearchTextChanged()
613 if (!hasSearchableView()) {
617 const QString text = searchLineEdit->text();
619 std::vector<QWidget *> matchedWidgets;
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();
627 matchedWidgets.insert(matchedWidgets.end(), matchingWidgets.begin(), matchingWidgets.end());
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();
638 current = model->index(i, 0);
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();
649 qWarning() <<
"Unreacheable, unknown view:" << view;
654 view->setCurrentIndex(current);
658 qDeleteAll(m_searchMatchOverlays);
659 m_searchMatchOverlays.clear();
661 using TabWidgetAndPage = QPair<QTabWidget *, QWidget *>;
662 auto tabWidgetParent = [](
QWidget *w) {
666 auto parent = w->parentWidget();
667 TabWidgetAndPage p = {
nullptr,
nullptr};
669 parentChain << parent;
671 if (
auto tw = qobject_cast<QTabWidget *>(parent)) {
672 if (parentChain.
size() >= 3) {
676 p.second = parentChain.
value((parentChain.
size() - 1) - 2);
681 parent = parent->parentWidget();
682 parentChain << parent;
687 for (
auto w : matchedWidgets) {
689 m_searchMatchOverlays <<
new SearchMatchOverlay(w);
690 if (!w->isVisible()) {
691 const auto [tabWidget, page] = tabWidgetParent(w);
692 if (!tabWidget && !page) {
695 const int idx = tabWidget->indexOf(page);
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();
705 if (!alreadyOverlayed) {
706 m_searchMatchOverlays <<
new SearchMatchOverlay(tabWidget->tabBar(), idx);
744 d->dataChanged(topLeft, bottomRight);
749 d->view->setModel(
model);
765 d->faceType = faceType;
779 if (!d->view || !d->view->selectionModel()) {
789 if (!d->view || !d->view->selectionModel()) {
793 return d->view->selectionModel()->currentIndex();
800 d->view->setItemDelegate(delegate);
808 return d->view->itemDelegate();
820 bool isCurrent = (d->stack->currentIndex() == d->stack->indexOf(d->defaultWidget));
823 d->stack->removeWidget(d->defaultWidget);
824 delete d->defaultWidget;
827 d->defaultWidget = widget;
828 d->stack->addWidget(d->defaultWidget);
831 d->stack->setCurrentWidget(d->defaultWidget);
838 if (d->pageHeader == header) {
843 d->layout->removeWidget(d->pageHeader);
845 d->layout->removeWidget(d->titleWidget);
846 d->layout->removeWidget(d->actionsToolBar);
848 d->pageHeader = header;
852 d->layout->addWidget(d->pageHeader, 1, 1, 1, 1);
853 d->layout->addWidget(d->actionsToolBar, 1, 2);
856 d->layout->addWidget(d->titleWidget, 1, 1, 1, 1);
857 d->layout->addWidget(d->actionsToolBar, 1, 2);
865 if (!d->pageHeader) {
866 return d->titleWidget;
868 return d->pageHeader;
874 if (d->pageFooter == footer) {
879 d->layout->removeWidget(d->pageFooter);
882 d->pageFooter = footer;
885 d->pageFooter->setContentsMargins(4, 4, 4, 4);
886 d->layout->addWidget(d->pageFooter, 4, 1, 1, 2);
893 return d->pageFooter;
899 const FaceType faceType = d->effectiveFaceType();
901 if (faceType ==
Plain) {
902 return new KDEPrivate::KPagePlainView(
this);
905 return new KDEPrivate::KPageListView(
this);
907 if (faceType ==
List) {
908 auto view =
new KDEPrivate::KPageListView(
this);
909 view->setItemDelegate(
new KDEPrivate::KPageListViewDelegate(
this));
910 view->setFlexibleWidth(
true);
913 if (faceType ==
Tree) {
914 return new KDEPrivate::KPageTreeView(
this);
917 return new KDEPrivate::KPageTabbedView(
this);
926 const FaceType faceType = d->effectiveFaceType();
931 return d->pageHeader || !d->titleWidget->text().isEmpty();
938 const FaceType faceType = d->effectiveFaceType();
947#include "moc_kpageview.cpp"
@ ActionsRole
The list of actions associated to the page.
@ WidgetRole
A pointer to the page widget.
@ HeaderVisibleRole
when true, show the page header, if false don't
@ HeaderRole
A string to be rendered as page header.
A base class which can handle multiple pages.
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.
@ List
An icon list is used as navigation view.
@ Auto
Depending on the number of pages in the model, the Plain (one page), the List (several pages) or the ...
@ Tree
A tree list is used as navigation view.
@ Plain
No navigation view will be visible and only the first page of the model will be shown.
@ FlatList
A flat list with small icons is used as navigation view.
@ Tabbed
A tab widget is used as navigation view.
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.
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
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
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
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 isEmpty() const const
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