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