1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 // MARKER(update_precomp.py): autogen include statement, do not remove 25 #include "precompiled_sw.hxx" 26 27 #include "fldbas.hxx" // fuer FieldType 28 #include <fmtfld.hxx> 29 #include <txtfld.hxx> 30 #include <txtannotationfld.hxx> 31 #include <docufld.hxx> 32 #include <doc.hxx> 33 34 #include "reffld.hxx" 35 #include "ddefld.hxx" 36 #include "usrfld.hxx" 37 #include "expfld.hxx" 38 #include "swfont.hxx" // fuer GetFldsColor 39 #include "ndtxt.hxx" // SwTxtNode 40 #include "calc.hxx" // Update fuer UserFields 41 #include "hints.hxx" 42 #include <IDocumentFieldsAccess.hxx> 43 #include <fieldhint.hxx> 44 #include <svl/smplhint.hxx> 45 46 TYPEINIT3( SwFmtFld, SfxPoolItem, SwClient,SfxBroadcaster) 47 TYPEINIT1(SwFmtFldHint, SfxHint); 48 49 /**************************************************************************** 50 * 51 * class SwFmtFld 52 * 53 ****************************************************************************/ 54 55 // constructor for default item in attribute-pool 56 SwFmtFld::SwFmtFld( sal_uInt16 nWhich ) 57 : SfxPoolItem( nWhich ) 58 , SwClient() 59 , SfxBroadcaster() 60 , mpField( NULL ) 61 , mpTxtFld( NULL ) 62 { 63 } 64 65 SwFmtFld::SwFmtFld( const SwField &rFld ) 66 : SfxPoolItem( RES_TXTATR_FIELD ) 67 , SwClient( rFld.GetTyp() ) 68 , SfxBroadcaster() 69 , mpField( rFld.CopyField() ) 70 , mpTxtFld( NULL ) 71 { 72 if ( GetField()->GetTyp()->Which() == RES_INPUTFLD ) 73 { 74 // input field in-place editing 75 SetWhich( RES_TXTATR_INPUTFIELD ); 76 dynamic_cast<SwInputField*>(GetField())->SetFmtFld( *this ); 77 } 78 else if ( GetField()->GetTyp()->Which() == RES_POSTITFLD ) 79 { 80 // text annotation field 81 SetWhich( RES_TXTATR_ANNOTATION ); 82 } 83 } 84 85 // #i24434# 86 // Since Items are used in ItemPool and in default constructed ItemSets with 87 // full pool range, all items need to be clonable. Thus, this one needed to be 88 // corrected 89 SwFmtFld::SwFmtFld( const SwFmtFld& rAttr ) 90 : SfxPoolItem( RES_TXTATR_FIELD ) 91 , SwClient() 92 , SfxBroadcaster() 93 , mpField( NULL ) 94 , mpTxtFld( NULL ) 95 { 96 if ( rAttr.GetField() ) 97 { 98 rAttr.GetField()->GetTyp()->Add(this); 99 mpField = rAttr.GetField()->CopyField(); 100 if ( GetField()->GetTyp()->Which() == RES_INPUTFLD ) 101 { 102 // input field in-place editing 103 SetWhich( RES_TXTATR_INPUTFIELD ); 104 dynamic_cast<SwInputField*>(GetField())->SetFmtFld( *this ); 105 } 106 else if ( GetField()->GetTyp()->Which() == RES_POSTITFLD ) 107 { 108 // text annotation field 109 SetWhich( RES_TXTATR_ANNOTATION ); 110 } 111 } 112 } 113 114 SwFmtFld::~SwFmtFld() 115 { 116 SwFieldType* pType = mpField ? mpField->GetTyp() : 0; 117 118 if (pType && pType->Which() == RES_DBFLD) 119 pType = 0; // DB-Feldtypen zerstoeren sich selbst 120 121 Broadcast( SwFmtFldHint( this, SWFMTFLD_REMOVED ) ); 122 delete mpField; 123 124 // bei einige FeldTypen muessen wir den FeldTypen noch loeschen 125 if( pType && pType->IsLastDepend() ) 126 { 127 sal_Bool bDel = sal_False; 128 switch( pType->Which() ) 129 { 130 case RES_USERFLD: 131 bDel = ((SwUserFieldType*)pType)->IsDeleted(); 132 break; 133 134 case RES_SETEXPFLD: 135 bDel = ((SwSetExpFieldType*)pType)->IsDeleted(); 136 break; 137 138 case RES_DDEFLD: 139 bDel = ((SwDDEFieldType*)pType)->IsDeleted(); 140 break; 141 } 142 143 if( bDel ) 144 { 145 // vorm loeschen erstmal austragen 146 pType->Remove( this ); 147 delete pType; 148 } 149 } 150 } 151 152 void SwFmtFld::RegisterToFieldType( SwFieldType& rType ) 153 { 154 rType.Add(this); 155 } 156 157 158 // #111840# 159 void SwFmtFld::SetField(SwField * _pField) 160 { 161 if (NULL != mpField) 162 delete mpField; 163 164 mpField = _pField; 165 if ( GetField()->GetTyp()->Which() == RES_INPUTFLD ) 166 { 167 dynamic_cast<SwInputField* >(GetField())->SetFmtFld( *this ); 168 } 169 Broadcast( SwFmtFldHint( this, SWFMTFLD_CHANGED ) ); 170 } 171 172 void SwFmtFld::SetTxtFld( SwTxtFld& rTxtFld ) 173 { 174 mpTxtFld = &rTxtFld; 175 } 176 177 void SwFmtFld::ClearTxtFld() 178 { 179 mpTxtFld = NULL; 180 } 181 182 int SwFmtFld::operator==( const SfxPoolItem& rAttr ) const 183 { 184 ASSERT( SfxPoolItem::operator==( rAttr ), "keine gleichen Attribute" ); 185 return ( ( mpField && ((SwFmtFld&)rAttr).GetField() 186 && mpField->GetTyp() == ((SwFmtFld&)rAttr).GetField()->GetTyp() 187 && mpField->GetFormat() == ((SwFmtFld&)rAttr).GetField()->GetFormat() ) ) 188 || ( !mpField && !((SwFmtFld&)rAttr).GetField() ); 189 } 190 191 SfxPoolItem* SwFmtFld::Clone( SfxItemPool* ) const 192 { 193 return new SwFmtFld( *this ); 194 } 195 196 void SwFmtFld::SwClientNotify( const SwModify&, const SfxHint& rHint ) 197 { 198 if( !mpTxtFld ) 199 return; 200 201 const SwFieldHint* pHint = dynamic_cast<const SwFieldHint*>( &rHint ); 202 if ( pHint ) 203 { 204 // replace field content by text 205 SwPaM* pPaM = pHint->GetPaM(); 206 SwDoc* pDoc = pPaM->GetDoc(); 207 const SwTxtNode& rTxtNode = mpTxtFld->GetTxtNode(); 208 pPaM->GetPoint()->nNode = rTxtNode; 209 pPaM->GetPoint()->nContent.Assign( (SwTxtNode*)&rTxtNode, *mpTxtFld->GetStart() ); 210 211 String const aEntry( GetField()->ExpandField( pDoc->IsClipBoard() ) ); 212 pPaM->SetMark(); 213 pPaM->Move( fnMoveForward ); 214 pDoc->DeleteRange( *pPaM ); 215 pDoc->InsertString( *pPaM, aEntry ); 216 } 217 } 218 219 void SwFmtFld::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) 220 { 221 if ( mpTxtFld == NULL ) 222 return; 223 224 if( pNew != NULL 225 && pNew->Which() == RES_OBJECTDYING ) 226 { 227 // don't do anything, especially not expand! 228 return; 229 } 230 231 SwTxtNode* pTxtNd = (SwTxtNode*) &mpTxtFld->GetTxtNode(); 232 ASSERT( pTxtNd, "wo ist denn mein Node?" ); 233 if ( pNew ) 234 { 235 switch (pNew->Which()) 236 { 237 case RES_TXTATR_FLDCHG: 238 // "Farbe hat sich geaendert !" 239 // this, this fuer "nur Painten" 240 pTxtNd->ModifyNotification( this, this ); 241 return; 242 243 case RES_REFMARKFLD_UPDATE: 244 // GetReferenz-Felder aktualisieren 245 if ( RES_GETREFFLD == GetField()->GetTyp()->Which() ) 246 { 247 dynamic_cast<SwGetRefField*>(GetField())->UpdateField( mpTxtFld ); 248 } 249 break; 250 251 case RES_DOCPOS_UPDATE: 252 // Je nach DocPos aktualisieren (SwTxtFrm::Modify()) 253 pTxtNd->ModifyNotification( pNew, this ); 254 return; 255 256 case RES_ATTRSET_CHG: 257 case RES_FMT_CHG: 258 pTxtNd->ModifyNotification( pOld, pNew ); 259 return; 260 261 default: 262 break; 263 } 264 } 265 266 switch (GetField()->GetTyp()->Which()) 267 { 268 case RES_HIDDENPARAFLD: 269 if ( !pOld || RES_HIDDENPARA_PRINT != pOld->Which() ) 270 break; 271 case RES_DBSETNUMBERFLD: 272 case RES_DBNUMSETFLD: 273 case RES_DBNEXTSETFLD: 274 case RES_DBNAMEFLD: 275 pTxtNd->ModifyNotification( 0, pNew ); 276 return; 277 } 278 279 if ( RES_USERFLD == GetField()->GetTyp()->Which() ) 280 { 281 SwUserFieldType* pType = (SwUserFieldType*) GetField()->GetTyp(); 282 if ( !pType->IsValid() ) 283 { 284 SwCalc aCalc( *pTxtNd->GetDoc() ); 285 pType->GetValue( aCalc ); 286 } 287 } 288 289 const bool bForceNotify = (pOld == NULL) && (pNew == NULL); 290 mpTxtFld->ExpandTxtFld( bForceNotify ); 291 } 292 293 sal_Bool SwFmtFld::GetInfo( SfxPoolItem& rInfo ) const 294 { 295 const SwTxtNode* pTxtNd; 296 if( RES_AUTOFMT_DOCNODE != rInfo.Which() || 297 !mpTxtFld || 0 == ( pTxtNd = mpTxtFld->GetpTxtNode() ) || 298 &pTxtNd->GetNodes() != ((SwAutoFmtGetDocNode&)rInfo).pNodes ) 299 return sal_True; 300 301 ((SwAutoFmtGetDocNode&)rInfo).pCntntNode = pTxtNd; 302 return sal_False; 303 } 304 305 306 bool SwFmtFld::IsFldInDoc() const 307 { 308 return mpTxtFld != NULL 309 && mpTxtFld->IsFldInDoc(); 310 } 311 312 sal_Bool SwFmtFld::IsProtect() const 313 { 314 return mpTxtFld != NULL 315 && mpTxtFld->GetpTxtNode() != NULL 316 && mpTxtFld->GetpTxtNode()->IsProtect(); 317 } 318 319 320 321 322 SwTxtFld::SwTxtFld( 323 SwFmtFld & rAttr, 324 xub_StrLen const nStartPos, 325 const bool bIsClipboardDoc ) 326 : SwTxtAttr( rAttr, nStartPos ) 327 , m_aExpand( rAttr.GetField()->ExpandField( bIsClipboardDoc ) ) 328 , m_pTxtNode( NULL ) 329 { 330 rAttr.SetTxtFld( *this ); 331 SetHasDummyChar(true); 332 } 333 334 SwTxtFld::~SwTxtFld( ) 335 { 336 SwFmtFld & rFmtFld( static_cast<SwFmtFld &>(GetAttr()) ); 337 if ( this == rFmtFld.GetTxtFld() ) 338 { 339 rFmtFld.ClearTxtFld(); 340 } 341 } 342 343 344 bool SwTxtFld::IsFldInDoc() const 345 { 346 return GetpTxtNode() != NULL 347 && GetpTxtNode()->GetNodes().IsDocNodes(); 348 } 349 350 void SwTxtFld::ExpandTxtFld( const bool bForceNotify ) const 351 { 352 ASSERT( m_pTxtNode, "SwTxtFld: where is my TxtNode?" ); 353 354 const SwField* pFld = GetFmtFld().GetField(); 355 const XubString aNewExpand( pFld->ExpandField(m_pTxtNode->GetDoc()->IsClipBoard()) ); 356 357 if ( !bForceNotify && 358 aNewExpand == m_aExpand ) 359 { 360 // Bei Seitennummernfeldern 361 const sal_uInt16 nWhich = pFld->GetTyp()->Which(); 362 if ( RES_CHAPTERFLD != nWhich 363 && RES_PAGENUMBERFLD != nWhich 364 && RES_REFPAGEGETFLD != nWhich 365 // Page count fields to not use aExpand during formatting, 366 // therefore an invalidation of the text frame has to be triggered even if aNewExpand == aExpand: 367 && ( RES_DOCSTATFLD != nWhich || DS_PAGE != static_cast<const SwDocStatField*>(pFld)->GetSubType() ) 368 && ( RES_GETEXPFLD != nWhich || ((SwGetExpField*)pFld)->IsInBodyTxt() ) ) 369 { 370 if( m_pTxtNode->CalcHiddenParaField() ) 371 { 372 m_pTxtNode->ModifyNotification( 0, 0 ); 373 } 374 return; 375 } 376 } 377 378 m_aExpand = aNewExpand; 379 380 const_cast<SwTxtFld*>(this)->NotifyContentChange( const_cast<SwFmtFld&>(GetFmtFld()) ); 381 } 382 383 384 void SwTxtFld::CopyTxtFld( SwTxtFld *pDest ) const 385 { 386 ASSERT( m_pTxtNode, "SwTxtFld: where is my TxtNode?" ); 387 ASSERT( pDest->m_pTxtNode, "SwTxtFld: where is pDest's TxtNode?" ); 388 389 IDocumentFieldsAccess* pIDFA = m_pTxtNode->getIDocumentFieldsAccess(); 390 IDocumentFieldsAccess* pDestIDFA = pDest->m_pTxtNode->getIDocumentFieldsAccess(); 391 392 SwFmtFld& rDestFmtFld = (SwFmtFld&)pDest->GetFmtFld(); 393 const sal_uInt16 nFldWhich = rDestFmtFld.GetField()->GetTyp()->Which(); 394 395 if( pIDFA != pDestIDFA ) 396 { 397 // Die Hints stehen in unterschiedlichen Dokumenten, 398 // der Feldtyp muss im neuen Dokument angemeldet werden. 399 // Z.B: Kopieren ins ClipBoard. 400 SwFieldType* pFldType; 401 if( nFldWhich != RES_DBFLD 402 && nFldWhich != RES_USERFLD 403 && nFldWhich != RES_SETEXPFLD 404 && nFldWhich != RES_DDEFLD 405 && RES_AUTHORITY != nFldWhich ) 406 { 407 pFldType = pDestIDFA->GetSysFldType( nFldWhich ); 408 } 409 else 410 { 411 pFldType = pDestIDFA->InsertFldType( *rDestFmtFld.GetField()->GetTyp() ); 412 } 413 414 // Sonderbehandlung fuer DDE-Felder 415 if( RES_DDEFLD == nFldWhich ) 416 { 417 if( rDestFmtFld.GetTxtFld() ) 418 { 419 ((SwDDEFieldType*)rDestFmtFld.GetField()->GetTyp())->DecRefCnt(); 420 } 421 ((SwDDEFieldType*)pFldType)->IncRefCnt(); 422 } 423 424 ASSERT( pFldType, "unbekannter FieldType" ); 425 pFldType->Add( &rDestFmtFld ); // ummelden 426 rDestFmtFld.GetField()->ChgTyp( pFldType ); 427 } 428 429 // Expressionfelder Updaten 430 if( nFldWhich == RES_SETEXPFLD 431 || nFldWhich == RES_GETEXPFLD 432 || nFldWhich == RES_HIDDENTXTFLD ) 433 { 434 SwTxtFld* pFld = (SwTxtFld*)this; 435 pDestIDFA->UpdateExpFlds( pFld, true ); 436 } 437 // Tabellenfelder auf externe Darstellung 438 else if( RES_TABLEFLD == nFldWhich 439 && ((SwTblField*)rDestFmtFld.GetField())->IsIntrnlName() ) 440 { 441 // erzeuge aus der internen (fuer CORE) die externe (fuer UI) Formel 442 const SwTableNode* pTblNd = m_pTxtNode->FindTableNode(); 443 if( pTblNd ) // steht in einer Tabelle 444 ((SwTblField*)rDestFmtFld.GetField())->PtrToBoxNm( &pTblNd->GetTable() ); 445 } 446 } 447 448 449 void SwTxtFld::NotifyContentChange(SwFmtFld& rFmtFld) 450 { 451 //if not in undo section notify the change 452 if (m_pTxtNode && m_pTxtNode->GetNodes().IsDocNodes()) 453 { 454 m_pTxtNode->ModifyNotification(0, &rFmtFld); 455 } 456 } 457 458 459 /*static*/ 460 void SwTxtFld::GetPamForTxtFld( 461 const SwTxtFld& rTxtFld, 462 boost::shared_ptr< SwPaM >& rPamForTxtFld ) 463 { 464 if ( rTxtFld.GetpTxtNode() == NULL ) 465 { 466 ASSERT( false, "<SwTxtFld::GetPamForField> - missing <SwTxtNode>" ); 467 return; 468 } 469 470 const SwTxtNode& rTxtNode = rTxtFld.GetTxtNode(); 471 472 rPamForTxtFld.reset( new SwPaM( rTxtNode, 473 ( (rTxtFld.End() != NULL) ? *(rTxtFld.End()) : ( *(rTxtFld.GetStart()) + 1 ) ), 474 rTxtNode, 475 *(rTxtFld.GetStart()) ) ); 476 477 } 478 479 480 /*static*/ 481 void SwTxtFld::DeleteTxtFld( const SwTxtFld& rTxtFld ) 482 { 483 if ( rTxtFld.GetpTxtNode() != NULL ) 484 { 485 boost::shared_ptr< SwPaM > pPamForTxtFld; 486 GetPamForTxtFld( rTxtFld, pPamForTxtFld ); 487 if ( pPamForTxtFld.get() != NULL ) 488 { 489 rTxtFld.GetTxtNode().GetDoc()->DeleteAndJoin( *pPamForTxtFld ); 490 } 491 } 492 } 493 494 495 496 // input field in-place editing 497 SwTxtInputFld::SwTxtInputFld( 498 SwFmtFld & rAttr, 499 xub_StrLen const nStart, 500 xub_StrLen const nEnd, 501 const bool bIsClipboardDoc ) 502 503 : SwTxtFld( rAttr, nStart, bIsClipboardDoc ) 504 , m_nEnd( nEnd ) 505 , m_bLockNotifyContentChange( false ) 506 { 507 SetHasDummyChar( false ); 508 SetHasContent( true ); 509 510 SetDontExpand( true ); 511 SetLockExpandFlag( true ); 512 SetDontExpandStartAttr( true ); 513 514 SetNesting( true ); 515 } 516 517 SwTxtInputFld::~SwTxtInputFld() 518 { 519 } 520 521 xub_StrLen* SwTxtInputFld::GetEnd() 522 { 523 return &m_nEnd; 524 } 525 526 527 void SwTxtInputFld::LockNotifyContentChange() 528 { 529 m_bLockNotifyContentChange = true; 530 } 531 532 533 void SwTxtInputFld::UnlockNotifyContentChange() 534 { 535 m_bLockNotifyContentChange = false; 536 } 537 538 539 void SwTxtInputFld::NotifyContentChange( SwFmtFld& rFmtFld ) 540 { 541 if ( !m_bLockNotifyContentChange ) 542 { 543 LockNotifyContentChange(); 544 545 SwTxtFld::NotifyContentChange( rFmtFld ); 546 UpdateTextNodeContent( GetFieldContent() ); 547 548 UnlockNotifyContentChange(); 549 } 550 } 551 552 const String SwTxtInputFld::GetFieldContent() const 553 { 554 return GetFmtFld().GetField()->ExpandField(false); 555 } 556 557 void SwTxtInputFld::UpdateFieldContent() 558 { 559 if ( IsFldInDoc() 560 && (*GetStart()) != (*End()) ) 561 { 562 ASSERT( (*End()) - (*GetStart()) >= 2, 563 "<SwTxtInputFld::UpdateFieldContent()> - Are CH_TXT_ATR_INPUTFIELDSTART and/or CH_TXT_ATR_INPUTFIELDEND missing?" ); 564 // skip CH_TXT_ATR_INPUTFIELDSTART character 565 const xub_StrLen nIdx = (*GetStart()) + 1; 566 // skip CH_TXT_ATR_INPUTFIELDEND character 567 const xub_StrLen nLen = static_cast<xub_StrLen>(std::max( 0, ( (*End()) - 1 - nIdx ) )); 568 const String aNewFieldContent = GetTxtNode().GetExpandTxt( nIdx, nLen ); 569 570 const SwInputField* pInputFld = dynamic_cast<const SwInputField*>(GetFmtFld().GetField()); 571 ASSERT( pInputFld != NULL, 572 "<SwTxtInputFld::GetContent()> - Missing <SwInputFld> instance!" ); 573 if ( pInputFld != NULL ) 574 { 575 const_cast<SwInputField*>(pInputFld)->applyFieldContent( aNewFieldContent ); 576 } 577 } 578 } 579 580 void SwTxtInputFld::UpdateTextNodeContent( const String& rNewContent ) 581 { 582 if ( !IsFldInDoc() ) 583 { 584 ASSERT( false, "<SwTxtInputFld::UpdateTextNodeContent(..)> - misusage as Input Field is not in document content." ); 585 return; 586 } 587 588 ASSERT( (*End()) - (*GetStart()) >= 2, 589 "<SwTxtInputFld::UpdateTextNodeContent(..)> - Are CH_TXT_ATR_INPUTFIELDSTART and/or CH_TXT_ATR_INPUTFIELDEND missing?" ); 590 // skip CH_TXT_ATR_INPUTFIELDSTART character 591 const xub_StrLen nIdx = (*GetStart()) + 1; 592 // skip CH_TXT_ATR_INPUTFIELDEND character 593 const xub_StrLen nDelLen = static_cast<xub_StrLen>(std::max( 0, ( (*End()) - 1 - nIdx ) )); 594 SwIndex aIdx( &GetTxtNode(), nIdx ); 595 GetTxtNode().ReplaceText( aIdx, nDelLen, rNewContent ); 596 } 597 598 599 600 601 // text annotation field 602 SwTxtAnnotationFld::SwTxtAnnotationFld( 603 SwFmtFld & rAttr, 604 xub_StrLen const nStart, 605 const bool bIsClipboardDoc ) 606 : SwTxtFld( rAttr, nStart, bIsClipboardDoc ) 607 { 608 } 609 610 SwTxtAnnotationFld::~SwTxtAnnotationFld() 611 { 612 } 613 614 615 ::sw::mark::IMark* SwTxtAnnotationFld::GetAnnotationMark( 616 SwDoc* pDoc ) const 617 { 618 const SwPostItField* pPostItField = dynamic_cast<const SwPostItField*>(GetFmtFld().GetField()); 619 ASSERT( pPostItField != NULL, "<SwTxtAnnotationFld::GetAnnotationMark()> - field missing" ); 620 if ( pPostItField == NULL ) 621 { 622 return NULL; 623 } 624 625 if ( pDoc == NULL ) 626 { 627 pDoc = static_cast<const SwPostItFieldType*>(pPostItField->GetTyp())->GetDoc(); 628 } 629 ASSERT( pDoc != NULL, "<SwTxtAnnotationFld::GetAnnotationMark()> - missing document" ); 630 if ( pDoc == NULL ) 631 { 632 return NULL; 633 } 634 635 IDocumentMarkAccess* pMarksAccess = pDoc->getIDocumentMarkAccess(); 636 IDocumentMarkAccess::const_iterator_t pMark = pMarksAccess->findAnnotationMark( pPostItField->GetName() ); 637 return pMark != pMarksAccess->getAnnotationMarksEnd() 638 ? pMark->get() 639 : NULL; 640 } 641 642