summaryrefslogtreecommitdiffstats
path: root/plugins/contacts/symbian/contactsmodel/cntview/filteredview.cpp
blob: 8b69ca65e6fac8e8b29c6b5483319408b18d2610 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
/*
* Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
* Contact: http://www.qt-project.org/legal
* 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: 
*
*/


#include <cntview.h>
#include "cntstd.h"

//#define CNTVIEW_API_PROFILING
// To see the diferences between class versions check the in source documentation of TContactViewEvent
const TUint KClassVersion2 = 2;

/*
 * Threshold to choose whether to use base class implementation of 
 * ContactsMatchingCriteria or use remote view implementation.
 * If the filter view has 59% or more of the parent view, it is 
 * usually quicker to use the parent (remote) view.
 */
const TReal KFilterThreshold = 0.592437 ;

EXPORT_C CContactFilteredView* CContactFilteredView::NewL(MContactViewObserver& aObserver,const CContactDatabase& aDb,CContactViewBase& aView,TInt aFilter)
/** Allocates and constructs a CContactFilteredView version 1 object.

When adding/deleting contacts in the view, MContactViewObserver observer will receive 
TContactViewEvent events with iInt parameter set to index into the observed view of the added/deleted item

@param aObserver An observer that receives notifications when this view is 
ready for use and when changes take place in it. The observer receives a TContactViewEvent::EReady 
event when the view is ready. An attempt to use the view before this notification 
causes a panic.
@param aDb The database containing the contact items.
@param aView The underlying contact view over which this view provides a filter.
@param aFilter The filter to use. For possible values, see CContactDatabase::TContactViewFilter.
@return The newly constructed filtered view object. */
	{
#ifdef CNTVIEW_API_PROFILING
	RDebug::Print(_L("[CNTMODEL] CContactFilteredView::NewL()\n"));
#endif
	CContactFilteredView* self=new(ELeave) CContactFilteredView(aDb,aFilter,aView);
	CleanupStack::PushL(self);
	self->ConstructL(aObserver);
	CleanupStack::Pop(self); 
	return self;
	}

EXPORT_C CContactFilteredView* CContactFilteredView::NewL(CContactViewBase& aView,const CContactDatabase& aDb,MContactViewObserver& aObserver,TInt aFilter)
/** Allocates and constructs a CContactFilteredView version 2 object. 

When adding/deleting contacts in the view, MContactViewObserver observer will receive 
TContactViewEvent events with iInt parameter set to index into the observed view of the added/deleted item

@param aObserver An observer that receives notifications when this view is 
ready for use and when changes take place in it. The observer receives a TContactViewEvent::EReady 
event when the view is ready. An attempt to use the view before this notification 
causes a panic.
@param aDb The database containing the contact items.
@param aView The underlying contact view over which this view provides a filter.
@param aFilter The filter to use. For possible values, see CContactDatabase::TContactViewFilter.
@return The newly constructed filtered view object. */
	{
#ifdef CNTVIEW_API_PROFILING
	RDebug::Print(_L("[CNTMODEL] CContactFilteredView::NewL()\n"));
#endif
	CContactFilteredView* self=new(ELeave) CContactFilteredView(aDb,aFilter,aView);
	CleanupStack::PushL(self);
	self->ConstructL(aObserver);
	self->iClassVersion = KClassVersion2;
	CleanupStack::Pop(self); 
	return self;
	}

/*
 * This is a reserved virtual exported function that is used for BC proofing 
 * against present and future additions of new exported virtual functions.
 @return Any return values of the helper methods called from this function or NULL.
 **/
 TAny* CContactFilteredView::CContactViewBase_Reserved_1(TFunction aFunction,TAny* aParams)
	{
	return CContactViewBase::CContactViewBase_Reserved_1(aFunction,aParams);
	}

TContactItemId CContactFilteredView::AtL(TInt aIndex) const
/** Gets the contact item ID at the specified index into the filtered view.

@param aIndex Index of the contact item ID into the filtered view. 
@leave KErrNotReady The view is not ready for use.
@leave KErrNotFound aIndex is outside the bounds of the filtered view.
@return The contact item ID. 
*/
	{
	if (iState != EReady)
		{
		User::Leave(KErrNotReady);
		}
	if (aIndex>=iFilteredIdArray.Count())
		{
		//Out of Bounds.
		User::Leave(KErrNotFound);
		}
	return iFilteredIdArray[aIndex].iId;
	}

const CViewContact& CContactFilteredView::ContactAtL(TInt aIndex) const
/** Gets the contact item at the specified index into the view.

@param aIndex Index into the view of the required item.
@leave KErrNotReady The view is not ready for use.
@leave KErrNotFound aIndex is outside the bounds of the filtered ID array.
@return The contact item. 
*/
	{
	if (iState != EReady)
		{
		User::Leave(KErrNotReady);
		}
	if (aIndex>=iFilteredIdArray.Count())
		{
		//Out of Bounds.
		User::Leave(KErrNotFound);
		}
	TContactIdWithMapping idWithMapping = (iFilteredIdArray)[aIndex];
	return iView.ContactAtL(idWithMapping.iMapping);
	}


TInt CContactFilteredView::CountL() const
/** Gets the number of contact item IDs in the filtered view.
@leave KErrNotReady The view is not ready for use. 
@return The number of contact items in the filtered view. 
*/
	{
	if (iState != EReady)
		{
		User::Leave(KErrNotReady);
		}
	return iFilteredIdArray.Count();
	}

TInt CContactFilteredView::FindL(TContactItemId aId) const
/** Finds the index into the filtered view of the specified contact item.

@param aId The contact item ID to search for. 
@leave KErrNotReady The view is not ready for use.
@return The index of the first matching item in the array or KErrNotFound if 
no matching object can be found. 
*/
	{
	if (iState != EReady)
		{
		User::Leave(KErrNotReady);
		}
	TContactIdWithMapping idWithDummyMapping;
	idWithDummyMapping.iId=aId;
	return iFilteredIdArray.Find(idWithDummyMapping,TIdentityRelation<TContactIdWithMapping>(IdsEqual));
	}

HBufC* CContactFilteredView::AllFieldsLC(TInt aIndex,const TDesC& aSeparator) const
/** Returns a descriptor containing the contents of all fields for an item in the 
view.

The fields are separated by aSeparator.

@param aIndex The index into the filtered view of the contact item.
@param aSeparator The string to use to separate the fields.
@return Pointer to the specified contact item descriptor. */
	{
	return iView.AllFieldsLC((iFilteredIdArray)[aIndex].iMapping,aSeparator);
	}

#ifdef _DEBUG
void CContactFilteredView::HandleContactViewEvent(const CContactViewBase& aView,const TContactViewEvent& aEvent)
#else
void CContactFilteredView::HandleContactViewEvent(const CContactViewBase& /*aView*/,const TContactViewEvent& aEvent)
#endif
	{
	ASSERT(&aView==&iView);
	TBool doUpdate(EFalse);
	TContactViewEvent event=aEvent;
	switch (event.iEventType)
		{
		case TContactViewEvent::ESortError:
		case TContactViewEvent::EServerError:
		case TContactViewEvent::EUnavailable:
			iState=ENotReady;
			break;
		case TContactViewEvent::EReady:
		case TContactViewEvent::ESortOrderChanged:
			{
			//Underlying view is ready, create filtered view.
			CreateFilteredIdArray();
			return; // Notification is handled in CreateFilteredIdArray
			}
		case TContactViewEvent::EItemAdded:
			{
			TBool viewModified(EFalse);
			TRAPD(err,UpdateFilteredIdArrayL(event,viewModified));
			if(err!=KErrNone && err!=KErrNotFound)
				{
				NotifyObservers(TContactViewEvent(TContactViewEvent::ESortError,err));
				return;
				}
			if (!viewModified)
				return; //If view wasn't modified, don't notify the observers
			}
			break;
		case TContactViewEvent::EItemRemoved:
			{
			TBool viewModified(EFalse);
			doUpdate = ETrue;
			TRAPD(err,UpdateFilteredIdArrayL(event,viewModified));
			if(err!=KErrNone && err!=KErrNotFound)
				{
				NotifyObservers(TContactViewEvent(TContactViewEvent::ESortError,err));
				return;
				}
			if (!viewModified)
				{
				UpdateMappingsL();
				return; //If view wasn't modified, don't notify the observers
				}
			}
			break;
		case TContactViewEvent::EGroupChanged:
			break;
		default:
			ASSERT(EFalse);
		}

	NotifyObservers(event);
	if(doUpdate) 
		{ 
		UpdateMappingsL(); //Update mappings for multiple changes and notify observers. 
		} 

	}

CContactFilteredView::~CContactFilteredView()
	{
	iFilteredIdArray.Close();
	iView.Close(*this);
	}

CContactFilteredView::CContactFilteredView(const CContactDatabase& aDb,TInt aFilter,CContactViewBase& aView)
	: CContactViewBase(aDb),iFilter(aFilter),iView(aView)
	{
	}
	
void CContactFilteredView::ConstructL(MContactViewObserver& aObserver)
	{
	CContactViewBase::ConstructL();
	OpenL(aObserver);
	iView.OpenL(*this);
	}

/* 
 * Construct the filtered view. 
 * This method is called when the underlying view sends an EReady or 
 * ESortOrderChanged event. It is implemented by asking the underlying
 * view which contacts match the filter (via CContactViewBase_Reserved_1)
 */
void CContactFilteredView::CreateFilteredIdArray()
	{
	iFilteredIdArray.Reset();

	CContactViewBase::TVirtualFunction2Params structOfParams(iFilter, iFilteredIdArray);
	TRAPD(err, iView.CContactViewBase_Reserved_1(CContactViewBase::ECContactViewBaseVirtualFunction2, &structOfParams)); //leaving method

	if(err==KErrNone) // View built OK, set to EReady or ESortOrderChanged
		{
		TState oldState = iState;
 		iState=EReady;
 
 		if (oldState == EInitializing)
			{
			NotifyObservers(TContactViewEvent(TContactViewEvent::EReady));
			}
 		else
 			{
 			NotifyObservers(TContactViewEvent(TContactViewEvent::ESortOrderChanged));
 			}
		}
	else // View building had problems
		{
		NotifyObservers(TContactViewEvent(TContactViewEvent::ESortError,err));
		}
	}

void CContactFilteredView::UpdateFilteredIdArrayL(TContactViewEvent& aEvent, TBool& aViewModified)
	{
	// Note, this method should update the index in aEvent - it will be passed on to observers of this view.
	switch(aEvent.iEventType)
		{
		case TContactViewEvent::EItemAdded:
			HandleAddEventL(aEvent, aViewModified);
			break;
		case TContactViewEvent::EItemRemoved:
			HandleRemoveEventL(aEvent, aViewModified);
			break;
		default:
			ASSERT(EFalse);
		};
	}

void CContactFilteredView::HandleAddEventL(TContactViewEvent& aEvent, TBool& aViewModified)
	{
	// The id contained in aEvent can refer to a inexistent contact by now 
	// so check that the contact with that id still exist first
	TInt underlyingViewIndex = iView.FindL(aEvent.iContactId);
	User::LeaveIfError(underlyingViewIndex);

	// get contact data from view rather than re-read from disk (speeds up access time)
	const CViewContact& contact = iView.ContactAtL(underlyingViewIndex);
	TContactIdWithMapping findMap;
	findMap.iId=contact.Id();
	findMap.iMapping=underlyingViewIndex;
	UpdateMappingsL();
	if(contact.ContactMatchesFilter(iFilter))
		{
		aViewModified=ETrue;
		//Contact Matches Filter, need to find the correct position to insert.
		aEvent.iInt=BinaryInsertL(findMap);
		}
	}

void CContactFilteredView::HandleRemoveEventL(TContactViewEvent& aEvent, TBool& aViewModified)
	{
	//Remove the contact from the filter array and alter the Event to local mapping
	TContactIdWithMapping findMap;
	findMap.iId = aEvent.iContactId;
	findMap.iMapping=KErrNotFound;//not important
	const TInt position = iFilteredIdArray.Find(findMap,TIdentityRelation<TContactIdWithMapping>(IdsEqual));
	if (position!=KErrNotFound)
		{
		aViewModified=ETrue;
		iFilteredIdArray.Remove(position);
		}
	aEvent.iInt=position;
	}

/* Update index mappings used in the filtered view. */
void CContactFilteredView::UpdateMappingsL()
	{
	 // View mappings are now invalid Fix all mapped indexes
	TInt updatedViewIndex=KErrNotFound;
	for (TInt i=0; i < iFilteredIdArray.Count(); ++i)
		{
		TContactItemId idUpdate = iFilteredIdArray[i].iId;
		updatedViewIndex=iView.FindL(idUpdate);
		if(updatedViewIndex==KErrNotFound)
			{
			//local view has removed this contact, so we need to as well.
			iFilteredIdArray.Remove(i);
			NotifyObservers(TContactViewEvent(TContactViewEvent::EItemRemoved, i, idUpdate));
			i--;
			continue;
			}
		iFilteredIdArray[i].iMapping=updatedViewIndex;
		}
	}


TInt CContactFilteredView::BinaryInsertL(TContactIdWithMapping aId)
	{
	TInt min=0;
	TInt max=iFilteredIdArray.Count();
	TInt match = aId.iMapping;
	FOREVER
		{
		if (min==max)
			break; // min is the new position
		TInt index=(max-min)/2+min;
		TInt compare = (iFilteredIdArray)[index].iMapping;
		if (compare<=match)
			{
			if (min==index)
				min++;
			else
				min=index;
			}
		else if (compare>match)
			max=index;
		}
	TInt err = iFilteredIdArray.Insert(aId,min);
	User::LeaveIfError(err);
	return min;
	}

TContactViewPreferences CContactFilteredView::ContactViewPreferences()
/** Gets the underlying view's view preferences.

@return The view preferences. */
	{
	return iView.ContactViewPreferences();
	}

const RContactViewSortOrder& CContactFilteredView::SortOrderL() const
/** Gets the underlying view's sort order.

@return The sort order. */
	{
	return iView.SortOrderL();
	}


void CContactFilteredView::ContactsMatchingCriteriaL(const MDesCArray& aFindWords,RPointerArray<CViewContact>& aMatchedContacts)
/** Searches all contact items in the filtered view for fields that contain all 
of the search strings specified.

The search uses wildcard matching so that the search strings can occur anywhere 
in an item's fields. For a match to occur, all of the search strings must 
be found in the contact item.

@param aFindWords A descriptor array containing one or more search strings.
@param aMatchedContacts On return, an array of matching contact items. */
	{
	const TReal parentViewCount = static_cast<TReal>(iView.CountL());
	const TReal filterViewCount = static_cast<TReal>(iFilteredIdArray.Count());
	
	if (parentViewCount!=0 && (filterViewCount/parentViewCount) < KFilterThreshold)
		{
		CContactViewBase::ContactsMatchingCriteriaL(aFindWords, aMatchedContacts);
		}
	else
		{
		iView.ContactsMatchingCriteriaL(aFindWords, aMatchedContacts);
		FilterResultsArray(aMatchedContacts);
		} 
	}

void CContactFilteredView::ContactsMatchingPrefixL(const MDesCArray& aFindWords, RPointerArray<CViewContact>& aMatchedContacts)
/** Searches all contact items in the filtered view for fields that contain all 
of the search strings specified.

Unlike ContactsMatchingCriteriaL(), the search term can only occur at the 
beginning of a field.

@param aFindWords A descriptor array containing one or more search strings.
@param aMatchedContacts On return, an array of matching contact items. */
	{
    if (iFilter == CContactDatabase::ECustomFilter2)
        {
        // This filtered view is for top contacts. There are not so many top contacts usually,
        // so it's faster to fetch these contacts from the server side and do matching here,
        // rather than matching among all contacts in the remote view and checking results
        // whether they belong to the filtered view or not. 
        CContactViewBase::ContactsMatchingPrefixL(aFindWords,aMatchedContacts);
	    }
    else
        {
	    iView.ContactsMatchingPrefixL(aFindWords, aMatchedContacts);
	    FilterResultsArray(aMatchedContacts);
        }
	}

/* 
 * Filter the results from ContactsMatchingCriteria/ContactsMatchingPrefix
 * removing contacts which are not in the current filtered view
 */
void CContactFilteredView::FilterResultsArray(RPointerArray<CViewContact>& aMatchedContacts)
	{
	TInt counter = 0;
	CViewContact* contactPtr;
	TContactIdWithMapping contactIdWithMapping;
	TInt max = aMatchedContacts.Count();
	// for each returned CContactFilteredView, check to
	// make sure that it is a contact which exists in
	// our list.

	// our list						: iFilteredIdArray
	// list to export is 			: aMatchedContacts
	while (counter < max)
		{
		contactPtr = aMatchedContacts[counter];
		contactIdWithMapping.iId = contactPtr->Id();

		// if the contact does not exist in our filtered list, then
		if ( iFilteredIdArray.Find(contactIdWithMapping, CompareMappings) == KErrNotFound )
			{
			// remove it from our list
			aMatchedContacts.Remove(counter); // does not delete pointer
			delete contactPtr;
			// we've removed an item from the array
			// this means that the item above, will drop to fill the hole left by
			// the item we've deleted.
			// so there is no need to increment the counter, but we do need to
			// decrement the max number of contacts we're parsing.
			max--;
			}
		else
			{
			counter++;
			}
		} 
	}

//static
TBool CContactFilteredView::CompareMappings(const TContactIdWithMapping& aFirst,const TContactIdWithMapping& aSecond)
	{
	return (aFirst.iId == aSecond.iId);
	}