photosgallery/viewframework/medialists/src/glxitemlist.cpp
changeset 0 4e91876724a2
child 20 d1bdfdf534bd
--- /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 <glxassert.h>
+#include <glxtracer.h>
+#include <mpxcollectionpath.h>
+
+// 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<TGlxMedia> 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
+    }