|
1 /* |
|
2 * Copyright (c) 2007 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: Predective text input engine core local methods. |
|
15 * |
|
16 */ |
|
17 |
|
18 |
|
19 |
|
20 #include <f32file.h> |
|
21 #include <s32file.h> |
|
22 |
|
23 #include "PtiSymbolList.h" |
|
24 |
|
25 |
|
26 //=========== constant definition ============== |
|
27 _LIT( KSymbolMutexName,"PtiHwrSymbol_101F8610" ); |
|
28 const TInt KUdmFileHeader = 0x20080808; |
|
29 const TInt KUdmFileVersion = 0x00001000; |
|
30 const TInt KInvalidPresetIndex = 0; |
|
31 |
|
32 |
|
33 void CSymbol::InternalizeL(RReadStream& /*aStream*/) |
|
34 { |
|
35 } |
|
36 |
|
37 void CSymbol::ExternalizeL(RWriteStream& aStream) |
|
38 { |
|
39 aStream.WriteInt32L( iHelpLine ); |
|
40 aStream.WriteInt32L( iBaseLine ); |
|
41 aStream.WriteInt32L( iRange.iScript ); |
|
42 aStream.WriteInt32L( iRange.iRange ); |
|
43 aStream.WriteUint32L( iPresetCode ); |
|
44 if ( iSymbolName ) |
|
45 { |
|
46 aStream.WriteInt32L( iSymbolName->Length() ); |
|
47 aStream.WriteL( *iSymbolName, iSymbolName->Length() ); |
|
48 } |
|
49 else |
|
50 { |
|
51 aStream.WriteInt32L( 0 ); |
|
52 } |
|
53 |
|
54 aStream.WriteInt32L( iPointVectorLen ); |
|
55 aStream.WriteL( reinterpret_cast< TUint8* >( iPointVector ), iPointVectorLen*sizeof( TPoint ) ); |
|
56 |
|
57 } |
|
58 |
|
59 CSymbol* CSymbol::NewL(RReadStream& aStream) |
|
60 { |
|
61 CSymbol* self = new ( ELeave ) CSymbol(); |
|
62 CleanupStack::PushL( self ); |
|
63 self->ConstructL( aStream ); |
|
64 CleanupStack::Pop( self ); |
|
65 return self; |
|
66 } |
|
67 |
|
68 void CSymbol::ConstructL(RReadStream& aStream) |
|
69 { |
|
70 iHelpLine = aStream.ReadInt32L(); |
|
71 iBaseLine = aStream.ReadInt32L(); |
|
72 iRange.iScript = aStream.ReadInt32L(); |
|
73 iRange.iRange = aStream.ReadInt32L(); |
|
74 iPresetCode = aStream.ReadUint32L(); |
|
75 |
|
76 TInt symbolLen = aStream.ReadInt32L(); |
|
77 iSymbolName = NULL; |
|
78 if ( symbolLen ) |
|
79 { |
|
80 iSymbolName = HBufC::NewL( symbolLen ); |
|
81 TPtr ptr = iSymbolName->Des(); |
|
82 aStream.ReadL( ptr, symbolLen ); |
|
83 } |
|
84 |
|
85 iPointVectorLen = aStream.ReadInt32L(); |
|
86 iPointVector = new ( ELeave ) TPoint[iPointVectorLen]; |
|
87 aStream.ReadL( reinterpret_cast< TUint8* >( iPointVector ), iPointVectorLen*sizeof( TPoint ) ); |
|
88 } |
|
89 |
|
90 CSymbol::CSymbol() |
|
91 { |
|
92 iPresetCode = 0; |
|
93 } |
|
94 |
|
95 CSymbol* CSymbol::NewL(const TDesC& aText, const RArray<TPoint>& aModel, int aHelpLine, int aBaseLine,const THwrUdmRange& aRange) |
|
96 { |
|
97 CSymbol* self = new ( ELeave )CSymbol(); |
|
98 CleanupStack::PushL( self ); |
|
99 self->ConstructL( aText, aModel, aHelpLine, aBaseLine, aRange ); |
|
100 CleanupStack::Pop( self ); |
|
101 return self; |
|
102 } |
|
103 |
|
104 CSymbol::~CSymbol() |
|
105 { |
|
106 delete iSymbolName; |
|
107 delete [] iPointVector; |
|
108 } |
|
109 |
|
110 void CSymbol::ConstructL(const TDesC& aText, const RArray<TPoint>& aModel, int aHelpLine, int aBaseLine,const THwrUdmRange& aRange) |
|
111 { |
|
112 iHelpLine = aHelpLine; |
|
113 iBaseLine = aBaseLine; |
|
114 iRange = aRange; |
|
115 iSymbolName = aText.AllocL( ); |
|
116 iPresetCode = KInvalidPresetIndex; |
|
117 |
|
118 iPointVectorLen = aModel.Count(); |
|
119 iPointVector = new ( ELeave ) TPoint[iPointVectorLen]; |
|
120 memcpy( iPointVector, &(aModel[0]), iPointVectorLen*sizeof( TPoint ) ); |
|
121 } |
|
122 |
|
123 TInt CSymbol::SymbolOrderDescending(const CSymbol& aFirst, const CSymbol& aSecond) |
|
124 { |
|
125 return aFirst.iSymbolName->Compare( *(aSecond.iSymbolName ) ); |
|
126 } |
|
127 |
|
128 TBool CSymbol::Match ( const THwrUdmRange& aRange ) |
|
129 { |
|
130 if ( aRange.iScript != EPtiHwrScriptAny ) |
|
131 { |
|
132 if ( aRange.iScript != iRange.iScript ) |
|
133 { |
|
134 return EFalse; |
|
135 } |
|
136 } |
|
137 |
|
138 if ( aRange.iRange != EPtiHwrRangeAny ) |
|
139 { |
|
140 if ( aRange.iRange != iRange.iRange ) |
|
141 { |
|
142 return EFalse; |
|
143 } |
|
144 } |
|
145 |
|
146 return ETrue; |
|
147 } |
|
148 |
|
149 CSymbolList* CSymbolList::NewL(const TDesC& aFilePath, CSymbolList* aPresetList ) |
|
150 { |
|
151 CSymbolList* self = new ( ELeave ) CSymbolList( aPresetList ); |
|
152 CleanupStack::PushL( self ); |
|
153 self->ConstructL( aFilePath ); |
|
154 CleanupStack::Pop( self ); |
|
155 return self; |
|
156 } |
|
157 |
|
158 void CSymbolList::SetSymbolModelL(const TDesC& aText, const RArray<TPoint>& aModel, TInt aHelpLine , TInt aBaseLine ,const THwrUdmRange& aRange ) |
|
159 { |
|
160 if( !aText.Length() || !aModel.Count() ) |
|
161 User::Leave( KErrGeneral ); |
|
162 if ( CheckSymbolModel( aText , aRange ) ) |
|
163 { |
|
164 User::Leave( KErrAlreadyExists ); |
|
165 } |
|
166 |
|
167 CSymbol* symbol = CSymbol::NewL( aText, aModel, aHelpLine, aBaseLine, aRange ); |
|
168 CleanupStack::PushL( symbol ); |
|
169 TLinearOrder<CSymbol> order( CSymbol::SymbolOrderDescending ); |
|
170 // using order insertion later |
|
171 // iSymbolList.InsertInOrderL( symbol, order ); |
|
172 iSymbolList.AppendL( symbol ); |
|
173 CleanupStack::Pop( symbol ); |
|
174 ExternalizeL(); |
|
175 } |
|
176 |
|
177 TBool CSymbolList::CheckSymbolModel(const TDesC& aChar ,const THwrUdmRange& aRange ) |
|
178 { |
|
179 TInt idx = -1; |
|
180 return GetSymbolIndex( aChar, idx, aRange ) == KErrNone ? ETrue : EFalse ; |
|
181 } |
|
182 |
|
183 void CSymbolList::GetSymbolModelL(const TDesC& aChar, RArray<TPoint>& aModel ,TUint& aUnicode, const THwrUdmRange& aRange ) |
|
184 { |
|
185 TInt idx = -1; |
|
186 if ( GetSymbolIndex( aChar, idx, aRange ) != KErrNone ) |
|
187 { |
|
188 User::Leave( KErrNotFound ); |
|
189 } |
|
190 // decides which list the idx belongs to |
|
191 CSymbol* symbol = NULL; |
|
192 if ( idx >= iSymbolList.Count() && iPresetModels ) |
|
193 { |
|
194 symbol = iPresetModels->iSymbolList[ idx - iSymbolList.Count() ]; |
|
195 } |
|
196 else |
|
197 { |
|
198 symbol = iSymbolList[idx]; |
|
199 } |
|
200 |
|
201 aUnicode = symbol->iPresetCode; |
|
202 aModel.Reset(); |
|
203 for ( int i = 0; i < symbol->iPointVectorLen; i++ ) |
|
204 { |
|
205 aModel.AppendL( symbol->iPointVector[i] ); |
|
206 } |
|
207 } |
|
208 |
|
209 void CSymbolList::DeleteSymbolModelL(const TDesC& aChar ,const THwrUdmRange& aRange ) |
|
210 { |
|
211 TInt idx = -1; |
|
212 if ( GetSymbolIndex( aChar, idx, aRange ) != KErrNone ) |
|
213 { |
|
214 User::Leave( KErrNotFound ); |
|
215 } |
|
216 if ( idx < iSymbolList.Count() ) |
|
217 { |
|
218 CSymbol* symbol = iSymbolList[idx]; |
|
219 iSymbolList.Remove( idx ); |
|
220 delete symbol; |
|
221 ExternalizeL(); |
|
222 } |
|
223 else if ( iPresetModels ) |
|
224 { |
|
225 // modify preset models |
|
226 TInt presetIdx = idx -iSymbolList.Count(); |
|
227 CSymbol& symbol = *iPresetModels->iSymbolList[presetIdx]; |
|
228 delete symbol.iSymbolName; |
|
229 symbol.iSymbolName = NULL; |
|
230 iPresetModels->ExternalizeL(); |
|
231 } |
|
232 } |
|
233 |
|
234 void CSymbolList::GetModelTextListL(RPointerArray<HBufC>& aList ,const THwrUdmRange& /*aRange*/ ) |
|
235 { |
|
236 for ( int i = 0; i < iSymbolList.Count(); i++ ) |
|
237 { |
|
238 aList.AppendL( (*iSymbolList[i]->iSymbolName).AllocL() ); |
|
239 } |
|
240 |
|
241 // then get preset shortcut models if have |
|
242 if ( iPresetModels ) |
|
243 { |
|
244 for ( int i = 0; i < iPresetModels->iSymbolList.Count(); i++ ) |
|
245 { |
|
246 // if the preset model is assigned to a shourtcut, then append it to the list |
|
247 CSymbol* symbol = iPresetModels->iSymbolList[i]; |
|
248 if ( symbol->iSymbolName && symbol->iPresetCode ) |
|
249 { |
|
250 aList.AppendL( symbol->iSymbolName->AllocL() ); |
|
251 } |
|
252 } |
|
253 } |
|
254 } |
|
255 |
|
256 void CSymbolList::ChangeSymbolTextL(const TDesC& aOldText, const TDesC& aNewText ,const THwrUdmRange& aRange ) |
|
257 { |
|
258 TInt idx = -1; |
|
259 if ( GetSymbolIndex( aOldText, idx, aRange ) != KErrNone ) |
|
260 { |
|
261 User::Leave( KErrNotFound ); |
|
262 } |
|
263 |
|
264 if ( aOldText.Compare( aNewText ) == 0 ) |
|
265 { |
|
266 return ; |
|
267 } |
|
268 |
|
269 if ( CheckSymbolModel( aNewText, aRange ) ) |
|
270 { |
|
271 User::Leave( KErrAlreadyExists ); |
|
272 } |
|
273 if ( idx < iSymbolList.Count() ) |
|
274 { |
|
275 CSymbol& symbol = *iSymbolList[idx]; |
|
276 delete symbol.iSymbolName; |
|
277 symbol.iSymbolName = aNewText.AllocL(); |
|
278 ExternalizeL(); |
|
279 } |
|
280 else if ( iPresetModels ) |
|
281 { |
|
282 // modify preset models |
|
283 TInt presetIdx = idx -iSymbolList.Count(); |
|
284 |
|
285 CSymbol& symbol = *iPresetModels->iSymbolList[presetIdx]; |
|
286 delete symbol.iSymbolName; |
|
287 symbol.iSymbolName = aNewText.AllocL(); ; |
|
288 |
|
289 iPresetModels->ExternalizeL(); |
|
290 } |
|
291 } |
|
292 |
|
293 void CSymbolList::GetModelIndexListL( RArray<TInt>& aList, const THwrUdmRange& aRange) |
|
294 { |
|
295 aList.Reset(); |
|
296 for ( int i = 0; i < iSymbolList.Count(); i++ ) |
|
297 { |
|
298 if ( iSymbolList[i]->Match( aRange ) ) |
|
299 { |
|
300 aList.AppendL( i ); |
|
301 } |
|
302 } |
|
303 |
|
304 // append the symbols to the array |
|
305 if ( iPresetModels ) |
|
306 { |
|
307 for ( int i = 0; i < iPresetModels->iSymbolList.Count(); i++ ) |
|
308 { |
|
309 if ( iPresetModels->iSymbolList[i]->iSymbolName && |
|
310 iPresetModels->iSymbolList[i]->Match( aRange ) ) |
|
311 { |
|
312 aList.AppendL( i + iSymbolList.Count() ); |
|
313 } |
|
314 } |
|
315 } |
|
316 } |
|
317 |
|
318 void CSymbolList::InternalizeL(const TDesC& /*aFile*/) |
|
319 { |
|
320 // Read the data file and construct the object |
|
321 iMutex.Wait(); |
|
322 CleanupStack::PushL(TCleanupItem(SignalMutex, &iMutex)); |
|
323 RFile readFile; |
|
324 TInt errCode = readFile.Open( iRfs, *iFilePath, EFileRead ); |
|
325 CleanupClosePushL( readFile ); |
|
326 |
|
327 if ( errCode == KErrNone ) |
|
328 { |
|
329 RFileReadStream readStream( readFile ); |
|
330 CleanupClosePushL( readStream ); |
|
331 |
|
332 // check file Type&Version |
|
333 TInt fileType = readStream.ReadInt32L(); |
|
334 TInt fileVersion = readStream.ReadInt32L(); |
|
335 if ( fileType != KUdmFileHeader || fileVersion != KUdmFileVersion ) |
|
336 { |
|
337 User::Leave( KErrGeneral ); |
|
338 } |
|
339 |
|
340 // construct each CSymbol object and add to array |
|
341 TInt symbolNO = readStream.ReadInt32L(); |
|
342 for ( int i = 0; i < symbolNO; i++ ) |
|
343 { |
|
344 CSymbol* symbol = CSymbol::NewL( readStream ); |
|
345 iSymbolList.AppendL( symbol ); |
|
346 } |
|
347 |
|
348 // check wether the file is valid. |
|
349 if ( symbolNO != iSymbolList.Count() ) |
|
350 { |
|
351 User::Leave( KErrGeneral ); |
|
352 } |
|
353 CleanupStack::PopAndDestroy( &readStream ); |
|
354 } |
|
355 CleanupStack::PopAndDestroy( &readFile ); |
|
356 CleanupStack::PopAndDestroy(); // TCleanupItem(SignalMutex, &iMutex) |
|
357 } |
|
358 |
|
359 void CSymbolList::ExternalizeL() |
|
360 { |
|
361 // write a temp file and use replace. |
|
362 iMutex.Wait(); |
|
363 CleanupStack::PushL(TCleanupItem(SignalMutex, &iMutex)); |
|
364 |
|
365 RFile fileTemp; |
|
366 CleanupClosePushL( fileTemp ); |
|
367 |
|
368 //HBufC* tempFile = HBufC::NewLC( iFilePath->Length() + 3 ); |
|
369 //TPtr tempFilePtr( tempFile->Des() ); |
|
370 //tempFilePtr.Copy( *iFilePath ); |
|
371 //tempFilePtr.Append( KTempPathFix ); |
|
372 |
|
373 |
|
374 TParse fileParse; |
|
375 fileParse.Set( *iFilePath, NULL, NULL ); |
|
376 |
|
377 TFileName tempName; |
|
378 iRfs.MkDirAll( fileParse.DriveAndPath() ); |
|
379 User::LeaveIfError ( fileTemp.Temp( iRfs, fileParse.DriveAndPath(), tempName, EFileWrite ) ); |
|
380 |
|
381 RFileWriteStream writeStream( fileTemp ); |
|
382 CleanupClosePushL( writeStream ); |
|
383 |
|
384 // write file type&version |
|
385 writeStream.WriteInt32L( KUdmFileHeader ); |
|
386 writeStream.WriteInt32L( KUdmFileVersion ); |
|
387 |
|
388 writeStream.WriteInt32L( iSymbolList.Count() ); |
|
389 for ( int i = 0; i < iSymbolList.Count(); i++ ) |
|
390 { |
|
391 iSymbolList[i]->ExternalizeL( writeStream ); |
|
392 } |
|
393 writeStream.CommitL(); |
|
394 CleanupStack::PopAndDestroy( &writeStream ); |
|
395 CleanupStack::PopAndDestroy( &fileTemp ); |
|
396 |
|
397 iRfs.Delete( *iFilePath ) ; |
|
398 User::LeaveIfError( iRfs.Rename( tempName ,*iFilePath ) ); |
|
399 |
|
400 CleanupStack::PopAndDestroy(); // TCleanupItem(SignalMutex, &iMutex) |
|
401 } |
|
402 |
|
403 CSymbolList::CSymbolList( CSymbolList* aPresetList ) |
|
404 { |
|
405 iPresetModels = aPresetList; |
|
406 iFilePath = NULL; |
|
407 } |
|
408 |
|
409 void CSymbolList::ConstructL(const TDesC& aFilePath) |
|
410 { |
|
411 User::LeaveIfError( iRfs.Connect() ); |
|
412 iFilePath = aFilePath.AllocL(); |
|
413 |
|
414 // create mutex |
|
415 TInt error( KErrNotFound ); |
|
416 while( error == KErrNotFound ) |
|
417 { |
|
418 error = iMutex.CreateGlobal( KSymbolMutexName ); |
|
419 if( error != KErrAlreadyExists ) |
|
420 { |
|
421 break; |
|
422 } |
|
423 error = iMutex.OpenGlobal( KSymbolMutexName ); |
|
424 } |
|
425 User::LeaveIfError( error ); |
|
426 |
|
427 InternalizeL( aFilePath ); |
|
428 } |
|
429 |
|
430 CSymbolList::~CSymbolList() |
|
431 { |
|
432 delete iFilePath; |
|
433 for ( int i = 0; i < iSymbolList.Count(); i++ ) |
|
434 { |
|
435 delete iSymbolList[i]; |
|
436 } |
|
437 iSymbolList.Close(); |
|
438 iRfs.Close(); |
|
439 iMutex.Close(); |
|
440 } |
|
441 |
|
442 TInt CSymbolList::GetSymbolIndex(const TDesC& aText, TInt& aIndex, const THwrUdmRange& aRange ) |
|
443 { |
|
444 // using quick search later. |
|
445 for ( int i = 0; i < iSymbolList.Count(); i++ ) |
|
446 { |
|
447 if ( iSymbolList[i]->iSymbolName->Compare( aText ) == 0 && iSymbolList[i]->Match( aRange )) |
|
448 { |
|
449 aIndex = i; |
|
450 return KErrNone; |
|
451 } |
|
452 } |
|
453 |
|
454 // then find in preset model |
|
455 if ( iPresetModels ) |
|
456 { |
|
457 for ( int i = 0; i < iPresetModels->iSymbolList.Count(); i++ ) |
|
458 { |
|
459 CSymbol& symbol = *iPresetModels->iSymbolList[i]; |
|
460 // not assigned |
|
461 if ( !symbol.iSymbolName ) |
|
462 { |
|
463 continue; |
|
464 } |
|
465 |
|
466 if ( symbol.iSymbolName->Compare( aText ) == 0 && symbol.Match( aRange )) |
|
467 { |
|
468 aIndex = iSymbolList.Count() + i; |
|
469 return KErrNone; |
|
470 } |
|
471 } |
|
472 } |
|
473 |
|
474 return KErrNotFound; |
|
475 } |
|
476 |
|
477 TInt CSymbolList::GetSymbolDataRef( TInt aIndex, TPtrC& aSymbolName,RArray<TPoint>& aModel, TInt& aHelpLine, TInt& aBaseLine ) |
|
478 { |
|
479 |
|
480 CSymbol* symbol = NULL; |
|
481 if ( aIndex >= 0 && aIndex < iSymbolList.Count() ) |
|
482 { |
|
483 symbol = iSymbolList[aIndex]; |
|
484 } |
|
485 else if ( iPresetModels && aIndex >= iSymbolList.Count() |
|
486 && aIndex < iSymbolList.Count() + iPresetModels->iSymbolList.Count() ) |
|
487 { |
|
488 symbol = iPresetModels->iSymbolList[ aIndex - iSymbolList.Count() ]; |
|
489 } |
|
490 else |
|
491 { |
|
492 return KErrNotFound; |
|
493 } |
|
494 |
|
495 aModel.Reset(); |
|
496 aModel = RArray<TPoint>( sizeof(TPoint), symbol->iPointVector, symbol->iPointVectorLen ); |
|
497 aHelpLine = symbol->iHelpLine; |
|
498 aBaseLine = symbol->iBaseLine; |
|
499 aSymbolName.Set( *symbol->iSymbolName ); |
|
500 |
|
501 return KErrNone; |
|
502 } |
|
503 |
|
504 |
|
505 void CSymbolList::SavePresetShortcutL( TUint aUnicode, const TDesC& aShortcut ) |
|
506 { |
|
507 User::LeaveIfNull( iPresetModels ); |
|
508 |
|
509 for ( int i = 0; i < iPresetModels->iSymbolList.Count(); i ++ ) |
|
510 { |
|
511 CSymbol* sym = iPresetModels->iSymbolList[i]; |
|
512 if ( aUnicode == sym->iPresetCode ) |
|
513 { |
|
514 if ( sym-> iSymbolName ) |
|
515 { |
|
516 delete sym->iSymbolName; |
|
517 sym->iSymbolName = NULL; |
|
518 } |
|
519 |
|
520 if ( aShortcut.Length() > 0 ) |
|
521 { |
|
522 sym->iSymbolName = aShortcut.AllocL(); |
|
523 } |
|
524 iPresetModels->ExternalizeL(); |
|
525 return; |
|
526 } |
|
527 } |
|
528 |
|
529 User::Leave( KErrNotFound ); |
|
530 } |
|
531 |
|
532 void CSymbolList::GetAllPresetSymbolsL( RArray<TUint>& aPresets ) |
|
533 { |
|
534 User::LeaveIfNull( iPresetModels ); |
|
535 |
|
536 for ( int i = 0; i < iPresetModels->iSymbolList.Count(); i ++ ) |
|
537 { |
|
538 aPresets.AppendL( iPresetModels->iSymbolList[i]->iPresetCode ); |
|
539 } |
|
540 } |
|
541 |
|
542 void CSymbolList::GetPresetSymbolByUnicodeL( TUint aUnicode, RArray<TPoint>& aModel, TDes& aShortcut ) |
|
543 { |
|
544 User::LeaveIfNull( iPresetModels ); |
|
545 |
|
546 for ( int i = 0; i < iPresetModels->iSymbolList.Count(); i ++ ) |
|
547 { |
|
548 CSymbol* sym = iPresetModels->iSymbolList[i]; |
|
549 if ( aUnicode == sym->iPresetCode ) |
|
550 { |
|
551 aShortcut = KNullDesC; |
|
552 if ( sym->iSymbolName ) |
|
553 { |
|
554 aShortcut.Copy( *sym->iSymbolName ); |
|
555 } |
|
556 |
|
557 for ( int i = 0; i < sym->iPointVectorLen; i++ ) |
|
558 { |
|
559 aModel.AppendL( sym->iPointVector[i] ); |
|
560 } |
|
561 return; |
|
562 } |
|
563 } |
|
564 User::Leave( KErrNotFound ); |
|
565 } |
|
566 |
|
567 void CSymbolList::SignalMutex(TAny* aMutex) |
|
568 { |
|
569 STATIC_CAST( RMutex*, aMutex )->Signal(); |
|
570 } |
|
571 |
|
572 |
|
573 |
|
574 |
|
575 |
|
576 |
|
577 |