1 | // Copyright (C) 2016 The Qt Company Ltd. |
---|---|
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qdatawidgetmapper.h" |
5 | |
6 | #include "qabstractitemmodel.h" |
7 | #include "qmetaobject.h" |
8 | #include "qwidget.h" |
9 | #include "qstyleditemdelegate.h" |
10 | |
11 | #include "private/qobject_p.h" |
12 | #include "private/qabstractitemmodel_p.h" |
13 | #include <QtCore/qpointer.h> |
14 | |
15 | #include <array> |
16 | #include <iterator> |
17 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | class QDataWidgetMapperPrivate: public QObjectPrivate |
21 | { |
22 | public: |
23 | Q_DECLARE_PUBLIC(QDataWidgetMapper) |
24 | |
25 | QDataWidgetMapperPrivate() |
26 | : model(QAbstractItemModelPrivate::staticEmptyModel()), delegate(nullptr), |
27 | orientation(Qt::Horizontal), submitPolicy(QDataWidgetMapper::AutoSubmit) |
28 | { |
29 | } |
30 | |
31 | QAbstractItemModel *model; |
32 | QAbstractItemDelegate *delegate; |
33 | Qt::Orientation orientation; |
34 | QDataWidgetMapper::SubmitPolicy submitPolicy; |
35 | QPersistentModelIndex rootIndex; |
36 | QPersistentModelIndex currentTopLeft; |
37 | |
38 | inline int itemCount() |
39 | { |
40 | return orientation == Qt::Horizontal |
41 | ? model->rowCount(parent: rootIndex) |
42 | : model->columnCount(parent: rootIndex); |
43 | } |
44 | |
45 | inline int currentIdx() const |
46 | { |
47 | return orientation == Qt::Horizontal ? currentTopLeft.row() : currentTopLeft.column(); |
48 | } |
49 | |
50 | inline QModelIndex indexAt(int itemPos) |
51 | { |
52 | return orientation == Qt::Horizontal |
53 | ? model->index(row: currentIdx(), column: itemPos, parent: rootIndex) |
54 | : model->index(row: itemPos, column: currentIdx(), parent: rootIndex); |
55 | } |
56 | |
57 | void flipEventFilters(QAbstractItemDelegate *oldDelegate, |
58 | QAbstractItemDelegate *newDelegate) const |
59 | { |
60 | for (const WidgetMapper &e : widgetMap) { |
61 | QWidget *w = e.widget; |
62 | if (!w) |
63 | continue; |
64 | w->removeEventFilter(obj: oldDelegate); |
65 | w->installEventFilter(filterObj: newDelegate); |
66 | } |
67 | } |
68 | |
69 | void populate(); |
70 | |
71 | // private slots |
72 | void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, |
73 | const QList<int> &); |
74 | void commitData(QWidget *); |
75 | void closeEditor(QWidget *, QAbstractItemDelegate::EndEditHint); |
76 | void modelDestroyed(); |
77 | |
78 | void disconnectModel() |
79 | { |
80 | for (const QMetaObject::Connection &connection : modelConnections) |
81 | QObject::disconnect(connection); |
82 | } |
83 | void disconnectDelegate() |
84 | { |
85 | for (const QMetaObject::Connection &connection : delegateConnections) |
86 | QObject::disconnect(connection); |
87 | } |
88 | |
89 | struct WidgetMapper |
90 | { |
91 | QPointer<QWidget> widget; |
92 | int section; |
93 | QPersistentModelIndex currentIndex; |
94 | QByteArray property; |
95 | }; |
96 | |
97 | void populate(WidgetMapper &m); |
98 | int findWidget(QWidget *w) const; |
99 | |
100 | bool commit(const WidgetMapper &m); |
101 | |
102 | std::vector<WidgetMapper> widgetMap; |
103 | std::array<QMetaObject::Connection, 2> modelConnections; |
104 | std::array<QMetaObject::Connection, 2> delegateConnections; |
105 | }; |
106 | Q_DECLARE_TYPEINFO(QDataWidgetMapperPrivate::WidgetMapper, Q_RELOCATABLE_TYPE); |
107 | |
108 | int QDataWidgetMapperPrivate::findWidget(QWidget *w) const |
109 | { |
110 | for (const WidgetMapper &e : widgetMap) { |
111 | if (e.widget == w) |
112 | return int(&e - &widgetMap.front()); |
113 | } |
114 | return -1; |
115 | } |
116 | |
117 | bool QDataWidgetMapperPrivate::commit(const WidgetMapper &m) |
118 | { |
119 | if (m.widget.isNull()) |
120 | return true; // just ignore |
121 | |
122 | if (!m.currentIndex.isValid()) |
123 | return false; |
124 | |
125 | // Create copy to avoid passing the widget mappers data |
126 | QModelIndex idx = m.currentIndex; |
127 | if (m.property.isEmpty()) |
128 | delegate->setModelData(editor: m.widget, model, index: idx); |
129 | else |
130 | model->setData(index: idx, value: m.widget->property(name: m.property), role: Qt::EditRole); |
131 | |
132 | return true; |
133 | } |
134 | |
135 | void QDataWidgetMapperPrivate::populate(WidgetMapper &m) |
136 | { |
137 | if (m.widget.isNull()) |
138 | return; |
139 | |
140 | m.currentIndex = indexAt(itemPos: m.section); |
141 | if (m.property.isEmpty()) |
142 | delegate->setEditorData(editor: m.widget, index: m.currentIndex); |
143 | else |
144 | m.widget->setProperty(name: m.property, value: m.currentIndex.data(role: Qt::EditRole)); |
145 | } |
146 | |
147 | void QDataWidgetMapperPrivate::populate() |
148 | { |
149 | for (WidgetMapper &e : widgetMap) |
150 | populate(m&: e); |
151 | } |
152 | |
153 | static bool qContainsIndex(const QModelIndex &idx, const QModelIndex &topLeft, |
154 | const QModelIndex &bottomRight) |
155 | { |
156 | return idx.row() >= topLeft.row() && idx.row() <= bottomRight.row() |
157 | && idx.column() >= topLeft.column() && idx.column() <= bottomRight.column(); |
158 | } |
159 | |
160 | void QDataWidgetMapperPrivate::dataChanged(const QModelIndex &topLeft, |
161 | const QModelIndex &bottomRight, const QList<int> &) |
162 | { |
163 | if (topLeft.parent() != rootIndex) |
164 | return; // not in our hierarchy |
165 | |
166 | for (WidgetMapper &e : widgetMap) { |
167 | if (qContainsIndex(idx: e.currentIndex, topLeft, bottomRight)) |
168 | populate(m&: e); |
169 | } |
170 | } |
171 | |
172 | void QDataWidgetMapperPrivate::commitData(QWidget *w) |
173 | { |
174 | if (submitPolicy == QDataWidgetMapper::ManualSubmit) |
175 | return; |
176 | |
177 | int idx = findWidget(w); |
178 | if (idx == -1) |
179 | return; // not our widget |
180 | |
181 | commit(m: widgetMap[idx]); |
182 | } |
183 | |
184 | void QDataWidgetMapperPrivate::closeEditor(QWidget *w, QAbstractItemDelegate::EndEditHint hint) |
185 | { |
186 | int idx = findWidget(w); |
187 | if (idx == -1) |
188 | return; // not our widget |
189 | |
190 | switch (hint) { |
191 | case QAbstractItemDelegate::RevertModelCache: { |
192 | populate(m&: widgetMap[idx]); |
193 | break; } |
194 | case QAbstractItemDelegate::EditNextItem: |
195 | w->focusNextChild(); |
196 | break; |
197 | case QAbstractItemDelegate::EditPreviousItem: |
198 | w->focusPreviousChild(); |
199 | break; |
200 | case QAbstractItemDelegate::SubmitModelCache: |
201 | case QAbstractItemDelegate::NoHint: |
202 | // nothing |
203 | break; |
204 | } |
205 | } |
206 | |
207 | void QDataWidgetMapperPrivate::modelDestroyed() |
208 | { |
209 | Q_Q(QDataWidgetMapper); |
210 | |
211 | model = nullptr; |
212 | q->setModel(QAbstractItemModelPrivate::staticEmptyModel()); |
213 | } |
214 | |
215 | /*! |
216 | \class QDataWidgetMapper |
217 | \brief The QDataWidgetMapper class provides mapping between a section |
218 | of a data model to widgets. |
219 | \since 4.2 |
220 | \ingroup model-view |
221 | \ingroup advanced |
222 | \inmodule QtWidgets |
223 | |
224 | QDataWidgetMapper can be used to create data-aware widgets by mapping |
225 | them to sections of an item model. A section is a column of a model |
226 | if the orientation is horizontal (the default), otherwise a row. |
227 | |
228 | Every time the current index changes, each widget is updated with data |
229 | from the model via the property specified when its mapping was made. |
230 | If the user edits the contents of a widget, the changes are read using |
231 | the same property and written back to the model. |
232 | By default, each widget's \l{Q_PROPERTY()}{user property} is used to |
233 | transfer data between the model and the widget. Since Qt 4.3, an |
234 | additional addMapping() function enables a named property to be used |
235 | instead of the default user property. |
236 | |
237 | It is possible to set an item delegate to support custom widgets. By default, |
238 | a QStyledItemDelegate is used to synchronize the model with the widgets. |
239 | |
240 | Let us assume that we have an item model named \c{model} with the following contents: |
241 | |
242 | \table |
243 | \row \li 1 \li Qt Norway \li Oslo |
244 | \row \li 2 \li Qt Australia \li Brisbane |
245 | \row \li 3 \li Qt USA \li Palo Alto |
246 | \row \li 4 \li Qt China \li Beijing |
247 | \row \li 5 \li Qt Germany \li Berlin |
248 | \endtable |
249 | |
250 | The following code will map the columns of the model to widgets called \c mySpinBox, |
251 | \c myLineEdit and \c{myCountryChooser}: |
252 | |
253 | \snippet code/src_gui_itemviews_qdatawidgetmapper.cpp 0 |
254 | |
255 | After the call to toFirst(), \c mySpinBox displays the value \c{1}, \c myLineEdit |
256 | displays \c{Qt Norway} and \c myCountryChooser displays \c{Oslo}. The |
257 | navigational functions toFirst(), toNext(), toPrevious(), toLast() and setCurrentIndex() |
258 | can be used to navigate in the model and update the widgets with contents from |
259 | the model. |
260 | |
261 | The setRootIndex() function enables a particular item in a model to be |
262 | specified as the root index - children of this item will be mapped to |
263 | the relevant widgets in the user interface. |
264 | |
265 | QDataWidgetMapper supports two submit policies, \c AutoSubmit and \c{ManualSubmit}. |
266 | \c AutoSubmit will update the model as soon as the current widget loses focus, |
267 | \c ManualSubmit will not update the model unless submit() is called. \c ManualSubmit |
268 | is useful when displaying a dialog that lets the user cancel all modifications. |
269 | Also, other views that display the model won't update until the user finishes |
270 | all their modifications and submits. |
271 | |
272 | Note that QDataWidgetMapper keeps track of external modifications. If the contents |
273 | of the model are updated in another module of the application, the widgets are |
274 | updated as well. |
275 | |
276 | \sa QAbstractItemModel, QAbstractItemDelegate |
277 | */ |
278 | |
279 | /*! \enum QDataWidgetMapper::SubmitPolicy |
280 | |
281 | This enum describes the possible submit policies a QDataWidgetMapper |
282 | supports. |
283 | |
284 | \value AutoSubmit Whenever a widget loses focus, the widget's current |
285 | value is set to the item model. |
286 | \value ManualSubmit The model is not updated until submit() is called. |
287 | */ |
288 | |
289 | /*! |
290 | \fn void QDataWidgetMapper::currentIndexChanged(int index) |
291 | |
292 | This signal is emitted after the current index has changed and |
293 | all widgets were populated with new data. \a index is the new |
294 | current index. |
295 | |
296 | \sa currentIndex(), setCurrentIndex() |
297 | */ |
298 | |
299 | /*! |
300 | Constructs a new QDataWidgetMapper with parent object \a parent. |
301 | By default, the orientation is horizontal and the submit policy |
302 | is \c{AutoSubmit}. |
303 | |
304 | \sa setOrientation(), setSubmitPolicy() |
305 | */ |
306 | QDataWidgetMapper::QDataWidgetMapper(QObject *parent) |
307 | : QObject(*new QDataWidgetMapperPrivate, parent) |
308 | { |
309 | setItemDelegate(new QStyledItemDelegate(this)); |
310 | } |
311 | |
312 | /*! |
313 | Destroys the object. |
314 | */ |
315 | QDataWidgetMapper::~QDataWidgetMapper() |
316 | { |
317 | Q_D(QDataWidgetMapper); |
318 | d->disconnectModel(); |
319 | d->disconnectDelegate(); |
320 | } |
321 | |
322 | /*! |
323 | Sets the current model to \a model. If another model was set, |
324 | all mappings to that old model are cleared. |
325 | |
326 | \sa model() |
327 | */ |
328 | void QDataWidgetMapper::setModel(QAbstractItemModel *model) |
329 | { |
330 | Q_D(QDataWidgetMapper); |
331 | |
332 | if (d->model == model) |
333 | return; |
334 | |
335 | d->disconnectModel(); |
336 | clearMapping(); |
337 | d->rootIndex = QModelIndex(); |
338 | d->currentTopLeft = QModelIndex(); |
339 | |
340 | d->model = model; |
341 | |
342 | d->modelConnections = { |
343 | QObjectPrivate::connect(sender: model, signal: &QAbstractItemModel::dataChanged, |
344 | receiverPrivate: d, slot: &QDataWidgetMapperPrivate::dataChanged), |
345 | QObjectPrivate::connect(sender: model, signal: &QAbstractItemModel::destroyed, |
346 | receiverPrivate: d, slot: &QDataWidgetMapperPrivate::modelDestroyed) |
347 | }; |
348 | } |
349 | |
350 | /*! |
351 | Returns the current model. |
352 | |
353 | \sa setModel() |
354 | */ |
355 | QAbstractItemModel *QDataWidgetMapper::model() const |
356 | { |
357 | Q_D(const QDataWidgetMapper); |
358 | return d->model == QAbstractItemModelPrivate::staticEmptyModel() |
359 | ? static_cast<QAbstractItemModel *>(nullptr) |
360 | : d->model; |
361 | } |
362 | |
363 | /*! |
364 | Sets the item delegate to \a delegate. The delegate will be used to write |
365 | data from the model into the widget and from the widget to the model, |
366 | using QAbstractItemDelegate::setEditorData() and QAbstractItemDelegate::setModelData(). |
367 | |
368 | Any existing delegate will be removed, but not deleted. QDataWidgetMapper |
369 | does not take ownership of \a delegate. |
370 | |
371 | The delegate also decides when to apply data and when to change the editor, |
372 | using QAbstractItemDelegate::commitData() and QAbstractItemDelegate::closeEditor(). |
373 | |
374 | \warning You should not share the same instance of a delegate between widget mappers |
375 | or views. Doing so can cause incorrect or unintuitive editing behavior since each |
376 | view connected to a given delegate may receive the \l{QAbstractItemDelegate::}{closeEditor()} |
377 | signal, and attempt to access, modify or close an editor that has already been closed. |
378 | */ |
379 | void QDataWidgetMapper::setItemDelegate(QAbstractItemDelegate *delegate) |
380 | { |
381 | Q_D(QDataWidgetMapper); |
382 | QAbstractItemDelegate *oldDelegate = d->delegate; |
383 | d->disconnectDelegate(); |
384 | |
385 | d->delegate = delegate; |
386 | |
387 | if (delegate) { |
388 | d->delegateConnections = { |
389 | QObjectPrivate::connect(sender: delegate, signal: &QAbstractItemDelegate::commitData, |
390 | receiverPrivate: d, slot: &QDataWidgetMapperPrivate::commitData), |
391 | QObjectPrivate::connect(sender: delegate, signal: &QAbstractItemDelegate::closeEditor, |
392 | receiverPrivate: d, slot: &QDataWidgetMapperPrivate::closeEditor) |
393 | }; |
394 | } |
395 | |
396 | d->flipEventFilters(oldDelegate, newDelegate: delegate); |
397 | } |
398 | |
399 | /*! |
400 | Returns the current item delegate. |
401 | */ |
402 | QAbstractItemDelegate *QDataWidgetMapper::itemDelegate() const |
403 | { |
404 | Q_D(const QDataWidgetMapper); |
405 | return d->delegate; |
406 | } |
407 | |
408 | /*! |
409 | Sets the root item to \a index. This can be used to display |
410 | a branch of a tree. Pass an invalid model index to display |
411 | the top-most branch. |
412 | |
413 | \sa rootIndex() |
414 | */ |
415 | void QDataWidgetMapper::setRootIndex(const QModelIndex &index) |
416 | { |
417 | Q_D(QDataWidgetMapper); |
418 | d->rootIndex = index; |
419 | } |
420 | |
421 | /*! |
422 | Returns the current root index. |
423 | |
424 | \sa setRootIndex() |
425 | */ |
426 | QModelIndex QDataWidgetMapper::rootIndex() const |
427 | { |
428 | Q_D(const QDataWidgetMapper); |
429 | return QModelIndex(d->rootIndex); |
430 | } |
431 | |
432 | /*! |
433 | Adds a mapping between a \a widget and a \a section from the model. |
434 | The \a section is a column in the model if the orientation is |
435 | horizontal (the default), otherwise a row. |
436 | |
437 | For the following example, we assume a model \c myModel that |
438 | has two columns: the first one contains the names of people in a |
439 | group, and the second column contains their ages. The first column |
440 | is mapped to the QLineEdit \c nameLineEdit, and the second is |
441 | mapped to the QSpinBox \c{ageSpinBox}: |
442 | |
443 | \snippet code/src_gui_itemviews_qdatawidgetmapper.cpp 1 |
444 | |
445 | \b{Notes:} |
446 | \list |
447 | \li If the \a widget is already mapped to a section, the |
448 | old mapping will be replaced by the new one. |
449 | \li Only one-to-one mappings between sections and widgets are allowed. |
450 | It is not possible to map a single section to multiple widgets, or to |
451 | map a single widget to multiple sections. |
452 | \endlist |
453 | |
454 | \sa removeMapping(), mappedSection(), clearMapping() |
455 | */ |
456 | void QDataWidgetMapper::addMapping(QWidget *widget, int section) |
457 | { |
458 | Q_D(QDataWidgetMapper); |
459 | |
460 | removeMapping(widget); |
461 | d->widgetMap.push_back(x: {.widget: widget, .section: section, .currentIndex: d->indexAt(itemPos: section), .property: QByteArray()}); |
462 | widget->installEventFilter(filterObj: d->delegate); |
463 | } |
464 | |
465 | /*! |
466 | \since 4.3 |
467 | |
468 | Essentially the same as addMapping(), but adds the possibility to specify |
469 | the property to use specifying \a propertyName. |
470 | |
471 | \sa addMapping() |
472 | */ |
473 | |
474 | void QDataWidgetMapper::addMapping(QWidget *widget, int section, const QByteArray &propertyName) |
475 | { |
476 | Q_D(QDataWidgetMapper); |
477 | |
478 | removeMapping(widget); |
479 | d->widgetMap.push_back(x: {.widget: widget, .section: section, .currentIndex: d->indexAt(itemPos: section), .property: propertyName}); |
480 | widget->installEventFilter(filterObj: d->delegate); |
481 | } |
482 | |
483 | /*! |
484 | Removes the mapping for the given \a widget. |
485 | |
486 | \sa addMapping(), clearMapping() |
487 | */ |
488 | void QDataWidgetMapper::removeMapping(QWidget *widget) |
489 | { |
490 | Q_D(QDataWidgetMapper); |
491 | |
492 | int idx = d->findWidget(w: widget); |
493 | if (idx == -1) |
494 | return; |
495 | |
496 | d->widgetMap.erase(position: d->widgetMap.begin() + idx); |
497 | widget->removeEventFilter(obj: d->delegate); |
498 | } |
499 | |
500 | /*! |
501 | Returns the section the \a widget is mapped to or -1 |
502 | if the widget is not mapped. |
503 | |
504 | \sa addMapping(), removeMapping() |
505 | */ |
506 | int QDataWidgetMapper::mappedSection(QWidget *widget) const |
507 | { |
508 | Q_D(const QDataWidgetMapper); |
509 | |
510 | int idx = d->findWidget(w: widget); |
511 | if (idx == -1) |
512 | return -1; |
513 | |
514 | return d->widgetMap[idx].section; |
515 | } |
516 | |
517 | /*! |
518 | \since 4.3 |
519 | Returns the name of the property that is used when mapping |
520 | data to the given \a widget. |
521 | |
522 | \sa mappedSection(), addMapping(), removeMapping() |
523 | */ |
524 | |
525 | QByteArray QDataWidgetMapper::mappedPropertyName(QWidget *widget) const |
526 | { |
527 | Q_D(const QDataWidgetMapper); |
528 | |
529 | int idx = d->findWidget(w: widget); |
530 | if (idx == -1) |
531 | return QByteArray(); |
532 | const auto &m = d->widgetMap[idx]; |
533 | if (m.property.isEmpty()) |
534 | return m.widget->metaObject()->userProperty().name(); |
535 | else |
536 | return m.property; |
537 | } |
538 | |
539 | /*! |
540 | Returns the widget that is mapped at \a section, or |
541 | 0 if no widget is mapped at that section. |
542 | |
543 | \sa addMapping(), removeMapping() |
544 | */ |
545 | QWidget *QDataWidgetMapper::mappedWidgetAt(int section) const |
546 | { |
547 | Q_D(const QDataWidgetMapper); |
548 | |
549 | for (auto &e : d->widgetMap) { |
550 | if (e.section == section) |
551 | return e.widget; |
552 | } |
553 | |
554 | return nullptr; |
555 | } |
556 | |
557 | /*! |
558 | Repopulates all widgets with the current data of the model. |
559 | All unsubmitted changes will be lost. |
560 | |
561 | \sa submit(), setSubmitPolicy() |
562 | */ |
563 | void QDataWidgetMapper::revert() |
564 | { |
565 | Q_D(QDataWidgetMapper); |
566 | |
567 | d->populate(); |
568 | } |
569 | |
570 | /*! |
571 | Submits all changes from the mapped widgets to the model. |
572 | |
573 | For every mapped section, the item delegate reads the current |
574 | value from the widget and sets it in the model. Finally, the |
575 | model's \l {QAbstractItemModel::}{submit()} method is invoked. |
576 | |
577 | Returns \c true if all the values were submitted, otherwise false. |
578 | |
579 | Note: For database models, QSqlQueryModel::lastError() can be |
580 | used to retrieve the last error. |
581 | |
582 | \sa revert(), setSubmitPolicy() |
583 | */ |
584 | bool QDataWidgetMapper::submit() |
585 | { |
586 | Q_D(QDataWidgetMapper); |
587 | |
588 | for (auto &e : d->widgetMap) { |
589 | if (!d->commit(m: e)) |
590 | return false; |
591 | } |
592 | |
593 | return d->model->submit(); |
594 | } |
595 | |
596 | /*! |
597 | Populates the widgets with data from the first row of the model |
598 | if the orientation is horizontal (the default), otherwise |
599 | with data from the first column. |
600 | |
601 | This is equivalent to calling \c setCurrentIndex(0). |
602 | |
603 | \sa toLast(), setCurrentIndex() |
604 | */ |
605 | void QDataWidgetMapper::toFirst() |
606 | { |
607 | setCurrentIndex(0); |
608 | } |
609 | |
610 | /*! |
611 | Populates the widgets with data from the last row of the model |
612 | if the orientation is horizontal (the default), otherwise |
613 | with data from the last column. |
614 | |
615 | Calls setCurrentIndex() internally. |
616 | |
617 | \sa toFirst(), setCurrentIndex() |
618 | */ |
619 | void QDataWidgetMapper::toLast() |
620 | { |
621 | Q_D(QDataWidgetMapper); |
622 | setCurrentIndex(d->itemCount() - 1); |
623 | } |
624 | |
625 | |
626 | /*! |
627 | Populates the widgets with data from the next row of the model |
628 | if the orientation is horizontal (the default), otherwise |
629 | with data from the next column. |
630 | |
631 | Calls setCurrentIndex() internally. Does nothing if there is |
632 | no next row in the model. |
633 | |
634 | \sa toPrevious(), setCurrentIndex() |
635 | */ |
636 | void QDataWidgetMapper::toNext() |
637 | { |
638 | Q_D(QDataWidgetMapper); |
639 | setCurrentIndex(d->currentIdx() + 1); |
640 | } |
641 | |
642 | /*! |
643 | Populates the widgets with data from the previous row of the model |
644 | if the orientation is horizontal (the default), otherwise |
645 | with data from the previous column. |
646 | |
647 | Calls setCurrentIndex() internally. Does nothing if there is |
648 | no previous row in the model. |
649 | |
650 | \sa toNext(), setCurrentIndex() |
651 | */ |
652 | void QDataWidgetMapper::toPrevious() |
653 | { |
654 | Q_D(QDataWidgetMapper); |
655 | setCurrentIndex(d->currentIdx() - 1); |
656 | } |
657 | |
658 | /*! |
659 | \property QDataWidgetMapper::currentIndex |
660 | \brief the current row or column |
661 | |
662 | The widgets are populated with with data from the row at \a index |
663 | if the orientation is horizontal (the default), otherwise with |
664 | data from the column at \a index. |
665 | |
666 | \sa setCurrentModelIndex(), toFirst(), toNext(), toPrevious(), toLast() |
667 | */ |
668 | void QDataWidgetMapper::setCurrentIndex(int index) |
669 | { |
670 | Q_D(QDataWidgetMapper); |
671 | |
672 | if (index < 0 || index >= d->itemCount()) |
673 | return; |
674 | d->currentTopLeft = d->orientation == Qt::Horizontal |
675 | ? d->model->index(row: index, column: 0, parent: d->rootIndex) |
676 | : d->model->index(row: 0, column: index, parent: d->rootIndex); |
677 | d->populate(); |
678 | |
679 | emit currentIndexChanged(index); |
680 | } |
681 | |
682 | int QDataWidgetMapper::currentIndex() const |
683 | { |
684 | Q_D(const QDataWidgetMapper); |
685 | return d->currentIdx(); |
686 | } |
687 | |
688 | /*! |
689 | Sets the current index to the row of the \a index if the |
690 | orientation is horizontal (the default), otherwise to the |
691 | column of the \a index. |
692 | |
693 | Calls setCurrentIndex() internally. This convenience slot can be |
694 | connected to the signal \l |
695 | {QItemSelectionModel::}{currentRowChanged()} or \l |
696 | {QItemSelectionModel::}{currentColumnChanged()} of another view's |
697 | \l {QItemSelectionModel}{selection model}. |
698 | |
699 | The following example illustrates how to update all widgets |
700 | with new data whenever the selection of a QTableView named |
701 | \c myTableView changes: |
702 | |
703 | \snippet code/src_gui_itemviews_qdatawidgetmapper.cpp 2 |
704 | |
705 | \sa currentIndex() |
706 | */ |
707 | void QDataWidgetMapper::setCurrentModelIndex(const QModelIndex &index) |
708 | { |
709 | Q_D(QDataWidgetMapper); |
710 | |
711 | if (!index.isValid() |
712 | || index.model() != d->model |
713 | || index.parent() != d->rootIndex) |
714 | return; |
715 | |
716 | setCurrentIndex(d->orientation == Qt::Horizontal ? index.row() : index.column()); |
717 | } |
718 | |
719 | /*! |
720 | Clears all mappings. |
721 | |
722 | \sa addMapping(), removeMapping() |
723 | */ |
724 | void QDataWidgetMapper::clearMapping() |
725 | { |
726 | Q_D(QDataWidgetMapper); |
727 | |
728 | decltype(d->widgetMap) copy; |
729 | d->widgetMap.swap(x&: copy); // a C++98 move |
730 | for (auto it = copy.crbegin(), end = copy.crend(); it != end; ++it) { |
731 | if (it->widget) |
732 | it->widget->removeEventFilter(obj: d->delegate); |
733 | } |
734 | } |
735 | |
736 | /*! |
737 | \property QDataWidgetMapper::orientation |
738 | \brief the orientation of the model |
739 | |
740 | If the orientation is Qt::Horizontal (the default), a widget is |
741 | mapped to a column of a data model. The widget will be populated |
742 | with the model's data from its mapped column and the row that |
743 | currentIndex() points at. |
744 | |
745 | Use Qt::Horizontal for tabular data that looks like this: |
746 | |
747 | \table |
748 | \row \li 1 \li Qt Norway \li Oslo |
749 | \row \li 2 \li Qt Australia \li Brisbane |
750 | \row \li 3 \li Qt USA \li Silicon Valley |
751 | \row \li 4 \li Qt China \li Beijing |
752 | \row \li 5 \li Qt Germany \li Berlin |
753 | \endtable |
754 | |
755 | If the orientation is set to Qt::Vertical, a widget is mapped to |
756 | a row. Calling setCurrentIndex() will change the current column. |
757 | The widget will be populates with the model's data from its |
758 | mapped row and the column that currentIndex() points at. |
759 | |
760 | Use Qt::Vertical for tabular data that looks like this: |
761 | |
762 | \table |
763 | \row \li 1 \li 2 \li 3 \li 4 \li 5 |
764 | \row \li Qt Norway \li Qt Australia \li Qt USA \li Qt China \li Qt Germany |
765 | \row \li Oslo \li Brisbane \li Silicon Valley \li Beijing \li Berlin |
766 | \endtable |
767 | |
768 | Changing the orientation clears all existing mappings. |
769 | */ |
770 | void QDataWidgetMapper::setOrientation(Qt::Orientation orientation) |
771 | { |
772 | Q_D(QDataWidgetMapper); |
773 | |
774 | if (d->orientation == orientation) |
775 | return; |
776 | |
777 | clearMapping(); |
778 | d->orientation = orientation; |
779 | } |
780 | |
781 | Qt::Orientation QDataWidgetMapper::orientation() const |
782 | { |
783 | Q_D(const QDataWidgetMapper); |
784 | return d->orientation; |
785 | } |
786 | |
787 | /*! |
788 | \property QDataWidgetMapper::submitPolicy |
789 | \brief the current submit policy |
790 | |
791 | Changing the current submit policy will revert all widgets |
792 | to the current data from the model. |
793 | */ |
794 | void QDataWidgetMapper::setSubmitPolicy(SubmitPolicy policy) |
795 | { |
796 | Q_D(QDataWidgetMapper); |
797 | if (policy == d->submitPolicy) |
798 | return; |
799 | |
800 | revert(); |
801 | d->submitPolicy = policy; |
802 | } |
803 | |
804 | QDataWidgetMapper::SubmitPolicy QDataWidgetMapper::submitPolicy() const |
805 | { |
806 | Q_D(const QDataWidgetMapper); |
807 | return d->submitPolicy; |
808 | } |
809 | |
810 | QT_END_NAMESPACE |
811 | |
812 | #include "moc_qdatawidgetmapper.cpp" |
813 |
Definitions
- QDataWidgetMapperPrivate
- QDataWidgetMapperPrivate
- itemCount
- currentIdx
- indexAt
- flipEventFilters
- disconnectModel
- disconnectDelegate
- WidgetMapper
- findWidget
- commit
- populate
- populate
- qContainsIndex
- dataChanged
- commitData
- closeEditor
- modelDestroyed
- QDataWidgetMapper
- ~QDataWidgetMapper
- setModel
- model
- setItemDelegate
- itemDelegate
- setRootIndex
- rootIndex
- addMapping
- addMapping
- removeMapping
- mappedSection
- mappedPropertyName
- mappedWidgetAt
- revert
- submit
- toFirst
- toLast
- toNext
- toPrevious
- setCurrentIndex
- currentIndex
- setCurrentModelIndex
- clearMapping
- setOrientation
- orientation
- setSubmitPolicy
Start learning QML with our Intro Training
Find out more