|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (developer.feedback@nokia.com) |
|
6 ** |
|
7 ** This file is part of the HbWidgets module of the UI Extensions for Mobile. |
|
8 ** |
|
9 ** GNU Lesser General Public License Usage |
|
10 ** This file may be used under the terms of the GNU Lesser General Public |
|
11 ** License version 2.1 as published by the Free Software Foundation and |
|
12 ** appearing in the file LICENSE.LGPL included in the packaging of this file. |
|
13 ** Please review the following information to ensure the GNU Lesser General |
|
14 ** Public License version 2.1 requirements will be met: |
|
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
16 ** |
|
17 ** In addition, as a special exception, Nokia gives you certain additional |
|
18 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
20 ** |
|
21 ** If you have questions regarding the use of this file, please contact |
|
22 ** Nokia at developer.feedback@nokia.com. |
|
23 ** |
|
24 ****************************************************************************/ |
|
25 #include "hbabstractitemcontainer_p.h" |
|
26 #include "hbabstractitemcontainer_p_p.h" |
|
27 |
|
28 #include "hbabstractviewitem.h" |
|
29 #include "hbabstractitemview.h" |
|
30 #include "hbabstractitemview_p.h" |
|
31 #include "hbmodeliterator.h" |
|
32 #include <hbapplication.h> |
|
33 |
|
34 #include <QGraphicsLayout> |
|
35 #include <QGraphicsSceneResizeEvent> |
|
36 #include <QEvent> |
|
37 #include <QDebug> |
|
38 |
|
39 |
|
40 /*! |
|
41 HbAbstractItemContainer is used in HbAbstractItemView to hold the layout and view items. |
|
42 Container should have a layout, otherwise its size is zero always. |
|
43 HbAbstractItemContainer can have any kind of layout as it child. |
|
44 */ |
|
45 |
|
46 /*! |
|
47 Function is called after new \a item was added into \a index position into |
|
48 container. |
|
49 */ |
|
50 |
|
51 /*! |
|
52 Function is called after the \a item was removed from the container. |
|
53 */ |
|
54 |
|
55 /*! |
|
56 Function is called when container needs to be resized. |
|
57 */ |
|
58 |
|
59 /*! |
|
60 Returns the default prototype. |
|
61 |
|
62 Subclasses of this class must implement this function to introduce their own default prototype. |
|
63 Default prototype is used to create view items unless the class user excplicitly sets prototype with setItemPrototype method. |
|
64 Implementation of this method must construct and return a new view item widget. |
|
65 */ |
|
66 |
|
67 const int HB_DEFAULT_BUFFERSIZE = 4; |
|
68 const int UpdateItemBufferEvent = QEvent::registerEventType(); |
|
69 |
|
70 HbAbstractItemContainerPrivate::HbAbstractItemContainerPrivate() : |
|
71 HbWidgetPrivate(), |
|
72 mItemView(0), |
|
73 mBufferSize(HB_DEFAULT_BUFFERSIZE), |
|
74 mItemRecycling(false), |
|
75 mUniformItemSizes(false) |
|
76 { |
|
77 } |
|
78 |
|
79 HbAbstractItemContainerPrivate::~HbAbstractItemContainerPrivate() |
|
80 { |
|
81 } |
|
82 |
|
83 |
|
84 void HbAbstractItemContainerPrivate::init() |
|
85 { |
|
86 Q_Q(HbAbstractItemContainer); |
|
87 q->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); |
|
88 } |
|
89 |
|
90 /*! |
|
91 Returns given item's bounding rectangle in scene coordinates. |
|
92 */ |
|
93 QRectF HbAbstractItemContainerPrivate::itemBoundingRect(const QGraphicsItem *item) const |
|
94 { |
|
95 Q_Q(const HbAbstractItemContainer); |
|
96 if (q->layout()) { |
|
97 q->layout()->activate(); |
|
98 } |
|
99 return item->mapToItem(mItemView, item->boundingRect()).boundingRect(); |
|
100 } |
|
101 |
|
102 void HbAbstractItemContainerPrivate::firstAndLastVisibleBufferIndex( |
|
103 int& firstVisibleBufferIndex, |
|
104 int& lastVisibleBufferIndex, |
|
105 const QRectF &viewRect, |
|
106 bool fullyVisible) const |
|
107 { |
|
108 Q_Q(const HbAbstractItemContainer); |
|
109 |
|
110 if (q->layout() && !q->layout()->isActivated()) { |
|
111 q->layout()->activate(); |
|
112 } |
|
113 |
|
114 firstVisibleBufferIndex = -1; |
|
115 lastVisibleBufferIndex = -1; |
|
116 |
|
117 int count = mItems.count(); |
|
118 for (int i = 0; i < count; ++i) { |
|
119 if (visible(mItems.at(i), viewRect, fullyVisible)) { |
|
120 if (firstVisibleBufferIndex == -1) { |
|
121 firstVisibleBufferIndex = i; |
|
122 } |
|
123 lastVisibleBufferIndex = i; |
|
124 } else if ( lastVisibleBufferIndex != -1 ) { |
|
125 // no need to check the remaining ones. |
|
126 break; |
|
127 } |
|
128 } |
|
129 } |
|
130 |
|
131 /*! |
|
132 \private |
|
133 |
|
134 Returns true if given item is located within viewport (i.e. view), otherwise |
|
135 returns false. If fullyVisible parameter is true method will return true only |
|
136 for item that is shown fully. In this case for partially visible items false is returned. |
|
137 */ |
|
138 bool HbAbstractItemContainerPrivate::visible(HbAbstractViewItem* item, const QRectF &viewRect, bool fullyVisible) const |
|
139 { |
|
140 if (item) { |
|
141 QRectF itemRect(itemBoundingRect(item)); |
|
142 if (fullyVisible) { |
|
143 if (viewRect.contains(itemRect)) { |
|
144 return true; |
|
145 } |
|
146 } else { |
|
147 if (viewRect.intersects(itemRect)) { |
|
148 return true; |
|
149 } |
|
150 } |
|
151 } |
|
152 |
|
153 return false; |
|
154 } |
|
155 |
|
156 /*! |
|
157 Clears the prototype list and deletes the prototypes. |
|
158 */ |
|
159 void HbAbstractItemContainerPrivate::deletePrototypes() |
|
160 { |
|
161 qDeleteAll(mPrototypes); |
|
162 mPrototypes.clear(); |
|
163 } |
|
164 |
|
165 int HbAbstractItemContainerPrivate::findStateItem(const QModelIndex &index) const |
|
166 { |
|
167 for (int current = 0; current < mItemStateList.count(); ++current) { |
|
168 if (mItemStateList.at(current).index == index) { |
|
169 return current; |
|
170 } |
|
171 } |
|
172 return -1; |
|
173 } |
|
174 |
|
175 void HbAbstractItemContainerPrivate::initPrototype(HbAbstractViewItem *prototype) const |
|
176 { |
|
177 prototype->setParentItem(mItemView); |
|
178 prototype->setItemView(mItemView); |
|
179 prototype->resize(QSizeF(0, 0)); |
|
180 prototype->hide(); |
|
181 } |
|
182 |
|
183 HbAbstractViewItem* HbAbstractItemContainerPrivate::createItem(const QModelIndex& index) |
|
184 { |
|
185 Q_Q(HbAbstractItemContainer); |
|
186 |
|
187 HbAbstractViewItem *result = 0; |
|
188 HbAbstractViewItem *prototype = itemPrototype(index); |
|
189 if (prototype) { |
|
190 result = q_check_ptr(prototype->createItem()); |
|
191 Q_ASSERT_X(result->prototype() == prototype, "HbAbstractItemContainerPrivate::createItem", "Copy constructor must be used for creating concrete view items in createItem(). Create your custom view item with 'new YourCustomViewItem(*this)' instead of 'new YourCustomViewItem(this)'"); |
|
192 result->setParentItem(q); |
|
193 |
|
194 emit q->itemCreated(result); |
|
195 } |
|
196 return result; |
|
197 } |
|
198 |
|
199 HbAbstractViewItem* HbAbstractItemContainerPrivate::itemPrototype(const QModelIndex& index) const |
|
200 { |
|
201 Q_Q(const HbAbstractItemContainer); |
|
202 |
|
203 if (mPrototypes.isEmpty()) { |
|
204 HbAbstractViewItem *defaultPrototype = q->createDefaultPrototype(); |
|
205 if (defaultPrototype) { |
|
206 initPrototype(defaultPrototype); |
|
207 |
|
208 mPrototypes.append(defaultPrototype); |
|
209 } |
|
210 } |
|
211 |
|
212 HbAbstractViewItem *result = 0; |
|
213 int count = mPrototypes.count() - 1; |
|
214 for (int i = count; i >= 0; i--) { |
|
215 if (mPrototypes[i]->canSetModelIndex(index)) { |
|
216 result = mPrototypes[i]; |
|
217 break; |
|
218 } |
|
219 } |
|
220 return result; |
|
221 } |
|
222 |
|
223 |
|
224 /*! |
|
225 \private |
|
226 |
|
227 Updates item buffer to contain correct amount of items for the current situation. |
|
228 */ |
|
229 void HbAbstractItemContainerPrivate::updateItemBuffer() |
|
230 { |
|
231 Q_Q(HbAbstractItemContainer); |
|
232 |
|
233 if (!mItemView) { |
|
234 return; |
|
235 } |
|
236 |
|
237 int targetCount = q->maxItemCount(); |
|
238 int itemCount = mItems.count(); |
|
239 |
|
240 if (itemCount == targetCount) { |
|
241 return; |
|
242 } |
|
243 |
|
244 // Store the first item position related to view. |
|
245 QPointer<HbAbstractViewItem> firstItem = mItems.value(qMax(0, itemCount - targetCount)); |
|
246 QPointF firstItemPos; |
|
247 if (firstItem) { |
|
248 firstItemPos = itemBoundingRect(firstItem).topLeft(); |
|
249 } |
|
250 |
|
251 // Perform the incresing/decreasing |
|
252 if (itemCount < targetCount) { |
|
253 increaseBufferSize(targetCount - itemCount); |
|
254 } else { |
|
255 decreaseBufferSize(itemCount - targetCount); |
|
256 } |
|
257 |
|
258 restoreItemPosition(firstItem, firstItemPos); |
|
259 } |
|
260 |
|
261 /*! |
|
262 Increases the item buffer size with given \a amount. |
|
263 |
|
264 First tries to add as many items after the last item with valid |
|
265 index as it can. If there is not enough valid indexes then inserts items |
|
266 before the first item with valid index. |
|
267 */ |
|
268 void HbAbstractItemContainerPrivate::increaseBufferSize(int amount) |
|
269 { |
|
270 Q_Q(HbAbstractItemContainer); |
|
271 |
|
272 // Append new items. |
|
273 QModelIndex index; |
|
274 for (int i = mItems.count() - 1; i >= 0; --i) { |
|
275 index = mItems.at(i)->modelIndex(); |
|
276 if (index.isValid()) { |
|
277 break; |
|
278 } |
|
279 } |
|
280 |
|
281 int itemsAdded = 0; |
|
282 // in practise following conditions must apply: itemview is empty and scrollTo() has been called. |
|
283 // Starts populating items from given mFirstItemIndex |
|
284 if ( !index.isValid() |
|
285 && mFirstItemIndex.isValid()) { |
|
286 index = mFirstItemIndex; |
|
287 q->insertItem(mItems.count(), index); |
|
288 itemsAdded++; |
|
289 |
|
290 mFirstItemIndex = QModelIndex(); |
|
291 } |
|
292 |
|
293 while (itemsAdded < amount) { |
|
294 index = mItemView->modelIterator()->nextIndex(index); |
|
295 if (!index.isValid()) { |
|
296 break; |
|
297 } |
|
298 |
|
299 q->insertItem(mItems.count(), index); |
|
300 itemsAdded++; |
|
301 } |
|
302 |
|
303 if (itemsAdded == amount) { |
|
304 return; |
|
305 } |
|
306 |
|
307 // Prepend new items. |
|
308 for (int i = 0; i < mItems.count(); ++i) { |
|
309 index = mItems.at(i)->modelIndex(); |
|
310 if (index.isValid()) { |
|
311 break; |
|
312 } |
|
313 } |
|
314 |
|
315 while (itemsAdded < amount) { |
|
316 index = mItemView->modelIterator()->previousIndex(index); |
|
317 if (!index.isValid()) { |
|
318 break; |
|
319 } |
|
320 |
|
321 q->insertItem(0, index); |
|
322 itemsAdded++; |
|
323 } |
|
324 } |
|
325 |
|
326 /*! |
|
327 Decreases the item buffer size with given \a amount. |
|
328 |
|
329 Tries to avoid deleting visible items and keep the buffer balanced. |
|
330 */ |
|
331 void HbAbstractItemContainerPrivate::decreaseBufferSize(int amount) |
|
332 { |
|
333 Q_UNUSED(amount) |
|
334 |
|
335 int firstVisible = 0; |
|
336 int lastVisible = 0; |
|
337 firstAndLastVisibleBufferIndex(firstVisible, lastVisible, mItemView->geometry(), false); |
|
338 |
|
339 int deletableItemsOnTop = firstVisible - 1; |
|
340 int deletableItemsOnBottom = mItems.count() - lastVisible - 1; |
|
341 int itemsDeleted = 0; |
|
342 HbAbstractViewItem* item = 0; |
|
343 |
|
344 // in decreasing the buffer we try to keep it balanced |
|
345 while (itemsDeleted < amount ) { |
|
346 if (deletableItemsOnTop > deletableItemsOnBottom) { |
|
347 item = mItems.takeFirst(); |
|
348 deletableItemsOnTop--; |
|
349 } else { |
|
350 item = mItems.takeLast(); |
|
351 deletableItemsOnBottom--; |
|
352 } |
|
353 deleteItem(item); |
|
354 ++itemsDeleted; |
|
355 } |
|
356 |
|
357 } |
|
358 |
|
359 /*! |
|
360 \private |
|
361 */ |
|
362 HbAbstractViewItem* HbAbstractItemContainerPrivate::item(const QModelIndex &index) const |
|
363 { |
|
364 int itemCount = mItems.count(); |
|
365 for (int i = 0; i < itemCount; ++i) { |
|
366 // This could use binary search as model indexes are in sorted. |
|
367 if (mItems.at(i)->modelIndex() == index) { |
|
368 return mItems.at(i); |
|
369 } |
|
370 } |
|
371 |
|
372 // TODO: The lower (commented out) part of the code is an optimized version of the above. |
|
373 // However, there are problems with TreeView's deep models concerning the optimized version. |
|
374 // The optimized version should be fixed and taken into use later on. |
|
375 |
|
376 /* |
|
377 int itemCount = mItems.count(); |
|
378 if (itemCount > 0) { |
|
379 if (index.isValid()) { |
|
380 int itemIndex = mItemView->indexPosition(index) - mItemView->indexPosition(mItems.first()->modelIndex()); |
|
381 return mItems.value(itemIndex); |
|
382 } else { |
|
383 for (int i = 0; i < itemCount; ++i) { |
|
384 // This could use binary search as model indexes are in sorted. |
|
385 HbAbstractViewItem *item = mItems.at(i); |
|
386 if (item->modelIndex() == index) { |
|
387 return item; |
|
388 } |
|
389 } |
|
390 } |
|
391 } |
|
392 */ |
|
393 |
|
394 return 0; |
|
395 } |
|
396 |
|
397 /*! |
|
398 \private |
|
399 |
|
400 Removes \a item with model index \a index. |
|
401 */ |
|
402 void HbAbstractItemContainerPrivate::doRemoveItem(HbAbstractViewItem *item, const QModelIndex &index, bool animate) |
|
403 { |
|
404 if (item) { |
|
405 deleteItem(item, animate); |
|
406 if (!index.isValid()) { |
|
407 mItemStates.remove(index); |
|
408 } |
|
409 } |
|
410 } |
|
411 |
|
412 |
|
413 /*! |
|
414 \private |
|
415 |
|
416 Deletes \a item. |
|
417 */ |
|
418 void HbAbstractItemContainerPrivate::deleteItem(HbAbstractViewItem *item, bool animate) |
|
419 { |
|
420 Q_Q(HbAbstractItemContainer); |
|
421 |
|
422 q->setItemTransientState(item->modelIndex(), item->transientState()); |
|
423 mItems.removeOne(item); |
|
424 q->itemRemoved(item, animate); |
|
425 |
|
426 #ifndef HB_EFFECTS |
|
427 delete item; |
|
428 #else |
|
429 if (!HbEffect::effectRunning(item, "disappear") |
|
430 && !HbEffect::effectRunning(item, "collapse")) { |
|
431 delete item; |
|
432 } |
|
433 #endif |
|
434 } |
|
435 |
|
436 /* |
|
437 \private |
|
438 The previous or the next index must be in the buffer. We cannot assume in this base class |
|
439 that the container is a list we just have to loop through the items and find if the previous |
|
440 or next exist in the buffer. Next index should not be the first one in the buffer and the |
|
441 previous index should not be the last one in the buffer in order this index to the buffer. |
|
442 */ |
|
443 bool HbAbstractItemContainerPrivate::intoContainerBuffer(const QModelIndex &index) const |
|
444 { |
|
445 QModelIndex nextIndex = mItemView->modelIterator()->nextIndex(index); |
|
446 QModelIndex previousIndex = mItemView->modelIterator()->previousIndex(index); |
|
447 |
|
448 int itemCount = mItems.count(); |
|
449 for (int i = 0; i < itemCount; ++i) { |
|
450 QModelIndex currentIndex = mItems.at(i)->modelIndex(); |
|
451 if (currentIndex == nextIndex && i != 0){ |
|
452 return true; |
|
453 } else if (currentIndex == previousIndex && i != (itemCount - 1)) { |
|
454 return true; |
|
455 } |
|
456 } |
|
457 return false; |
|
458 } |
|
459 |
|
460 int HbAbstractItemContainerPrivate::containerBufferIndexForModelIndex(const QModelIndex &index) const |
|
461 { |
|
462 int bufferIndex = 0; |
|
463 QModelIndex nextIndex = mItemView->modelIterator()->nextIndex(index); |
|
464 QModelIndex previousIndex = mItemView->modelIterator()->previousIndex(index); |
|
465 |
|
466 while (bufferIndex < mItems.count()) { |
|
467 QModelIndex currentIndex = mItems.at(bufferIndex)->modelIndex(); |
|
468 if (currentIndex == nextIndex) { |
|
469 break; |
|
470 } |
|
471 |
|
472 ++bufferIndex; |
|
473 |
|
474 if (currentIndex == previousIndex) { |
|
475 break; |
|
476 } |
|
477 } |
|
478 return bufferIndex; |
|
479 } |
|
480 |
|
481 void HbAbstractItemContainerPrivate::restoreItemPosition(HbAbstractViewItem *item, const QPointF &position) |
|
482 { |
|
483 Q_Q(HbAbstractItemContainer); |
|
484 |
|
485 if (item) { |
|
486 QPointF delta = itemBoundingRect(item).topLeft() - position; |
|
487 if (!delta.isNull()) { |
|
488 q->setPos(q->pos() - delta); |
|
489 |
|
490 if (mItemView) { |
|
491 // this will force the HbScrollArea to adjust the content correctly. Adjustment |
|
492 // is not done in the setPos generated event handling by default to speed up scrolling. |
|
493 HbAbstractItemViewPrivate::d_ptr(mItemView)->adjustContent(); |
|
494 } |
|
495 } |
|
496 } |
|
497 } |
|
498 |
|
499 void HbAbstractItemContainerPrivate::insertItem(HbAbstractViewItem *item, int pos, const QModelIndex &index, bool animate) |
|
500 { |
|
501 Q_Q(HbAbstractItemContainer); |
|
502 |
|
503 if (item) { |
|
504 mItems.insert(pos, item); |
|
505 q->itemAdded(pos, item, animate); |
|
506 |
|
507 q->setItemModelIndex(item, index); |
|
508 } |
|
509 } |
|
510 |
|
511 qreal HbAbstractItemContainerPrivate::getDiffWithoutScrollareaCompensation(const QPointF &delta) const |
|
512 { |
|
513 Q_Q( const HbAbstractItemContainer); |
|
514 const QSizeF containerSize(q->size()); |
|
515 const QPointF containerPos(q->pos()); |
|
516 qreal diff = 0.0; |
|
517 qreal invisibleArea = 0.0; |
|
518 if (delta.y() > 0) { |
|
519 // space at the bottom |
|
520 QSizeF viewSize = mItemView->size(); |
|
521 invisibleArea = containerSize.height() - viewSize.height() + containerPos.y(); |
|
522 if (invisibleArea < delta.y()) { |
|
523 diff = delta.y() - invisibleArea; |
|
524 } |
|
525 } else { |
|
526 // space at the top |
|
527 invisibleArea = -containerPos.y(); |
|
528 if (containerPos.y() > delta.y()) { |
|
529 diff = delta.y() + invisibleArea; |
|
530 } |
|
531 } |
|
532 |
|
533 return diff; |
|
534 } |
|
535 |
|
536 /*! |
|
537 Constructs a new HbAbstractItemContainer with \a parent. |
|
538 */ |
|
539 HbAbstractItemContainer::HbAbstractItemContainer(QGraphicsItem *parent) : |
|
540 HbWidget(*new HbAbstractItemContainerPrivate(), parent) |
|
541 { |
|
542 Q_D(HbAbstractItemContainer); |
|
543 |
|
544 d->q_ptr = this; |
|
545 d->init(); |
|
546 } |
|
547 |
|
548 /*! |
|
549 Constructs an item with private class object \a dd and \a parent. |
|
550 */ |
|
551 HbAbstractItemContainer::HbAbstractItemContainer(HbAbstractItemContainerPrivate &dd, QGraphicsItem *parent) : |
|
552 HbWidget(dd, parent) |
|
553 { |
|
554 Q_D(HbAbstractItemContainer); |
|
555 |
|
556 d->q_ptr = this; |
|
557 d->init(); |
|
558 } |
|
559 |
|
560 /*! |
|
561 Destroys the container. |
|
562 */ |
|
563 HbAbstractItemContainer::~HbAbstractItemContainer() |
|
564 { |
|
565 } |
|
566 |
|
567 /*! |
|
568 \reimp |
|
569 */ |
|
570 bool HbAbstractItemContainer::event(QEvent *e) |
|
571 { |
|
572 if (e->type() == QEvent::LayoutRequest) { |
|
573 QGraphicsWidget *parentWid = parentWidget(); |
|
574 if (!parentLayoutItem() |
|
575 && parentWid) { |
|
576 HbApplication::postEvent(parentWid, new QEvent(QEvent::LayoutRequest)); |
|
577 } |
|
578 Q_D(HbAbstractItemContainer); |
|
579 d->updateItemBuffer(); |
|
580 } else if (e->type() == UpdateItemBufferEvent) { |
|
581 Q_D(HbAbstractItemContainer); |
|
582 d->updateItemBuffer(); |
|
583 } |
|
584 |
|
585 return HbWidget::event(e); |
|
586 } |
|
587 |
|
588 /*! |
|
589 Returns view item object corresponding given model \a index. This might be 0 pointer if |
|
590 there is no view item representing given index or given \a index is invalid. |
|
591 */ |
|
592 HbAbstractViewItem* HbAbstractItemContainer::itemByIndex(const QModelIndex &index) const |
|
593 { |
|
594 Q_D(const HbAbstractItemContainer); |
|
595 |
|
596 if (!index.isValid()) { |
|
597 return 0; |
|
598 } |
|
599 return d->item(index); |
|
600 } |
|
601 |
|
602 /*! |
|
603 This function is provided for convenience. |
|
604 |
|
605 It removes all the items from the container and clears the internal state model. |
|
606 */ |
|
607 void HbAbstractItemContainer::removeItems() |
|
608 { |
|
609 Q_D(HbAbstractItemContainer); |
|
610 |
|
611 qDeleteAll(d->mItems); |
|
612 d->mItems.clear(); |
|
613 d->mItemStateList.clear(); |
|
614 d->mItemStates.clear(); |
|
615 } |
|
616 |
|
617 /*! |
|
618 Returns the item view that container is connected. |
|
619 */ |
|
620 HbAbstractItemView *HbAbstractItemContainer::itemView() const |
|
621 { |
|
622 Q_D(const HbAbstractItemContainer); |
|
623 return d->mItemView; |
|
624 } |
|
625 |
|
626 /*! |
|
627 Sets item \a view that container is connected. |
|
628 */ |
|
629 void HbAbstractItemContainer::setItemView(HbAbstractItemView *view) |
|
630 { |
|
631 Q_D(HbAbstractItemContainer); |
|
632 |
|
633 if (view != d->mItemView) { |
|
634 if (d->mItemView) { |
|
635 d->mItemView->removeEventFilter(this); |
|
636 } |
|
637 |
|
638 d->mItemView = view; |
|
639 |
|
640 foreach (HbAbstractViewItem *prototype, d->mPrototypes) { |
|
641 d->initPrototype(prototype); |
|
642 } |
|
643 |
|
644 if (d->mItemView) { |
|
645 setParentItem(view); |
|
646 d->mItemView->installEventFilter(this); |
|
647 } |
|
648 } |
|
649 } |
|
650 |
|
651 /*! |
|
652 Assigns new model \a index to the given \a item. Item's current state is saved |
|
653 and state for \a index is restored to item. |
|
654 */ |
|
655 void HbAbstractItemContainer::setItemModelIndex(HbAbstractViewItem *item, const QModelIndex &index) |
|
656 { |
|
657 |
|
658 if (item && item->modelIndex() != index) { |
|
659 |
|
660 setItemTransientState(item->modelIndex(), item->transientState()); |
|
661 |
|
662 // Transfer the state from item currently representing index to new item, if such exists. |
|
663 HbAbstractViewItem *oldItem = itemByIndex(index); |
|
664 |
|
665 if (oldItem) { |
|
666 item->setTransientState(oldItem->transientState()); |
|
667 } else { |
|
668 item->setTransientState(itemTransientState(index)); |
|
669 } |
|
670 |
|
671 |
|
672 item->setModelIndex(index); |
|
673 } |
|
674 } |
|
675 |
|
676 /*! |
|
677 Sets item's model indexes starting from given \a startIndex. If \a startIndex is |
|
678 QModelIndex() then startIndex is taken from the first item. |
|
679 |
|
680 \note If there are not enough model indexes from \a startIndex to last model index |
|
681 for all the items then QModelIndex() is assigned to rest of the view items. |
|
682 */ |
|
683 void HbAbstractItemContainer::setModelIndexes(const QModelIndex &startIndex) |
|
684 { |
|
685 Q_D(HbAbstractItemContainer); |
|
686 |
|
687 if (!d->mItemView || !d->mItemView->model()) { |
|
688 return; |
|
689 } |
|
690 |
|
691 QModelIndex index = startIndex; |
|
692 if (!index.isValid()) { |
|
693 if (!d->mItems.isEmpty()) { |
|
694 index = d->mItems.first()->modelIndex(); |
|
695 } |
|
696 |
|
697 if (!index.isValid()) { |
|
698 index = d->mItemView->modelIterator()->nextIndex(index); |
|
699 } |
|
700 } |
|
701 |
|
702 QModelIndexList indexList; |
|
703 |
|
704 int itemCount(d->mItems.count()); |
|
705 |
|
706 if (itemCount != 0 && index.isValid()) { |
|
707 indexList.append(index); |
|
708 } |
|
709 |
|
710 for (int i = indexList.count(); i < itemCount; ++i) { |
|
711 index = d->mItemView->modelIterator()->nextIndex(indexList.last()); |
|
712 |
|
713 if (index.isValid()) { |
|
714 indexList.append(index); |
|
715 } else { |
|
716 break; |
|
717 } |
|
718 } |
|
719 |
|
720 for (int i = indexList.count(); i < itemCount; ++i) { |
|
721 index = d->mItemView->modelIterator()->previousIndex(indexList.first()); |
|
722 |
|
723 if (index.isValid()) { |
|
724 indexList.prepend(index); |
|
725 } else { |
|
726 break; |
|
727 } |
|
728 } |
|
729 |
|
730 // if items have been added/removed in the middle of the buffer, there might be items |
|
731 // that can be reused at the end of the buffer. The following block will scan for |
|
732 // those items and move them in correct position |
|
733 int lastUsedItem = -1; |
|
734 for (int indexCounter = 0; indexCounter < indexList.count(); ++indexCounter) { |
|
735 HbAbstractViewItem *item = d->mItems.at(indexCounter); |
|
736 if (item && item->modelIndex() == indexList.at(indexCounter)) { |
|
737 lastUsedItem = indexCounter; |
|
738 } else { |
|
739 for (int itemCounter = lastUsedItem + 1; itemCounter < d->mItems.count(); itemCounter++) { |
|
740 HbAbstractViewItem *item2 = d->mItems.at(itemCounter); |
|
741 if (item2->modelIndex() == indexList.at(indexCounter)) { |
|
742 d->mItems.swap(indexCounter, itemCounter); |
|
743 itemRemoved(d->mItems.at(indexCounter), false); |
|
744 itemRemoved(d->mItems.at(itemCounter), false); |
|
745 itemAdded(qMin(indexCounter, itemCounter), d->mItems.at(qMin(indexCounter, itemCounter)), false); |
|
746 itemAdded(qMax(indexCounter, itemCounter), d->mItems.at(qMax(indexCounter, itemCounter)), false); |
|
747 lastUsedItem = itemCounter; |
|
748 break; |
|
749 } |
|
750 } |
|
751 } |
|
752 } |
|
753 |
|
754 int indexCount(indexList.count()); |
|
755 for (int i = 0; i < itemCount; ++i) { |
|
756 HbAbstractViewItem *item = d->mItems.at(i); |
|
757 HbAbstractViewItem *prototype = 0; |
|
758 // setItemModelIndex() is considered recycling. It is called only, if recycling is permitted |
|
759 if (i < indexCount) { |
|
760 prototype = d->itemPrototype(indexList.at(i)); |
|
761 } |
|
762 if (prototype) { |
|
763 if ( prototype == item->prototype() |
|
764 && d->mItemRecycling) { |
|
765 setItemModelIndex(item, indexList.at(i)); |
|
766 } else if ( d->mItemRecycling |
|
767 || ( !d->mItemRecycling |
|
768 && item->modelIndex() != indexList.at(i))) { |
|
769 d->deleteItem(item); |
|
770 insertItem(i, indexList.at(i)); |
|
771 } // else: !d->mItemRecycling && item->modelIndex().isValid() == indexList.at(i)) |
|
772 } else { |
|
773 d->deleteItem(item); |
|
774 itemCount--; |
|
775 i--; |
|
776 } |
|
777 } |
|
778 } |
|
779 |
|
780 /*! |
|
781 Returns list of items inside item buffer. |
|
782 */ |
|
783 QList<HbAbstractViewItem *> HbAbstractItemContainer::items() const |
|
784 { |
|
785 Q_D(const HbAbstractItemContainer); |
|
786 return d->mItems; |
|
787 } |
|
788 |
|
789 /*! |
|
790 Adds item for model \a index to container. |
|
791 */ |
|
792 void HbAbstractItemContainer::addItem(const QModelIndex &index, bool animate) |
|
793 { |
|
794 Q_D(HbAbstractItemContainer); |
|
795 |
|
796 // Add new item if maximum item count allows it or item falls within the range of |
|
797 // item buffer items. |
|
798 if (d->intoContainerBuffer(index)) { |
|
799 int bufferIndex = d->containerBufferIndexForModelIndex(index); |
|
800 |
|
801 if (bufferIndex >= d->mItems.count() |
|
802 || d->mItems.at(bufferIndex)->modelIndex() != index) { |
|
803 // Store the second item position related to view. |
|
804 HbAbstractViewItem *referenceItem = d->mItems.value(1); |
|
805 QPointF referenceItemPos; |
|
806 if (referenceItem) { |
|
807 referenceItemPos = d->itemBoundingRect(referenceItem).topLeft(); |
|
808 } |
|
809 |
|
810 HbAbstractViewItem *recycledItem = 0; |
|
811 QRectF viewRect = d->itemBoundingRect(d->mItemView); |
|
812 |
|
813 if (d->mItemRecycling && !viewRect.isEmpty()) { |
|
814 // Recycling allowed. Try recycling the items from buffer. |
|
815 int firstVisible = 0; |
|
816 int lastVisible = 0; |
|
817 d->firstAndLastVisibleBufferIndex(firstVisible, lastVisible, viewRect, false); |
|
818 |
|
819 int itemsOnTop = firstVisible - 1; |
|
820 int itemsOnBottom = d->mItems.count() - lastVisible - 1; |
|
821 |
|
822 if (itemsOnBottom > 0) { |
|
823 recycledItem = d->mItems.takeLast(); |
|
824 } else if (itemsOnTop > 0) { |
|
825 recycledItem = d->mItems.takeFirst(); |
|
826 bufferIndex--; |
|
827 bufferIndex = qMax(0, bufferIndex); |
|
828 } |
|
829 |
|
830 if (recycledItem) { |
|
831 itemRemoved(recycledItem, false); |
|
832 d->insertItem(recycledItem, bufferIndex, index, animate); |
|
833 } |
|
834 } |
|
835 |
|
836 if (!recycledItem) { |
|
837 // No recycling has happened. Insert completely new item. |
|
838 insertItem(bufferIndex, index, animate); |
|
839 } |
|
840 |
|
841 // Restore second item position. |
|
842 d->restoreItemPosition(referenceItem, referenceItemPos); |
|
843 |
|
844 if (!recycledItem && d->mItemRecycling) { |
|
845 // Resize the buffer. |
|
846 d->updateItemBuffer(); |
|
847 } |
|
848 } |
|
849 } else if (d->mItems.count() < maxItemCount()) { |
|
850 d->updateItemBuffer(); |
|
851 } |
|
852 } |
|
853 |
|
854 /*! |
|
855 Removes item representing \a index from container. |
|
856 */ |
|
857 void HbAbstractItemContainer::removeItem(const QModelIndex &index, bool animate) |
|
858 { |
|
859 Q_D(HbAbstractItemContainer); |
|
860 |
|
861 HbAbstractViewItem *item = d->item(index); |
|
862 d->doRemoveItem(item, index, animate); |
|
863 } |
|
864 |
|
865 /*! |
|
866 Removes item from \a pos. |
|
867 */ |
|
868 void HbAbstractItemContainer::removeItem(int pos, bool animate) |
|
869 { |
|
870 Q_D(HbAbstractItemContainer); |
|
871 |
|
872 Q_ASSERT(pos >= 0 && pos < d->mItems.count()); |
|
873 |
|
874 HbAbstractViewItem *item = d->mItems.at(pos); |
|
875 QModelIndex index = item->modelIndex(); |
|
876 d->doRemoveItem(item, index, animate); |
|
877 } |
|
878 |
|
879 /*! |
|
880 Derived class should implement this function to perform item recycling based on container \a delta. |
|
881 Given \a delta is the distance between container's current position and desired new position. Recycling |
|
882 should be done based on the new position and function should return the actual delta. Actual delta could |
|
883 be modified value from \a delta. Delta modification can be useful e.g. to compensate the item changes |
|
884 that are caused by relayouting of items after changing the item position within layout. |
|
885 |
|
886 Default implementation does not recycle items and returns the \a delta unchanged. |
|
887 */ |
|
888 QPointF HbAbstractItemContainer::recycleItems(const QPointF &delta) |
|
889 { |
|
890 return delta; |
|
891 } |
|
892 |
|
893 /*! |
|
894 \reimp |
|
895 */ |
|
896 bool HbAbstractItemContainer::eventFilter(QObject *obj, QEvent *event) |
|
897 { |
|
898 Q_D(HbAbstractItemContainer); |
|
899 |
|
900 if (obj == d->mItemView) { |
|
901 switch (event->type()){ |
|
902 case QEvent::GraphicsSceneResize: { |
|
903 viewResized(d->mItemView->size()); |
|
904 d->updateItemBuffer(); |
|
905 break; |
|
906 } |
|
907 default: |
|
908 break; |
|
909 } |
|
910 } |
|
911 |
|
912 return HbWidget::eventFilter(obj, event); |
|
913 } |
|
914 |
|
915 /*! |
|
916 Returns maximum amount of items that item buffer can hold. |
|
917 |
|
918 Default implementation returns the total number of indexes that can |
|
919 be accessed on current view. |
|
920 |
|
921 Derived class that supports item recycling should define their own implementation |
|
922 for this function. |
|
923 |
|
924 \note Return value should not be more than HbAbstractItemView::indexCount() |
|
925 */ |
|
926 int HbAbstractItemContainer::maxItemCount() const |
|
927 { |
|
928 Q_D(const HbAbstractItemContainer); |
|
929 |
|
930 if (d->mItemView) { |
|
931 return d->mItemView->modelIterator()->indexCount(); |
|
932 } else { |
|
933 return 0; |
|
934 } |
|
935 } |
|
936 |
|
937 /*! |
|
938 Deletes other prototypes and sets \a prototype as only prototype. |
|
939 |
|
940 Returns true if the prototype list was changed; otherwise returns false. |
|
941 */ |
|
942 bool HbAbstractItemContainer::setItemPrototype(HbAbstractViewItem *prototype) |
|
943 { |
|
944 QList<HbAbstractViewItem *> prototypeList; |
|
945 prototypeList.append(prototype); |
|
946 return setItemPrototypes(prototypeList); |
|
947 } |
|
948 |
|
949 /*! |
|
950 Returns the list of item prototypes. |
|
951 */ |
|
952 QList<HbAbstractViewItem *> HbAbstractItemContainer::itemPrototypes() const |
|
953 { |
|
954 Q_D(const HbAbstractItemContainer); |
|
955 |
|
956 if (d->mPrototypes.isEmpty()) { |
|
957 HbAbstractViewItem *defaultPrototype = createDefaultPrototype(); |
|
958 if (defaultPrototype) { |
|
959 d->initPrototype(defaultPrototype); |
|
960 |
|
961 d->mPrototypes.append(defaultPrototype); |
|
962 } |
|
963 } |
|
964 |
|
965 return d->mPrototypes; |
|
966 } |
|
967 |
|
968 /*! |
|
969 Sets the list of prototypes. |
|
970 |
|
971 Returns true if the prototype list was changed; otherwise returns false. |
|
972 */ |
|
973 bool HbAbstractItemContainer::setItemPrototypes(const QList<HbAbstractViewItem *> &prototypes) |
|
974 { |
|
975 Q_D(HbAbstractItemContainer); |
|
976 |
|
977 bool changed = false; |
|
978 |
|
979 if (prototypes.count() > 0) { |
|
980 if (d->mPrototypes != prototypes) { |
|
981 foreach (HbAbstractViewItem *prototype, d->mPrototypes) { |
|
982 if (!prototypes.contains(prototype)) { |
|
983 delete prototype; |
|
984 } |
|
985 } |
|
986 |
|
987 foreach (HbAbstractViewItem *prototype, prototypes) { |
|
988 if (!d->mPrototypes.contains(prototype)) { |
|
989 d->initPrototype(prototype); |
|
990 } |
|
991 } |
|
992 changed = true; |
|
993 } |
|
994 |
|
995 d->mPrototypes = prototypes; |
|
996 } |
|
997 |
|
998 return changed; |
|
999 |
|
1000 } |
|
1001 |
|
1002 /*! |
|
1003 Returns transient state of view item with \a index. |
|
1004 */ |
|
1005 QHash<QString, QVariant> HbAbstractItemContainer::itemTransientState(const QModelIndex &index) const |
|
1006 { |
|
1007 Q_D(const HbAbstractItemContainer); |
|
1008 return d->mItemStates.value(index); |
|
1009 } |
|
1010 |
|
1011 /*! |
|
1012 This is an overloaded member function, provided for convenience. |
|
1013 |
|
1014 Stores \a key with \a value of a view item with \a index into state model. |
|
1015 \a key is usually name of a Qt property. If \a value is invalid, state item with the \a key is removed. |
|
1016 |
|
1017 Default values of properties should not be added. |
|
1018 */ |
|
1019 void HbAbstractItemContainer::setItemTransientStateValue(const QModelIndex &index, const QString &key, const QVariant &value) |
|
1020 { |
|
1021 Q_D(HbAbstractItemContainer); |
|
1022 if (index.isValid()) { |
|
1023 QHash<QString, QVariant> stateItem = d->mItemStates.value(index); |
|
1024 if (!value.isValid()) { |
|
1025 stateItem.remove(key); |
|
1026 } else { |
|
1027 stateItem.insert(key, value); |
|
1028 } |
|
1029 if (stateItem.count()) { |
|
1030 d->mItemStates.insert(index, stateItem); |
|
1031 } else { |
|
1032 d->mItemStates.remove(index); |
|
1033 } |
|
1034 } else { |
|
1035 d->mItemStates.remove(index); |
|
1036 } |
|
1037 } |
|
1038 |
|
1039 |
|
1040 /*! |
|
1041 Stores state of a view item with \a index into item state model. State of the view item is usually |
|
1042 retrieved by calling HbAbstractViewItem::transientState(). |
|
1043 |
|
1044 Existing state is replaced. If \a state is empty, existing state is removed. |
|
1045 Default values of state items should not be added into \a state. |
|
1046 |
|
1047 \sa HbAbstractViewItem::transientState() |
|
1048 */ |
|
1049 void HbAbstractItemContainer::setItemTransientState(const QModelIndex &index, QHash<QString,QVariant> state) |
|
1050 { |
|
1051 Q_D(HbAbstractItemContainer); |
|
1052 if (index.isValid() && state.count()) { |
|
1053 d->mItemStates.insert(index, state); |
|
1054 } else { |
|
1055 d->mItemStates.remove(index); |
|
1056 } |
|
1057 } |
|
1058 |
|
1059 /*! |
|
1060 \reimp |
|
1061 */ |
|
1062 QVariant HbAbstractItemContainer::itemChange(GraphicsItemChange change, const QVariant & value) |
|
1063 { |
|
1064 return HbWidget::itemChange(change, value); |
|
1065 } |
|
1066 |
|
1067 /*! |
|
1068 Returns the model indexes of items that are located on top left and bottom right corners |
|
1069 of visible area. |
|
1070 */ |
|
1071 void HbAbstractItemContainer::firstAndLastVisibleModelIndex( |
|
1072 QModelIndex& firstVisibleModelIndex, |
|
1073 QModelIndex& lastVisibleModelIndex, |
|
1074 bool fullyVisible) const |
|
1075 { |
|
1076 Q_D(const HbAbstractItemContainer); |
|
1077 |
|
1078 QRectF viewRect(d->itemBoundingRect(d->mItemView)); |
|
1079 |
|
1080 int firstVisibleBufferIndex( -1 ); |
|
1081 int lastVisibleBufferIndex( -1 ); |
|
1082 d->firstAndLastVisibleBufferIndex( firstVisibleBufferIndex, lastVisibleBufferIndex, viewRect, fullyVisible ); |
|
1083 if (firstVisibleBufferIndex != -1) { |
|
1084 firstVisibleModelIndex = d->mItems.at(firstVisibleBufferIndex)->modelIndex(); |
|
1085 } |
|
1086 if (lastVisibleBufferIndex != -1) { |
|
1087 lastVisibleModelIndex = d->mItems.at(lastVisibleBufferIndex)->modelIndex(); |
|
1088 } |
|
1089 } |
|
1090 |
|
1091 /*! |
|
1092 Clears the state model. |
|
1093 */ |
|
1094 void HbAbstractItemContainer::removeItemTransientStates() |
|
1095 { |
|
1096 Q_D(HbAbstractItemContainer); |
|
1097 d->mItemStateList.clear(); |
|
1098 d->mItemStates.clear(); |
|
1099 } |
|
1100 |
|
1101 /*! |
|
1102 Sets item recycling to \a enabled. |
|
1103 By default recycling is off. |
|
1104 */ |
|
1105 void HbAbstractItemContainer::setItemRecycling(bool enabled) |
|
1106 { |
|
1107 Q_D(HbAbstractItemContainer); |
|
1108 if (d->mItemRecycling != enabled) { |
|
1109 d->mItemRecycling = enabled; |
|
1110 if (!enabled) { |
|
1111 removeItemTransientStates(); |
|
1112 } |
|
1113 |
|
1114 d->updateItemBuffer(); |
|
1115 } |
|
1116 } |
|
1117 |
|
1118 /*! |
|
1119 Returns whether item recycling feature is in use. |
|
1120 */ |
|
1121 bool HbAbstractItemContainer::itemRecycling() const |
|
1122 { |
|
1123 Q_D(const HbAbstractItemContainer); |
|
1124 return d->mItemRecycling; |
|
1125 } |
|
1126 |
|
1127 /*! |
|
1128 Sets the feature informing whether all items in the item view have the same size. |
|
1129 In case all the items have the same size, the item view can do some |
|
1130 optimizations for performance purposes. |
|
1131 */ |
|
1132 void HbAbstractItemContainer::setUniformItemSizes(bool enable) |
|
1133 { |
|
1134 Q_D(HbAbstractItemContainer); |
|
1135 d->mUniformItemSizes = enable; |
|
1136 } |
|
1137 |
|
1138 /*! |
|
1139 Returns whether the uniform item sizes feature is in use. |
|
1140 */ |
|
1141 bool HbAbstractItemContainer::uniformItemSizes() const |
|
1142 { |
|
1143 Q_D(const HbAbstractItemContainer); |
|
1144 return d->mUniformItemSizes; |
|
1145 } |
|
1146 |
|
1147 /*! |
|
1148 Inserts item for \a index to \a pos. |
|
1149 */ |
|
1150 void HbAbstractItemContainer::insertItem(int pos, const QModelIndex &index, bool animate) |
|
1151 { |
|
1152 Q_D(HbAbstractItemContainer); |
|
1153 HbAbstractViewItem *item = d->createItem(index); |
|
1154 d->insertItem(item, pos, index, animate); |
|
1155 } |
|
1156 |
|
1157 /*! |
|
1158 Reset the internal state of the container. |
|
1159 */ |
|
1160 void HbAbstractItemContainer::reset() |
|
1161 { |
|
1162 // position need to be reseted while changing model |
|
1163 setPos(0.0, 0.0); |
|
1164 removeItems(); |
|
1165 QCoreApplication::postEvent(this, new QEvent((QEvent::Type)UpdateItemBufferEvent)); |
|
1166 } |
|
1167 |
|
1168 /*! |
|
1169 */ |
|
1170 void HbAbstractItemContainer::resizeContainer() |
|
1171 { |
|
1172 Q_D(HbAbstractItemContainer); |
|
1173 |
|
1174 QSizeF newSize = effectiveSizeHint(Qt::PreferredSize); |
|
1175 |
|
1176 if (d->mItemView) { |
|
1177 Qt::Orientations scrollingDirections = d->mItemView->scrollDirections(); |
|
1178 |
|
1179 if (!scrollingDirections.testFlag(Qt::Vertical)) { |
|
1180 newSize.setHeight(d->mItemView->size().height()); |
|
1181 } |
|
1182 |
|
1183 if (!scrollingDirections.testFlag(Qt::Horizontal)) { |
|
1184 newSize.setWidth(d->mItemView->size().width()); |
|
1185 } |
|
1186 } |
|
1187 |
|
1188 resize(newSize); |
|
1189 } |
|
1190 |
|
1191 #include "moc_hbabstractitemcontainer_p.cpp" |
|
1192 |