KWidgetsAddons

kpageview.cpp
1 /*
2  This file is part of the KDE Libraries
3  SPDX-FileCopyrightText: 2006 Tobias Koenig <[email protected]>
4  SPDX-FileCopyrightText: 2007 Rafael Fernández López <[email protected]>
5 
6  SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "kpageview.h"
10 #include "kpageview_p.h"
11 
12 #include "kpagemodel.h"
13 #include "loggingcategory.h"
14 
15 #include <ktitlewidget.h>
16 
17 #include <QAbstractItemView>
18 #include <QGridLayout>
19 #include <QSize>
20 #include <QTimer>
21 
22 void KPageViewPrivate::rebuildGui()
23 {
24  // clean up old view
25  Q_Q(KPageView);
26 
27  QModelIndex currentLastIndex;
28  if (view && view->selectionModel()) {
29  QObject::disconnect(m_selectionChangedConnection);
30  currentLastIndex = view->selectionModel()->currentIndex();
31  }
32 
33  delete view;
34  view = q->createView();
35 
36  Q_ASSERT(view);
37 
38  view->setSelectionBehavior(QAbstractItemView::SelectItems);
39  view->setSelectionMode(QAbstractItemView::SingleSelection);
40 
41  if (model) {
42  view->setModel(model);
43  }
44 
45  // setup new view
46  if (view->selectionModel()) {
47  m_selectionChangedConnection = QObject::connect(view->selectionModel(),
49  q,
50  [this](const QItemSelection &selected, const QItemSelection &deselected) {
51  pageSelected(selected, deselected);
52  });
53 
54  if (currentLastIndex.isValid()) {
55  view->selectionModel()->setCurrentIndex(currentLastIndex, QItemSelectionModel::Select);
56  } else if (model) {
57  view->selectionModel()->setCurrentIndex(model->index(0, 0), QItemSelectionModel::Select);
58  }
59  }
60 
61  if (faceType == KPageView::Tabbed) {
62  stack->setVisible(false);
63  layout->removeWidget(stack);
64  } else {
65  layout->addWidget(stack, 2, 1);
66  stack->setVisible(true);
67  }
68 
69  layout->removeWidget(titleWidget);
70 
71  if (pageHeader) {
72  layout->removeWidget(pageHeader);
73  pageHeader->setVisible(q->showPageHeader());
74  titleWidget->setVisible(false);
75 
76  if (faceType == KPageView::Tabbed) {
77  layout->addWidget(pageHeader, 1, 1);
78  } else {
79  layout->addWidget(pageHeader, 1, 1, 1, 2);
80  }
81  } else {
82  titleWidget->setVisible(q->showPageHeader());
83  if (faceType == KPageView::Tabbed) {
84  layout->addWidget(titleWidget, 1, 1);
85  } else {
86  layout->addWidget(titleWidget, 1, 1, 1, 2);
87  }
88  }
89 
90  Qt::Alignment alignment = q->viewPosition();
91  if (alignment & Qt::AlignTop) {
92  layout->addWidget(view, 2, 1);
93  } else if (alignment & Qt::AlignRight) {
94  layout->addWidget(view, 1, 2, 4, 1);
95  } else if (alignment & Qt::AlignBottom) {
96  layout->addWidget(view, 4, 1);
97  } else if (alignment & Qt::AlignLeft) {
98  layout->addWidget(view, 1, 0, 4, 1);
99  }
100 }
101 
102 void KPageViewPrivate::updateSelection()
103 {
104  // Select the first item in the view if not done yet.
105 
106  if (!model) {
107  return;
108  }
109 
110  if (!view || !view->selectionModel()) {
111  return;
112  }
113 
114  const QModelIndex index = view->selectionModel()->currentIndex();
115  if (!index.isValid()) {
116  view->selectionModel()->setCurrentIndex(model->index(0, 0), QItemSelectionModel::Select);
117  }
118 }
119 
120 void KPageViewPrivate::cleanupPages()
121 {
122  // Remove all orphan pages from the stacked widget.
123 
124  const QList<QWidget *> widgets = collectPages();
125 
126  for (int i = 0; i < stack->count(); ++i) {
127  QWidget *page = stack->widget(i);
128 
129  bool found = false;
130  for (int j = 0; j < widgets.count(); ++j) {
131  if (widgets[j] == page) {
132  found = true;
133  }
134  }
135 
136  if (!found) {
137  stack->removeWidget(page);
138  }
139  }
140 }
141 
142 QList<QWidget *> KPageViewPrivate::collectPages(const QModelIndex &parentIndex)
143 {
144  // Traverse through the model recursive and collect all widgets in
145  // a list.
146  QList<QWidget *> retval;
147 
148  int rows = model->rowCount(parentIndex);
149  for (int j = 0; j < rows; ++j) {
150  const QModelIndex index = model->index(j, 0, parentIndex);
151  retval.append(qvariant_cast<QWidget *>(model->data(index, KPageModel::WidgetRole)));
152 
153  if (model->rowCount(index) > 0) {
154  retval += collectPages(index);
155  }
156  }
157 
158  return retval;
159 }
160 
161 KPageView::FaceType KPageViewPrivate::effectiveFaceType() const
162 {
163  if (faceType == KPageView::Auto) {
164  return detectAutoFace();
165  }
166 
167  return faceType;
168 }
169 
170 KPageView::FaceType KPageViewPrivate::detectAutoFace() const
171 {
172  if (!model) {
173  return KPageView::Plain;
174  }
175 
176  // Check whether the model has sub pages.
177  bool hasSubPages = false;
178  const int count = model->rowCount();
179  for (int i = 0; i < count; ++i) {
180  if (model->rowCount(model->index(i, 0)) > 0) {
181  hasSubPages = true;
182  break;
183  }
184  }
185 
186  if (hasSubPages) {
187  return KPageView::Tree;
188  }
189 
190  if (model->rowCount() > 1) {
191  return KPageView::List;
192  }
193 
194  return KPageView::Plain;
195 }
196 
197 void KPageViewPrivate::modelChanged()
198 {
199  if (!model) {
200  return;
201  }
202 
203  // If the face type is Auto, we rebuild the GUI whenever the layout
204  // of the model changes.
205  if (faceType == KPageView::Auto) {
206  rebuildGui();
207  // If you discover some crashes use the line below instead...
208  // QTimer::singleShot(0, q, SLOT(rebuildGui()));
209  }
210 
211  // Set the stack to the minimum size of the largest widget.
212  QSize size = stack->size();
213  const QList<QWidget *> widgets = collectPages();
214  for (int i = 0; i < widgets.count(); ++i) {
215  const QWidget *widget = widgets[i];
216  if (widget) {
217  size = size.expandedTo(widget->minimumSizeHint());
218  }
219  }
220  stack->setMinimumSize(size);
221 
222  updateSelection();
223 }
224 
225 void KPageViewPrivate::pageSelected(const QItemSelection &index, const QItemSelection &previous)
226 {
227  if (!model) {
228  return;
229  }
230 
231  // Return if the current Index is not valid
232  if (index.indexes().size() != 1) {
233  return;
234  }
235  QModelIndex currentIndex = index.indexes().first();
236 
237  QModelIndex previousIndex;
238  // The previous index can be invalid
239  if (previous.indexes().size() == 1) {
240  previousIndex = previous.indexes().first();
241  }
242 
243  if (faceType != KPageView::Tabbed) {
244  QWidget *widget = qvariant_cast<QWidget *>(model->data(currentIndex, KPageModel::WidgetRole));
245 
246  if (widget) {
247  if (stack->indexOf(widget) == -1) { // not included yet
248  stack->addWidget(widget);
249  }
250 
251  stack->setCurrentWidget(widget);
252  } else {
253  stack->setCurrentWidget(defaultWidget);
254  }
255 
256  updateTitleWidget(currentIndex);
257  }
258 
259  Q_Q(KPageView);
260  Q_EMIT q->currentPageChanged(currentIndex, previousIndex);
261 }
262 
263 void KPageViewPrivate::updateTitleWidget(const QModelIndex &index)
264 {
265  Q_Q(KPageView);
266 
267  const bool headerVisible = model->data(index, KPageModel::HeaderVisibleRole).toBool();
268  if (!headerVisible) {
269  titleWidget->setVisible(false);
270  return;
271  }
272  QString header = model->data(index, KPageModel::HeaderRole).toString();
273  if (header.isNull()) { // TODO KF6 remove that ugly logic, see also doxy-comments in KPageWidgetItem::setHeader()
274  header = model->data(index, Qt::DisplayRole).toString();
275  }
276 
277  titleWidget->setText(header);
278 
279  titleWidget->setVisible(q->showPageHeader());
280 }
281 
282 void KPageViewPrivate::dataChanged(const QModelIndex &, const QModelIndex &)
283 {
284  // When data has changed we update the header and icon for the currently selected
285  // page.
286  if (!view) {
287  return;
288  }
289 
290  QModelIndex index = view->selectionModel()->currentIndex();
291  if (!index.isValid()) {
292  return;
293  }
294 
295  updateTitleWidget(index);
296 }
297 
298 KPageViewPrivate::KPageViewPrivate(KPageView *_parent)
299  : q_ptr(_parent)
300  , model(nullptr)
301  , faceType(KPageView::Auto)
302  , layout(nullptr)
303  , stack(nullptr)
304  , titleWidget(nullptr)
305  , view(nullptr)
306 {
307 }
308 
309 void KPageViewPrivate::init()
310 {
311  Q_Q(KPageView);
312  layout = new QGridLayout(q);
313  stack = new KPageStackedWidget(q);
314  titleWidget = new KTitleWidget(q);
315  layout->addWidget(titleWidget, 1, 1, 1, 2);
316  layout->addWidget(stack, 2, 1);
317 
318  defaultWidget = new QWidget(q);
319  stack->addWidget(defaultWidget);
320 
321  // stack should use most space
322  layout->setColumnStretch(1, 1);
323  layout->setRowStretch(2, 1);
324 }
325 
326 // KPageView Implementation
328  : KPageView(*new KPageViewPrivate(this), parent)
329 {
330 }
331 
332 KPageView::KPageView(KPageViewPrivate &dd, QWidget *parent)
333  : QWidget(parent)
334  , d_ptr(&dd)
335 {
336  d_ptr->init();
337 }
338 
339 KPageView::~KPageView() = default;
340 
342 {
343  Q_D(KPageView);
344  // clean up old model
345  if (d->model) {
346  disconnect(d->m_layoutChangedConnection);
347  disconnect(d->m_dataChangedConnection);
348  }
349 
350  d->model = model;
351 
352  if (d->model) {
353  d->m_layoutChangedConnection = connect(d->model, &QAbstractItemModel::layoutChanged, this, [d]() {
354  d->modelChanged();
355  });
356  d->m_dataChangedConnection = connect(d->model, &QAbstractItemModel::dataChanged, this, [d](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
357  d->dataChanged(topLeft, bottomRight);
358  });
359 
360  // set new model in navigation view
361  if (d->view) {
362  d->view->setModel(model);
363  }
364  }
365 
366  d->rebuildGui();
367 }
368 
370 {
371  Q_D(const KPageView);
372  return d->model;
373 }
374 
376 {
377  Q_D(KPageView);
378  d->faceType = faceType;
379 
380  d->rebuildGui();
381 }
382 
383 KPageView::FaceType KPageView::faceType() const
384 {
385  Q_D(const KPageView);
386  return d->faceType;
387 }
388 
390 {
391  Q_D(KPageView);
392  if (!d->view || !d->view->selectionModel()) {
393  return;
394  }
395 
396  d->view->selectionModel()->setCurrentIndex(index, QItemSelectionModel::SelectCurrent);
397 }
398 
400 {
401  Q_D(const KPageView);
402  if (!d->view || !d->view->selectionModel()) {
403  return QModelIndex();
404  }
405 
406  return d->view->selectionModel()->currentIndex();
407 }
408 
410 {
411  Q_D(KPageView);
412  if (d->view) {
413  d->view->setItemDelegate(delegate);
414  }
415 }
416 
418 {
419  Q_D(const KPageView);
420  if (d->view) {
421  return d->view->itemDelegate();
422  } else {
423  return nullptr;
424  }
425 }
426 
428 {
429  Q_D(KPageView);
430 
431  Q_ASSERT(widget);
432 
433  bool isCurrent = (d->stack->currentIndex() == d->stack->indexOf(d->defaultWidget));
434 
435  // remove old default widget
436  d->stack->removeWidget(d->defaultWidget);
437  delete d->defaultWidget;
438 
439  // add new default widget
440  d->defaultWidget = widget;
441  d->stack->addWidget(d->defaultWidget);
442 
443  if (isCurrent) {
444  d->stack->setCurrentWidget(d->defaultWidget);
445  }
446 }
447 
449 {
450  Q_D(KPageView);
451  if (d->pageHeader == header) {
452  return;
453  }
454 
455  if (d->pageHeader) {
456  d->layout->removeWidget(d->pageHeader);
457  }
458  d->layout->removeWidget(d->titleWidget);
459 
460  d->pageHeader = header;
461 
462  // Give it a colSpan of 2 to add a margin to the right
463  if (d->pageHeader) {
464  d->layout->addWidget(d->pageHeader, 1, 1, 1, 2);
465  d->pageHeader->setVisible(showPageHeader());
466  } else {
467  d->layout->addWidget(d->titleWidget, 1, 1, 1, 2);
468  d->titleWidget->setVisible(showPageHeader());
469  }
470 }
471 
473 {
474  Q_D(const KPageView);
475  if (!d->pageHeader) {
476  return d->titleWidget;
477  }
478  return d->pageHeader;
479 }
480 
482 {
483  Q_D(KPageView);
484  if (d->pageFooter == footer) {
485  return;
486  }
487 
488  if (d->pageFooter) {
489  d->layout->removeWidget(d->pageFooter);
490  }
491 
492  d->pageFooter = footer;
493 
494  if (footer) {
495  d->layout->addWidget(d->pageFooter, 3, 1);
496  }
497 }
498 
500 {
501  Q_D(const KPageView);
502  return d->pageFooter;
503 }
504 
506 {
507  Q_D(KPageView);
508  const FaceType faceType = d->effectiveFaceType();
509 
510  if (faceType == Plain) {
511  return new KDEPrivate::KPagePlainView(this);
512  }
513  if (faceType == FlatList) {
514  return new KDEPrivate::KPageListView(this);
515  }
516  if (faceType == List) {
517  auto view = new KDEPrivate::KPageListView(this);
518  view->setItemDelegate(new KDEPrivate::KPageListViewDelegate(this));
519  return view;
520  }
521  if (faceType == Tree) {
522  return new KDEPrivate::KPageTreeView(this);
523  }
524  if (faceType == Tabbed) {
525  return new KDEPrivate::KPageTabbedView(this);
526  }
527 
528  return nullptr;
529 }
530 
532 {
533  Q_D(const KPageView);
534  const FaceType faceType = d->effectiveFaceType();
535 
536  if (faceType == Tabbed) {
537  return false;
538  } else {
539  return d->pageHeader || !d->titleWidget->text().isEmpty();
540  }
541 }
542 
544 {
545  Q_D(const KPageView);
546  const FaceType faceType = d->effectiveFaceType();
547 
548  if (faceType == Plain || faceType == Tabbed) {
549  return Qt::AlignTop;
550  } else {
551  return Qt::AlignLeft;
552  }
553 }
554 
555 #include "moc_kpageview.cpp"
void append(const T &value)
@ Plain
No navigation view will be visible and only the first page of the model will be shown.
Definition: kpageview.h:72
typedef Alignment
bool isNull() const const
void setModel(QAbstractItemModel *model)
Sets the model of the page view.
Definition: kpageview.cpp:341
void setDefaultWidget(QWidget *widget)
Sets the widget which will be shown when a page is selected that has no own widget set.
Definition: kpageview.cpp:427
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
DisplayRole
virtual QAbstractItemView * createView()
Returns the navigation view, depending on the current face type.
Definition: kpageview.cpp:505
@ FlatList
A flat list with small icons is used as navigation view.
Definition: kpageview.h:88
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
void setCurrentPage(const QModelIndex &index)
Sets the page with.
Definition: kpageview.cpp:389
A base class which can handle multiple pages.
Definition: kpageview.h:49
int count(const T &value) const const
QLayout * layout() const const
void layoutChanged(const QList< QPersistentModelIndex > &parents, QAbstractItemModel::LayoutChangeHint hint)
void setPageFooter(QWidget *footer)
Set a widget as the footer for this Page view.
Definition: kpageview.cpp:481
virtual bool showPageHeader() const
Returns whether the page header should be visible.
Definition: kpageview.cpp:531
@ Tree
A tree list is used as navigation view.
Definition: kpageview.h:80
@ Auto
Depending on the number of pages in the model, the Plain (one page), the List (several pages) or the ...
Definition: kpageview.h:67
void setItemDelegate(QAbstractItemDelegate *delegate)
Sets the item.
Definition: kpageview.cpp:409
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QModelIndex currentPage() const
Returns the index for the current page or an invalid index if no current page exists.
Definition: kpageview.cpp:399
QModelIndexList indexes() const const
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles)
KPageView(QWidget *parent=nullptr)
Creates a page view with given parent.
Definition: kpageview.cpp:327
Standard title widget.
Definition: ktitlewidget.h:55
void setPageHeader(QWidget *header)
Set a widget as the header for this Page view It will replace the standard page title.
Definition: kpageview.cpp:448
@ Tabbed
A tab widget is used as navigation view.
Definition: kpageview.h:84
@ HeaderRole
A string to be rendered as page header.
Definition: kpagemodel.h:59
bool isValid() const const
QSize expandedTo(const QSize &otherSize) const const
void addWidget(QWidget *w)
@ HeaderVisibleRole
when true, show the page header, if false don't
Definition: kpagemodel.h:75
virtual Qt::Alignment viewPosition() const
Returns the position where the navigation view should be located according to the page stack.
Definition: kpageview.cpp:543
~KPageView() override
Destroys the page view.
@ WidgetRole
A pointer to the page widget.
Definition: kpagemodel.h:70
QAbstractItemDelegate * itemDelegate() const
Returns the item delegate of the page view.
Definition: kpageview.cpp:417
QChar * data()
QWidget * pageHeader() const
Widget of the header for this page view.
Definition: kpageview.cpp:472
FaceType
This enum is used to decide which type of navigation view shall be used in the page view.
Definition: kpageview.h:60
QWidget * pageFooter() const
Widget of the footer for this page view.
Definition: kpageview.cpp:499
QAbstractItemModel * model() const
Returns the model of the page view.
Definition: kpageview.cpp:369
@ List
An icon list is used as navigation view.
Definition: kpageview.h:76
void setFaceType(FaceType faceType)
Sets the face type of the page view.
Definition: kpageview.cpp:375
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Tue Oct 3 2023 04:01:07 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.