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
51class SearchMatchOverlay :
public QWidget
85 const QRect r = tabBar->tabRect(m_tabIdx);
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 titleWidget->setAutoFillBackground(
false);
170 layout->setSpacing(0);
171 separatorLine->setVisible(
false);
172 titleWidget->setObjectName(
"KPageView::TitleWidgetNonSearchable");
178 titleWidget->setObjectName(
"KPageView::TitleWidget");
179 searchLineEditContainer->setVisible(
true);
181 separatorLine->setVisible(
true);
184 searchLineEditContainer->setContentsMargins(4, 3, 4, 3);
185 titleWidget->setContentsMargins(5, 4, 4, 2);
188 layout->setSpacing(0);
189 layout->setContentsMargins({});
192 layout->removeWidget(titleWidget);
193 layout->removeWidget(actionsToolBar);
195 actionsToolBar->setVisible(q->showPageHeader());
197 layout->removeWidget(pageHeader);
198 pageHeader->setVisible(q->showPageHeader());
199 titleWidget->setVisible(
false);
202 layout->addWidget(pageHeader, 1, 1);
204 layout->addWidget(pageHeader, 1, 1);
205 layout->addWidget(actionsToolBar, 1, 2);
208 titleWidget->setVisible(q->showPageHeader());
210 layout->addWidget(titleWidget, 1, 1);
212 layout->addWidget(titleWidget, 1, 1);
213 layout->addWidget(actionsToolBar, 1, 2);
219 layout->addWidget(view, 2, 1);
224 layout->addWidget(view, 3, 2, 3, 1);
226 layout->addWidget(view, 4, 1);
231 layout->addWidget(view, 3, 0, 3, 1);
235void KPageViewPrivate::updateSelection()
243 if (!view || !view->selectionModel()) {
247 const QModelIndex index = view->selectionModel()->currentIndex();
253void KPageViewPrivate::cleanupPages()
259 for (
int i = 0; i < stack->count(); ++i) {
260 QWidget *page = stack->widget(i);
263 for (
int j = 0; j < widgets.
count(); ++j) {
264 if (widgets[j] == page) {
270 stack->removeWidget(page);
281 int rows = model->rowCount(parentIndex);
282 for (
int j = 0; j < rows; ++j) {
283 const QModelIndex index = model->index(j, 0, parentIndex);
286 if (model->rowCount(index) > 0) {
287 retval += collectPages(index);
297 return detectAutoFace();
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) {
323 if (model->rowCount() > 1) {
330void KPageViewPrivate::modelChanged()
345 QSize size = stack->size();
347 for (
int i = 0; i < widgets.
count(); ++i) {
348 const QWidget *widget = widgets[i];
353 stack->setMinimumSize(size);
365 if (index.
indexes().size() != 1) {
372 if (previous.
indexes().size() == 1) {
373 previousIndex = previous.
indexes().first();
380 if (stack->indexOf(widget) == -1) {
381 stack->addWidget(widget);
384 stack->setCurrentWidget(widget);
386 stack->setCurrentWidget(defaultWidget);
389 updateTitleWidget(currentIndex);
390 updateActionsLayout(currentIndex, previousIndex);
394 Q_EMIT q->currentPageChanged(currentIndex, previousIndex);
397void KPageViewPrivate::updateTitleWidget(
const QModelIndex &index)
402 if (!headerVisible) {
403 titleWidget->setVisible(
false);
411 titleWidget->setText(header);
413 titleWidget->setVisible(q->showPageHeader());
422 for (
const auto action : previousActions) {
423 actionsToolBar->removeAction(action);
428 if (actions.isEmpty()) {
429 actionsToolBar->hide();
431 actionsToolBar->show();
432 for (
const auto action : actions) {
433 actionsToolBar->addAction(action);
446 QModelIndex index = view->selectionModel()->currentIndex();
451 updateTitleWidget(index);
454KPageViewPrivate::KPageViewPrivate(
KPageView *_parent)
460 , titleWidget(nullptr)
461 , searchLineEditContainer(nullptr)
462 , searchLineEdit(nullptr)
467void KPageViewPrivate::init()
471 stack =
new KPageStackedWidget(q);
474 titleWidget->setObjectName(
"KPageView::TitleWidget");
477 separatorLine =
new QFrame(q);
479 separatorLine->setFixedHeight(1);
483 actionsToolBar->setObjectName(
QLatin1String(
"KPageView::TitleWidget"));
486 actionsToolBar->setStyle(
new NoPaddingToolBarProxyStyle);
487 actionsToolBar->show();
490 layout->addWidget(titleWidget, 1, 1);
491 layout->addWidget(actionsToolBar, 1, 1);
493 layout->addWidget(separatorLine, 2, 0, 1, 3);
495 layout->addWidget(stack, 3, 1, 1, 2);
497 defaultWidget =
new QWidget(q);
498 stack->addWidget(defaultWidget);
501 layout->setColumnStretch(1, 1);
502 layout->setRowStretch(3, 1);
504 searchLineEdit =
new QLineEdit(defaultWidget);
505 searchTimer.setInterval(400);
506 searchTimer.setSingleShot(
true);
507 searchTimer.callOnTimeout(q, [
this] {
508 onSearchTextChanged();
510 q->setFocusProxy(searchLineEdit);
511 searchLineEdit->setPlaceholderText(
KPageView::tr(
"Search…",
"@info:placeholder"));
512 searchLineEdit->setClearButtonEnabled(
true);
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");
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) {
535 ret << getAllPages(model, child);
541template<
typename W
idgetType>
545 const auto widgets = page->
findChildren<WidgetType *>();
546 for (
auto label : widgets) {
559 for (
auto cb : comboxBoxes) {
560 if (cb->findText(text, Qt::MatchFlag::MatchContains) != -1) {
568struct FindChildrenHelper {
575template<
typename First,
typename... Rest>
576struct FindChildrenHelper<First, Rest...> {
579 return hasMatchingText<First>(text, page) << FindChildrenHelper<Rest...>::hasMatchingTextForTypes(text, page);
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);
593 current = model->index(i, 0, parent);
595 auto curr = walkTreeAndHideItems(tree, searchText, pagesToHide, index);
603bool KPageViewPrivate::hasSearchableView()
const
606 return qobject_cast<KDEPrivate::KPageListView *>(view) || qobject_cast<KDEPrivate::KPageTreeView *>(view);
609void KPageViewPrivate::onSearchTextChanged()
611 if (!hasSearchableView()) {
615 const QString text = searchLineEdit->text();
617 std::vector<QWidget *> matchedWidgets;
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();
625 matchedWidgets.insert(matchedWidgets.end(), matchingWidgets.begin(), matchingWidgets.end());
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();
636 current = model->index(i, 0);
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();
647 qWarning() <<
"Unreacheable, unknown view:" << view;
652 view->setCurrentIndex(current);
656 qDeleteAll(m_searchMatchOverlays);
657 m_searchMatchOverlays.clear();
659 using TabWidgetAndPage = QPair<QTabWidget *, QWidget *>;
660 auto tabWidgetParent = [](
QWidget *w) {
664 auto parent = w->parentWidget();
665 TabWidgetAndPage p = {
nullptr,
nullptr};
667 parentChain << parent;
669 if (
auto tw = qobject_cast<QTabWidget *>(parent)) {
670 if (parentChain.
size() >= 3) {
674 p.second = parentChain.
value((parentChain.
size() - 1) - 2);
679 parent = parent->parentWidget();
680 parentChain << parent;
685 for (
auto w : matchedWidgets) {
687 m_searchMatchOverlays <<
new SearchMatchOverlay(w);
688 if (!w->isVisible()) {
689 const auto [tabWidget, page] = tabWidgetParent(w);
690 if (!tabWidget && !page) {
693 const int idx = tabWidget->indexOf(page);
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();
703 if (!alreadyOverlayed) {
704 m_searchMatchOverlays <<
new SearchMatchOverlay(tabWidget->tabBar(), idx);
713 :
KPageView(*new KPageViewPrivate(this), parent)
742 d->dataChanged(topLeft, bottomRight);
747 d->view->setModel(
model);
763 d->faceType = faceType;
777 if (!d->view || !d->view->selectionModel()) {
787 if (!d->view || !d->view->selectionModel()) {
791 return d->view->selectionModel()->currentIndex();
798 d->view->setItemDelegate(delegate);
806 return d->view->itemDelegate();
818 bool isCurrent = (d->stack->currentIndex() == d->stack->indexOf(d->defaultWidget));
821 d->stack->removeWidget(d->defaultWidget);
822 delete d->defaultWidget;
825 d->defaultWidget = widget;
826 d->stack->addWidget(d->defaultWidget);
829 d->stack->setCurrentWidget(d->defaultWidget);
836 if (d->pageHeader == header) {
841 d->layout->removeWidget(d->pageHeader);
843 d->layout->removeWidget(d->titleWidget);
844 d->layout->removeWidget(d->actionsToolBar);
846 d->pageHeader = header;
850 d->layout->addWidget(d->pageHeader, 1, 1, 1, 1);
851 d->layout->addWidget(d->actionsToolBar, 1, 2);
854 d->layout->addWidget(d->titleWidget, 1, 1, 1, 1);
855 d->layout->addWidget(d->actionsToolBar, 1, 2);
863 if (!d->pageHeader) {
864 return d->titleWidget;
866 return d->pageHeader;
872 if (d->pageFooter == footer) {
877 d->layout->removeWidget(d->pageFooter);
880 d->pageFooter = footer;
883 d->pageFooter->setContentsMargins(4, 4, 4, 4);
884 d->layout->addWidget(d->pageFooter, 4, 1, 1, 2);
891 return d->pageFooter;
897 const FaceType faceType = d->effectiveFaceType();
899 if (faceType ==
Plain) {
900 return new KDEPrivate::KPagePlainView(
this);
903 return new KDEPrivate::KPageListView(
this);
905 if (faceType ==
List) {
906 auto view =
new KDEPrivate::KPageListView(
this);
907 view->setItemDelegate(
new KDEPrivate::KPageListViewDelegate(
this));
908 view->setFlexibleWidth(
true);
911 if (faceType ==
Tree) {
912 return new KDEPrivate::KPageTreeView(
this);
915 return new KDEPrivate::KPageTabbedView(
this);
924 const FaceType faceType = d->effectiveFaceType();
929 return d->pageHeader || !d->titleWidget->text().isEmpty();
936 const FaceType faceType = d->effectiveFaceType();
945#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
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
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