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