diff -r 000000000000 -r 4e91876724a2 photosgallery/viewframework/medialists/src/glxitemlist.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/photosgallery/viewframework/medialists/src/glxitemlist.cpp Thu Dec 17 08:45:44 2009 +0200 @@ -0,0 +1,560 @@ +/* +* Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies). +* All rights reserved. +* This component and the accompanying materials are made available +* under the terms of "Eclipse Public License v1.0" +* which accompanies this distribution, and is available +* at the URL "http://www.eclipse.org/legal/epl-v10.html". +* +* Initial Contributors: +* Nokia Corporation - initial contribution. +* +* Contributors: +* +* Description: List of media items +* +*/ + + + + +// my include +#include "glxitemlist.h" + +// system includes +#include +#include +#include + +// user includes +#include "mglxitemlistobserver.h" +#include "mglxmediapool.h" + +using namespace NGlxItemList; + +namespace NGlxItemList + { + // ----------------------------------------------------------------------------- + // Remove item from list and remove linking + // ----------------------------------------------------------------------------- + // + void RemoveItem( TInt aIndex, RArray< TGlxMedia >& aList, + MGlxMediaUser& aMediaUser ) + { + // Remove link between TGlxMedia and CGlxMedia + aList[ aIndex ].SetMedia( NULL, aMediaUser ); + // Remove from list + aList.Remove( aIndex ); + } + + /** + * Class to contain change processing current state and difference length + * + * @author Aki Vanhatalo + */ + NONSHARABLE_CLASS ( TDifferenceInfo ) + { + public: + /** Constructor */ + TDifferenceInfo() + { + iSourceIndex = 0; + iTargetIndex = 0; + iCount = 0; + } + + /// current index on source list. Modification strategy can change this + /// to skip items in the source list. + TInt iSourceIndex; + + /// current index on target list. Modification strategy can change this + /// to skip items in the target list. + TInt iTargetIndex; + + /// lenght of change: number of items that would need to be added or removed + /// to eliminate difference between source and target list. It is up to + /// the modification strategy to decide if it actually eliminates the + /// difference or simply observes it. Any change of this variable by + /// a strategy is ignored. + TInt iCount; + }; + + /** + * Modification strategy interface + * + * This interface will be called when differences are found in source and + * target list, and instructs how the differences should be solved, + * + * Use of modification strategies allows the same difference evaluation + * algorithm to be used for both calculating required target array space, and + * for doing the actual changes to into the target array. + * + * @author Aki Vanhatalo + */ + class MListModificationStrategy + { + public: + /** + * There is a difference in lists that requires removing items for + * the difference to be eliminated + * @param aInfo iTargetIndex index from which to remove items + * iCount number of items that would need to be removed + * iSourceIndex index in source list (to allow adjustement) + */ + virtual void Remove( TDifferenceInfo& aInfo ) = 0; + + /** + * There is a difference in lists that requires adding items from source + * list to target lits for the difference to be eliminated + * @param aInfo iTargetIndex index in target list at which to insert + * iCount number of items that would need to be copied + * iSourceIndex index in source list from which to copy + */ + virtual void Insert( TDifferenceInfo& aInfo ) = 0; + }; + + /** + * Modification strategy that calculates how much slack space is required for + * differences in lists to be fixed (i.e., TChangeListStategy to be executed) + * + * @author Aki Vanhatalo + */ + NONSHARABLE_CLASS( TCalculateRequiredSpaceStrategy ) : + public MListModificationStrategy + { + public: + /** Constructor */ + TCalculateRequiredSpaceStrategy( const RArray< TGlxMedia >& aTargetList ) + { + // Pick up initial required space + iRequiredSpace = aTargetList.Count(); + // Set initial value + iCurrentLengthDifference = 0; + } + + // from MListModificationStrategy + void Remove( TDifferenceInfo& aInfo ) + { + // Currently required space is reduced + iCurrentLengthDifference -= aInfo.iCount; + + // Skip over "removed" items (items were not removed, simply counted) + aInfo.iTargetIndex += aInfo.iCount; + } + + // from MListModificationStrategy + void Insert( TDifferenceInfo& aInfo ) + { + // Currently required space is increased + iCurrentLengthDifference += aInfo.iCount; + // Check if the currently required space is the maximum space + iRequiredSpace = Max( iCurrentLengthDifference, iRequiredSpace ); + + // Skip over source items, but not target items, since nothing was + // actually inserted + aInfo.iSourceIndex += aInfo.iCount; + } + + public: // allow public since internal to CGlxItemList implementation + /// space required in item list for TChangeListStategy to be + /// executable without fail + TInt iRequiredSpace; + /// current difference in space needed; iRequiredSpace is a max of this + TInt iCurrentLengthDifference; + }; + + /** + * Modification strategy that fixes the differences between source and target + * list. + * + * Note: Implementation assumes that there is enough slack space in target + * list for insertion operations to always succeed. (I.e., + * TCalculateRequiredSpaceStrategy needs to be used first to find out how much + * slack is required) + * + * @author Aki Vanhatalo + */ + NONSHARABLE_CLASS( TChangeListStategy ) : public MListModificationStrategy + { + public: + /** Constructor */ + TChangeListStategy( const CMPXCollectionPath& aSourceList, + const TGlxIdSpaceId& aIdSpaceId, RArray< TGlxMedia >& aTargetList, + const MGlxMediaPool& aMediaPool, MGlxMediaUser& aMediaUser, + MGlxItemListObserver& aObserver ) + : iSource( aSourceList ), iTarget( aTargetList ), + iMediaPool( aMediaPool ), iIdSpaceId( aIdSpaceId ), + iMediaUser( aMediaUser ), iObserver( aObserver ) + { + // do nothing (more) + } + + // from MListModificationStrategy + void Remove( TDifferenceInfo& aInfo ) + { + // Remove block from target list, start from end and work backwards (quicker) + for ( TInt i = aInfo.iTargetIndex + aInfo.iCount - 1; + i >= aInfo.iTargetIndex; + i-- ) + { + RemoveItem( i, iTarget, iMediaUser ); + } + + // Notify observer + iObserver.HandleItemsRemoved( aInfo.iTargetIndex, aInfo.iCount ); + } + + // from MListModificationStrategy + void Insert( TDifferenceInfo& aInfo ) + { + // Insert all items to target list + InsertBlock( aInfo ); + + // Notify observer + iObserver.HandleItemsAdded( aInfo.iTargetIndex, aInfo.iCount ); + + // Skip over inserted and source items + aInfo.iTargetIndex += aInfo.iCount; + aInfo.iSourceIndex += aInfo.iCount; + } + + private: + /** + * Copy a block of items from source to target + * @param aInfo See @ref MListModificationStrategy::Insert + */ + inline void InsertBlock( const TDifferenceInfo& aInfo ) + { + TInt sourceIndex = aInfo.iSourceIndex; + + // Copy items from source list to target list + TInt untilTargetIndex = aInfo.iTargetIndex + aInfo.iCount; + for ( TInt targetIndex = aInfo.iTargetIndex; + targetIndex < untilTargetIndex; targetIndex++ ) + { + // IdOfIndex will not return "invalid id", since the code is only + // asking for ids within the range of the list + TGlxMedia item( TGlxMediaId( iSource.IdOfIndex( sourceIndex ) ) ); + // set media, assumes that media is either NULL, or has an + // allocation made for a new user. Builds a link to CGlxMedia + // object, and from CGlxMedia object to iMediaUser + item.SetMedia( iMediaPool.Media( iIdSpaceId, item.Id() ), iMediaUser ); + // add to item list + // ignore error, cannot fail since reservation made + iTarget.Insert( item, targetIndex ); + + sourceIndex++; + } + } + + private: + /// Source list to copy ids from. + const CMPXCollectionPath& iSource; + /// Target list to which to copy / from which to remove + RArray< TGlxMedia >& iTarget; + /// Provider of media objects + const MGlxMediaPool& iMediaPool; + /// Id space id, for looking up items from media provider + TGlxIdSpaceId iIdSpaceId; + /// User of the new media objects, if any TGlxMedia-CGlxMedia links are added + MGlxMediaUser& iMediaUser; + /// Observer for changes in list + MGlxItemListObserver& iObserver; + }; + + } // namespace NGlxItemList + +// ----------------------------------------------------------------------------- +// CGlxItemList implementation +// ----------------------------------------------------------------------------- + +// ----------------------------------------------------------------------------- +// Two-phase constructor +// ----------------------------------------------------------------------------- +// +CGlxItemList* CGlxItemList::NewL( const TGlxIdSpaceId& aIdSpaceId, + MGlxItemListObserver& aObserver, MGlxMediaUser& aMediaUser ) + { + TRACER("CGlxItemList::NewL"); + // No ConstructL function currently, so simply return an instance + return new (ELeave) CGlxItemList( aIdSpaceId, aObserver, aMediaUser ); + } + +// ----------------------------------------------------------------------------- +// Constructor +// ----------------------------------------------------------------------------- +// +CGlxItemList::CGlxItemList( const TGlxIdSpaceId& aIdSpaceId, + MGlxItemListObserver& aObserver, MGlxMediaUser& aMediaUser ) + : iIdSpaceId( aIdSpaceId ), iMediaUser( aMediaUser ), + iObserver( aObserver ) + { + TRACER("CGlxItemList::Default Constructor"); + + __TEST_INVARIANT; + } + +// ----------------------------------------------------------------------------- +// Destructor +// ------------------------------------------------------------------------- ---- +// +CGlxItemList::~CGlxItemList() + { + TRACER( "CGlxItemList::~CGlxItemList" ); + + // Remove user of media objects + TInt count = iItems.Count(); + for ( TInt i = 0; i < count; i++ ) + { + iItems[ i ].SetMedia( NULL, iMediaUser ); + } + + iItems.Close(); + } + +// ----------------------------------------------------------------------------- +// Synchronise contents of the list with the collection path +// ----------------------------------------------------------------------------- +// +void CGlxItemList::SetContentsL( const CMPXCollectionPath& aSource, + const MGlxMediaPool& aMediaPool ) + { + TRACER( "CGlxItemList::SetContentsL" ); + __TEST_INVARIANT; + + // calculate how much space needs to be reserved in the items array + // before doing any modifications. This allows the update to + // be completed successfully or not at all if leave occurs + TCalculateRequiredSpaceStrategy calculateDeltaLengthStrategy ( iItems ); + ProcessDifferences ( aSource, calculateDeltaLengthStrategy ); + + // make reservation so that paths can be syncronised without fail + iItems.ReserveL( iItems.Count() + + calculateDeltaLengthStrategy.iRequiredSpace ); + + // now implement the changes (notifies observer) + TChangeListStategy changeListStrategy( aSource, iIdSpaceId, iItems, + aMediaPool, iMediaUser, iObserver ); + ProcessDifferences ( aSource, changeListStrategy ); + + // Remove slack space from list + iItems.Compress(); + + __TEST_INVARIANT; + } + +// ----------------------------------------------------------------------------- +// Find differences in new and old path, and ask strategy to act on them +// ----------------------------------------------------------------------------- +// +void CGlxItemList::ProcessDifferences( const CMPXCollectionPath& aSource, + MListModificationStrategy& aModificationStrategy ) + { + TRACER("CGlxItemList::ProcessDifferences"); + + TDifferenceInfo info; + + // Find the indexes of the first items that are the same on both + // source and target list, until there are no more items in either list. + // (If either one of the lists reaches its end, return the next index + // from the last one, i.e., count.) + // Ask the strategy object to handle cases in which items + // have been inserted to the list or removed from the list. + // + // Note: not using a temporary variable for "target count", since count can change + // during the loop, as items may get added/removed. + TInt sourceCount = aSource.Count(); + while ( info.iSourceIndex < sourceCount || info.iTargetIndex < Count() ) + { + // find next matching source and target indexes + TInt sourceMatchIndex = KErrNotFound; + TInt targetMatchIndex = KErrNotFound; + FindMatchingItems( info, aSource, sourceMatchIndex, targetMatchIndex ); + + // Process differences + ProcessRemove( targetMatchIndex, info, aModificationStrategy ); + ProcessInsert( sourceMatchIndex, info, aModificationStrategy ); + + // go to next items + info.iSourceIndex++; + info.iTargetIndex++; + } + } + +// ----------------------------------------------------------------------------- +// Find the next matching item in source and target path +// inline in cpp file only, so will be inlined in arm compiler +// ----------------------------------------------------------------------------- +// +inline void CGlxItemList::FindMatchingItems( const TDifferenceInfo& aInfo, + const CMPXCollectionPath& aSource, TInt& aSourceMatchIndex, + TInt& aTargetMatchIndex ) + { + TRACER("CGlxItemList::FindMatchingItems"); + + // For each remaining source item, test each remaining target item, until + // match found + TInt sourceCount = aSource.Count(); + TInt targetCount = Count(); + TInt sourceIndex = aInfo.iSourceIndex; + + // The most common case is that the next items match (no items changed + // at the indexes being examined). The loop is optimised for that case. + // The loop (including the inner loop) terminates on the first round, + // since matching items are immediately found. + // The case optimised next is the "items were removed from middle" case, + // as this is likely to happen in an active view. In that case, the upper + // loop is run only once, and the inner loop finds the match. + // The slowest cases are when items were inserted or removed from the end. + // In that case both loops may run almost to end, so that is slow. + // However, that case should be a rare case (while view is active), + // so from performance perspective this loop should be ok, even + // though it has N*M complexity. + while ( sourceIndex < sourceCount ) + { + // get next source item id + // IdOfIndex will not return "invalid id", since the code is only + // asking for ids within the range of the list, but even if it did, + // the logic would still work + TGlxMediaId sourceId ( aSource.IdOfIndex( sourceIndex ) ); + + // try to find a target item that matches the source item + // (cannot use RArray::Find, since it does not allow specifying + // start index) + TInt targetIndex = aInfo.iTargetIndex; + while ( targetIndex < targetCount) + { + if ( sourceId == iItems[ targetIndex ].Id() ) + { + // Found match, store return values + aTargetMatchIndex = targetIndex; + aSourceMatchIndex = sourceIndex; + return; + } + targetIndex++; + } + sourceIndex++; + } + + // No match found + aTargetMatchIndex = Count(); + aSourceMatchIndex = aSource.Count(); + } + +// ----------------------------------------------------------------------------- +// Process the need to remove items to eliminate differences +// inline in cpp file only, so will be inlined in arm compiler +// ----------------------------------------------------------------------------- +// +inline void CGlxItemList::ProcessRemove( TInt aTargetMatchIndex, + TDifferenceInfo& aInfo, MListModificationStrategy& aStrategy ) + { + TRACER("CGlxItemList::ProcessRemove"); + + aInfo.iCount = aTargetMatchIndex - aInfo.iTargetIndex; + if ( aInfo.iCount > 0 ) + { + aStrategy.Remove( aInfo ); + } + } + +// ----------------------------------------------------------------------------- +// Process the need to insert items to eliminate differences +// inline in cpp file only, so will be inlined in arm compiler +// ----------------------------------------------------------------------------- +// +inline void CGlxItemList::ProcessInsert( TInt aSourceMatchIndex, + TDifferenceInfo& aInfo, MListModificationStrategy& aStrategy ) + { + TRACER("CGlxItemList::ProcessInsert"); + + aInfo.iCount = aSourceMatchIndex - aInfo.iSourceIndex; + if ( aInfo.iCount > 0 ) + { + aStrategy.Insert( aInfo ); + } + } + +// ----------------------------------------------------------------------------- +// Remove an item from the list +// ----------------------------------------------------------------------------- +// +void CGlxItemList::Remove( const TGlxIdSpaceId& aIdSpaceId, const TGlxMediaId& aItemId ) + { + TRACER("CGlxItemList::Remove"); + + // Assume id space id does not have to be checked for performance reasons, i.e., id + // space id is usually, if not always, correct + TInt index = Index(aIdSpaceId, aItemId ); + if ( KErrNotFound != index && iIdSpaceId == aIdSpaceId ) + { + // Remove item from the list + RemoveItem( index, iItems, iMediaUser ); + // Notify observer + iObserver.HandleItemsRemoved( index, 1 ); + } + __TEST_INVARIANT; + } + +// ----------------------------------------------------------------------------- +// Remove any pointers to the media object at the specified index +// ----------------------------------------------------------------------------- +// +void CGlxItemList::RemoveReference( TInt aIndex ) + { + TRACER( "CGlxItemList::RemoveReference" ); + __TEST_INVARIANT; + + GLX_ASSERT_DEBUG( 0 <= aIndex && aIndex < iItems.Count(), + Panic(EGlxPanicIllegalArgument), "removing reference for item out of bounds"); + + // remove the reference to the media + (*this)[ aIndex ].SetMedia( NULL, iMediaUser ); + + __TEST_INVARIANT; + } + +// ----------------------------------------------------------------------------- +// Return index by id +// ----------------------------------------------------------------------------- +// +TInt CGlxItemList::Index( const TGlxIdSpaceId& aIdSpaceId, const TGlxMediaId& aId ) const + { + TRACER("CGlxItemList::Index"); + + if ( iIdSpaceId == aIdSpaceId ) + { + // set up comparison functor + TIdentityRelation match ( &TGlxMedia::MatchById ); + // create dummy object to compare against + TGlxMedia mediaToCompare( aId ); + // try to find; may return KErrNotFound + return iItems.Find( mediaToCompare, match ); + } + + return KErrNotFound; + } + +// --------------------------------------------------------------------------- +// Test invariant +// --------------------------------------------------------------------------- +void CGlxItemList::__DbgTestInvariant() const + { + #ifdef _DEBUG + + __ASSERT_DEBUG( &iObserver , Panic( EGlxPanicIllegalState ) ); // Null observer + __ASSERT_DEBUG( iIdSpaceId != KGlxIdNone , Panic( EGlxPanicIllegalState ) ); // No id space + + // Make sure the list contains no duplication + TInt count = iItems.Count(); + for ( TInt indexA = 0; indexA < count; ++indexA ) + { + for ( TInt indexB = indexA + 1; indexB < count; ++indexB ) + { + __ASSERT_DEBUG( !TGlxMedia::MatchById( iItems[indexA], iItems[indexB] ), + Panic( EGlxPanicIllegalState ) ); // Duplicate item + } + } + + #endif // _DEBUG + }