12#include "kpageview_p.h"
14#include "kpagemodel.h"
15#include "kpagewidgetmodel.h"
16#include "loggingcategory.h"
18#include <ktitlewidget.h>
20#include <QAbstractButton>
21#include <QAbstractItemView>
22#include <QApplication>
34#include <QWidgetAction>
37class NoPaddingToolBarProxyStyle :
public QProxyStyle
50class SearchMatchOverlay :
public QWidget
84 const QRect r = tabBar->tabRect(m_tabIdx);
107 p.setClipRegion(e->
region());
110 p.fillRect(
rect(), c);
116void KPageViewPrivate::rebuildGui()
122 if (view && view->selectionModel()) {
124 currentLastIndex = view->selectionModel()->currentIndex();
128 view = q->createView();
136 view->setModel(model);
140 if (view->selectionModel()) {
145 pageSelected(selected, deselected);
148 if (currentLastIndex.
isValid()) {
156 stack->setVisible(
false);
157 layout->removeWidget(stack);
159 layout->addWidget(stack, 3, 1, 1, 2);
160 stack->setVisible(
true);
163 titleWidget->setPalette(qApp->palette(titleWidget));
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");
177 titleWidget->setObjectName(
"KPageView::TitleWidget");
178 searchLineEditContainer->setVisible(
true);
180 separatorLine->setVisible(
true);
183 searchLineEditContainer->setContentsMargins(4, 3, 4, 3);
184 titleWidget->setContentsMargins(5, 4, 4, 2);
187 layout->setSpacing(0);
188 layout->setContentsMargins({});
191 layout->removeWidget(titleWidget);
192 layout->removeWidget(actionsToolBar);
194 actionsToolBar->setVisible(q->showPageHeader());
196 layout->removeWidget(pageHeader);
197 pageHeader->setVisible(q->showPageHeader());
198 titleWidget->setVisible(
false);
201 layout->addWidget(pageHeader, 1, 1);
203 layout->addWidget(pageHeader, 1, 1);
204 layout->addWidget(actionsToolBar, 1, 2);
207 titleWidget->setVisible(q->showPageHeader());
209 layout->addWidget(titleWidget, 1, 1);
211 layout->addWidget(titleWidget, 1, 1);
212 layout->addWidget(actionsToolBar, 1, 2);
218 layout->addWidget(view, 2, 1);
223 layout->addWidget(view, 3, 2, 3, 1);
225 layout->addWidget(view, 4, 1);
230 layout->addWidget(view, 3, 0, 3, 1);
234void KPageViewPrivate::updateSelection()
242 if (!view || !view->selectionModel()) {
246 const QModelIndex index = view->selectionModel()->currentIndex();
252void KPageViewPrivate::cleanupPages()
258 for (
int i = 0; i < stack->count(); ++i) {
259 QWidget *page = stack->widget(i);
262 for (
int j = 0; j < widgets.
count(); ++j) {
263 if (widgets[j] == page) {
269 stack->removeWidget(page);
280 int rows = model->rowCount(parentIndex);
281 for (
int j = 0; j < rows; ++j) {
282 const QModelIndex index = model->index(j, 0, parentIndex);
285 if (model->rowCount(index) > 0) {
286 retval += collectPages(index);
296 return detectAutoFace();
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) {
322 if (model->rowCount() > 1) {
329void KPageViewPrivate::modelChanged()
344 QSize size = stack->size();
346 for (
int i = 0; i < widgets.
count(); ++i) {
347 const QWidget *widget = widgets[i];
352 stack->setMinimumSize(size);
364 if (index.
indexes().size() != 1) {
371 if (previous.
indexes().size() == 1) {
372 previousIndex = previous.
indexes().first();
379 if (stack->indexOf(widget) == -1) {
380 stack->addWidget(widget);
383 stack->setCurrentWidget(widget);
385 stack->setCurrentWidget(defaultWidget);
388 updateTitleWidget(currentIndex);
389 updateActionsLayout(currentIndex, previousIndex);
393 Q_EMIT q->currentPageChanged(currentIndex, previousIndex);
396void KPageViewPrivate::updateTitleWidget(
const QModelIndex &index)
401 if (!headerVisible) {
402 titleWidget->setVisible(
false);
410 titleWidget->setText(header);
412 titleWidget->setVisible(q->showPageHeader());
421 for (
const auto action : previousActions) {
422 actionsToolBar->removeAction(action);
427 if (actions.isEmpty()) {
428 actionsToolBar->hide();
430 actionsToolBar->show();
431 for (
const auto action : actions) {
432 actionsToolBar->addAction(action);
445 QModelIndex index = view->selectionModel()->currentIndex();
450 updateTitleWidget(index);
453KPageViewPrivate::KPageViewPrivate(
KPageView *_parent)
459 , titleWidget(nullptr)
460 , searchLineEditContainer(nullptr)
461 , searchLineEdit(nullptr)
466void KPageViewPrivate::init()
470 stack =
new KPageStackedWidget(q);
473 titleWidget->setObjectName(
"KPageView::TitleWidget");
476 separatorLine =
new QFrame(q);
478 separatorLine->setFixedHeight(1);
482 actionsToolBar->setObjectName(
QLatin1String(
"KPageView::TitleWidget"));
485 actionsToolBar->setStyle(
new NoPaddingToolBarProxyStyle);
486 actionsToolBar->show();
489 layout->addWidget(titleWidget, 1, 1);
490 layout->addWidget(actionsToolBar, 1, 1);
492 layout->addWidget(separatorLine, 2, 0, 1, 3);
494 layout->addWidget(stack, 3, 1, 1, 2);
496 defaultWidget =
new QWidget(q);
497 stack->addWidget(defaultWidget);
500 layout->setColumnStretch(1, 1);
501 layout->setRowStretch(3, 1);
503 searchLineEdit =
new QLineEdit(defaultWidget);
504 searchTimer.setInterval(400);
505 searchTimer.setSingleShot(
true);
506 searchTimer.callOnTimeout(q, [
this] {
507 onSearchTextChanged();
509 q->setFocusProxy(searchLineEdit);
510 searchLineEdit->setPlaceholderText(
KPageView::tr(
"Search…",
"@info:placeholder"));
511 searchLineEdit->setClearButtonEnabled(
true);
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");
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) {
534 ret << getAllPages(model, child);
540template<
typename W
idgetType>
544 const auto widgets = page->
findChildren<WidgetType *>();
545 for (
auto label : widgets) {
558 for (
auto cb : comboxBoxes) {
559 if (cb->findText(text, Qt::MatchFlag::MatchContains) != -1) {
567struct FindChildrenHelper {
574template<
typename First,
typename... Rest>
575struct FindChildrenHelper<First, Rest...> {
578 return hasMatchingText<First>(text, page) << FindChildrenHelper<Rest...>::hasMatchingTextForTypes(text, page);
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);
592 current = model->index(i, 0, parent);
594 auto curr = walkTreeAndHideItems(tree, searchText, pagesToHide, index);
602bool KPageViewPrivate::hasSearchableView()
const
605 return qobject_cast<KDEPrivate::KPageListView *>(view) || qobject_cast<KDEPrivate::KPageTreeView *>(view);
608void KPageViewPrivate::onSearchTextChanged()
610 if (!hasSearchableView()) {
614 const QString text = searchLineEdit->text();
616 std::vector<QWidget *> matchedWidgets;
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();
624 matchedWidgets.insert(matchedWidgets.end(), matchingWidgets.begin(), matchingWidgets.end());
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();
635 current = model->index(i, 0);
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();
646 qWarning() <<
"Unreacheable, unknown view:" << view;
651 view->setCurrentIndex(current);
655 qDeleteAll(m_searchMatchOverlays);
656 m_searchMatchOverlays.clear();
658 using TabWidgetAndPage = QPair<QTabWidget *, QWidget *>;
659 auto tabWidgetParent = [](
QWidget *w) {
663 auto parent = w->parentWidget();
664 TabWidgetAndPage p = {
nullptr,
nullptr};
666 parentChain << parent;
668 if (
auto tw = qobject_cast<QTabWidget *>(parent)) {
669 if (parentChain.
size() >= 3) {
673 p.second = parentChain.
value((parentChain.
size() - 1) - 2);
678 parent = parent->parentWidget();
679 parentChain << parent;
684 for (
auto w : matchedWidgets) {
686 m_searchMatchOverlays <<
new SearchMatchOverlay(w);
687 if (!w->isVisible()) {
688 const auto [tabWidget, page] = tabWidgetParent(w);
689 if (!tabWidget && !page) {
692 const int idx = tabWidget->indexOf(page);
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();
702 if (!alreadyOverlayed) {
703 m_searchMatchOverlays <<
new SearchMatchOverlay(tabWidget->tabBar(), idx);
712 :
KPageView(*new KPageViewPrivate(this), parent)
741 d->dataChanged(topLeft, bottomRight);
746 d->view->setModel(
model);
762 d->faceType = faceType;
776 if (!d->view || !d->view->selectionModel()) {
786 if (!d->view || !d->view->selectionModel()) {
790 return d->view->selectionModel()->currentIndex();
797 d->view->setItemDelegate(delegate);
805 return d->view->itemDelegate();
817 bool isCurrent = (d->stack->currentIndex() == d->stack->indexOf(d->defaultWidget));
820 d->stack->removeWidget(d->defaultWidget);
821 delete d->defaultWidget;
824 d->defaultWidget = widget;
825 d->stack->addWidget(d->defaultWidget);
828 d->stack->setCurrentWidget(d->defaultWidget);
835 if (d->pageHeader == header) {
840 d->layout->removeWidget(d->pageHeader);
842 d->layout->removeWidget(d->titleWidget);
843 d->layout->removeWidget(d->actionsToolBar);
845 d->pageHeader = header;
849 d->layout->addWidget(d->pageHeader, 1, 1, 1, 1);
850 d->layout->addWidget(d->actionsToolBar, 1, 2);
853 d->layout->addWidget(d->titleWidget, 1, 1, 1, 1);
854 d->layout->addWidget(d->actionsToolBar, 1, 2);
862 if (!d->pageHeader) {
863 return d->titleWidget;
865 return d->pageHeader;
871 if (d->pageFooter == footer) {
876 d->layout->removeWidget(d->pageFooter);
879 d->pageFooter = footer;
882 d->pageFooter->setContentsMargins(4, 4, 4, 4);
883 d->layout->addWidget(d->pageFooter, 4, 1, 1, 2);
890 return d->pageFooter;
896 const FaceType faceType = d->effectiveFaceType();
898 if (faceType ==
Plain) {
899 return new KDEPrivate::KPagePlainView(
this);
902 return new KDEPrivate::KPageListView(
this);
904 if (faceType ==
List) {
905 auto view =
new KDEPrivate::KPageListView(
this);
906 view->setItemDelegate(
new KDEPrivate::KPageListViewDelegate(
this));
907 view->setFlexibleWidth(
true);
910 if (faceType ==
Tree) {
911 return new KDEPrivate::KPageTreeView(
this);
914 return new KDEPrivate::KPageTabbedView(
this);
923 const FaceType faceType = d->effectiveFaceType();
928 return d->pageHeader || !d->titleWidget->text().isEmpty();
935 const FaceType faceType = d->effectiveFaceType();
944#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