Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickrepeater.cpp
Go to the documentation of this file.
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// Qt-Security score:significant reason:default
4
7
8#include <private/qqmlglobal_p.h>
9#include <private/qqmlchangeset_p.h>
10#include <private/qqmldelegatemodel_p.h>
11
12#include <QtQml/QQmlInfo>
13#include <QtQml/qqmlcomponent.h>
14
16
17QQuickRepeaterPrivate::QQuickRepeaterPrivate()
18 : model(nullptr)
19 , ownModel(false)
20 , delegateValidated(false)
21 , explicitDelegate(false)
22 , explicitDelegateModelAccess(false)
23 , itemCount(0)
24{
25 setTransparentForPositioner(true);
26}
27
29{
30 if (ownModel)
31 delete model;
32}
33
34/*!
35 \qmltype Repeater
36 \nativetype QQuickRepeater
37 \inqmlmodule QtQuick
38 \ingroup qtquick-models
39 \ingroup qtquick-positioning
40 \inherits Item
41 \brief Instantiates a number of Item-based components using a provided model.
42
43 The Repeater type is used to create a large number of
44 similar items. Like other view types, a Repeater has a \l model and a \l delegate:
45 for each entry in the model, the delegate is instantiated
46 in a context seeded with data from the model. A Repeater item is usually
47 enclosed in a positioner type such as \l Row or \l Column to visually
48 position the multiple delegate items created by the Repeater.
49
50 The following Repeater creates three instances of a \l Rectangle item within
51 a \l Row:
52
53 \snippet qml/repeaters/repeater.qml import
54 \codeline
55 \snippet qml/repeaters/repeater.qml simple
56
57 \image repeater-simple.png {Three yellow rectangles in a row created
58 by Repeater}
59
60 A Repeater's \l model can be any of the supported \l {qml-data-models}{data models}.
61 Additionally, like delegates for other views, a Repeater delegate can access
62 its index within the repeater, as well as the model data relevant to the
63 delegate. See the \l delegate property documentation for details.
64
65 Items instantiated by the Repeater are inserted, in order, as
66 children of the Repeater's parent. The insertion starts immediately after
67 the repeater's position in its parent stacking list. This allows
68 a Repeater to be used inside a layout. For example, the following Repeater's
69 items are stacked between a red rectangle and a blue rectangle:
70
71 \snippet qml/repeaters/repeater.qml layout
72
73 \image repeater.png {Row with red rectangle, ten green circles from
74 Repeater, and blue rectangle}
75
76
77 \note A Repeater item owns all items it instantiates. Removing or dynamically destroying
78 an item created by a Repeater results in unpredictable behavior.
79
80
81 \section2 Considerations when using Repeater
82
83 The Repeater type creates all of its delegate items when the repeater is first
84 created. This can be inefficient if there are a large number of delegate items and
85 not all of the items are required to be visible at the same time. If this is the case,
86 consider using other view types like ListView (which only creates delegate items
87 when they are scrolled into view) or use the \l {Dynamic Object Creation} methods to
88 create items as they are required.
89
90 Also, note that Repeater is \l {Item}-based, and can only repeat \l {Item}-derived objects.
91 For example, it cannot be used to repeat QtObjects:
92
93 \qml
94 // bad code:
95 Item {
96 // Can't repeat QtObject as it doesn't derive from Item.
97 Repeater {
98 model: 10
99 QtObject {}
100 }
101 }
102 \endqml
103 */
104
105/*!
106 \qmlsignal QtQuick::Repeater::itemAdded(int index, Item item)
107
108 This signal is emitted when an item is added to the repeater. The \a index
109 parameter holds the index at which the item has been inserted within the
110 repeater, and the \a item parameter holds the \l Item that has been added.
111*/
112
113/*!
114 \qmlsignal QtQuick::Repeater::itemRemoved(int index, Item item)
115
116 This signal is emitted when an item is removed from the repeater. The \a index
117 parameter holds the index at which the item was removed from the repeater,
118 and the \a item parameter holds the \l Item that was removed.
119
120 Do not keep a reference to \a item if it was created by this repeater, as
121 in these cases it will be deleted shortly after the signal is handled.
122*/
123QQuickRepeater::QQuickRepeater(QQuickItem *parent)
124 : QQuickItem(*(new QQuickRepeaterPrivate), parent)
125{
126}
127
128QQuickRepeater::~QQuickRepeater()
129{
130 Q_D(QQuickRepeater);
131 QQmlDelegateModelPointer model(d->model);
132 d->disconnectModel(this, &model);
133}
134
135/*!
136 \qmlproperty var QtQuick::Repeater::model
137
138 The model providing data for the repeater.
139
140 This property can be set to any of the supported \l {qml-data-models}{data models}:
141
142 \list
143 \li A number that indicates the number of delegates to be created by the repeater
144 \li A model (e.g. a ListModel item, or a QAbstractItemModel subclass)
145 \li A string list
146 \li An object list
147 \endlist
148
149 The type of model affects the properties that are exposed to the \l delegate.
150
151 \sa {qml-data-models}{Data Models}
152*/
153QVariant QQuickRepeater::model() const
154{
155 Q_D(const QQuickRepeater);
156
157 if (d->ownModel)
158 return static_cast<QQmlDelegateModel *>(d->model.data())->model();
159 if (d->model)
160 return QVariant::fromValue(d->model.data());
161 return QVariant();
162}
163
164void QQuickRepeater::setModel(const QVariant &m)
165{
166 Q_D(QQuickRepeater);
167 QVariant model = m;
168 if (model.userType() == qMetaTypeId<QJSValue>())
169 model = model.value<QJSValue>().toVariant();
170
171 QQmlDelegateModelPointer oldModel(d->model);
172 if (d->ownModel) {
173 if (oldModel.delegateModel()->model() == model)
174 return;
175 } else if (QVariant::fromValue(d->model) == model) {
176 return;
177 }
178
179 clear();
180
181 d->disconnectModel(this, &oldModel);
182 d->model = nullptr;
183
184 QObject *object = qvariant_cast<QObject *>(model);
185
186 QQmlDelegateModelPointer newModel(qobject_cast<QQmlInstanceModel *>(object));
187 if (newModel) {
188 if (d->explicitDelegate) {
189 QQmlComponent *delegate = nullptr;
190 if (QQmlDelegateModel *old = oldModel.delegateModel())
191 delegate = old->delegate();
192
193 if (QQmlDelegateModel *delegateModel = newModel.delegateModel()) {
194 delegateModel->setDelegate(delegate);
195 } else if (delegate) {
196 qmlWarning(this) << "Cannot retain explicitly set delegate on non-DelegateModel";
197 d->explicitDelegate = false;
198 }
199 }
200
201 if (d->explicitDelegateModelAccess) {
202 QQmlDelegateModel::DelegateModelAccess access = QQmlDelegateModel::Qt5ReadWrite;
203 if (QQmlDelegateModel *old = oldModel.delegateModel())
204 access = old->delegateModelAccess();
205
206 if (QQmlDelegateModel *delegateModel = newModel.delegateModel()) {
207 delegateModel->setDelegateModelAccess(access);
208 } else if (access != QQmlDelegateModel::Qt5ReadWrite) {
209 qmlWarning(this) << "Cannot retain explicitly set delegate model access "
210 "on non-DelegateModel";
211 d->explicitDelegateModelAccess = false;
212 }
213 }
214
215 if (d->ownModel) {
216 delete oldModel.instanceModel();
217 d->ownModel = false;
218 }
219 d->model = newModel.instanceModel();
220 } else if (d->ownModel) {
221 // d->ownModel can only be set if the old model is a QQmlDelegateModel.
222 Q_ASSERT(oldModel.delegateModel());
223 newModel = oldModel;
224 d->model = newModel.instanceModel();
225 newModel.delegateModel()->setModel(model);
226 } else {
227 newModel = QQmlDelegateModel::createForView(this, d);
228 if (d->explicitDelegate) {
229 QQmlComponent *delegate = nullptr;
230 if (QQmlDelegateModel *old = oldModel.delegateModel())
231 delegate = old->delegate();
232 newModel.delegateModel()->setDelegate(delegate);
233 }
234
235 if (d->explicitDelegateModelAccess) {
236 QQmlDelegateModel::DelegateModelAccess access = QQmlDelegateModel::Qt5ReadWrite;
237 if (QQmlDelegateModel *old = oldModel.delegateModel())
238 access = old->delegateModelAccess();
239 newModel.delegateModel()->setDelegateModelAccess(access);
240 }
241
242 newModel.delegateModel()->setModel(model);
243 }
244
245 d->connectModel(this, &newModel);
246 emit modelChanged();
247 emit countChanged();
248}
249
250/*!
251 \qmlproperty Component QtQuick::Repeater::delegate
252 \qmldefault
253
254 The delegate provides a template defining each item instantiated by the repeater.
255
256 Delegates are exposed to a read-only \c index property that indicates the index
257 of the delegate within the repeater. For example, the following \l Text delegate
258 displays the index of each repeated item:
259
260 \table
261 \row
262 \li \snippet qml/repeaters/repeater.qml index
263 \li \image repeater-index.png {Text items displaying "I'm item 0"
264 through "I'm item 9"}
265 \endtable
266
267 If the \l model is a \l{QStringList-based model}{string list} or
268 \l{QObjectList-based model}{object list}, the delegate is also exposed to
269 a read-only \c modelData property that holds the string or object data. For
270 example:
271
272 \table
273 \row
274 \li \snippet qml/repeaters/repeater.qml modeldata
275 \li \image repeater-modeldata.png {Text items displaying "Data: apples",
276 "Data: oranges", "Data: pears"}
277 \endtable
278
279 If the \l model is a model object (such as a \l ListModel) the delegate
280 can access all model roles as named properties, in the same way that delegates
281 do for view classes like ListView.
282
283 \sa {QML Data Models}
284 */
285QQmlComponent *QQuickRepeater::delegate() const
286{
287 Q_D(const QQuickRepeater);
288 if (d->model) {
289 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(d->model))
290 return dataModel->delegate();
291 }
292
293 return nullptr;
294}
295
296void QQuickRepeater::setDelegate(QQmlComponent *delegate)
297{
298 Q_D(QQuickRepeater);
299 const auto setExplicitDelegate = [&](QQmlDelegateModel *delegateModel) {
300 if (delegateModel->delegate() == delegate) {
301 d->explicitDelegate = true;
302 return;
303 }
304
305 const int oldCount = delegateModel->count();
306 delegateModel->setDelegate(delegate);
307 regenerate();
308 if (oldCount != delegateModel->count())
309 emit countChanged();
310 d->explicitDelegate = true;
311 d->delegateValidated = false;
312 };
313
314 if (!d->model) {
315 if (!delegate) {
316 // Explicitly set a null delegate. We can do this without model.
317 d->explicitDelegate = true;
318 return;
319 }
320
321 setExplicitDelegate(QQmlDelegateModel::createForView(this, d));
322 // The new model is not connected to applyDelegateChange, yet. We only do this once
323 // there is actual data, via an explicit setModel(). So we have to manually emit the
324 // delegateChanged() here.
325 emit delegateChanged();
326 return;
327 }
328
329 if (QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel *>(d->model)) {
330 // Disable the warning in applyDelegateChange since the new delegate is also explicit.
331 d->explicitDelegate = false;
332 setExplicitDelegate(delegateModel);
333 return;
334 }
335
336 if (delegate)
337 qmlWarning(this) << "Cannot set a delegate on an explicitly provided non-DelegateModel";
338 else
339 d->explicitDelegate = true; // Explicitly set null delegate always works
340}
341
342/*!
343 \qmlproperty int QtQuick::Repeater::count
344
345 This property holds the number of items in the \l model.
346
347 The value of \c count does not always match the number of instantiated
348 \l {delegate}{delegates}; use \l itemAt() to check if a delegate at a given
349 index exists. It returns \c null if the delegate is not instantiated.
350
351 \list
352 \li While the Repeater is in the process of instantiating delegates (at
353 startup, or because of \c model changes), the \l itemAdded signal is
354 emitted for each delegate created, and the \c count property changes
355 afterwards.
356 \li If the Repeater is not part of a completed
357 \l {Concepts - Visual Parent in Qt Quick}{visual hierarchy},
358 \c count reflects the model size, but no delegates are created.
359 \li If the Repeater destroys delegates because of \c model changes,
360 the \l itemRemoved() signal is emitted for each, and the \c count
361 property changes afterwards.
362 \li If the Repeater is taken out of the visual hierarchy (for example by
363 setting \c {parent = null}), delegates are destroyed, the \l itemRemoved()
364 signal is emitted for each, but \c count does not change.
365 \endlist
366
367 \sa itemAt(), itemAdded(), itemRemoved()
368*/
369int QQuickRepeater::count() const
370{
371 Q_D(const QQuickRepeater);
372 if (d->model)
373 return d->model->count();
374 return 0;
375}
376
377/*!
378 \qmlmethod Item QtQuick::Repeater::itemAt(index)
379
380 Returns the \l Item that has been created at the given \a index, or \c null
381 if no item exists at \a index.
382*/
383QQuickItem *QQuickRepeater::itemAt(int index) const
384{
385 Q_D(const QQuickRepeater);
386 if (index >= 0 && index < d->deletables.size())
387 return d->deletables[index];
388 return nullptr;
389}
390
391void QQuickRepeater::componentComplete()
392{
393 Q_D(QQuickRepeater);
394 if (d->model && d->ownModel)
395 static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
396 QQuickItem::componentComplete();
397 regenerate();
398 if (d->model && d->model->count())
399 emit countChanged();
400}
401
402void QQuickRepeater::itemChange(ItemChange change, const ItemChangeData &value)
403{
404 QQuickItem::itemChange(change, value);
405 if (change == ItemParentHasChanged) {
406 regenerate();
407 }
408}
409
410void QQuickRepeater::clear()
411{
412 Q_D(QQuickRepeater);
413 bool complete = isComponentComplete();
414
415 if (d->model) {
416 // We remove in reverse order deliberately; so that signals are emitted
417 // with sensible indices.
418 for (int i = d->deletables.size() - 1; i >= 0; --i) {
419 if (QQuickItem *item = d->deletables.at(i)) {
420 if (complete)
421 emit itemRemoved(i, item);
422 d->model->release(item);
423 }
424 }
425 for (QQuickItem *item : std::as_const(d->deletables)) {
426 if (item)
427 item->setParentItem(nullptr);
428 }
429 }
430 d->deletables.clear();
431 d->itemCount = 0;
432}
433
434void QQuickRepeater::regenerate()
435{
436 Q_D(QQuickRepeater);
437 if (!isComponentComplete())
438 return;
439
440 clear();
441
442 if (!d->model || !d->model->count() || !d->model->isValid() || !parentItem() || !isComponentComplete())
443 return;
444
445 d->itemCount = count();
446 d->deletables.resize(d->itemCount);
447 d->requestItems();
448}
449
450void QQuickRepeaterPrivate::requestItems()
451{
452 for (int i = 0; i < itemCount; i++) {
453 QObject *object = model->object(i, QQmlIncubator::AsynchronousIfNested);
454 if (object)
455 model->release(object);
456 }
457}
458
459void QQuickRepeaterPrivate::connectModel(QQuickRepeater *q, QQmlDelegateModelPointer *model)
460{
461 QQmlInstanceModel *instanceModel = model->instanceModel();
462 if (!instanceModel)
463 return;
464
465 QObject::connect(instanceModel, &QQmlInstanceModel::modelUpdated,
466 q, &QQuickRepeater::modelUpdated);
467 QObject::connect(instanceModel, &QQmlInstanceModel::createdItem,
468 q, &QQuickRepeater::createdItem);
469 QObject::connect(instanceModel, &QQmlInstanceModel::initItem,
470 q, &QQuickRepeater::initItem);
471 if (QQmlDelegateModel *dataModel = model->delegateModel()) {
472 QObjectPrivate::connect(
473 dataModel, &QQmlDelegateModel::delegateChanged,
474 this, &QQuickRepeaterPrivate::applyDelegateChange);
475 QObjectPrivate::connect(
476 dataModel, &QQmlDelegateModel::delegateModelAccessChanged,
477 this, &QQuickRepeaterPrivate::applyDelegateModelAccessChange);
478 if (ownModel) {
479 QObject::connect(dataModel, &QQmlDelegateModel::modelChanged,
480 q, &QQuickRepeater::modelChanged);
481 }
482 }
483 q->regenerate();
484}
485
486void QQuickRepeaterPrivate::disconnectModel(QQuickRepeater *q, QQmlDelegateModelPointer *model)
487{
488 QQmlInstanceModel *instanceModel = model->instanceModel();
489 if (!instanceModel)
490 return;
491
492 QObject::disconnect(instanceModel, &QQmlInstanceModel::modelUpdated,
493 q, &QQuickRepeater::modelUpdated);
494 QObject::disconnect(instanceModel, &QQmlInstanceModel::createdItem,
495 q, &QQuickRepeater::createdItem);
496 QObject::disconnect(instanceModel, &QQmlInstanceModel::initItem,
497 q, &QQuickRepeater::initItem);
498 if (QQmlDelegateModel *delegateModel = model->delegateModel()) {
499 QObjectPrivate::disconnect(
500 delegateModel, &QQmlDelegateModel::delegateChanged,
501 this, &QQuickRepeaterPrivate::applyDelegateChange);
502 QObjectPrivate::disconnect(
503 delegateModel, &QQmlDelegateModel::delegateModelAccessChanged,
504 this, &QQuickRepeaterPrivate::applyDelegateModelAccessChange);
505 if (ownModel) {
506 QObject::disconnect(delegateModel, &QQmlDelegateModel::modelChanged,
507 q, &QQuickRepeater::modelChanged);
508 }
509 }
510}
511
512void QQuickRepeater::createdItem(int index, QObject *)
513{
514 Q_D(QQuickRepeater);
515 QObject *object = d->model->object(index, QQmlIncubator::AsynchronousIfNested);
516 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
517 emit itemAdded(index, item);
518}
519
520void QQuickRepeater::initItem(int index, QObject *object)
521{
522 Q_D(QQuickRepeater);
523 if (index >= d->deletables.size()) {
524 // this can happen when Package is used
525 // calling regenerate does too much work, all we need is to call resize
526 // so that d->deletables[index] = item below works
527 d->deletables.resize(d->model->count() + 1);
528 }
529 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
530
531 if (!d->deletables.at(index)) {
532 if (!item) {
533 if (object) {
534 d->model->release(object);
535 if (!d->delegateValidated) {
536 d->delegateValidated = true;
537 QObject* delegate = this->delegate();
538 qmlWarning(delegate ? delegate : this) << QQuickRepeater::tr("Delegate must be of Item type");
539 }
540 }
541 return;
542 }
543 d->deletables[index] = item;
544 item->setParentItem(parentItem());
545
546 // If the item comes from an ObjectModel, it might be used as
547 // ComboBox/Menu/TabBar's contentItem. These types unconditionally cull items
548 // that are inserted, so account for that here.
549 if (d->model && !d->ownModel)
550 QQuickItemPrivate::get(item)->setCulled(false);
551 if (index > 0 && d->deletables.at(index-1)) {
552 item->stackAfter(d->deletables.at(index-1));
553 } else {
554 QQuickItem *after = this;
555 for (int si = index+1; si < d->itemCount; ++si) {
556 if (d->deletables.at(si)) {
557 after = d->deletables.at(si);
558 break;
559 }
560 }
561 item->stackBefore(after);
562 }
563 }
564}
565
566void QQuickRepeater::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
567{
568 Q_D(QQuickRepeater);
569
570 if (!isComponentComplete())
571 return;
572
573 if (reset) {
574 regenerate();
575 if (changeSet.difference() != 0)
576 emit countChanged();
577 return;
578 }
579
580 int difference = 0;
581 QHash<int, QList<QPointer<QQuickItem> > > moved;
582 for (const QQmlChangeSet::Change &remove : changeSet.removes()) {
583 int index = qMin(remove.index, d->deletables.size());
584 int count = qMin(remove.index + remove.count, d->deletables.size()) - index;
585 if (remove.isMove()) {
586 moved.insert(remove.moveId, d->deletables.mid(index, count));
587 d->deletables.erase(
588 d->deletables.begin() + index,
589 d->deletables.begin() + index + count);
590 } else while (count--) {
591 QQuickItem *item = d->deletables.at(index);
592 d->deletables.remove(index);
593 emit itemRemoved(index, item);
594 if (item) {
595 d->model->release(item);
596 item->setParentItem(nullptr);
597 }
598 --d->itemCount;
599 }
600
601 difference -= remove.count;
602 }
603
604 for (const QQmlChangeSet::Change &insert : changeSet.inserts()) {
605 int index = qMin(insert.index, d->deletables.size());
606 if (insert.isMove()) {
607 QList<QPointer<QQuickItem> > items = moved.value(insert.moveId);
608 d->deletables = d->deletables.mid(0, index) + items + d->deletables.mid(index);
609 QQuickItem *stackBefore = index + items.size() < d->deletables.size()
610 ? d->deletables.at(index + items.size())
611 : this;
612 if (stackBefore) {
613 for (int i = index; i < index + items.size(); ++i) {
614 if (i < d->deletables.size()) {
615 QPointer<QQuickItem> item = d->deletables.at(i);
616 if (item)
617 item->stackBefore(stackBefore);
618 }
619 }
620 }
621 } else for (int i = 0; i < insert.count; ++i) {
622 int modelIndex = index + i;
623 ++d->itemCount;
624 d->deletables.insert(modelIndex, nullptr);
625 QObject *object = d->model->object(modelIndex, QQmlIncubator::AsynchronousIfNested);
626 if (object)
627 d->model->release(object);
628 }
629 difference += insert.count;
630 }
631
632 if (difference != 0)
633 emit countChanged();
634}
635
636/*!
637 \qmlproperty enumeration QtQuick::Repeater::delegateModelAccess
638 \since 6.10
639
640 \include delegatemodelaccess.qdocinc
641*/
642QQmlDelegateModel::DelegateModelAccess QQuickRepeater::delegateModelAccess() const
643{
644 Q_D(const QQuickRepeater);
645 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel *>(d->model))
646 return dataModel->delegateModelAccess();
647 return QQmlDelegateModel::Qt5ReadWrite;
648}
649
650void QQuickRepeater::setDelegateModelAccess(
651 QQmlDelegateModel::DelegateModelAccess delegateModelAccess)
652{
653 Q_D(QQuickRepeater);
654 const auto setExplicitDelegateModelAccess = [&](QQmlDelegateModel *delegateModel) {
655 delegateModel->setDelegateModelAccess(delegateModelAccess);
656 d->explicitDelegateModelAccess = true;
657 };
658
659 if (!d->model) {
660 if (delegateModelAccess == QQmlDelegateModel::Qt5ReadWrite) {
661 // Explicitly set delegateModelAccess to Legacy. We can do this without model.
662 d->explicitDelegateModelAccess = true;
663 return;
664 }
665
666 setExplicitDelegateModelAccess(QQmlDelegateModel::createForView(this, d));
667
668 // The new model is not connected to applyDelegateModelAccessChange, yet. We only do this
669 // once there is actual data, via an explicit setModel(). So we have to manually emit the
670 // delegateModelAccessChanged() here.
671 emit delegateModelAccessChanged();
672 return;
673 }
674
675 if (QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel *>(d->model)) {
676 // Disable the warning in applyDelegateModelAccessChange since the new delegate model
677 // access is also explicit.
678 d->explicitDelegateModelAccess = false;
679 setExplicitDelegateModelAccess(delegateModel);
680 return;
681 }
682
683 if (delegateModelAccess == QQmlDelegateModel::Qt5ReadWrite) {
684 d->explicitDelegateModelAccess = true; // Explicitly set null delegate always works
685 } else {
686 qmlWarning(this) << "Cannot set a delegateModelAccess on an explicitly provided "
687 "non-DelegateModel";
688 }
689}
690
691QT_END_NAMESPACE
692
693#include "moc_qquickrepeater_p.cpp"
Combined button and popup list for selecting options.