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