|
1 /* |
|
2 * Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies). |
|
3 * All rights reserved. |
|
4 * This component and the accompanying materials are made available |
|
5 * under the terms of "Eclipse Public License v1.0" |
|
6 * which accompanies this distribution, and is available |
|
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 * |
|
9 * Initial Contributors: |
|
10 * Nokia Corporation - initial contribution. |
|
11 * |
|
12 * Contributors: |
|
13 * |
|
14 * Description: List of media items |
|
15 * |
|
16 */ |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 // my include |
|
22 #include "glxitemlist.h" |
|
23 |
|
24 // system includes |
|
25 #include <glxassert.h> |
|
26 #include <glxtracer.h> |
|
27 #include <mpxcollectionpath.h> |
|
28 |
|
29 // user includes |
|
30 #include "mglxitemlistobserver.h" |
|
31 #include "mglxmediapool.h" |
|
32 |
|
33 using namespace NGlxItemList; |
|
34 |
|
35 namespace NGlxItemList |
|
36 { |
|
37 // ----------------------------------------------------------------------------- |
|
38 // Remove item from list and remove linking |
|
39 // ----------------------------------------------------------------------------- |
|
40 // |
|
41 void RemoveItem( TInt aIndex, RArray< TGlxMedia >& aList, |
|
42 MGlxMediaUser& aMediaUser ) |
|
43 { |
|
44 // Remove link between TGlxMedia and CGlxMedia |
|
45 aList[ aIndex ].SetMedia( NULL, aMediaUser ); |
|
46 // Remove from list |
|
47 aList.Remove( aIndex ); |
|
48 } |
|
49 |
|
50 /** |
|
51 * Class to contain change processing current state and difference length |
|
52 * |
|
53 * @author Aki Vanhatalo |
|
54 */ |
|
55 NONSHARABLE_CLASS ( TDifferenceInfo ) |
|
56 { |
|
57 public: |
|
58 /** Constructor */ |
|
59 TDifferenceInfo() |
|
60 { |
|
61 iSourceIndex = 0; |
|
62 iTargetIndex = 0; |
|
63 iCount = 0; |
|
64 } |
|
65 |
|
66 /// current index on source list. Modification strategy can change this |
|
67 /// to skip items in the source list. |
|
68 TInt iSourceIndex; |
|
69 |
|
70 /// current index on target list. Modification strategy can change this |
|
71 /// to skip items in the target list. |
|
72 TInt iTargetIndex; |
|
73 |
|
74 /// lenght of change: number of items that would need to be added or removed |
|
75 /// to eliminate difference between source and target list. It is up to |
|
76 /// the modification strategy to decide if it actually eliminates the |
|
77 /// difference or simply observes it. Any change of this variable by |
|
78 /// a strategy is ignored. |
|
79 TInt iCount; |
|
80 }; |
|
81 |
|
82 /** |
|
83 * Modification strategy interface |
|
84 * |
|
85 * This interface will be called when differences are found in source and |
|
86 * target list, and instructs how the differences should be solved, |
|
87 * |
|
88 * Use of modification strategies allows the same difference evaluation |
|
89 * algorithm to be used for both calculating required target array space, and |
|
90 * for doing the actual changes to into the target array. |
|
91 * |
|
92 * @author Aki Vanhatalo |
|
93 */ |
|
94 class MListModificationStrategy |
|
95 { |
|
96 public: |
|
97 /** |
|
98 * There is a difference in lists that requires removing items for |
|
99 * the difference to be eliminated |
|
100 * @param aInfo iTargetIndex index from which to remove items |
|
101 * iCount number of items that would need to be removed |
|
102 * iSourceIndex index in source list (to allow adjustement) |
|
103 */ |
|
104 virtual void Remove( TDifferenceInfo& aInfo ) = 0; |
|
105 |
|
106 /** |
|
107 * There is a difference in lists that requires adding items from source |
|
108 * list to target lits for the difference to be eliminated |
|
109 * @param aInfo iTargetIndex index in target list at which to insert |
|
110 * iCount number of items that would need to be copied |
|
111 * iSourceIndex index in source list from which to copy |
|
112 */ |
|
113 virtual void Insert( TDifferenceInfo& aInfo ) = 0; |
|
114 }; |
|
115 |
|
116 /** |
|
117 * Modification strategy that calculates how much slack space is required for |
|
118 * differences in lists to be fixed (i.e., TChangeListStategy to be executed) |
|
119 * |
|
120 * @author Aki Vanhatalo |
|
121 */ |
|
122 NONSHARABLE_CLASS( TCalculateRequiredSpaceStrategy ) : |
|
123 public MListModificationStrategy |
|
124 { |
|
125 public: |
|
126 /** Constructor */ |
|
127 TCalculateRequiredSpaceStrategy( const RArray< TGlxMedia >& aTargetList ) |
|
128 { |
|
129 // Pick up initial required space |
|
130 iRequiredSpace = aTargetList.Count(); |
|
131 // Set initial value |
|
132 iCurrentLengthDifference = 0; |
|
133 } |
|
134 |
|
135 // from MListModificationStrategy |
|
136 void Remove( TDifferenceInfo& aInfo ) |
|
137 { |
|
138 // Currently required space is reduced |
|
139 iCurrentLengthDifference -= aInfo.iCount; |
|
140 |
|
141 // Skip over "removed" items (items were not removed, simply counted) |
|
142 aInfo.iTargetIndex += aInfo.iCount; |
|
143 } |
|
144 |
|
145 // from MListModificationStrategy |
|
146 void Insert( TDifferenceInfo& aInfo ) |
|
147 { |
|
148 // Currently required space is increased |
|
149 iCurrentLengthDifference += aInfo.iCount; |
|
150 // Check if the currently required space is the maximum space |
|
151 iRequiredSpace = Max( iCurrentLengthDifference, iRequiredSpace ); |
|
152 |
|
153 // Skip over source items, but not target items, since nothing was |
|
154 // actually inserted |
|
155 aInfo.iSourceIndex += aInfo.iCount; |
|
156 } |
|
157 |
|
158 public: // allow public since internal to CGlxItemList implementation |
|
159 /// space required in item list for TChangeListStategy to be |
|
160 /// executable without fail |
|
161 TInt iRequiredSpace; |
|
162 /// current difference in space needed; iRequiredSpace is a max of this |
|
163 TInt iCurrentLengthDifference; |
|
164 }; |
|
165 |
|
166 /** |
|
167 * Modification strategy that fixes the differences between source and target |
|
168 * list. |
|
169 * |
|
170 * Note: Implementation assumes that there is enough slack space in target |
|
171 * list for insertion operations to always succeed. (I.e., |
|
172 * TCalculateRequiredSpaceStrategy needs to be used first to find out how much |
|
173 * slack is required) |
|
174 * |
|
175 * @author Aki Vanhatalo |
|
176 */ |
|
177 NONSHARABLE_CLASS( TChangeListStategy ) : public MListModificationStrategy |
|
178 { |
|
179 public: |
|
180 /** Constructor */ |
|
181 TChangeListStategy( const CMPXCollectionPath& aSourceList, |
|
182 const TGlxIdSpaceId& aIdSpaceId, RArray< TGlxMedia >& aTargetList, |
|
183 const MGlxMediaPool& aMediaPool, MGlxMediaUser& aMediaUser, |
|
184 MGlxItemListObserver& aObserver ) |
|
185 : iSource( aSourceList ), iTarget( aTargetList ), |
|
186 iMediaPool( aMediaPool ), iIdSpaceId( aIdSpaceId ), |
|
187 iMediaUser( aMediaUser ), iObserver( aObserver ) |
|
188 { |
|
189 // do nothing (more) |
|
190 } |
|
191 |
|
192 // from MListModificationStrategy |
|
193 void Remove( TDifferenceInfo& aInfo ) |
|
194 { |
|
195 // Remove block from target list, start from end and work backwards (quicker) |
|
196 for ( TInt i = aInfo.iTargetIndex + aInfo.iCount - 1; |
|
197 i >= aInfo.iTargetIndex; |
|
198 i-- ) |
|
199 { |
|
200 RemoveItem( i, iTarget, iMediaUser ); |
|
201 } |
|
202 |
|
203 // Notify observer |
|
204 iObserver.HandleItemsRemoved( aInfo.iTargetIndex, aInfo.iCount ); |
|
205 } |
|
206 |
|
207 // from MListModificationStrategy |
|
208 void Insert( TDifferenceInfo& aInfo ) |
|
209 { |
|
210 // Insert all items to target list |
|
211 InsertBlock( aInfo ); |
|
212 |
|
213 // Notify observer |
|
214 iObserver.HandleItemsAdded( aInfo.iTargetIndex, aInfo.iCount ); |
|
215 |
|
216 // Skip over inserted and source items |
|
217 aInfo.iTargetIndex += aInfo.iCount; |
|
218 aInfo.iSourceIndex += aInfo.iCount; |
|
219 } |
|
220 |
|
221 private: |
|
222 /** |
|
223 * Copy a block of items from source to target |
|
224 * @param aInfo See @ref MListModificationStrategy::Insert |
|
225 */ |
|
226 inline void InsertBlock( const TDifferenceInfo& aInfo ) |
|
227 { |
|
228 TInt sourceIndex = aInfo.iSourceIndex; |
|
229 |
|
230 // Copy items from source list to target list |
|
231 TInt untilTargetIndex = aInfo.iTargetIndex + aInfo.iCount; |
|
232 for ( TInt targetIndex = aInfo.iTargetIndex; |
|
233 targetIndex < untilTargetIndex; targetIndex++ ) |
|
234 { |
|
235 // IdOfIndex will not return "invalid id", since the code is only |
|
236 // asking for ids within the range of the list |
|
237 TGlxMedia item( TGlxMediaId( iSource.IdOfIndex( sourceIndex ) ) ); |
|
238 // set media, assumes that media is either NULL, or has an |
|
239 // allocation made for a new user. Builds a link to CGlxMedia |
|
240 // object, and from CGlxMedia object to iMediaUser |
|
241 item.SetMedia( iMediaPool.Media( iIdSpaceId, item.Id() ), iMediaUser ); |
|
242 // add to item list |
|
243 // ignore error, cannot fail since reservation made |
|
244 iTarget.Insert( item, targetIndex ); |
|
245 |
|
246 sourceIndex++; |
|
247 } |
|
248 } |
|
249 |
|
250 private: |
|
251 /// Source list to copy ids from. |
|
252 const CMPXCollectionPath& iSource; |
|
253 /// Target list to which to copy / from which to remove |
|
254 RArray< TGlxMedia >& iTarget; |
|
255 /// Provider of media objects |
|
256 const MGlxMediaPool& iMediaPool; |
|
257 /// Id space id, for looking up items from media provider |
|
258 TGlxIdSpaceId iIdSpaceId; |
|
259 /// User of the new media objects, if any TGlxMedia-CGlxMedia links are added |
|
260 MGlxMediaUser& iMediaUser; |
|
261 /// Observer for changes in list |
|
262 MGlxItemListObserver& iObserver; |
|
263 }; |
|
264 |
|
265 } // namespace NGlxItemList |
|
266 |
|
267 // ----------------------------------------------------------------------------- |
|
268 // CGlxItemList implementation |
|
269 // ----------------------------------------------------------------------------- |
|
270 |
|
271 // ----------------------------------------------------------------------------- |
|
272 // Two-phase constructor |
|
273 // ----------------------------------------------------------------------------- |
|
274 // |
|
275 CGlxItemList* CGlxItemList::NewL( const TGlxIdSpaceId& aIdSpaceId, |
|
276 MGlxItemListObserver& aObserver, MGlxMediaUser& aMediaUser ) |
|
277 { |
|
278 TRACER("CGlxItemList::NewL"); |
|
279 // No ConstructL function currently, so simply return an instance |
|
280 return new (ELeave) CGlxItemList( aIdSpaceId, aObserver, aMediaUser ); |
|
281 } |
|
282 |
|
283 // ----------------------------------------------------------------------------- |
|
284 // Constructor |
|
285 // ----------------------------------------------------------------------------- |
|
286 // |
|
287 CGlxItemList::CGlxItemList( const TGlxIdSpaceId& aIdSpaceId, |
|
288 MGlxItemListObserver& aObserver, MGlxMediaUser& aMediaUser ) |
|
289 : iIdSpaceId( aIdSpaceId ), iMediaUser( aMediaUser ), |
|
290 iObserver( aObserver ) |
|
291 { |
|
292 TRACER("CGlxItemList::Default Constructor"); |
|
293 |
|
294 __TEST_INVARIANT; |
|
295 } |
|
296 |
|
297 // ----------------------------------------------------------------------------- |
|
298 // Destructor |
|
299 // ------------------------------------------------------------------------- ---- |
|
300 // |
|
301 CGlxItemList::~CGlxItemList() |
|
302 { |
|
303 TRACER( "CGlxItemList::~CGlxItemList" ); |
|
304 |
|
305 // Remove user of media objects |
|
306 TInt count = iItems.Count(); |
|
307 for ( TInt i = 0; i < count; i++ ) |
|
308 { |
|
309 iItems[ i ].SetMedia( NULL, iMediaUser ); |
|
310 } |
|
311 |
|
312 iItems.Close(); |
|
313 } |
|
314 |
|
315 // ----------------------------------------------------------------------------- |
|
316 // Synchronise contents of the list with the collection path |
|
317 // ----------------------------------------------------------------------------- |
|
318 // |
|
319 void CGlxItemList::SetContentsL( const CMPXCollectionPath& aSource, |
|
320 const MGlxMediaPool& aMediaPool ) |
|
321 { |
|
322 TRACER( "CGlxItemList::SetContentsL" ); |
|
323 __TEST_INVARIANT; |
|
324 |
|
325 // calculate how much space needs to be reserved in the items array |
|
326 // before doing any modifications. This allows the update to |
|
327 // be completed successfully or not at all if leave occurs |
|
328 TCalculateRequiredSpaceStrategy calculateDeltaLengthStrategy ( iItems ); |
|
329 ProcessDifferences ( aSource, calculateDeltaLengthStrategy ); |
|
330 |
|
331 // make reservation so that paths can be syncronised without fail |
|
332 iItems.ReserveL( iItems.Count() + |
|
333 calculateDeltaLengthStrategy.iRequiredSpace ); |
|
334 |
|
335 // now implement the changes (notifies observer) |
|
336 TChangeListStategy changeListStrategy( aSource, iIdSpaceId, iItems, |
|
337 aMediaPool, iMediaUser, iObserver ); |
|
338 ProcessDifferences ( aSource, changeListStrategy ); |
|
339 |
|
340 // Remove slack space from list |
|
341 iItems.Compress(); |
|
342 |
|
343 __TEST_INVARIANT; |
|
344 } |
|
345 |
|
346 // ----------------------------------------------------------------------------- |
|
347 // Find differences in new and old path, and ask strategy to act on them |
|
348 // ----------------------------------------------------------------------------- |
|
349 // |
|
350 void CGlxItemList::ProcessDifferences( const CMPXCollectionPath& aSource, |
|
351 MListModificationStrategy& aModificationStrategy ) |
|
352 { |
|
353 TRACER("CGlxItemList::ProcessDifferences"); |
|
354 |
|
355 TDifferenceInfo info; |
|
356 |
|
357 // Find the indexes of the first items that are the same on both |
|
358 // source and target list, until there are no more items in either list. |
|
359 // (If either one of the lists reaches its end, return the next index |
|
360 // from the last one, i.e., count.) |
|
361 // Ask the strategy object to handle cases in which items |
|
362 // have been inserted to the list or removed from the list. |
|
363 // |
|
364 // Note: not using a temporary variable for "target count", since count can change |
|
365 // during the loop, as items may get added/removed. |
|
366 TInt sourceCount = aSource.Count(); |
|
367 while ( info.iSourceIndex < sourceCount || info.iTargetIndex < Count() ) |
|
368 { |
|
369 // find next matching source and target indexes |
|
370 TInt sourceMatchIndex = KErrNotFound; |
|
371 TInt targetMatchIndex = KErrNotFound; |
|
372 FindMatchingItems( info, aSource, sourceMatchIndex, targetMatchIndex ); |
|
373 |
|
374 // Process differences |
|
375 ProcessRemove( targetMatchIndex, info, aModificationStrategy ); |
|
376 ProcessInsert( sourceMatchIndex, info, aModificationStrategy ); |
|
377 |
|
378 // go to next items |
|
379 info.iSourceIndex++; |
|
380 info.iTargetIndex++; |
|
381 } |
|
382 } |
|
383 |
|
384 // ----------------------------------------------------------------------------- |
|
385 // Find the next matching item in source and target path |
|
386 // inline in cpp file only, so will be inlined in arm compiler |
|
387 // ----------------------------------------------------------------------------- |
|
388 // |
|
389 inline void CGlxItemList::FindMatchingItems( const TDifferenceInfo& aInfo, |
|
390 const CMPXCollectionPath& aSource, TInt& aSourceMatchIndex, |
|
391 TInt& aTargetMatchIndex ) |
|
392 { |
|
393 TRACER("CGlxItemList::FindMatchingItems"); |
|
394 |
|
395 // For each remaining source item, test each remaining target item, until |
|
396 // match found |
|
397 TInt sourceCount = aSource.Count(); |
|
398 TInt targetCount = Count(); |
|
399 TInt sourceIndex = aInfo.iSourceIndex; |
|
400 |
|
401 // The most common case is that the next items match (no items changed |
|
402 // at the indexes being examined). The loop is optimised for that case. |
|
403 // The loop (including the inner loop) terminates on the first round, |
|
404 // since matching items are immediately found. |
|
405 // The case optimised next is the "items were removed from middle" case, |
|
406 // as this is likely to happen in an active view. In that case, the upper |
|
407 // loop is run only once, and the inner loop finds the match. |
|
408 // The slowest cases are when items were inserted or removed from the end. |
|
409 // In that case both loops may run almost to end, so that is slow. |
|
410 // However, that case should be a rare case (while view is active), |
|
411 // so from performance perspective this loop should be ok, even |
|
412 // though it has N*M complexity. |
|
413 while ( sourceIndex < sourceCount ) |
|
414 { |
|
415 // get next source item id |
|
416 // IdOfIndex will not return "invalid id", since the code is only |
|
417 // asking for ids within the range of the list, but even if it did, |
|
418 // the logic would still work |
|
419 TGlxMediaId sourceId ( aSource.IdOfIndex( sourceIndex ) ); |
|
420 |
|
421 // try to find a target item that matches the source item |
|
422 // (cannot use RArray::Find, since it does not allow specifying |
|
423 // start index) |
|
424 TInt targetIndex = aInfo.iTargetIndex; |
|
425 while ( targetIndex < targetCount) |
|
426 { |
|
427 if ( sourceId == iItems[ targetIndex ].Id() ) |
|
428 { |
|
429 // Found match, store return values |
|
430 aTargetMatchIndex = targetIndex; |
|
431 aSourceMatchIndex = sourceIndex; |
|
432 return; |
|
433 } |
|
434 targetIndex++; |
|
435 } |
|
436 sourceIndex++; |
|
437 } |
|
438 |
|
439 // No match found |
|
440 aTargetMatchIndex = Count(); |
|
441 aSourceMatchIndex = aSource.Count(); |
|
442 } |
|
443 |
|
444 // ----------------------------------------------------------------------------- |
|
445 // Process the need to remove items to eliminate differences |
|
446 // inline in cpp file only, so will be inlined in arm compiler |
|
447 // ----------------------------------------------------------------------------- |
|
448 // |
|
449 inline void CGlxItemList::ProcessRemove( TInt aTargetMatchIndex, |
|
450 TDifferenceInfo& aInfo, MListModificationStrategy& aStrategy ) |
|
451 { |
|
452 TRACER("CGlxItemList::ProcessRemove"); |
|
453 |
|
454 aInfo.iCount = aTargetMatchIndex - aInfo.iTargetIndex; |
|
455 if ( aInfo.iCount > 0 ) |
|
456 { |
|
457 aStrategy.Remove( aInfo ); |
|
458 } |
|
459 } |
|
460 |
|
461 // ----------------------------------------------------------------------------- |
|
462 // Process the need to insert items to eliminate differences |
|
463 // inline in cpp file only, so will be inlined in arm compiler |
|
464 // ----------------------------------------------------------------------------- |
|
465 // |
|
466 inline void CGlxItemList::ProcessInsert( TInt aSourceMatchIndex, |
|
467 TDifferenceInfo& aInfo, MListModificationStrategy& aStrategy ) |
|
468 { |
|
469 TRACER("CGlxItemList::ProcessInsert"); |
|
470 |
|
471 aInfo.iCount = aSourceMatchIndex - aInfo.iSourceIndex; |
|
472 if ( aInfo.iCount > 0 ) |
|
473 { |
|
474 aStrategy.Insert( aInfo ); |
|
475 } |
|
476 } |
|
477 |
|
478 // ----------------------------------------------------------------------------- |
|
479 // Remove an item from the list |
|
480 // ----------------------------------------------------------------------------- |
|
481 // |
|
482 void CGlxItemList::Remove( const TGlxIdSpaceId& aIdSpaceId, const TGlxMediaId& aItemId ) |
|
483 { |
|
484 TRACER("CGlxItemList::Remove"); |
|
485 |
|
486 // Assume id space id does not have to be checked for performance reasons, i.e., id |
|
487 // space id is usually, if not always, correct |
|
488 TInt index = Index(aIdSpaceId, aItemId ); |
|
489 if ( KErrNotFound != index && iIdSpaceId == aIdSpaceId ) |
|
490 { |
|
491 // Remove item from the list |
|
492 RemoveItem( index, iItems, iMediaUser ); |
|
493 // Notify observer |
|
494 iObserver.HandleItemsRemoved( index, 1 ); |
|
495 } |
|
496 __TEST_INVARIANT; |
|
497 } |
|
498 |
|
499 // ----------------------------------------------------------------------------- |
|
500 // Remove any pointers to the media object at the specified index |
|
501 // ----------------------------------------------------------------------------- |
|
502 // |
|
503 void CGlxItemList::RemoveReference( TInt aIndex ) |
|
504 { |
|
505 TRACER( "CGlxItemList::RemoveReference" ); |
|
506 __TEST_INVARIANT; |
|
507 |
|
508 GLX_ASSERT_DEBUG( 0 <= aIndex && aIndex < iItems.Count(), |
|
509 Panic(EGlxPanicIllegalArgument), "removing reference for item out of bounds"); |
|
510 |
|
511 // remove the reference to the media |
|
512 (*this)[ aIndex ].SetMedia( NULL, iMediaUser ); |
|
513 |
|
514 __TEST_INVARIANT; |
|
515 } |
|
516 |
|
517 // ----------------------------------------------------------------------------- |
|
518 // Return index by id |
|
519 // ----------------------------------------------------------------------------- |
|
520 // |
|
521 TInt CGlxItemList::Index( const TGlxIdSpaceId& aIdSpaceId, const TGlxMediaId& aId ) const |
|
522 { |
|
523 TRACER("CGlxItemList::Index"); |
|
524 |
|
525 if ( iIdSpaceId == aIdSpaceId ) |
|
526 { |
|
527 // set up comparison functor |
|
528 TIdentityRelation<TGlxMedia> match ( &TGlxMedia::MatchById ); |
|
529 // create dummy object to compare against |
|
530 TGlxMedia mediaToCompare( aId ); |
|
531 // try to find; may return KErrNotFound |
|
532 return iItems.Find( mediaToCompare, match ); |
|
533 } |
|
534 |
|
535 return KErrNotFound; |
|
536 } |
|
537 |
|
538 // --------------------------------------------------------------------------- |
|
539 // Test invariant |
|
540 // --------------------------------------------------------------------------- |
|
541 void CGlxItemList::__DbgTestInvariant() const |
|
542 { |
|
543 #ifdef _DEBUG |
|
544 |
|
545 __ASSERT_DEBUG( &iObserver , Panic( EGlxPanicIllegalState ) ); // Null observer |
|
546 __ASSERT_DEBUG( iIdSpaceId != KGlxIdNone , Panic( EGlxPanicIllegalState ) ); // No id space |
|
547 |
|
548 // Make sure the list contains no duplication |
|
549 TInt count = iItems.Count(); |
|
550 for ( TInt indexA = 0; indexA < count; ++indexA ) |
|
551 { |
|
552 for ( TInt indexB = indexA + 1; indexB < count; ++indexB ) |
|
553 { |
|
554 __ASSERT_DEBUG( !TGlxMedia::MatchById( iItems[indexA], iItems[indexB] ), |
|
555 Panic( EGlxPanicIllegalState ) ); // Duplicate item |
|
556 } |
|
557 } |
|
558 |
|
559 #endif // _DEBUG |
|
560 } |